Guide

Monitor your Claude Agent SDK agents in production.

The Claude Agent SDK runs the same loop as Claude Code: Claude reads files, runs tools and calls the model until the task is done, then hands you one ResultMessage with the usage per model and why it stopped. Agent Ping turns that session into a run, so the cost lands against the customer and feature it was for, and a session that ends badly pages someone.

Two lines, then every run reports in.

Install the SDK and point it at your Claude Agent SDK agent. One record lands per run with the cost, status, latency and output behind it.

shellinstall
pip install agentping-io claude-agent-sdk
pythonmonitor your agent
import agentping
from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query

agentping.init(api_key="apk_…")

# One query() is one run. The ResultMessage at the end carries the
# usage per model and how the session ended; report both and finish.
async with agentping.run(agent="pr-reviewer", customer_id="acme-corp") as run:
    async for message in query(prompt="Review the open pull request", options=ClaudeAgentOptions()):
        if isinstance(message, ResultMessage):
            for model, usage in (message.model_usage or {}).items():
                run.event("llm_call", {
                    "provider": "anthropic", "model": model,
                    "input_tokens": usage["inputTokens"] + usage["cacheReadInputTokens"] + usage["cacheCreationInputTokens"],
                    "output_tokens": usage["outputTokens"],
                    "cached_input_tokens": usage["cacheReadInputTokens"],
                })
            run.finish(status="failed" if message.is_error else "success",
                       output={"result": message.result, "subtype": message.subtype})

The SDK reports usage once, at the end, and ResultMessage.model_usage is the only field that includes subagent calls. One llm_call per model from it, priced server side from the rate card, is the whole integration. Full reference is in the docs.

What Agent Ping tracks for Claude Agent SDK.

  • Cost per session, per model, attributed to the customer and feature you tag, including the subagent usage that ResultMessage.usage leaves out.
  • Why the session ended, as a run status you can alert on: success, max turns, max budget or an execution error.
  • Every tool use as a step in the run timeline, through a PostToolUse hook.
  • A missed-run alert when a scheduled agent stops firing.

See the pillars behind these: Spend, Pulse and Verify.

Cost and token usage: read the right field.

A Claude Agent SDK session makes many model calls, some of them inside subagents, and reports them once when the loop ends. Which field you read decides whether your numbers are right.

Use model_usage, not usage

ResultMessage.usage covers the top-level agent loop only; tokens spent inside subagents are missing from it. ResultMessage.model_usage is keyed by model and includes the whole tree, with inputTokens, outputTokens, cacheReadInputTokens, cacheCreationInputTokens and a client-side costUSD estimate. Emit one llm_call per model from it and Agent Ping prices the tokens from its own rate card, so the dashboard reconciles with the Anthropic invoice rather than with an estimate.

Send gross input tokens

Agent Ping expects input_tokens to include the cached prefix, the way Anthropic bills it, with cached_input_tokens broken out so the cache discount is applied. Add cacheReadInputTokens and cacheCreationInputTokens to inputTokens before you send, as the snippet above does. Claude Code sessions reuse long contexts, so cache reads are usually most of the input and the discount is large.

Cap the spend in the SDK, budget it in Agent Ping

ClaudeAgentOptions takes max_turns and max_budget_usd. When either trips, the ResultMessage arrives with subtype error_max_turns or error_max_budget_usd and is_error set, and model_usage still includes the response that crossed the line. Map the subtype to a status so a capped session is a failed run, not a quiet success. Agent Ping budgets work across runs: give the agent or customer a monthly budget and the alert fires before the twentieth session, not inside one.

pythonbudget caps, and a status that says why
options = ClaudeAgentOptions(max_turns=25, max_budget_usd=2.00)

STATUS = {
    "success": "success",
    "error_max_turns": "failed",
    "error_max_budget_usd": "failed",
    "error_during_execution": "failed",
}

async with agentping.run(agent="pr-reviewer", customer_id="acme-corp") as run:
    async for message in query(prompt="…", options=options):
        if isinstance(message, ResultMessage):
            run.finish(
                status=STATUS.get(message.subtype, "failed"),
                output={"subtype": message.subtype, "num_turns": message.num_turns,
                        "duration_ms": message.duration_ms, "result": message.result},
            )

Hooks: every tool use as a step.

Hooks run inside the session on each event. A PostToolUse hook that appends a tool_call event gives the run a timeline: which files were read, which commands ran, what failed, in order.

Attach a PostToolUse hook

ClaudeAgentOptions.hooks maps an event name to a list of HookMatcher entries. A matcher with no pattern fires for every tool; set matcher="Bash" or "Write|Edit" to narrow it. The callback receives the hook input (tool_name, tool_input, tool_response), the tool use id and a context, and returns a dict. Return an empty dict to let the session continue unchanged.

Failures are a separate event

