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
observewrapper makes each task the rootAGENTspan. - 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 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 framework | In the trace |
|---|---|
| A task | The root AGENT span from your observe wrapper |
| The whole runtime turn | One generation with model, tokens and cost |
| Tool calls, sub-turns and file operations inside the runtime | Nothing |
Set it up
- Install the SDK next to the Claude Agent SDK. The base package is enough, because no extra applies. The
claudeCLI must be installed and authenticated.
pip install glassflow-rius claude-agent-sdk- Call
init()once at startup, wrap each task inobservewith the agent kind, and wrap thequery()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-
Use a full model id such as
claude-haiku-4-5, not an alias such ashaiku, 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. -
Pass the cache counts from the
ResultMessageas they are, and keepprovider="anthropic"on the generation. Anthropic reports aninput_tokensthat 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. -
Scope each conversation with
rius.session(). The SDK’s ownsession_idworks well as the Rius session id. -
Run one task and open the trace in the console. You should see the root
AGENTspan 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
AGENTspan at the root and a single generation under it, for examplehandle-taskoverclaude_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.