System Design
Hexagonal architecture with clean ports and adapters.
Overview
Irrlicht follows hexagonal (ports-and-adapters) architecture. Every external dependency is behind an interface. Every component is testable in isolation.
┌─────────────────────────────────────────┐
│ HTTP/WebSocket Interface │
│ GET /api/v1/sessions, /stream, /state │
└────────────────────┬────────────────────┘
│
┌────────────────────┴────────────────────┐
│ Application Layer (Services) │
│ SessionDetector · PushService │
│ OrchestratorMonitor │
└────────────────────┬────────────────────┘
│
┌────────────────────┴────────────────────┐
│ Domain Model │
│ SessionState · SessionMetrics │
│ agent.Event · orchestrator.State │
└────────────────────┬────────────────────┘
│
┌────────────────────┴────────────────────┐
│ Inbound Adapters │ Outbound Adapters│
│ Claude Code │ Filesystem │
│ Codex │ WebSocket Hub │
│ Process Scanner │ kqueue Process │
│ Gas Town │ mDNS · Git · Log │
└──────────────────────┴───────────────────┘
Project Structure
core/
├── cmd/
│ ├── irrlichd/ # Daemon entry point
│ └── irrlicht-ls/ # CLI listing tool
├── domain/ # Pure domain types
│ ├── session/ # SessionState, SessionMetrics
│ ├── agent/ # Agent event types
│ ├── orchestrator/ # Orchestrator state
│ └── config/ # Configuration
├── ports/
│ ├── inbound/ # AgentWatcher, OrchestratorWatcher
│ └── outbound/ # Repository, Logger, ProcessWatcher, etc.
├── adapters/
│ ├── inbound/ # Event sources
│ └── outbound/ # System integrations
├── application/services/ # Orchestration logic
└── pkg/ # Shared utilities (tailer, capacity)
Port Interfaces
Inbound Ports
type AgentWatcher interface {
Watch(ctx context.Context) error
Subscribe() <-chan agent.Event
Unsubscribe(ch <-chan agent.Event)
}
Outbound Ports
Key interfaces that define outbound boundaries:
SessionRepository— Persist and retrieve session stateProcessWatcher— Monitor process lifecycle (kqueue)MetricsCollector— Compute session metrics from transcriptsGitResolver— Resolve project names from git repositoriesPushBroadcaster— Fan-out state changes to connected clientsLogger— Structured logging abstraction
Component Wiring
The main.go entry point wires all components together following dependency injection:
- Logger — structured JSON output
- Config — loaded from environment variables
- SessionRepository — filesystem adapter for state persistence
- GitResolver — resolves project names via git commands
- MetricsCollector — transcript tailer for session metrics
- ProcessWatcher — kqueue adapter for process exit detection
- AgentWatchers — Claude Code + Codex + Process Lifecycle
- SessionDetector — thin coordinator that delegates to:
- StateClassifier — pure functions for working/waiting/ready transitions
- MetadataEnricher — git metadata, CWD, model, and metrics resolution
- PIDManager — PID discovery with retry/backoff, process exit handling, liveness sweeps, subagent cleanup
- PushService — WebSocket fan-out to connected clients
- HTTP Server — API endpoints + embedded web UI
Data Flow
- Inbound adapter emits
agent.Event - SessionDetector receives event, creates or updates session
- MetricsCollector computes metrics from transcript
- State machine determines working / waiting / ready
- PushService broadcasts change via WebSocket
- SessionRepository persists to filesystem
Performance
| Metric | Value |
|---|---|
| Memory | <5 MB typical footprint |
| Detection latency | ~50–200 ms via FSEvents |
| Process exit | ~1 ms via kqueue |
| Concurrency | Tested up to 8 simultaneous sessions |
| Disk (state files) | <100 KB |
| Disk (logs) | <50 MB |
Safety Guarantees
- Zero configuration — works out of the box with no setup required
- Idempotent operations — safe to restart at any time
- Non-destructive — never corrupts existing configs or data
- Atomic writes — temp file + rename pattern prevents partial writes
- Kill switch — set
IRRLICHT_DISABLED=1to disable entirely