Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

This chapter covers git-paw’s internal architecture: module structure, data flow, and key design decisions.

Module Diagram

┌─────────────────────────────────────────────────────────────────┐
│                              main.rs                             │
│                       (entry point, dispatch)                    │
├──────────┬──────────────┬──────────────┬─────────────┬───────────┤
│  cli.rs  │ interactive  │   config.rs  │  error.rs   │  dirs.rs  │
│  (clap)  │ (dialoguer)  │    (TOML)    │ (PawError)  │  (XDG)    │
├──────────┴──────────────┴──────────────┴─────────────┴───────────┤
│                                                                   │
│  detect.rs    git.rs      tmux.rs    session.rs    logging.rs    │
│  (PATH scan)  (worktrees) (builder)  (JSON state)  (pane logs)   │
│                                                                   │
│  agents.rs    skills.rs   init.rs    replay.rs                   │
│  (AGENTS.md)  (skill      (project    (log                       │
│               templates)  bootstrap)  playback)                  │
├───────────────────────────────────────────────────────────────────┤
│  broker/                  supervisor/             specs/         │
│  ├── mod.rs               ├── mod.rs              ├── mod.rs     │
│  ├── server.rs            ├── approve.rs          ├── openspec.rs│
│  ├── messages.rs          ├── auto_approve.rs     ├── markdown.rs│
│  ├── delivery.rs          ├── curl_allowlist.rs   ├── speckit.rs │
│  ├── conflict.rs          ├── dev_allowlist.rs    └── resolve.rs │
│  ├── learnings.rs         ├── layout.rs                          │
│  ├── watcher.rs           ├── permission_prompt.rs               │
│  └── publish.rs           ├── poll.rs                            │
│                           └── stall.rs                           │
└───────────────────────────────────────────────────────────────────┘

Module Responsibilities

ModuleFilePurpose
CLIsrc/cli.rsArgument parsing with clap v4 derive macros. Defines all subcommands, flags (--from-all-specs, --specs, --specs-format, --supervisor, --no-supervisor, --force, …), and help text.
Detectionsrc/detect.rsScans PATH for known AI CLI binaries (KNOWN_CLIS). Resolves custom CLIs from config. Merges and deduplicates.
Gitsrc/git.rsValidates git repos, lists branches (local + remote, deduplicated), creates/removes worktrees, derives safe directory names.
Tmuxsrc/tmux.rsBuilder pattern for tmux operations. Creates sessions, splits panes, sends commands, applies the supervisor-as-pane layout, sets pane titles.
Sessionsrc/session.rsPersists session state to JSON files under ~/.local/share/git-paw/sessions/. Atomic writes, crash recovery.
Configsrc/config.rsParses TOML from global (~/.config/git-paw/config.toml) and per-repo (.git-paw/config.toml). Merges with repo-wins semantics.
Interactivesrc/interactive.rsTerminal prompts via dialoguer. Mode picker, branch multi-select, CLI picker. Skips prompts when flags are provided.
Errorsrc/error.rsPawError enum with thiserror. Actionable error messages and distinct exit codes.
Dirssrc/dirs.rsIn-tree platform XDG path helper. Replaces the upstream dirs crate (removed in v0.5.0 for license reasons); see AGENTS.md § Dependencies.
Agentssrc/agents.rsGenerates worktree AGENTS.md files; manages the <!-- git-paw:start … end --> marker region; supports the boot-prompt-full-body model.
Skillssrc/skills.rsLoads standardized agent skills from .agents/skills/ following the agentskills.io specification. Injects coordination + supervisor instructions into worktree AGENTS.md.
Initsrc/init.rsgit paw init bootstrap. Creates .git-paw/, default config, logs directory, gitignore entries. Auto-detects .specify/ for Spec Kit.
Replaysrc/replay.rsgit paw replay. Reads pane logs from .git-paw/logs/ and either strips ANSI or pipes through less -R.
Loggingsrc/logging.rsPer-pane log capture via tmux pipe-pane. Files at .git-paw/logs/<session>/<branch>.log.
Brokersrc/broker/HTTP coordination server (axum) with watcher + conflict detector + learnings subsystems. Detail below.
Supervisorsrc/supervisor/Supervisor-mode subsystems (auto-approve, dev allowlist, stall sweeps, permission prompts, pane layout). Detail below.
Specssrc/specs/Spec scanning. Three backends (openspec, markdown, speckit); resolve.rs is the dispatch entry point.

