Short memory
The working context for this conversation on this turn — the messages the LLM actually sees, kept within the model's token budget. It's distinct from the two other "memories":
- Short memory (this page) — the live conversation window + a rolling summary. Bounded, per-turn.
- Memory — semantic search across all past chats (long-term, pgvector).
- Durable facts — things the agent chose to remember (
agent/memory, MEMORY-style).
The constraint: an ephemeral brain
Our brain is the api run per turn — stateless, scales by traffic. So short memory cannot live in process between turns. Two consequences:
- The working context is rehydrated from Postgres every turn (from the chat of record).
- The result of compaction must be persisted — a running summary stored on the chat — so the next ephemeral turn doesn't recompute it.
(Within one long turn — e.g. an hour driving a worker — the window grows in-process while that brain instance is alive; it's persisted at turn end.)
Context assembly (per turn)
The orchestrator's context engine builds the prompt fresh each turn:
[ system prompt ] ← snapshot on the chat → keeps the model's prefix cache warm
[ running summary ] ← rolling structured summary of older turns, fenced "reference-only"
[ tail window ] ← the most recent messages VERBATIM, within a tail token budget
[ retrieved long-memory ] ← pgvector hits, fenced <memory-context> (scrubbed from output)
[ new user message ]
+ tools = the MCP toolset (brain-MCP · worker-MCP · connector-MCP) — not in historyCompaction pipeline
Budget = the model's context window − a reserve for the response. When the window is over budget:
- Cheap pre-pass (no LLM): shrink old tool results to one-line summaries, dedupe identical ones, drop stale screenshots/images. Often this alone is enough.
- Protect head & tail: keep the system prompt + first turn and the recent tail (by token budget, not message count) verbatim.
- Memory-flush hook: before summarizing, extract durable facts into
agent/memoryso compaction never silently loses them. - Summarize the middle with a cheap auxiliary model (via
system/llm) into a structured summary — resolved · pending · active task — and merge it into the previous summary. - Anti-thrash: skip compaction if recent passes saved little; then persist the new summary.
Where the recap actually lives
Not in columns on the thread — as a row in the thread, of kind = summary. The window is cut at the newest one, so the model reads the recap and everything after it while the rows before it stay where a person can scroll to them.
Storing it as a row rather than a field buys two things a column could not:
- The boundary is visible. A compacted conversation and a substituted one are told apart by a line the reader can see, and "start over" is the same line placed deliberately.
- It is idempotent. The row's id is derived from the message it follows, so a retry — or two tabs at once — writes one row, not two.
Where it lives
| Concern | Slice |
|---|---|
| messages + the recap row (the source of the window) | agent/chat |
| the context engine + the turn loop | agent/orchestrator |
| the cheap auxiliary model for summaries | system/llm |
| long-term + durable memory | agent/memory |
Short memory is not a new store — it's a derived assembly over agent/chat plus a small summary field. That's exactly what makes it fit the ephemeral brain: every api turn rehydrates the window from Postgres, compacts if needed, persists the summary, and goes stateless again.
See also
- Storage — the chat of record the window is built from.
- Memory — long-term search (short memory ≠ long memory).
- Runtime model — where the turn loop sits.
- Implementation — building the context engine.