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?
- ✓ Tool use tracing — every tool call appears as a span
- ✓ Token-level visibility — log input/output tokens in span metadata
- ✓ Multi-turn agents — trace the full agentic loop, not just one call
- ✓ TypeScript + Python — works with @anthropic-ai/sdk and anthropic Python
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.