feat(mind): auto-merge ledger branches, so Mind's own work stops stranding - #358
Merged
Merged
Conversation
…nding
PyAutoMind's own work strands on `claude/**`. `prompt_sync.sh` pushes HEAD
deliberately — a cloud session must not push straight to main — but nothing
downstream moves the branch on: no workflow so much as *looks* at a `claude/**`
push, because lifecycle_drift, dashboard_refresh, firewall_gate and spawn_drift
all trigger on `push: main` or `pull_request` only. So a filed prompt, a task
moved to `complete/`, a regenerated dashboard all wait for a human to write an
explicit "merge that branch" prompt, and the dashboard renders a stale backlog
until they do.
The last twelve merges into main say what is actually stranding: `draft/`,
`active/`, `complete/`, the root registry files, `dashboard.{md,html}`. It is
the organism's own bookkeeping, its drift checks are already automated, and
review adds nothing to a moved prompt file. Review adds everything to a changed
script — so draw that line and merge only the ledger side of it.
- scripts/ledger_merge.py — the gate, as a script rather than workflow YAML so
it is testable and a session can predict the verdict before pushing. DEFAULT
DENY: an unclassified path (a new root file, a new top-level folder) is code.
Two exceptions inside the ledger dirs: a dot-path anywhere, and a file pytest
would collect — inert prompt assets ride along, a file CI would execute does
not.
- .github/workflows/mind_ledger_merge.yml — on push to `claude/**`: classify,
block on `lifecycle.py check` (structural drift heals nowhere), merge, delete
the branch, then dispatch the self-healing legs on main (a GITHUB_TOKEN push
triggers no workflows, so stale renders must be asked for). An open PR is
merged THROUGH the PR so it records as MERGED; a branch without one gets a
direct merge commit and is deleted on the proof branch_sweep.yml uses — main
must actually contain the head sha. Manual dispatch defaults to audit.
- The workflow and the gate are both on the code side of their own line, so
neither can auto-merge a change to itself. This branch classifies as code.
- Tests are the refusals: default deny, traversal, dot-paths, collectable
files, every code home the repo has. One caught a fail-open — a stdin of
"\n" is one empty string, a truthy list, and read as "0 ledger paths, exit 0".
- spawn.py DROPs the workflow (it merges to main and checks out a sibling
Brain: dashboard_refresh.yml's rule-9c reasoning, twice over).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmGdNA97ajAyLJ4jemvki8
Jammy2211
pushed a commit
that referenced
this pull request
Sep 4, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYs4cX2wT5vsZScjLNLZif
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The seam
PyAutoMind's own work strands on
claude/**.prompt_sync.shpushes HEAD deliberately — a cloud session must not push straight tomain— but nothing downstream moves the branch on: no workflow so much as looks at aclaude/**push, becauselifecycle_drift,dashboard_refresh,firewall_gateandspawn_driftall trigger onpush: mainorpull_requestonly.So a filed prompt, a task moved to
complete/, a regenerated dashboard all wait for a human to write an explicit "merge that branch" prompt, and the dashboard renders a stale backlog until they do.The last twelve merges into
mainsay what is actually stranding:draft/,active/,complete/, the root registry files,dashboard.{md,html}. It is the organism's own bookkeeping, its drift checks are already automated, and review adds nothing to a moved prompt file. Review adds everything to a changed script — so draw that line and merge only the ledger side of it.What lands
scripts/ledger_merge.py— the gate, as a script rather than workflow YAML so it is testable and a session can predict the verdict before it pushes. Default deny: an unclassified path (a new root file, a new top-level folder) is code.draft/**,active/**,complete/**scripts/,tests/,.github/,skills/,policy/,docs/active.md,planned.md,parked.md,condemned.md,epics.md,ideas.md,autonomy_log.mdrepos.yaml,README.md,AGENTS.md,REFERENCE.md, …dashboard.md,dashboard.htmlTwo exceptions inside the ledger dirs: a dot-path anywhere, and a file pytest would collect (
conftest.py,test_*.py,*_test.py) — inert prompt assets likedraft/bug/autofit/*_assets/run_once.pyride along, a file CI would execute does not..github/workflows/mind_ledger_merge.yml— on push toclaude/**:main.lifecycle.py checkblocks — structural drift (a prompt inactive/with noactive.mdentry) heals nowhere, and merging it would put the contradiction onmain. Stale renders do not block:complete/index.md, the registry contents blocks and the dashboard all self-heal onmain.MERGED; a branch without one gets a direct merge commit, retried on the current tip ofmain(the same convergence the self-heal loops use). A conflict stops the run and leaves the branch alone.branch_sweep.ymluses —mainmust actually contain the head sha, so a failed or partial merge never loses a branch. A PR merge usually self-deletes; this covers the direct path, which is what let 188 provably-spent branches pile up.dashboard_refresh.yml+lifecycle_drift.ymlonmain— aGITHUB_TOKENpush triggers no workflows, so the heals must be asked for explicitly (the lessondashboard_refresh.ymlalready carries from 2026-08-21).workflow_dispatchruns the same gate inauditmode by default, so a manual look never merges by accident.Not a review bypass
Review adds nothing to a moved prompt file; it adds everything to a changed script. The gate encodes precisely that distinction and nothing else. The workflow and the gate script are both on the code side of their own line — neither can auto-merge a change to itself, and this PR classifies as code, which is why it is a PR.
Validation
267 tests (
-n auto),ruffclean,lifecycle check/registry_toc --check/repos_sync --checkclean,spawn.pyrule matches.11 new tests, and they are the refusals: default deny, traversal behind a ledger prefix, dot-paths, pytest-collectable files, and every code home the repo actually has. One caught a fail-open in the first draft — a stdin of
"\n"is one empty string, which is a truthy list, and was read as "0 ledger paths, exit 0".spawn.pyDROPs the workflow: it merges tomainand checks out a sibling Brain for the dashboard render —dashboard_refresh.yml's rule-9c reasoning, twice over. An adopter re-adds it once they have a Brain and have decided which of their own paths are ledger.Noted, not touched
Two pre-existing drifts on
main, unrelated to this diff:spawn.pyreports.claude/hooks/session-start.shand.claude/settings.jsonUNMATCHED (so the weeklyspawn_driftcron is failing), andrepos_sync.py --checkflags PyAutoMemory's session-start hook as differing frompolicy/session_start_hook.sh.Generated by Claude Code