How do I trace the Claude Agent SDK in Python?
Rius traces the Claude Agent SDK in Python with an observe wrapper that makes each task the root span and a manual generation that records the model, tokens and cost from the run result. You install glassflow-rius next to claude-agent-sdk, call rius.init() once, and wrap your query() loop. Each task then shows up as a trace with an agent span and one model-call span underneath it.
How it works
The Claude Agent SDK drives the Claude Code runtime in a separate process. No model client runs inside your own process, so auto-instrumentation has nothing to hook. Rius covers it with two explicit pieces:
- An
observewrapper withkind=SpanKind.AGENTmakes each task the root agent span. - A manual generation wraps the
query()loop. You fill it from the run’sResultMessagewith the token usage, the real model id and the collected answer.
Rius computes cost on its side from the model and token counts, as for any generation. You never send a cost value. Costs are list-price estimates in US dollars.
The base SDK is enough. No extra applies, and the integration is listed with the others on the Integrations page.
Set it up
-
Install the SDK next to the Claude Agent SDK. Python 3.10 or newer is required, and the
claudeCLI must be installed and authenticated. The runtime uses whatever auth Claude Code has, and an exportedANTHROPIC_API_KEYtakes precedence over a claude.ai login.pip install glassflow-rius claude-agent-sdk -
Call
rius.init()once, as early as possible, then wrap your task. The code below is a complete minimal traced call. Setservice_nameexplicitly, because it is how you find your agent later. The installation page covers every otherinit()option.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. Rius prices from the requested model, and its pricing table matches real model ids only. An alias produces a generation with no cost. -
Pass the cache counts from the
ResultMessageas they are, and keepprovider="anthropic"on the generation. Anthropic reports aninput_tokensfigure that excludes cache reads and writes. With that provider,set_usage()adds the two cache counts to the input tokens for you, so you do no arithmetic. Cache-heavy runs, which covers most agent loops, would otherwise undercount badly. -
Optional: scope each conversation with
rius.session()so its tasks group as one session. The Claude Agent SDK’s ownsession_idfor resumable conversations works well as the Rius session id. -
Run one task and open the trace in the console. You should see the root agent span and the generation with a full model id, tokens and cost. A generation without cost means an alias was used as the model id.
What you get
Each task becomes one trace with this shape:
handle-task AGENT 5.0s
└─ claude_agent_turn LLM 5.0s claude-haiku-4-5, tokens + cost- The root agent span is your
observewrapper, named after the task handler and timed for the whole task. - The generation is the whole runtime turn. It carries the model, input and output tokens, cache read and write tokens, the answer you collected and the computed cost.
- The response model comes from the run result, so you see the model that actually answered.
The runtime’s inner work does not appear. Tool calls, sub-turns and file operations happen inside the Claude Code process and produce no spans, so the generation records the totals for the turn only.
From there you can filter and compare these traces by the service name you set, as with any other agent. For the full list of supported libraries and frameworks, see Integrations.
When something else fits
- If your agent calls a model client such as Anthropic or OpenAI directly inside your own process, auto-instrumentation records those calls without manual generations. The Integrations page lists them.
- If you need a span for each tool call inside the Claude Code runtime, this integration does not provide it. It records the turn’s totals only.