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, andtraj.usagenow covers the whole of it. So caw marks each harvested trajectoryusage_in_parent=Trueandtotal_usageskips 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 theToolUsethat spawned it, sotraj.subagent_trajectoriesand both viewers show them like any other. - A per-subagent
cost_usdis therefore a share of the parent's, apportioned by tokens (metadata["cost_basis"] == "allocated"), not a separately measured figure. traj.metadatacarriesmodel_usage(per-model tokens for the whole session, so a run can be priced exactly rather than at a blended rate) andsubagent_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()