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

SituationUse
Opens and closes in one functionwrapAgent
Append spans from a queue or worker (you already have the run ID)joinRun
A run spans many requests or workers over timestartRun + joinRun + endRun
Your own MCP servertrackMcp

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_id

joinRun 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

OptionNotes
agentName (1st arg)Required. Free-text identifier, e.g. "chat", "ingest_pipeline".
runId / run_idOptional. Supply your own UUID for cross-process correlation; otherwise the SDK mints and returns one.
distinctId / distinct_idThe end user. See Users.
conversationId / conversation_idGroups runs in one conversation. See Conversations.
parentRunId / parent_run_idMarks this as a sub-agent run.
metadataFree-form JSON tags.
inputRecorded as the run's input.

Returns the runId. The run row appears immediately with status: "running".

endRun

OptionNotes
runId / run_id (1st arg)Required.
status"ok" (default) or "error".
outputFinal output payload.
errorSummary / error_summaryHuman-readable message; set when status="error".
metadataFinal 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

On this page