Skip to content

Memory System

Talon maintains persistent memory across four layers. Each layer has a different durability, mutability, and access pattern. Together they give agents durable context that survives session boundaries, daemon restarts, and context window rotations.

LayerStorageDescription
Transcriptmessages tableCanonical message log, append-only
Working memoryIn-prompt contextRecent message window included in agent prompts
Thread notebookFilesystemHuman-editable per-thread notes
Structured memorymemory_items tableExtracted facts and summaries, agent-managed

Every inbound and outbound message is persisted to the messages table. This is the source of truth for what was said. It’s append-only — messages are never modified or deleted. The transcript is what session-summarizer reads when creating context summaries.

When an agent run starts, Talon assembles the in-context prompt. This includes the most recent N messages from the transcript verbatim. The count is configurable via context.recentMessageCount. This is the agent’s immediate short-term view of the conversation.

Each thread gets a writable directory at data/threads/<thread_id>/. The agent can read and write files here via fs.read and fs.write capabilities. This is useful for structured notes, scratch work, or human-readable summaries that you want to inspect or edit directly.

The memory_items table holds extracted facts, session summaries, observation logs, and freeform notes. The agent writes to it via memory_access.

TypePurpose
factDiscrete pieces of information (“user prefers JSON output”)
summarySession summaries created by session-summarizer
observationDated decision-log entries created by session-observer
noteFreeform notes the agent wants to retain

All items are scoped to a thread. An agent in thread A cannot read or write memory items from thread B.

The memory_access host tool is the agent’s interface to structured memory. Required capability: memory.access:thread.

# write a fact
memory_access({
operation: "write",
key: "user-format-preference",
value: "prefers markdown tables",
namespace: "fact"
})
# read it back
memory_access({
operation: "read",
key: "user-format-preference"
})
# list all facts
memory_access({
operation: "list",
namespace: "fact"
})

The memory-retriever sub-agent provides semantic search over memory items. It uses keyword matching combined with LLM reranking to surface relevant facts for a given query.

subagent_invoke({
name: "memory-retriever",
input: { query: "what does the user prefer?" }
})

Requires subagent.invoke capability and memory-retriever in the persona’s subagents list.

When session-summarizer or session-observer produces a compression result, it can include structured memoryUpdates. The context roller extracts these and writes them as named keys to structured memory instead of anonymous blobs. For example, a summary might produce:

{
"memoryUpdates": {
"work:current-project": "Building A2A protocol for Talon",
"preference:output-format": "Prefers markdown tables"
}
}

Each key-value pair is written via memory_access with the key as the memory key. This gives the agent stable, addressable memory entries that can be read back by key rather than requiring a semantic search. Over time, named keys build up a structured profile of the conversation.

Over time, memory_items accumulates redundant or outdated entries. The memory-groomer sub-agent consolidates and prunes them. Call it periodically via a scheduled task:

# In a persona schedule
- cronExpr: "0 3 * * *"
promptFile: memory-grooming

Where personas/<name>/prompts/memory-grooming.md contains the instruction to invoke memory-groomer.

The default timeout for memory-groomer is now 180 seconds. That gives it enough room to work through larger memory stores without timing out mid-pass.

PR #189 adds observational memory as a second context-compression mode. Instead of storing one summary item that gets replaced on each rotation, Talon can append observation items over time. This is useful for long-running threads where the agent needs a durable record of decisions, open work, and chronology.

Observation entries:

  • are grouped by date
  • use priority markers (🔴, 🟡, 🟢)
  • preserve currentTask and suggestedContinuation metadata
  • are periodically consolidated by session-reflector when the log gets too large

That makes the memory layer more like an operational notebook than a rolling synopsis.

Thread files persist at data/threads/<thread_id>/. This path is stable for the lifetime of the thread. Agents can use this for anything that benefits from filesystem persistence: drafts, research notes, generated files.

# example: agent writes a research summary
fs.write({ path: "data/threads/abc123/research-notes.md", content: "..." })