Observe Claude Agent SDK Instrumentation
This library traces LLM requests made through the Claude Agent SDK and emits OpenTelemetry GenAI semantic conventions.
Don’t install it next to the OpenTelemetry project’s opentelemetry-instrumentation-genai-claude-agent-sdk: both instrument the same SDK, so every call would be recorded twice.
Installation
pip install observe-instrumentation-genai-claude-agent-sdk
If you don’t have a Claude Agent SDK application yet, try our examples which only need a valid Anthropic API key.
Check out the zero-code example for a quick start.
Usage
This section describes how to set up Claude Agent SDK instrumentation if you’re setting OpenTelemetry up manually. Check out the manual example for more details.
from observeinc.instrumentation.genai.claude_agent_sdk import ClaudeAgentSDKInstrumentor
# Instrument Claude Agent SDK
ClaudeAgentSDKInstrumentor().instrument()
# Use Claude Agent SDK as normal
import anyio
from claude_agent_sdk import ClaudeAgentOptions, AgentDefinition, AssistantMessage, TextBlock, query
async def main():
options = ClaudeAgentOptions(
agents={
"assistant": AgentDefinition(
description="A helpful assistant",
prompt="You are a helpful assistant.",
tools=["Read"],
model="sonnet",
),
},
include_partial_messages=True,
)
async for message in query(
prompt="Hello, Claude!",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)
Call instrument() before from claude_agent_sdk import query. The instrumentation replaces query on the claude_agent_sdk module, so a query imported earlier still points at the original function and its calls are not traced. Calls to claude_agent_sdk.query(...) and ClaudeSDKClient are traced regardless of import order. This only matters for manual setup: opentelemetry-instrument instruments before your code runs.
With ClaudeSDKClient, each turn of a connection gets its own invoke_agent span, however many receive_response() calls read it. The span opens under whatever span was active when you called query() for that turn. A turn that starts background work stays open until that work finishes or the next turn begins, so its span can include the time you spent before sending the next prompt.
Configuration
Capture Message Content
By default, prompts and completions are not captured. To capture message content, set the environment variable OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT to one of NO_CONTENT, SPAN_ONLY, EVENT_ONLY, SPAN_AND_EVENT, or ALLOW_LIST:
export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_AND_EVENT
ALLOW_LIST captures no content, like NO_CONTENT, and keeps only a fixed list of attributes on everything recorded, as described in the observe-util-genai README. It drops claude_agent_sdk.usage.is_partial, so under it you cannot tell which token counts are undercounts.
Any other value is rejected with a warning and falls back to NO_CONTENT.
Naming the Agent
The root invoke_agent span is named claude-agent-sdk. To name it after the calling application instead:
ClaudeAgentSDKInstrumentor().instrument(agent_name="support-triage")
Subagents declared in options.agents do not name it. They are delegation targets, and each gets its own nested invoke_agent span when the Agent tool reaches it.
Claude Agent SDK Options
Two ClaudeAgentOptions fields change how much of a run the instrumentation can see. Neither is set on your behalf, because both change what your own message loop receives.
include_partial_messages=True makes the CLI forward stream events, which is what the chat spans need to report a call’s final token counts and stop reason. Without it, those spans carry the mid-stream usage snapshot instead — an undercount, marked on the span so a consumer can tell the difference — and no gen_ai.client.operation.time_to_first_chunk or gen_ai.client.operation.time_per_output_chunk timings are recorded. The token counts on the root invoke_agent span are accurate either way. Turning it on adds StreamEvent messages to the stream your loop iterates.
A subagent’s calls get no stream events even with the option on, so every one of them reports the usage it started with and is marked as a snapshot.
forward_subagent_text=True makes the CLI forward the text and thinking blocks of a subagent spawned through the Agent tool. By default it forwards only that subagent’s tool_use and tool_result blocks, which is enough for a progress heartbeat: the delegation still gets its own nested invoke_agent span, but the content recorded on it is thin, and empty if the subagent never called a tool. Either way the forwarded messages arrive as AssistantMessage / UserMessage objects whose parent_tool_use_id is the spawning Agent tool_use id, so turning the option on adds the subagent’s own text to your loop.
Token Usage
Each invoke_agent span reports only the tokens of its own agent’s calls. A subagent’s calls count on the subagent’s span, not on the span of the agent that delegated to it, so adding up the invoke_agent spans of a run counts each call once. Don’t also add the chat spans: they cover the same calls, so each call would be counted twice. With ClaudeSDKClient each turn gets its own root invoke_agent span; gen_ai.conversation.id groups the turns.
That sum falls short of what the run used in three cases, described below: a subagent’s output, calls the CLI makes for itself, and a turn that ends without a result, whose root span then reports no tokens.
Agent spans carry only the two totals, gen_ai.usage.input_tokens (cache reads and writes included) and gen_ai.usage.output_tokens. The cache and reasoning breakdowns are only on the chat spans, because the GenAI conventions define them for model calls, not for agent spans. To get them for a whole run, add them up from the chat spans.
The root agent takes its counts from the usage field of each ResultMessage, which counts the root agent’s own calls since the previous result, not its subagents’. They are accurate whether or not stream events are on, and for resumed sessions too.
A subagent gets no total of its own from the CLI, so its span adds up its chat spans. Input counts are accurate; output counts are not. The CLI forwards no stream events for a subagent, so each of its calls reports the near-zero output figure from when the call started. The subagent span and each of its chat spans carry claude_agent_sdk.usage.is_partial set to true.
Calls the CLI makes for itself, such as the one that gives a new session its title, are counted on no span. Only the session-wide ResultMessage.model_usage includes them.
A turn that ends without a result, because the stream failed or the run was closed part-way, leaves its root span without token counts. Its chat spans still carry theirs.
No span reports cost. ResultMessage.total_cost_usd covers the whole session and every agent in it, so it fits no single span. Read it in your own code if you need it.
References
Metadata
Release files for observe-instrumentation-genai-claude-agent-sdk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| observe_instrumentation_genai_claude_agent_sdk-0.1.0.tar.gz | 242.2 kB | Without provenance | Details for observe_instrumentation_genai_claude_agent_sdk-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| observe_instrumentation_genai_claude_agent_sdk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for observe_instrumentation_genai_claude_agent_sdk-0.1.0-py3-none-any.whl |
Total release size: 279.5 kB
Release files / observe_instrumentation_genai_claude_agent_sdk-0.1.0.tar.gz
| Download URL | observe_instrumentation_genai_claude_agent_sdk-0.1.0.tar.gz |
|---|---|
| Size | 242.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a35e718683280295cf13bd76a2a5c2fde614e8290b326dc62fb8c927047bb06
|
|
BLAKE2b-256 checksum How to use checksums |
9daa07dbcdb345fbf34c0a9a4f0b91e3c3bfcbf2791909860f45472eb438bbd1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / observe_instrumentation_genai_claude_agent_sdk-0.1.0-py3-none-any.whl
| Download URL | observe_instrumentation_genai_claude_agent_sdk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
003f1ca73b3372b7096f75d18822c21159936e090310c4318e68551a9067e972
|
|
BLAKE2b-256 checksum How to use checksums |
a06c040fe598d5dea0a7a6394c73a8698af166a41fb805baf9fc39576fa4851e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Security for observe-instrumentation-genai-claude-agent-sdk 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.