fn-1: Memory system for persistent learning
Overview
Add .flow/memory/ for project-scoped persistent learnings that survive context compaction. The core insight: reviewers surface things the implementing model misses. By capturing NEEDS_WORK feedback, the system learns patterns, framework quirks, and project conventions that models tend to overlook.
Problem
- Context compaction loses important decisions and learnings
- Each session/iteration starts fresh, repeating past mistakes
- Reviewer feedback is valuable but ephemeral - fixed once, forgotten next time
- Models consistently miss the same types of things (framework idioms, project conventions, edge cases)
Design Principles
- Simple: Markdown files, no database, no worker service
- Project-scoped: Lives in
.flow/memory/, travels with the repo - Reviewer-driven: Primary signal is NEEDS_WORK feedback from cross-model reviews
- Selective capture: Only record learnings that models tend to miss:
- Framework/library-specific patterns
- Project conventions not in CLAUDE.md
- Common pitfalls for this codebase
- Architectural decisions with non-obvious rationale
- Subagent retrieval: Memory-scout finds relevant entries without polluting main context
Directory Structure
.flow/
config.json # Project settings (memory enabled, etc.)
memory/
pitfalls.md # Things that went wrong, extracted from NEEDS_WORK feedback
conventions.md # Project patterns discovered during work
decisions.md # Architectural choices with rationale
Configuration
Settings live in .flow/config.json:
{
"memory": {
"enabled": false
}
}
| Setting | Default | Description |
|---|---|---|
memory.enabled |
false |
Enable memory system (opt-in for now) |
One switch. When enabled: read + write both active.
Check in code:
# In flowctl.py or hooks
config = load_flow_config()
if not config.get("memory", {}).get("enabled", False):
return # Memory disabled, skip
# In skills
if memory_enabled:
Task flow-next:memory-scout(<request>)
Enable for a project:
flowctl config set memory.enabled true
# or manually edit .flow/config.json
Backwards Compatibility
Memory is opt-in and disabled by default. This ensures:
- Existing plan/work/review workflows unchanged when disabled
- Ralph prompts and receipt flow untouched
- No subagents spawn, no hooks fire when disabled
- Zero overhead for users who don't enable memory
Integration with existing patterns
flowctl init
Update cmd_init() in flowctl.py to create config.json with defaults:
# In cmd_init(), after creating directories
config_path = flow_dir / "config.json"
if not config_path.exists():
default_config = {
"memory": {
"enabled": False
}
}
config_path.write_text(json.dumps(default_config, indent=2))
First run / missing config
Any command that needs config should handle missing file gracefully:
def load_flow_config() -> dict:
config_path = get_flow_dir() / "config.json"
if not config_path.exists():
return {} # Use defaults
return json.loads(config_path.read_text())
def get_config(key: str, default=None):
"""Get nested config value like 'memory.enabled'."""
config = load_flow_config()
for part in key.split('.'):
config = config.get(part, {})
if config == {}:
return default
return config if config != {} else default
/flow-next:setup
Setup creates config if missing (same as init), then mentions it:
# In workflow.md Step 1 (after creating .flow/)
Check if .flow/config.json exists:
- If missing: create with defaults (memory.enabled: false)
- If exists: leave as-is
# In Step 7 summary
Memory system: disabled by default
Enable with: flowctl config set memory.enabled true
Both flowctl init and /flow-next:setup ensure config exists - whichever runs first creates it.
Config gating (both read and write)
Read operations (subagent):
# In /flow-next:plan steps.md Step 1 (parallel with other scouts)
# Add to the parallel Task calls if memory.enabled:
- Task flow-next:memory-scout(<request>)
# In /flow-next:work phases.md Phase 1 (re-anchor)
# Add after flowctl show/cat if memory.enabled:
- Task flow-next:memory-scout(<task-id>: <task-title>)
Same subagent, same call pattern, different input context.
Write operations (hook):
# In ralph-guard.py PostToolUse handler
if tool_name == "Bash" and "chat-send" in command:
if not get_config("memory.enabled", False):
return # Memory disabled, skip
# ... parse NEEDS_WORK and append to pitfalls.md
One setting controls both read and write.
What To Capture (and What NOT To)
Capture (things models miss repeatedly)
| Category | Example | Why Models Miss It |
|---|---|---|
| Framework idioms | "React 19: use() instead of useEffect for data" | Training data staleness |
| Project conventions | "This repo uses barrel exports in index.ts" | Not in CLAUDE.md |
| API quirks | "flowctl rp requires --repo-root flag" | Wrapper-specific |
| Build/test gotchas | "Must run npm test from repo root" | Implicit assumptions |
| Edge cases | "Empty arrays need special handling in X" | Discovered via review |
DON'T Capture
- Generic coding best practices (models know these)
- Obvious bugs (typos, syntax errors)
- One-off mistakes unlikely to recur
- Sensitive data (API keys, credentials)
Implementation
Phase 1: Capture (PostToolUse hook)
Extend ralph-guard.py to capture learnings from NEEDS_WORK reviews:
# In PostToolUse handler for chat-send
if "NEEDS_WORK" in response or "MAJOR_RETHINK" in response:
feedback = extract_feedback(response)
if is_learnable(feedback): # Filter to actionable patterns
append_to_memory("pitfalls", {
"date": today,
"task": task_id,
"issue": feedback.issue,
"fix": feedback.fix,
"category": classify(feedback) # framework|convention|api|edge-case
})
Filtering logic (is_learnable):
- Has specific actionable fix (not vague "improve this")
- References code pattern, API, or convention (not just "wrong output")
- Not a one-off typo or obvious bug
Phase 2: Retrieval (memory-scout subagent)
One subagent used in both plan and work phases:
# agents/memory-scout.md
You search `.flow/memory/` for entries relevant to the current task.
Input: <context> (planning request OR task title/description)
Steps:
1. Read `.flow/memory/pitfalls.md`, `conventions.md`, `decisions.md`
2. Find entries semantically related to the context
3. Return ONLY relevant entries (not the whole memory)
Output format:
## Relevant Memory
- [pitfall] When doing X, remember to Y (from fn-1.1)
- [convention] This repo uses Z pattern for W
- [decision] We chose X over Y because Z
Called from two places (matches existing scout pattern):
# In /flow-next:plan steps.md Step 1 (parallel with repo-scout, practice-scout, docs-scout)
- Task flow-next:memory-scout(<request>)
# In /flow-next:work phases.md Phase 1 (after flowctl show/cat)
- Task flow-next:memory-scout(<task-id>: <task-title>)
Same call pattern as other scouts. Subagent returns only relevant entries.
Phase 3: Configuration scope
Memory config is separate from Ralph config:
| Config | Location | Scope |
|---|---|---|
| Memory | .flow/config.json |
All flow-next usage (manual + Ralph) |
| Ralph | scripts/ralph/env.config |
Ralph loop only |
Memory works in:
/flow-next:plan(manual)/flow-next:work(manual)- Ralph mode (automated)
Document clearly: "Memory is a flow-next feature, not Ralph-specific. Enable via .flow/config.json, not env.config."
flowctl Commands
# Auto-populated by hooks
flowctl memory init # Create .flow/memory/ structure
# Manual additions (for decisions, conventions discovered manually)
flowctl memory add --type pitfall "Always use flowctl rp wrappers"
flowctl memory add --type convention "Error handlers go in src/utils/errors.ts"
flowctl memory add --type decision "Chose SQLite over Postgres for simplicity"
# Retrieval
flowctl memory read # Dump all memory
flowctl memory read --type pitfalls # Filter by type
flowctl memory list # Show entry count per file
flowctl memory search "pattern" # Regex search across all memory
Memory File Format
# pitfalls.md
## 2026-01-10 fn-1.1 impl-review [framework]
**Issue**: placeholder export still present after adding new export
**Fix**: Remove unused exports before review - reviewers flag dead code
**Context**: src/index.ts had `export const placeholder = 0` from scaffold
## 2026-01-09 fn-2.3 impl-review [api]
**Issue**: flowctl rp builder called without --window flag
**Fix**: Always use `flowctl rp setup-review` which handles window selection
**Context**: Direct builder calls require manual window ID lookup
Curation (preventing bloat)
Start simple, add complexity only if needed:
- v1 (MVP): Global memory, manual review, no limits
- v2 (if bloat): Add
[archived]tag, exclude from default read - v3 (if still bloat): LLM summarization to condense old entries
- v4 (large projects): Epic-scoped memory subdirs
If memory gets large, memory-scout subagent returns only relevant entries (not everything).
Tasks
fn-1.1: Config + memory directory structure
- Add
.flow/config.jsonsupport to flowctl -
flowctl config get <key>/flowctl config set <key> <value> - Default config with
memory.enabled: false - Add
flowctl memory initcommand (creates.flow/memory/if enabled) - Create memory templates: pitfalls.md, conventions.md, decisions.md
- Memory commands check
memory.enabledbefore running
fn-1.2: Capture hook
- Extend ralph-guard.py PostToolUse for chat-send
- Parse NEEDS_WORK feedback into structured format
- Implement
is_learnable()filter - Append to
.flow/memory/pitfalls.md
fn-1.3: flowctl memory commands
-
flowctl memory add --type <type> "<content>" -
flowctl memory read [--type <type>] -
flowctl memory list -
flowctl memory search "<pattern>"(regex)
fn-1.4: Memory-scout subagent
- Create
agents/memory-scout.md - Add to plan skill as 4th parallel subagent (if memory.enabled)
- Add to work skill re-anchor phase (if memory.enabled)
- Test relevance matching in both contexts
fn-1.5: Integration testing
- Test in manual
/flow-next:plan - Test in manual
/flow-next:work - Test in Ralph mode end-to-end
fn-1.6: Documentation
- Add memory section to flow-next README
- Document in ralph.md (note: separate from Ralph config)
- Add to CLAUDE.md project guide
- Clarify: memory is flow-next feature, works in manual + Ralph
Acceptance Criteria
- NEEDS_WORK feedback auto-appends to pitfalls.md (filtered)
-
flowctl memorycommands work - Memory-scout runs in parallel during planning
- Re-anchoring includes relevant memory
- Ralph prompts include memory context
- Memory-scout returns relevant entries only (handles large memory gracefully)
- Manual additions via
flowctl memory addwork
Comparison: flow-next vs claude-mem
| Aspect | claude-mem | flow-next memory |
|---|---|---|
| Scope | Cross-project | Project-scoped |
| Storage | SQLite + Chroma | .flow/memory/*.md |
| Capture | All tool use | NEEDS_WORK feedback only |
| Retrieval | MCP tools + worker | Subagent + flowctl |
| Complexity | High (worker, DB, vectors) | Low (files, grep, subagent) |
| Signal | Everything | Reviewer insights only |
Our advantage: Reviewers (different model) catch things the implementing model misses. By capturing only NEEDS_WORK feedback, we get high-signal learnings without noise.
Quick Commands
# Validate flowctl
plugins/flow-next/scripts/flowctl validate --all
# Test memory commands (after implementation)
plugins/flow-next/scripts/flowctl memory init
plugins/flow-next/scripts/flowctl memory add --type pitfall "test entry"
plugins/flow-next/scripts/flowctl memory read
References
- plans/flow-next.md - original roadmap mention
- Anthropic long-context guidance on re-anchoring
- claude-mem architecture: /tmp/claude-mem/docs/public/architecture/hooks.mdx
- Ralph E2E notes: plans/ralph-e2e-notes.md (examples of learnable issues)