src/broker/ modules

FilePurpose
src/broker/mod.rsPublic surface (start/stop entry points, shared state types).
src/broker/server.rsaxum HTTP server: /publish, /messages/:agent_id, /status.
src/broker/messages.rsBrokerMessage enum + payload types + slug validation. Source of truth for the wire format used in user-facing examples.
src/broker/publish.rsValidation + sequence assignment for incoming /publish calls.
src/broker/delivery.rsRouting layer: which inboxes a message lands in (broadcast, supervisor inbox, targeted delivery).
src/broker/watcher.rsFilesystem watcher that auto-publishes agent.status (with modified_files) whenever a tracked file changes in a worktree.
src/broker/conflict.rsForward / in-flight / ownership conflict detection. Auto-emits [conflict-detector]-tagged agent.feedback and escalates via agent.question.
src/broker/learnings.rsOpt-in learnings subsystem. Aggregates the five deterministic categories and flushes to .git-paw/session-learnings.md.

src/dashboard.rs (top-level, not inside src/broker/) renders the dashboard pane — the live status table and the optional message-log panel — by reading the shared broker state. The dashboard pane sits at pane index 1 in supervisor mode (see the layout diagram below) and at pane 0 in non-supervisor broker mode.

src/supervisor/ modules

FilePurpose
src/supervisor/mod.rsSupervisor boot — composes the subsystems below and drives the supervisor pane.
src/supervisor/approve.rsGeneric approval/feedback decision plumbing shared by the auto-approver.
src/supervisor/auto_approve.rsSafe-command auto-approver against stalled panes (approval_level, safe_commands, sweeps).
src/supervisor/curl_allowlist.rsSeeds the broker curl endpoints into .claude/settings.json::allowed_bash_prefixes so the first broker call never hits a permission prompt.
src/supervisor/dev_allowlist.rsSeeds the curated [supervisor.common_dev_allowlist] preset (cargo / git / just / mdBook / OpenSpec) into .claude/settings.json.
src/supervisor/layout.rsSupervisor-as-pane tmux layout: pane 0 supervisor, pane 1 dashboard, agent panes 2 onwards in the bottom-row grid (row-height proportions documented below).
src/supervisor/permission_prompt.rsPane classification for permission-prompt detection (tmux capture-pane parsing).
src/supervisor/poll.rsStalled-pane polling loop driving the auto-approver.
src/supervisor/stall.rsStall heuristics (last-seen window, approval-level filter).

src/specs/ modules

FilePurpose
src/specs/mod.rsPublic surface for the spec subsystem.
src/specs/resolve.rsDispatch entry point. Picks the backend from [specs] type, the --specs-format CLI override, or .specify/ auto-detection.
src/specs/openspec.rsOpenSpec backend: scans <dir>/<change>/tasks.md directories, skips <dir>/archive/.
src/specs/markdown.rsMarkdown backend: scans flat .md files with YAML frontmatter; only paw_status: pending is picked up.
src/specs/speckit.rsSpec Kit backend: scans .specify/specs/<feature>/, decomposes the current phase into [P]-task worktrees plus one consolidated phase/… worktree; probes <dir>/../memory/constitution.md for the governance auto-wire.

Start Flow

The start command is the primary flow. Here’s what happens step by step:

git paw start
     │
     ▼
┌─ Check for existing session ──────────────────────┐
│                                                     │
│  Session active + tmux alive?  ──yes──► Reattach   │
│         │ no                                        │
│  Session saved + tmux dead?   ──yes──► Recover     │
│         │ no                                        │
│  No session                   ──────► Fresh start  │
└─────────────────────────────────────────────────────┘
     │
     ▼ (fresh start)
