v4.39.0 — merge-scale memory: one Open Thread per file
Highlights
- Merge conflicts in shared memory are now removed structurally, not resolved. As team
adoption grew,memory/continuity.mdmerge conflicts became regular business,
concentrated in## Open Threads(multi-line blocks appended and edited at one anchor)
and thelast_sessionscalar (bumped by nearly every session). This release takes the
conflict surfaces away instead of automating their resolution — no merge-time machinery,
no per-clone registration, nothing to keep in sync. Design record:
docs/DESIGN-merge-scale.md. - One thread, one file. Each Open Thread lives in its own
memory/open-threads/thread-<id>.md— filename = the thread's fact id (stable for its
lifetime), content = exactly the bullet block that previously sat in continuity. Parallel
work on different threads cannot conflict (different files). Both sides editing the
same thread still conflicts per-file — that is theMERGE.mdTier 2 human gate,
preserved by design and pinned by a mutation-checked test: a clean merge there would
be a silent fork, the exact failure thenever-pick-a-winnerinvariant forbids. - The directory is the index, like
sessions/: no index file (an index line per thread
would recreate the add/add conflict one line at a time).ls memory/open-threads/lists
the threads; the checkbox in each file is its state. last_sessionis gone — it was fully derivable (newestsessions/filename; the
agent name is that log's**Agent:**header) and the most frequent scalar conflict.- Archive files union-merge (
memory/archive/*.md merge=unionin.gitattributes,
git-native): concurrent review sweeps' appends keep both sides; the one wrong case — a
reactivation's removal resurrected — is caught deterministically by the existing
[both]/[over-archived]ERRORs. - With credit to Roland Heusser (#27): his field artifact (two stacked branches, both
appending a thread and bumpinglast_session) motivated the shape of this release, his
[duplicate-state-key]check ships essentially as written, and his merge-rehearsal test
pattern pins the new layout's behavior. The PR's merge driver itself was closed after
review findings (verified against the branch; recorded in the design doc), with a
scalars-only driver pre-scoped as the follow-up if scalar-bump conflicts persist.
What's changed
- Layout:
memory/open-threads/(onethread-<id>.mdper Open Thread); continuity's
## Open Threadssection is now a pointer note;last_sessionremoved from Project
State;memory/archive/*.md merge=unionin.gitattributes; reviews run serialized
(REVIEW.md: up-to-date default branch, committed promptly). - Scripts (all three built-in pairs, python + node at byte parity):
memory-lint
treats thread files as live fact surfaces (footers, checkbox pinning, conflict-marker and
secret scans, closed-thread bloat) and gains three checks —[thread-file](one block
per file, filename = footer id),[duplicate-id](an id exists exactly once across the
live layer — the silent-fork backstop for same-id creation collisions), and
[duplicate-state-key](a Project State scalar set twice).refresh-metadatarefreshes
thread-file footers in place (filenames never churn).archive-factsweeps a thread by
moving its block to the quarter archive +INDEX.mdand deleting the file. - Contract test:
tests/test_thread_layout_merge.sh— four real-git-merge cases:
different-threads merge clean; same-thread edits conflict (the load-bearing case);
archive union keeps both sides; and a mutation check (stripping the union attribute must
restore the conflict). - Lockstep:
DECAY.md§1/§12,REVIEW.md,MERGE.md,.agent/schema.md(new
memory/open-threads/section),ENABLE.md5b/5c/7b,MIGRATE.md§D, both
memory/PROTOCOL.mdcopies (activation reads the thread files; the close ritual creates
them; nolast_session), all four bootstrap "still scan" notes, templates,MANIFEST.md
(new row + two semantic steps, including the mandated protocol-propagation row),
UPGRADE.mdladder row +4.38.1 → 4.39.0rung, CHANGELOG, README, example fixtures
annotated (real fixtures left unfalsified), mkdocs nav. - Dogfooded: this repo's own 35 threads migrated (23 verbatim extractions + 12 legacy
items given minted ids per the 3.0.0 backfill rule); continuity shrank 387 → ~130 lines. - Suites: memory-lint 67 ×2, refresh-metadata 6 ×2, archive-fact 9 ×2, reconcile 38 ×2
--check-manifestOK; live-tree lint 0 errors / 0 warnings, byte-identical across
runtimes.
Upgrading an enabled repo
Say "AI enable this repo" as usual — Mode B reconciles against MANIFEST.md (doc
re-copies, the three updated skill dirs, the .gitattributes union rule via
sentinel-merge, the memory/open-threads/ seed), then two semantic steps do the judgment
half: (1) thread migration — every Open Thread block moves verbatim out of
continuity.md into its own thread-<id>.md (a legacy thread with no footer gets one
minted first), the section body becomes the pointer note, and the last_session line is
deleted; (2) protocol re-copy/arbitrate — a still-stock memory/PROTOCOL.md is
re-copied, a customized one arbitrated per ENABLE.md §5i. memory-lint gates the
migration: [thread-file] and [duplicate-id] fail on a mis-named file or an id left
behind. Full steps: the 4.38.1 → 4.39.0 rung in UPGRADE.md.
Full details
CHANGELOG.md— Version 4.39.0docs/DESIGN-merge-scale.md— the design record, including the evaluated-and-rejected
merge-driver alternative and what was deliberately deferredUPGRADE.md— the4.38.1 → 4.39.0rung