Data Flow
Hook to JSON Pipeline
Section titled “Hook to JSON Pipeline”When Claude Code fires an event, the hook system writes status to the filesystem:
Claude Code Event │ ▼Hook Script (notify.sh) │ ├── Reads stdin JSON for event metadata │ (teammate_name, team_name, session_id, hook_event_name) │ ├── Determines target file: ~/.claude-sessions/<tmux_session>.json │ ├── Routes based on teammate presence: │ ├── No teammate → Update main session status │ └── Has teammate → Update team.agents[] entry │ ├── Preserves external agents field (agents.opencode, etc.) │ └── Writes JSON atomicallyExternal Agent Pipeline
Section titled “External Agent Pipeline”OpenCode (and other agents) write to the same status file via plugins:
OpenCode Lifecycle Event │ ▼navi.js Plugin │ ├── Resolves tmux session name (cached 30s) │ ├── Read-modify-write: ~/.claude-sessions/<session>.json │ ├── Preserves all root-level fields (Claude Code's domain) │ └── Updates agents.opencode entry │ ├── Duplicate suppression (1s window for same status) │ └── Atomic write (temp file + rename)Status File Format
Section titled “Status File Format”{ "tmux_session": "my-session", "status": "working", "message": "Implementing login feature", "cwd": "/home/user/projects/app", "timestamp": 1738972800, "metrics": { "started": 1738970000, "tools": {"Read": 5, "Write": 3, "Bash": 2}, "recent_tools": ["Read", "Write", "Bash"] }, "team": { "name": "my-project", "agents": [ {"name": "researcher", "status": "working", "timestamp": 1738972800} ] }, "agents": { "opencode": { "status": "idle", "timestamp": 1738972780 } }}TUI Polling Pipeline
Section titled “TUI Polling Pipeline”The TUI runs multiple concurrent polling loops:
Session Polling (500ms)
Section titled “Session Polling (500ms)”Timer tick │ ▼Read ~/.claude-sessions/*.json │ ▼Parse each JSON file → []session.Info │ ▼Cross-reference with `tmux list-sessions` │ ├── Remove entries with no matching tmux session │ ▼Compute CompositeStatus() per session │ ├── Consider Claude Code + all external agents ├── Return highest-priority status and source │ ▼SortSessions() │ ├── Priority statuses first (waiting, permission from any agent) ├── Active sessions next (working from any agent) └── Then by timestamp (most recent first) │ ▼Detect status changes │ ├── Compare session states vs lastSessionStates ├── Compare agent states vs lastAgentStates └── Fire audio notifications on transitions │ ▼Send sessionsMsg to Update │ ▼Re-render viewGit Info Polling
Section titled “Git Info Polling”Session list updated / Timer tick │ ▼For each session with a CWD: │ ├── Check cache (5s TTL, 10s max age) │ ├── Cache hit → skip │ └── Cache miss ▼ │ ▼Run git commands in CWD │ ├── git branch --show-current ├── git status --porcelain ├── git rev-list --count @{upstream}..HEAD └── git log -1 --format="%h %s" │ ▼Send gitInfoMsg to UpdatePR Detail Fetching (On-Demand)
Section titled “PR Detail Fetching (On-Demand)”User opens git detail view (G key) │ ▼Check PRDetail cache (60s TTL) │ ├── Cache hit → display immediately └── Cache miss ▼ │ ▼gh pr view --json <all fields> │ ├── Local: uses working directory context └── Remote: uses -R owner/repo flag │ ▼Parse PR metadata → PRDetail │ ├── Checks: aggregate passed/failed/pending ├── Reviews: per-reviewer decisions └── Merge status, labels, change stats │ ▼Display in git detail view │ ├── If checks pending → start auto-refresh (30s) └── Auto-refresh stops when checks terminalToken Metrics
Section titled “Token Metrics”Session CWD │ ▼Convert to Claude project path /home/user/project → -home-user-project │ ▼Find most recent .jsonl in ~/.claude/projects/<path>/ │ ▼Parse JSONL for assistant messages with usage data │ ▼Aggregate: input_tokens + cache_read + cache_creation → total input output_tokens → total outputTask Polling (30s)
Section titled “Task Polling (30s)”Timer tick / Manual refresh │ ▼Discover .navi.yaml files │ ▼For each project config: │ ├── Check cache (configurable TTL) │ ├── Cache hit → skip │ └── Cache miss ▼ │ ▼Execute provider scripts (4 concurrent workers) │ ├── Bounded concurrency via semaphore channel ├── Per-project error isolation └── Results collected deterministically │ ▼Parse JSON output → ProviderResult │ ▼Send to Update → render task panelBackground Attach Monitor
Section titled “Background Attach Monitor”User presses Enter to attach │ ▼startAttachMonitor() │ ├── Pass lastSessionStates and lastAgentStates to monitor ├── Create context with cancel └── Launch polling goroutine │ ▼tea.ExecProcess hands terminal to tmux │Monitor goroutine (500ms tick) │ ├── Read ~/.claude-sessions/*.json ├── Compare session states against known states ├── Compare agent states against known states └── Fire audio.Notifier on transitions │ ▼User detaches (Ctrl-B D) │ ▼stopAttachMonitor() │ ├── Cancel context → goroutine exits ├── Recover final states via monitor.States() and monitor.AgentStates() └── Assign back to lastSessionStates and lastAgentStates │ ▼TUI resumes polling — no duplicate notificationsnavi status CLI
Section titled “navi status CLI”tmux status bar runs `navi status` (every 5s) │ ▼Read ~/.claude-sessions/*.json via session.ReadStatusFiles() │ ▼Count sessions by status │ ├── Default: show waiting + permission only └── --verbose: show all non-zero counts │ ▼Print summary and exitPM Engine Pipeline
Section titled “PM Engine Pipeline”PM tick (5min) / View entry / Manual refresh (r key) │ ▼DiscoverProjects() │ ├── Group sessions by expanded CWD ├── Deduplicate via path expansion └── Derive project name from directory basename │ ▼CaptureSnapshot() per project │ ├── Git state: rev-parse HEAD, branch, ahead, dirty ├── Task state: aggregate provider results by status ├── Session state: composite status from grouped sessions ├── Current PBI: multi-strategy resolver │ ├── 1. Provider hint (current_pbi_id) │ ├── 2. Session metadata (current_pbi field) │ ├── 3. Branch pattern (regex matching) │ ├── 4. Status heuristic (InProgress > Agreed > ...) │ └── 5. First group fallback └── Last activity: max timestamp across sessions │ ▼DiffSnapshots(previous, current) │ ├── task_completed: Done count increased ├── task_started: InProgress count increased ├── commit: HEAD SHA changed (runs git log old..new) ├── session_status_change: composite status changed ├── pbi_completed: all tasks done ├── branch_created: branch name changed └── pr_created: PR number 0 → non-zero │ ▼AppendEvents() to ~/.config/navi/pm/events.jsonl │ ├── Prune events older than 24 hours └── Append new events │ ▼PMOutput {snapshots, events} │ ├── Render in PM TUI view (three zones) └── Evaluate triggers for PM agent invocationPM Agent Invocation
Section titled “PM Agent Invocation”Trigger event (task_completed, commit, on-demand) │ ▼BuildInbox(trigger, snapshots, events) │ ▼InvokeWithRecovery(inbox) │ ├── claude -p --output-format json --json-schema <schema> ├── Pipe inbox JSON to stdin ├── Timeout: 120 seconds │ ├── Success → ParseOutput() → PMBriefing │ └── CacheOutput() to last-output.json │ ├── Failure → LoadCachedOutput() (fallback, marked stale) │ └── Rate limit → Exponential backoff (1s, 2s, 4s, max 3 retries) │ ▼PMBriefing displayed in Zone 1 (briefing area)Message Flow in the TUI
Section titled “Message Flow in the TUI”The Bubble Tea Update function processes messages in priority order:
- Dialog mode — Route to dialog-specific handler (including sound pack picker)
- PM view — Route to PM view handler (navigation, expansion, scrolling)
- Task panel focus — Route to task panel handler
- Preview focus — Route to preview handler
- Search mode — Route to search handler
- Main keybindings — Handle navigation, actions, toggles
- Async messages — Process polling results, command outputs, PM data