┌─ Validate git repo ─────────────────────────────────┐
│  git.validate_repo() → repo root path               │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Load config ────────────────────────────────────────┐
│  config.load_config() → merged PawConfig             │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Detect CLIs ────────────────────────────────────────┐
│  detect.detect_clis() → Vec<CliInfo>                 │
│  (auto-detected + custom, deduplicated)              │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Interactive selection ──────────────────────────────┐
│  interactive.run_selection()                          │
│  → Vec<(branch, cli)> mappings                       │
│  (skipped if --cli + --branches provided)            │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Create worktrees ───────────────────────────────────┐
│  git.create_worktree() for each branch               │
│  → ../project-branch-name/ directories               │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Build tmux session ────────────────────────────────┐
│  TmuxSessionBuilder                                  │
│    .session_name("paw-project")                      │
│    .pane(branch, worktree, cli) × N                  │
│    .mouse(true)                                      │
│    .build() → TmuxSession with command sequence      │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Mode? ─────────────────────────────────────────────┐
│  supervisor   → Pane 0 = supervisor CLI              │
│                 Pane 1 = `git paw __dashboard`       │
│                 Pane 2..N = per-spec agent CLIs      │
│  broker-only  → Pane 0 = `git paw __dashboard`       │
│                 Pane 1..N = per-branch agent CLIs    │
│  no broker    → Pane 0..N = per-branch agent CLIs    │
│                                                       │
│  In every broker mode the dashboard pane:            │
│   ├─ Starts axum HTTP server on configured port      │
│   ├─ Injects GIT_PAW_BROKER_URL into all agent panes │
│   └─ Renders the ratatui status table                │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Save session state ────────────────────────────────┐
│  session.save_session() → atomic JSON write          │
└──────────────────────────────────────────────────────┘
     │
     ▼
┌─ Attach ─────────────────────────────────────────────┐
│  tmux.attach() → user enters tmux session            │
└──────────────────────────────────────────────────────┘

Broker Architecture

When [broker] enabled = true, the dashboard pane runs git paw __dashboard. This single process hosts both the HTTP broker and the dashboard TUI. The dashboard pane sits at pane 1 in supervisor mode and at pane 0 in non-supervisor broker mode.

Dashboard pane process (git paw __dashboard):
├── tokio runtime (background threads)
│   ├── axum HTTP server on localhost:9119
│   │   ├── POST /publish
│   │   ├── GET /messages/:agent_id?since=N
│   │   └── GET /status
│   ├── Filesystem watcher (src/broker/watcher.rs)
│   │   └── Auto-publishes agent.status on file changes
│   ├── Conflict detector (src/broker/conflict.rs)
│   │   └── Forward / in-flight / ownership shapes
│   └── Learnings aggregator (src/broker/learnings.rs)
│       └── Opt-in; flushes to .git-paw/session-learnings.md
├── Flush thread (std::thread, 5s interval)
│   └── Appends to broker.log
└── Main thread
    └── ratatui dashboard (1s tick)

Broker state

The broker state is held in Arc<Mutex<...>> by src/broker/mod.rs and shared between the axum server handlers, the watcher, the conflict detector, the learnings aggregator, and the ratatui dashboard render loop. The server writes incoming messages (validated and sequenced by src/broker/publish.rs, routed by src/broker/delivery.rs); the dashboard reads the latest snapshot each tick.

The flush thread periodically serializes the message log to .git-paw/broker.log as a JSONL audit trail. This runs on a plain std::thread to avoid contention with the tokio runtime.

Environment injection

When the broker is enabled, git-paw sets GIT_PAW_BROKER_URL=http://127.0.0.1:<port> in the tmux environment for the session. Each agent pane inherits this variable and can use it to communicate with the broker.

Supervisor Mode Layout

When --supervisor is active (or [supervisor] enabled = true), the tmux session is laid out as a 50/50 top row plus a row-major agent grid below. This is the canonical v0.5.0 supervisor-as-pane layout established by the supervisor-as-pane archive.

┌──────────────────────────┬──────────────────────────┐
│  pane 0: supervisor      │  pane 1: dashboard       │
├──┬──┬──┬──┬──┬───────────┴──────────────────────────┤
│ 2│ 3│ 4│ 5│ 6│  agent grid (row 1)                  │
├──┴──┴──┴──┴──┤                                      │
│ 7│..│..│..│ N│  agent grid (row 2..M)               │
└──┴──┴──┴──┴──┴──────────────────────────────────────┘

Pane 0 always hosts the supervisor CLI; pane 1 always hosts the dashboard. Pane indices 2 onwards host one CLI per agent. The supervisor reads agent state via the broker and the dashboard; the dashboard reads the same broker state for its status table.

Row-height proportions

