symfonic.agent.subagents¶
subagents ¶
Sub-agent delegation primitive.
Declare child agents a parent can delegate to via run_agent(name, task):
from symfonic.agent.subagents import SubAgent
parent = SymfonicAgent(
model_provider=provider,
config=FrameworkConfig(),
sub_agents=[
SubAgent(name="researcher", agent=research_agent,
description="Deep web research"),
],
)
Declaring sub_agents auto-registers a concrete AgentStore plus the
run_agent / list_agents tools before the graph compiles. Children are
isolated agents that inherit the parent's tenant scope and run under a
depth guard (max_agent_depth).
A child never holds a prompt-block write surface, whichever way it was
declared -- see :mod:symfonic.agent.subagents.lockdown.
SubAgent
dataclass
¶
A named child agent the parent may delegate to.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique routing key the parent's model passes to
|
agent |
_Runnable
|
The child agent — any object exposing |
description |
str
|
One-line summary shown to the parent model in the
|
when_to_use |
str | None
|
Optional longer guidance appended to the roster listing. |
SubAgentRegistry ¶
Concrete AgentStore that runs registered child agents in-process.
Satisfies the AgentStore protocol (list / read / run). The
run method inherits the parent's tenant scope into the child and
threads the (already incremented) agent depth so the child's own
delegation guard can enforce max_agent_depth for nested delegation.
Registration is the last point at which a child can be refused, so it
is where the block-edit lockdown lands for children the parent did not
build. Every child passes through here -- specs the parent constructed
and pre-built SubAgent(agent=...) instances alike -- which makes it
the one place the guarantee covers every construction path.
Source code in src/symfonic/agent/subagents/registry.py
run
async
¶
run(name: str, agent_input: Any, *, parent_scope: Any = None, agent_depth: int = 1, run_id: str | None = None, parent_run_id: str | None = None) -> Any
Run child name on agent_input; returns its AgentResponse.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Registered sub-agent name. |
required |
agent_input
|
Any
|
The delegated task (coerced to |
required |
parent_scope
|
Any
|
Tenant scope inherited from the parent run. |
None
|
agent_depth
|
int
|
Depth to stamp on the child run (parent depth + 1). |
1
|
run_id
|
str | None
|
The id the child runs under. Minted here when the caller
names none, so the value is known rather than left for the
child to invent privately -- |
None
|
parent_run_id
|
str | None
|
v9.12.0 (issue #64) the run delegating this task.
Recorded as the child's lineage; never passed as the child's
own |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
If |
Source code in src/symfonic/agent/subagents/registry.py
SubAgentSpec
dataclass
¶
SubAgentSpec(name: str, description: str, when_to_use: str | None = None, tools: Sequence[Any] = tuple(), domain_description: str | None = None, model_name: str | None = None, temperature: float | None = None, max_tokens: int | None = None, provider: Any | None = None, config: Any | None = None)
A declarative sub-agent: describe the child, let the parent build it.
v9.1.0 (issue #27). SubAgent requires a fully pre-constructed child
agent; SubAgentSpec closes that ergonomics gap -- you declare what
the child should be and the parent constructs the inner SymfonicAgent
at wiring time, inheriting the parent's :class:ModelProvider (#26A) and
base :class:FrameworkConfig via :meth:FrameworkConfig.child (#28). Pass
either form in SymfonicAgent(sub_agents=[...]); mix freely.
The child is an isolated agent with its own fresh domain (scoped to
name/domain_description) so its tool manifest auto-derives from its
own tools rather than leaking the parent's. Every behaviour flag
(auto_hydrate, lazy_tooling, enable_hms_prompt, ...) is inherited from the
parent unless a full config is supplied. For a child that needs shared
backends or bespoke wiring, build it yourself and use SubAgent(agent=...)
as the escape hatch.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique routing key the parent's model passes to
|
description |
str
|
One-line summary shown to the parent model in the
|
when_to_use |
str | None
|
Optional longer guidance appended to the roster listing. |
tools |
Sequence[Any]
|
Tools the child agent gets (its manifest auto-derives from them). |
domain_description |
str | None
|
Optional child |
model_name |
str | None
|
Optional child model override; inherits the parent model. |
temperature |
float | None
|
Optional child sampling temperature override. |
max_tokens |
int | None
|
Optional child max-output-tokens override. |
provider |
Any | None
|
Optional :class: |
config |
Any | None
|
Optional full :class: |
assert_no_block_edit_surface ¶
Raise if pre-built child agent can write a prompt block.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The child's routing key, for the error message. |
required |
agent
|
Any
|
The child agent being registered. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the child carries a block-edit tool, or a config requesting one. |
Source code in src/symfonic/agent/subagents/lockdown.py
deny_child_self_edit ¶
Return config with the block self-edit request cleared.
The parent's answer on every path where it builds the child's config itself. Returns the object unchanged when the flag is already off -- which is the default, so the common path allocates nothing and keeps config identity intact for callers that compare it.
A config that asks for self-edit but cannot be copied (a stub, a plain namespace) is refused rather than passed through: a lock that silently gives up on an input shape it did not expect is not a lock.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Any
|
The child |
required |
Returns:
| Type | Description |
|---|---|
Any
|
A config whose self-edit flag is |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the flag is set and |