symfonic.agent.middleware.pause_token_store¶
pause_token_store ¶
Pause-token consumption stores (v7.1.1).
A PauseTokenStore records which pause-token JTIs have been redeemed so the
single-use property of the ask_user flow is enforceable. Two implementations
ship:
-
:class:
InMemoryPauseTokenStore-- the v7.1.0 set-based fallback. Single process only; tokens leak across worker restarts and are NOT safe in a horizontally-scaled deployment. Used implicitly when no Postgres pool is wired (e.g. unit tests usingMemorySaverorSqliteSaver). -
:class:
PostgresPauseTokenStore-- the production path. UsesINSERT ... ON CONFLICT DO NOTHING RETURNING jtiin a single SQL round-trip so the first consumer always wins atomically, even across two workers redeeming the same token simultaneously.
The split mirrors the convention used by postgres_graph.py: DDL is a
module-level constant, the backend exposes an ensure_schema() method,
and the asyncpg pool is injected at construction time.
InMemoryPauseTokenStore ¶
Set-based in-process store.
Backwards-compatible fallback for deployments without a Postgres pool
(typical: unit tests with MemorySaver / SqliteSaver). Emits a
warning the first time consume is called so production operators
know they are running on a single-process token cache that does NOT
survive worker restart and does NOT coordinate across workers.
Source code in src/symfonic/agent/middleware/pause_token_store.py
MongoPauseTokenStore ¶
MongoDB-backed atomic store using a unique _id insert.
The Mongo counterpart to :class:PostgresPauseTokenStore. Single-use
redemption is enforced by using the token jti as the document _id:
the first insert_one wins; a concurrent replay hits the unique-_id
constraint and raises DuplicateKeyError -> False. The check is
atomic at the document level, so exactly one caller sees True.
Reuses the graph backend's async motor database (MongoGraphBackend._db)
rather than opening a parallel client — the store only needs one small
collection alongside the tenant graph collections. The checkpointer, by
contrast, needs its own sync pymongo client (see
MongoCheckpointerFactory); this store stays fully async on motor.
Raises:
| Type | Description |
|---|---|
ImportError
|
when |
Source code in src/symfonic/agent/middleware/pause_token_store.py
consume
async
¶
Atomic single-use redemption via unique _id insert.
- First caller:
insert_onesucceeds -> True. - Replay caller: duplicate
_id->DuplicateKeyError-> False.
Source code in src/symfonic/agent/middleware/pause_token_store.py
ensure_schema
async
¶
No-op: single-use is enforced by the unique _id (jti).
Provided for parity with :class:PostgresPauseTokenStore so callers
can invoke it uniformly. Mongo needs no DDL — the _id index is
implicit and unique.
Source code in src/symfonic/agent/middleware/pause_token_store.py
PauseTokenStore ¶
Bases: Protocol
Single-use token consumption store.
Implementations MUST guarantee that consume(jti, scope_hash) returns
True exactly once for any given jti across the lifetime of the
store. Concurrent callers competing for the same jti MUST see
exactly one True and the rest False.
Roadmap Item 9 (v7.1.x): the name kwarg is an opt-in telemetry
field carrying the interrupt's registered name ("ask_user" for
the built-in case). Stores that persist rows MAY record it; the
in-memory store ignores it.
consume
async
¶
Mark jti consumed atomically.
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
|
Source code in src/symfonic/agent/middleware/pause_token_store.py
PostgresPauseTokenStore ¶
Postgres-backed atomic store using INSERT ... ON CONFLICT DO NOTHING.
Construction takes the same PostgresPoolManager already used by the
semantic-memory backend so we re-use the existing asyncpg pool rather
than opening a parallel connection. ensure_schema() must be awaited
once at agent boot before the first consume call.
Raises:
| Type | Description |
|---|---|
ImportError
|
when |
Source code in src/symfonic/agent/middleware/pause_token_store.py
consume
async
¶
Atomic single-use redemption.
The query semantics
INSERT ... ON CONFLICT (jti) DO NOTHING RETURNING jti
- First caller: row inserted,
RETURNINGyields the jti -> True. - Replay caller:
ON CONFLICT DO NOTHINGsuppresses the insert,RETURNINGyields nothing -> False.
The check is atomic at the row level; concurrent inserts of the
same jti are serialised by the PRIMARY KEY constraint -- exactly
one wins, the rest see ON CONFLICT.
name defaults to "ask_user" so unchanged callers keep
their semantics. Roadmap Item 9 generic interrupts populate it
with the registered interrupt name (e.g. "approval_required").
Source code in src/symfonic/agent/middleware/pause_token_store.py
ensure_schema
async
¶
Create the consumed-tokens table + index (idempotent).
Called automatically before the first consume if it has not
already run; explicit invocation at agent boot is also supported
for callers that want to surface DDL errors eagerly.