Long-running & background runs
Open a run in one process and finish it in another, for sessions that span many requests or workers.
wrapAgent opens and closes a run in one call stack. Some runs don't fit that shape: a chat session pinned to a websocket over many messages, or a background job that gets picked up and resumed on a different worker. For those, open the run with startRun, append spans from anywhere with joinRun, and finalise it later with endRun.
When to use it
| Situation | Use |
|---|---|
| Opens and closes in one function | wrapAgent |
| Append spans from a queue or worker (you already have the run ID) | joinRun |
| A run spans many requests or workers over time | startRun + joinRun + endRun |
| Your own MCP server | trackMcp |
The same runId threads through everything. Store it where the next request or worker can read it (a job payload, Redis, your DB).
Open and close a run across requests
import trodo from 'trodo-node';
// 1. Open the run when the job is created.
const runId = await trodo.startRun('ingest_pipeline', {
conversationId: jobId,
metadata: { params },
});
await redis.set(`job:run:${jobId}`, runId, 'EX', 86400);
// 2. Later, on any worker — append a span to the same run.
const runId = await redis.get(`job:run:${jobId}`);
await trodo.joinRun(runId, null, async (span) => {
span.setInput({ step: 'embed' });
const out = await embedBatch(batch);
span.setOutput({ vectors: out.length });
}, { name: 'embed', kind: 'agent' });
// 3. When the job finishes — finalise.
await trodo.endRun(runId, { status: 'ok' });import trodo
# 1. Open the run when the job is created.
run_id = trodo.start_run('ingest_pipeline', conversation_id=job_id, metadata={'params': params})
redis_client.set(f'job:run:{job_id}', run_id, ex=86400)
# 2. Later, on any worker — append a span to the same run.
run_id = redis_client.get(f'job:run:{job_id}').decode()
with trodo.join_run(run_id, name='embed', kind='agent') as span:
span.set_input({'step': 'embed'})
out = embed_batch(batch)
span.set_output({'vectors': len(out)})
# 3. When the job finishes — finalise.
trodo.end_run(run_id, status='ok')The dashboard shows one run, with every step as a child span beneath it, in running status until endRun.
Append several spans at once
The form above creates a single span. To nest multiple spans under the run, pass joinRun a block and add manual spans inside it:
await trodo.joinRun(runId, async () => {
await trodo.withSpan('fetch-doc', async (span) => {
span.setTool('fetch-doc');
span.setInput({ docId });
const doc = await db.get(docId);
span.setOutput({ bytes: doc.length });
return doc;
}, { kind: 'tool' });
// …more spans here, all attach to runId
});with trodo.join_run(run_id):
with trodo.span('fetch-doc', kind='tool') as span:
span.set_tool('fetch-doc')
span.set_input({'docId': doc_id})
doc = db.get(doc_id)
span.set_output({'bytes': len(doc)})
# …more spans here, all attach to run_idjoinRun does not create a run. Calling it with a runId that doesn't exist on your site is a no-op and the spans are dropped. For a separate run linked to a parent, use wrapAgent with parentRunId instead.
API
startRun
| Option | Notes |
|---|---|
agentName (1st arg) | Required. Free-text identifier, e.g. "chat", "ingest_pipeline". |
runId / run_id | Optional. Supply your own UUID for cross-process correlation; otherwise the SDK mints and returns one. |
distinctId / distinct_id | The end user. See Users. |
conversationId / conversation_id | Groups runs in one conversation. See Conversations. |
parentRunId / parent_run_id | Marks this as a sub-agent run. |
metadata | Free-form JSON tags. |
input | Recorded as the run's input. |
Returns the runId. The run row appears immediately with status: "running".
endRun
| Option | Notes |
|---|---|
runId / run_id (1st arg) | Required. |
status | "ok" (default) or "error". |
output | Final output payload. |
errorSummary / error_summary | Human-readable message; set when status="error". |
metadata | Final metadata merged into the run. |
endRun aggregates any buffered spans and finalises the run server-side.
Crossing services?
If a run flows over HTTP from one service to another, you don't pass the run ID by hand. See Distributed tracing.
Next
- Distributed tracing for cross-service runs
- Add manual spans for the span helpers used inside
joinRun - MCP Server if you're instrumenting an MCP server