Skip to content

The Directory Inventory silenced a row by making it unparseable, and a live SessionStart reader still consumes that directory #1757

Description

@anikinsasha

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:

  1. Direction 2 never checks it — which is the intended outcome, reached by
    accident rather than by the status value.
  2. 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:91loadRelationshipContext() 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
  • :131if (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions