All framework data moves to the tf-data branch (fix #1582) - #1595
Conversation
The core module only, consumers follow: DATA_BRANCH checked out as a worktree at .the-framework/branches/the-framework_data (adopted from origin, or born parentless off the empty tree), a root tickets symlink into it, and one serialized write cycle — sync, apply, commit, push — with the op re-run against origin's state when a push loses a race, and stranded local commits carried out by the next cycle. Eager pull = the same cycle with a no-op, so it also drains what a network failure left behind. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r one local writer (#1582) Readers: findTodoBacklog/nextQueuedTicket/readDocs' backlog read off the branch via readDataFile (checkout, local ref, then origin — so a project checkout, an agent worktree, and a cloud clone all read the same queue); cli's (fix #N) ticket read does too. Writers: appendTodoEntry/appendFlatTodoEntry are funneled withDataBranch cycles (an agent worktree resolves its project root via the git common dir); ticket locks acquire/release are funneled ops — the old commit/push/default-branch machinery in ticket-locks.ts goes, and a re-run op recognizes its own claims. The backlog loop's check-off is the framework's own funneled write now, not an agent file edit (the loop stalls only when the write cannot land, retried inline). queue-promote.ts is deleted whole: a pinned drain's entry is checked off by the daemon at settle, once the run's epilogue reports the work published — the same reported ending the #1583 lock release keys off, so auto-pm now holds any entry-carrying agent through the end-before-handoff gap. The daemon clock gains an eager 'data sync' pull, so every machine converges on what others pushed (#1577/#1397 die structurally when the remaining writers move in the follow-up commits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tle (#1582) The lasting copy moves from main's .the-framework/<user>/agents/ to agents/<user>/ at the data branch's root: teardown funnels archiveWorktreeAgent through withDataBranch, so every archive is a pushed commit the moment the session settles — no debounce, no flush pass, no human. That deletes agent-commit.ts whole (the committer service, its clock job, and the shutdown flushAgents phase existed to commit archives a human would otherwise have to notice). Readers (listAgents/findArchive and friends) look at the data checkout first, then the transient .the-framework/agents/ a worktree-less agent still writes; the pre-#1582 <user>/agents location on main is not read — hand-run migration moves the existing history. Delete removes a data-branch record through the funnel too, so the deletion is itself a pushed commit. .the-framework/.gitignore collapses to 'ignore it all' (nothing under it is committed on main any more), and ensureArchiveIgnored goes with the rules it repaired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… through it (#1582) New 'The data branch' section rides the system channel beside the two format specs: where tickets/ and TODO_AGENTS.md live, how to read them from any checkout (git show), and the fetch → detached worktree → commit → push → rebase-on-reject write cycle that works the same from a local worktree and a cloud clone. The presets that touch framework data (drain, plan-tickets, both triages, both suggests, update-tickets, maintenance, market-research), the two in-code pins in auto-pm.ts, and the system/on-before-mergeable TODO_FILE macros all point at it. The pinned drain contract changes shape: the agent no longer checks its entry off (the daemon retires it at settle) and the ticket/plan/lock cleanup is a data-branch commit instead of riding the PR; a plan is now a data write, not a PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e says why (#1582) Live smoke found the pair: retiring the last ticket removes tickets/ itself (git keeps no empty dirs, and the branch is born without it), and acquireTicketLocks' default write was a bare writeFile — so the next ticket-linked drain claim died on ENOENT inside the funnel, acquire resolved [], and the sweep stood the batch down with 'every entry in this batch links a ticket another agent already claimed', which was wrong on every word. The default write now creates parents, the way data-branch.ts' own default already does, and a cycle that fails before any lock lands is logged with its real error instead of only when part of the batch had already been claimed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ch funnel (#1582) Two textual conflicts: worktrees.ts' import line (union — their readFile for the strict meta read, our sep for the data-root prefix), and daemon-runtime.SPEC.md (their shutdown-refusal bullet kept; the archive-committer sentence stays deleted — this branch removes that service). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#1594) The merge crossed two changes: #1594's lifecycle test keeps the handoff-local checkout and reads the archived replay right after the worktree shows on the Remove list — which is true the moment the meta flips done, before teardown runs — while #1582 made the archive a data-branch git cycle instead of a file copy. The read now waits for the journal the way the rest of the story waits for its states; the product surface was already fine (the dashboard polls). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three integration tests on the real-git fixture, straight from the live smoke: a run whose archived record reports the hand-off gets its entry checked off as a data-branch commit (and only that entry); a run whose hand-off failed leaves its entry open — unpublished work is not retired; and a drain still claims an entry whose ticket file is gone, recreating tickets/ on the way — the batch-level proof of the missing-parent fix, which previously stood the sweep down with the wrong message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@brillout prompt changes in Manually tested end to end on a scratch repo: dashboard queue-add, two real drain runs following the new protocol unprompted, check-off + archives landing as data commits. Objections to plans-as-data-writes, or wording tweaks? |
…tignore keeps !LAYOUT
|
The prompt changes are messy, not DRY enough, and not succinct enough. WDYT? Ideas? |
| | 83 | Handoff panel: push / open PR / merge, as buttons | | ||
| | 84 | A withheld merge is reported with its reason | | ||
| | 85 | Agent history archived in the repo under per-user directories | | ||
| | 85 | Agent history archived on the `the-framework_data` branch under per-user directories — pushed the moment a session settles | |
There was a problem hiding this comment.
I thought we now prefix with tf-?
There was a problem hiding this comment.
Right, missed #1581's convention — renaming to tf-data. All occurrences are new in this PR, so it's a pure rename.
|
Agreed — fix is deletion. The system prompt already says it twice (the tickets/TODO context bullets + the "The data branch" section), so all ten per-preset "(on the data branch — see …)" parentheticals go. Only behavior survives: drain's check-off step and update_tickets' one-commit rule. Prompt diff shrinks to the protocol file + two context-bullet tweaks. |
…e data lives Review follow-ups on #1595: - the-framework_data -> tf-data everywhere: #1581 made tf- the prefix for every framework-minted branch, and the data branch was the one newcomer that ignored it. The name only exists on this PR's branch, so it is a pure rename. - The per-preset "(on the data branch — see ...)" reminders are deleted. The system prompt already carries the fact twice — the tickets/TODO context bullets and the "The data branch" protocol section — so the presets keep only genuine behavior: drain's check-off-when-published timing and update_tickets' whole-update-as-one-commit rule.
| 6. `$ git worktree remove --force .tf-data` | ||
|
|
||
| Rules: | ||
| - Never commit these files to your session branch, and never mix a data commit with code commits — a data change is pushed directly, it does not ride your PR |
There was a problem hiding this comment.
Is that true? I think there are use cases when a PR also modifies data?
There was a problem hiding this comment.
Actually, because a PR always targets a non-data branch, it kinda must be true, I think?
There was a problem hiding this comment.
Yes, must be true — main carries zero data files now, so a PR with a ticket edit would re-land data on main at merge. The rule is there because committing tickets next to code is the old habit this branch breaks.
There was a problem hiding this comment.
old habit
There shouldn't be any old habit left, since we should remove all old code. Zero compat code, as always.
There was a problem hiding this comment.
Bad wording from me, there is no compat code — "habit" = the model's default to commit tickets next to code. The rule guards against that.
Tested e2e on a scratch repo: drain agent with the trimmed prompt did clean data commits, nothing leaked on its code branch.
|
|
||
| Rules: | ||
| - Never commit these files to your session branch, and never mix a data commit with code commits — a data change is pushed directly, it does not ride your PR | ||
| - With no remote, push is skipped: the commit on the local `tf-data` branch is the write |
There was a problem hiding this comment.
That's a weird exception, isn't it?
There was a problem hiding this comment.
Agreed, it only reads weird — the write is the commit; push is just propagation, and no remote means nothing to propagate to. Folding it into the write sentence.
There was a problem hiding this comment.
Why not ignoring this egde case? (AFAICT it would almost never happen.)
If anything such edge case is an error state that should be surface to the UI, right?
Swallowing is actually the worst way to handle this erroneous state?
There was a problem hiding this comment.
Agree, dropping it, protocol just says commit + push, no special case.
No remote becomes an error surfaced to the UI.
Removing the clause + small ticket for the UI part.
|
|
||
| Only when the instructions tell you to edit tickets or the queue: | ||
|
|
||
| 1. `$ git fetch origin tf-data` (skip without a remote) |
There was a problem hiding this comment.
It feels like too much like micro management? Is it needed? Agents are quite proficient with git, so not sure it's needed? (I'd be curious how Claude Code Web handles git operations, but I'm inclined to think it doesn't micro manage agents.) WDYT?
There was a problem hiding this comment.
Agree — dropping the command recipe, keeping just the contract: commit on tf-data, push immediately, rebase on rejection, never via your checkout or PR. Claude Code's own prompts don't micromanage git either. The recipe did pass the E2E drain runs, so if trimmed agents fumble, the cheap fallback is one worktree hint.
|
Brainstorming:
I think we don't need Not sure about |
Review follow-ups on #1595: the six-step command recipe goes — agents get the contract instead (commit on tf-data pushed immediately, rebase on a rejected push, never via the checkout or the PR). The no-remote line stops being a standalone exception: the write is the commit, push is just propagation, so it folds into the write sentence. 24 lines down to 7.
1: agree, the trimmed protocol has no paths anymore. Will test this and report back. |
Per the review round: the protocol says commit + push, no special case. A project without a remote is an erroneous state for the daemon to surface, not one the prompt accommodates.
Verified the arbitration mechanics on a scratch remote: two agents claiming the same ticket race on the push — the loser's rebase hits an add/add conflict on the .lock.md. The lock section now tells the agent what that conflict means: back off, never resolve it in your own favor. Claims of different tickets rebase cleanly and both land.
|
Tested the lock race on a scratch remote: two agents claim the same ticket → second push rejected, rebase conflicts on the lock file → loser backs off, winner's lock intact. |
An uncommitted symlink at the repo root rides any sweeping git add -A onto a code branch — the leak the drain E2E surfaced. The info/exclude mechanism moves out of worktree-deps into git-exclude.ts so both callers share it; the rule is root-anchored and only written when the link is ours, so a user's own tickets path stays visible. The sibling leak (auto-onboard skipping installProject) is #1600. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🤖 agent comment One more commit after the review round: the drain E2E showed the new root Amendment ( |
|
Before the migration runs after this merges: can you hand-publish |
The repo-level exclude speaks for every worktree at once — including the tf-data checkout, whose root holds the real tickets/ the branch exists to carry. A bare /tickets rule swallowed it: data commits silently dropped every ticket file (CI's control.test caught it). The rule becomes a pair, /tickets then !/tickets/ — a trailing slash never matches a symlink, so the root link stays hidden while the directory keeps committing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… emitter: the data-branch sync (fix #1599, towards #1500) A per-project error state the daemon's background jobs set and clear (`project-errors.ts`): one slot per project and kind, with the detail and since when. The project list carries it (`onProjects` attaches each project's errors), the sidebar turns the project's dot red naming the error on hover, and the project's page shows a banner above the start form. The first emitter is the data-sync tick: a push origin rejects, or a repository with no remote at all, sets the `data-sync` error; the first sync that converges clears it. `pullDataBranch` now reports its outcome, counting no-remote as a failure to converge, since a data branch nobody else can reach is an error (#1595), not a mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements #1582: main is 100% code, 0% framework data. Everything The Framework writes —
tickets/**(plans and locks included),TODO_AGENTS.md, the session archives — moves to one dedicated branch,the-framework_data, checked out at.the-framework/branches/the-framework_datawith a rootticketssymlink into it.Net −1,300 LoC. Three mechanisms stop needing to exist:
queue-promote.ts— one queue, one branch, one local writer (the daemon). A drained entry is checked off by the daemon at settle, once the run's ending says the work was published. The Queue promote can clobber a newer main: an agent's stale TODO_AGENTS.md copy lands over externally-merged entries #1577 clobber can't happen anymore.agent-commit.ts— archives are committed and pushed the moment a session settles, so the debounced committer and its flush phase go. Daemon commits bookkeeping to local main but never pushes it #1397's stranded bookkeeping dies with it.How writes work (
data-branch.ts): every local write goes through one serialized cycle — sync with origin, apply, commit, push. A lost push race re-syncs and re-applies; a failed push keeps the commit local and the next cycle carries it out. The daemon also pulls the branch eagerly (~1 min), so machines and cloud sessions converge. Reads work from anywhere: the checkout,git show the-framework_data:…, ororigin/…in a cloud clone.Prompts (@brillout — yours to review, see my comment): one new "The data branch" section on the system channel; all data-touching presets point at it. Two deliberate changes: drain agents no longer check their entry off (the daemon retires it), and a plan is now a data write, not a PR — plan review moves to the human queue.
Tested:
tickets/itself, and the lock write didn't create parents — every later drain claim failed silently and the sweep stood down with a wrong message. Fix + regression tests in5e3785b4; the whole retire loop is now also covered by three integration tests (1fa12a6b).Hand-run migration (zero migration code, per the no-compat rule) — after merge, on the main checkout:
git checkout --orphan the-framework_data && git rm -rf . && git commit --allow-empty -m "[The Framework] the-framework_data"), then work in.the-framework/branches/the-framework_data.tickets/**andTODO_AGENTS.mdinto the data checkout; move.the-framework/<user>/agents/**toagents/<user>/**; commit + push.tickets/,TODO_AGENTS.md, and the committed archives; add theticketssymlink and the simplified.the-framework/.gitignore.Closes #1582. Structurally closes #1577 and #1397.