Pitfalls & footguns
The footgun register — how not to hurt yourself.
pgmem gives you fine control over an agent's memory, and fine control means sharp edges. This is the register of the ways integrators actually hurt themselves, grouped by area, each with the fix. Skim it before you ship.
Memory hygiene
Storing current state as memory. Copying a value another system owns ("subscription is cancelled", "balance is $0") into memory makes it a stale shadow copy the moment that system changes. Fix: apply the events-vs-state litmus test — store the event, fetch the state live.
add_episode(content=...) is ungoverned extraction. It runs the LLM over raw
text and will happily mint a state edge from a state-laden payload.
Fix: assert events through external_facts (no extraction, carries provenance),
and keep current-state text out of the extraction content.
A derived projection is not immediately refreshed. The claim ledger is the
source of truth; vector, graph, and narrative projections are rebuilt
asynchronously after session.end() or by the projection worker. A newly observed
claim is available to the session immediately, but a derived projection may lag.
Fix: read active claims through the session or claim-recall surface when freshness
matters, and treat projections as eventually consistent. See
Trust & memory formation.
Authority and evidence are load-bearing. If every observation is presented as
an agent inference, the default claim policy correctly treats it as weaker than a
validated application or tool result.
Fix: record the real Authority, attach evidence where available, and let the
claim policy decide whether to create, corroborate, supersede, or hold a claim.
Latency & the critical path
Doing memory work on the response path. add_episode, compaction, and
projection refresh are heavy (LLM + embeddings). Running them while the user waits
stalls every turn.
Fix: only compile_context (pure indexed reads) belongs on the turn; run
everything else off-turn. See How a turn flows.
injected_context_provider runs on the turn. A provider that calls an API
puts that network round-trip on the response path.
Fix: fetch live state concurrently in your own turn code and pass it via the
per-call injected_context= argument; reserve the provider for fast, pre-warmed
lookups.
Lifecycle & state
Reusing an old session as memory. resume() is for picking up a session
interrupted mid-flight (a crashed worker), not for "continuing" a past
conversation. Reusing an old execution tree resurrects stale reasoning.
Fix: create a fresh session per conversation and let retrieval bring back the
saga and claims. See The five-tier model.
Expecting overwrite semantics. pgmem is bi-temporal: a correction creates a new validity interval, it does not mutate the old fact in place. Fix: read facts with their time bounds in mind; "the latest" is a query, not the only row. See The bi-temporal model.
compaction_triggered is not compaction. compile_context returning
compaction_triggered: true (or dropped: ["T1"]) just means the budget fitter
trimmed recent turns for this one prompt — nothing durable happened.
Fix: treat it as a signal to call compact_async() off-turn; that is the
durable operation.
Mechanics
Confusing mem.search with session.compile_context. They are different
entry points: search is raw Tier 4 graph retrieval; compile_context is the
full multi-tier prompt assembly under a budget.
Fix: use compile_context to build a turn's context; reach for search only
for direct graph queries.
The token budget counts content only. compile_context's budget measures
message text — not provider chat framing or tool-call overhead.
Fix: reserve headroom in your token_budget for the provider's framing.
Forgetting group_id. Every memory operation is scoped by group_id — it is
the tenant boundary. Mixing or omitting it leaks memory across tenants.
Fix: thread the correct group_id through every call; session child data
inherits it transitively from the session handle.
Wrong embedding dimension. The schema is fixed at vector(1536). An embedder
of a different dimension fails at construction.
Fix: use a 1536-dim embedder (or configure the matching expected_embed_dim).
Related
- Events vs state — the litmus test
- How a turn flows — the critical-path rule
- The five-tier model — execution state vs memory
- Trust & memory formation — authority, evidence, and lifecycle