What does Rius record when my agent calls the Claude Agent SDK?
Rius records each Claude Agent SDK task as an agent trace with one generation for the whole runtime turn, carrying the model, token counts, cost and the answer text. The Claude Agent SDK drives the Claude Code runtime in a separate process, so you add two small pieces of tracing code yourself and Rius fills in the rest. Tool calls and other work inside that runtime do not appear as spans.
How it works
The Claude Agent SDK runs the Claude Code runtime outside your process. No model client runs in your Python process, so auto-instrumentation has nothing to hook. Coverage is manual, in two pieces:
- An
observewrapper makes each task the rootAGENTspan. - A manual generation wraps the
query()loop. You fill it from the run’sResultMessagewhen it arrives.
This is what lands in the trace:
| In the Claude Agent SDK | In the Rius trace |
|---|---|
| A task | The root AGENT span, from your observe wrapper |
| The whole runtime turn | One generation with model, tokens and cost |
| The runtime’s internal steps | Nothing |
On the generation, Rius records:
- Input: the task text you pass as the generation’s input.
- Output: the answer, which your code collects from the text blocks of each assistant message.
- Requested model: the model id you set on the generation, with
anthropicas its provider. - Response model: the real model id, taken from the
ResultMessage’s model usage. - Tokens: input, output, cache read and cache write counts, taken from the
ResultMessage’s usage.
Cost is not something you send. Rius computes it on the server from the requested model and the token counts, as for any generation. It is a list-price estimate in US dollars, not a billed amount.
Cache counts matter here. Anthropic reports an input token count that excludes cache reads and writes. Because the generation’s provider is anthropic, set_usage() adds the two cache counts to the input tokens for you. You pass the ResultMessage values as they are, with no arithmetic, and the computed cost tracks the ResultMessage’s own total_cost_usd. A trivial one-turn run can report 10 input tokens next to 24,000 cache tokens, so leaving the cache fields out would badly undercount.
A trace looks like this, from a real run of the example:
handle-task AGENT 5.0s
└─ claude_agent_turn LLM 5.0s claude-haiku-4-5, tokens + costSet it up
- Install the SDK next to the Claude Agent SDK. The base package is enough, no extra applies. The
claudeCLI must be installed and authenticated, because the runtime uses whatever auth Claude Code has. An exportedANTHROPIC_API_KEYtakes precedence over a claude.ai login.
pip install glassflow-rius claude-agent-sdk- Wrap each task with
observeas anAGENTspan, and open a generation around thequery()loop. Fill the generation’s usage and response model from theResultMessage, and set its output to the collected answer.
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. The pricing table matches real model ids only, so an alias gives you a generation with no cost. Keepprovider="anthropic"on the generation so the cache counts are added to the input tokens. -
Group a conversation by scoping each task with
rius.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
AGENTspan and the generation with a full model id, tokens and cost. A generation without cost means an alias was used as the model id.
The Claude Agent SDK integration is Python only today. The full recipe is on the Claude Agent SDK integration page.
What you get
- One trace per task, with a root agent span you can name and filter on.
- A single generation per runtime turn showing the requested model, the real response model, input, output, cache read and cache write tokens, and the question and answer text.
- A cost estimate for that turn, computed by Rius from the model and the tokens, with cache reads and writes counted.
- Conversation grouping through sessions when you wrap tasks in
rius.session().
The limits to plan around:
- The runtime’s inner work is invisible. Tool calls, sub-turns and file operations happen inside the Claude Code process and produce no spans. The generation records the turn’s totals only, so you cannot filter this agent’s traces by tool.
- Coverage depends on your code. Rius sees only what you wrap, so a task without the
observewrapper and generation is not traced.
These limits were verified against claude-agent-sdk 0.2.148. For how the other integrations are set up, and which languages each one covers, see Integrations.
When something else fits
- If you need spans for each tool call or sub-step, the Claude Agent SDK runtime does not expose them, so this integration will not show them. Trace those steps with your own code where you control them.
- If your code calls the Anthropic API directly instead of driving the Claude Code runtime, the Anthropic client integration instruments those calls automatically, with no manual generation.