Distributed tracing

Make a run nest across services with propagation headers, middleware, and joinRun.

When one service calls another mid-run, you want both sides under a single timeline, not two disconnected runs. Trodo propagates the run over an HTTP header: the caller attaches it, the callee joins the same run. Every span on the downstream service then nests under the original run.

When to use it

Use this when a run crosses a network boundary: an orchestrator calling a tool service, a gateway fanning out to workers, a Node service calling a Python service. For runs that stay in one process but span time or workers, see Long-running & background runs.

Propagate across an HTTP call

The outbound side attaches the run with propagationHeaders(). The inbound side installs middleware that reads the headers and joins the run automatically, so nothing else changes in your handlers.

// Caller — attach the propagation headers to the outbound request.
await trodo.wrapAgent('parent-service', async () => {
  await fetch('http://child-service/work', {
    method: 'POST',
    headers: { ...trodo.propagationHeaders(), 'content-type': 'application/json' },
    body: JSON.stringify({ task: 'summarise' }),
  });
});

// Child service (Express) — middleware joins the caller's run.
import express from 'express';
const app = express();
app.use(trodo.expressMiddleware()); // reads X-Trodo-Run-Id and joins

app.post('/work', async (req, res) => {
  await trodo.withSpan('summarise', async (span) => {
    span.setTool('summarise');
    span.setInput(req.body);
    const summary = await summarise(req.body.task);
    span.setOutput({ summary });
    res.json({ summary });
  }, { kind: 'tool' });
});
# Caller — attach the propagation headers to the outbound request.
with trodo.wrap_agent('parent-service'):
    requests.post(
        'http://child-service/work',
        headers=trodo.propagation_headers(),
        json={'task': 'summarise'},
    )

# Child service (FastAPI) — middleware joins the caller's run.
from fastapi import FastAPI
import trodo

app = FastAPI()
app.middleware('http')(trodo.fastapi_middleware())  # joins the caller's run

@app.post('/work')
def work(body: dict):
    with trodo.span('summarise', kind='tool') as span:
        span.set_tool('summarise')
        span.set_input(body)
        summary = summarise(body['task'])
        span.set_output({'summary': summary})
        return {'summary': summary}

The downstream spans now appear nested under the original run in the dashboard.

Without middleware

If you can't add middleware, read the run ID off the headers and call joinRun yourself. The caller sends X-Trodo-Run-Id and X-Trodo-Parent-Span-Id.

await trodo.joinRun(
  req.headers['x-trodo-run-id'],
  req.headers['x-trodo-parent-span-id'],
  async () => {
    // every span emitted here nests under the caller's run
  },
);
with trodo.join_run(
    run_id=request.headers['x-trodo-run-id'],
    parent_span_id=request.headers['x-trodo-parent-span-id'],
):
    ...  # every span emitted here nests under the caller's run

Custom trace IDs

To correlate runs with IDs your own systems already use (a request ID, a job ID), supply your own run ID at startRun or carry it across services. See Trace IDs.

Next

On this page