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:

  1. 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.
  2. 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);
      }
    }
  3. 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.json

2. 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 });
}

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.