The top row is fixed at 50% of the supervisor pane width and the agent rows share the remaining vertical space. Row-height proportions for the agent grid depend on how many bottom rows the layout produces:

Agent rowsBottom-row heights
160% (top row 40%)
240% / 30% / 30% (top + 2 bottom rows)
328% / 24% / 24% / 24%
428% / 18% / 18% / 18% / 18%
528% / 14.4% / 14.4% / 14.4% / 14.4% / 14.4%

The agent-grid columns within each row are split evenly via tmux’s tiled layout. src/supervisor/layout.rs is the source of truth.

Non-Supervisor Layout

When supervisor mode is OFF and the broker is on, the dashboard occupies pane 0 and the agent CLIs occupy panes 1 onwards in a single row-major grid (no top row):

┌───────────────────────────────────────────────────────┐
│  pane 0: dashboard                                    │
├──┬──┬──┬──┬──┬────────────────────────────────────────┤
│ 1│ 2│ 3│ 4│ 5│  agent grid (row 1)                   │
├──┴──┴──┴──┴──┤                                       │
│ 6│..│..│..│ N│  agent grid (row 2..M)                │
└──┴──┴──┴──┴──┴───────────────────────────────────────┘

When the broker is disabled too, every pane (0..N) is an agent CLI and there is no dashboard pane.

Worktree Lifecycle

Git worktrees are the foundation of git-paw’s parallel workflow.

Creation

For a project named my-app and branch feature/auth-flow:

my-app/                         ← main repo (current directory)
my-app-feature-auth-flow/       ← worktree (created by git-paw)
my-app-feat-api/                ← worktree (created by git-paw)

Worktrees are created as siblings of the main repo directory. The naming convention is <project>-<sanitized-branch> where slashes become hyphens.

Lifecycle states

create_worktree()          stop              start (recover)
     │                      │                     │
     ▼                      ▼                     ▼
  [exists on disk]  →  [still on disk]  →  [reused as-is]
                                                  │
                                            purge │
                                                  ▼
                                          [removed from disk]

Key points:

  • Stop preserves worktrees — uncommitted work survives
  • Recover reuses existing worktrees — no data loss
  • Purge removes worktrees — git worktree remove followed by prune

Session State

Session state is persisted as JSON under ~/.local/share/git-paw/sessions/:

{
  "session_name": "paw-my-app",
  "repo_path": "/Users/you/projects/my-app",
  "project_name": "my-app",
  "created_at": "2025-01-15T10:30:00Z",
  "status": "active",
  "broker_port": 9119,
  "broker_enabled": true,
  "worktrees": [
    {
      "branch": "feat/auth",
      "worktree_path": "/Users/you/projects/my-app-feat-auth",
      "cli": "claude"
    },
    {
      "branch": "feat/api",
      "worktree_path": "/Users/you/projects/my-app-feat-api",
      "cli": "gemini"
    }
  ]
}

The broker_port and broker_enabled fields are present when the broker is configured. They allow git paw status to display broker information and git paw purge to clean up broker.log.

Atomic writes

Session state is written atomically: write to a temporary file, then rename. This prevents corruption if the process is killed mid-write.

Effective status

The on-disk status may not reflect reality (e.g., tmux was killed externally). git-paw checks the actual tmux state:

File statustmux alive?Effective status
activeYesActive (reattach)
activeNoStopped (recover)
stoppedN/AStopped (recover)
No fileN/ANo session

Tmux Builder Pattern

The tmux module uses a builder pattern that accumulates operations as data structures rather than immediately executing shell commands. This enables:

  • Testability — generate commands without executing them
  • Dry run — print the plan without side effects
  • Atomicity — validate the full plan before running anything
#![allow(unused)]
fn main() {
TmuxSessionBuilder::new()
    .session_name("paw-my-app")
    .pane(PaneSpec { branch, worktree_path, cli_command })
    .pane(PaneSpec { ... })
    .mouse(true)
    .build()
    // → TmuxSession { name, commands: Vec<TmuxCommand> }
}

The built TmuxSession can be inspected, printed (dry run), or executed.

Error Strategy

All errors flow through PawError (defined with thiserror). Each variant carries an actionable message telling the user what went wrong and how to fix it. No panics in non-test code — all Result propagation.

Exit codes:

  • 0 — success
  • 1 — operational error
  • 2 — user cancelled