Background
docker-agent already ships a SQLite-backed memory toolset (pkg/tools/builtin/memory/) with explicit add_memory, get_memories, search_memories, update_memory, and delete_memory tools. The agent can persist and retrieve facts, but retrieval is entirely reactive — the model must decide to call get_memories or search_memories before it can use stored knowledge. This means memories are often ignored at the start of a turn, which undermines the value of the memory store.
Motivation
A proactive injection pattern solves this: before every model call, relevant memories are automatically retrieved and injected into the context as a transient system message. The agent always has its memories available without spending a tool-call round-trip. docker-agent's turn_start hook + builtin system is the natural home for this behaviour, following the precedent set by add_date, add_environment_info, and add_prompt_files.
Proposed Design
1. New inject_memories builtin
Register a new turn_start builtin named inject_memories:
- Input: current user turn text from
hooks.Input.Prompt
- Retrieval strategy: run
SearchMemories(ctx, keywords, "") extracting keywords from the prompt; fall back to GetMemories when the prompt is short/empty
- Result cap: configurable
max_inject_memories limit (default: 20)
- Output: a transient
turn_start system message — never persisted to session.Messages
2. Prefix-cache stability (frozen snapshot)
- Maintain a frozen snapshot of injected memory text
- Rebuild only after a compaction event or a memory write (
add_memory, update_memory, delete_memory)
- Snapshot held in-process; not persisted
3. Per-agent configuration
Add an inject_memories boolean to AgentConfig, wired through builtins.AgentDefaults → ApplyAgentDefaults → buildHooksExecutors. Only activates when a memory toolset is also present for the agent.
agents:
my_agent:
tools:
- type: memory
inject_memories: true # new flag
4. Implementation checklist
Acceptance Criteria
- When
inject_memories: true and a memory toolset are both set, relevant memories appear as a transient system message at every turn_start.
- Injection does not persist to
session.Messages.
- Empty memory store → no injection system message added.
max_inject_memories cap respected; excess dropped without error.
- Memory write invalidates the frozen snapshot; next turn reflects the change.
- Compaction event triggers snapshot rebuild.
- Keyword search used for non-trivial prompts; full dump used as fallback.
inject_memories: false (default) → no behavioural change from today.
- ≥80% test coverage on new code.
Additional Context
- Related packages:
pkg/tools/builtin/memory/, pkg/memory/database/, pkg/hooks/builtins/, pkg/runtime/hooks.go, pkg/session/session.go
- Two follow-up issues are planned: (a) Separate user-profile memory tier, (b) Background memory review daemon
- The existing tool-based memory (explicit model tool calls) is preserved; this adds automatic proactive injection on top.
Background
docker-agent already ships a SQLite-backed memory toolset (
pkg/tools/builtin/memory/) with explicitadd_memory,get_memories,search_memories,update_memory, anddelete_memorytools. The agent can persist and retrieve facts, but retrieval is entirely reactive — the model must decide to callget_memoriesorsearch_memoriesbefore it can use stored knowledge. This means memories are often ignored at the start of a turn, which undermines the value of the memory store.Motivation
A proactive injection pattern solves this: before every model call, relevant memories are automatically retrieved and injected into the context as a transient system message. The agent always has its memories available without spending a tool-call round-trip. docker-agent's
turn_starthook + builtin system is the natural home for this behaviour, following the precedent set byadd_date,add_environment_info, andadd_prompt_files.Proposed Design
1. New
inject_memoriesbuiltinRegister a new
turn_startbuiltin namedinject_memories:hooks.Input.PromptSearchMemories(ctx, keywords, "")extracting keywords from the prompt; fall back toGetMemorieswhen the prompt is short/emptymax_inject_memorieslimit (default: 20)turn_startsystem message — never persisted tosession.Messages2. Prefix-cache stability (frozen snapshot)
add_memory,update_memory,delete_memory)3. Per-agent configuration
Add an
inject_memoriesboolean toAgentConfig, wired throughbuiltins.AgentDefaults→ApplyAgentDefaults→buildHooksExecutors. Only activates when amemorytoolset is also present for the agent.4. Implementation checklist
pkg/hooks/builtins/inject_memories.go— newturn_startbuiltinpkg/hooks/builtins/builtins.go— register builtin constant + handler +AgentDefaultsfieldpkg/agent/agent.go— addInjectMemories() boolaccessorpkg/config/latest/types.go— addInjectMemories *booltoAgentConfigpkg/runtime/hooks.go— wireInjectMemoriesthroughAgentDefaultsturn_startinjects relevant memories, filters unrelated onesAcceptance Criteria
inject_memories: trueand amemorytoolset are both set, relevant memories appear as a transient system message at everyturn_start.session.Messages.max_inject_memoriescap respected; excess dropped without error.inject_memories: false(default) → no behavioural change from today.Additional Context
pkg/tools/builtin/memory/,pkg/memory/database/,pkg/hooks/builtins/,pkg/runtime/hooks.go,pkg/session/session.go