Sub-Agents
Sub-agents are full agents that can be exposed to a parent agent as callable tools. A parent delegates with a natural-languagemessage; the sub-agent then runs its own agent loop, prompt, tools, memory scope, knowledge-base assignments, model settings, and lifecycle events.
The default parent is MainAgent, but the relationship is explicit. A sub-agent is callable only from the parent agents it is assigned to.
Built-in agents
System agents are seeded from code and registered withAgentFactory.register_implementation() during startup:
| Agent | Role | Notes |
|---|---|---|
MainAgent | Root orchestrator | Receives user turns and delegates domain work |
EmailsAgent | Sub-agent | Email search, drafting, sending, replies, and thread work |
MeetingsAgent | Sub-agent | Calendar, scheduling, availability, meetings, and transcripts |
RecordsAgent | Sub-agent | CRM/account/contact/opportunity and pipeline work |
TasksAgent | Sub-agent | Task listing, creation planning, updates, and follow-ups |
ResearchAgent | Sub-agent | Web and account intelligence research |
SnowflakeAgent | Sub-agent | Snowflake data analysis and query workflows |
JediAgent | Internal hidden sub-agent | Used by the jedi_council tool; not exposed to MainAgent because its parent list is empty |
GeneralSubAgent. If the orchestrator cannot match a request to an available specialist, it should answer that it lacks the right capability instead of delegating to a catch-all.
Define a system sub-agent
Backend framework extensions use the@sub_agent decorator on a SubAgent subclass:
parents parameter stores parent agent names for bootstrap seeding. It does not immediately mutate an in-memory parent at decoration time. The startup flow imports agent classes, registers implementations in agents/__init__.py, then tenant bootstrap upserts agents and parent-child relationships into the tenant database.
Create a dynamic sub-agent
Tenant-managed sub-agents are created through the API or SDK and do not require Python code:knowledge_bases are KB IDs. tool_assignments on create/update are tool names stored in sub-agent metadata; the dedicated assign/remove endpoints use tool UUIDs as tool_identifier.
Delegation modes
Sub-agent calls use a small tool schema:| Field | Meaning |
|---|---|
message | The task or question to delegate |
model | Optional per-call model override |
temperature | Optional per-call temperature override |
blocking | true waits in the current turn; false queues background work |
Blocking delegation
Withblocking=true, the parent waits for the sub-agent result in the same turn:
- The parent emits a tool call named after the sub-agent.
- AgentFlow creates a child execution with its own
call_id, the parent’scall_idasparent_call_id, and the sameroot_call_id. - The delegated query is persisted as a user part scoped under the sub-agent call.
- The sub-agent receives scoped prior history for that same sub-agent before the current invocation.
- The sub-agent runs normally, including its own tools and retrieval.
- Its final answer becomes the parent tool result, and the parent continues.
Non-blocking delegation
Withblocking=false, the current turn receives a task handle instead of waiting:
end metrics also include task IDs under custom_metrics.background_tasks.
Parent and sub-agent communication
Blocking calls communicate through the returned tool result. Non-blocking calls add three helper tools onMainAgent:
| Tool | Purpose |
|---|---|
get_background_sub_agent_result | Poll task status and optionally return terminal output |
fetch_sub_agent_progress | Ask a read-only progress question using an LLM pass over recent sub-agent history |
message_sub_agent | Persist a parent message for a running background sub-agent, optionally waiting for the next reply |
fetch_sub_agent_progress is the non-interfering side channel: it does not mutate the child run, consume a child turn, or interrupt in-flight work. It asks a separate LLM pass to answer from the sub-agent’s recent persisted history.
message_sub_agent writes a user part scoped to the sub-agent’s execution. The running sub-agent sees that message at its next turn boundary, replies when useful, and then continues the original delegated task unless the parent changes or cancels it. Set mode="send" to save the message and return immediately, or mode="send_and_wait" to save the message and wait for the next persisted child reply.
Questions are a separate user-interaction mechanism. A tool can emit question_required; the run waits for POST /api/v1/questions/{question_id}/respond or the SDK’s client.questions.respond(...). Answers are validated against the emitted question IDs and option IDs. The active waiter is process-local, and pending question requests/responses are mirrored through shared Redis when configured so another worker can accept the response and wake the original run.
Event hierarchy
Every event carriescall_id, parent_call_id, and root_call_id:
verbose=false suppresses nested tool and sub-agent lifecycle events from the live stream, while root events and user-interaction events still surface. verbose=true emits the nested start, delta, end, and error events so timeline UIs can render the full tree.
Depth is bounded by MAX_SUB_AGENT_DEPTH; exceeding it raises an error to prevent recursive delegation loops.
Scoped memory and history
Sub-agents do not inherit the parent’s entire scratchpad. On delegated runs, AgentFlow rebuilds scoped history from persisted conversation parts for the same sub-agent/tool name before the current invocation. This letsTasksAgent remember earlier task-specific delegations without mixing in unrelated EmailsAgent or parent-only reasoning. Context blocks and user memory are also rendered for the sub-agent as its own agent, so cache keys are agent-scoped.

