Unlocking Smarter Workflows: A Deep Dive into Our New Memory System
We just completed a major milestone: a robust memory system for our workflow engine. This post covers the new MemoryPicker UI, critical bug fixes, and the journey to a more intelligent application.
Ever felt like your development tools forget everything the moment you close a tab? We did too. That's why we've been hard at work building a sophisticated memory system right into our workflow engine. This past session was a massive leap forward, bringing us to a fully functional, end-to-end memory integration. We're talking about a system that remembers, suggests, and injects context right when you need it most.
Let's unpack the journey, the challenges we overcame, and what's next for making our workflows truly intelligent.
The Heart of Recall: Introducing the MemoryPicker UI
The core of our new memory system is the MemoryPicker component. Imagine being able to instantly recall past insights, decisions, and contextual information right when you're defining a new workflow step. That's the power the MemoryPicker brings.
Located at src/components/workflow/memory-picker.tsx, this UI is designed to be intuitive and powerful:
- Search Bar: Quickly find relevant memories.
- Category Filter Chips: Narrow down results by specific types of insights.
- Severity-Colored Badges: Visually prioritize or categorize memories at a glance.
- Expandable Details/Suggestions: Dive deeper into a memory without leaving the picker.
{{memory}}Injection Preview: See exactly how a selected memory will be injected into your workflow prompt.
Under the hood, it efficiently queries our memory.listInsights tRPC endpoint, ensuring a snappy and type-safe experience.
Seamless Integration & Backend Refinements
Wiring the MemoryPicker into our workflow creation page (src/app/(dashboard)/dashboard/workflows/new/page.tsx) was a crucial step. We introduced a memoryIds state to track selected insights and hooked it directly into our create mutation, whose schema was already prepared for this new field. It's incredibly satisfying to see the frontend selections flow directly into the backend.
But it wasn't just about new features; we also took the opportunity to refine existing logic:
Squashing the SaveInsightsDialog Bug
A subtle but critical bug was lurking in our SaveInsightsDialog. After a review step, key points extracted by our extractKeyPoints() function were appearing without an action field. This meant our dialog's filter (kp.action === "keep" || kp.action === "edit") incorrectly evaluated to false for all valid points, preventing them from being displayed as saveable.
The fix was elegant: updating our filter logic to !kp.action || kp.action === "keep" || kp.action === "edit" in both save-insights-dialog.tsx and workflows/[id]/page.tsx. This change correctly treats key points with an undefined action as saveable, ensuring no valuable insights are missed.
Consolidating saveInsights Mutations
We noticed some duplication in our saveInsights mutation logic across different parts of the application. To ensure a single source of truth and leverage richer validation, we streamlined this by removing the duplicate from src/server/trpc/routers/workflows.ts. Now, all insight saving flows through the more robust memory.saveInsights endpoint, with explicit stepLabel and projectId parameters for better context.
Enhancing User Experience: Collapsible Context Sections
Beyond core functionality, we're always looking to improve the user experience. Our workflow creation page can get quite dense with context sections like Consolidations, Personas, and Docs. To combat visual clutter and improve focus, we introduced a new CollapsibleSection component (f998772).
Now, these sections are collapsed by default, and a badge in the header conveniently shows the selection count (e.g., number of personas chosen). This small but impactful change significantly declutters the UI, allowing users to focus on the most relevant information without losing access to deeper context.
The Ultimate Test: End-to-End Validation
The true test of any complex feature is seeing it work seamlessly from start to finish. We ran a full end-to-end test, mimicking a real user journey:
- Created a new workflow.
- Ran through the "Analyze" step.
- Proceeded to "Design Features."
- Completed the "Review" step, which extracted 10 key points.
- Approved the review.
- The
SaveInsightsDialogappeared as expected! - Saved the 10 insights to our
workflow_insightstable. - Returned to the workflow creation page, and voilà! The MemoryPicker proudly displayed our newly saved insights.
That moment when all the pieces clicked, and the insights flowed from extraction to storage to retrieval, was incredibly satisfying. Our dev server is humming along on http://localhost:3000, ready for the next phase.
Lessons Learned & Future-Proofing
Even with successful feature delivery, the development journey always presents valuable lessons:
The Disappearing Dialog Act
- Challenge: Clicking "Approve & Continue" after a review step, only for the
SaveInsightsDialogto not appear, immediately proceeding to the next step. - Root Cause: As detailed above, our
extractKeyPoints()function was returning key points without anactionfield, causing the filter in the dialog to incorrectly hide them. - Lesson: This highlighted the importance of robust default states and careful handling of
undefinedvalues, especially when filtering UI elements. Small discrepancies in data contracts can lead to significant UI malfunctions.
The Duplicate Data Dilemma
- Challenge: During testing, clicking the "Save insights" button multiple times resulted in duplicate records being created (e.g., 30 records instead of 10).
- Lesson: User interfaces need to be resilient to rapid-fire actions. While we've cleaned up the existing duplicates with a quick SQL query, this immediately flagged a need for a
disabledguard on the save button after the first click, or implementing mutation-level deduping on the backend to prevent future occurrences. This is a critical item for our immediate next steps.
What's Next: The Road Ahead
With the core memory system now robust and end-to-end functional, our gaze turns to the horizon:
- Fortifying Data Integrity: Implementing the duplicate-save guard to prevent accidental duplicate insights.
- Unlocking Semantic Search with
pgvector: This is where things get truly exciting. Our next big step is integratingpgvectorinto our Postgres setup. This will allow us to store vector embeddings for our insights, paving the way for advanced semantic similarity search. Imagine querying for "similar design challenges" and getting truly relevant past memories, not just keyword matches! (Currently, our Docker Postgres ispostgres:16-alpine, but we're moving topgvector/pgvector:pg16). - Template Resolution & Prompt Injection: Thoroughly testing the
{{memory}}template resolution to ensure selected memory insights are correctly injected into step execution prompts, making our AI interactions truly context-aware. - Project-Scoped Insight Filtering: Enhancing the MemoryPicker to filter insights by
projectIdfor