symfonic.capabilities.human.contribution¶
contribution ¶
The compiler seam: how a pause point reaches a compiled turn (HK1, TA8.34).
The seam is the one TA8.12 built for delegation and TA8.21 built for
extensions, used rather than re-invented:
:func:~symfonic.kernel.contracts.contributions.fold_contributions folds a
:class:~symfonic.kernel.contracts.contributions.CapabilityContribution, the
composition root carries the folded pieces in its bundle, and the plan factory
compiles them. Nothing new is introduced for the third capability to do the
thing the first two already do.
Both halves, because a pause needs both. Delegation contributes tools and no stage; extensions contribute a stage and no tool. Human interaction is the first capability that needs one of each, and they are not two designs:
- the tool is the door -- how the model asks. Without one the model has no way to reach a registered interaction at all;
- the stage is the stop --
pre-tool, after the kernel reserved the round's calls and before any of them is dispatched. It is the only place in a turn that sees the reserved call id the pause has to be bound to, which is the id a resume later joins on. A tool body cannot see its own call id, so a pause minted there would carry notool_call_idand its payload would have nowhere to live (checkpoints.payload_key).
So the tool body is not dead code. It is reached whenever the stage
declines to pause, which happens two ways: the deployment wired no binding for
this run; or the capability refused the pause -- the payload the model wrote
does not fit the registration's schema, or a checkpoint port had a bad minute.
Both decline rather than raise: a raise from pre-tool becomes a FAILED
trace and require_no_crashed_stage turns that into a
ContractViolationError that ends the turn. Declining hands the model the
refusal back as the tool's observation and the turn carries on -- the shape
delegation uses when a hand-off is refused at the depth ceiling.
The behaviour lives next door in :mod:~symfonic.capabilities.human.pausing
(HK2, TA8.35), which is where PauseBinding and pause_handler are defined
and from where they are re-exported here at the addresses importers already
hold. This module is the declaration -- what the capability offers a compiler,
and why each half of it exists -- and that one is the behaviour the declaration
routes to. They were one module while the behaviour was "mint a token and
raise"; giving the pause a turn state to record put it over the module-size
budget, and the split follows the line typed_interrupts follows next door to
typed_projection: one module per shape.
InteractionToolSpec
dataclass
¶
InteractionToolSpec(name: str, description: str, coroutine: Callable[..., Any], parameters: tuple[str, ...] = ())
One interaction, described as a tool the composition root can bind.
A description and not a runtime tool object, for the reason
:class:~symfonic.capabilities.delegation.contracts.DelegationToolSpec is
one: a capability that constructed a StructuredTool would put a
third-party tool library on the import path of a package with no other use
for it. symfonic.agent.cutover.delegation.bind_contributed_tool is the
step that wraps it, at the composition root, which is where the runtime's
tool type is known.
PauseBinding
dataclass
¶
PauseBinding(pin: Any = None, scope: Any = None, run_id: str = '', root_run_id: str = '', session_id: str = '', thread_id: str = '', checkpoint_id: str | None = None)
What a pause has to be bound to, resolved per run by the composition root.
Every field is a fact about this run and none of them is known when the
plan is compiled, which is why this arrives through a callable rather than
on the capability: a bundle folded once at construction would bind every
turn to the first turn's tenant, run and thread. That is the same defect
TurnRequest.scope exists to correct, one capability over.
checkpoint_id may be None, in which case the capability resolves it
through its checkpoint port. A deployment with neither cannot pause, and
:meth:HumanInteractionCapability.pause refuses rather than minting a
token bound to no checkpoint.
ask_user_tool_spec ¶
The built-in's door, and only the built-in's.
ask_user is the one interaction whose payload shape this package knows
-- a set of questions -- so it is the one whose tool signature this package
can write. A deployment's own registered interaction carries a schema only
that deployment knows, so it binds its own tool under the registered name
and the stage below pauses on it exactly the same way. Guessing a signature
for a schema we cannot read would advertise a tool the model could not call
correctly.
Source code in src/symfonic/capabilities/human/contribution.py
build_contribution ¶
build_contribution(capability: Any, request: CapabilityRequest, *, binding: Callable[[], PauseBinding | None] | None, encode_token: Callable[[Any], str] | None) -> CapabilityContribution
Fold one human-interaction capability into what a compiler needs.
Source code in src/symfonic/capabilities/human/contribution.py
pause_handler ¶
pause_handler(capability: Any, binding: Callable[[], PauseBinding | None], encode_token: Callable[[Any], str]) -> Callable[[Any], Any]
Build the handler that turns a reserved interaction call into a pause.
Source code in src/symfonic/capabilities/human/pausing.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
pause_stage ¶
The pre-tool stage a pause happens in.
pre-tool and not post-model: the pause has to be bound to the call
ids the transcript will join on, and those are minted by the kernel's own
reservation, which happens between the two. It declares no effect family:
minting a token and recording its payload are the capability's own ports,
not an :class:~symfonic.kernel.contracts.effects.EffectFamily the plan
grants, and declaring one it was not granted would be refused at fold.