Skip to content

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:

  1. The working context is rehydrated from Postgres every turn (from the chat of record).
  2. 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 history

Compaction pipeline

Budget = the model's context window − a reserve for the response. When the window is over budget:

  1. 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.
  2. Protect head & tail: keep the system prompt + first turn and the recent tail (by token budget, not message count) verbatim.
  3. Memory-flush hook: before summarizing, extract durable facts into agent/memory so compaction never silently loses them.
  4. 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.
  5. 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

ConcernSlice
messages + the recap row (the source of the window)agent/chat
the context engine + the turn loopagent/orchestrator
the cheap auxiliary model for summariessystem/llm
long-term + durable memoryagent/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.