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, ask_user_description: str | 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: current_run_binding(scope=..., 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
|
ask_user_description
|
str | None
|
optional model-visible wording for the built-in
|
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
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 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 | |
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.