Development record
Durable memory writes — single-writer, atomic-rename crash model
Context
abcd's memory store is built on markdown files (.abcd/memory/index.md,
log.md, per-page files, .sources_index.json). A near-ACID design — a
transaction journal, pending markers, crash-recovery reconciliation, lock
ordering — is only warranted if concurrent writers can race and torn writes can
occur. Neither premise holds for this store, so its crash-consistency model is a
deliberate choice rather than a default to the heaviest option.
Decision
The memory store uses a single-writer, atomic-rename crash model (a Go implementation):
- Single-writer to the memory store. The memory writer touches only
.abcd/memory/**. Concurrent activities — a facilitator refining.abcd/development/brief/**, a product thinker amending.abcd/intents/**, issue capture into the.abcd/work/issues/ledger — write disjoint file trees and never mutate the memory store. There is no real concurrent-writer scenario against.abcd/memory/**.
- Atomic-rename only. Each file write is write-temp-then-
rename(). A crash leaves either the complete old or complete new file, never a torn file. No transaction journal, nopendingmarkers, no lock-ordering protocol. (No mid-write corruption has ever been observed in the memory files — the empirical failure rate of what a transaction model would defend against is ~zero.)
- No explicit crash-recovery. With atomic rename there is no torn write to heal; the only residue is cross-file staleness, healed by the idempotent sibling-reconciliation that already runs before every mutating write. Bare / read-only commands stay strictly non-mutating and merely report drift ("index stale; run an ingest"). This dissolves the recovery-vs-non-mutation tension at the root rather than patching it.
- Write and lint are separate concerns. The memory write core —
registry/index (atomic-rename), ingest/atomic-promotion, the
principle-distillerprompt, launch consumer and docs — is the part that stores. The memory quality/lint — the MS/ML/MQ/SD constraint validation, citations, provenance budgets, thelintcommand — is the part that audits. They are built and reasoned about independently, so the actually-blocking write core can land ahead of the audit surface.
Review of the memory design is host-delegated (adr-25): abcd hands the design and its prompts to the host's subagent dispatch and consumes the structured verdict, rather than owning a fixed multi-backend review pipeline.
Consequences
- The entire class of concurrency objection (lost-update races, transaction
ownership of
pendingmarkers, recovery-mutation colliding with the non-mutating-read contract) is removed by construction, not argued down. - The write core is independent of the lint surface and can ship first.
- The atomic-rename model keeps the store's durability guarantee cheap: one
rename()per file, no journal to replay, no recovery pass to schedule.
Related Documentation
Frontmatter
| id | adr-13 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-06-09 |
.abcd/development/decisions/adrs/0013-fn38-memory-single-writer-and-write-lint.md
open on GitHub ↗ · commit history ↗
References2
mentions2
- dcn-26082100000000042026-08-21 — Bug-hunt round 6, branch bughunt-b/round-6. Baseline (make preflight
- iss-2609012037430191GHSA-xj89-cc2c-wgwr (CWE-313): memory lint (memoryLinter.run is source classes + licence + quotation) imports no scanner, so a page whose frontmatter or body already holds a PAT or a home path yields zero findings and a clean exit, and fileBackSource deep-copies citation and licence from every matched page onto the file-back page, so a contaminated store propagates itself on ask --file-back (reproduced at v0.7.0: lint --json reports one MS001 info and blockers 0 over a page carrying a PAT in citation.title, recall and the body). The fix must add a blocker-severity MR001 finding built from the store redactor (BlockingResidual over the scan plus the literal-home backstop) over every stored page, .sources_index.json and each text kept-original under sources/ — report, never mutate, per adr-13 — carrying the kind and the line but never the span in the message or the report files, emitting a blocker MR001 naming the store when the scanner is degraded rather than aborting, and the file-back clone must land redacted through the frontmatter walk.