Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7047f94
test: add unit tests for compactor_dog config helpers (gt-3u1)
DreadPirateRobertz Mar 7, 2026
67c7a9b
feat: wasteland reputation scoring algorithm (gt-97h)
DreadPirateRobertz Mar 7, 2026
c131bb6
bd: backup 2026-03-07 03:56
DreadPirateRobertz Mar 7, 2026
393f580
fix: cross-rig dependency tracking in convoy blocking checks (gt-sl3)
DreadPirateRobertz Mar 7, 2026
4ee4510
chore: remove start_command from role configs
DreadPirateRobertz Mar 7, 2026
b9c7e41
feat: daemon auto-restarts crashed polecats with backoff (gt-w1m)
DreadPirateRobertz Mar 7, 2026
0c66542
feat: wasteland task claim timeout and auto-release (gt-wln)
DreadPirateRobertz Mar 7, 2026
e2023e9
feat: gt sling dry-run validation mode (gt-i6p)
DreadPirateRobertz Mar 7, 2026
9dea85b
feat: witness log aggregation and search (gt-a53)
DreadPirateRobertz Mar 7, 2026
595efae
feat: detect nested git repos and warn agents during prime (gt-k7dy)
DreadPirateRobertz Mar 7, 2026
39cf4b9
bd: backup 2026-03-07 14:35
DreadPirateRobertz Mar 7, 2026
f9899eb
feat: daemon rate limit recovery for usage-limited sessions (gt-lfot)
DreadPirateRobertz Mar 7, 2026
8612a53
fix: preserve context across auto-handoff before compaction (gt-ny4g)
DreadPirateRobertz Mar 7, 2026
3eaa85b
fix: clear agent bead hook_bead on unsling and done (gt-ufs5)
DreadPirateRobertz Mar 7, 2026
7c28aa4
feat: unify single-sling rig dispatch with executeSling() (gt-s04l)
DreadPirateRobertz Mar 7, 2026
5f5a9ce
feat: add lightweight headless role for non-repo tasks (gt-iwhj)
DreadPirateRobertz Mar 7, 2026
45da2cd
feat: bump MinDoltVersion to 1.83.1 for nil pointer fixes (gt-x23u)
DreadPirateRobertz Mar 7, 2026
235505a
fix: always delete polecat branches after merge (gt-jqnu)
DreadPirateRobertz Mar 7, 2026
e6df29d
bd: backup 2026-03-07 17:26
DreadPirateRobertz Mar 7, 2026
f737b51
fix: defer work bead close to Refinery when MR submitted (gt-uhj8)
DreadPirateRobertz Mar 7, 2026
7663238
feat: add gt reload command for hot config and env reload (gt-twmu)
DreadPirateRobertz Mar 7, 2026
ef8c0ed
feat: add local model integration for cost-aware orchestration (gt-wed8)
DreadPirateRobertz Mar 7, 2026
69be46b
bd: backup 2026-03-07 21:49
DreadPirateRobertz Mar 7, 2026
279815a
feat: notify mayor after successful merge via nudge (gt-34plb)
DreadPirateRobertz Mar 7, 2026
db8d762
bd: backup 2026-03-07 22:24
DreadPirateRobertz Mar 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .beads/backup/backup_state.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"last_dolt_commit": "dgoou26f6q7b4s9m6e6jvkikkjme735m",
"last_dolt_commit": "krhvkg7thvs1g7ibtrm9uu5b09b8rdtg",
"last_event_id": 7382,
"timestamp": "2026-03-06T01:17:11.531011Z",
"timestamp": "2026-03-07T22:24:09.656288Z",
"counts": {
"issues": 677,
"events": 3643,
"comments": 14,
"dependencies": 315,
"labels": 169,
"config": 17
"issues": 1700,
"events": 2533,
"comments": 10,
"dependencies": 1287,
"labels": 402,
"config": 14
}
}
24 changes: 10 additions & 14 deletions .beads/backup/comments.jsonl

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions .beads/backup/config.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
{"key":"compact_tier2_dep_levels","value":"5"}
{"key":"compaction_enabled","value":"false"}
{"key":"issue_prefix","value":"gt"}
{"key":"kv.memory.formula-toml-syntax","value":"Formula files are TOML in internal/formula/formulas/. All {{variable}} placeholders must be declared in [vars] section. Computed variables need default=\"\" so they are not required as input. [vars] entries must be TOML tables ([vars.name]) NOT bare strings. Template keywords filtered by isHandlebarsKeyword(). Tests read from BOTH source (formulas/) AND deployed (.beads/formulas/) dirs."}
{"key":"kv.memory.hooks-package-structure","value":"Hooks system: internal/hooks/ package. config.go = data model + file I/O (HooksConfig, SettingsJSON, Target types). merge.go = MergeHooks (base + role override + rig/role override). discover.go = DiscoverTargets (finds all worktrees needing settings.json). Base config: ~/.gt/hooks-base.json. Overrides: ~/.gt/hooks-overrides/{target}.json (/ replaced with __)."}
{"key":"kv.memory.refinery-worktree-merge-flow","value":"Refinery works from a git worktree — cannot checkout main (checked out by mayor). Use 'git push origin temp:main' for fast-forward pushes. Merge flow: 1) git fetch origin main \u003cbranch\u003e, 2) git reset --hard origin/\u003cbranch\u003e, 3) git rebase origin/main, 4) go test ./..., 5) git push origin temp:main, 6) gt mail send witness MERGED, 7) bd update MR --status=closed, 8) git push origin --delete \u003cbranch\u003e"}
{"key":"kv.memory.style-and-workspace","value":"Styling via internal/style/ package — lipgloss wrappers: Success, Warning, Error, Dim, Bold, Info. Workspace discovery via internal/workspace.FindFromCwd(). Commands use cobra in internal/cmd/."}
{"key":"schema_version","value":"6"}
{"key":"types.custom","value":"molecule,gate,convoy,merge-request,slot,agent,role,rig,message"}
{"key":"status.custom","value":"staged_ready,staged_warnings,status.custom (not set)"}
{"key":"types.custom","value":"agent,role,rig,convoy,slot,queue,event,message,molecule,gate,merge-request"}
1,602 changes: 1,287 additions & 315 deletions .beads/backup/dependencies.jsonl

