How a turn flows
What runs on the response path versus off it.
One turn touches every memory tier. The load-bearing rule is what runs on the
response path versus off it: only retrieval (compile_context) rides the
turn; claim formation, compaction, and projection refresh happen after the reply — so a
voice agent never pauses to think about its memory.
Why it matters
For a voice agent, latency is the product. If summarising the conversation, writing claims, or refreshing projections happened while the user waited, every turn would stall on an LLM call. pgmem draws a hard line: the only work on the response path is reading — assembling the next prompt from indexed data under a token budget. Everything that thinks (and everything heavy) happens after you've already replied.
The flow
How it works
Three things this makes concrete.
Retrieval is the only thing on the turn. compile_context is pure indexed
reads under a token budget — no LLM call — so it fits the latency budget. It pulls
from every memory tier at once (narrative, episodic, semantic) plus the recent
turns, sheds lowest-priority tiers first if the budget is tight, and returns
ready-to-send messages. Everything heavy runs after the reply. See
The context compiler.
Current state is fetched live, not remembered. The system of record is read concurrently with compilation and injected as Tier 6 live context — present in the prompt, never written to memory. This is the read half of Events vs state: memory holds the event, the live fetch holds the current value, and the two compose without conflict.
Memory formation is a decision, not a default. When a tool result becomes a candidate claim, ClaimPolicy decides its lifecycle. Current state is redirected to live context. Formation, compaction, and projection refresh all run off-turn.
Tradeoffs & pitfalls
compile_context returning compaction_triggered: true (or dropped: ["T1"])
is not compaction. That's the budget fitter transiently trimming recent turns
for this one prompt — nothing durable happened. Treat it as a signal to call
compact_async() off-turn; the durable compaction is a separate operation.
- A live-state provider runs on the turn. If you wire
injected_context_providerto call an API, that call is on the response path. Prefer fetching concurrently in your own turn code and passing the result via the per-callinjected_context=argument; reserve the provider for fast, pre-warmed lookups. - Derived projections are eventually consistent. An active claim is readable from Tier 3 immediately; its lexical, vector, and graph projections catch up after the worker runs.
Related
- Events vs state — why live state is fetched, not stored
- The context compiler — how the prompt is assembled under budget
- The five-tier model — what each tier holds
- Compaction (MAGE) — the off-turn summarization
- Compaction off the critical path — wiring it for voice