Skip to Content
Rius

Can I trace the Claude Agent SDK without changing my code?

Rius traces the Claude Agent SDK with a small amount of code, not zero: one init() call, an observe wrapper for each task and one generation filled from the run result. Auto-instrumentation has nothing to hook here, because the SDK drives the Claude Code runtime in a separate process and no model client runs in yours. You still get one trace per task with the model, tokens and cost. The runtime’s inner steps do not appear.

How it works

Automatic tracing in Rius works by patching a model client or framework inside your process. The Claude Agent SDK sends its model calls from the Claude Code process, so there is nothing in your process to patch. Auto-instrumentation covers the other integrations, and the Integrations support matrix lists this one as manual tracing.

Two explicit pieces replace it:

  • 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 computes the cost on its side, as for any generation. You never send a cost value.

You still call init(). It sets your API key and service name, and with a global init() it turns on every installed integration for other libraries in the same process. It does not attach to the Claude Agent SDK itself.

In the frameworkIn the trace
A taskThe root AGENT span from your observe wrapper
The whole runtime turnOne generation with model, tokens and cost
Tool calls, sub-turns and file operations inside the runtimeNothing

Set it up

  1. Install the SDK next to the Claude Agent SDK. The base package is enough, because no extra applies. The claude CLI must be installed and authenticated.
pip install glassflow-rius claude-agent-sdk
  1. Call init() once at startup, wrap each task in observe with the agent kind, and wrap the query() loop in a generation. The Claude Agent SDK integration page covers the setup in full.
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
  1. 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. Cost is computed from the requested model, and the price table matches real model ids only, so an alias gives a generation with no cost.

  2. Pass the cache counts from the ResultMessage as they are, and keep provider="anthropic" on the generation. Anthropic reports an input_tokens that excludes cache reads and writes. With that provider, set_usage() adds the two cache counts to the input tokens for you, so cache-heavy agent loops are not undercounted.

  3. Scope each conversation with rius.session(). The SDK’s own session_id works well as the Rius session id.

  4. 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.

What you get

  • One trace per task, with the AGENT span at the root and a single generation under it, for example handle-task over claude_agent_turn.
  • The model, input and output tokens, and cache reads and writes on that generation. The real model id comes from the run result.
  • Cost per task as a list-price estimate in US dollars, which tracks the run’s own reported cost when you pass the cache counts as shown.
  • Sessions that group a conversation’s tasks together.
  • The same privacy controls that apply to your other spans.

The generation records the turn’s totals only. Tool calls, sub-turns and file operations inside the Claude Code process produce no spans, so a trace shows what a task cost, not what the runtime did step by step.

When something else fits

If your agent calls a model client or framework in your own process, such as OpenAI, Anthropic or LangChain, auto-instrumentation needs only the integration’s extra and init(), with no wrappers. If you need step-level detail from inside the Claude Agent SDK runtime, this integration does not provide it, and you can add your own observe wrappers around the code you control to get structure there.

Still stuck? Get help

Last updated on