docs/design/ecc-memory-vault.md
An operator can save, inspect, search, and hand off durable context through one
human-readable vault that Claude Code, Codex, Hermes, OpenCode, and other
harnesses can share. Project and team memories live under .ecc/memory/; user
memories live under ~/.ecc/memory/. The same ecc.memory.v1 documents are
available through the ecc memory CLI and an opt-in local stdio MCP server, so
knowledge transfer does not depend on email, one vendor's transcript format, or
one harness's hook support.
trust: "unreviewed" and cannot silently become rules, skills, or policy.scripts/, skills/,
and the MCP server rather than separate Claude/Codex/Hermes stores.The first-release runtime defends against hostile vault documents, stable
symlink/path escapes, accidental project-memory commits, cross-harness MCP
identity spoofing, known secret shapes, terminal control data, and bounded
resource exhaustion. Vault roots must remain writable only by the operator.
It is not a security boundary between concurrent processes running as the same
OS user: Node.js does not expose the directory-file-descriptor-relative
openat2 guarantees needed to eliminate every parent-directory swap race.
Operators who need protection from a malicious local process must use separate
OS accounts, containers, or equivalent filesystem isolation.
<repo>/.ecc/memory/
├── project/
│ ├── contexts/
│ ├── decisions/
│ ├── facts/
│ ├── handoffs/
│ ├── lessons/
│ ├── notes/
│ ├── preferences/
│ └── runbooks/
└── team/
└── <same kind directories>
~/.ecc/memory/
└── <same kind directories>
The project scope is repo-local operator context and receives its own
fail-closed .gitignore: initialization and writes stop if the protection file
exists with unexpected content. The team scope is intended to be inspected by
a human before it is committed, but committed vault entries remain unreviewed
context. The user scope follows the operator across repos and is recalled only
when explicitly requested.
ECC_MEMORY_PROJECT_ROOT and ECC_MEMORY_USER_ROOT may override the two vault
locations explicitly.
Each memory is a Markdown file with strict JSON-valued YAML frontmatter:
---
schema: "ecc.memory.v1"
id: "mem_20260726_01k123example"
title: "Authentication migration handoff"
kind: "handoff"
scope: "project"
trust: "unreviewed"
status: "active"
source_harness: "codex"
target_harnesses: ["claude"]
tags: ["auth", "migration"]
links: ["mem_20260725_01kolder"]
created_at: "2026-07-26T20:00:00.000Z"
updated_at: "2026-07-26T20:00:00.000Z"
---
The token rotation tests pass. The remaining task is ...
Required fields are schema, ID, title, kind, scope, trust, status, source
harness, targets, tags, links, and timestamps. IDs, kinds, tags, and harness
names use a bounded lowercase slug grammar. Bodies are bounded Markdown text.
Backlinks are derived from other documents' links fields.
tool save ──> active + unreviewed
│
├── human verifies evidence
│ └──> governed rule, decision record, runbook, or doc
│
└── new memory links with supersedes relation
└──> old item may be marked superseded manually
The initial runtime creates active, unreviewed memories only, and normal search
recall returns active entries only. A direct ID read may still retrieve a
non-active entry for inspection. Human review does not change a vault entry's
trust field; accepted knowledge is promoted into a governed repository
artifact. The runtime exposes no automated promotion transition. This is
intentional: a shell-capable agent cannot be treated as an independent human
approval boundary.
CLI:
ecc memory init [--scope project|team|user]
ecc memory save --title <text> [--body-file <path>|--stdin] [metadata flags]
ecc memory handoff --from <harness> --target <harness> --title <text> ...
ecc memory search <query> [--scope ...] [--target-harness ...] [--json]
ecc memory read <id> [--scope ...] [--json]
ecc memory doctor [--json]
MCP tools:
memory_save
memory_search
memory_read
memory_doctor
The CLI searches active project and team memories by default. user recall
requires an explicit --scope user. Its --target-harness option is a
caller-selected routing filter, not an authorization boundary.
The MCP server requires a lowercase ECC_MEMORY_HARNESS identity at launch.
That server-side identity supplies source_harness for writes and constrains
search/read to memories targeted to that harness or all; clients cannot
override it in tool arguments. MCP access to user scope is disabled unless
the operator also sets ECC_MEMORY_ALLOW_USER_SCOPE=1, after which the client
must still request that scope explicitly. MCP writes always produce unreviewed
documents. Structured errors omit stack traces and secret values.
doctor and excluded from search; it is
never deleted or rewritten automatically.The first release reports operation results only. Write acknowledgements omit the raw body and use a scope-relative vault path; only an explicit read returns the full body. A later event-sourced ECC2 projection may record content hashes and operation metadata, but it must not log raw memory bodies or credentials.
.context/ worktree handoffs should materialize from vault handoffs once
the conductor fork lifecycle is stable.The local file/CLI/MCP slice is implemented behind explicit CLI or MCP activation and covered by core, schema, CLI, protocol, packaging, and cross-harness tests. ECC2 graph sync, automatic session capture, semantic adapters, governed-reference recall, and event-log promotion belong in follow-up lanes after real-world retrieval evaluation.