Skip to main content
Tracing records what happens inside an interaction (multi-step pipelines, model calls, tool calls and sub-agents) so you can see the full execution flow, debug prompt chains and understand the intermediate steps behind a response. Pick your language in any code sample; every sample and language note on this page switches with it. In Python, turn tracing on first with tracing_enabled=True (see Configuration); the other SDKs record spans by default. This page builds on interactions from the SDKs page, which also covers recording a tool call after it happened (Tool spans).

Spans

Wrap a unit of work, such as retrieval, a pipeline step or an agent run, in a span. Spans you start from an interaction inherit its user, conversation and event, so Raindrop shows them on that event, and spans can nest to form the run’s tree.
Use withSpan to trace tasks or operations. Any LLM calls within the span are automatically captured:

Tool calls

Trace a tool call as it runs: its input, output, duration and any error are recorded on a tool span. To record a call after it happened, use trackTool (see Tool spans).
Use withTool to trace agent actions—memory operations, web searches, API calls, and more:

Model calls

The TypeScript and Python SDKs instrument common LLM client libraries, so model calls made inside an interaction appear as spans without extra code. The other SDKs record the model calls you describe. Either way, the list of tools a model was given is recorded as ai.prompt.tools; see Tool catalog capture.
Wrap your code with withSpan or withTool on an interaction, and LLM calls inside are automatically captured:
Next.js users: Add raindrop-ai to serverExternalPackages in your config:
Module InstrumentationIn some environments, automatic instrumentation may not work due to module loading order or bundler behavior. Use instrumentModules to explicitly specify which modules to instrument:
Anthropic users: You must use a module namespace import (import * as ...), not the default export.
Supported modules: openAI, anthropic, cohere, bedrock, google_vertexai, google_aiplatform, pinecone, together, langchain, llamaIndex, chromadb, qdrant, mcp.For Bedrock, use a namespace import — same pattern as Anthropic above. Cross-region inference profile model IDs (e.g. us.anthropic.claude-*) are supported.
OpenTelemetry IntegrationIf you already have an OpenTelemetry setup (Sentry, Datadog, Honeycomb, etc.), integrate Raindrop alongside it using useExternalOtel:
Without instrumentModules, getInstrumentations() returns instrumentations for all supported AI libraries. Specify instrumentModules to instrument only specific libraries.

Sub-agents

A detached sub-agent is work launched from one event that reports as its own event, linked back to the launcher, possibly in another process or service. The launcher records the hand-off: this adds a dispatch span to its event and returns the child’s event id and carrier headers. Send all of the headers with the job, not only traceparent. The worker opens the child’s event from them, and every span it records links back to the launcher. The carrier is the same in every SDK, so a hand-off can cross languages. A missing or unreadable carrier never fails the work: the child reports as an unlinked event. Only accept carriers from inside your own trust boundary, since a carrier decides which event the child’s data is attributed to.
A detached sub-agent runs asynchronously in another process, worker, or machine. Unlike resumeInteraction(), which joins the parent’s event, it reports as its own event. The parent mints the child event and sends a hand-off carrier; the worker resumes that event and reports its result.
Beta. The detached sub-agent API surface may change in a minor release.
1

Dispatch from the parent

Each process creates its own Raindrop instance. The parent allocates the child event before sending the job:
2

Send the hand-off

Send the job with all of dispatch.headers:
childEventId is allocated before dispatch, so the link is resolvable while the job is queued. The dispatch span is named launch_subagent by default; use toolName to choose another name.interaction.subagent() returns:
  • childEventId — the child event id, allocated before dispatch
  • name — the sub-agent name
  • parentEventId — the parent event id
  • dispatchSpanId — the dispatch span id, when tracing is enabled
  • headers — the native carrier headers
  • langsmithHeaders — the carrier in LangSmith’s header shape
  • carrier — the parsed carrier for non-HTTP transports
Send all returned headers, not only traceparent. The carrier includes traceparent, baggage with raindrop-* fields, and x-raindrop-handoff. The hand-off header wins if standard propagation headers are rewritten or replaced.
With disabled: true, childEventId is still allocated, but carrier is null and both header objects are empty. There is no dispatch span or carrier link.
3

Resume in the worker

The carrier travels with the job. It is JSON-serialisable, so a queue, file, or another process can carry it. The worker’s request.headers below means the received header bag:
For a non-HTTP transport, the first argument accepts the received TraceCarrier too:
A short-lived worker must await raindrop.close() before exit to flush buffered events.
4

Report the outcome

withSubagentRun() wraps the same worker flow:
It resolves with the callback’s return value. If the callback returns without reporting, the SDK aborts the run. If it throws, the SDK reports the failure and rethrows the error.
run.interaction is the child’s ordinary interaction object. It supports withSpan, withTool, trackTool, and the other interaction APIs. Use the object form: withTool({ name: "search_corpus" }, fn).While the run is open, its reverse reference is bound to the execution context. Every span the child emits—including auto-instrumented spans—is linked back to the dispatch. cancel() emits no span, so a cancelled child has no reverse reference on a child span.Reported outcomes
  • finish({ output }) reports a successful result; the caller supplies the output.
  • fail(reason) marks telemetry as errored and synthesizes output from the reason.
  • cancel(reason) writes a terminal event with raindrop.handoff.terminal=cancelled and synthesized output, but emits no span or errored span.
Status comes from the child’s telemetry, so a failed run needs an errored span. Only a child that reports no outcome at all produces no event, leaving its launcher queued.Carrier rulesThe carrier is authoritative. SubagentResumeOptions values (name, eventId, userId, convoId, event, input, model, or properties) are fallbacks only when no carrier is available.A missing carrier is valid: the child still reports its own event, with run.linked === false and run.parent === null.When a carrier resolves, run.linked === true and run.parent describes the launching context, including the parent event id and dispatch span id. The child’s run.eventId is the childEventId allocated by the parent.
Only accept carriers inside your own trust boundary; an untrusted carrier can attribute a child to another tenant’s event. For non-HTTP transports, pass the carrier as the first argument or use resumeSubagent(undefined, { parent: carrier }). Passing { parent: carrier } as the first argument does not link the run.
Dispatch options