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
| Type | Use 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
- Check your write key — Ensure
RAINDROP_WRITE_KEY is set correctly
- Call shutdown before exit —
await raindrop.shutdown() flushes all pending data
- Enable debug logging — Set
events: { debug: true } to see what’s being shipped
- Enable trace debugging — Set
traces: { debug: true } to see span shipping
- 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.