Contents
Moving from Braintrust to Moda is three changes to how your app sends traces, and one import for the history you already have.
The changes depend on how your app talks to Braintrust today. If your spans come from OpenTelemetry instrumentation, you change where they are exported. If you call Braintrust's own SDK, you swap it for the Moda SDK. Either way you set your session and user IDs on the keys Moda reads, so each agent run stays one trace under the right user. Then you import your Braintrust history from Settings, so the traces from before the switch sit in Moda next to the ones from after it.
What moves, and what stays in Braintrust
Everything about your traces moves:
- New traces. Spans go to Moda's OTLP endpoint, or through the Moda SDK.
- Sessions. The session or conversation ID your app already tracks becomes the Moda trace ID, so a multi-turn conversation is one trace.
- Users. Your user IDs become Moda user IDs, so problems are ranked by the people they affect.
- History. The Braintrust importer brings over past traces with all of their spans, sessions, users, and errors.
Some things are Braintrust features with no Moda equivalent, and they stay where they are:
- Datasets and experiments. Moda builds eval sets from your production traces instead of curated datasets. Experiments, datasets, and their scores are not imported.
- Prompts loaded at runtime. Braintrust serves versioned prompts to your app through
loadPrompt(). Moda versions prompt files from your repository and does not sit in the request path, so there is nothing to fetch. - Scorers and online scoring rules. Moda's detections run on every trace without a scorer to write or a sampling rate to set, so there is no rule to port.
Step 1: Point your OpenTelemetry exporter at Moda
If your spans come from OpenTelemetry GenAI instrumentation, OpenLLMetry, or the Vercel AI SDK's telemetry and go to Braintrust's /otel endpoint, Moda reads them as they are. Change the exporter's endpoint and authorization header, and drop the x-bt-parent header, which only Braintrust reads:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://moda-ingest.modas.workers.dev/v1/traces"
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer YOUR_MODA_API_KEY"
export OTEL_EXPORTER_OTLP_TRACES_COMPRESSION="none"
The last line matters. Moda ingest accepts uncompressed request bodies, and the OpenTelemetry spec leaves the default compression to each SDK, so set it to none explicitly.
If you export through an OpenTelemetry Collector, replace the Braintrust exporter with one for Moda. The Collector compresses by default, so set compression: none:
exporters:
otlphttp/moda:
traces_endpoint: https://moda-ingest.modas.workers.dev/v1/traces
headers:
Authorization: Bearer ${env:MODA_API_KEY}
compression: none
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/moda]
If you send spans through Braintrust's span processor, BraintrustSpanProcessor, replace it with a standard OTLP exporter configured with the variables above. Moda does not read braintrust.* attributes such as braintrust.input, braintrust.output, or braintrust.metadata. Spans that carry their content only in those attributes need OpenTelemetry GenAI attributes, or the Moda SDK in step 2.
Step 2: Replace Braintrust SDK tracing with the Moda SDK
Braintrust's TypeScript and Python SDKs, with initLogger, wrappers such as wrapOpenAI, and the Node.js auto-instrumentation hook, log spans to Braintrust through their own logger. Their OpenTelemetry support is a separate package, so for these calls there is no exporter to repoint.
Remove those calls and initialize the Moda SDK once at startup. It instruments the OpenAI and Anthropic clients, including streamed responses and tool calls, without changes to your provider code:
import os
import moda
moda.init(api_key=os.environ["MODA_API_KEY"])
moda.conversation_id = session_id
moda.user_id = user_id
In Node.js, await Moda.init(process.env.MODA_API_KEY!) before you create your provider clients, import withContext from moda-ai and wrap each request in withContext(sessionId, userId, async () => { /* your existing agent code */ }). This keeps IDs isolated across concurrent requests. Reserve the process-wide Moda.conversationId and Moda.userId setters for scripts handling one session at a time. If you would rather not do it by hand, moda init has your coding agent add the SDK for you.
Step 3: Set your session and user IDs on the Moda keys
Moda groups spans into one trace by moda.conversation_id, or gen_ai.conversation.id, across OpenTelemetry traces, and attributes them to a user by moda.user_id. A span with neither conversation key is grouped by its own OpenTelemetry trace, so a multi-turn conversation split across several traces needs the conversation ID on every span.
Set moda.conversation_id to the session or conversation ID your app already tracks, and moda.user_id to its user ID. With the Moda SDK, that is the two lines in step 2. With OpenTelemetry instrumentation, set them as span attributes on every span.
The importer in step 4 reads the user ID from user_id or userId in a trace's Braintrust metadata. If you set moda.user_id to the same value, a person's traces from before and after the switch land under one Moda user. Session IDs do not carry across the same way: the importer stores each imported trace as braintrust:<project ID>:<session ID>, so a conversation that continues after the switch starts a new Moda trace.
Step 4: Import your Braintrust history
Moda imports the traces you already have in Braintrust. In Moda, open Settings, then Integrations, and find Import traces:
- Choose your Braintrust region, US or EU. It matches the region under Settings, then Data plane, in Braintrust.
- Paste a Braintrust API key and select Connect Braintrust. Moda stores the key encrypted for the import and removes it when the import completes or is cancelled.
- Pick the project and a date range of up to 30 days, in UTC, starting no earlier than January 1, 2022. Run another import for each earlier window.
- Select Import traces. The import runs in the background, so you can leave the page. One import runs at a time, and an owner or admin can pause, resume, or cancel it.
The import selects every trace whose root span Braintrust created in your date range, and brings each one over whole:
- Spans. Every span arrives under its parent with its name, type, start and end time, input, output, model, provider, and prompt and completion token counts. LLM spans become the messages of the conversation, and tool spans become tool calls with their input and result.
- Sessions. Traces whose Braintrust metadata carries the same
session_id,sessionId, orconversation_idbecome one Moda trace, and child spans inherit the session from their root. A trace with none of these becomes a Moda trace of its own. - Users. A
user_idoruserIdin the metadata becomes the Moda user ID. - Errors. A span with an error arrives as a failed span with its error message.
- The original record. Each span keeps the full Braintrust record it came from, including its scores and metadata.
A few limits are worth knowing before you start. Each Braintrust trace must fit within the 512 KB import limit. A single oversized trace fails the import job, and shortening the date range does not make that trace smaller. Contact support for a bulk import if your traces exceed this limit. The importer copies what exists in Braintrust when it runs; it is not a sync, and deletions in Braintrust are not carried over. Avoid editing the traces you are importing while the import runs, for a consistent result. Importing the same window again rereads and reingests the full window, including unchanged spans. Stable span IDs let Moda update existing spans without duplicating them. Imported traces are analyzed under your workspace's existing analysis settings. Datasets, experiments, prompts, and attachments stay in Braintrust, and self-hosted Braintrust data planes are not supported.
What you get on day one
Once traces flow, Moda's built-in detections run on every new trace as it arrives: user frustration, tool failures, laziness, and hallucinations checked against the tool results the agent actually received. Failures are grouped into problems ranked by how many users they affect, each linked to its traces.
When you change a prompt or a model, replay it against the traces that failed before it ships. A simulated user plays each conversation, every tool call is simulated from the tool calls recorded in your traces, and a judge scores your current configuration and the change side by side. There are no tool mocks to write and no real tool is called.
The full comparison, including the three places where Braintrust still leads, is on Moda vs Braintrust.
Frequently asked questions
Do I have to re-instrument my app?
Not if it already emits OpenTelemetry GenAI, OpenLLMetry, or Vercel AI SDK spans. Change the exporter's endpoint, API key, and compression. Calls traced with Braintrust's own SDK move to the Moda SDK, which instruments the OpenAI and Anthropic clients with one init call.
Does the Braintrust importer bring my datasets and experiments?
No. It imports project logs: traces with their spans, sessions, users, and errors, and the original record of each span. Datasets, experiments, prompts, scorers, and attachments stay in Braintrust.
Can I run Braintrust and Moda at the same time while I switch?
Yes. A Collector can export the same spans to both, with compression: none on the Moda exporter, so you can compare them on live traffic before you turn the Braintrust exporter off.
Does the importer work with a self-hosted Braintrust data plane?
No. It reads from Braintrust's US and EU regions.
