The arithmetic
MemorySystem.md § Directory Inventory is the authoritative table and
MemoryDirIntegrity.ts enforces it: an active row with no directory on disk is
drift (:199). The payload ships no MEMORY/ tree — git ls-tree returns zero
MEMORY/ paths at v7.28.3 — and guided setup's DeployCore.ts:158 scaffolds
exactly six directories:
MEMORY_SUBDIRS = ["WORK", "KNOWLEDGE", "LEARNING", "STATE", "OBSERVABILITY", "SKILLS"]
The table carries 20 active rows. Six of them are those six. So a fresh guided
install gets 14 drift warnings at its first SessionEnd, and at every SessionEnd
after it. That is down from 19 at the previous release, which is real progress —
the on-demand status added this release is the right idea.
Six of the 14 cannot be cleared by anything the operator does. TEAMS/, PLANS/,
REFERENCE/, SCRATCHPAD/, PROJECT/ and ARCHIVE/ are written by no shipped
code at all: zero hits across hooks/, LIFEOS/TOOLS/ and LIFEOS/PULSE/. The
other eight self-create only when a specific trigger fires — a StopFailure for
SECURITY/, a manual ActivityParser --generate for SYSTEMUPDATES/, a
cross-vendor audit for VERIFICATION/ — most of which never fire on a stock
install.
The row that stopped parsing
RELATIONSHIP/ (:283) now reads:
| `RELATIONSHIP/` | core | dormant archive | Daily principal↔DA interaction notes… | (none — read-only archive) |
The checker's row pattern is MemoryDirIntegrity.ts:103:
const rowRegex = /^\|\s*`([\w_]+)\/?`\s*\|\s*([\w-]+)\s*\|\s*([\w-]+)\s*\|/gm;
[\w-]+ cannot match a space. Running that exact regex against that exact
section yields 27 rows out of the 29 written. RELATIONSHIP/ is not one of
them — and neither is the generic _<skill>/ row at :300, for a different
reason (< is outside [\w_]).
A row that fails to parse is not silenced in one direction. It is removed from the
inventory, so:
- Direction 2 never checks it — which is the intended outcome, reached by
accident rather than by the status value.
- Direction 1 now fires on it.
:187-192 reports any on-disk directory with
no inventory row as unknown_on_disk, telling the operator to "add a row or
remove the directory" — for a directory that has a row. Every install that has
been running since v6 has MEMORY/RELATIONSHIP/ on disk. Those installs did not
lose a warning at this release; they traded it for a different one that is
harder to act on.
Independent of the space: enforcement is row.status === 'active', with no
allow-list and no else branch, so any unrecognised value passes silently. The
type declaration still says the vocabulary is two values (:57 —
status: string; // "active" | "reserved") while the table uses four, and the
doc's own § Drift Detection prose (:679) describes the same stale pair. A typo
and a deliberate exemption are indistinguishable.
The reader is still live
This is the part I would most like you to look at. RELATIONSHIP/ was moved to a
silent status while a shipped reader still consumes it, and no empty state was
added:
LoadContext.hook.ts:91 — loadRelationshipContext() still defined
:104-109 — still reads MEMORY/RELATIONSHIP/<YYYY-MM>/<date>.md, today and
yesterday
:413-414 — still gated on the knob and still called
settings.system.json:368 (both copies) — relationshipContext: true, and
isDynamicEnabled (:64-68) defaults to enabled even when the key is absent
:131 — if (parts.length === 0) return null; is the entire no-data path. No
section, no message, not even a log line
LoadContext.hook.ts is registered at hooks.json:309 on SessionStart. So on
every public install, every session, this function runs, reads a tree nothing has
ever written there, returns null, and says nothing. Its silence is
indistinguishable from it working.
The same document knows the reader exists. MemorySystem.md:539 says the tree is
"Surfaced at session start by LoadContext.hook.ts (Recent Relationship Notes
block)", and :976 lists LoadContext (recent notes block) as its loader — while
the inventory row two hundred lines earlier calls it a read-only archive with no
participants.
Rows that describe an instance rather than the product
Several writer cells name components that exist in no shipped file:
| Row |
Writer cell names |
Implementations in the payload |
SECURITY/ |
SecurityPipeline.hook.ts |
0 (8 files reference it) |
TEAMS/ |
TeammateIdle |
0 (5 files reference it) |
OBSERVABILITY/ |
TeammateIdle, observability-transport, HomeSensorDetector, Speedtest |
0 each |
SKILLS/ |
ShadowRelease (test-shadow-release) |
0 (see the companion issue on private references) |
Two of those go the other way and matter more, because getting them wrong would
silence live subsystems:
SECURITY/ does have a shipped writer — EventLogger.hook.ts:336-356
self-mkdirs and writes SECURITY/<YYYY>/<MM>/stop-failures-*.jsonl. The row
names the wrong component, not a phantom directory. The same document already
records the true writer at :640, one line above the false one at :642.
SYSTEMUPDATES/ does have shipped writers — ActivityParser.ts:634-644
self-mkdirs, and CreateUpdate.ts graduated into the public tree this release
at LIFEOS/TOOLS/CreateUpdate.ts with IntegrityMaintenance.ts:114 re-pointed
at it. That row is correct at head.
The provenance is stated in the table's own changelog. :831 still reads
"Documented all live core subsystems that had grown organically since v7.x", and
the surrounding entries date releases that never shipped: :617 says the firehose
role moved "in v7.0 (2026-01-12)" while public v7.0.0 is tagged 2026-07-11;
:613 cites "v7.4 (2026-03-31)", and no v7.4 tag has ever existed. The dates,
not the version numbers, are what make this legible: the changelog is a private
instance's migration log, published as public documentation.
What I propose
The Status column is the right axis and you already chose it. The problem is that
it is not enforced. PR-B keeps your column and makes it mean something:
active — guided setup creates it. Absent afterwards is drift → warn.
on-demand — created on first use, by a shipped writer that self-creates it
or by a hand or agent write. Absence is normal → silent.
reserved — nothing creates it and nothing reads it → silent, reason
required.
pending — a shipped reader still consumes it and has no empty state yet.
Absence keeps warning; the warning is the debt.
Anything else warns. Silence is only ever granted by an explicit recognised
value, so a typo, a value with a space in it, and a row added before this
vocabulary existed are all reported instead of exempted. Cells are read
positionally rather than by one row-wide pattern, so an unclassifiable row is
reported as itself rather than vanishing — which also restores the
unknown_on_disk coverage a vanished row loses.
The trap this design exists to avoid is the one the release walked into:
reclassifying is a way to silence a checker, and on-demand silences exactly as
effectively as reserved. So PR-B ships a rule — a row may not be moved to a
status whose absence is silent while a shipped component still reads the
directory, unless the same change gives that reader a visible empty state — and
then obeys it. WISDOM/ and RELATIONSHIP/ are pending, which still warns.
Each is cleared by the change that gives its reader an empty state, not before.
Net on a fresh guided install after setup: 14 permanent warnings → 2, and an
upgraded install that already has both trees on disk sees none. DeployCore
is unchanged on purpose: scaffolding directories whose writers do not ship would
quiet the checker by faking disk.
PR-B implements this and is linked below.
Questions I could not answer from the public tree
- (Q-d) Per-row disposition for rows with no shipped writer: which are
intended to ship eventually (keep reserved with a reason) and which are
private-forever (delete the row)? My reading is TEAMS/ is the latter, but that
is your call and PR-B takes the conservative option.
- (Q-e) Should the checker learn "was written, then vanished" for
on-demand
rows? That is the one blindness this design accepts. It needs a state file and a
new failure class for a rare event, so PR-B stays stateless and asks.
- (Q-f)
RELATIONSHIP/ specifically: retire the reader, or restore a writer?
PR-B does not decide it — the row stays pending either way. The companion PR
retires the reader, and its body says plainly that the opposite fix is one word
away.
The arithmetic
MemorySystem.md§ Directory Inventory is the authoritative table andMemoryDirIntegrity.tsenforces it: anactiverow with no directory on disk isdrift (
:199). The payload ships noMEMORY/tree —git ls-treereturns zeroMEMORY/paths at v7.28.3 — and guided setup'sDeployCore.ts:158scaffoldsexactly six directories:
The table carries 20
activerows. Six of them are those six. So a fresh guidedinstall gets 14 drift warnings at its first SessionEnd, and at every SessionEnd
after it. That is down from 19 at the previous release, which is real progress —
the
on-demandstatus added this release is the right idea.Six of the 14 cannot be cleared by anything the operator does.
TEAMS/,PLANS/,REFERENCE/,SCRATCHPAD/,PROJECT/andARCHIVE/are written by no shippedcode at all: zero hits across
hooks/,LIFEOS/TOOLS/andLIFEOS/PULSE/. Theother eight self-create only when a specific trigger fires — a StopFailure for
SECURITY/, a manualActivityParser --generateforSYSTEMUPDATES/, across-vendor audit for
VERIFICATION/— most of which never fire on a stockinstall.
The row that stopped parsing
RELATIONSHIP/(:283) now reads:The checker's row pattern is
MemoryDirIntegrity.ts:103:[\w-]+cannot match a space. Running that exact regex against that exactsection yields 27 rows out of the 29 written.
RELATIONSHIP/is not one ofthem — and neither is the generic
_<skill>/row at:300, for a differentreason (
<is outside[\w_]).A row that fails to parse is not silenced in one direction. It is removed from the
inventory, so:
accident rather than by the status value.
:187-192reports any on-disk directory withno inventory row as
unknown_on_disk, telling the operator to "add a row orremove the directory" — for a directory that has a row. Every install that has
been running since v6 has
MEMORY/RELATIONSHIP/on disk. Those installs did notlose a warning at this release; they traded it for a different one that is
harder to act on.
Independent of the space: enforcement is
row.status === 'active', with noallow-list and no else branch, so any unrecognised value passes silently. The
type declaration still says the vocabulary is two values (
:57—status: string; // "active" | "reserved") while the table uses four, and thedoc's own § Drift Detection prose (
:679) describes the same stale pair. A typoand a deliberate exemption are indistinguishable.
The reader is still live
This is the part I would most like you to look at.
RELATIONSHIP/was moved to asilent status while a shipped reader still consumes it, and no empty state was
added:
LoadContext.hook.ts:91—loadRelationshipContext()still defined:104-109— still readsMEMORY/RELATIONSHIP/<YYYY-MM>/<date>.md, today andyesterday
:413-414— still gated on the knob and still calledsettings.system.json:368(both copies) —relationshipContext: true, andisDynamicEnabled(:64-68) defaults to enabled even when the key is absent:131—if (parts.length === 0) return null;is the entire no-data path. Nosection, no message, not even a log line
LoadContext.hook.tsis registered athooks.json:309on SessionStart. So onevery public install, every session, this function runs, reads a tree nothing has
ever written there, returns null, and says nothing. Its silence is
indistinguishable from it working.
The same document knows the reader exists.
MemorySystem.md:539says the tree is"Surfaced at session start by
LoadContext.hook.ts(Recent Relationship Notesblock)", and
:976listsLoadContext (recent notes block)as its loader — whilethe inventory row two hundred lines earlier calls it a read-only archive with no
participants.
Rows that describe an instance rather than the product
Several writer cells name components that exist in no shipped file:
SECURITY/SecurityPipeline.hook.tsTEAMS/TeammateIdleOBSERVABILITY/TeammateIdle,observability-transport,HomeSensorDetector,SpeedtestSKILLS/ShadowRelease (test-shadow-release)Two of those go the other way and matter more, because getting them wrong would
silence live subsystems:
SECURITY/does have a shipped writer —EventLogger.hook.ts:336-356self-mkdirs and writes
SECURITY/<YYYY>/<MM>/stop-failures-*.jsonl. The rownames the wrong component, not a phantom directory. The same document already
records the true writer at
:640, one line above the false one at:642.SYSTEMUPDATES/does have shipped writers —ActivityParser.ts:634-644self-mkdirs, and
CreateUpdate.tsgraduated into the public tree this releaseat
LIFEOS/TOOLS/CreateUpdate.tswithIntegrityMaintenance.ts:114re-pointedat it. That row is correct at head.
The provenance is stated in the table's own changelog.
:831still reads"Documented all live core subsystems that had grown organically since v7.x", and
the surrounding entries date releases that never shipped:
:617says the firehoserole moved "in v7.0 (2026-01-12)" while public
v7.0.0is tagged 2026-07-11;:613cites "v7.4 (2026-03-31)", and nov7.4tag has ever existed. The dates,not the version numbers, are what make this legible: the changelog is a private
instance's migration log, published as public documentation.
What I propose
The Status column is the right axis and you already chose it. The problem is that
it is not enforced. PR-B keeps your column and makes it mean something:
active— guided setup creates it. Absent afterwards is drift → warn.on-demand— created on first use, by a shipped writer that self-creates itor by a hand or agent write. Absence is normal → silent.
reserved— nothing creates it and nothing reads it → silent, reasonrequired.
pending— a shipped reader still consumes it and has no empty state yet.Absence keeps warning; the warning is the debt.
Anything else warns. Silence is only ever granted by an explicit recognised
value, so a typo, a value with a space in it, and a row added before this
vocabulary existed are all reported instead of exempted. Cells are read
positionally rather than by one row-wide pattern, so an unclassifiable row is
reported as itself rather than vanishing — which also restores the
unknown_on_diskcoverage a vanished row loses.The trap this design exists to avoid is the one the release walked into:
reclassifying is a way to silence a checker, and
on-demandsilences exactly aseffectively as
reserved. So PR-B ships a rule — a row may not be moved to astatus whose absence is silent while a shipped component still reads the
directory, unless the same change gives that reader a visible empty state — and
then obeys it.
WISDOM/andRELATIONSHIP/arepending, which still warns.Each is cleared by the change that gives its reader an empty state, not before.
Net on a fresh guided install after setup: 14 permanent warnings → 2, and an
upgraded install that already has both trees on disk sees none.
DeployCoreis unchanged on purpose: scaffolding directories whose writers do not ship would
quiet the checker by faking disk.
PR-B implements this and is linked below.
Questions I could not answer from the public tree
intended to ship eventually (keep
reservedwith a reason) and which areprivate-forever (delete the row)? My reading is
TEAMS/is the latter, but thatis your call and PR-B takes the conservative option.
on-demandrows? That is the one blindness this design accepts. It needs a state file and a
new failure class for a rare event, so PR-B stays stateless and asks.
RELATIONSHIP/specifically: retire the reader, or restore a writer?PR-B does not decide it — the row stays
pendingeither way. The companion PRretires the reader, and its body says plainly that the opposite fix is one word
away.