Docs Anthropic SDK

Integration Guide

Monitor Claude Agents with Nexus

Instrument AI agents built with Anthropic's Claude API. Track every LLM call, tool use, and agent decision — in TypeScript or Python.

The meta-narrative

Nexus was built by an AI agent named Ralph, using Claude. Ralph instrumented itself using this SDK to track its own build sessions. This guide shows you how to replicate that: Claude-powered agents monitoring themselves through Nexus. It's agents all the way down.

Why use Nexus with Claude?

Step 1 — Install both SDKs

TypeScript

npm install @keylightdigital/nexus @anthropic-ai/sdk

Python

pip install keylightdigital-nexus anthropic

Step 2 — Create an API key

Go to /dashboard/keys and create a new API key. Add it as NEXUS_API_KEY alongside your ANTHROPIC_API_KEY.

Step 3 — Instrument your Claude agent

TypeScript — tool-use agent loop

import Anthropic from '@anthropic-ai/sdk';
import { NexusClient } from '@keylightdigital/nexus';

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const nexus = new NexusClient({
  apiKey: process.env.NEXUS_API_KEY!,
  agentId: 'claude-research-agent',
});

const tools: Anthropic.Tool[] = [
  {
    name: 'web_search',
    description: 'Search the web for information',
    input_schema: {
      type: 'object',
      properties: { query: { type: 'string', description: 'Search query' } },
      required: ['query'],
    },
  },
];

async function runAgent(userMessage: string) {
  const trace = await nexus.startTrace({
    name: `Claude agent: ${userMessage.slice(0, 60)}`,
    metadata: { model: 'claude-opus-4-6', maxTurns: 5 },
  });

  const messages: Anthropic.MessageParam[] = [
    { role: 'user', content: userMessage }
  ];

  try {
    let turn = 0;
    while (turn < 5) {
      turn++;

      // Track each LLM call as a span
      const llmSpan = await trace.addSpan({
        name: `llm-call-turn-${turn}`,
        input: { messages: messages.length, turn },
      });

      const response = await anthropic.messages.create({
        model: 'claude-opus-4-6',
        max_tokens: 1024,
        tools,
        messages,
      });

      await trace.addSpan({
        name: `llm-response-turn-${turn}`,
        output: {
          stop_reason: response.stop_reason,
          input_tokens: response.usage.input_tokens,
          output_tokens: response.usage.output_tokens,
        },
      });

      if (response.stop_reason === 'end_turn') {
        const text = response.content
          .filter(b => b.type === 'text')
          .map(b => (b as Anthropic.TextBlock).text)
          .join('');
        await trace.end({ status: 'success' });
        return text;
      }

      // Handle tool calls
      if (response.stop_reason === 'tool_use') {
        const toolUses = response.content.filter(b => b.type === 'tool_use') as Anthropic.ToolUseBlock[];
        const toolResults: Anthropic.ToolResultBlockParam[] = [];

        for (const toolUse of toolUses) {
          // Track each tool call as a span
          await trace.addSpan({
            name: `tool-${toolUse.name}`,
            input: toolUse.input as Record,
            output: { result: 'tool result here' },
          });

          // Execute the tool (your implementation here)
          toolResults.push({
            type: 'tool_result',
            tool_use_id: toolUse.id,
            content: `Result for ${toolUse.name}`,
          });
        }

        messages.push(
          { role: 'assistant', content: response.content },
          { role: 'user', content: toolResults }
        );
      }
    }

    await trace.end({ status: 'error' });
    return 'Max turns reached';
  } catch (error) {
    await trace.end({ status: 'error' });
    throw error;
  }
}

Python — tool-use agent loop

import anthropic
from nexus_client import NexusClient
import os

client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
nexus = NexusClient(
    api_key=os.environ["NEXUS_API_KEY"],
    agent_id="claude-research-agent",
)

tools = [
    {
        "name": "web_search",
        "description": "Search the web for information",
        "input_schema": {
            "type": "object",
            "properties": {"query": {"type": "string", "description": "Search query"}},
            "required": ["query"],
        },
    }
]

def run_agent(user_message: str) -> str:
    trace = nexus.start_trace(
        name=f"Claude agent: {user_message[:60]}",
        metadata={"model": "claude-opus-4-6", "max_turns": 5},
    )

    messages = [{"role": "user", "content": user_message}]

    try:
        for turn in range(1, 6):
            trace.add_span(
                name=f"llm-call-turn-{turn}",
                input={"message_count": len(messages), "turn": turn},
            )

            response = client.messages.create(
                model="claude-opus-4-6",
                max_tokens=1024,
                tools=tools,
                messages=messages,
            )

            trace.add_span(
                name=f"llm-response-turn-{turn}",
                output={
                    "stop_reason": response.stop_reason,
                    "input_tokens": response.usage.input_tokens,
                    "output_tokens": response.usage.output_tokens,
                },
            )

            if response.stop_reason == "end_turn":
                text = "".join(
                    block.text for block in response.content
                    if block.type == "text"
                )
                trace.end(status="success")
                return text

            if response.stop_reason == "tool_use":
                tool_uses = [b for b in response.content if b.type == "tool_use"]
                tool_results = []

                for tool_use in tool_uses:
                    # Your tool implementation here
                    result = f"Result for {tool_use.name}"
                    trace.add_span(
                        name=f"tool-{tool_use.name}",
                        input=tool_use.input,
                        output={"result": result},
                    )
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": result,
                    })

                messages.append({"role": "assistant", "content": response.content})
                messages.append({"role": "user", "content": tool_results})

        trace.end(status="error")
        return "Max turns reached"

    except Exception as e:
        trace.end(status="error")
        raise

Step 4 — View agent traces

Run your agent and navigate to /dashboard/traces. Each agent invocation appears as a trace. The span waterfall shows every LLM call and tool use in sequence — with token counts in the span metadata.

View demo with sample agent traces →

More resources

Start monitoring your Claude agents

Free plan: 1,000 traces/month. No credit card needed. Built for Claude agents, by a Claude agent.