# 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 : 1. Les hooks fournissent des donnees structurees (session_id, cwd, transcript_path, tool_name) 2. Pas de parsing JSONL en continu 3. 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`. ```go // 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:** 1. Le hook fournit `cwd` (ex: `/home/pierre/Code/front-commerce/worktrees/feature-x`) 2. `GetTree()` retourne l'arbre des fenetres avec leur PID 3. On matche le `cwd` du hook avec le `cwd` du processus terminal (via `/proc/PID/cwd`) 4. 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 `) │ 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` : ```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 :** 1. Le Registry + Scanner valide que la detection fonctionne sans aucune config Claude Code 2. Les hooks ajoutent la reactivite mais necessitent que le registry existe 3. L'i3 bridge a besoin du registry peuple pour faire le mapping 4. Le TUI/CLI est la couche de presentation, besoin de donnees 5. 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](https://i3wm.org/docs/ipc.html) - [go.i3wm.org/i3/v4 - Official Go library](https://pkg.go.dev/go.i3wm.org/i3/v4) - [Claude Code Hooks reference](https://code.claude.com/docs/en/hooks) - [claude-sessions-monitor](https://github.com/yepzdk/claude-sessions-monitor) - Existing tool, validates the JSONL + process detection approach - [Real-time dashboard for Claude Code sessions](https://www.ksred.com/managing-multiple-claude-code-sessions-building-a-real-time-dashboard/) - [Claude Code session storage](https://milvus.io/blog/why-claude-code-feels-so-stable-a-developers-deep-dive-into-its-local-storage-design.md) - [fsnotify - Go filesystem notifications](https://github.com/fsnotify/fsnotify) - Considered but hooks are superior - [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference) --- *Architecture research for: vmux (Claude Code session cockpit + i3 integration)* *Researched: 2026-03-23*