Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.trodo.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

What / When / Minimum

When your agent is spread across multiple files (a triage function that calls a researcher that calls a writer), you don’t pass the runId between them. The SDK uses Node’s AsyncLocalStorage (Python’s contextvars) to propagate the active run context through every async call — import withSpan in any file and it attaches to the current run automatically. Minimum: one wrapAgent at the entry point. Every nested file calls trodo.withSpan(...) directly.

The pattern

index.js
 └─ trodo.wrapAgent('pipeline', async () => {
      await triage(input)
         └─ await researcher(topic)
              └─ await writer(facts)
    })
Each of triage, researcher, writer lives in its own file and calls trodo.withSpan(...) for whatever sub-steps it owns. None of them receives a runId; none of them imports anything from the entry file.

triage.js

import trodo from 'trodo-node';

export async function triage(input) {
  return await trodo.withSpan('triage', async (s) => {
    s.setInput(input);
    const topic = await classify(input);
    s.setOutput({ topic });
    const facts = await researcher(topic);
    return await writer(facts);
  }, { kind: 'agent' });
}

researcher.js

import trodo from 'trodo-node';

export async function researcher(topic) {
  return await trodo.withSpan('research', async (s) => {
    s.setInput({ topic });
    const hits = await vectorSearch(topic);
    s.setOutput({ hits: hits.length });
    return hits;
  }, { kind: 'retrieval' });
}

writer.js

import trodo from 'trodo-node';

export async function writer(facts) {
  return await trodo.llm('write', async () => {
    return await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: buildPrompt(facts) });
  }, { model: 'gpt-4o-mini', provider: 'openai' })();
}

index.js

import trodo from 'trodo-node';
import { triage } from './triage.js';

trodo.init({ siteId: process.env.TRODO_SITE_ID });

const { runId } = await trodo.wrapAgent('pipeline', async (run) => {
  const answer = await triage({ userMessage: '...' });
  run.setOutput(answer);
  return answer;
});
One run. Nested waterfall: pipelinetriage (agent) → research (retrieval) → write (llm).

Python equivalent

contextvars are propagated automatically across await, threads (via copy_context), and asyncio.create_task. Same pattern:
# triage.py
import trodo
async def triage(input):
    with trodo.span("triage", kind="agent") as s:
        s.set_input(input)
        topic = await classify(input)
        facts = await researcher(topic)
        return await writer(facts)

# index.py
with trodo.wrap_agent("pipeline") as run:
    answer = await triage({"userMessage": "..."})
    run.set_output(answer)

Checking the current context

Need to know mid-code whether you’re inside a run?
const runId = trodo.currentRunId(); // string | null
if (runId) { /* inside wrapAgent */ }
Outside a run, withSpan is a no-op. That means your library code can call trodo.withSpan(...) unconditionally — when invoked standalone it emits nothing, when invoked inside a wrapAgent it joins the tree.

Running sub-steps in parallel

await trodo.wrapAgent('fanout', async () => {
  const [a, b, c] = await Promise.all([
    trodo.withSpan('step.a', () => doA()),
    trodo.withSpan('step.b', () => doB()),
    trodo.withSpan('step.c', () => doC()),
  ]);
  return combine(a, b, c);
});
All three spans have the same parent (the wrapAgent root) and render side-by-side in the waterfall. AsyncLocalStorage handles the branching automatically.

Gotchas

  • Callbacks outside the async chain break context. setImmediate, setTimeout, EventEmitter listeners scheduled before wrapAgent opened may run outside the context. Fix: schedule them inside.
  • Worker threads and child processes don’t share context. Use cross-service propagation or explicit joinRun.
  • Queue workers (BullMQ, SQS, RabbitMQ). The consumer runs in a fresh event loop tick; open a new wrapAgent per job and pass runId on the job payload if you need to link it to the producer. See sub-agents for parent_run_id.
  • Top-level await in a module that runs before trodo.init races the processor. Call init first, synchronously.