symfonic.core.contracts.interrupt¶
interrupt ¶
Generic interrupt contracts — Roadmap Item 9 (v7.2-bound).
Provides the public Pydantic shapes that surface a named graph pause to
stream consumers. ask_user continues to use its own dedicated event
classes (AskUserQuestionEvent / AskUserAnsweredEvent /
AskUserExpiredEvent); the generic interrupt path is additive and
emitted only when a caller registers a custom interrupt name and toggles
FrameworkConfig.experimental_interrupt=True.
Event taxonomy (mirrors the ask_user trio so consumers can hand-roll identical UI treatments):
- :class:
InterruptEvent-- the pause itself; carries name, opaque token, serialized payload, expiry. - :class:
InterruptResolvedEvent-- emitted on successful/resumeof a generic interrupt (analogous toAskUserAnsweredEvent). - :class:
InterruptExpiredEvent-- emitted when the janitor sweeps an expired checkpoint (analogous toAskUserExpiredEvent).
Replay semantics¶
Each interrupt is independent. Two registered interrupts "a" and
"b" triggered in sequence within one run mint two separate JTIs and
two separate pause tokens; interrupt_consumed_tokens.jti PRIMARY KEY
enforces single-use per token, but b's token can be consumed before
or after a's -- the rows do not depend on each other.
Scope semantics¶
By default a pause token is bound to the FrameworkTenantScope that
minted it (via PauseToken.hash_scope). A registration may opt into
cross_scope_allowed=True to permit redemption under a different scope
(e.g. tenant-admin approving a sub-tenant action). When cross-scope is
allowed, the resume path still logs the mismatch as an audit event so
the security envelope's relaxation is observable in operations.
InterruptEvent ¶
Bases: BaseModel
Generic pause event emitted when a registered interrupt fires.
Mirrors :class:AskUserQuestionEvent for non-ask_user interrupts.
Fields
name: Registered interrupt name (must match a prior
:meth:SymfonicAgent.register_interrupt call).
pause_token: Opaque JWT carrying the same security envelope as
ask_user (HMAC-signed, scope-bound, request-hash-bound,
expiry-bounded, single-use via the DB-backed
interrupt_consumed_tokens table).
payload: JSON-serialized payload matching the registered
payload_schema.
expires_at: Token expiry (UTC). Resume after this raises.
interrupt_id: Client-side correlation id, format i-<12hex>.
InterruptExpiredEvent ¶
Bases: BaseModel
Emitted when the janitor sweeps an expired generic-interrupt token.
Analogous to AskUserExpiredEvent.
InterruptRegistration
dataclass
¶
InterruptRegistration(
name: str,
payload_schema: type[BaseModel],
response_schema: type[BaseModel],
validate_response: Callable[
[BaseModel, BaseModel], None
]
| None = None,
cross_scope_allowed: bool = False,
built_in: bool = False,
metadata: dict[str, Any] = dict(),
)
Boot-time registration record for a named interrupt point.
Held in a per-agent registry (SymfonicAgent._registered_interrupts)
and consulted by the engine's mint path AND the resume router so
the same metadata controls both ends of the pause/resume cycle.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Stable identifier (e.g. |
payload_schema |
type[BaseModel]
|
Pydantic |
response_schema |
type[BaseModel]
|
Pydantic |
validate_response |
Callable[[BaseModel, BaseModel], None] | None
|
Optional cross-validator that receives
|
cross_scope_allowed |
bool
|
When True, the resume endpoint accepts a token issued under one scope to be redeemed under another. Logs an audit entry on each cross-scope redemption. When False (default) same-scope binding is enforced. |
built_in |
bool
|
True for |
InterruptResolvedEvent ¶
Bases: BaseModel
Emitted when a generic interrupt is resumed successfully.
Companion to InterruptEvent; analogous to
AskUserAnsweredEvent.