> ## 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.

# Strands Agents (Beta)

> Automatic tracing for Strands Agents with Raindrop — captures invocations, model calls, and tool usage via the hook system.

The Strands Agents integration instruments the [Strands Agent SDK](https://strandsagents.com) via its hook system to capture agent invocations, model calls, tool usage, and token metrics.

**Features:**

* No OpenTelemetry setup required
* Automatic input/output capture from agent invocations
* Model call tracking with token usage
* Tool call span tracking
* Works with both TypeScript and Python SDKs

## Installation

<Tabs>
  <Tab title="TypeScript">
    <CodeGroup>
      ```bash npm theme={null}
      npm install @raindrop-ai/strands @strands-agents/sdk
      ```

      ```bash yarn theme={null}
      yarn add @raindrop-ai/strands @strands-agents/sdk
      ```

      ```bash pnpm theme={null}
      pnpm add @raindrop-ai/strands @strands-agents/sdk
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    pip install raindrop-strands strands-agents
    ```
  </Tab>
</Tabs>

## Quick Start

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Agent } from "@strands-agents/sdk";
    import { createRaindropStrandsAgents } from "@raindrop-ai/strands";

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

    // 2. Create your agent
    const agent = new Agent({
      model: "us.amazon.nova-lite-v1:0",
      systemPrompt: "You are a helpful assistant.",
    });

    // 3. Register hooks
    raindrop.handler.registerHooks(agent);

    // 4. Use the agent normally — invocations are traced automatically
    const result = await agent("What is the capital of France?");
    console.log(result);

    // 5. Flush before shutdown
    await raindrop.flush();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    from strands import Agent
    from raindrop_strands import RaindropStrands

    # 1. Create the Raindrop client
    raindrop = RaindropStrands(
        api_key=os.environ["RAINDROP_API_KEY"],
        user_id="user_123",
    )

    # 2. Create your agent
    agent = Agent(
        model="us.amazon.nova-lite-v1:0",
        system_prompt="You are a helpful assistant.",
    )

    # 3. Register hooks
    raindrop.handler.register_hooks(agent)

    # 4. Use the agent normally — invocations are traced automatically
    result = agent("What is the capital of France?")
    print(result)

    # 5. Flush before shutdown
    raindrop.flush()
    ```
  </Tab>
</Tabs>

***

## What Gets Traced

The Strands integration automatically captures:

* **Agent invocations** — input prompt (last user message), output text, model name
* **Token usage** — prompt\_tokens, completion\_tokens, and cached\_tokens (from Bedrock/Anthropic `cacheReadInputTokens` / `cacheCreationInputTokens`)
* **Model calls** — output and usage extracted from each model call within an invocation
* **Tool call spans** — individual tool spans tracked via `interaction.track_tool()` with name, input, output, duration (ms), and error
* **Finish reason** — `stop_reason` or `finish_reason` from model responses (e.g., `end_turn`, `tool_use`), included in properties as `strands.finish_reason`
* **Errors** — captured and re-raised; telemetry failures never crash your pipeline

***

## Configuration

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const raindrop = createRaindropStrandsAgents({
      writeKey: "your-write-key",           // Optional: your Raindrop write key (omit to disable telemetry)
      userId: "user-123",          // Optional: associate events with a user
      convoId: "convo-456",        // Optional: conversation/thread ID
      projectId: "support-prod",   // Optional: route events to a specific project (slug)
      endpoint: "https://...",     // Optional: custom API endpoint
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    raindrop = RaindropStrands(
        api_key="your-write-key",           # Optional: your Raindrop API key (omit to disable telemetry)
        user_id="user-123",        # Optional: associate events with a user
        convo_id="convo-456",      # Optional: conversation/thread ID
        project_id="support-prod", # Optional: route events to a specific project (slug)
        tracing_enabled=True,      # Optional: enable OTEL-based tracing (default: True)
        bypass_otel_for_tools=True,# Optional: bypass OTEL for tool spans (default: True)
        debug=False,               # Optional: enable debug logging (default: False)
    )
    ```
  </Tab>
</Tabs>

***