PostToolUse fires only when the tool succeeded. Register the same callback for PostToolUseFailure to record the error, and count those events when you decide the run status. A session that finished with subtype success after three failed Bash commands is worth a look.

Keep the hook cheap

Hooks sit on the session path. run.event() enqueues and returns; the Agent Ping worker ships events in the background with a hard timeout, so the hook adds microseconds, not a network round trip.

pythontool uses as run steps
from claude_agent_sdk import HookContext, HookMatcher

run = agentping.run(agent="pr-reviewer", customer_id="acme-corp")

async def record_tool_use(input_data, tool_use_id, context: HookContext):
    failed = input_data["hook_event_name"] == "PostToolUseFailure"
    run.event("tool_call", {
        "name": input_data["tool_name"],
        "tool_invocation_id": tool_use_id,
        "status": "error" if failed else "ok",
    })
    return {}

options = ClaudeAgentOptions(
    hooks={
        "PostToolUse": [HookMatcher(hooks=[record_tool_use])],
        "PostToolUseFailure": [HookMatcher(hooks=[record_tool_use])],
    },
)

async with run:
    async for message in query(prompt="…", options=options):
        ...

Long and resumed sessions: one record per piece of work.

ClaudeSDKClient keeps a conversation open across several query() calls, and a resumed session continues an earlier one. Pick the Agent Ping shape that matches, and read the totals the way the SDK reports them.

Multi-turn with ClaudeSDKClient

Each receive_response() ends with its own ResultMessage, but its model_usage and total_cost_usd are running totals for the whole call so far, not that turn alone. Summing them double counts. Use agentping.conversation() with a session id you keep alongside the client, record a step per turn, and emit the llm_call events once from the last ResultMessage when the client closes. The conversation lands as one run with its combined cost.

Scheduled sessions

An agent that runs from cron or a queue is the one that fails silently. Set an expected schedule and a grace window on the agent and Agent Ping raises a missed-run alert when the next session does not arrive. A session that crashes before its ResultMessage is caught too: the run was opened and never finished, and the async with block closes it as failed when the exception propagates.

Interactive Claude Code sessions

If the agent is a person at a terminal rather than a program calling the SDK, skip the Python and run npx agentping-claude-code init. It wires Claude Code hooks so each session reports as a run, with the first prompt as its goal and every tool use as a step, and nothing in the session can block on it.

pythona multi-turn session as one run
from claude_agent_sdk import ClaudeSDKClient

session_id = agentping.new_session_id()
chat = agentping.conversation("support-copilot", session_id, customer_id="acme-corp")
last = None

async with ClaudeSDKClient() as client:
    for turn, prompt in enumerate(prompts, start=1):
        await client.query(prompt)
        async for message in client.receive_response():
            if isinstance(message, ResultMessage):
                last = message
                chat.event("step", {"kind": "turn", "subtype": message.subtype}, turn=turn)

# model_usage on the last result is the running total for the whole call.
for model, usage in (last.model_usage or {}).items():
    chat.event("llm_call", {
        "provider": "anthropic", "model": model,
        "input_tokens": usage["inputTokens"] + usage["cacheReadInputTokens"] + usage["cacheCreationInputTokens"],
        "output_tokens": usage["outputTokens"],
        "cached_input_tokens": usage["cacheReadInputTokens"],
    })
chat.finish(status="failed" if last.is_error else "success")
Does Agent Ping price the session or trust costUSD?
It prices the tokens from its rate card, per model, so the figure matches how Anthropic bills and reconciles with the invoice. costUSD in model_usage is a client-side estimate from a price table bundled with the SDK; keep it in the run output if you want to compare the two.
How do I get the total cost of a Claude Agent SDK query?
Read the ResultMessage at the end of the query() stream. total_cost_usd is the cumulative estimate for the call, and model_usage breaks the same spend down by model with token counts. Both include subagent calls; the usage field does not.
Can I see which customer a session cost?
Yes. Tag the run with customer_id and feature when you open it and the spend rolls up by customer, feature, agent and model, with budgets and alerts on each.
What if the session crashes before the ResultMessage?
The async with block closes the run as failed when the exception propagates, and an alert rule on failing runs notifies you. If the process dies outright, the agent's expected schedule and grace window turn the silence into a missed-run alert.
Do I need the Python SDK for Claude Code itself?
No. For interactive Claude Code sessions, npx agentping-claude-code init wires the hooks and each session reports as a run with no code.
Will it slow the agent down?
No. Telemetry runs off the hot path with a hard timeout and a bounded queue, so the session runs unchanged if Agent Ping is unreachable.

Monitor your Claude Agent SDK agents in minutes.

Add the SDK, send one run, and watch cost, status and quality land in the dashboard.

How to monitor LangGraph agents → How to monitor OpenAI Agents SDK agents → How to monitor CrewAI agents →