Skip to Content

I trace the Claude Agent SDK with Arize Phoenix. How do I switch to Rius?

You switch the Claude Agent SDK from Arize Phoenix to Rius by replacing register() with rius.init() and tracing each task with an observe root span and one generation. The Claude Agent SDK runs the Claude Code runtime in a separate process, so Rius coverage for it is explicit rather than automatic. You get one trace per task, with the model, tokens and cost on the model call. Cost is computed by Rius from the model and token counts.

How it works

The Claude Agent SDK drives the Claude Code runtime outside your process. No model client runs in your code, so Rius auto-instrumentation has nothing to hook. Rius covers it with two explicit pieces, described on the Claude Agent SDK integration page:

  • An observe wrapper makes each task the root AGENT span. It replaces the agent decorator or span you used with Phoenix.
  • A manual generation wraps the query() loop. You fill it from the run’s ResultMessage with the token usage, the real model id and the collected answer. Rius then computes cost server-side, as for any generation.

This is the one integration in the integrations list that works this way. Most others are picked up automatically once installed.

The Phoenix concepts map across like this:

  • register(project_name=...) becomes rius.init(service_name=...). Rius groups traces by the service name, not by the Phoenix project attribute.
  • using_session(session_id) becomes with rius.session(session_id):.
  • @tracer.agent becomes @rius.observe(kind=rius.SpanKind.AGENT).

The general Phoenix mapping and a cutover checklist are in Migrate from Arize Phoenix. The overview of all paths is Migrate to Rius.

Set it up

  1. Swap the packages. The base SDK is enough, and no extra applies. The claude CLI must still be installed and authenticated.

    pip uninstall arize-phoenix-otel pip install glassflow-rius claude-agent-sdk
  2. Replace your Phoenix setup and agent wrapper with this. Delete the register() call and any Phoenix instrumentor calls for this agent, so only Rius records the run. Put your own Rius API key where ri_... is.

    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
  3. 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 an alias gives a generation with no cost.

  4. Pass the cache counts from the ResultMessage as they are, and keep provider="anthropic" on the generation. Rius then adds the cache counts to the input tokens for you. Without the cache fields, cache-heavy agent loops would undercount badly.

  5. Replace using_session with rius.session. The Claude Agent SDK’s own session_id for resumable conversations works well as the Rius session id.

    with rius.session(conversation_id): await handle_task(message)
  6. Run one task and open the trace in the console. You should see the root AGENT span and a generation with a full model id, tokens and cost.

What you get

Each task shows as one trace with this shape:

handle-task AGENT 5.0s └─ claude_agent_turn LLM 5.0s claude-haiku-4-5, tokens + cost
  • A root agent span per task, named by your observe wrapper, with the generation under it.
  • Tokens and cost on the model call. Cost is a list-price estimate in US dollars, computed by Rius. You never send a cost value.
  • Sessions. Traces from one conversation group under the session you set with rius.session.

The generation records the turn’s totals only. Tool calls, sub-turns and file operations happen inside the Claude Code process and produce no spans, so they do not appear as steps in the trace. Auth stays the CLI’s: an exported ANTHROPIC_API_KEY takes precedence over a claude.ai login.

When something else fits

  • If you rely on Phoenix evals, datasets, experiments, annotations or prompt management, keep Phoenix for those. Rius does not offer them, and the Phoenix migration guide shows how to send spans to both backends while you decide.
  • If you need a span for every tool call inside the Claude Code runtime, note that Rius records the turn as one generation with totals and no inner spans.
  • Rius cannot import your historical Phoenix traces. It starts from the first span you send.

Still stuck? Get help

Last updated on