Real-Time Performance Monitoring and Optimization for Claude Code Agents
Overview
As Claude Code agents handle increasingly complex autonomous tasks, real-time performance monitoring and optimization becomes crucial. This guide provides comprehensive patterns for implementing observability, detecting performance bottlenecks, and automatically optimizing agent workflows to maintain peak efficiency.
Practical First Steps
This guide covers an advanced, comprehensive architecture for observability. To get started without implementing the entire framework, follow these initial steps:
-
Define Basic Metrics: Before writing complex collectors, identify 3-5 key performance indicators (KPIs) for your agent. Start with:
task_completion_time: How long a single task takes.token_usage_per_task: The token cost of a single task.error_rate: The percentage of tasks that fail.
-
Implement a Simple Logger: Use a simple, structured logger to record these metrics to the console or a file. You can build from this later.
// src/simple-logger.ts import fs from 'fs/promises'; export interface SimpleMetric { metric: 'task_completion_time' | 'token_usage_per_task' | 'error_rate'; value: number; agentId: string; timestamp: string; } export async function logMetric(metric: SimpleMetric) { const logLine = JSON.stringify(metric) + '\n'; try { await fs.appendFile('performance.log', logLine); } catch (err) { console.error('Failed to write metric:', err); } } -
Instrument Your Core Logic: Add this logging to the most critical parts of your agent’s workflow.
// src/agent.ts import { logMetric } from './simple-logger'; async function handleTask(task) { const startTime = Date.now(); try { // ... your agent logic ... const tokens = 1000; // Replace with actual token count await logMetric({ metric: 'task_completion_time', value: Date.now() - startTime, agentId: 'agent-001', timestamp: new Date().toISOString() }); await logMetric({ metric: 'token_usage_per_task', value: tokens, agentId: 'agent-001', timestamp: new Date().toISOString() }); } catch (error) { await logMetric({ metric: 'error_rate', value: 1, agentId: 'agent-001', timestamp: new Date().toISOString() }); } }
With these simple steps, you have a foundational monitoring system. You can now incrementally adopt the more advanced patterns described in this document as your needs evolve.
Performance Metrics Framework
Core Metrics to Track
interface AgentPerformanceMetrics {
// Task execution metrics
taskMetrics: {
completionRate: number; // Success rate percentage
averageExecutionTime: number; // milliseconds
taskThroughput: number; // tasks per hour
queueDepth: number; // pending tasks
};
// Resource utilization
resourceMetrics: {
tokenUsage: {
input: number;
output: number;
total: number;
costEstimate: number;
};
apiCalls: {
count: number;
rateLimitUtilization: number; // percentage
averageLatency: number;
};
memoryUsage: number;
cpuUtilization: number;
};
// Quality metrics
qualityMetrics: {
codeQualityScore: number;
testCoverage: number;
bugDensity: number;
userSatisfaction: number;
};
// Efficiency metrics
efficiencyMetrics: {
contextWindowUtilization: number;
cacheHitRate: number;
parallelizationEfficiency: number;
wastedOperations: number;
};
}Real-Time Collection Pipeline
// High-performance metrics collection
class MetricsCollector {
private buffer: MetricEvent[] = [];
private flushInterval = 1000; // 1 second
constructor(private sink: MetricsSink) {
this.startFlushTimer();
}
record(metric: MetricEvent) {
// Non-blocking collection
this.buffer.push({
...metric,
timestamp: Date.now(),
agentId: this.getAgentId()
});
// Flush if buffer is full
if (this.buffer.length >= 1000) {
this.flush();
}
}
private async flush() {
if (this.buffer.length === 0) return;
const batch = this.buffer.splice(0);
// Async flush to not block agent
setImmediate(async () => {
try {
await this.sink.ingest(batch);
} catch (error) {
console.error('Metrics flush failed:', error);
// Re-queue critical metrics
this.requeueCriticalMetrics(batch);
}
});
}
}Performance Monitoring Architecture
1. Distributed Tracing
// Trace context for distributed operations
class TraceContext {
private spans: Map<string, Span> = new Map();
startSpan(name: string, parent?: string): Span {
const span = {
id: generateSpanId(),
traceId: this.traceId,
name,
startTime: performance.now(),
parent,
tags: new Map<string, any>(),
events: []
};
this.spans.set(span.id, span);
return span;
}
endSpan(spanId: string) {
const span = this.spans.get(spanId);
if (!span) return;
span.endTime = performance.now();
span.duration = span.endTime - span.startTime;
// Send to collector
this.sendSpan(span);
}
// Instrument Claude Code operations
async instrumentOperation<T>(
name: string,
operation: () => Promise<T>
): Promise<T> {
const span = this.startSpan(name);
try {
const result = await operation();
span.tags.set('status', 'success');
return result;
} catch (error) {
span.tags.set('status', 'error');
span.tags.set('error', error.message);
throw error;
} finally {
this.endSpan(span.id);
}
}
}2. Real-Time Analytics Engine
// Stream processing for real-time insights
class PerformanceAnalytics {
private windows: Map<string, TimeWindow> = new Map();
async processMetricStream(stream: ReadableStream<MetricEvent>) {
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Update time windows
this.updateWindows(value);
// Detect anomalies
const anomalies = await this.detectAnomalies(value);
if (anomalies.length > 0) {
await this.handleAnomalies(anomalies);
}
// Check for optimization opportunities
const optimizations = this.identifyOptimizations();
if (optimizations.length > 0) {
await this.applyOptimizations(optimizations);
}
}
}
private detectAnomalies(metric: MetricEvent): Anomaly[] {
const anomalies = [];
// Response time anomaly
if (metric.type === 'response_time') {
const baseline = this.getBaseline('response_time');
if (metric.value > baseline * 2) {
anomalies.push({
type: 'performance_degradation',
severity: 'high',
metric: 'response_time',
value: metric.value,
threshold: baseline * 2
});
}
}
// Token usage spike
if (metric.type === 'token_usage') {
const avgUsage = this.getAverage('token_usage', '5m');
if (metric.value > avgUsage * 3) {
anomalies.push({
type: 'token_spike',
severity: 'medium',
metric: 'token_usage',
value: metric.value,
average: avgUsage
});
}
}
return anomalies;
}
}3. Performance Dashboard
// Real-time dashboard configuration
const dashboardConfig = {
layout: {
grid: [
{ x: 0, y: 0, w: 6, h: 4, component: 'TaskThroughput' },
{ x: 6, y: 0, w: 6, h: 4, component: 'ResponseTimeChart' },
{ x: 0, y: 4, w: 4, h: 3, component: 'TokenUsageGauge' },
{ x: 4, y: 4, w: 4, h: 3, component: 'ErrorRateChart' },
{ x: 8, y: 4, w: 4, h: 3, component: 'CostTracker' }
]
},
widgets: {
TaskThroughput: {
type: 'timeseries',
metrics: ['tasks.completed', 'tasks.failed'],
window: '1h',
refreshRate: 5000
},
ResponseTimeChart: {
type: 'heatmap',
metric: 'response_time',
percentiles: [50, 75, 90, 95, 99],
window: '30m'
},
TokenUsageGauge: {
type: 'gauge',
metric: 'tokens.total',
thresholds: {
green: { max: 10000 },
yellow: { max: 50000 },
red: { max: 100000 }
}
}
}
};Automatic Performance Optimization
1. Dynamic Resource Allocation
// Intelligent resource management
class ResourceOptimizer {
private performanceHistory: PerformanceHistory;
private resourceLimits: ResourceLimits;
async optimizeAllocation() {
const currentMetrics = await this.getCurrentMetrics();
const predictions = await this.predictResourceNeeds();
// Optimize token allocation
if (predictions.tokenPressure > 0.8) {
await this.optimizeTokenUsage();
}
// Adjust concurrency
const optimalConcurrency = this.calculateOptimalConcurrency(
currentMetrics.throughput,
currentMetrics.latency
);
await this.adjustConcurrency(optimalConcurrency);
// Cache optimization
if (currentMetrics.cacheHitRate < 0.7) {
await this.optimizeCaching();
}
}
private async optimizeTokenUsage() {
// Compress context
await this.enableContextCompression();
// Use more efficient prompts
await this.switchToOptimizedPrompts();
// Enable token recycling
await this.enableTokenRecycling();
}
private calculateOptimalConcurrency(
throughput: number,
latency: number
): number {
// Little's Law: L = λW
const arrivalRate = throughput / 3600; // per second
const responseTime = latency / 1000; // seconds
const optimalConcurrency = Math.ceil(arrivalRate * responseTime * 1.2);
// Apply constraints
return Math.min(
Math.max(optimalConcurrency, 1),
this.resourceLimits.maxConcurrency
);
}
}2. Adaptive Workflow Optimization
// Self-optimizing workflow engine
class WorkflowOptimizer {
private workflowPerformance = new Map<string, WorkflowStats>();
async optimizeWorkflow(workflow: Workflow): Promise<OptimizedWorkflow> {
// Analyze historical performance
const stats = this.workflowPerformance.get(workflow.id);
// Identify bottlenecks
const bottlenecks = this.identifyBottlenecks(workflow, stats);
// Generate optimization strategies
const strategies = [];
for (const bottleneck of bottlenecks) {
switch (bottleneck.type) {
case 'sequential_tasks':
strategies.push(this.parallelizeIndependentTasks(bottleneck));
break;
case 'redundant_operations':
strategies.push(this.eliminateRedundancy(bottleneck));
break;
case 'expensive_operations':
strategies.push(this.optimizeExpensiveOps(bottleneck));
break;
}
}
// Apply optimizations
return this.applyOptimizations(workflow, strategies);
}
private parallelizeIndependentTasks(bottleneck: Bottleneck) {
return {
type: 'parallelize',
tasks: bottleneck.tasks,
expectedImprovement: bottleneck.tasks.length * 0.7,
implementation: async (workflow: Workflow) => {
const dependencies = this.analyzeDependencies(bottleneck.tasks);
const parallelGroups = this.createParallelGroups(dependencies);
return this.restructureWorkflow(workflow, parallelGroups);
}
};
}
}3. Intelligent Caching
// Smart caching system with ML-based eviction
class IntelligentCache {
private cache = new Map<string, CacheEntry>();
private accessPatterns: AccessPattern[] = [];
private evictionModel: EvictionModel;
async get(key: string): Promise<any> {
const entry = this.cache.get(key);
if (entry) {
// Record access
this.recordAccess(key, 'hit');
// Update entry statistics
entry.lastAccess = Date.now();
entry.accessCount++;
return entry.value;
}
this.recordAccess(key, 'miss');
return null;
}
async set(key: string, value: any, metadata?: CacheMetadata) {
// Predict value lifetime
const predictedLifetime = await this.predictLifetime(key, metadata);
// Check if worth caching
if (!this.shouldCache(value, predictedLifetime)) {
return;
}
// Make room if needed
if (this.cache.size >= this.maxSize) {
await this.evict();
}
this.cache.set(key, {
value,
created: Date.now(),
lastAccess: Date.now(),
accessCount: 1,
predictedLifetime,
size: this.calculateSize(value)
});
}
private async evict() {
// ML-based eviction scoring
const scores = await this.evictionModel.scoreEntries(
Array.from(this.cache.entries()),
this.accessPatterns
);
// Evict lowest scoring entries
const toEvict = scores
.sort((a, b) => a.score - b.score)
.slice(0, Math.ceil(this.cache.size * 0.2))
.map(s => s.key);
for (const key of toEvict) {
this.cache.delete(key);
}
}
}Claude Code Specific Optimizations
1. Context Window Management
// Optimize context window usage
class ContextOptimizer {
private contextStats = new ContextStatistics();
async optimizeContext(
messages: Message[]
): Promise<OptimizedContext> {
// Analyze context usage patterns
const analysis = await this.analyzeContext(messages);
// Apply optimization strategies
let optimized = messages;
// Remove redundant information
if (analysis.redundancy > 0.2) {
optimized = await this.deduplicateContext(optimized);
}
// Compress verbose sections
if (analysis.verbosity > 0.7) {
optimized = await this.compressContext(optimized);
}
// Prioritize recent and relevant
if (analysis.size > 0.8 * this.maxContextSize) {
optimized = await this.prioritizeContext(optimized);
}
return {
messages: optimized,
reduction: 1 - (optimized.length / messages.length),
quality: await this.assessQuality(optimized)
};
}
private async compressContext(messages: Message[]): Promise<Message[]> {
return messages.map(msg => ({
...msg,
content: this.compressContent(msg.content)
}));
}
private compressContent(content: string): string {
// Remove extra whitespace
let compressed = content.replace(/\s+/g, ' ').trim();
// Abbreviate common patterns
const abbreviations = {
'function': 'fn',
'return': 'ret',
'const': 'const',
'implements': 'impl'
};
for (const [full, abbr] of Object.entries(abbreviations)) {
compressed = compressed.replace(
new RegExp(`\\b${full}\\b`, 'g'),
abbr
);
}
return compressed;
}
}2. Tool Call Optimization
// Optimize tool usage patterns
class ToolOptimizer {
private toolStats = new Map<string, ToolStatistics>();
async optimizeToolCalls(
plannedCalls: ToolCall[]
): Promise<OptimizedToolCalls> {
// Batch similar operations
const batched = this.batchOperations(plannedCalls);
// Parallelize independent calls
const parallelized = this.parallelizeIndependent(batched);
// Cache frequent results
const cached = await this.applyCaching(parallelized);
// Reorder for efficiency
const reordered = this.reorderForEfficiency(cached);
return {
calls: reordered,
estimatedSavings: this.calculateSavings(plannedCalls, reordered)
};
}
private batchOperations(calls: ToolCall[]): ToolCall[] {
const grouped = new Map<string, ToolCall[]>();
// Group by tool and operation type
for (const call of calls) {
const key = `${call.tool}-${call.operation}`;
if (!grouped.has(key)) {
grouped.set(key, []);
}
grouped.get(key)!.push(call);
}
// Create batched calls
const batched = [];
for (const [key, group] of grouped) {
if (group.length > 1 && this.canBatch(group[0].tool)) {
batched.push(this.createBatchCall(group));
} else {
batched.push(...group);
}
}
return batched;
}
}3. Workflow Learning
// Learn and optimize from execution patterns
class WorkflowLearner {
private executionHistory: ExecutionTrace[] = [];
private patterns = new Map<string, WorkflowPattern>();
async learnFromExecution(trace: ExecutionTrace) {
this.executionHistory.push(trace);
// Extract patterns
const patterns = await this.extractPatterns(trace);
// Update pattern database
for (const pattern of patterns) {
this.updatePattern(pattern);
}
// Generate optimizations
if (this.executionHistory.length % 100 === 0) {
await this.generateOptimizations();
}
}
private async generateOptimizations(): Promise<Optimization[]> {
const optimizations = [];
// Analyze common sequences
const sequences = this.findCommonSequences();
for (const seq of sequences) {
if (seq.frequency > 10 && seq.averageTime > 1000) {
optimizations.push({
type: 'create_macro',
description: `Create macro for ${seq.description}`,
expectedSaving: seq.averageTime * 0.5
});
}
}
// Identify redundant operations
const redundancies = this.findRedundancies();
for (const redundancy of redundancies) {
optimizations.push({
type: 'eliminate_redundancy',
description: redundancy.description,
expectedSaving: redundancy.wastedTime
});
}
return optimizations;
}
}Performance Testing and Benchmarking
1. Load Testing Framework
// Comprehensive load testing
class LoadTester {
async runLoadTest(config: LoadTestConfig): Promise<LoadTestResults> {
const results = {
throughput: [],
latency: [],
errors: [],
resourceUsage: []
};
// Ramp up load gradually
for (let users = 1; users <= config.maxUsers; users *= 2) {
const stageResults = await this.runStage({
users,
duration: config.stageDuration,
scenario: config.scenario
});
results.throughput.push(stageResults.throughput);
results.latency.push(stageResults.latency);
results.errors.push(stageResults.errors);
results.resourceUsage.push(stageResults.resources);
// Check for breaking point
if (stageResults.errorRate > config.errorThreshold) {
results.breakingPoint = users;
break;
}
}
return results;
}
}2. Performance Regression Detection
// Detect performance regressions automatically
class RegressionDetector {
private baselines = new Map<string, PerformanceBaseline>();
async checkForRegression(
metric: string,
value: number
): Promise<RegressionResult> {
const baseline = this.baselines.get(metric);
if (!baseline) {
return { regression: false, newBaseline: true };
}
// Statistical significance test
const zScore = (value - baseline.mean) / baseline.stdDev;
const pValue = this.calculatePValue(zScore);
if (pValue < 0.05 && value > baseline.mean) {
return {
regression: true,
severity: this.calculateSeverity(value, baseline),
confidence: 1 - pValue,
recommendation: this.generateRecommendation(metric, value, baseline)
};
}
return { regression: false };
}
}Best Practices
1. Lightweight Instrumentation
// Minimal overhead monitoring
const lightweightMonitor = {
// Use sampling for high-frequency operations
shouldSample: () => Math.random() < 0.01, // 1% sampling
// Async metrics collection
recordMetric: (metric) => {
setImmediate(() => metricsQueue.push(metric));
},
// Batch processing
flushMetrics: debounce(() => {
const batch = metricsQueue.splice(0);
sendMetricsBatch(batch);
}, 1000)
};2. Actionable Alerts
# Alert configuration
alerts:
- name: high_token_usage
condition: "tokens_per_minute > 50000"
severity: warning
actions:
- notify: slack
- auto_remedy: enable_token_optimization
- name: task_queue_backup
condition: "queue_depth > 100 AND processing_rate < 10"
severity: critical
actions:
- notify: pagerduty
- auto_remedy: scale_workers
- runbook: "https://docs/runbooks/queue-backup.md"3. Performance Budgets
// Enforce performance budgets
const performanceBudgets = {
task_completion: {
p50: 1000, // ms
p95: 5000,
p99: 10000
},
token_usage: {
per_task: 1000,
per_hour: 50000,
per_day: 1000000
},
cost: {
per_task: 0.10, // USD
per_day: 100
}
};
// Automatic enforcement
class BudgetEnforcer {
async checkBudget(operation: string, metrics: Metrics) {
const budget = performanceBudgets[operation];
const violations = [];
for (const [key, limit] of Object.entries(budget)) {
if (metrics[key] > limit) {
violations.push({
metric: key,
actual: metrics[key],
budget: limit,
exceeded: ((metrics[key] / limit - 1) * 100).toFixed(1)
});
}
}
if (violations.length > 0) {
await this.handleViolations(violations);
}
}
}Integration Examples
1. GitHub Actions Integration
# .github/workflows/performance-monitor.yml
name: Performance Monitoring
on:
workflow_run:
workflows: ["Claude Code Agent"]
types: [completed]
jobs:
analyze-performance:
runs-on: ubuntu-latest
steps:
- name: Collect performance metrics
run: |
claude-code metrics export \
--run-id=${{ github.run_id }} \
--format=json > metrics.json
- name: Check performance regression
run: |
claude-code metrics analyze \
--baseline=main \
--current=metrics.json \
--fail-on-regression
- name: Update performance dashboard
if: success()
run: |
claude-code metrics push \
--dashboard=team-performance \
--data=metrics.json2. Real-Time Monitoring Setup
// Initialize real-time monitoring
async function setupMonitoring() {
// Create metrics collector
const collector = new MetricsCollector({
flushInterval: 1000,
batchSize: 100
});
// Setup performance hooks
const hooks = [
{
name: 'task-performance',
events: ['task.start', 'task.complete'],
handler: async (event) => {
collector.record({
type: 'task_duration',
value: event.duration,
tags: { task: event.taskType }
});
}
},
{
name: 'api-monitoring',
events: ['api.call'],
handler: async (event) => {
collector.record({
type: 'api_latency',
value: event.latency,
tags: { endpoint: event.endpoint }
});
}
}
];
// Start monitoring
await startMonitoring({ collector, hooks });
}Related Resources
- Application Performance Optimization Workflows
- Observability Patterns
- Remote Agent Supervision
- AI Observability Best Practices
- Real-time ML Monitoring
This guide represents state-of-the-art practices in real-time performance monitoring and optimization for Claude Code agents, incorporating the latest developments from 2025.