Skip to Content

What should I trace in a multi-agent workflow?

In a multi-agent workflow, Rius shows one trace per run as a tree: a root span for the run, a span for each agent, a generation for each model call, and a span for each tool call or step. Set a session id on the run and a user id on the request so every span carries them. You send token counts and Rius computes the cost.

How it works

A trace is one end-to-end run and has exactly one root span. The root’s status and duration are the run’s status and latency, so a failed root means a failed run. Everything else nests under it through standard OpenTelemetry parent context, which gives you the waterfall. The concepts page defines the model in full.

For a multi-agent run, trace these layers:

  • The root span per run. Wrap the entry point, the function that receives the request, in observe with the AGENT kind. This is the span run-level views read.
  • One agent span per sub-agent. Each time one agent hands work to another, open a nested AGENT span. The agent_name argument names the agent the span invokes. If you leave it out, it defaults to the configured agent name, never to the function name.
  • A generation for each model call. A generation is an LLM span with the requested model, provider, token usage and finish reasons, and optionally the input and output messages. Console and alert counts of LLM calls count only these spans.
  • A span for each tool call. Use the TOOL kind and pass tool_name. A tool span records the agent that executes it, taken from the innermost enclosing AGENT span opened with observe or a scoped span helper. A span opened with the manual start_span never becomes current, so it does not name the tools that run while it is open.
  • A span for each step. Retrieval is RETRIEVER, embeddings are EMBEDDING, reranking is RERANKER, and any other stage is the default CHAIN. Use tool_call_id on a tool span to join it back to the generation that asked for it.

On top of the tree, a session groups the traces of one conversation or job, and a user identifies the person the request served. Each turn of a chat is its own trace, and the session ties the turns together.

Set it up

  1. Install the SDK and call rius.init once at startup, as the SDK pages show. Auto-instrumentation turns on for the libraries you have installed, so many model calls already appear as generations.
  2. Wrap your entry point and each sub-agent in observe with the AGENT kind. Wrap each tool with the TOOL kind and a tool_name. The names below are examples, use your own.
import rius from rius import SpanKind @rius.observe(kind=SpanKind.AGENT, agent_name="planner") def plan(query: str) -> str: ... @rius.observe(kind=SpanKind.TOOL, tool_name="search_orders") def search_orders(order_id: str) -> str: ...
  1. For a model call you make yourself, open a generation and record usage. Pass token counts, never cost. Use a manual generation only for a call no integration traces. If the openai integration is installed, drop this block or leave openai out of rius.init(instruments=[...]), or the call’s tokens and cost are counted twice.
with rius.start_as_current_generation( model="gpt-4o", provider="openai", model_parameters={"temperature": 0.2, "max_completion_tokens": 512}, ) as gen: gen.set_input(messages) response = client.chat.completions.create(model="gpt-4o", messages=messages) gen.set_output(response.choices[0].message.content) gen.set_usage( input_tokens=response.usage.prompt_tokens, output_tokens=response.usage.completion_tokens, )
  1. Scope the user around each request and the session around each unit of work, so every span inside carries both:
with rius.user(request.user_id): with rius.session(conversation_id): handle_turn(message)
  1. Check the details. Pass provider="anthropic" for every Claude call so cache tokens are counted correctly. Prefer an opaque user id to an email. Do not set a process-wide session id in a server that handles many users, because their traces would merge into one session. TypeScript has the same pieces: observe, startAsCurrentGeneration, withSession and withUser. Tracing your code has every option in both languages.

What you get

  • One tree per run. The waterfall shows which agent called which tool and which model call fed which step. When a span errors, the path from the root to the failing span is highlighted.
  • Run-level health. The root span gives you the status and latency of each run, and the nested agent spans show where the time went.
  • Cost per call. Rius prices each generation from the model and token counts, with cache reads and writes at their own rates. Costs are list-price estimates in US dollars.
  • Sessions and users. Traces group by session.id across turns. The same user.id follows a person across conversations. A span with no user is anonymous, and a trace with no session is its own session.
  • Tool and step visibility. Each tool, retrieval and step is its own span with a name, timing and error type, so you see which one failed.

Monitoring the finished runs is covered in Monitor AI agents in production.

When something else fits

  • If your agents run on a supported framework or client library, auto-instrumentation already creates the model-call spans, and you only need manual spans for your own steps and the session and user scopes.
  • If a run is one model call with no agents or tools, a single generation is enough, and you do not need a nested agent tree.

Still stuck? Get help

Last updated on