Skip to content

Architecture

System design and component overview.

High-Level Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                            agentwatch daemon                            │
│                                                                         │
│  ┌────────────────────────────────────────────────────────────────────┐ │
│  │                        Session Discovery                           │ │
│  │                     (find tmux sessions)                           │ │
│  └────────────────────────────────────────────────────────────────────┘ │
│                                  │                                      │
│                    ┌─────────────┴─────────────┐                       │
│                    ▼                           ▼                       │
│  ┌────────────────────────────┐  ┌────────────────────────────┐       │
│  │      Session Monitor       │  │      Session Monitor       │       │
│  │        (session-1)         │  │        (session-2)         │       │
│  │  ┌────────────────────┐   │  │  ┌────────────────────┐   │       │
│  │  │  Capture Engine    │   │  │  │  Capture Engine    │   │       │
│  │  └────────────────────┘   │  │  └────────────────────┘   │       │
│  │  ┌────────────────────┐   │  │  ┌────────────────────┐   │       │
│  │  │  Detection Engine  │   │  │  │  Detection Engine  │   │       │
│  │  └────────────────────┘   │  │  └────────────────────┘   │       │
│  └────────────────────────────┘  └────────────────────────────────┘   │
│                    │                           │                       │
│                    └─────────────┬─────────────┘                       │
│                                  ▼                                      │
│  ┌────────────────────────────────────────────────────────────────────┐ │
│  │                          State Store                               │ │
│  │         (per-session state, parse cache, history)                  │ │
│  └────────────────────────────────────────────────────────────────────┘ │
│                    │                           │                       │
│          ┌────────┴────────┐         ┌────────┴────────┐              │
│          ▼                 ▼         ▼                 ▼              │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐       │
│  │ Hooks Engine │  │  Web Server  │  │   Session Service      │       │
│  │  (webhook,   │  │  (viewer,    │  │   (spawn/coordinate    │       │
│  │   shell)     │  │   REST API)  │  │    Claude Code, Codex) │       │
│  └──────────────┘  └──────────────┘  └────────────────────────┘       │
│                             │                    │                     │
└─────────────────────────────┼────────────────────┼─────────────────────┘
                              │                    │
              ┌───────────────┴───────────────┐    │
              │                               │    │
              ▼                               ▼    ▼
      ┌──────────────┐              ┌─────────────────────┐
      │   Browser    │              │   /api clients      │
      │  (viewer)    │              │ (agentwatch session)│
      └──────────────┘              └─────────────────────┘

Components

Session Discovery

File: agentwatch/service/session_manager.py

Periodically scans for tmux sessions:

  • Lists all tmux sessions
  • Compares with known sessions
  • Creates monitors for new sessions
  • Cleans up removed sessions

Configuration:

service:
  session_discovery_interval: 5  # seconds

Session Monitor

File: agentwatch/service/monitor.py

One per tmux session. Runs async capture loop:

  1. Capture terminal content from tmux
  2. Run detection pipeline
  3. Update state store
  4. Fire hooks on state changes

Configuration:

service:
  capture_interval: 1  # seconds per session

Capture Engine

File: agentwatch/capture.py

Captures terminal content from tmux panes:

def capture_tmux_pane(target: str) -> CaptureResult:
    """Capture terminal content with ANSI codes."""
    pass

Uses tmux capture-pane with various flags for:

  • Content with ANSI escape sequences
  • Pane dimensions
  • Cursor position

Detection Engine

Files: - agentwatch/detectors/registry.py - Orchestration - agentwatch/detectors/patterns.py - Pattern definitions - agentwatch/detectors/claude_code.py - Claude Code detector - agentwatch/detectors/shell.py - Shell detector

Detection pipeline:

Raw Text → ANSI Strip → Program Detection → State Detection → Result

Program Detection:

  1. Run all detectors by priority
  2. Return highest confidence match

State Detection:

  1. Get detector for program
  2. Match patterns with weights
  3. Calculate confidence
  4. Return state + detail

State Store

File: agentwatch/service/state_store.py

In-memory state management:

@dataclass
class SessionState:
    """State for a monitored session."""
    session_id: str
    program: str
    state: str
    detail: str
    changed_at: datetime
    duration: timedelta
    # Parse cache
    parsed_output: Optional[dict]
    parsed_at: Optional[datetime]

