docs: complete project research

This commit is contained in:
Pierre Martin
2026-03-23 11:25:05 +01:00
parent ec1e6134eb
commit 3030b88057
5 changed files with 1172 additions and 0 deletions

View File

@@ -0,0 +1,385 @@
# 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 <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` :
```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*