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:
Session Monitor¶
File: agentwatch/service/monitor.py
One per tmux session. Runs async capture loop:
- Capture terminal content from tmux
- Run detection pipeline
- Update state store
- Fire hooks on state changes
Configuration:
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:
Program Detection:
- Run all detectors by priority
- Return highest confidence match
State Detection:
- Get detector for program
- Match patterns with weights
- Calculate confidence
- 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
/apiclients - 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:
- Match hooks against filters
- Build payload (status/json/text/png)
- Execute matching hooks concurrently
- 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:
- Client connects to
/ws/{session_id} - Server sends PNG frames + status
- Client sends keyboard input
- 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 sessionsession respond- Send inputsession capture- Get contentsession parse- Get structured outputsession wait/session wait-any- Wait for statesession 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¶
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¶
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 |