refactor: Mayor's per-rig clone is canonical for beads and worktrees

- Mayor has <rig>/mayor/rig/ clone (decentralized, discoverable)
- Rig .beads/ symlinks to mayor/rig/.beads (Mayor is beads authority)
- Polecats are worktrees from Mayor's clone (not Refinery)
- Updated architecture.md with new structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-17 20:00:28 -08:00
parent 691971a16a
commit 3b1ce3afe1
2 changed files with 44 additions and 33 deletions

View File

@@ -186,13 +186,17 @@ Created by `gt rig add <name> <git-url>`:
``` ```
gastown/ # Rig = container (NOT a git clone) gastown/ # Rig = container (NOT a git clone)
├── config.json # Rig configuration (git_url, beads prefix) ├── config.json # Rig configuration (git_url, beads prefix)
├── .beads/ → refinery/rig/.beads # Symlink to canonical beads in refinery ├── .beads/ → mayor/rig/.beads # Symlink to canonical beads in Mayor
├── refinery/ # Refinery agent ├── mayor/ # Mayor's per-rig presence
│ ├── rig/ # Authoritative "main" clone │ ├── rig/ # CANONICAL clone (beads authority)
│ │ └── .beads/ # Canonical rig beads (prefix: gt-, etc.) │ │ └── .beads/ # Canonical rig beads (prefix: gt-, etc.)
│ └── state.json │ └── state.json
├── refinery/ # Refinery agent (merge queue processor)
│ ├── rig/ # Refinery's clone (for merge operations)
│ └── state.json
├── witness/ # Witness agent (per-rig pit boss) ├── witness/ # Witness agent (per-rig pit boss)
│ └── state.json # No clone needed (monitors polecats) │ └── state.json # No clone needed (monitors polecats)
@@ -200,15 +204,15 @@ gastown/ # Rig = container (NOT a git clone)
│ └── <name>/ # Workspace (full git clone) │ └── <name>/ # Workspace (full git clone)
└── polecats/ # Worker directories (git worktrees) └── polecats/ # Worker directories (git worktrees)
├── Nux/ # Worktree from refinery (faster than clone) ├── Nux/ # Worktree from Mayor's clone
└── Toast/ # Worktree from refinery └── Toast/ # Worktree from Mayor's clone
``` ```
**Beads architecture:** **Beads architecture:**
- Refinery's clone holds the canonical `.beads/` for the rig - Mayor's clone holds the canonical `.beads/` for the rig
- Rig root symlinks `.beads/``refinery/rig/.beads` - Rig root symlinks `.beads/``mayor/rig/.beads`
- All agents (crew, polecats) inherit beads via parent directory lookup - All agents (crew, polecats, refinery) inherit beads via parent lookup
- Polecats are git worktrees, not full clones (much faster) - Polecats are git worktrees from Mayor's clone (much faster than full clones)
**Key points:** **Key points:**
- The rig root has no `.git/` - it's not a repository - The rig root has no `.git/` - it's not a repository
@@ -271,15 +275,21 @@ For reference without mermaid rendering:
├── gastown/ # RIG (container, NOT a git clone) ├── gastown/ # RIG (container, NOT a git clone)
│ ├── config.json # Rig configuration │ ├── config.json # Rig configuration
│ ├── .beads/ → refinery/rig/.beads # Symlink to canonical beads │ ├── .beads/ → mayor/rig/.beads # Symlink to Mayor's canonical beads
│ │ │ │
│ ├── refinery/ # Refinery agent │ ├── mayor/ # Mayor's per-rig presence
│ │ ├── rig/ # Canonical "main" clone │ │ ├── rig/ # CANONICAL clone (beads + worktree base)
│ │ │ ├── .git/ │ │ │ ├── .git/
│ │ │ ├── .beads/ # CANONICAL rig beads (gt-* prefix) │ │ │ ├── .beads/ # CANONICAL rig beads (gt-* prefix)
│ │ │ └── <project files> │ │ │ └── <project files>
│ │ └── state.json │ │ └── state.json
│ │ │ │
│ ├── refinery/ # Refinery agent (merge queue)
│ │ ├── rig/ # Refinery's clone (for merges)
│ │ │ ├── .git/
│ │ │ └── <project files>
│ │ └── state.json
│ │
│ ├── witness/ # Witness agent (pit boss) │ ├── witness/ # Witness agent (pit boss)
│ │ └── state.json # No clone needed │ │ └── state.json # No clone needed
│ │ │ │
@@ -289,9 +299,9 @@ For reference without mermaid rendering:
│ │ └── <project files> │ │ └── <project files>
│ │ │ │
│ ├── polecats/ # Worker directories (worktrees) │ ├── polecats/ # Worker directories (worktrees)
│ │ ├── Nux/ # Git worktree from refinery │ │ ├── Nux/ # Git worktree from Mayor's clone
│ │ │ └── <project files> # (inherits beads from rig) │ │ │ └── <project files> # (inherits beads from rig)
│ │ └── Toast/ # Git worktree from refinery │ │ └── Toast/ # Git worktree from Mayor's clone
│ │ │ │
│ └── plugins/ # Optional plugins │ └── plugins/ # Optional plugins
│ └── merge-oracle/ │ └── merge-oracle/
@@ -300,18 +310,19 @@ For reference without mermaid rendering:
└── wyvern/ # Another rig (same structure) └── wyvern/ # Another rig (same structure)
├── config.json ├── config.json
├── .beads/ → refinery/rig/.beads ├── .beads/ → mayor/rig/.beads
├── polecats/ ├── mayor/
├── refinery/ ├── refinery/
├── witness/ ├── witness/
── crew/ ── crew/
└── polecats/
``` ```
**Key changes from earlier design:** **Key changes from earlier design:**
- Town beads (`gm-*`) hold Mayor mail instead of JSONL files - Town beads (`gm-*`) hold Mayor mail instead of JSONL files
- Rig `.beads/` symlinks to refinery's canonical beads - Mayor has per-rig clone that's canonical for beads and worktrees
- Polecats use git worktrees (not full clones) for speed - Rig `.beads/` symlinks to Mayor's canonical beads
- No `mayor/rig/` in each rig (Mayor works from town level) - Polecats are git worktrees from Mayor's clone (fast)
### Why Decentralized? ### Why Decentralized?

