trail-boss/README.md
jedarden 480de1e4ca docs(tb-5wj): document tmux detector production enablement
Add comprehensive documentation for the tmux detector fallback:
- New 'Detection methods' section explaining hook-based vs tmux detector
- Production enablement instructions (3 methods)
- Configuration parameters table
- Operational considerations
- Updated Status section to mention tmux detector
- Updated repository layout and components table

The tmux detector is VIABLE as a harness-agnostic fallback for
coding harnesses without hook support. Full viability analysis in
docs/notes/decisions.md.

Bead-Id: tb-5wj
2026-07-02 19:35:48 -04:00

331 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Trail Boss
**Single-pane attention router for interactive AI coding agents.** When you run multiple
Claude Code (or other AI coding CLI) sessions in parallel tmux panes, Trail Boss surfaces the
ones waiting on you — a FIFO queue of stuck sessions — and jumps you directly to them with a
keystroke.
---
## The problem
Running many agent sessions in tmux means manually cycling windows to find the one that needs
you. Each stalled session — hitting a permission gate, asking a question, or finishing its
turn — sits blocked until you notice it. With five or ten parallel sessions, most of your
attention goes to *finding* the stuck one, not *answering* it.
Trail Boss eliminates the cycling. Every session that stalls raises its hand automatically.
You see one prioritized queue, respond in the live pane, and move on.
**Human on the loop, not in it.** The happy path never touches you. Only stalled work routes
to you; you process the exception and it goes back on the wire.
---
## How it works
Trail Boss integrates with Claude Code via hooks and tmux pane tracking:
1. **Hook emitter** — Claude Code fires `Stop` and `PermissionRequest` hooks whenever a session
blocks. The emitter script (`trailboss-emit.sh`) POSTs each event to the daemon with the
current `$TMUX_PANE` ID.
2. **Daemon** (Bun + SQLite, `localhost:4000`) — Maintains a session registry and FIFO queue.
Sessions enter the queue on `Stop`/`PermissionRequest` and leave when `UserPromptSubmit`
fires (you typed something in the pane) or when the reconcile loop detects the transcript
advanced on its own.
3. **TUI** (Go + Bubble Tea) — Split-panel view: queue list on the left, session detail on the
right. Auto-refreshes every 3 seconds. Press `Enter` to jump to the head pane, `s` to skip
it (30 s cooldown before it re-surfaces).
4. **Live preview** — A `tmux capture-pane` preview of the selected session updates in a pane
below the TUI as you navigate the queue.
```
┌─ Trail Boss ─────────────────── 3 stuck ──┐ ┌─ Detail ───────────────────────────────┐
│ ▶ alpha permission 2m14s │ │ Wants to run: │
│ bravo stopped 0m48s │ │ terraform apply -target=module.lb │
│ delta stopped 0m11s │ │ │
│ │ │ CWD: ~/infra/modules │
│ [enter] jump [s] skip [r] refresh [?] │ │ stuck 2m14s ago │
└────────────────────────────────────────────┘ └────────────────────────────────────────┘
```
---
## Architecture
```
Claude Code session (tmux pane)
│ Stop / PermissionRequest hook
.claude/trailboss-emit.sh ─── POST /event ──► daemon (Bun + SQLite, :4000)
│ reconcile loop (5 s)
│ GET /queue /next /skip
TUI (Go + Bubble Tea)
bin/trailboss-tui
│ tmux switch-client / select-pane
Target pane (live Claude Code session)
```
---
## Detection methods
Trail Boss supports two detection methods:
### 1. Hook-based detection (primary)
Claude Code fires `Stop` and `PermissionRequest` hooks whenever a session blocks. The emitter script (`trailboss-emit.sh`) POSTs each event to the daemon with the current `$TMUX_PANE` ID. This provides:
- **Immediate detection**: Event-driven, no polling delay
- **Full fidelity**: Includes session_id, transcript path, cwd, reason (permission vs stopped)
- **Zero configuration**: Automatic for all Claude Code sessions with hooks enabled
### 2. Tmux detector (fallback)
For coding harnesses without hook support, Trail Boss includes a **tmux-level detector** (`daemon/tmux-detector.ts`) that polls opted-in panes for stuck state. This provides:
- **Harness-agnostic**: Works with any coding tool that runs in a tmux pane
- **Low false positives**: 30-second quiet threshold + prompt pattern matching
- **Opt-in required**: Panes must include `@tb-` prefix in their title
**Enabling the tmux detector:**
```bash
# Method 1: Manual opt-in per pane
tmux rename-window '@tb-my-work'
tmux select-pane -T '@tb-task-name'
# Method 2: Run detector standalone
cd /home/coding/trail-boss
bun run daemon/tmux-detector.ts
# Method 3: Integrate with trailboss-start (automatic startup)
# Edit bin/trailboss-start to add detector startup after daemon
```
**Configuration:**
| Parameter | Environment Variable | Default |
|-----------|---------------------|---------|
| Poll interval | `TRAILBOSS_POLL_INTERVAL_MS` | 2000ms (2s) |
| Quiet threshold | `TRAILBOSS_QUIET_THRESHOLD_MS` | 30000ms (30s) |
| Opt-in prefix | `TRAILBOSS_OPT_IN_PREFIX` | `@tb-` |
| Daemon URL | `TRAILBOSS_DAEMON_URL` | `http://127.0.0.1:4000/event/normalized` |
| Tmux socket | `TMUX` | `tmux` |
**Operational considerations:**
- **No transcript path**: Synthetic sessions (`tmux-%446-timestamp`) have no `transcript.jsonl` for reconcile
- **No permission vs stopped distinction**: Always emits `reason: "stopped"`
- **Opt-in compliance**: Users must remember the `@tb-` prefix
- **Detection latency**: ~30s quiet threshold vs immediate hook-based detection
- **Multi-server discovery**: Detector polls all tmux servers on the system (correct behavior for production use)
- **Graceful shutdown**: SIGINT/SIGTERM emit `ended` events for all tracked panes
For Claude Code sessions, hook-based detection is primary (full fidelity, zero latency). The tmux detector enables Trail Boss to work with any future coding harness that lacks hooks. See `docs/notes/decisions.md` for full viability analysis and test results.
---
## Components
| Component | Language | Purpose |
|-----------|----------|---------|
| `daemon/` | TypeScript (Bun) | HTTP event ingestion, SQLite state, queue API |
| `daemon/tmux-detector.ts` | TypeScript (Bun) | Harness-agnostic fallback detector via pane polling |
| `tui/` | Go (Bubble Tea) | Split-panel TUI dashboard |
| `bin/trailboss` | Shell | CLI: `jump-next`, `skip`, `popup`, `return` |
| `bin/trailboss-tui` | Go binary | TUI binary (build from tui/ with Go) |
| `bin/trailboss-start` | Shell | Launches daemon + TUI in a `trail-boss` tmux session |
| `bin/trailboss-preview` | Shell | Live pane capture loop for the split-view preview |
| `bin/trailboss-status` | Shell | tmux status-bar segment showing stuck count |
| `.claude/trailboss-emit.sh` | Shell | Hook emitter wired into Claude Code settings |
---
## Requirements
- [Bun](https://bun.sh) — daemon runtime
- tmux
- Claude Code with hooks enabled (for primary detection)
- Go 1.24+ — for building the TUI
---
## Setup
### 1. Install daemon dependencies
```bash
cd /path/to/trail-boss
bun install
```
### 2. Build the TUI
```bash
cd /path/to/trail-boss/tui
go build -o ../bin/trailboss-tui .
```
The `bin/trailboss-start` script will automatically build the TUI if the binary is missing.
### 3. Wire Claude Code hooks
Add to your project's `.claude/settings.json`:
```json
{
"hooks": {
"Stop": [{
"type": "command",
"command": "/path/to/trail-boss/.claude/trailboss-emit.sh"
}],
"PermissionRequest": [{
"type": "command",
"command": "/path/to/trail-boss/.claude/trailboss-emit.sh"
}],
"UserPromptSubmit": [{
"type": "command",
"command": "/path/to/trail-boss/.claude/trailboss-emit.sh"
}],
"SessionStart": [{
"type": "command",
"command": "/path/to/trail-boss/.claude/trailboss-emit.sh"
}],
"SessionEnd": [{
"type": "command",
"command": "/path/to/trail-boss/.claude/trailboss-emit.sh"
}]
}
}
```
### 3. Start Trail Boss
```bash
bin/trailboss-start
```
This creates (or reuses) a `trail-boss` tmux session with two windows: `daemon` and
`dashboard`. The dashboard shows the TUI on top (60%) and a live pane preview below (40%).
### 4. Navigate
From inside any tmux session:
| Keybinding | Action |
|------------|--------|
| `prefix+Tab` | Jump to next stuck session |
| `prefix+S` | Skip current head, jump to next |
| `prefix+g` | Open queue picker popup |
| `prefix+B` | Return to Trail Boss dashboard |
Or source the provided `tmux.conf` to get these bindings:
```bash
tmux source-file /path/to/trail-boss/tmux.conf
```
### 5. Add to tmux status bar (optional)
```conf
set -g status-right "#(bin/trailboss-status) | %H:%M"
```
The segment shows `⚠ N` when N sessions are stuck, blank otherwise.
---
## TUI keys
| Key | Action |
|-----|--------|
| `j` / `k` | Move cursor down / up |
| `gg` / `G` | Jump to top / bottom |
| `Ctrl+d` / `Ctrl+u` | Half-page down / up |
| `Enter` | Jump to selected pane |
| `s` | Skip selected session (30 s cooldown) |
| `l` / `Tab` | Focus detail pane |
| `J` / `K` | Scroll detail pane |
| `1``9` | Jump directly to queue position N |
| `r` | Force refresh |
| `?` | Toggle help overlay |
| `q` | Quit |
---
## Repository layout
```
trail-boss/
├── daemon/
│ ├── index.ts # HTTP server + event ingest endpoint
│ ├── claude-adapter.ts # Hook event normalization
│ ├── db.ts # SQLite state layer
│ ├── reconcile.ts # Transcript-advance reconcile loop
│ ├── schema.sql # Database schema
│ ├── types.ts # Shared types
│ └── tmux-detector.ts # Harness-agnostic fallback detector
├── tui/
│ ├── main.go # Entry point (AltScreen + mouse)
│ ├── model.go # Bubble Tea model + split layout
│ ├── client.go # Daemon HTTP client + tmux navigation
│ └── theme.go # Dracula palette + dumb-terminal fallback
├── bin/
│ ├── trailboss # CLI: jump-next, skip, popup, return
│ ├── trailboss-tui # TUI binary (build from tui/ with Go)
│ ├── trailboss-start # Session launcher
│ ├── trailboss-bootstrap # Initial setup script
│ ├── trailboss-popup # Queue picker popup (box-drawing UI)
│ ├── trailboss-preview # Live pane capture loop
│ ├── trailboss-status # tmux status segment
│ └── trailboss-watch # (deprecated, superseded by TUI)
├── docs/
│ └── plan/plan.md # Complete design spec
├── .claude/
│ └── trailboss-emit.sh # Hook emitter
└── tmux.conf # Keybinding configuration
```
---
## Status
Phases 19 complete. All 7 acceptance scenarios pass end-to-end. Tmux detector fallback implemented and tested.
**What works:**
- Hook detection: `Stop` and `PermissionRequest` enqueue; `UserPromptSubmit` dequeues
- Tmux detector fallback: harness-agnostic stuck detection via pane polling (opt-in via `@tb-` prefix)
- FIFO ordering with skip cooldown (30 s before a skipped session re-surfaces)
- Reconcile loop: sessions dequeue automatically when transcripts advance (even if
`UserPromptSubmit` was missed because the daemon was temporarily down)
- Self-healing registry: a session reappearing in a different pane re-registers
- No forced focus-steal: resolving a session does not auto-switch you to another
- TUI resolves pane IDs to human-readable tmux session names (alpha, bravo, etc.)
**Upcoming:** refinements from daily use.
---
## Design principles
**Stuck is stuck.** Whether a session hit a permission gate or just finished its turn —
both mean "blocked until you act." The queue treats them identically; `reason` is
display-only metadata, never a routing input.
**Navigator, not relay.** Trail Boss routes your attention to the live session. You interact
with the real CLI directly. It never injects replies or relays input.
**Not a fleet spawner.** Trail Boss does not launch, kill, or cost-optimize agents. It assumes
sessions already exist and surfaces the stuck ones.
---
## License
MIT