Features:

  • Current state per session
  • Previous state for transitions
  • Parse cache for /api clients
  • Duration tracking

Hooks Engine

Files: - agentwatch/service/hooks/base.py - Base hook class - agentwatch/service/hooks/webhook.py - HTTP POST hooks - agentwatch/service/hooks/shell.py - Shell command hooks - agentwatch/service/hooks/executor.py - Hook execution

On state change:

  1. Match hooks against filters
  2. Build payload (status/json/text/png)
  3. Execute matching hooks concurrently
  4. Log results

Web Server

Files: - agentwatch/server/app.py - aiohttp application - agentwatch/server/routes.py - HTTP routes - agentwatch/server/websocket.py - WebSocket handling - agentwatch/server/auth.py - Authentication - agentwatch/server/session.py - Session management

Endpoints:

Type Authentication Purpose
Public None Health check, login page
Cookie Session cookie Browser access
Token Bearer token CLI/API access
Dual Cookie or Bearer /api endpoints (browser or CLI)

WebSocket:

Real-time terminal streaming:

  1. Client connects to /ws/{session_id}
  2. Server sends PNG frames + status
  3. Client sends keyboard input
  4. Multi-user driver/passenger mode

Session Service

Files: - agentwatch/service/session_service.py - Operation layer (SessionService) - agentwatch/sessions/session.py - Session lifecycle (CodingSessionManager) - agentwatch/sessions/spawner.py - Spawns sub-agent processes in tmux - agentwatch/sessions/tmux.py - Async tmux client wrapper

Coding sessions are driven by the agentwatch session ... CLI, which calls the daemon's dual-auth /api endpoints. Verbs map to operations:

  • session new - Create session
  • session respond - Send input
  • session capture - Get content
  • session parse - Get structured output
  • session wait / session wait-any - Wait for state
  • session status, session list, session exit, session terminate, etc.

Sub-agents (Claude Code, Codex) run with their own native tools; agentwatch coordinates them via the session operations above rather than proxying tool calls.

Data Flow

Terminal Capture

tmux pane → capture-pane → raw text → state store → consumers

State Detection

raw text → strip ANSI → program detection → state detection
                              │                    │
                              ▼                    ▼
                        ProgramResult        StateResult
                              │                    │
                              └────────┬───────────┘
                                 SessionState

Hook Execution

state change → match filters → build payload → execute hooks
                                    ┌──────────────┴──────────────┐
                                    ▼                             ▼
                              webhook POST                   shell exec

WebSocket Streaming

capture loop → PNG render → WebSocket → browser
browser → WebSocket → tmux send-keys

Threading Model

The daemon uses asyncio for concurrency:

  • Main loop: Session discovery
  • Per-session tasks: Capture and detection
  • Web server: aiohttp async handlers
  • WebSocket: Concurrent connections

Async Pattern

async def capture_loop(session: str):
    """Capture loop for a session."""
    while True:
        # Capture (may block on tmux)
        result = await asyncio.to_thread(capture_tmux_pane, session)

        # Detection (CPU-bound)
        detection = detect_all(result.text)

        # Update state (fast)
        await state_store.update(session, detection)

        # Sleep until next capture
        await asyncio.sleep(capture_interval)

Security Model

Authentication

Layer Method Scope
OS PAM/dscl User identity
Session HMAC cookie Browser state
Token Bearer token API access

Authorization

  • Only daemon user can authenticate
  • Sessions bound to authenticated user
  • Token stored with filesystem permissions

Transport

  • HTTPS required for all connections
  • Auto-generated localhost certificates
  • Cloudflare certificates for tunnels

Configuration

File: ~/.config/agentwatch/config.yaml

Loaded at startup, cached in memory. Changes require restart.

Schema: agentwatch/service/config_loader.py

Persistence

Data Location Persistence
Config ~/.config/agentwatch/ User-managed
Tokens ~/.config/agentwatch/tokens/ Generated
State Memory Lost on restart
Logs ~/.cache/agentwatch/ Rotated
Captures ~/.cache/agentwatch/captures/ Auto-cleaned