Migrate from Traceloop to Rius
Traceloop’s SDK is OpenLLMetry, and OpenLLMetry is plain OpenTelemetry: it exports OTLP to whichever endpoint you give it. That makes migrating from Traceloop to Rius a configuration change. Keep your OpenLLMetry instrumentation, decorators included, and point its exporter at Rius: your agents, tools and LLM calls show up as such. This guide maps the concepts, walks the two migration paths, and ends with a cutover checklist. Run both backends in parallel during the evaluation; nothing here requires a hard switch.
TL;DR: Keep OpenLLMetry and change its export settings: the base URL,
the auth header, and, in Python, turning off metrics (Path A). Your @workflow, @task, @agent and @tool
decorators keep working. Swapping to the Rius SDK (Path B) is optional.
Fan out to both backends while you compare, then cut over.
Why migrate from Traceloop to Rius
The same OpenLLMetry traces you send Traceloop today gain session and user grouping, cost computed server-side, alerts, and an MCP server so your coding agent can query production traces. With the Rius SDK you also see which agent processes are alive, and, with partial spans turned on, runs still in progress.
Concept mapping: Traceloop vs Rius
| Traceloop | Rius | Notes |
|---|---|---|
| Trace | Trace | Same OpenTelemetry trace, same tree view. |
@workflow / withWorkflow span | CHAIN span | CHAIN is the span kind for a generic step. |
@task / withTask span | CHAIN span | Same as a workflow. |
@agent / withAgent span | AGENT span | Shown as an agent, named after the decorated function. |
@tool / withTool span | TOOL span | Shown as a tool, named after the decorated function. |
traceloop.entity.input / output | Span input and output | Rendered on the span’s Input / Output tab. |
LLM span (openai.chat, anthropic.chat) | Generation | Model, token counts, and cost computed server-side. |
Conversation (@conversation, set_conversation_id, withConversation) | Session | OpenLLMetry writes gen_ai.conversation.id, which Rius reads as the session. |
Association properties (user_id, chat_id, …) | Span attributes | Stored under traceloop.association.properties.* and inspectable, but not mapped to Rius users or sessions. |
| Project and environment | Agent (named by service.name) | Rius groups traces by agent, named by service.name. Give each environment (production, staging) its own workspace and API key. |
| Monitors, evaluators, guardrails, prompt registry, playgrounds, datasets, experiments | Not available | See what does not carry over. |
Choose your path
- Keep OpenLLMetry (recommended for most teams): change the export target and nothing else. Your decorators, auto-instrumentation and conversation ids carry over as they are. Path A.
- Move to the Rius SDK: worth it if you want Rius-native session and user scopes, the SDK’s privacy controls, or partial spans for long runs. The decorators map one to one. Path B.
Path A: keep OpenLLMetry (re-point the exporter)
Re-pointing switches off Traceloop’s platform features in Python.
Traceloop.init() builds its platform client only when the base URL is
Traceloop’s. With the base URL set to Rius, Traceloop.get() raises, so
get_prompt(), guardrails, user feedback, datasets and experiments stop
working. If your code uses any of them, keep OpenLLMetry pointed at
Traceloop and add Rius through the
Collector fan-out instead.
OpenLLMetry sends traces to TRACELOOP_BASE_URL (default
https://api.traceloop.com) with your TRACELOOP_API_KEY as a Bearer
token, and appends /v1/traces itself. Rius takes OTLP/HTTP with a Bearer
API key too, so the change is the base URL and the header:
# Before: Traceloop
export TRACELOOP_API_KEY="<your Traceloop API key>"
# After: Rius
export TRACELOOP_BASE_URL="https://ingest.eu.console.rius-glassflow.com"
export TRACELOOP_HEADERS="Authorization=Bearer%20<your Rius API key>"
export TRACELOOP_METRICS_ENABLED="false" # Python SDK only, see belowRemove TRACELOOP_API_KEY from your deployment: the Rius header replaces
it. TRACELOOP_HEADERS takes key=value pairs separated by commas, with %20
for the space. Or set the same values in code:
import os
from traceloop.sdk import Traceloop
Traceloop.init(
app_name="my-agent",
api_endpoint="https://ingest.eu.console.rius-glassflow.com",
headers={"Authorization": f"Bearer {os.environ['RIUS_API_KEY']}"},
)import * as traceloop from '@traceloop/node-server-sdk'
traceloop.initialize({
appName: 'my-agent',
baseUrl: 'https://ingest.eu.console.rius-glassflow.com',
headers: { Authorization: `Bearer ${process.env.RIUS_API_KEY}` },
})app_name / appName becomes the service.name, which names the
agent your traces are grouped under in Rius. Three details
trip people up:
- Leftover environment variables win in Python. In Python,
TRACELOOP_BASE_URLandTRACELOOP_HEADERSoverride theTraceloop.init()arguments (TypeScript is the reverse), so remove old values from your deployment. - Turn off OpenLLMetry metrics in Python. The Python SDK also exports
OTLP metrics to the same base URL. Rius ingests traces only, so those
requests fail with a 404 that is logged on every export.
TRACELOOP_METRICS_ENABLED=falsestops them. Traces are unaffected either way. - Use an
httporhttpsURL. OpenLLMetry picks OTLP/HTTP for anhttp(s)://base URL and gRPC otherwise, and Rius accepts OTLP/HTTP only.
What your traces look like in Rius
With no code changes, this is what you see in Rius:
-
LLM calls are generations with the request and response model, input and output tokens, cache-read tokens, and cost computed server-side.
-
Decorator spans keep their structure. Agents and tools appear as agent and tool spans with their names, and workflows and tasks appear as generic steps.
traceloop.entity.inputandoutputshow up as the span’s input and output. -
Conversations become sessions.
@conversation,set_conversation_id()andwithConversation()stampgen_ai.conversation.idon every span in scope, and Rius groups the traces into one session by it. Rius lists each trace under the session of its root span (the top-level span of the trace), so set the conversation id before that span starts. In TypeScript,withConversation()also adds a root workflow span. -
Association properties ride along as attributes.
user_id,chat_idand any other property arrive astraceloop.association.properties.<key>. You can see and filter on them in trace inspection, but Rius does not read them as its user or session. -
Users need
user.id. OpenLLMetry does not set it. Keep OpenLLMetry and set it with the OpenTelemetry API. Rius lists each trace under the user of its root span, so set it inside your outermost decorated function (the@workflowor@agentthat starts the trace), and ideally on the other spans of the trace too:from opentelemetry import trace trace.get_current_span().set_attribute("user.id", user_id)
Older OpenLLMetry releases work too; the OpenLLMetry page has the version details.
Side by side through a Collector
For an evaluation period, keep sending to Traceloop and add Rius as a second
destination. Point OpenLLMetry at an OpenTelemetry Collector
(TRACELOOP_BASE_URL=http://<collector-host>:4318, no headers) and fan out
from there:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
otlphttp/traceloop:
endpoint: https://api.traceloop.com
headers:
authorization: Bearer ${env:TRACELOOP_API_KEY}
otlphttp/rius:
endpoint: https://ingest.eu.console.rius-glassflow.com
encoding: proto
compression: gzip
headers:
authorization: Bearer ${env:RIUS_API_KEY}
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/traceloop, otlphttp/rius]Both backends now see identical spans, so you can compare trace trees,
token counts, and cost on the same traffic before deciding. The
Collector page covers the exporter
details, including why it must be otlphttp and not otlp.
With the Collector in between, OpenLLMetry’s Python SDK still sends its
metrics to the Collector. The config above has no metrics pipeline, so set
TRACELOOP_METRICS_ENABLED=false or add a metrics pipeline that exports
to Traceloop only.
Path B: swap OpenLLMetry for the Rius SDK
The Rius SDK (Python and TypeScript) instruments OpenAI,
Anthropic, LangChain and other supported libraries
on init(), so your LLM calls are covered without decorators. The
OpenLLMetry decorators map onto observe with a span kind:
| Traceloop (Python) | Rius (Python) |
|---|---|
Traceloop.init(app_name="my-agent", api_key=...) | rius.init(api_key="ri_...", service_name="my-agent") |
@workflow(name="plan") | @rius.observe(name="plan") (the default kind is CHAIN) |
@task(name="summarize") | @rius.observe(name="summarize") |
@agent(name="planner") | @rius.observe(kind=SpanKind.AGENT, agent_name="planner") |
@tool(name="search") | @rius.observe(kind=SpanKind.TOOL, tool_name="search") |
@conversation(conversation_id=cid) / set_conversation_id(cid) | with rius.session(cid): |
Traceloop.set_association_properties({"user_id": uid}) | with rius.user(uid):, which stamps user.id on every span in scope |
TRACELOOP_TRACE_CONTENT=false | The SDK’s privacy controls |
In TypeScript, withWorkflow, withTask, withAgent and withTool become
observe wrappers with
the matching kind, and withConversation becomes withSession.
A decorated agent, before and after:
# Traceloop
from traceloop.sdk import Traceloop
from traceloop.sdk.decorators import agent, tool
Traceloop.init(app_name="trip-planner")
@tool(name="lookup_weather")
def lookup_weather(city: str) -> str:
...
@agent(name="weather_agent")
def weather_agent(city: str) -> str:
...# Rius
import rius
from rius import SpanKind
rius.init(service_name="trip-planner") # reads RIUS_API_KEY
@rius.observe(kind=SpanKind.TOOL, tool_name="lookup_weather")
def lookup_weather(city: str) -> str:
...
@rius.observe(kind=SpanKind.AGENT, agent_name="weather_agent")
def weather_agent(city: str) -> str:
...Two differences worth knowing before you start:
- Remove OpenLLMetry when you add the Rius SDK. Both instrument the same client libraries with different instrumentors, so running both produces duplicate LLM spans. Run one or the other per process.
- Span names follow the GenAI conventions. Unnamed
AGENTandTOOLspans are calledinvoke_agent <name>andexecute_tool <name>rather than OpenLLMetry’s<name>.agentand<name>.tool, and workflow and task spans are called by their plain name rather than<name>.workflowand<name>.task. Update saved searches that match on span names. See Span names and identity.
What does not carry over from Traceloop
OpenLLMetry is open source and carries over completely; the Traceloop platform behind it does not. The Traceloop platform features below have no Rius equivalent:
- Monitors and evaluators: no LLM-as-a-judge or built-in evaluators running on production spans. Rius alerts cover errors, loops, and metric thresholds, not output quality.
- Guardrails: OpenLLMetry’s guardrail decorator runs Traceloop evaluators inline and needs a Traceloop API key. Rius has nothing that blocks or rewrites model output.
- Prompt registry and playgrounds: no versioned prompts, no
get_prompt(), no prompt deployment. Prompt content on spans is captured and inspectable, but there is no registry. - Datasets and experiments: no managed datasets or experiment runs, and no CI integration for them.
- User feedback and annotation tasks: no annotation objects on traces.
- Association properties as entities:
user_id,chat_idand the rest are stored and filterable as attributes, but Rius does not group by them. Rius users come fromuser.id(orenduser.id), which OpenLLMetry does not set; sessions come from the conversation id. See settinguser.id. - Traceloop Hub: Rius has no LLM gateway or proxy.
- Self-hosting: Rius is a hosted service only. Traceloop offers hybrid and full self-hosted deployments.
- Historical data: existing traces stay in Traceloop; Rius starts from the first span you send it.
- OpenLLMetry metrics: Rius ingests traces, not OTLP metrics. Token, cost, and latency analytics are computed from the spans instead.
If any of these are load-bearing in your workflow, keep Traceloop for those pieces and send traces to both through the Collector fan-out rather than cutting over completely.
Traceloop to Rius cutover checklist
- Create a Rius workspace and API key (quickstart). New organizations start with a free trial (30 days or 500,000 spans, whichever comes first), then a paid plan; see Plans and billing.
- Pick your path: keep OpenLLMetry or swap to the Rius SDK.
- Start with fan-out, not a switch: send the same traces to both backends (Collector fan-out), or re-point one service while the rest still report to Traceloop.
- Verify one traced request end to end: the trace tree shows your workflow, agent and tool spans, the generation shows model and token counts, cost appears, and traces with a conversation id share a session.
- Compare both backends on real traffic for a few days: trace counts, token totals, cost.
- Move anything that reads from Traceloop (dashboards, Slack digests, monitors) to Rius equivalents such as alerts, or deliberately leave them behind.
- Remove the Traceloop exporter, and revoke the Traceloop API key once nothing sends to it and no SDK feature still calls the Traceloop API.
Frequently asked questions
Do I have to re-instrument my code to migrate from Traceloop?
No. OpenLLMetry exports standard OTLP, so you change the base URL and the auth header and your existing spans, decorators included, arrive in Rius with their structure intact. What your traces look like in Rius shows how each one appears.
Can I run Traceloop and Rius at the same time?
Yes. Route OpenLLMetry through an OpenTelemetry Collector and export to both during the evaluation. The migration is reversible until you revoke your Traceloop API key.
Do my workflow, task, agent and tool decorators still work?
Yes. They are part of OpenLLMetry, not the Traceloop platform. Agents and tools appear as agent and tool spans with their names, and workflows and tasks appear as generic steps.
Does Rius support OpenLLMetry for TypeScript?
Yes. @traceloop/node-server-sdk exports the same OTLP/HTTP spans as the
Python SDK; set baseUrl and headers in initialize() or use the same
environment variables.