Skip to Content

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 observe wrapper makes each task the root AGENT span.
  • A manual generation wraps the query() loop. You fill it from the run’s ResultMessage with 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 frameworkIn the trace
A taskThe root AGENT span, your observe wrapper
The whole runtime turnOne generation with model, tokens and cost
Tool calls, sub-turns and file operations inside the runtimeNo 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 + cost

Tool 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

  1. Install the SDK next to the Claude Agent SDK. The base package is enough, and no extra applies. The claude CLI must be installed and authenticated.

    pip install glassflow-rius claude-agent-sdk
  2. Wrap the task in an AGENT span and the query() loop in a generation. Fill the generation from the ResultMessage:

    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
  3. Use a full model id such as claude-haiku-4-5, not an alias such as haiku, for both the CLI option and the generation. Pricing matches real model ids only, so an alias gives a generation with no cost.

  4. Pass the cache counts as the snippet does and keep provider="anthropic". Anthropic reports an input count that excludes cache reads and writes, and set_usage() adds them back for you. Without the cache fields, cache-heavy agent loops would undercount badly.

  5. Scope each conversation with rius.session(). The SDK’s own session_id makes a natural Rius session id.

  6. 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 observe and 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.

Still stuck? Get help

Last updated on