Event System and Logging

July 1, 2025 · View on GitHub

Overview

The SystemPrompt Coding Agent implements a comprehensive event system and logging framework that captures, processes, and persists all system activities. This provides visibility into AI agent operations, debugging capabilities, and audit trails for all tasks.

Architecture

Event Sources                Event Processing              Storage
┌────────────┐              ┌─────────────┐              ┌──────────┐
│ Claude CLI │ ─────────>   │ Event Parser│ ─────────>   │ Task     │
│ Process    │              │             │              │ Store    │
└────────────┘              └─────────────┘              └──────────┘

┌────────────┐              ┌─────▼───────┐              ┌──────────┐
│ MCP Server │ ─────────>   │ Event       │ ─────────>   │ Log      │
│ Operations │              │ Emitters    │              │ Files    │
└────────────┘              └─────────────┘              └──────────┘

┌────────────┐              ┌─────▼───────┐              ┌──────────┐
│ Tool       │ ─────────>   │ Logger      │ ─────────>   │ Console  │
│ Executions │              │ Service     │              │ Output   │
└────────────┘              └─────────────┘              └──────────┘

Claude Event System

Event Types

The system defines strongly-typed events for all Claude operations:

// Process lifecycle events
interface ClaudeProcessStartEvent {
  type: 'process:start';
  pid: number;
  command: string;
  workingDirectory: string;
  environment?: Record<string, string>;
}

interface ClaudeProcessEndEvent {
  type: 'process:end';
  exitCode: number | null;
  signal: string | null;
  duration: number;
}

// Tool usage events
interface ClaudeToolUseStartEvent {
  type: 'tool:start';
  toolName: string;
  toolId: string;
  parameters: Record<string, any>;
}

interface ClaudeToolUseEndEvent {
  type: 'tool:end';
  toolName: string;
  toolId: string;
  duration: number;
  success: boolean;
  result?: any;
  error?: string;
}

// Communication events
interface ClaudeMessageEvent {
  type: 'message';
  role: 'assistant' | 'user' | 'system';
  content: string;
  metadata?: {
    tokens?: number;
    model?: string;
  };
}

// Raw stream events
interface ClaudeStreamEvent {
  type: 'stream';
  data: string;
  streamType: 'stdout' | 'stderr';
}

// Error events
interface ClaudeErrorEvent {
  type: 'error';
  error: string;
  code?: string;
  stack?: string;
}

Event Flow

  1. Event Generation

    Claude Process → stdout/stderr → Daemon → Event Parser
    
  2. Event Parsing

    const parser = new ClaudeEventParser(sessionId, taskId);
    const events = parser.parseLine(outputLine, 'stdout');
    
  3. Event Emission

    claudeService.on('claude:event', (event: ClaudeEvent) => {
      // Handle event
    });
    
  4. Event Storage

    await taskStore.addLog(taskId, {
      timestamp: event.timestamp,
      level: 'info',
      type: 'tool',
      message: `${event.toolName} started`,
      metadata: event
    });
    

Event Parser

The event parser (event-parser.ts) uses pattern matching to extract structured events:

