symfonic.capabilities.delegation.runner_adapter¶
runner_adapter ¶
Normalise a caller-built child to the roster's runner contract.
:class:~.contracts.ChildRunner is run(query, **kwargs) and returns
something exposing final_response. The framework's own :class:Agent is
run(prompt, ...) returning an AgentResult with .text -- so the
composition the migration guide documents, and the one every scaffolded
project ships, satisfied neither half.
It failed quietly in both. The call raised TypeError, which
DelegationTools.delegate turns into a tool message by design (a child's
death is the parent's information, not the parent's death), so the parent's
turn succeeded carrying an error string where the answer belonged. Had the
call worked, final_response would have been missing and the message
empty.
This module closes that at the boundary where a child is adopted: a child
that already speaks the protocol is passed through untouched, an Agent
is wrapped, and anything else is refused when it is declared rather than
mid-turn.
ChildResponse
dataclass
¶
ChildResponse(final_response: str, result: Any = None, run_id: str = '', root_run_id: str = '', parent_run_id: str | None = None)
What the roster reads back from a child.
result keeps the child's own return value. The record only needs
final_response, but discarding the rest would make the adapter the
place where a child's structured output goes to die.
accepts_run_lineage ¶
May this runner be told root_run_id and parent_run_id?
Answered from the declared signature, never by making the call and reading
the exception. TypeError would be the easy detector and the wrong one:
a child that raises TypeError from inside its own body is
indistinguishable from one that refused the keyword, so the parent would
file a real failure as "this runner is old" and drop the correlation for
every later turn as well.
Yes for the three shapes that can take it:
- the run signature names both parameters -- what this module's own adapter does;
- a
**kwargscatch-all, which is whatChildRunnerdocuments a proxy or a test double may have, and which forwards; - an explicit :data:
ACCEPTS_RUN_LINEAGEmarker, for a runner whose forwarding is real but whose signature cannot say so.
No for exactly one: a catch-all named :data:RETIRED_VAR_KEYWORD. That is
the retired inheritance argument of the shipped legacy engine, and its
catch-all refuses unknown keys rather than forwarding them -- it raises
naming the retirement. Sending lineage there turned a working delegation
into "Sub-agent 'x' failed: the state_overrides argument ... was retired",
an error string standing where the child's answer belonged.
Narrow on purpose. The rule is not "opt in to lineage", which would take it away from every proxy the protocol promises it to; it is "one named signature is known to refuse it".
Correlation is not lost for such a child. The adapter opens a run scope carrying the identity, which is where every other observer reads it.
Source code in src/symfonic/capabilities/delegation/runner_adapter.py
adapt_child ¶
Return something satisfying :class:ChildRunner, or raise.
Raising here is the point. The old behaviour accepted any object with a
run and surfaced the mismatch mid-turn, in the one place the framework
deliberately swallows failures -- so a broken composition looked like a
working one that got an unhelpful answer.
Source code in src/symfonic/capabilities/delegation/runner_adapter.py
speaks_runner_contract ¶
Does this child already accept run(query, ...)?
A **kwargs catch-all counts: the protocol is structural, and a proxy
that forwards everything is exactly the kind of child PrebuiltChild
documents itself as accepting.