Use live system-of-record state
Inject current state at compile time instead of storing it.
When the agent needs a value another system owns — a subscription's status, an account balance, a ticket's state — don't store it; fetch it live and inject it into the prompt. pgmem gives the context compiler a Tier 6 slot for exactly this: current state is present in the prompt but never persisted, so memory can't go stale.
Prerequisites
- You've applied the events-vs-state litmus test and decided this value is state, not an event.
- A way to read the value from its system of record (an API client, a DB query).
- pgmem integrated into your turn loop.
Steps
Fetch the state concurrently, off the model's path
Read the system of record alongside your other turn work so the round-trip doesn't serialise in front of the reply.
import asyncio
live, _ = await asyncio.gather(
fetch_subscription_status(user_id), # your call to the system of record
session.add_turn(role="user", content=user_text),
)
# live == "Stripe: sub_123 status=active (fetched 2026-06-22)"Inject it into compile_context
Pass the resolved strings as injected_context. They become Tier 6 — rendered
into the prompt as Current: lines, right before the recent turns, and never
stored.
context = await session.compile_context(
query=user_text,
token_budget=12_000,
injected_context=[live],
)
print(context.injected) # ['Stripe: sub_123 status=active (fetched 2026-06-22)']This per-call argument is the primary path for voice agents: you control the fetch, you do it concurrently, and nothing slow runs inside the compiler.
Optional: a construction-time provider
For a small, fast, always-injected block (e.g. the user's plan tier), wire a
provider once. It runs on every compile_context — on the response path.
from pgmem import PgMem, PgMemConfig
from pgmem.compiler import CompilerConfig
async def inject(session, query):
return [await fast_cached_lookup(session.user_id)]
mem = await PgMem.create(
dsn,
config=PgMemConfig(compiler=CompilerConfig(injected_context_provider=inject)),
)The provider runs on the turn. A network call here is on the latency path.
Prefer the per-call injected_context= argument for anything that hits the
network; reserve the provider for cheap, pre-warmed lookups. When both are
present, the per-call argument wins.
Verify
Live context survives a tight budget over the memory tiers — which is the point,
since it's the corrective for stale memory. The compiler's drop order under
pressure is T4 → T3 → T5 → T6 → R: old semantic memory sheds first, live
context (T6) survives longest of the droppable tiers, and recent turns (R) never
drop. Inspect context.injected (what survived) and context.dropped (which
tiers were cut).
Gotchas
- This is the read half of events vs state; the write half is not storing the value. If you also stored it, you'd have a stale fact competing with the live one.
- Provenance helps you find what to fetch: events asserted via
external_factscarry anExternalReference(e.g.system="stripe", id="sub_123") — read it back to know which records to refresh.
Related
- Events vs state — why state is fetched, not stored
- How a turn flows — where the fetch sits on the turn
- Decide what to remember — skipping state at write time
- The context compiler — tiers and the budget