// Tool detection patterns
toolStart: /^(?:I'll|Let me|I will) (?:use|run|execute) (?:the )?(\w+)/i
toolCall: /^(?:Running|Executing|Calling) (?:the)? (\w+)/i

// File operation patterns
readFile: /^(?:Reading|Opening) file:\s*(.+)$/i
writeFile: /^(?:Writing|Creating) (?:to )?file:\s*(.+)$/i

// Command patterns
bashCommand: /^\$ (.+)$/

// Error patterns
error: /^(?:Error|Failed|Exception):\s*(.+)$/i

Logging System

Logger Service

The core logger (utils/logger.ts) provides multiple log levels:

const logger = {
  debug: (...args: any[]) => {
    if (process.env.DEBUG === 'true') {
      console.debug('[DEBUG]', ...args);
    }
  },
  info: (...args: any[]) => {
    console.info('[INFO]', ...args);
  },
  warn: (...args: any[]) => {
    console.warn('[WARN]', ...args);
  },
  error: (...args: any[]) => {
    console.error('[ERROR]', ...args);
  }
};

Task Logger

The task logger (task-logger.ts) handles task-specific logging:

class TaskLogger {
  // Log session creation
  async logSessionCreated(
    taskId: string,
    sessionId: string,
    type: string,
    projectPath: string
  ): Promise<void>

  // Log commands sent
  async logCommandSent(
    taskId: string,
    command: string
  ): Promise<void>

  // Log responses received
  async logResponseReceived(
    taskId: string,
    response: string
  ): Promise<void>

  // Log errors
  async logError(
    taskId: string,
    error: Error,
    context?: string
  ): Promise<void>
}

Log Entry Structure

interface TaskLogEntry {
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error';
  type: 'system' | 'agent' | 'tool' | 'user';
  prefix?: string;
  message: string;
  metadata?: {
    sessionId?: string;
    toolName?: string;
    duration?: number;
    error?: any;
    [key: string]: any;
  };
}

Log Types and Prefixes

System Logs

  • PROCESS_START: Claude process initiated
  • PROCESS_END: Claude process completed
  • SESSION_CREATE: New session created
  • SESSION_END: Session terminated
  • TASK_UPDATE: Task status changed

Agent Logs

  • AGENT_MESSAGE: AI assistant communication
  • AGENT_THINKING: Internal reasoning
  • AGENT_COMMAND: Commands sent to agent

Tool Logs

  • TOOL_START: Tool execution began
  • TOOL_END: Tool execution completed
  • TOOL_OUTPUT: Tool output data
  • TOOL_ERROR: Tool execution error

User Logs

  • USER_REQUEST: User input/request
  • USER_FEEDBACK: User feedback/response

Storage and Persistence

Task Store Integration

Logs are persisted through the task store:

// Add log entry
await taskStore.addLog(taskId, logEntry, mcpSessionId);

// Retrieve logs
const task = await taskStore.getTask(taskId);
const logs = task.logs;

// Filter logs
const errorLogs = logs.filter(log => log.level === 'error');
const toolLogs = logs.filter(log => log.type === 'tool');

Log Files

Additional log destinations:

  1. Daemon Logs: /daemon/logs/host-bridge.log
  2. Claude Hooks: /daemon/logs/claude-hooks.jsonl
  3. Docker Logs: docker-compose logs mcp-server

Real-time Event Streaming

Server-Sent Events (SSE)

For real-time updates to clients:

// Emit notification
await notificationManager.notify({
  type: 'task:log',
  taskId: task.id,
  data: logEntry
});

// Client receives via SSE
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'task:log') {
    updateTaskLogs(data.taskId, data.data);
  }
});

Event Subscriptions

// Subscribe to specific events
claudeService.on('tool:start', (event) => {
  console.log(`Tool ${event.toolName} started`);
});

// Subscribe to all Claude events
claudeService.on('claude:event', (event) => {
  switch(event.type) {
    case 'process:start':
      handleProcessStart(event);
      break;
    case 'tool:end':
      handleToolEnd(event);
      break;
  }
});

Debugging and Monitoring

Enable Debug Logging

# Enable debug output
export DEBUG=true
export LOG_LEVEL=debug

# Start server with debug logging
npm start

Log Analysis

# View recent errors
grep "ERROR" daemon/logs/host-bridge.log | tail -20

# Monitor tool usage
grep "TOOL_" daemon/logs/host-bridge.log | grep -v "TOOL_OUTPUT"

# Track specific session
grep "session_123" daemon/logs/host-bridge.log

Performance Monitoring

// Log with timing
const startTime = Date.now();
await executeOperation();
const duration = Date.now() - startTime;

logger.info('Operation completed', {
  operation: 'tool_execution',
  duration,
  toolName: 'bash'
});

Best Practices

Logging Guidelines

  1. Use Appropriate Levels

    • debug: Detailed internal state
    • info: Normal operations
    • warn: Recoverable issues
    • error: Failures requiring attention
  2. Include Context

    logger.error('Tool execution failed', {
      toolName,
      taskId,
      sessionId,
      error: error.message,
      stack: error.stack
    });
    
  3. Avoid Sensitive Data

    • Don't log passwords or API keys
    • Sanitize user data
    • Use placeholders for secrets

Event Design

  1. Consistent Structure

    • Always include timestamp
    • Use standard type names
    • Include relevant IDs
  2. Granular Events

    • Separate start/end events
    • Include duration calculations
    • Capture success/failure states
  3. Metadata Rich

    • Add contextual information
    • Include environment details
    • Track relationships (task/session)

Integration Examples

Custom Event Handler

class CustomEventHandler {
  constructor(private taskStore: TaskStore) {
    const claudeService = ClaudeCodeService.getInstance();
    
    claudeService.on('claude:event', async (event) => {
      await this.handleEvent(event);
    });
  }
  
  async handleEvent(event: ClaudeEvent) {
    // Custom processing
    if (isToolUseEndEvent(event) && !event.success) {
      await this.notifyToolFailure(event);
    }
    
    // Store in custom format
    await this.storeEvent(event);
  }
}

Log Aggregation

// Aggregate logs by type
const aggregateLogs = (logs: TaskLogEntry[]) => {
  return logs.reduce((acc, log) => {
    acc[log.type] = (acc[log.type] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);
};

// Generate report
const stats = aggregateLogs(task.logs);
console.log('Log statistics:', stats);
// Output: { system: 10, agent: 25, tool: 45, user: 5 }

This event system and logging framework provides comprehensive visibility into all operations, enabling effective debugging, monitoring, and auditing of AI agent activities.