symfonic.kernel.context¶
context ¶
RequestContext — everything an invocation did (RCX).
The plan's twin. It is created per invocation, never stored on an agent, never shared between runs, and never outlives the run that made it. That is the whole rule, and it is what retires the module-global per-run state pattern: state that lives on a module survives the run, and state that survives the run is state the next run can read.
RequestContext ¶
Request-local mutable state, with one writer per slot (RCX-2).
Source code in src/symfonic/kernel/context.py
lifecycle
property
¶
The run's lifecycle coordinator — resources, work, and teardown.
resolved
property
¶
The turn's resolved inputs, or None before prompt assembly ends.
Read-only by construction: ResolvedInputs has no mutating method and
freezes its payloads, so handing it to a later phase hands a fact rather
than a channel.
bind_generation ¶
Record the generation this invocation runs against, exactly once.
Write-once is what keeps a long-lived agent from mixing generations mid-invocation when a cutover flips underneath it (T2.3.6): the plan holds the binding stage, and the context holds the binding result.
Source code in src/symfonic/kernel/context.py
bind_resolved ¶
Record the turn's resolved-input snapshot, exactly once (#24).
Before this, the snapshot was a local in run_prompt_assembly: built
by the resolution pass, handed to the compilation pass, and unreachable
the moment that function returned. A value published by a
prompt-assembly stage could not be read by a later phase at all — not
because the envelope forbade it, but because nothing carried it.
Write-once for the same reason bind_generation is, and the reason
matters more here. scratch(namespace) could also transport a value,
and it is the wrong carrier: it hands back the live dict, so any holder
can rewrite what a resolution stage decided. A governance verdict read
three phases later must have one answer per turn, not a
last-writer-wins one. ResolvedInputs is already deeply immutable and
refuses payloads it cannot freeze, so the only mutability left to remove
was the binding itself.
Source code in src/symfonic/kernel/context.py
reserve ¶
Return request with a run-unique call_id.
Source code in src/symfonic/kernel/context.py
reserve_call_id ¶
Return a run-unique tool-call id, keeping the provider's when usable.
Source code in src/symfonic/kernel/context.py
scratch ¶
spawn ¶
spawn(work: Any, *, owner: str, purpose: str, deadline_seconds: float | None = None) -> asyncio.Task[Any]
Create run-owned background work (RCX-8/BP-8); never fire-and-forget.
Source code in src/symfonic/kernel/context.py
teardown
async
¶
Unwind this run's lifecycle exactly once and return its record.