Skip to content
TechBits
Go back

Building Coding Agent Part 4: Subagents – Clean Context per Subtask

Edit page

Welcome back to the series!

Where we left off: In the previous lessons, we built a sturdy foundation. We started with the core while loop, added a Dispatch Map for safe tool routing, and introduced the TodoWrite tool to force the agent to plan before it acts.

Our agent can now navigate multi-step tasks without losing its train of thought. But as it tackles larger codebases, we hit a new bottleneck: the context window.

The Problem: Context Bloat

As the agent works, its messages array grows. Every file it reads and every terminal command it runs stays in the conversation history permanently.

Imagine asking the agent, “What testing framework does this project use?” The agent might need to read 5 different configuration files and run a few grep commands to figure it out. All of that raw output clogs up the context window. But the parent agent only actually needs a one-word answer: “pytest”.

The Solution: Context Isolation

The motto for this lesson is: “Break big tasks down; each subtask gets a clean context”.

Instead of doing all the messy exploration itself, the parent agent can spawn a Subagent.

How It Works (In Code)

To make this work, we need to separate our tools into two tiers and create a function to run the child agent.

Step 1: Filtered Tools We give the parent agent a new tool called task. However, the child agent gets all the base tools except task. This prevents infinite recursive spawning, where a child spawns a child, which spawns a child, consuming tokens without making progress.

CHILD_TOOLS = [
    # bash, read_file, write_file, edit_file
]

PARENT_TOOLS = CHILD_TOOLS + [
    {
        "name": "task", 
        "description": "Spawn a subagent with fresh context. It shares the filesystem but not conversation history.",
        "input_schema": {
            "type": "object", 
            "properties": {"prompt": {"type": "string"}}, 
            "required": ["prompt"]
        }
    },
]

Note: For purely exploratory tasks, we can restrict the child even further by removing write_file and edit_file, enforcing the principle of least privilege so it doesn’t accidentally break things while looking around.

Step 2: The Subagent Loop When the parent calls the task tool, we trigger the run_subagent function. Notice how sub_messages starts fresh with just the prompt.

def run_subagent(prompt: str) -> str:
    # 1. Fresh context
    sub_messages = [{"role": "user", "content": prompt}]  
    
    # 2. Bounded execution loop (safety limit)
    for _ in range(30):  
        response = client.messages.create(
            model=MODEL, 
            system=SUBAGENT_SYSTEM, 
            messages=sub_messages,
            tools=CHILD_TOOLS, 
            max_tokens=8000,
        )
        sub_messages.append({"role": "assistant", "content": response.content})
        
        if response.stop_reason != "tool_use":
            break
            
        # ... tool execution logic here ...
        
    # 3. Discard history, return only the final text summary
    return "".join(b.text for b in response.content if hasattr(b, "text")) or "(no summary)"

The Key Insight

Process isolation gives context isolation for free. By spinning up a child loop with a blank array, we protect the parent’s clarity of thought. The parent stays focused on the high-level plan, while the subagent handles the noisy, token-heavy reality of navigating a codebase.


Edit page
Share this post on:

Previous Post
Building Coding Agent Part 5: Skill Loading – On-Demand Domain Knowledge
Next Post
Building Coding Agent Part 3: Visible Planning – Keeping the Agent on Course