How do I see every tool call in an agent built with the Claude Agent SDK?
Rius traces each Claude Agent SDK task as a root agent span with one generation that carries the turn’s model, tokens and cost. The individual tool calls inside the Claude Code runtime do not appear as spans, so Rius does not show every tool call for this framework. You see the task, the turn’s totals and the answer, plus any step you wrap yourself, in the trace waterfall and on the Spans tab.
How it works
The Claude Agent SDK drives the Claude Code runtime in a separate process. No model client runs in your process, so auto-instrumentation has nothing to hook, and coverage is manual. Two explicit pieces make up a trace:
- An
observewrapper makes each task the root AGENT span. - A manual generation wraps the
query()loop. You fill it from the run’sResultMessagewith token usage, the real model id and the collected answer. Rius then computes cost on the server, as for any generation.
This is what lands where:
| In the framework | In the trace |
|---|---|
| A task | The root AGENT span, your observe wrapper |
| The whole runtime turn | One generation with model, tokens and cost |
| Tool calls, sub-turns and file operations inside the runtime | No spans. The generation records the turn’s totals only |
A task looks like this:
handle-task AGENT 5.0s
└─ claude_agent_turn LLM 5.0s claude-haiku-4-5, tokens + costTool spans appear only for work that runs in your own process. If a function in your own Python code does the work, wrap it with observe and the TOOL kind, and it shows as a tool step under the task. The runtime’s own tool calls stay inside the generation’s totals.
Set it up
-
Install the SDK next to the Claude Agent SDK. The base package is enough, and no extra applies. The
claudeCLI must be installed and authenticated.pip install glassflow-rius claude-agent-sdk -
Wrap the task in an AGENT span and the
query()loop in a generation. Fill the generation from theResultMessage:import rius from claude_agent_sdk import ( AssistantMessage, ClaudeAgentOptions, ResultMessage, TextBlock, query, ) from rius import SpanKind rius.init(api_key="ri_...", service_name="my-agent") MODEL = "claude-haiku-4-5" @rius.observe(name="handle-task", kind=SpanKind.AGENT) async def handle_task(task: str) -> str: options = ClaudeAgentOptions(model=MODEL, max_turns=1) parts = [] with rius.start_as_current_generation( "claude_agent_turn", model=MODEL, provider="anthropic", input=task ) as gen: async for message in query(prompt=task, options=options): if isinstance(message, AssistantMessage): parts.extend( b.text for b in message.content if isinstance(b, TextBlock) ) elif isinstance(message, ResultMessage): usage = message.usage or {} gen.set_usage( input_tokens=usage.get("input_tokens"), output_tokens=usage.get("output_tokens"), cache_read_input_tokens=usage.get("cache_read_input_tokens"), # Anthropic's payload still calls the write count "cache creation" cache_write_input_tokens=usage.get("cache_creation_input_tokens"), ) models = list((message.model_usage or {}).keys()) if models: gen.set_response_model(models[0]) answer = "".join(parts) gen.set_output(answer) return answer -
Use a full model id such as
claude-haiku-4-5, not an alias such ashaiku, for both the CLI option and the generation. Pricing matches real model ids only, so an alias gives a generation with no cost. -
Pass the cache counts as the snippet does and keep
provider="anthropic". Anthropic reports an input count that excludes cache reads and writes, andset_usage()adds them back for you. Without the cache fields, cache-heavy agent loops would undercount badly. -
Scope each conversation with
rius.session(). The SDK’s ownsession_idmakes a natural Rius session id. -
Run one task and open the trace. You should see the root AGENT span and the generation with a full model id, tokens and cost.
What you get
- One trace per task. Open it in the waterfall to see the AGENT span, the generation under it, and the header with duration, span count, total tokens and cost. The exploring traces page covers the waterfall, the map view and the Spans tab.
- A Model section on the generation with provider, requested and response model, input and output tokens, and cost. Cost is a list-price estimate in US dollars that Rius computes, so you never send it.
- The task text and the collected answer on the generation’s Input / Output tab, unless you strip content with the SDK’s privacy controls.
- Every task of a conversation grouped under one session, which links to all of its spans.
- On the Spans tab, the All kinds menu filters by Agent, LLM, Tool and other kinds. For this framework, Tool spans appear only for functions you wrap yourself.
For the wider picture of watching agents run, see Monitor AI agents in production.
When something else fits
- If you need a span for every tool call, trace the tool functions that run in your own code with
observeand the TOOL kind. The runtime’s internal tool calls are not recorded as spans. - If your agent calls a model client or framework directly, the Integrations page lists which ones are traced automatically. The Claude Agent SDK page has the full setup and the known blind spots.