Background
Sub-issue of #3011.
The agent currently saves memories only when the model explicitly calls a memory tool (pkg/tools/builtin/memory/) mid-conversation. Many memory-worthy moments are missed: the user corrects the agent's behaviour, reveals a preference implicitly, or asks a question that implies a stable environmental fact — none of these trigger an explicit tool call.
A background review daemon solves this: after every N turns, a lightweight review process runs asynchronously after the response is delivered. It examines the recent conversation and decides whether anything is worth persisting, without competing with the user's next message for model attention or token budget.
This forks a constrained mini-agent with only memory-write tools allowed, runs it against a snapshot of the conversation, and lets its writes land directly on the shared memory store.
Proposed design
1. Trigger: configurable nudge interval
Add four config fields to AgentConfig in pkg/config/latest/types.go:
| YAML key |
Go type |
Default |
Description |
memory_review_interval |
*int |
0 (disabled) |
Run background review every N user turns. 0 = disabled. |
memory_review_model |
string |
"" (inherit main) |
Model to use for review. Empty = same model as main agent. |
memory_review_timeout |
*int |
30 |
Hard timeout in seconds for the review goroutine. |
memory_review_verbose |
*bool |
false |
If true, surface a compact summary to the user after writes. |
The runtime in pkg/runtime/ tracks turnsSinceReview int. At the end of every turn, if turnsSinceReview >= memory_review_interval and memory_review_interval > 0, a review is scheduled and turnsSinceReview is reset to 0.
2. Review goroutine
The review runs in a detached goroutine:
go runMemoryReview(ctx, reviewCfg, messagesSnapshot, memStore)
messagesSnapshot is a deep copy of the conversation at the moment the review is triggered — the main session continues independently.
- The goroutine has a hard timeout (
memory_review_timeout, default 30 s).
- Writes go directly to the shared memory DB via the same
add_memory / update_memory paths, so security scanning (sibling sub-issue B) and budget enforcement (sibling sub-issue A) apply automatically.
- The goroutine never calls any tool other than
add_memory, update_memory, and delete_memory — a runtime allow-list enforces this.
3. Review prompt
The review goroutine sends the conversation snapshot to the model with the following review prompt (stored alongside other prompts in the repo, e.g. pkg/memory/review/prompts/):
Review the conversation above and consider saving to memory if appropriate.
Focus on:
1. Has the user revealed things about themselves — persona, preferences,
communication style, pet peeves, or personal details worth remembering?
→ use target="user"
2. Has the agent learned something about the environment, project conventions,
tool behaviour, or workflow that would be useful in future sessions?
→ use target="memory"
If something stands out, save it using add_memory.
If nothing is worth saving, respond with "Nothing to save." and stop.
Priority: user preferences and corrections > environment facts > procedural notes.
Do NOT save task progress, completed-work logs, or temporary state.
4. Preventing review-prompt contamination
The review goroutine must not persist the harness prompt itself as a memory entry. This is enforced by:
- Setting a write-origin tag (
write_origin = "background_review") on the goroutine's DB connection.
- The security scan (sibling sub-issue B) already rejects obvious injection attempts.
- A max-entries-per-review cap (default: 5) prevents runaway writes.
5. User-visible feedback (optional)
When one or more entries are written and memory_review_verbose: true, emit a compact summary through the existing TUI / event bus — e.g. "Saved 2 memories from your last conversation." This integrates with pkg/tui/components/notification/ or the equivalent event stream.
6. Turn-counter persistence across restarts
In gateway / stateless-server mode (pkg/chatserver/, pkg/gateway/), turnsSinceReview is stored in the session DB (pkg/session/) so the counter survives restarts:
UPDATE sessions SET turns_since_memory_review = ? WHERE id = ?;
On session load, read the stored counter to resume from where the last session left off.
Implementation checklist
Acceptance criteria
Background
Sub-issue of #3011.
The agent currently saves memories only when the model explicitly calls a memory tool (
pkg/tools/builtin/memory/) mid-conversation. Many memory-worthy moments are missed: the user corrects the agent's behaviour, reveals a preference implicitly, or asks a question that implies a stable environmental fact — none of these trigger an explicit tool call.A background review daemon solves this: after every N turns, a lightweight review process runs asynchronously after the response is delivered. It examines the recent conversation and decides whether anything is worth persisting, without competing with the user's next message for model attention or token budget.
This forks a constrained mini-agent with only memory-write tools allowed, runs it against a snapshot of the conversation, and lets its writes land directly on the shared memory store.
Proposed design
1. Trigger: configurable nudge interval
Add four config fields to
AgentConfiginpkg/config/latest/types.go:memory_review_interval*int0(disabled)memory_review_modelstring""(inherit main)memory_review_timeout*int30memory_review_verbose*boolfalseThe runtime in
pkg/runtime/tracksturnsSinceReview int. At the end of every turn, ifturnsSinceReview >= memory_review_intervalandmemory_review_interval > 0, a review is scheduled andturnsSinceReviewis reset to 0.2. Review goroutine
The review runs in a detached goroutine:
messagesSnapshotis a deep copy of the conversation at the moment the review is triggered — the main session continues independently.memory_review_timeout, default 30 s).add_memory/update_memorypaths, so security scanning (sibling sub-issue B) and budget enforcement (sibling sub-issue A) apply automatically.add_memory,update_memory, anddelete_memory— a runtime allow-list enforces this.3. Review prompt
The review goroutine sends the conversation snapshot to the model with the following review prompt (stored alongside other prompts in the repo, e.g.
pkg/memory/review/prompts/):4. Preventing review-prompt contamination
The review goroutine must not persist the harness prompt itself as a memory entry. This is enforced by:
write_origin = "background_review") on the goroutine's DB connection.5. User-visible feedback (optional)
When one or more entries are written and
memory_review_verbose: true, emit a compact summary through the existing TUI / event bus — e.g. "Saved 2 memories from your last conversation." This integrates withpkg/tui/components/notification/or the equivalent event stream.6. Turn-counter persistence across restarts
In gateway / stateless-server mode (
pkg/chatserver/,pkg/gateway/),turnsSinceReviewis stored in the session DB (pkg/session/) so the counter survives restarts:On session load, read the stored counter to resume from where the last session left off.
Implementation checklist
pkg/config/latest/types.go— addMemoryReviewInterval,MemoryReviewModel,MemoryReviewTimeout,MemoryReviewVerbosetoAgentConfigagent-schema.json— extend schema; add descriptionspkg/agent/agent.go— accessors for the four new fieldspkg/runtime/—turnsSinceReviewcounter; trigger logic at end of turnpkg/memory/review/review.go—RunReview(ctx, cfg, snapshot, store); build review prompt; call LLM with allow-list tool set; write results to store; hard timeout; max-entries-per-review cappkg/memory/review/— allow-list enforcer: reject any tool call other thanadd_memory,update_memory,delete_memorypkg/session/— persistturns_since_memory_review; reload on session resumememory_review_verbosepath: surface compact summary to user after writes via existing notification/event mechanismmemory_review_interval: 3; assert at least one background write occurs; assert main-turn latency unaffected (< 5% increase)go test -racepasses onpkg/memory/review/Acceptance criteria
memory_review_interval: N, a review goroutine is launched after every Nth user turnmemory_review_timeoutseconds with no partial writes left behindadd_memory,update_memory,delete_memoryare callable from within the review; any other tool call is silently droppedmemory_review_interval: 0(default) produces zero behavioural changego test -racepasses