Claude Agent SDK
Trace agents built with the Claude Agent SDK with Rius. This framework is different from the others on this page’s siblings: it drives the Claude Code runtime in a separate process, so no model client runs in your process and auto-instrumentation has nothing to hook. Coverage is manual, and still gives you per-task traces with tokens and cost. The runnable example behind this guide lives in agent-observability-examples .
How coverage works
Two pieces, both explicit:
- An
observewrapper makes each task the rootAGENTspan. - A manual generation wraps the
query()loop and is filled from the run’sResultMessage: token usage, the real model id, and the collected answer. Cost is then computed server-side as for any generation.
Setup
pip install glassflow-rius claude-agent-sdkThe base SDK is enough; no extra applies. The claude CLI must be
installed and authenticated.
import rius
from claude_agent_sdk import (
AssistantMessage, ClaudeAgentOptions, ResultMessage, TextBlock, query,
)
from rius import SpanKind
rius.init(api_key="gf_...", 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"),
)
models = list((message.model_usage or {}).keys())
if models:
gen.set_response_model(models[0])
answer = "".join(parts)
gen.set_output(answer)
return answerUse a full model id (claude-haiku-4-5), not an alias (haiku), for
both the CLI option and the generation. Cost is computed from the
requested model, and the pricing table matches real model ids only; the
alias produces a costless generation.
What lands where
| In the framework | In the trace |
|---|---|
| A task | The root AGENT span (your observe wrapper) |
| The whole runtime turn | One generation with model, tokens, cost |
| The runtime’s internal steps | Nothing; see blind spots |
A task looks like this (real trace from the example, claude-agent-sdk
0.2.148):
handle-task AGENT 5.0s
└─ claude_agent_turn LLM 5.0s claude-haiku-4-5, tokens + costBlind spots
Verified against claude-agent-sdk 0.2.148:
- 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.
- Cached tokens are not recorded.
set_usagecarries input and output tokens; the ResultMessage’s cache token counts have nowhere to go, so recorded cost undercounts cache-heavy runs relative to the ResultMessage’s owntotal_cost_usd. - Auth is the CLI’s. The runtime uses whatever auth Claude Code has;
an exported
ANTHROPIC_API_KEYtakes precedence over a claude.ai login.
Sessions
One conversation is one session: scope each task with
rius.session(). The SDK’s own
session_id (resumable conversations) makes a natural Rius session id.
Verify
Run one task, then open the trace in the console: the root AGENT span
and the generation with a full model id, tokens, and cost should be there.
A generation without cost means an alias was used as the model id.