1. Executive Summary
ECC is a large agent-harness toolkit — skills, hooks, instincts and security tooling for Claude Code, Codex, OpenCode and Cursor. This report covers only its memory vault, which is a small and unusually opinionated part of it.
The opinion is in the type system. trust is an enum with
exactly one member, unreviewed, and the
schema's own description explains why: "Vault memories remain
unreviewed context. Governed truth is promoted into a canonical…"
The skill documentation completes it — "Tool-created memories are
always trust: "unreviewed" and writes are create-only. In
the first release, all vault entries remain unreviewed: review promotes
verified knowledge into a governed project artifact rather than changing
memory frontmatter."
That is a coherent and rare epistemic architecture: the vault is permanently non-authoritative, and truth lives somewhere else. It is Aukora's "memory navigates, it does not authorize" expressed as a single-valued enum rather than a runtime banner, and it is the honest answer to a question most systems here dodge — if you cannot verify a memory, do not ship a field that implies you did.
The finding sits directly beside it. status is a
validated enum of active | rejected | superseded, and both
read paths filter it: memory-vault.js:602 and
:663 keep only status === 'active'. So the
retrieval half of a correction mechanism is implemented and working. The
write half is not — line 294 sets status: 'active' on every
create, and no code path anywhere sets rejected or
superseded. Because the vault is Markdown with
frontmatter, hand-editing a file would be honoured by the
filter, which makes rejection an operator gesture the tooling neither
performs nor documents.
Scope is real: project | team | user are separate vault
roots, each requiring a configured boundary policy, with
assertWithinTrustedRoot on every read — so a traversal out
of a scope is an exception rather than a leak.
2. Mental Model
A memory is a Markdown file whose frontmatter is a validated record:
id, title, kind,
scope, trust, status,
sourceHarness, targetHarnesses,
tags, links, createdAt,
updatedAt, body.
kind is the interesting axis —
context | decision | fact | handoff | lesson | note | preference | runbook
— because it separates things this atlas usually sees conflated. A
decision, a lesson and a runbook have different lifetimes and different
reasons to be retrieved, and handoff being a first-class
kind matches ai-memory's observation that an
interrupted task leaves behind a specific shape.
The state machine, as implemented:
flowchart TB
A["ecc memory add<br/>memory_save"] --> W["write id.md into<br/>the scope's vault root"]
W --> T["trust = unreviewed<br/><i>the only permitted value</i>"]
W --> S["status = active<br/><i>the only value ever written</i>"]
S --> IDX["index and search<br/><i>filtered to status === 'active'</i>"]
S -.-> REJ["rejected"]
S -.-> SUP["superseded"]
style REJ fill:#f4e2bd,stroke:#b8860b
style SUP fill:#f4e2bd,stroke:#b8860b
The dotted transitions are the finding: rejected and
superseded are validated by the schema and honoured
on the read path, and set by nothing. The vocabulary for
correction exists and no code reaches it, so the read path filters for a
state transition that never happens.
Writes are create-only. There is no update verb and
no delete verb; a memory is written once and read thereafter. Combined
with the single-valued trust, that makes the vault an
append-only pile of unreviewed context — which is a defensible thing to
be, and exactly what the documentation claims it is.
3. Architecture
JavaScript, MIT, a very large repository of which memory is one module. The vault itself is about 1,100 lines:
scripts/lib/memory-vault.js(778) — roots, boundary assertions, create, index, search.scripts/lib/memory-vault-format.js(309) — frontmatter parse and validate,MEMORY_TRUST_STATES,MEMORY_STATUSES.schemas/memory.schema.json(129) — the published record shape.scripts/memory.js(504) andscripts/memory-mcp.mjs— CLI and MCP surfaces.skills/unified-memory/SKILL.md— the agent-facing instructions.hooks/memory-persistence/— lifecycle hooks.
flowchart TB
CLI[ecc memory CLI] --> V[memory-vault]
MCP[memory_save MCP tool] --> V
HK[session hooks] --> V
V --> F[memory-vault-format<br/>validate every enum]
F --> P[(project root)]
F --> T[(team root)]
F --> U[(user root)]
P --> S[index / search<br/>status === 'active']
T --> S
U --> S
S --> A[Agent]
Deployment and ergonomics
- What has to run: nothing. Markdown files on disk and a Node CLI.
- Local and offline: entirely. No model is required to store or retrieve.
- No API key is required.
- Hand-repairable: completely — and this matters more here than usual, since editing frontmatter is the only way to reach two thirds of the state machine.
ensureProjectScopeIgnoredhandles the project-scope vault's relationship with git, so project memories are not committed by accident.
4. Essential Implementation Paths
Schema. schemas/memory.schema.json —
trust with enum: ["unreviewed"] and a
description stating the promotion-out-of-vault policy;
status with
enum: ["active","rejected","superseded"];
scope with ["project","team","user"];
kind with eight values.
Validation.
scripts/lib/memory-vault-format.js:17-18 freezes
MEMORY_TRUST_STATES = ['unreviewed'] and
MEMORY_STATUSES = ['active','rejected','superseded']; lines
185–186 run both through validateEnum, so a file with an
out-of-vocabulary value fails to load rather than flowing through as an
unchecked string.
Write. scripts/lib/memory-vault.js:294
— the created record carries trust: 'unreviewed' and
status: 'active'. Line 319 writes
${memory.id}.md into the scope directory.
Scope boundary.
assertMemoryRootSafe(roots, scope) (97) requires both a
configured root and a configured boundary policy for the scope,
throwing if either is missing.
assertWithinTrustedRoot(filePath, options.trustedRoot, …)
runs on reads (131, 152). Scope enforced by path containment rather than
by a query predicate.
Read filter. memory-vault.js:602 —
.filter(({ memory }) => memory.status === 'active');
:663 —
.filter(entry => entry.memory.status === 'active').
Agent surface.
skills/unified-memory/SKILL.md,
scripts/memory-mcp.mjs (memory_save),
scripts/memory.js (ecc memory add,
handoff, search).
Tests. tests/scripts/memory.test.js,
memory-mcp.test.js,
tests/ci/unified-memory-surface.test.js,
tests/hooks/observer-memory.test.js.
5. Memory Data Model
The frontmatter is the model, and two fields carry the design.
trust: ['unreviewed'] — a single-member
enum, and a statement rather than a stub: the schema description and the
skill documentation both say verified knowledge leaves the vault for a
governed artifact instead of being upgraded in place. A field that can
hold one value is normally a smell; here it is a contract that the store
will never claim authority.
status: [active, rejected, superseded]
— validated, filtered on read, and written only as active.
Two of the three values are unreachable through the CLI and the MCP
tool.
sourceHarness and
targetHarnesses deserve a mention. A memory
records which harness produced it and which are meant to consume it, so
in a toolkit whose premise is one developer moving between four agent
tools, provenance becomes routing. That is the cleanest expression of
cross-harness memory here after ai-memory's
handoffs.
Missing: no validity interval (not bi-temporal), no confidence, no
evidence link beyond a free links array, and no
tombstone.
6. Retrieval Mechanics
An index over the vault plus lexical search, scope-selected by root and filtered to active. No embedding model appears in the memory path read here, which is consistent with a store meant to hold a few hundred deliberate notes rather than a conversation history.
The status filter is well placed: it runs in both the index and the search path rather than one of them, which is the mistake that usually makes a status field decorative. The problem is upstream — nothing produces a non-active status for it to exclude.
7. Write Mechanics
Create-only, model-free and validated.
ecc memory add or memory_save constructs a
record, the format layer validates every enum, and the file lands in the
scope root. There is no extraction, no consolidation, no deduplication
and no background rewriting.
That combination — create-only plus permanently-unreviewed trust —
means the vault cannot overstate its own reliability. It also means it
cannot correct itself: a wrong decision memory stays active
and retrievable until a human opens the file.
Operational cost
- Writes are a file write. No model, no network.
- Lag before a memory is retrievable: none beyond reindexing.
- No background pass rewrites the store.
- On the read path the vault bounds nothing; the caller decides what to inject.
8. Agent Integration
Three surfaces over one vault: a CLI, an MCP tool
(memory_save), and harness skills installed for Claude
Code, Codex, OpenCode and Cursor, plus session hooks under
hooks/memory-persistence/.
The agent can create and read. It cannot update, delete, reject or
supersede, because those verbs do not exist — a narrower grant than any
framework contract in this atlas and, given
trust: unreviewed, an internally consistent one. A store
that never claims authority does not need the agent to arbitrate it.
9. Reliability, Safety, and Trust
The trust design is the strength, and it is a design rather
than a mechanism. Pinning trust to
unreviewed and documenting that promotion happens outside
the vault is the clearest statement in this atlas that a memory store is
not a truth store. Most systems here imply authority with a
confidence float nothing revises; this one declines to
imply it at all. trust_state is nonetheless
withheld: a field with one possible value cannot
distinguish candidate from verified from rejected, which is what the
mark requires.
The status filter is a working read path over an unreachable
state. This is the atlas's recurring shape and a clean instance
of it. rejected and superseded are validated
by the schema, honoured by both read paths, and set by nothing. The
near-miss is genuinely near: writing status: rejected into
a file's frontmatter by hand works at this commit and removes that
memory from retrieval. The mechanism exists end to end except for a verb
— and nothing in the CLI, the MCP tool or the skill mentions that the
file can be edited to that effect.
Scope is enforced by containment. Separate roots per
scope, a required boundary policy per scope, and
assertWithinTrustedRoot on reads. A path traversal is an
exception rather than a cross-scope read, which is a stronger guarantee
than a WHERE clause and earns
scope_enforced.
No mutation audit. Files are created and never updated, so the file tree is its own history in the way a git log is — by this atlas's rule a different mechanism, noted rather than marked.
No human review surface, by explicit design: review happens outside the vault in the first release.
10. Tests, Evals, and Benchmarks
Four memory-specific test files inside a large repository-wide suite:
tests/scripts/memory.test.js,
memory-mcp.test.js,
tests/ci/unified-memory-surface.test.js and
tests/hooks/observer-memory.test.js. The surface test is
the notable one — asserting the shape of the unified memory API is what
stops four harness integrations drifting apart.
No memory benchmarks, and none would be meaningful for a lexical vault of deliberate notes.
No test asserts that particular material must not be retrieved, so
negative_eval is withheld — though the cheapest valuable
test here is obvious: write a file with status: rejected
and assert search does not return it. It would pin the one mechanism
that has no other way to be exercised.
11. For Your Own Build
Steal
- Pin
trustto a single value if your store cannot verify anything, and say so in the schema. More honest than a confidence float nothing revises, and it tells every consumer exactly how much weight to give a hit. - Promote verified knowledge out of the vault rather than upgrading it in place. Separating "things noticed" from "things decided" into different artifacts avoids a memory store having to arbitrate truth — and it is why this design can get away with create-only writes.
- Record
sourceHarnessandtargetHarnesses. When one person uses four agent tools, provenance becomes routing, and a note that knows where it should be read is worth more than one that does not. - Enforce scope by path containment. Separate roots plus an assertion that a resolved path lies inside the expected one turns a scope leak into an exception.
- Validate every enum at load. A frontmatter value outside the vocabulary should fail the read, not flow through as a string nobody checks.
Avoid
- Filtering on a status your write path cannot produce. It reads as a correction mechanism in review and does nothing in practice. Either implement the verb or drop the values from the enum so the gap is visible.
- Create-only as a permanent position. Fine for a
first release; it means a wrong
decisionmemory is retrievable forever unless someone edits a file. If the vault is genuinely append-only, the read path needs a way to demote — which is whatstatuswas for.
Fit
This suits a developer who moves between several agent harnesses and wants one Markdown vault of deliberate notes all of them can read. That is a real problem and a well-judged answer to it: the kind vocabulary, the harness routing and the honesty about trust are all worth having, and the whole thing is files you own.
It is not a memory system in the sense most of this atlas is. Nothing is extracted, nothing is consolidated, nothing is corrected, and the store is explicitly not authoritative. Treat it as a shared notebook with a schema, and expect to open a text editor when something in it turns out to be wrong.
12. Open Questions
- Is anything intended to set
status: rejected? The enum, the validator and both read filters exist; the verb does not. Whether that is a planned release or an abandoned direction is not stated anywhere read here. - What is the "governed project artifact" that verified knowledge is promoted into, and does anything link a vault memory to its promoted form? The documentation names the policy without naming the destination.
- Does the index honour a hand-edited
statusimmediately, or is there a cache that would keep a rejected memory retrievable until reindex? - Do
targetHarnessesaffect retrieval? The field is recorded; whether any read path filters on it was not determined. - What do the per-scope boundary policies look like in a real deployment, particularly for team scope?
Appendix: File Index
Schema and validation
schemas/memory.schema.jsonscripts/lib/memory-vault-format.js—MEMORY_TRUST_STATES(17),MEMORY_STATUSES(18),validateEnum(185–186)
Vault
scripts/lib/memory-vault.js—assertMemoryRootSafe(97),ensureProjectScopeIgnored(229), create (294), write (319), active filters (602, 663)
Surfaces
scripts/memory.js— CLIscripts/memory-mcp.mjs—memory_saveskills/unified-memory/SKILL.mdhooks/memory-persistence/
Tests
tests/scripts/memory.test.js,tests/scripts/memory-mcp.test.jstests/ci/unified-memory-surface.test.jstests/hooks/observer-memory.test.js