17 KiB
Architecture Research
Domain: Process monitoring + desktop integration (Claude Code sessions + i3 WM) Researched: 2026-03-23 Confidence: HIGH
System Overview
┌─────────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ TUI (Charm) │ │ i3bar/polybar│ │ Desktop Notifications│ │
│ │ Dashboard │ │ Status Line │ │ (notify-send) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
├─────────┴─────────────────┴──────────────────────┴──────────────┤
│ Core Daemon (vmuxd) │
│ ┌────────────┐ ┌──────────────┐ ┌────────────┐ ┌───────────┐ │
│ │ Session │ │ State │ │ i3 │ │ Event │ │
│ │ Registry │ │ Machine │ │ Bridge │ │ Bus │ │
│ └─────┬──────┘ └──────┬───────┘ └─────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
├────────┴───────────────┴───────────────┴──────────────┴─────────┤
│ Data Sources │
│ ┌───────────────┐ ┌────────────────┐ ┌───────────────────┐ │
│ │ Claude Hooks │ │ ~/.claude/ │ │ i3 IPC Socket │ │
│ │ (HTTP POST) │ │ projects/ │ │ (Unix Domain) │ │
│ └───────────────┘ └────────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Architecture Decision: Hook-First, File-Watch Fallback
La decouverte cle de cette recherche : Claude Code expose un systeme de hooks HTTP depuis debut 2026. Un hook HTTP permet a chaque session Claude Code de POSTer ses evenements (Stop, Notification, PreToolUse, PostToolUse) vers un serveur local. Cela change fondamentalement l'architecture.
Strategie retenue : Push via hooks + Pull via fichiers JSONL
- Push (primary): Chaque session Claude Code envoie ses changements d'etat au daemon via HTTP hooks. Pas de polling, pas de latence.
- Pull (fallback): Pour les sessions existantes au demarrage du daemon, ou si les hooks ne sont pas configures, on lit les fichiers JSONL de
~/.claude/projects/.
Cette approche est superieure au file-watching pur (fsnotify) car :
- Les hooks fournissent des donnees structurees (session_id, cwd, transcript_path, tool_name)
- Pas de parsing JSONL en continu
- Detection immediate du changement d'etat (vs polling toutes les N secondes)
Component Responsibilities
| Component | Responsibility | Implementation |
|---|---|---|
| vmuxd (daemon) | Processus central, orchestre tout | Go binary, lance au login (systemd user unit) |
| HTTP Hook Server | Recoit les evenements Claude Code | net/http sur localhost:7483, endpoints par event type |
| Session Registry | Maintient la liste des sessions actives + leur etat | Map en memoire, pas de DB necessaire |
| State Machine | Determine l'etat d'une session (Working / Needs Input / Waiting / Inactive) | Transitions basees sur les hooks recus |
| i3 Bridge | Associe sessions aux workspaces, switch workspace | go.i3wm.org/i3/v4 (lib officielle) |
| Event Bus | Distribue les changements d'etat aux consumers (TUI, notifications) | Channels Go internes |
| JSONL Scanner | Scan initial + fallback pour sessions sans hooks | Lecture ~/.claude/projects/ au demarrage |
| TUI | Interface principale, dashboard des sessions | Charm Bubbletea ou simple ANSI output |
| Notifier | Alerte quand une session passe a "Needs Input" | notify-send / libnotify |
Recommended Project Structure
vmux/
├── cmd/
│ ├── vmuxd/ # Daemon principal
│ │ └── main.go
│ └── vmux/ # CLI client (query le daemon)
│ └── main.go
├── internal/
│ ├── daemon/ # Lifecycle du daemon, signal handling
│ │ └── daemon.go
│ ├── hooks/ # HTTP server pour recevoir les hooks Claude
│ │ ├── server.go
│ │ └── handlers.go
│ ├── session/ # Session registry + state machine
│ │ ├── registry.go
│ │ ├── state.go
│ │ └── session.go
│ ├── scanner/ # JSONL file scanner (fallback)
│ │ └── scanner.go
│ ├── i3bridge/ # Integration i3 WM
│ │ ├── bridge.go
│ │ └── workspace.go
│ ├── notify/ # Desktop notifications
│ │ └── notify.go
│ └── events/ # Event bus interne
│ └── bus.go
├── go.mod
├── go.sum
└── flake.nix # NixOS packaging
Structure Rationale
- cmd/ split (vmuxd + vmux): Le daemon tourne en permanence, le CLI est ephemere (query/switch). Separation nette.
- internal/: Tout est interne, pas de lib publique a exposer.
- hooks/ separe de scanner/: Deux sources de donnees distinctes avec des lifecycles differents.
- i3bridge/ isole: La dependance a i3 est contenue. Si Pierre migre vers Sway un jour, seul ce package change (Sway utilise le meme protocole IPC).
Architectural Patterns
Pattern 1: Event-Driven State Machine
What: Chaque session Claude Code est modelisee comme une state machine. Les transitions sont declenchees par les hooks HTTP ou le scan JSONL.
States:
┌──────────┐
hook:Stop │ │ hook:PreToolUse
┌──────────>│ Waiting │<──────────┐
│ │ │ │
│ └────┬─────┘ │
│ │ │
│ hook:Notification │
│ (idle_prompt) │
│ │ │
│ ┌────v─────┐ ┌────┴──────┐
│ │ Needs │ │ Working │
│ │ Input │ │ │
│ └──────────┘ └───────────┘
│ ^
│ ┌──────────┐ │
│ │ Inactive │───────────┘
│ │ │ hook:SessionStart
│ └──────────┘ ou process detecte
│ ^
└────────────────┘
timeout / process mort
Trade-offs: Simple et previsible. Le risque est de manquer un evenement (hook timeout, daemon restart). La mitigation est le scan JSONL periodique (toutes les 30s) qui reconcilie l'etat.
Pattern 2: Daemon + CLI Client via Unix Socket
What: vmuxd expose un socket Unix local. vmux (CLI) s'y connecte pour query/commander.
When to use: Pour toutes les interactions utilisateur : lister les sessions, switcher de workspace, afficher le dashboard.
Trade-offs: Plus propre qu'un HTTP API local pour du tooling CLI. Le socket est dans $XDG_RUNTIME_DIR/vmux.sock.
// vmux CLI query
conn, _ := net.Dial("unix", "/run/user/1000/vmux.sock")
json.NewEncoder(conn).Encode(Request{Action: "list"})
json.NewDecoder(conn).Decode(&sessions)
Pattern 3: i3 Workspace Mapping via Window Tree
What: Associer une session Claude Code a un workspace i3 en matchant le PID du processus Claude avec les fenetres i3 (via GetTree()).
When to use: Au demarrage et quand un hook arrive avec un cwd inconnu.
Mapping strategy:
- Le hook fournit
cwd(ex:/home/pierre/Code/front-commerce/worktrees/feature-x) GetTree()retourne l'arbre des fenetres avec leur PID- On matche le
cwddu hook avec lecwddu processus terminal (via/proc/PID/cwd) - La fenetre terminale est dans un workspace i3 => association trouvee
Trade-offs: La resolution via /proc/PID/cwd est Linux-specific (OK pour NixOS). Le matching peut echouer si le terminal a change de repertoire depuis.
Data Flow
Flow 1: Hook-Driven State Update (primary path)
Claude Code session (hook: Stop)
│
│ HTTP POST localhost:7483/hooks/stop
│ Body: { session_id, cwd, last_assistant_message, transcript_path }
│
v
HTTP Hook Server
│
│ Parse, validate, extract session_id + cwd
│
v
Session Registry
│
│ Update session state: Working -> Waiting
│ Resolve i3 workspace (if not cached)
│
v
Event Bus
│
├──> Notifier: "Session X attend dans workspace 3"
├──> TUI: refresh dashboard
└──> i3bar: update status line
Flow 2: Initial Discovery (startup)
vmuxd starts
│
├──> Scan ~/.claude/projects/*/ # Find JSONL files
│ Parse last N lines of each # Determine state
│ Check /proc for matching processes # Alive or dead?
│
├──> i3.GetTree() # Map windows to workspaces
│ i3.GetWorkspaces() # List workspace names
│
└──> Build Session Registry
Emit initial state to Event Bus
Flow 3: Workspace Switch (user action)
User selects session in TUI (or `vmux switch <session>`)
│
v
vmux CLI ──> Unix Socket ──> vmuxd
│
v
Session Registry: lookup workspace for session
│
v
i3 Bridge: i3.RunCommand("workspace 3")
│
v
i3 focuses workspace 3
Flow 4: Notification on state change
Hook: Notification (type: permission_prompt)
│
v
State Machine: session -> Needs Input
│
v
Event Bus
│
v
Notifier: notify-send "Claude Code attend votre input"
"--hint=string:x-canonical-private-synchronous:vmux"
"Session: front-commerce/feature-x (workspace 3)"
Integration Points
External Services
| Service | Integration Pattern | Notes |
|---|---|---|
| Claude Code | HTTP hooks (Push) + JSONL files (Pull) | Hooks configurees dans ~/.claude/settings.json |
| i3 WM | go.i3wm.org/i3/v4 via Unix socket |
Subscribe WindowEvent + WorkspaceEvent pour mises a jour reactives |
| Desktop Notifications | exec notify-send |
Ou libnotify via cgo si besoin de callbacks |
| piaire (futur) | HTTP API ou Unix socket | Integration possible mais hors scope v1 |
Internal Boundaries
| Boundary | Communication | Notes |
|---|---|---|
| vmuxd <-> vmux CLI | Unix socket, JSON protocol | Unidirectionnel : CLI query, daemon repond |
| Hook Server <-> Session Registry | Direct Go function call | Meme processus, pas besoin d'IPC |
| Session Registry <-> Event Bus | Go channels | Fan-out vers multiple consumers |
| Event Bus <-> TUI/Notifier/i3bar | Go channels (subscribe) | Chaque consumer recoit tous les events, filtre localement |
| i3 Bridge <-> i3 WM | Unix socket (i3 IPC protocol) | Gere par la lib officielle, transparent |
Claude Code Hook Configuration
A installer dans ~/.claude/settings.json :
{
"hooks": {
"Stop": [{
"matcher": "",
"hooks": [{
"type": "http",
"url": "http://localhost:7483/hooks/stop"
}]
}],
"Notification": [{
"matcher": "",
"hooks": [{
"type": "http",
"url": "http://localhost:7483/hooks/notification"
}]
}],
"PreToolUse": [{
"matcher": "",
"hooks": [{
"type": "http",
"url": "http://localhost:7483/hooks/pre-tool-use",
"async": true
}]
}],
"PostToolUse": [{
"matcher": "",
"hooks": [{
"type": "http",
"url": "http://localhost:7483/hooks/post-tool-use",
"async": true
}]
}]
}
}
Important: PreToolUse et PostToolUse en async: true pour ne pas ralentir Claude Code. Stop et Notification en synchrone (latence negligeable, et on veut la confirmation de reception).
Anti-Patterns
Anti-Pattern 1: Polling-Only Architecture
What people do: Scanner les fichiers JSONL toutes les secondes pour detecter les changements. Why it's wrong: Consommation CPU inutile, latence de 0-1s, parsing JSON couteux en boucle. Avec 5+ sessions, ca s'accumule. Do this instead: Hooks HTTP comme source primaire, scan JSONL uniquement au demarrage et comme reconciliation periodique (toutes les 30s).
Anti-Pattern 2: Embedded Terminal / Session Injection
What people do: Tenter de lire le PTY de la session Claude Code ou d'injecter des inputs.
Why it's wrong: Fragile, security issues, peut corrompre la session. Le projet le reconnait deja (out of scope).
Do this instead: Observer via hooks + JSONL. Le last_assistant_message du hook Stop fournit l'apercu necessaire.
Anti-Pattern 3: Fat Client Without Daemon
What people do: Un seul binaire CLI qui fait tout (scan, watch, display) a chaque invocation.
Why it's wrong: Pas de persistance d'etat entre invocations. Chaque vmux doit re-scanner, re-parser, re-resoudre les workspaces. Lent et incapable de notifier.
Do this instead: Daemon leger (vmuxd) qui maintient l'etat, CLI leger (vmux) qui query le daemon.
Anti-Pattern 4: Over-Engineering the IPC
What people do: gRPC, protobuf, message queues entre les composants internes. Why it's wrong: vmux est un outil local single-user. La complexite du serialization/deserialization est pure overhead. Do this instead: Go channels internes + Unix socket JSON pour le CLI. Simple, debuggable.
Build Order (Dependencies Between Components)
L'ordre de construction est dicte par les dependances :
Phase 1: Session Registry + JSONL Scanner
(peut fonctionner standalone, valide le concept)
│
Phase 2: HTTP Hook Server
(depend du Registry, remplace le scanner comme source primaire)
│
Phase 3: i3 Bridge + Workspace Mapping
(depend du Registry pour associer sessions <-> workspaces)
│
Phase 4: TUI Dashboard + CLI
(depend de tout ce qui precede pour afficher et commander)
│
Phase 5: Notifications + Polish
(depend de l'Event Bus et du State Machine)
Rationale de l'ordre :
- Le Registry + Scanner valide que la detection fonctionne sans aucune config Claude Code
- Les hooks ajoutent la reactivite mais necessitent que le registry existe
- L'i3 bridge a besoin du registry peuple pour faire le mapping
- Le TUI/CLI est la couche de presentation, besoin de donnees
- Les notifications sont du polish, pas du core
Scaling Considerations
| Scale | Approach |
|---|---|
| 1-5 sessions (usage normal) | Architecture actuelle suffit largement |
| 5-15 sessions | Aucun changement, Go gere facilement |
| 15+ sessions | Improbable pour un seul utilisateur. Si ca arrive, limiter la frequence des hooks async |
Le bottleneck n'est pas la perf mais la complexite d'affichage : au-dela de 8 sessions, le TUI doit proposer du filtrage/grouping.
Sources
- i3 IPC documentation
- go.i3wm.org/i3/v4 - Official Go library
- Claude Code Hooks reference
- claude-sessions-monitor - Existing tool, validates the JSONL + process detection approach
- Real-time dashboard for Claude Code sessions
- Claude Code session storage
- fsnotify - Go filesystem notifications - Considered but hooks are superior
- Claude Code CLI reference
Architecture research for: vmux (Claude Code session cockpit + i3 integration) Researched: 2026-03-23