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

# Claude Agent SDK

> Install moda-claude-agent-sdk to capture Claude Agent SDK runs with prompts, responses, token usage, and tool-call counts.

`moda-claude-agent-sdk` is a separate PyPI package that instruments the Claude Agent SDK (`claude-agent-sdk`) for Python. The Claude Agent SDK runs Claude Code as a subprocess rather than calling the Anthropic API from your process, so the Anthropic instrumentation in `moda-ai` never sees those calls — this package wraps `ClaudeSDKClient` directly instead. It is Python only; there is no Node.js equivalent.

## Prerequisites

* Python 3.10 or later
* The `moda-ai` and `claude-agent-sdk` packages
* A Moda API key, created at **Settings → Ingestion keys** (see [Authentication](/administration/authentication))

## Set up

<Steps>
  <Step title="Install all three packages">
    ```bash theme={"dark"}
    pip install moda-ai moda-claude-agent-sdk claude-agent-sdk
    ```

    `moda-claude-agent-sdk` is not bundled with `moda-ai` — it must be installed explicitly.
  </Step>

  <Step title="Initialize Moda and run the agent">
    Call `moda.init()` before constructing `ClaudeSDKClient`. No other wiring is needed: `moda.init()` activates the instrumentation automatically when both packages are installed.

    ```python agent.py theme={"dark"}
    import asyncio
    import os

    import moda
    from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

    moda.init(os.environ["MODA_API_KEY"])

    moda.conversation_id = "session_8f2a"
    moda.user_id = "user_1042"

    async def main() -> None:
        options = ClaudeAgentOptions(model="claude-sonnet-4-20250514")
        async with ClaudeSDKClient(options=options) as client:
            await client.query("What is the capital of France?")

            # Consume the full stream — the final ResultMessage carries token usage
            async for message in client.receive_response():
                print(message)

    asyncio.run(main())

    moda.flush()
    ```
  </Step>

  <Step title="Verify in the dashboard">
    Open **Conversations** in the [dashboard](https://moda.dev/dashboard). Within minutes you should see a conversation with ID `session_8f2a` containing your prompt as a user message and the agent's replies as assistant messages, with token counts attached.
  </Step>
</Steps>

## How it works

The package wraps two methods on `ClaudeSDKClient`:

* `query()` — captures the prompt.
* `receive_response()` — wraps the returned async generator; messages pass through unchanged while token usage, completions, tool-call counts, and agent metadata accumulate.

Each `query()` / `receive_response()` cycle produces one span (`claude_agent.chat`) carrying the prompt, the assistant completions from that run, token usage, and agent metadata. Prompt content is captured up to 4,000 characters and each assistant completion up to 8,000 characters.

<Warning>
  Token usage comes from the `ResultMessage` at the end of the stream. If your code breaks out of `receive_response()` early, the run is still recorded but token counts are missing. Always consume the generator to completion. With `ClaudeAgentOptions(include_partial_messages=True)`, usage is also accumulated from streaming events as a secondary source.
</Warning>

## Conversation grouping

Each agent run is a separate trace, so without an explicit conversation ID every run lands in its own conversation. Set `moda.conversation_id` (or use the `with moda.set_conversation_id(...)` context manager) before running the agent so that multi-turn sessions group together:

```python multi_turn.py theme={"dark"}
import asyncio
import os

import moda
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

moda.init(os.environ["MODA_API_KEY"])

async def main() -> None:
    options = ClaudeAgentOptions(model="claude-sonnet-4-20250514")
    # All queries in this block share one conversation
    with moda.set_conversation_id("session_8f2a"), moda.set_user_id("user_1042"):
        async with ClaudeSDKClient(options=options) as client:
            await client.query("What is the capital of France?")
            async for message in client.receive_response():
                print(message)

            await client.query("What is its population?")
            async for message in client.receive_response():
                print(message)

asyncio.run(main())

moda.flush()
```

The agent's own session ID is recorded as the `claude_agent.session_id` attribute, but it is not used for conversation grouping — only `moda.conversation_id` is.

## What gets captured

| Attribute                              | Contents                                                     |
| -------------------------------------- | ------------------------------------------------------------ |
| `gen_ai.request.model`                 | Model requested via `ClaudeAgentOptions`                     |
| `gen_ai.response.model`                | Model reported in the response                               |
| `gen_ai.usage.input_tokens`            | Input tokens, including cache-read and cache-creation tokens |
| `gen_ai.usage.output_tokens`           | Output tokens                                                |
| `llm.usage.total_tokens`               | Input plus output tokens                                     |
| `claude_agent.num_turns`               | Number of turns in the run                                   |
| `claude_agent.session_id`              | The agent's session identifier                               |
| `claude_agent.tool_call_count`         | Tool-use blocks counted across the run                       |
| `moda.conversation_id`, `moda.user_id` | Your conversation and user context, when set                 |

Streaming partial messages (`include_partial_messages=True`) and tool use are captured; assistant text streamed in deltas is assembled per turn.

## Troubleshooting

| Symptom                               | Fix                                                                                                                                                                                   |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No data appears                       | Install `moda-claude-agent-sdk` explicitly (it is not part of `moda-ai`), call `moda.init()` before constructing `ClaudeSDKClient`, and call `moda.flush()` before the process exits. |
| Token counts are missing              | The stream was not consumed to completion — iterate `receive_response()` until it is exhausted, or enable `include_partial_messages=True`.                                            |
| Every turn is a separate conversation | No explicit conversation ID was set. Set `moda.conversation_id` before running the agent.                                                                                             |

## Next steps

* [Moda SDK for Python](/ingestion/python) — init options, environment variables, and context APIs.
* [Anthropic](/ingestion/providers/anthropic) — auto-instrumentation for direct Anthropic API calls.
* [Conversations](/dashboard/conversations) — how agent runs appear in the dashboard.
* [Data model](/concepts/data-model) — how conversations, messages, and signals relate.