## Projects

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

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const raindrop = createRaindropStrandsAgents({
      writeKey: "your-write-key",
      projectId: "support-prod",
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    raindrop = RaindropStrands(
        api_key="your-write-key",
        project_id="support-prod",
    )
    ```
  </Tab>
</Tabs>

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 Strands
agents that should report to **different** projects, create one
`RaindropStrands` wrapper per project. Each wrapper owns its own
`raindrop.Raindrop` client, so the two route independently — there is no shared
module-level state:

```python theme={null}
from raindrop_strands import RaindropStrands

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

# Register each wrapper's hooks on the corresponding agent.
rd_support.handler.register_hooks(support_agent)   # -> support-prod
rd_billing.handler.register_hooks(billing_agent)   # -> billing-prod

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

Each wrapper owns its configuration and delivery pipeline, so agents handled by
different wrappers 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")
raindrop = RaindropStrands(client=client)
```

***

## Identifying Users

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await raindrop.users.identify({
      userId: "user_123",
      traits: {
        email: "user@example.com",
        plan: "pro",
      },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    raindrop.identify(user_id="user_123", traits={"plan": "pro"})
    ```
  </Tab>
</Tabs>

***

## Signals (Feedback)

Track user feedback on AI responses:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await raindrop.signals.track({
      eventId: "evt_...",
      name: "thumbs_up",
      type: "feedback",
      sentiment: "POSITIVE",
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    raindrop.track_signal(
        event_id="evt_...",
        name="thumbs_up",
        signal_type="feedback",
        sentiment="POSITIVE",
    )
    ```
  </Tab>
</Tabs>

***

## Flush & Shutdown

Always flush before your process exits to ensure all data is sent:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Flush pending events
    await raindrop.flush();

    // Or shutdown gracefully (flush + release resources)
    await raindrop.shutdown();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    raindrop.flush()       # flush pending data
    raindrop.shutdown()    # flush + release resources
    ```
  </Tab>
</Tabs>

***

## Captured Properties

The following properties are automatically included in tracked events:

| Property                     | Description                                      |
| ---------------------------- | ------------------------------------------------ |
| `ai.usage.prompt_tokens`     | Input token count                                |
| `ai.usage.completion_tokens` | Output token count                               |
| `ai.usage.cached_tokens`     | Cached input tokens (Bedrock/Anthropic)          |
| `strands.finish_reason`      | Model stop reason (e.g., `end_turn`, `tool_use`) |
| `error.type`                 | Exception class name (when an error occurs)      |
| `error.message`              | Error message text (truncated to 500 chars)      |

***

## Token Tracking

The integration automatically captures token usage from model responses:

| Property                     | Description                                                                                    |
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
| `ai.usage.prompt_tokens`     | Input token count (`inputTokens` from Strands model response)                                  |
| `ai.usage.completion_tokens` | Output token count (`outputTokens` from Strands model response)                                |
| `ai.usage.cached_tokens`     | Cached input tokens (prefers `cacheReadInputTokens`, falls back to `cacheCreationInputTokens`) |

Tokens are extracted from both individual model call responses (`stop_response.usage`) and accumulated metrics (`result.metrics.accumulated_usage`).

***

## Finish Reason

The model's stop reason is captured automatically and included in event properties as `strands.finish_reason`. The integration checks `stop_reason` first (Bedrock convention), then falls back to `finish_reason`.

***

## Factory Function

A `create_raindrop_strands()` factory is available for convenience:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from raindrop_strands import create_raindrop_strands

    raindrop = create_raindrop_strands(api_key="rk_...")
    agent = Agent(model="us.amazon.nova-lite-v1:0")
    raindrop.handler.register_hooks(agent)
    result = agent("Hello!")
    raindrop.flush()
    ```
  </Tab>
</Tabs>

***

## Known Limitations

* **Streaming**: The integration captures the final result of each model call. Streaming token-by-token output is not individually traced.
* **Multi-agent**: Each agent needs its own `registerHooks()` / `register_hooks()` call. Nested agent hierarchies are not automatically linked.
* **Python WeakRef**: The Python handler uses `id(agent)` for internal tracking since some agent-like objects don't support weak references. Context is cleaned up explicitly after each invocation.

***

That's it! You're ready to explore your Strands agent events in the Raindrop dashboard. Ping us on Slack or [email us](mailto:founders@raindrop.ai) if you get stuck!
