Skip to content

Subagents

A subagent is a child agent that the parent can invoke as a tool. Register an AgentSpec and caw exposes it to the parent automatically; when the parent calls it, the subagent runs its own session and its full trajectory is captured.

from caw import Agent, AgentSpec

reviewer = AgentSpec(
    name="security_reviewer",
    description="Reviews code for security issues",
    system_prompt="You are a security expert. Review the given code.",
)

agent = Agent()
agent.add_subagent(reviewer)
traj = agent.completion("Review the auth module for vulnerabilities")

Nested trajectories and usage roll-up

Each subagent invocation attaches a nested Trajectory to the parent's ToolUse block, so you can inspect what the child did:

for sub in traj.subagent_trajectories:
    print(f"  subagent: {sub.agent}, {sub.num_turns} turns, ${sub.usage.cost_usd:.4f}")

Usage rolls up: traj.usage is what the parent's session was billed for, while traj.total_usage adds every nested subagent that was billed separately (recursively). This is the number to use for total cost.

Claude Code's own Agent subagents

The claude CLI has a subagent tool of its own, and an agent will reach for it whether or not you registered any AgentSpec. Those run inside the parent's CLI session, which makes them a different shape from the above:

  • Their tokens and dollars are already inside the parent's usage — the CLI reports one bill per session, and traj.usage now covers the whole of it. So caw marks each harvested trajectory usage_in_parent=True and total_usage skips it. Do not add these up yourself.
  • Their transcripts never reach the parent's stream — it sees only "Async agent launched successfully". caw reads them back off disk from the CLI's own projects/<slug>/<session>/subagents/ directory and attaches each to the ToolUse that spawned it, so traj.subagent_trajectories and both viewers show them like any other.
  • A per-subagent cost_usd is therefore a share of the parent's, apportioned by tokens (metadata["cost_basis"] == "allocated"), not a separately measured figure.
  • traj.metadata carries model_usage (per-model tokens for the whole session, so a run can be priced exactly rather than at a blended rate) and subagent_calls / subagent_stats (how wide the fan-out went, straight from the CLI).

Set CAW_NATIVE_SUBAGENTS=0 to skip the harvest, or CAW_NATIVE_SUBAGENT_MAX_BYTES to change the per-transcript ceiling (32 MB) above which a sidechain is left out of the trajectory.

Configuring a subagent

AgentSpec carries the same knobs as an Agent: system_prompt, model, reasoning, tools, plus its own tool_servers, mcp_servers, and even nested subagents. That means a subagent can have its own tools and its own children.

AgentSpec(
    name="researcher",
    description="Searches the web and summarizes findings",
    system_prompt="You research topics thoroughly.",
    model="opus",
    tools=ToolGroup.READER | ToolGroup.WEB,
)

Full example

examples/subagent.py shows a senior-engineer agent delegating code review to a subagent and inspecting the nested trajectory:

"""Subagent demo: a parent agent delegates code review to a subagent."""

import os

os.environ["CAW_LOG"] = "full"

from caw import Agent, AgentSpec


def main():
    reviewer = AgentSpec(
        name="Code Reviewer",
        description="Review code for correctness and style issues.",
        system_prompt="You are a code reviewer. Given code, identify bugs and style issues. Be concise.",
    )

    agent = Agent(
        system_prompt="You are a senior engineer. Use the Code Reviewer tool to review code when asked.",
        data_dir="caw_data",
    )
    agent.add_subagent(reviewer)

    with agent.start_session() as session:
        turn = session.send("Review this Python function:\n\ndef add(a, b):\n    return a - b\n")

        traj = session.trajectory
        print(f"\nParent own usage: ${traj.usage.cost_usd:.4f}")
        print(f"Parent total usage (with subagents): ${traj.total_usage.cost_usd:.4f}")
        print(f"Parent total tokens: {traj.total_usage.total_tokens}")

        for tc in turn.tool_calls:
            if tc.subagent_trajectory:
                st = tc.subagent_trajectory
                print(f"\n  Subagent '{tc.name}':")
                print(f"    Model: {st.model}")
                print(f"    System prompt: {st.system_prompt[:60]}...")
                print(f"    Tool calls: {st.total_tool_calls}")
                print(f"    Usage: ${st.usage.cost_usd:.4f} ({st.usage.total_tokens} tokens)")


if __name__ == "__main__":
    main()