Large diffs are not rendered by default.

6,176 changes: 2,533 additions & 3,643 deletions .beads/backup/events.jsonl

Large diffs are not rendered by default.

1,485 changes: 1,254 additions & 231 deletions .beads/backup/issues.jsonl

Large diffs are not rendered by default.

311 changes: 272 additions & 39 deletions .beads/backup/labels.jsonl

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Dockerfile.e2e
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

FROM golang:1.26-alpine

ARG DOLT_VERSION=1.82.4
ARG DOLT_VERSION=1.83.1
ARG BD_VERSION=v0.57.0

# Install dependencies including CGO build requirements for beads daemon
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Git-backed issue tracking system that stores work state as structured data.

- **Go 1.23+** - [go.dev/dl](https://go.dev/dl/)
- **Git 2.25+** - for worktree support
- **Dolt 1.82.4+** - [github.com/dolthub/dolt](https://github.com/dolthub/dolt)
- **Dolt 1.83.1+** - [github.com/dolthub/dolt](https://github.com/dolthub/dolt)
- **beads (bd) 0.55.4+** - [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
- **sqlite3** - for convoy database queries (usually pre-installed on macOS/Linux)
- **tmux 3.0+** - recommended for full experience
Expand Down
4 changes: 2 additions & 2 deletions docs/INSTALLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Complete setup guide for Gas Town multi-agent orchestrator.
|------|---------|-------|---------|
| **Go** | 1.24+ | `go version` | See [golang.org](https://go.dev/doc/install) |
| **Git** | 2.20+ | `git --version` | See below |
| **Dolt** | >= 1.82.4 | `dolt version` | See [dolthub/dolt](https://github.com/dolthub/dolt?tab=readme-ov-file#installation) |
| **Dolt** | >= 1.83.1 | `dolt version` | See [dolthub/dolt](https://github.com/dolthub/dolt?tab=readme-ov-file#installation) |
| **Beads** | >= 0.55.4 | `bd version` | `go install github.com/steveyegge/beads/cmd/bd@latest` |

### Optional (for Full Stack Mode)
Expand Down Expand Up @@ -74,7 +74,7 @@ sudo dnf install -y tmux
# Check all prerequisites
go version # Should show go1.24 or higher
git --version # Should show 2.20 or higher
dolt version # Should show 1.82.4 or higher
dolt version # Should show 1.83.1 or higher
tmux -V # (Optional) Should show 3.0 or higher
```

Expand Down
26 changes: 25 additions & 1 deletion docs/WASTELAND.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ board, claiming your first task, and submitting evidence of completion.
| `gt wl claim <id>` | Claim a wanted item |
| `gt wl done <id> --evidence <url>` | Submit completion evidence |
| `gt wl post --title "..."` | Post a new wanted item |
| `gt wl sweep` | Release expired claims |
| `gt wl sync` | Pull upstream changes |

## Prerequisites
Expand All @@ -37,7 +38,7 @@ You need a running Gas Town installation and a DoltHub account.
| Requirement | Check | Setup |
|-------------|-------|-------|
| **Gas Town** | `gt version` | See [INSTALLING.md](INSTALLING.md) |
| **Dolt** | `dolt version` (>= 1.82.4) | See [dolthub/dolt](https://github.com/dolthub/dolt?tab=readme-ov-file#installation) |
| **Dolt** | `dolt version` (>= 1.83.1) | See [dolthub/dolt](https://github.com/dolthub/dolt?tab=readme-ov-file#installation) |
| **DoltHub account** | — | [Sign up](https://www.dolthub.com/signin) |
| **DoltHub API token** | — | [Generate token](https://www.dolthub.com/settings/tokens) |

Expand Down Expand Up @@ -200,6 +201,29 @@ what establishes priority.

Future phases will introduce automatic claim propagation via DoltHub PRs.

### Claim Timeout and Auto-Release

Claims have a default timeout of **72 hours (3 days)**. If a claimed item
is not completed within this window, it can be automatically released back
to `open` status so other rigs can claim it.

Release happens in two ways:

1. **Manual sweep**: Run `gt wl sweep` to release all expired claims
2. **Auto-sweep on sync**: `gt wl sync` automatically sweeps expired claims
after merging upstream changes

```bash
gt wl sweep # Release claims older than 72h (default)
gt wl sweep --timeout 24h # Release claims older than 24h
gt wl sweep --timeout 168h # Release claims older than 1 week
gt wl sweep --dry-run # Show what would be released
```

The `claimed_at` timestamp is set when you claim an item and cleared when
a claim is released. Items without a `claimed_at` (claimed before this
feature was added) are not affected by the sweep.

### Choosing What to Claim

Tips for picking your first task:
Expand Down
16 changes: 13 additions & 3 deletions internal/beads/beads_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,15 +430,22 @@ func (b *Beads) UpdateAgentState(id string, state string) (retErr error) {
return nil
}

// SetHookBead and ClearHookBead removed (hq-l6mm5).
// Hook slot on agent beads is no longer maintained. Work bead status=hooked
// and assignee=<agent> is the authoritative source for hook tracking.
// ClearAgentHookBead clears the hook_bead field in an agent bead's description.
// Called by gt unsling to remove stale references (gt-ufs5). The hook_bead
// description field is still written at spawn time by FormatAgentDescription
// for backward compat with display readers (daemon crash detection, manager
// loadFromBeads fallback). Unsling must clear it to prevent stale reads.
func (b *Beads) ClearAgentHookBead(id string) error {
empty := ""
return b.UpdateAgentDescriptionFields(id, AgentFieldUpdates{HookBead: &empty})
}

// AgentFieldUpdates specifies which agent description fields to update.
// Only non-nil fields are modified; nil fields are left unchanged.
// This allows multiple fields to be updated in a single read-modify-write
// cycle, avoiding races where concurrent callers overwrite each other's changes.
type AgentFieldUpdates struct {
HookBead *string // Clear/set hook_bead (gt-ufs5: unsling must clear stale refs)
CleanupStatus *string
ActiveMR *string
NotificationLevel *string
Expand Down Expand Up @@ -480,6 +487,9 @@ func (b *Beads) UpdateAgentDescriptionFields(id string, updates AgentFieldUpdate

fields := ParseAgentFields(issue.Description)

if updates.HookBead != nil {
fields.HookBead = *updates.HookBead
}
if updates.CleanupStatus != nil {
fields.CleanupStatus = *updates.CleanupStatus
}
Expand Down
121 changes: 85 additions & 36 deletions internal/cmd/done.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,18 @@ func init() {

func runDone(cmd *cobra.Command, args []string) (retErr error) {
defer func() { telemetry.RecordDone(context.Background(), strings.ToUpper(doneStatus), retErr) }()
// Guard: Only polecats should call gt done
// Guard: Only polecats/headless workers should call gt done
// Crew, deacons, witnesses etc. don't use gt done - they persist across tasks.
// Polecat sessions end with gt done — the session is cleaned up, but the
// polecat's persistent identity (agent bead, CV chain) survives across assignments.
// Polecat/headless sessions end with gt done — the session is cleaned up, but the
// agent's persistent identity (agent bead, CV chain) survives across assignments.
actor := os.Getenv("BD_ACTOR")
if actor != "" && !isPolecatActor(actor) {
return fmt.Errorf("gt done is for polecats only (you are %s)\nPolecat sessions end with gt done — the session is cleaned up, but identity persists.\nOther roles persist across tasks and don't use gt done.", actor)
}

// Detect headless mode (no git worktree)
isHeadless := os.Getenv("GT_HEADLESS") == "true"

// Validate exit status
exitType := strings.ToUpper(doneStatus)
if exitType != ExitCompleted && exitType != ExitEscalated && exitType != ExitDeferred {
Expand Down Expand Up @@ -192,40 +195,51 @@ func runDone(cmd *cobra.Command, args []string) (retErr error) {
}

// Initialize git - use cwd if available, otherwise use rig's mayor clone
// Headless workers don't have git worktrees, so git operations are limited.
var g *git.Git
if cwdAvailable {
g = git.NewGit(cwd)
} else {
// Fallback: use the rig's mayor clone for git operations
var branch string
if isHeadless {
// Headless: use mayor clone for beads operations only (no push/MR).
mayorClone := filepath.Join(townRoot, rigName, "mayor", "rig")
g = git.NewGit(mayorClone)
}
branch = "" // No branch for headless
if doneCleanupStatus == "" {
doneCleanupStatus = "clean" // Headless workers have no git state to check
}
} else {
if cwdAvailable {
g = git.NewGit(cwd)
} else {
// Fallback: use the rig's mayor clone for git operations
mayorClone := filepath.Join(townRoot, rigName, "mayor", "rig")
g = git.NewGit(mayorClone)
}

// Get current branch - try env var first if cwd is gone
var branch string
if !cwdAvailable {
// Try to get branch from GT_BRANCH env var (set by session manager)
branch = os.Getenv("GT_BRANCH")
}
// CRITICAL FIX: Only call g.CurrentBranch() if we're using the cwd-based git.
// When cwdAvailable is false, we fall back to the mayor clone for git operations,
// but the mayor clone is on main/master - NOT the polecat branch. Calling
// g.CurrentBranch() in that case would incorrectly return main/master.
if branch == "" {
// Get current branch - try env var first if cwd is gone
if !cwdAvailable {
// We don't have GT_BRANCH and we're using mayor clone - can't determine branch.
// Session stays alive (persistent polecat model) — Witness handles recovery.
return fmt.Errorf("cannot determine branch: GT_BRANCH not set and working directory unavailable")
// Try to get branch from GT_BRANCH env var (set by session manager)
branch = os.Getenv("GT_BRANCH")
}
var err error
branch, err = g.CurrentBranch()
if err != nil {
// Last resort: try to extract from polecat name (polecat/<name>-<suffix>)
if polecatName := os.Getenv("GT_POLECAT"); polecatName != "" {
branch = fmt.Sprintf("polecat/%s", polecatName)
style.PrintWarning("could not get branch from git, using fallback: %s", branch)
} else {
return fmt.Errorf("getting current branch: %w", err)
// CRITICAL FIX: Only call g.CurrentBranch() if we're using the cwd-based git.
// When cwdAvailable is false, we fall back to the mayor clone for git operations,
// but the mayor clone is on main/master - NOT the polecat branch. Calling
// g.CurrentBranch() in that case would incorrectly return main/master.
if branch == "" {
if !cwdAvailable {
// We don't have GT_BRANCH and we're using mayor clone - can't determine branch.
// Session stays alive (persistent polecat model) — Witness handles recovery.
return fmt.Errorf("cannot determine branch: GT_BRANCH not set and working directory unavailable")
}
var err error
branch, err = g.CurrentBranch()
if err != nil {
// Last resort: try to extract from polecat name (polecat/<name>-<suffix>)
if polecatName := os.Getenv("GT_POLECAT"); polecatName != "" {
branch = fmt.Sprintf("polecat/%s", polecatName)
style.PrintWarning("could not get branch from git, using fallback: %s", branch)
} else {
return fmt.Errorf("getting current branch: %w", err)
}
}
}
}
Expand Down Expand Up @@ -347,6 +361,22 @@ func runDone(cmd *cobra.Command, args []string) (retErr error) {
var doneErrors []string
var convoyInfo *ConvoyInfo // Populated if issue is tracked by a convoy
if exitType == ExitCompleted {
// Headless workers skip all git operations — no branch, no push, no MR.
// They complete by closing the issue and transitioning to idle.
if isHeadless {
fmt.Printf("%s Headless worker completing (no git operations)\n", style.Bold.Render("→"))
// Close the issue directly (no Refinery MR to trigger close)
if issueID != "" {
bd := beads.New(cwd)
if err := bd.Close(issueID); err != nil {
style.PrintWarning("could not close issue %s: %v", issueID, err)
} else {
fmt.Printf(" Closed issue: %s\n", issueID)
}
}
goto notifyWitness
}

if branch == defaultBranch || branch == "master" {
return fmt.Errorf("cannot submit %s/master branch to merge queue", defaultBranch)
}
Expand Down Expand Up @@ -951,7 +981,11 @@ notifyWitness:
}

// Update agent bead state (ZFC: self-report completion)
updateAgentStateOnDone(cwd, townRoot, exitType, issueID)
// gt-uhj8: Pass mrSubmitted so we skip closing the work bead when an MR
// was created. The Refinery closes the source issue after merge — closing
// it here allows dependents to dispatch before the code lands on main.
mrSubmitted := mrID != "" && !mrFailed
updateAgentStateOnDone(cwd, townRoot, exitType, issueID, mrSubmitted)

// Persistent polecat model (gt-hdf8): polecats transition to IDLE after completion.
// Session stays alive, sandbox preserved, worktree synced to main for reuse.
Expand Down Expand Up @@ -1135,6 +1169,10 @@ func clearDoneCheckpoints(bd *beads.Beads, agentBeadID string) {
// Uses issueID directly to find the hooked bead instead of reading the agent bead's
// hook_bead slot (hq-l6mm5: direct bead tracking).
//
// gt-uhj8: When mrSubmitted is true, the work bead is NOT closed here. The Refinery
// closes it after merge (engineer.go). Closing prematurely allows dependents to
// dispatch before the dependency code lands on main.
//
// Per gt-zecmc: observable states ("done", "idle") removed - use tmux to discover.
// Non-observable states ("stuck", "awaiting-gate") are still set since they represent
// intentional agent decisions that can't be observed from tmux.
Expand All @@ -1144,7 +1182,7 @@ func clearDoneCheckpoints(bd *beads.Beads, agentBeadID string) {
// BUG FIX (hq-3xaxy): This function must be resilient to working directory deletion.
// If the polecat's worktree is deleted before gt done finishes, we use env vars as fallback.
// All errors are warnings, not failures - gt done must complete even if bead ops fail.
func updateAgentStateOnDone(cwd, townRoot, exitType, issueID string) {
func updateAgentStateOnDone(cwd, townRoot, exitType, issueID string, mrSubmitted bool) {
// Get role context - try multiple sources for resilience
roleInfo, err := GetRoleWithContext(cwd, townRoot)
if err != nil {
Expand Down Expand Up @@ -1247,8 +1285,14 @@ func updateAgentStateOnDone(cwd, townRoot, exitType, issueID string) {
}
}

// Acceptance criteria gate: skip close if criteria are unchecked.
if unchecked := beads.HasUncheckedCriteria(hookedBead); unchecked > 0 {
// gt-uhj8: Skip closing the work bead when an MR was submitted to the
// Refinery merge queue. The Refinery closes the source issue after merge
// (engineer.go). Closing here would allow dependents to dispatch before
// the code lands on main.
if mrSubmitted {
fmt.Fprintf(os.Stderr, "Bead %s left open — Refinery will close after merge\n", hookedBeadID)
} else if unchecked := beads.HasUncheckedCriteria(hookedBead); unchecked > 0 {
// Acceptance criteria gate: skip close if criteria are unchecked.
style.PrintWarning("hooked bead %s has %d unchecked acceptance criteria — skipping close", hookedBeadID, unchecked)
fmt.Fprintf(os.Stderr, " The bead will remain open for witness/mayor review.\n")
} else if err := bd.Close(hookedBeadID); err != nil {
Expand All @@ -1258,7 +1302,12 @@ func updateAgentStateOnDone(cwd, townRoot, exitType, issueID string) {
}
}

// No ClearHookBead call needed — agent bead hook slot is no longer maintained (hq-l6mm5).
// Clear agent bead's hook_bead description field to prevent stale references (gt-ufs5).
// ResetAgentBeadForReuse also clears this on nuke, but persistent polecats may
// sit idle between assignments — clear it now so daemon/manager don't read stale values.
if err := bd.ClearAgentHookBead(agentBeadID); err != nil {
fmt.Fprintf(os.Stderr, "Warning: couldn't clear hook_bead on agent %s: %v\n", agentBeadID, err)
}

// Self-managed completion (gt-1qlg, polecat-self-managed-completion.md Phase 2):
// Polecat sets agent_state=idle directly, skipping the intermediate "done" state.
Expand Down
Loading
Loading