View File

@@ -65,18 +65,18 @@ func (m *Manager) Add(name string) (*Polecat, error) {
return nil, fmt.Errorf("creating polecats dir: %w", err) return nil, fmt.Errorf("creating polecats dir: %w", err)
} }
// Use refinery clone as the base for worktrees // Use Mayor's clone as the base for worktrees (Mayor is canonical for the rig)
refineryPath := filepath.Join(m.rig.Path, "refinery", "rig") mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
refineryGit := git.NewGit(refineryPath) mayorGit := git.NewGit(mayorPath)
// Verify refinery clone exists // Verify Mayor's clone exists
if _, err := os.Stat(refineryPath); os.IsNotExist(err) { if _, err := os.Stat(mayorPath); os.IsNotExist(err) {
return nil, fmt.Errorf("refinery clone not found at %s (run 'gt rig add' to set up rig structure)", refineryPath) return nil, fmt.Errorf("mayor clone not found at %s (run 'gt rig add' to set up rig structure)", mayorPath)
} }
// Create worktree with new branch // Create worktree with new branch
// git worktree add -b polecat/<name> <path> // git worktree add -b polecat/<name> <path>
if err := refineryGit.WorktreeAdd(polecatPath, branchName); err != nil { if err := mayorGit.WorktreeAdd(polecatPath, branchName); err != nil {
return nil, fmt.Errorf("creating worktree: %w", err) return nil, fmt.Errorf("creating worktree: %w", err)
} }
@@ -95,7 +95,7 @@ func (m *Manager) Add(name string) (*Polecat, error) {
// Save state // Save state
if err := m.saveState(polecat); err != nil { if err := m.saveState(polecat); err != nil {
// Clean up worktree on failure // Clean up worktree on failure
refineryGit.WorktreeRemove(polecatPath, true) mayorGit.WorktreeRemove(polecatPath, true)
return nil, fmt.Errorf("saving state: %w", err) return nil, fmt.Errorf("saving state: %w", err)
} }
@@ -120,12 +120,12 @@ func (m *Manager) Remove(name string, force bool) error {
} }
} }
// Use refinery to remove the worktree properly // Use Mayor's clone to remove the worktree properly
refineryPath := filepath.Join(m.rig.Path, "refinery", "rig") mayorPath := filepath.Join(m.rig.Path, "mayor", "rig")
refineryGit := git.NewGit(refineryPath) mayorGit := git.NewGit(mayorPath)
// Try to remove as a worktree first (use force flag for worktree removal too) // Try to remove as a worktree first (use force flag for worktree removal too)
if err := refineryGit.WorktreeRemove(polecatPath, force); err != nil { if err := mayorGit.WorktreeRemove(polecatPath, force); err != nil {
// Fall back to direct removal if worktree removal fails // Fall back to direct removal if worktree removal fails
// (e.g., if this is an old-style clone, not a worktree) // (e.g., if this is an old-style clone, not a worktree)
if removeErr := os.RemoveAll(polecatPath); removeErr != nil { if removeErr := os.RemoveAll(polecatPath); removeErr != nil {
@@ -134,7 +134,7 @@ func (m *Manager) Remove(name string, force bool) error {
} }
// Prune any stale worktree entries // Prune any stale worktree entries
refineryGit.WorktreePrune() mayorGit.WorktreePrune()
return nil return nil
} }