symfonic.capabilities.human.factory¶
factory ¶
Human interaction as something a composition root can compose.
HumanInteractionCapability.compile has always assembled the pieces, and it
already carries contribute() -- so unlike governance, the kernel would have
accepted this object all along. What was missing was the call that produces
one: the constructor takes four collaborators and the classmethod takes a
signer, a TTL policy, a consumption seam and a registration list, and an
adopter who wanted ask_user had to know all of that first.
This is the one call, in the shape memory_capabilities(store, scope)
established.
The signer has no default, and that is deliberate. It mints and verifies the pause tokens a paused turn is resumed with. A default one would ship every deployment the same key, and a token minted by one would be honoured by all of them. There is no safe placeholder, so the argument is required.
The consumption seam does have a default, and it is single-process. It is
what records that a token was spent, so a second redemption can be refused.
InMemoryConsumption keeps that record in one process: two replicas each
keep their own, and a token spent on one is still unspent on the other. Fine
for development, and :func:redemption_is_shared reports which one a
deployment ended up with rather than leaving it to be discovered by a replay.
The factory refuses to return a capability that contributes nothing.
contribute() answers with an empty contribution in three states -- nothing
registered, no binding, no encode_token -- and that is the right answer
for the capability: advertising ask_user when a pause could never be
answered invites the model to stop a run nobody can resume. It is the wrong
thing for a factory to hand back silently. The first version of this door did
exactly that: it composed, an Agent accepted it, a turn ran, and the model
was never offered the tool. So the check is here, where the deployment can
still be told which argument is missing.
AskUserPayload ¶
Bases: BaseModel
What the agent asks. One field, because one question is the whole tool.
AskUserResponse ¶
Bases: BaseModel
What the person answers.
ask_user_registration ¶
The ask_user interaction, with schemas that actually validate.
cross_scope_allowed stays false: a token minted for one scope being
redeemable in another is the cross-scope redemption the ledger exists to
record, and it is not something a default should hand out.
Source code in src/symfonic/capabilities/human/factory.py
human_interaction ¶
human_interaction(*, signer: Any, binding: Callable[[], Any], encode_token: Callable[[Any], str], decode_token: Callable[[str], Any] | None = None, registrations: Iterable[InteractionRegistration] | None = None, ttl: TTLPolicy | None = None, consumption: Any = None, **options: Any) -> HumanInteractionCapability
Build the capability that lets a turn pause and be resumed.
Agent(provider, capabilities=[human_interaction(
signer=my_signer,
binding=lambda: PauseBinding(run_id=..., session_id=...),
encode_token=my_encoder,
)])
Three arguments are required because only the deployment can supply them, and because the capability contributes nothing without them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signer
|
Any
|
mints and verifies pause tokens. No default: a shared one would make every deployment honour every other's tokens. |
required |
binding
|
Callable[[], Any]
|
resolves what this run is, per run. No default: a
|
required |
encode_token
|
Callable[[Any], str]
|
renders a minted envelope as the opaque string a person answers with. No default: the token format is a deployment's choice, not the framework's. |
required |
decode_token
|
Callable[[str], Any] | None
|
the inverse. Optional, and its absence has a cost -- without it a pause taken on the kernel route has no public reader, so the run can be stopped and not continued. |
None
|
registrations
|
Iterable[InteractionRegistration] | None
|
which interactions this agent offers. Defaults to
|
None
|
ttl
|
TTLPolicy | None
|
how long a pause may stay open. Defaults to :data: |
None
|
consumption
|
Any
|
where spending a token is recorded. Defaults to a
single-process store; see :func: |
None
|
**options
|
Any
|
forwarded to |
{}
|
Raises:
| Type | Description |
|---|---|
InteractionConfigurationError
|
if the composed capability would contribute nothing, naming what is missing. |
Source code in src/symfonic/capabilities/human/factory.py
redemption_is_shared ¶
Whether two replicas would agree that a token has been spent.
False means each replica holds its own record, so a token redeemed on
one can be redeemed again on another. That is a legitimate choice for a
single-process deployment and an unpleasant surprise for any other, which
is the reason it is reported rather than assumed.