In Chapter 3: Skills & Capabilities System, we gave our Lead Agent "hands" (Skills) to interact with the world. It can now run SQL queries or read files.
However, some tasks are too big for one pair of hands.
Imagine asking the Lead Agent: "Create a fully functional Snake game in Python." If the Lead Agent tries to do this in the main chat, it will be messy. It has to write code, run it, see an error, rewrite it, run it again... The chat window would be flooded with 50 messages of trial-and-error code.
In this chapter, we introduce the Sub-Agent Execution System. This allows the Lead Agent to hire "Subcontractors" to handle complex jobs in the background.
Think of the Lead Agent as a General Contractor building a house.
When it's time to wire the electricity:
Benefits:
User: "Write a Snake game in Python."
To solve this, the Lead Agent will:
task tool to hire a "Coder" Sub-Agent.task Tool
The interface between the Lead Agent and the Sub-Agent is a specialized tool called task.
Unlike the "Skills" from Chapter 3 which run a specific Python script, the task tool spins up another AI Brain.
Let's look at how the Lead Agent calls for help. This is defined in backend/src/tools/builtins/task_tool.py.
Simplified Tool Logic:
@tool("task")
def task_tool(description: str, prompt: str, subagent_type: str):
"""
Delegate a complex task to a subagent.
args:
description: Short label for the UI (e.g., "Writing Snake Game").
prompt: Detailed instructions for the subagent.
subagent_type: Who to hire (e.g., "general-purpose", "bash").
"""
# 1. Configure the sub-agent
config = get_subagent_config(subagent_type)
# 2. Start the agent in the background
executor = SubagentExecutor(config=config, ...)
task_id = executor.execute_async(prompt)
# 3. Wait for results (Polling)
return wait_for_result(task_id)
Explanation:
prompt: This is the new "User Message" for the Sub-Agent.subagent_type: We can define different specialists. A bash agent might be good at terminal commands, while general-purpose is good at logic.How does this run without freezing the whole application? We use Background Threads.
When the Lead Agent calls the tool, we don't block the server. We spin off a separate thread where the Sub-Agent has its own conversation with the LLM.
executor.py)
The SubagentExecutor is responsible for setting up the new agent. It gives the Sub-Agent a fresh start but shares important data (like the file sandbox) from the parent.
Simplified Code (src/subagents/executor.py):
class SubagentExecutor:
def _create_agent(self):
# 1. Create a new independent AI model
model = create_chat_model(self.config.model)
# 2. Give it tools (But NOT the task tool - no recursive hiring!)
tools = get_available_tools(subagent_enabled=False)
# 3. Create the agent graph
return create_agent(model=model, tools=tools, ...)
Explanation: Notice step 2. We usually prevent Sub-Agents from hiring their own Sub-Agents to strictly control costs and complexity.
To keep the application responsive, the actual work happens in a ThreadPoolExecutor.
Simplified Code (src/subagents/executor.py):
def execute_async(self, task: str):
# 1. Generate a unique ID for this job
task_id = str(uuid.uuid4())
# 2. Run the heavy AI work in a separate thread
_execution_pool.submit(self.execute, task)
# 3. Return the ID immediately so we can track it
return task_id
task_tool.py)The Lead Agent's tool needs to wait for the Sub-Agent to finish. While it waits, it sends updates to the Frontend.
Simplified Code (src/tools/builtins/task_tool.py):
# Inside task_tool function
task_id = executor.execute_async(prompt)
while True:
# 1. Check status
result = get_background_task_result(task_id)
# 2. Send live updates to Frontend (See Chapter 1)
if new_messages_found(result):
send_frontend_update("task_running", result.last_message)
# 3. Check if finished
if result.status == "completed":
return f"Task Succeeded. Result: {result.result}"
time.sleep(5) # Wait 5 seconds before checking again
Explanation: This loop is why the user sees "Running..." updates in the UI. The task_tool is acting as a bridge, relaying messages from the hidden Sub-Agent thread to the visible Frontend stream.
We define what types of Sub-Agents exist in our config.yaml or app_config.py.
Simplified Config:
subagents:
types:
- name: "general-purpose"
system_prompt: "You are a problem solver. Solve the user's request."
timeout_seconds: 600
- name: "bash"
system_prompt: "You are a DevOps engineer. Use the terminal."
allowed_tools: ["terminal_tool"]
This allows us to create specialized workers. A bash sub-agent might only be allowed to use the terminal, making it safer and more focused than a general agent.
In this chapter, we scaled our AI's ability to work:
task tool acts as the hiring phone call.task tool polls for completion, streaming progress to the user.Now our agent can plan (Chapter 2), use tools (Chapter 3), and execute complex multi-step projects (Chapter 4).
However, whether it's the Lead Agent or a Sub-Agent running code, they are both executing commands on your computer. How do we ensure they don't accidentally delete your hard drive?
We need a safe place to play.
Next Chapter: Sandboxed Environment
Generated by Code IQ