> ## Documentation Index
> Fetch the complete documentation index at: https://raindrop.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Deep Agents (Beta)

> Automatic tracing for LangChain Deep Agents workflows with Raindrop

Raindrop automatically instruments [LangChain Deep Agents](https://docs.langchain.com/oss/python/deepagents/overview) workflows. Pass the callback handler once and Raindrop captures every LLM call, tool invocation, chain step, and agent action — with full OpenTelemetry trace nesting linked to the resulting dashboard event.

Available for both TypeScript (`@raindrop-ai/deep-agents`) and Python (`raindrop-deep-agents`).

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @raindrop-ai/deep-agents @langchain/core @langchain/anthropic deepagents
  ```

  ```bash pnpm theme={null}
  pnpm add @raindrop-ai/deep-agents @langchain/core @langchain/anthropic deepagents
  ```

  ```bash pip theme={null}
  pip install raindrop-deep-agents deepagents langchain-core langchain-openai
  ```
</CodeGroup>

## Quick Start

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createDeepAgent } from "deepagents";
  import { ChatAnthropic } from "@langchain/anthropic";
  import { createRaindropDeepAgents } from "@raindrop-ai/deep-agents";

  const raindrop = createRaindropDeepAgents({
    writeKey: "your-write-key",
    userId: "user-123",
    convoId: "session-abc",
  });

  const agent = createDeepAgent({
    model: new ChatAnthropic({ model: "claude-sonnet-4-6" }),
    tools: [/* filesystem, shell, etc. */],
  });

  const result = await agent.invoke(
    { messages: [{ role: "user", content: "Create a hello world file" }] },
    { callbacks: [raindrop.handler] },
  );

  await raindrop.shutdown();
  ```

  ```python Python theme={null}
  from raindrop_deep_agents import RaindropDeepAgents
  from deepagents import create_deep_agent
  from langchain_openai import ChatOpenAI
  from langchain_core.messages import HumanMessage

  rd = RaindropDeepAgents(
      api_key="your-write-key",
      user_id="user-123",
      convo_id="session-abc",
  )

  llm = ChatOpenAI(model="gpt-4o-mini")
  agent = create_deep_agent(model=llm, system_prompt="Be helpful.")

  result = agent.invoke(
      {"messages": [HumanMessage(content="Create a hello world file")]},
      config={"callbacks": [rd.handler]},
  )

  rd.shutdown()
  ```
</CodeGroup>

## What Gets Traced

The Deep Agents integration automatically captures:

* **LLM calls** — model name, input messages, output text, prompt + completion tokens, finish reason
* **Tool calls** — tool name (`write_todos`, `read_file`, `write_file`, `edit_file`, `ls`, `glob`, `grep`, `execute`, `task`), input arguments, output, duration, errors as nested `TOOL_CALL` spans linked to the parent event (via `interaction.track_tool()`)
* **Chains** — root chain input (the user's question) and output (the final assistant reply) captured as the canonical event, with aggregated model + token usage merged in from all child LLM calls
* **Agent actions** — tool selection events captured during the agent loop
* **Errors** — exception type and message captured for both LLM and tool failures
* **Tags & metadata** — anything passed via LangChain `config={"tags": [...], "metadata": {...}}` is forwarded to event properties
* **Extended token categories** — cached tokens (`ai.usage.cached_tokens`) and reasoning tokens (`ai.usage.thoughts_tokens`) when available from the provider (e.g. OpenAI, Anthropic)
* **Finish reason** — captured as `ai.finish_reason` in event properties (`"stop"`, `"length"`, `"tool_calls"`, etc.)
* **Multi-modal messages** — typed-parts content (`[{type: "text", text: "..."}, ...]`) is extracted cleanly; text parts are concatenated, non-text parts (images, audio) are skipped

All operations are linked with parent-child relationships, so you can see the full execution tree in the Raindrop dashboard's Trace tab.

## Configuration

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropDeepAgents({
    writeKey: "your-write-key",         // Optional: omit to disable telemetry
    endpoint: "https://...",            // Optional: custom API endpoint
    userId: "user-123",                 // Optional: associate events with a user
    convoId: "session-abc",             // Optional: conversation/thread ID
    projectId: "support-prod",          // Optional: route events to a specific project (slug)
    traceChains: true,                  // Optional: create spans for chain execution (default: true)
    debug: false,                       // Optional: enable verbose logging (default: false)
  });
  ```

  ```python Python theme={null}
  rd = RaindropDeepAgents(
      api_key="your-write-key",        # Optional: omit to disable telemetry
      user_id="user-123",              # Optional: associate events with a user
      convo_id="session-abc",          # Optional: conversation/thread ID
      project_id="support-prod",       # Optional: route events to a specific project (slug)
      tracing_enabled=True,            # Optional: enable distributed tracing (default: True)
      bypass_otel_for_tools=True,      # Optional: bypass OTEL for tool spans (default: True)
      debug=False,                     # Optional: enable debug logging (default: False)
  )
  ```
</CodeGroup>

## Projects

Route events to a specific [project](/docs/platform/projects) by passing its slug as `projectId` (`project_id` in Python):

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropDeepAgents({
    writeKey: "your-write-key",
    projectId: "support-prod",
  });
  ```

  ```python Python theme={null}
  rd = RaindropDeepAgents(
      api_key="your-write-key",
      project_id="support-prod",
  )
  ```
</CodeGroup>

This sets the `X-Raindrop-Project-Id` header on every event. Omit it (or pass `"default"`) to use your org's default **Production** project, which is the existing behavior. Single-project orgs need nothing new.

### Multiple projects in one process

Available in `raindrop-ai>=0.0.56`. When one service runs several Deep Agents
that should report to **different** projects, create one `RaindropDeepAgents`
wrapper per project. Each wrapper owns its own `raindrop.Raindrop` client — the
handler is a plain LangChain callback with no shared module-level state — so the
two route independently:

```python theme={null}
from raindrop_deep_agents import RaindropDeepAgents

# One long-lived wrapper per project, created at startup and reused.
rd_support = RaindropDeepAgents(api_key="your-write-key", project_id="support-prod")
rd_billing = RaindropDeepAgents(api_key="your-write-key", project_id="billing-prod")

# Pass each wrapper's handler to the corresponding agent invocation.
support_result = support_agent.invoke(
    {"messages": [HumanMessage(content="How do I reset my password?")]},
    config={"callbacks": [rd_support.handler]},   # -> support-prod
)
billing_result = billing_agent.invoke(
    {"messages": [HumanMessage(content="Why was I charged twice?")]},
    config={"callbacks": [rd_billing.handler]},    # -> billing-prod
)

rd_support.flush()
rd_billing.flush()
```

Each wrapper owns its configuration and delivery pipeline, so concurrent
requests handled by different agents route independently. To share a single
client across wrappers (or with the module-level API), construct a
`raindrop.Raindrop` yourself and pass it via `client=`:

```python theme={null}
from raindrop import Raindrop

client = Raindrop(api_key="your-write-key", project_id="support-prod")
rd = RaindropDeepAgents(client=client)
```

<Note>
  Distributed **tracing** uses a single process-global OpenTelemetry pipeline, so
  auto-instrumented trace spans follow whichever client initialized tracing first.
  The per-wrapper routing above applies to the flat events shipped by the callback
  handler (`begin`/`track_ai`/`flush`/`identify`/`track_signal`), which is the
  data the dashboard's event views rely on. For fully independent tracing across
  projects, run one project per process.
</Note>

## Tool Calls

When your agent uses tools, each tool execution appears as a nested `TOOL_CALL` span under the root event with the tool name, input arguments, output, and duration. Tool errors include the exception type and message.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { tool } from "@langchain/core/tools";
  import { z } from "zod";

  const getWeather = tool(
    async ({ city }) => `The weather in ${city} is 22°C.`,
    {
      name: "get_weather",
      description: "Get the current weather for a city.",
      schema: z.object({ city: z.string() }),
    },
  );

  const agent = createDeepAgent({
    model: new ChatAnthropic({ model: "claude-sonnet-4-6" }),
    tools: [getWeather],
  });
  ```

  ```python Python theme={null}
  from langchain_core.tools import tool

  @tool
  def get_weather(city: str) -> str:
      """Get the current weather for a city."""
      return f"The weather in {city} is 22°C."

  agent = create_deep_agent(
      model=ChatOpenAI(model="gpt-4o-mini"),
      tools=[get_weather],
  )
  ```
</CodeGroup>

The tool call appears in the event's `toolCalls` array and as a span in the Trace tab.

## Tags and Metadata

Tags and metadata passed via LangChain `config` are forwarded to Raindrop event properties:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await agent.invoke(
    { messages: [{ role: "user", content: "Hello" }] },
    {
      callbacks: [raindrop.handler],
      tags: ["production", "v2"],
      metadata: { feature: "onboarding", experimentId: "exp-42" },
    },
  );
  ```

  ```python Python theme={null}
  agent.invoke(
      {"messages": [HumanMessage(content="Hello")]},
      config={
          "callbacks": [rd.handler],
          "tags": ["production", "v2"],
          "metadata": {"feature": "onboarding", "experiment_id": "exp-42"},
      },
  )
  ```
</CodeGroup>

## Identify Users

Associate user metadata with events for richer filtering and trajectories:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await raindrop.users.identify({
    userId: "user-123",
    traits: { plan: "pro", company: "Acme", country: "US" },
  });
  ```

  ```python Python theme={null}
  rd.identify("user-123", traits={"plan": "pro", "company": "Acme", "country": "US"})
  ```
</CodeGroup>

## Track Signals

Track positive / negative feedback or edits attached to a specific event. The TypeScript client exposes `lastEventId`, the `event_id` of the most recently finalized root event, so you can attach feedback to the agent invocation you just ran without polling:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await agent.invoke(
    { messages: [{ role: "user", content: "..." }] },
    { callbacks: [raindrop.handler] },
  );

  if (raindrop.lastEventId) {
    await raindrop.signals.track({
      eventId: raindrop.lastEventId,
      name: "thumbs_up",
      type: "feedback",
      sentiment: "POSITIVE",
      comment: "user liked the answer",
    });
  }
  ```

  ```python Python theme={null}
  rd.track_signal(
      event_id="evt-abc",
      name="thumbs_up",
      signal_type="feedback",
      sentiment="POSITIVE",
      comment="user liked the answer",
  )
  ```
</CodeGroup>

## Async Usage

The handler inherits from LangChain's `BaseCallbackHandler` and supports both sync and async invocations:

<CodeGroup>
  ```typescript TypeScript theme={null}
  // All Deep Agents invocations are already async in TypeScript.
  const result = await agent.invoke(
    { messages: [{ role: "user", content: "Hello" }] },
    { callbacks: [raindrop.handler] },
  );
  ```

  ```python Python theme={null}
  result = await agent.ainvoke(
      {"messages": [HumanMessage(content="Hello")]},
      config={"callbacks": [rd.handler]},
  )
  ```
</CodeGroup>

## Debug Mode

Enable verbose logging when you need to diagnose extraction or shipping issues:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const raindrop = createRaindropDeepAgents({
    writeKey: "your-write-key",
    debug: true,
  });
  ```

  ```python Python theme={null}
  rd = RaindropDeepAgents(api_key="your-write-key", debug=True)
  ```
</CodeGroup>

In Python, `debug=True` raises the `raindrop_deep_agents` logger to `DEBUG` level. The setting is process-global (it mutates the named module logger) — once any instance enables it, subsequent instances will also see DEBUG output.

## Flushing and Shutdown

Always call `shutdown()` before your process exits — `flush()` only sends queued data without waiting for the worker thread to drain:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await raindrop.flush();    // optional: send queued events
  await raindrop.shutdown(); // flush + release resources (always at process exit)
  ```

  ```python Python theme={null}
  rd.flush()      # optional: send queued events
  rd.shutdown()   # flush + release resources (always at process exit)
  ```
</CodeGroup>

## LangGraph Internals

Deep Agents is built on LangGraph. The handler keeps the outer `LangGraph` chain as the canonical root (so OpenTelemetry association context is set before any span exists) and filters internal LangGraph nodes (`__start__`, `__end__`, `ChannelWrite:*`, `ChannelRead:*`, `Branch:*`, dunder names) so they don't pollute the dashboard. LLM callbacks that LangGraph fires multiple times with the same `runId` are deduplicated automatically.

## Python: `wrapt` compatibility

<Note>
  This section applies to the Python integration only. The TypeScript package does not depend on `wrapt`.
</Note>

Works with both `wrapt < 2.0` and `wrapt >= 2.0`. The upstream LangChain OpenTelemetry instrumentor (and \~15 sibling instrumentors) still call `wrap_function_wrapper(module="...")` with the legacy `module=` kwarg that wrapt 2.0 renamed to `target=`. Without a workaround the instrumentor silently fails to activate and traces never land — tracked at [traceloop/openllmetry#4009](https://github.com/traceloop/openllmetry/issues/4009) (and a parallel [Arize-ai/openinference#2996](https://github.com/Arize-ai/openinference/issues/2996) for a sibling instrumentor family). On import, the package installs a small backwards-compat shim (`raindrop_deep_agents/_wrapt_compat.py`) that translates `module=` → `target=`, so both wrapt versions work transparently — no constraint imposed on your environment. The shim becomes pointless once upstream ships a fix.

## Known Limitations

* **Beta status** — API surface may change in minor releases.
* **Deep Agents version** — Requires `deepagents >= 0.5.0`.
* **Streaming** — Token-by-token streaming events are not captured individually; only the final aggregated response per LLM call is tracked.
* **Subagent isolation** — Each subagent invoked via the `task` tool fires its own callback chain. Events are correctly grouped by `convo_id` / `convoId`, but the parent–child trace relationship across subagents is best-effort.
* **OTel context propagation (Python)** — The LangChain instrumentor sometimes does not propagate `traceloop.association.properties.user_id` baggage to every span produced inside a LangGraph callback context. The dashboard still shows the trace under the correct event because the SDK records `trace_id` on the root event's properties, but filtering the global Traces page by `user_id` may not surface every relevant span. Tracked upstream at [traceloop/openllmetry#2271](https://github.com/traceloop/openllmetry/issues/2271).
* **Concurrent invocations on a shared handler** — A single handler instance keeps one in-flight association / span map at a time. Running multiple `agent.invoke(...)` calls **concurrently with the same handler** (e.g. `Promise.all([...])` in TS or `asyncio.gather(...)` in Python) will let later invocations overwrite the earlier one's interaction. Workaround: instantiate one Raindrop client per concurrent request — sequential invocations on a shared instance are fully supported.
* **Long chain inputs/outputs are truncated** — Chain-level input and output captured on the root event are truncated to \~8 KB to stay within the SDK's payload limits. Per-LLM child events still carry full prompts; only the canonical-row summary is shortened.
* **Python SDK feature surface** — The TypeScript client exposes additional low-level APIs (`events.patch`, `events.finish`, `events.addAttachments`, `events.setProperties`). The Python SDK uses module-level functions; equivalent capability is exposed as top-level methods (`identify()`, `track_signal()`) on the `RaindropDeepAgents` class.
