Skip to main content
The @raindrop-ai/claude-managed-agents package automatically instruments Claude Managed Agents sessions to capture events and traces. Wrap the Anthropic client once, then use it normally — session creation, event streaming, and tool calls are tracked automatically. Features:
  • No OpenTelemetry setup required
  • Automatic tool call tracing (bash, file operations, web search, grep, MCP tools, custom tools)
  • Token usage tracking with cache creation/read breakdown
  • Per-session context overrides (userId, convoId, properties)

Installation

npm install @raindrop-ai/claude-managed-agents @anthropic-ai/sdk

Quick Start

import Anthropic from "@anthropic-ai/sdk";
import { createRaindropClaudeManagedAgents } from "@raindrop-ai/claude-managed-agents";

// 1. Create the Raindrop client
const raindrop = createRaindropClaudeManagedAgents({
  writeKey: process.env.RAINDROP_WRITE_KEY,  // Optional
  userId: "user_123",
});

// 2. Wrap the Anthropic SDK
const wrapped = raindrop.wrap(new Anthropic());

// 3. Use the SDK normally — sessions are tracked automatically
const session = await wrapped.beta.sessions.create({
  agent: agentId,
  environment_id: envId,
  title: "My session",
});

const stream = await wrapped.beta.sessions.events.stream(session.id);

await wrapped.beta.sessions.events.send(session.id, {
  events: [
    {
      type: "user.message",
      content: [{ type: "text", text: "List files in the current directory" }],
    },
  ],
});

for await (const event of stream) {
  if (event.type === "agent.message") {
    for (const block of event.content) {
      process.stdout.write(block.text);
    }
  } else if (event.type === "session.status_idle") {
    break;
  }
}

// 4. Flush before shutdown
await raindrop.shutdown();
The wrapped client is a transparent proxy — all methods work identically to the original Anthropic client. Session creation, event streaming, and tool calls are instrumented automatically.

Configuration

Client Options

const raindrop = createRaindropClaudeManagedAgents({
  writeKey: "your-write-key",   // Optional — omitting disables telemetry
  userId: "user_123",           // Default user ID for all sessions
  convoId: "conv_456",          // Default conversation ID
  eventName: "managed_agent_session",  // Default event name

  traces: {
    enabled: true,              // Default: true
    debug: false,               // Logs trace shipping
  },

  events: {
    enabled: true,              // Default: true
    debug: false,               // Logs event shipping
  },
});

Per-Session Context

Override defaults for specific sessions using the second argument to wrap():
const wrapped = raindrop.wrap(client, {
  userId: "different_user",
  convoId: "specific_conversation",
  properties: { environment: "production", tier: "enterprise" },
});

Identifying Users

Use users.identify to associate traits with a user:
await raindrop.users.identify({
  userId: "user_123",
  traits: {
    email: "user@example.com",
    plan: "pro",
    orgId: "org_456",
  },
});

Signals (Feedback)

Track user feedback on agent sessions:
await raindrop.signals.track({
  name: "thumbs_up",
  type: "feedback",
  sentiment: "POSITIVE",
  comment: "Agent handled the task well",
});

// Thumbs down with comment
await raindrop.signals.track({
  name: "thumbs_down",
  type: "feedback",
  sentiment: "NEGATIVE",
  comment: "Agent used the wrong tool",
});

// User edit
await raindrop.signals.track({
  name: "user_edit",
  type: "edit",
  after: "The corrected response text",
});

Signal Types

TypeUse Case
"default"Generic signals (thumbs up/down)
"feedback"User comments about quality
"edit"User corrected the output

Manual Event Updates

Update events after they’re created:
// Add properties
await raindrop.events.setProperties(eventId, {
  latencyMs: 1234,
  cached: true,
});

// Add attachments
await raindrop.events.addAttachments(eventId, [
  {
    type: "code",
    name: "generated.py",
    value: "print('hello')",
    role: "output",
    language: "python",
  },
]);

// Mark as complete (triggers immediate shipping)
await raindrop.events.finish(eventId);
Events are automatically finalized when the session stream completes. Use finish() only when you need to force immediate shipping.

Flush & Shutdown

Always flush before your process exits to ensure all data is sent:
// Serverless: flush at end of request
await raindrop.flush();

// Long-running: shutdown gracefully
process.on("SIGTERM", async () => {
  await raindrop.shutdown();
  process.exit(0);
});

What Gets Captured

Each managed agent session produces:
  • One Raindrop event with input (user message), output (agent response), model, and token counts
  • A root trace span (managed_agent.session) with the session ID
  • Child trace spans for each tool call (managed_agent.toolCall, managed_agent.mcpToolCall, managed_agent.customToolCall)
  • Token usage aggregated across all model requests, including cache creation/read tokens
  • Session errors captured in event properties

Debugging

Enable debug logging to troubleshoot issues:
const raindrop = createRaindropClaudeManagedAgents({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  events: { debug: true },
  traces: { debug: true },
});
Or via environment variable:
RAINDROP_AI_DEBUG=1 node your-script.js

Troubleshooting

Events not appearing in dashboard

  1. Check your write key — Ensure RAINDROP_WRITE_KEY is set correctly
  2. Call shutdown before exitawait raindrop.shutdown() flushes all pending data
  3. Enable debug logging — Set events: { debug: true } to see what’s being shipped

Traces or tool calls missing

  1. Enable trace debugging — Set traces: { debug: true } to see span shipping
  2. Check the stream was consumed — Tool call spans are only created when events are iterated

Wrapper not capturing sessions

Ensure you call sessions.create() on the wrapped client, not the original. Only sessions created through the wrapped client are tracked.

Self Diagnostics (Optional)

Self Diagnostics lets your agent proactively report its own issues — capability gaps, missing context, persistent tool failures — back to your team as Raindrop signals. A custom tool (__raindrop_report) is automatically injected into the agent definition. When the agent calls it, the wrapper ships a signal, auto-responds, and filters the tool call from the consumer stream. The agent never mentions the tool to the user.
const raindrop = createRaindropClaudeManagedAgents({
  writeKey: process.env.RAINDROP_WRITE_KEY,
  userId: "user_123",
  selfDiagnostics: {
    // Default signals (used when `signals` is omitted):
    signals: {
      missing_context: {
        description: "Critical information or access is missing and the user cannot provide it.",
        sentiment: "NEGATIVE",
      },
      repeatedly_broken_tool: {
        description: "A tool has failed on multiple attempts, preventing task completion.",
        sentiment: "NEGATIVE",
      },
      capability_gap: {
        description: "The task requires a capability you do not have.",
        sentiment: "NEGATIVE",
      },
      complete_task_failure: {
        description: "You tried and failed to deliver what the user asked.",
        sentiment: "NEGATIVE",
      },
    },
  },
});

// IMPORTANT: create the agent through the wrapped client so the tool is injected
const wrapped = raindrop.wrap(new Anthropic());
const agent = await wrapped.beta.agents.create({
  name: "My Agent",
  model: "claude-sonnet-4-6",
  tools: [{ type: "agent_toolset_20260401" }],
});
When invoked, the signal appears in Raindrop with the category and detail:
{
  "signal_name": "self diagnostics - missing_context",
  "signal_type": "agent",
  "properties": {
    "category": "missing_context",
    "detail": "User asked to fix deployment but no access to logs."
  }
}
The agent must be created through the wrapped client (wrapped.beta.agents.create()), not the original. Otherwise the reporting tool won’t be injected.