Skip to main content
The @raindrop-ai/eve package instruments Vercel’s Eve agent framework using its native agent/instrumentation.ts entry point. No wrapping, no per-call setup — Eve auto-discovers the integration at server startup and every agent turn flows to Raindrop. What gets tracked:
  • Every agent turn as a single Raindrop event, grouped by the Eve session
  • Multi-step streamText / generateText / generateObject calls with full attribute capture
  • Tool calls (inputs, outputs, durations, errors)
  • Token usage per step and per turn
  • Sub-agent dispatches as nested agent.subagent spans — even across Eve’s V8 sandbox boundary
  • Cross-sandbox parent linkage (raindrop.parent.eventId / sessionId / turnId / turnSequence)
  • Model name, finish reason, errors, and full AI SDK telemetry attributes
  • Automatic mirroring to the local Workshop daemon during development

Installation

eve is expected to already be in your agent project. The integration declares eve >= 0.11.0 as an optional peer dependency and reads only Eve’s documented runtime surface, so a single defineRaindropInstrumentation() call works without any per-version configuration.
  • Cross-sandbox sub-agent linkage is sourced from the events["step.started"] session.parent lineage Eve forwards on every dispatch.
  • Per-turn dynamic metadata (the step.started callback — see Identifying Users) is wired through Eve’s events config and runs once per model call.

Quick Start

Drop a single file into your Eve project at agent/instrumentation.ts:
That’s it. Eve auto-discovers agent/instrumentation.ts and runs it at server startup before any agent code. Every turn from every session in your agent will land in Raindrop.

How It Works

The package does two things:
  1. Implements a SpanExporter (RaindropEveSpanExporter) that serializes OTel spans to OTLP/HTTP JSON and ships them to https://api.raindrop.ai/v1/traces. It also mirrors every export to a local Workshop daemon when one is reachable (http://localhost:5899/), so the same agent run shows up in raindrop workshop AND hosted Raindrop without changing code.
  2. Registers a Raindrop TelemetryIntegration with Vercel AI SDK v7’s global registry so every streamText / generateText / tool call in your agent becomes a proper Raindrop LLM / tool span — and a track_partial semantic event — instead of opaque AI SDK spans. This is what hydrates the Workshop Overview tab and the hosted Raindrop event feed. All AI SDK calls within one Eve turn are grouped under a single eventId derived from Eve’s active OTel trace, so a single agent turn shows up as a single Raindrop event in both UIs.
For users who’d rather wire @vercel/otel themselves, RaindropEveSpanExporter is exported separately for use with any registerOTel({ traceExporter }) call.

Sub-Agents

Eve runs each sub-agent in its own V8 sandbox. The integration detects when the current sandbox was dispatched as a sub-agent and lifts the parent’s turn identity onto every sub-agent event, sourcing the lineage from the events["step.started"] session.parent Eve forwards on dispatch: These attributes power the AGENT block in Workshop’s Overview tab and let the Raindrop dashboard stitch sub-agent events under the parent turn that dispatched them (matching on raindrop.parent.eventId).

Configuration

Two ways to attach metadata: Both feed the same event fields — the events callback just lets you compute them per turn.

Projects

If your org has multiple projects, route this agent’s events to a specific one by passing its slug as projectId (or via RAINDROP_PROJECT_ID):
This sets the X-Raindrop-Project-Id header on every cloud export. Omit it (or pass "default") to use your org’s default Production project, which is the existing behavior. Single-project orgs need nothing new.
Eve is one instrumentation per process by design — run one project per process. defineRaindropInstrumentation() installs its OpenTelemetry exporter behind a process-global symbol gate (the first call wins) and registers its AI SDK telemetry integration on globalThis (the last call wins). Two defineRaindropInstrumentation() calls with different writeKeys or projectIds in the same process therefore split-brain — the exporter belongs to the first call, the AI SDK capture to the last — and spans land in the wrong project. To report several agents to different projects, run one instrumented Eve process per project rather than calling defineRaindropInstrumentation() more than once.

Identifying Users

Raindrop sets each event’s user and conversation from two reserved keys: raindrop.userId and raindrop.convoId. Put them on staticMetadata if they never change, or return them from the events["step.started"] callback to compute them per turn. Same user every time — put the keys on staticMetadata:
Different user per turn — return the keys from the events["step.started"] callback. Eve runs it once per model call with the live input, so you can derive identity from the request that triggered the turn — e.g. map the Slack user onto the Raindrop event userId:
How the keys map onto the event:
  • raindrop.userId → the event’s userId; raindrop.convoId → the event’s convoId.
  • Every other key → the event’s properties.
The same values are also attached to the AI SDK spans (as ai.settings.context.*).
When the same key is set in more than one place, the most specific value wins: per-call AI SDK metadata > the events["step.started"] callback > staticMetadata. If no raindrop.userId is set anywhere, Raindrop falls back to the Eve session.id.

Using the Slack user’s name / email / channel name

Whatever string you return as raindrop.userId becomes the event’s userId — so to make Raindrop track e.g. the Slack email instead of the user id, just return that string. What’s reachable from step.started:
  • User id (Uxxxx) — directly on input.channel.metadata.triggeringUserId.
  • User’s name — already resolved, no API call: read input.session.auth.current?.attributes and use full_name / user_name (Eve’s default Slack auth puts them there).
  • Email / channel namenot in any Slack mention payload, so resolve them once on the async inbound side (onAppMention) via the Slack Web API (users.infouser.profile.email, needs the users:read.email scope; conversations.infochannel.name), stash them on the session auth attributes, then read them back in step.started (which is synchronous and can’t call Slack itself):

Workshop / Production Mirroring

localWorkshopUrl controls Workshop mirroring: Run raindrop workshop locally to get a daemon at http://localhost:5899 — the integration starts mirroring as soon as the daemon is reachable.

Production-Only Mode

Workshop-Only Mode (no Raindrop account)

Leave writeKey undefined. Spans still flow to Workshop:

Captured Trace Hierarchy

A typical Eve turn produces this trace shape:
When a sub-agent is dispatched, the sub-agent’s sandbox opens its own OTel trace. The integration tags the sub-agent’s events with raindrop.parent.eventId (plus sessionId / turnId / turnSequence) so the Raindrop dashboard renders them nested under the parent’s turn — matching on raindrop.parent.eventId — even though they are on a different OTel trace.

Captured Properties

The following properties land on every event: Any keys you set on staticMetadata (or return from the events["step.started"] callback) are also forwarded to event.properties, except the reserved raindrop.* identity keys.

Flush & Shutdown

Eve’s BatchSpanProcessor flushes spans automatically at process exit, and the integration registers a shutdown() hook that drains the internal Raindrop client buffer before the process exits. Most users don’t need to call anything manually. If you’re embedding RaindropEveSpanExporter directly, call await exporter.shutdown() before exit.

Known Limitations

  • Streaming: The integration captures aggregate token usage and the final outputs of each step. Individual token-by-token deltas are not traced separately.
  • OTel provider access: The integration calls addSpanProcessor on the NodeTracerProvider returned by @vercel/otel. Custom OTel setups that hide the underlying provider behind a non-standard proxy will gracefully skip the ai.eve.turn session-id enrichment path and fall back to the events["step.started"] session input only.