symfonic.agent.cutover.continuation¶
continuation ¶
The public redemption door, and what decides which body opens it (TA8.45).
TA8.36 walked the public surface structurally and found a fourth route:
SymfonicAgent.resume and SymfonicAgent.resume_interrupt each start a
turn — a continuation of a paused one — through
AgentRuntime.stream_events → _workflow.astream_events, and neither
consulted a capability decision. Three switches had moved and the door a
human-in-the-loop consumer actually knocks on still opened onto the graph
runtime.
HK2 (TA8.35) had already built the whole inside: the checkpointer role, the
rehydration, the seven-step redemption and three entry points that share one
preparation. What nothing under src/ did was reach it. This module is
that wiring, and it is deliberately three small functions rather than a second
resume implementation — :mod:symfonic.agent.cutover.kernel_resume stays the
only place a token is redeemed.
Admission is a recognition question, not an execution one. Three facts decide whether the kernel can serve a redemption, and all three are known before anything is redeemed:
- a composition root wired a :class:
HumanInteractionCapabilityat all; - it wired :attr:
~HumanInteractionCapability.decode_token, so the opaque string a consumer holds can be turned back into the envelope that was minted; - that transport recognises this string —
decode_tokenreturningNonemeans "not one of mine", which is what an HMAC token minted by the pre-cutover engine looks like to it.
Anything that goes wrong after those three hold is a failure, not a fallback: an expired token, a mismatched axis, a missing checkpoint or a kernel error all propagate to the caller. That rule is ST3's, and it is here for ST3's reason — a route that quietly degraded to the legacy body on failure would make every later "zero legacy reaches" meaningless, because the probe would find the body unreached on a healthy run and reached on a broken one, and no certificate can tell those two apart.
Recognition ¶
What the door found: an envelope to continue with, or why it cannot.
Two fields and never both: envelope is the thing HK2's prepare
takes, refusal is the counted fallback reason. Keeping them on one
value is what stops a caller reading "no envelope" as "no reason" and
dispatching to the kernel with None.
Source code in src/symfonic/agent/cutover/continuation.py
recognise ¶
Can the kernel serve this redemption, and if not, why not — by name.
Called before the switch is even consulted is not what happens: the switch is read first, and this is asked only of a flipped one, so a rolled-back capability never touches the transport. Both orders give the same answer; this one keeps the rollback lever total.
decode_token is allowed to raise, and a raise is not converted into a
refusal here. A transport that cannot parse a string it did issue has a
real fault, and turning it into a silent legacy continuation is the degrade
this module's docstring refuses.