Declare specialists when creating an orchestrator. HarnessX handles creating, running, and closing the specialist for each task; you do not write or register delegation wrappers.
import asyncio
from harnessx import Agent, AgentConfig, Limits, SubAgent
from harnessx.builtin.web import fetch_url
async def main():
async with Agent(
config=AgentConfig(
system_prompt="Delegate research when useful, then synthesize the findings.",
),
subagents=[
SubAgent(
name="researcher",
description="Read public URLs and summarize their evidence.",
config=AgentConfig(
system_prompt="Read the supplied URLs and cite your sources.",
limits=Limits(max_iterations=5),
),
tools=[fetch_url],
),
],
) as orchestrator:
result = await orchestrator.run("Read https://example.com and summarize it.")
result.raise_for_status() # RunFailed, RunAwaitingInput, or RunCancelled otherwise
print(result.output)
asyncio.run(main())
Definition and routing
SubAgent is a definition, not a running agent. Required fields:
name: 1–55 letters, digits, underscores, or hyphens, starting with a letter or underscore.description: tells the orchestrator when this specialist is useful.config: an explicitAgentConfig; its system prompt instructs the specialist.
Optional fields:
tools: the same functions, built-in names, definitions, or registry formats accepted byAgent.skills: a list of skill paths, loaded separately for each task.permission: permission to delegate; omitted means inherit the parent's manager.timeout_seconds: whole-task timeout, defaultDEFAULT_TIMEOUT_SECONDS(300 seconds), including child model and tool calls.
The SDK exposes delegate_<name>(task: str) to the model internally. The schema
and description tell the orchestrator how to request a specialist. Do not also
register a tool with that name; collisions and duplicate specialist names fail
during construction. No separate orchestrator class is necessary.
The model decides whether to delegate. Naming specialists does not force a fixed
sequence; write workflow code when you need a mandatory sequence. Use
examples/multi_agent.py for research, code review, and scoped file analysis.
Context, resources, and permissions
Each delegated task gets fresh memory, configuration, and a tool registry. It sees only its own prompt and the assigned task. Include the code, question, URLs, paths, or other context it needs in the task. The child's model/provider comes from its explicit config; it does not inherit the parent's model or injected provider.
Handler functions remain shared application objects: separate conversations do
not isolate files, databases, closures, or arbitrary Python code. Child-owned
providers and memory close on completion, failure, and cancellation. Skills may
add their own tool and prompt catalog; do not also register a Skill tool.
The parent's permission manager is shared, including its defaults, name overrides, session grants, and approval callback. Generated delegation permission is separate from each child tool's permission. Approving delegation does not approve its tools. The same tool-name override applies in parent and child. An ordinary manager defaults to ALLOW; use explicit permissions or another manager to change that policy.
Parent tools, hooks, middleware, extensions, MCP connections, and sandboxes are not automatically copied. The generated call uses the parent's normal tool pipeline; the child uses its own ordinary engine. Use the supported manual-wrapper pattern for advanced child dependencies and their lifecycle management. It remains useful for a specialist with custom tracing extensions or an injected provider.
Results, streaming, and limits
The child returns its completed output to the orchestrator, which continues and synthesizes a response. There is no conversation handoff. Non-completed child statuses and child failures become delegation errors; partial output is not reported as successful specialist work.
Generated delegation tools are non-concurrent and use manual replay policy. The
current engine serializes a tool batch containing such a delegation. Both
run and run_stream support the same behavior. Streaming reports ordinary
tool_call_start, tool_call_complete, and tool_result events for delegation;
child text does not appear as parent answer text.
Each specialist has its own iteration and cost limits. Parent RunResult.usage
and guardrail totals remain parent-only, not a combined team budget. Set child
limits explicitly with AgentConfig(limits=Limits(...)). A timeout cancels
waiting and closes child resources, but
cannot reverse external effects or forcibly stop a synchronous tool thread.
Durable execution boundary
AgentRuntime persists the outer delegation as an ordinary manual-replay tool
call. Committed results are reused on recovery. An interrupted delegation with an
unknown outcome stops for manual recovery rather than automatically starting a
new specialist. Live model reruns are not deterministic.
The child uses an ordinary, non-durable run. Child progress, internal approvals, intermediate artifacts, and conversations are not independently persisted by the parent runtime. A child ASK tool uses the approval callback and fails closed without one; it does not create a resumable approval in the parent runtime. Treat output referring to child-local artifacts accordingly; durable child-artifact transfer is not supplied by this API.
Rebind the same subagents definitions when restoring sessions or constructing
versioned runtime factories, just as you rebind tools. Session snapshots do not
serialize Python handlers. Keep definition changes under the application's normal
versioned deployment controls.
Independent child recovery, persistent specialist conversations, nested declarative teams, handoffs, and child-token streaming are outside this initial API.