# echo-memory — v0.7 Persistent memory for Claude / CoWork sessions via the **ECHO** Obsidian vault, driven over the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api). No MCP server — the skill makes direct REST calls, now through a bundled validated client (`scripts/echo.sh`). Built for **Jason Stedwell** (Director of Technical Services / Systems Engineer, MPM / ALABAMA wISP), who is both the **operator** and the **architect** of this vault. This is a personal plugin, not for distribution. This repository (`jason/echo-v.05`) holds the plugin **source** (tracked tree at `echo-memory.plugin.src/`), the built `echo-memory.plugin` package artifact (rebuilt on each version bump), and a credential-free A/B `eval/` harness. **0.7 in one line:** the prose-and-raw-curl skill grew an executable spine — a status-checking API client, a machine-readable routing manifest the linter enforces, deterministic bootstrap/migrate scripts, an advisory multi-writer lock, four slash commands, and an eval harness. See the [0.7.0 version-history entry](#version-history) for the full list. --- ## Core design principle — the plugin is the single source of truth Everything that defines how ECHO behaves — bootstrap/repair logic, the operating contract, the taxonomy, frontmatter conventions, the routing map, and the canonical note templates — ships **inside the plugin** under `skills/echo-memory/`. The vault itself holds **data only**. There are no `CLAUDE.md` / `BOOTSTRAP.md` / `STRUCTURE.md` / `index.md` control docs in it. The single in-vault control artifact is a one-line marker, `_agent/echo-vault.md`, that records the schema version. Three consequences follow: - **Self-bootstrapping** — point the REST API at an empty Obsidian vault and the plugin stands up the full folder tree, templates, anchors, and marker from its bundled `scaffold/`. - **Easy to update** — change behavior by updating the plugin, not by editing files scattered through the vault. - **Portable** — the same plugin brings any empty vault online identically; nothing essential depends on files pre-existing in the vault. --- ## Configuration The plugin is hardcoded for a single personal endpoint: - **Server:** `https://echoapi.alwisp.com` (reverse proxy → backend Obsidian Local REST API). This is the **only** valid endpoint — never use LAN addresses (`10.x`, `192.168.x`, `:27124`); those belong to retired memory systems. Override per-run with `ECHO_BASE`. - **Auth:** a bearer token stored in the skill / `references` and injected by `scripts/echo.sh`. The key lives only in the plugin — **never inside a vault note** (per the vault's own safety rules). Override with `ECHO_KEY`. - **TLS:** the endpoint presents a valid certificate, so `-k` is not needed. Vault paths are addressed **at the root** (e.g. `GET /vault/_agent/context/current-context.md`). Prefer `scripts/echo.sh` over raw `curl`: it injects auth, checks HTTP status (a failed write exits non-zero instead of looking like success), retries transient 5xx, verifies PUTs, and does idempotent appends. --- ## Requirements - Obsidian running on the backend with the Local REST API plugin enabled. - HTTPS reachability to `https://echoapi.alwisp.com` from the Claude / CoWork session environment. --- ## Repository layout ``` echo-v.05/ ├── echo-memory.plugin ← built, installable plugin (zip artifact, rebuilt on version bump) ├── echo-memory-.plugin ← versioned build artifacts (history) ├── eval/ ← credential-free A/B eval harness (0.6 vs 0.7); not bundled │ ├── mock_olrapi.py ← deterministic mock of the REST API + fault injection │ ├── run_eval.py ← orchestrator (runs the real echo.sh vs modeled raw curl) │ └── README.md └── echo-memory.plugin.src/ ← tracked source tree (the plugin) ├── .claude-plugin/plugin.json ← manifest (name, version, description) ├── README.md ← plugin-level README ├── commands/ ← slash commands: echo-load, echo-save, echo-triage, echo-health └── skills/echo-memory/ ├── SKILL.md ← operating procedure (authoritative) ├── references/ │ ├── operating-contract.md ← durable principles + safety rules + concurrency │ ├── bootstrap.md ← bootstrap / repair / migrate manifest │ ├── vault-layout.md ← canonical layout + frontmatter conventions │ ├── routing-map.md ← complete endpoint→logic routing map (human authority) │ ├── api-reference.md ← REST endpoint patterns + routing map │ └── session-log-template.md ├── scripts/ ← executable logic (NEW in 0.7) │ ├── echo.sh ← validated API client (auth, status-check, retry, verify, lock) │ ├── routing.json ← canonical machine-readable route manifest (linter enforces it) │ ├── vault-lint.sh ← read-only invariant checker (Vault Health) │ ├── bootstrap.sh ← deterministic, idempotent vault setup/repair │ └── migrate.sh ← deterministic schema migration (dry-run by default) └── scaffold/ ← verbatim files the bootstrap writes into the vault ├── echo-vault.md ← bootstrap marker ├── README.vault.md ← thin human signpost ├── anchors/ ← operator-preferences, current-context, inbox seeds └── templates/ ← 8 canonical note templates ``` **Division of responsibility:** `SKILL.md` owns day-to-day *procedure* (loading order, search-first, triage, scope switching, PATCH rules) and points at the bundled tooling. `references/operating-contract.md` owns the durable, client-independent *principles, safety rules, and concurrency model*. `scripts/routing.json` is the machine-readable source of truth for routing; `references/routing-map.md` is its human-readable authority. The other references are the canonical layout, API, and bootstrap specs. --- ## Bundled tooling (0.7) Executable logic ships under `skills/echo-memory/scripts/`; the agent prefers it over hand-built curl. | Tool | Purpose | |------|---------| | `echo.sh` | The validated API client. `get/ls/map/search/put/post/append/patch/fm/bump/delete/lock/unlock`. Injects auth, **checks HTTP status** (non-zero exit on ≥400 — a failed write can't masquerade as success), one bounded retry on transient 5xx/connection errors, read-back verify on PUT, idempotent `append` (read-before-POST), correct `::` heading targets. | | `routing.json` | The **canonical machine-readable** route manifest — one regex pattern per valid destination plus retired paths. The single source of truth for "what may be written where"; `vault-lint.sh` enforces it. | | `vault-lint.sh` | Read-only invariant checker (Vault Health). Real YAML parsing, clock injected via `ECHO_TODAY`, exits `3` if the vault isn't bootstrapped (instead of falsely reporting "clean"). | | `bootstrap.sh` | Deterministic, idempotent, probe-before-write vault setup/repair (`--dry-run` to preview). Resolves the scaffold relative to itself, so it works from any CWD. | | `migrate.sh` | Deterministic schema migration. Dry-run by default; destructive steps gated behind `--apply` and printed first. | ### Slash commands `/echo-load` (cold-start read), `/echo-save ` (route + persist), `/echo-triage` (drain the inbox), `/echo-health` (run the linter) — explicit, reproducible entry points to the procedures below. ### Concurrency (shared vault) ECHO is read/written by multiple clients (Claude Code **and** CoWork). Single-line files (`heartbeat/last-session.md`, `current-context.md::Scope`) and append targets (`inbox.md`, `## Agent Log`) assume one writer at a time. Before an overlapping burst of writes, take the cooperative advisory lock (`echo.sh lock ` → `_agent/locks/vault.lock`, TTL-reclaimable) and release it at session end. Idempotent append and status-checked writes are the second line of defense. --- ## Vault layout (data only, root-addressed) ``` /vault/ ├── README.md ← thin human signpost (NOT read for routing) ├── inbox/ │ ├── captures/ ← quick captures (inbox.md), date-prefixed lines │ ├── imports/ ← raw imported material │ └── processing-log/ ├── journal/ ← one append-only time-series stream; rollups are coarser journal entries (no separate reviews/ tree) │ ├── daily/ ← YYYY-MM-DD.md (has an "## Agent Log" section) │ ├── weekly/ ← YYYY-Www.md (ISO week, opt-in rollup) │ ├── monthly/ ← YYYY-MM.md (monthly rollup) │ ├── quarterly/ ← YYYY-Qn.md (manual / on request) │ ├── annual/ ← YYYY.md (manual / on request) │ └── templates/ ├── projects/ ← lifecycle: incubating → active → on-hold / archived │ ├── active/ incubating/ on-hold/ archived/ │ └── project-template.md ├── areas/ ← business / personal / learning / systems ├── resources/ │ ├── companies/ ← .md — organizations (clients, vendors, partners, employers) │ ├── concepts/ references/ meetings/ │ └── people/ ← .md ├── decisions/ │ ├── by-date/ ← YYYY-MM-DD-.md (ADR-style) — the canonical home │ └── decision-template.md (by-project/ is retired, not created) │ (reviews/ is retired — journal rollups live under journal/; vault-health under _agent/health/) └── _agent/ ├── echo-vault.md ← bootstrap marker: schema_version + date (plugin-owned probe) ├── context/ ← current-context.md and task bundles ├── memory/ │ ├── working/ ← transient, time-boxed │ ├── episodic/ ← what happened, when │ └── semantic/ ← durable facts / patterns (operator-preferences.md) ├── sessions/ ← YYYY-MM-DD-HHMM-.md ├── health/ ← YYYY-MM-vault-health.md (monthly self-maintenance audit) ├── templates/ ← canonical note templates (seeded from the plugin's scaffold/) ├── skills/ ← active / archived capability catalog ├── heartbeat/ ← last-session.md pointer (read at load, written at session end) └── locks/ ← vault.lock — cooperative advisory multi-writer lock (echo.sh lock/unlock) ``` ### Memory model | Layer | Path | What it holds | |-------|------|---------------| | Working | `_agent/memory/working/` | Transient, time-boxed state | | Episodic | `_agent/memory/episodic/` | What happened, when | | Semantic | `_agent/memory/semantic/` | Durable facts, patterns, preferences (`operator-preferences.md`) | | Context | `_agent/context/` | Task-focused reading lists + active scope | --- ## Operating procedures (logic) ### Cold-start loading Load memory at the start of any substantive conversation (anything beyond a single quick factual question). The cold-start reads are **issued in parallel** (one batch of 5–6 GETs): 1. `_agent/echo-vault.md` — the bootstrap marker. `404` → vault not set up (run bootstrap); `200` → check `schema_version` and migrate if older than the plugin's. 2. `_agent/memory/semantic/operator-preferences.md` — Jason's profile. 3. `_agent/context/current-context.md` — active scope + Scope History. 4. `_agent/heartbeat/last-session.md` then `_agent/sessions/` — read the heartbeat pointer first (O(1) jump to the latest log); fall back to the listing's ~5 most recent by reverse lexical sort. 5. `journal/daily/YYYY-MM-DD.md` — today's note (`404` is fine). 6. `inbox/captures/inbox.md` — inbox-depth probe for the load-time reconcile. After the batch, **reconcile**: if the inbox holds captures older than ~7 days, surface the count and offer to triage (so triage self-fires rather than waiting to be invoked); and if the active scope diverges from what Jason just asked for, run Scope Switching. If a specific project is in play, follow up with a `search/simple/` across **all** lifecycle subfolders, querying **both the slug and any human title** used in conversation, then read the match at `projects//.md`. Loading is not narrated to the operator. ### Inbox triage `inbox/captures/inbox.md` is the catch-all. At session start, GET it; if it holds lines older than ~7 days that were never routed, surface a count once and offer to triage. Accepted items route to their proper home (preference → operator-preferences; project idea → `projects/incubating/`; durable fact → semantic; person fact → `resources/people/`), with the move recorded one-line in `inbox/processing-log/YYYY-MM-DD.md` as an audit trail. Originals aren't deleted unless explicitly asked. ### Project lifecycle Projects move through four folders; the folder name and the `status:` frontmatter field **MUST agree** — they are two views of one state. A file in `projects/active/` with `status: on-hold` is broken state. | Folder | `status:` | Meaning | |--------|-----------|---------| | `incubating/` | `incubating` | Idea captured; not actively worked | | `active/` | `active` | Current work (default for anything in motion) | | `on-hold/` | `on-hold` | Paused but still tracked; resumable | | `archived/` | `archived` | Done, abandoned, or rolled up — not deleted | Promotion = move the file **and** update `status:` in the same change. ### Scope switching `_agent/context/current-context.md` tracks one active scope. When scope changes: (1) PATCH-prepend a dated bullet capturing the **prior** scope to `## Scope History`; (2) PATCH-replace `## Scope` with the new scope; (3) bump the frontmatter `updated:`. Scope History is trimmed to the last ~10 entries. ### Daily note — Agent Log After substantive activity, append a one-line entry to today's daily note's `## Agent Log`. The procedure is resilient: GET the note → if `404`, PUT it from the template → if `200` but the heading is missing, POST the heading. **Heading detection greps the raw markdown for an anchored `^## Agent Log`** — not the document-map JSON, whose headings are `::`-delimited paths (a bug fixed in 0.4.1 that previously appended duplicate headings). ### Session logging At the end of substantive conversations (those producing decisions, artifacts, or commitments), write a session log to `_agent/sessions/YYYY-MM-DD-HHMM-.md`. **The four-digit HHMM component is canonical, not optional** — it makes filenames lex-sort in true chronological order, which cold-start loading depends on. New writes are validated against `^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9-]+\.md$`. ### Journal rollups The journal is one append-only time-series stream; rollups are coarser-grained entries on the same timeline, so they live under `journal/` — there is no separate `reviews/` tree. - **Weekly** — opt-in; offered on the first substantive session of a new ISO week, written only if accepted, to `journal/weekly/YYYY-Www.md`. - **Monthly** — offered alongside the Vault Health pass on the first session of a calendar month, to `journal/monthly/YYYY-MM.md`. - **Quarterly / annual** — manual / on request only, to `journal/quarterly/YYYY-Qn.md` and `journal/annual/YYYY.md`. ### Vault Health (monthly) Agent self-maintenance (not a journal entry), written to `_agent/health/YYYY-MM-vault-health.md`. Run `scripts/vault-lint.sh` (or `/echo-health`) with `ECHO_TODAY` = the conversation's date so stale/aging math uses one clock. It mechanically asserts: folder↔status mismatch, duplicate slugs across lifecycle folders, wikilinks in frontmatter (swept across all folders), duplicate `## Agent Log` headings, stale active projects (`updated:` > 30 days), aging inbox items (> 14 days), **paths matching no route in `routing.json` or sitting at a retired path**, and **frontmatter integrity** (missing required fields, `updated` < `created`, future dates, wikilinks leaking into `source_notes`). Exit codes: `0` clean · `1` violations · `2` unreachable · `3` not bootstrapped. Findings are reported, not auto-fixed. --- ## Write-safety rules - **Search first (mandatory for new notes).** Before creating any slug-addressed note, `POST /search/simple/?query=` across the **whole** vault (and search the human title too) — listing one folder misses duplicates elsewhere. On a match: promote/merge from a non-active subfolder, update-in-place in the same folder, or ask which is canonical if elsewhere. - **Read before append (idempotency).** `POST` is not idempotent — a retry duplicates lines. Before any POST that adds an entry (inbox, Agent Log, an Observations/Log heading), GET the target and skip if the exact line is already present. (Does not apply to PUT or PATCH-replace.) - **Bump `updated:` on substance.** PATCH the frontmatter `updated:` to today after a meaningful content change (status update, decision, scope switch). Skip it for routine log appends — bump on substance, not heartbeat. - **Preserve `created:`.** It is the earliest known date the entity was tracked anywhere — never reset it to "today" when merging. - **No `[[wikilinks]]` in frontmatter** — YAML parses them as nested lists and they break. Cross-references go in a `## Related` body section. `source_notes` holds plain relative-path strings (backward links to inputs only). - **Check the HTTP status (or use `echo.sh`).** A `PATCH` to a missing heading returns `400 invalid-target` (40080) and the write is *silently lost*; a transient `503` or an accepted-but-unpersisted `PUT` can do the same. `echo.sh` enforces this (exit ≠ 0, retry, read-back verify); raw `curl` must branch on `-w "%{http_code}"`. Treat ≥ 400 as a failed op — surface it, don't continue. --- ## REST API operations Server `https://echoapi.alwisp.com`, bearer auth, root-addressed paths. | Operation | Method | Notes | |-----------|--------|-------| | Read file | `GET /vault/` | Raw markdown; `404` = absent | | Read heading | `GET /vault//heading/

::

` | `::`-delimited, URL-encode spaces as `%20` | | List dir | `GET /vault//` | Trailing slash; returns `{files, folders}` | | Document map | `GET` + `Accept: application/vnd.olrapi.document-map+json` | Discover exact heading / block / frontmatter targets | | Append | `POST /vault/` | Appends to end-of-file; creates if absent | | Create / overwrite | `PUT /vault/` | Auto-creates intermediate directories | | Patch section | `PATCH /vault/` | `Operation: append\|prepend\|replace`, `Target-Type: heading\|frontmatter\|block` | | Search | `POST /search/simple/?query=` | Returns `[{filename, score, matches}]` | | Delete | `DELETE /vault/` | Only on explicit operator request | **PATCH heading targets must be the full `::`-delimited path from the top-level heading** (e.g. `Operator Preferences::Fact / Pattern`) — a bare subheading name returns `400 invalid-target` (errorCode 40080). GET the document map first when unsure of the exact path, and copy the heading string verbatim. --- ## Memory routing map | Situation | Vault path | Method | |-----------|-----------|--------| | Quick capture / unsorted | `inbox/captures/inbox.md` (date-prefixed line) | POST | | Raw imported material | `inbox/imports/` | PUT | | Operator preference / durable fact | `_agent/memory/semantic/operator-preferences.md` | PATCH | | Other durable facts, patterns | `_agent/memory/semantic/.md` | PUT | | Event record (what happened) | `_agent/memory/episodic/.md` | PUT | | Short-lived, time-boxed state | `_agent/memory/working/.md` | PUT | | Task-scoped context / focus | `_agent/context/current-context.md` | PATCH / PUT | | Working-session log | `_agent/sessions/YYYY-MM-DD-HHMM-.md` | PUT | | Long-running project state | `projects//.md` (folder and `status:` MUST agree) | PUT + PATCH | | Standing area of responsibility (no end state) | `areas//.md` (`business`/`personal`/`learning`/`systems`) | PUT | | Non-obvious decision (ADR) | `decisions/by-date/YYYY-MM-DD-.md` (mirror into a project's `## Key Decisions`; else skip) | PUT | | Person context | `resources/people/.md` | PUT / PATCH | | Concept / reference note | `resources/concepts/` or `resources/references/` | PUT | | Meeting notes / call recap | `resources/meetings/YYYY-MM-DD-.md` | PUT | | Skill / plugin capability entry | `_agent/skills/active/.md` (→ `archived/` when retired) | PUT | | Daily activity / Agent Log | `journal/daily/YYYY-MM-DD.md` | POST / PATCH | | Journal rollup | `journal/{weekly/YYYY-Www,monthly/YYYY-MM,quarterly/YYYY-Qn,annual/YYYY}.md` | PUT | | Vault-health audit (agent self-maintenance) | `_agent/health/YYYY-MM-vault-health.md` | PUT | | Session-end orientation pointer | `_agent/heartbeat/last-session.md` | PUT | | Bootstrap marker (plugin-owned) | `_agent/echo-vault.md` (`schema_version`, date) | GET / PUT | The complete endpoint→logic map — every path with its trigger and why it's distinct — is `skills/echo-memory/references/routing-map.md`. **Slug rules:** kebab-case, ASCII, ~40 chars max. Every file carries canonical YAML frontmatter. --- ## Frontmatter conventions Every note opens with this block (fill what applies; leave the rest empty rather than guessing): ```yaml --- type: # daily-note | project | area | concept | reference | person | # meeting | decision | review | session-log | working-memory | # episodic-memory | semantic-memory | context-bundle | skill | ... status: # active | draft | done | archived | complete | incubating | on-hold created: # YYYY-MM-DD — earliest known date the entity was tracked updated: # YYYY-MM-DD — bumped on meaningful change tags: [] agent_written: # true on agent-generated notes source_notes: [] # plain relative paths (backward links) — NEVER [[wikilinks]] --- ``` `agent_written: true` + a populated `source_notes` is the key signal separating agent-managed content from human-authored content. ### operator-preferences.md — Rules vs Observations - `## Fact / Pattern` — promoted, deduped, **timeless** rules (no date prefix). - `## Observations` — **timestamped** raw observations (`- 2026-06-06: ...`); the default landing zone for new evidence. Observations are promoted into Fact / Pattern (dropping the date) once a pattern stabilizes, and trimmed to the last ~30 entries. --- ## Bootstrap, repair & migration The plugin carries everything needed to stand up a vault in `scaffold/`; there is no dependency on any in-vault control doc. In 0.7 these are scripts (`scripts/bootstrap.sh`, `scripts/migrate.sh`), not hand-run curl loops — the prose in `references/bootstrap.md` documents what they do and serves as the fallback. - **Probe** — GET `_agent/echo-vault.md`. `200` → bootstrapped (check `schema_version`); `404` → run a fresh bootstrap. - **Fresh bootstrap** (`bootstrap.sh`; idempotent, additive — never overwrites): create the folder tree (a one-line README seeds each leaf since Obsidian/git ignore empty dirs) → PUT the 8 templates to their mirrored vault paths → PUT the 3 anchor seeds only if absent (the operator-preferences seed is deliberately empty — no fabricated facts) → PUT the thin vault README → PUT the marker **last** (so the vault is only flagged "set up" once everything is in place) → write a first-run trace (daily note + session log). `--dry-run` previews. - **Repair** — `bootstrap.sh` again: GET-probes each path and creates only what's missing; malformed files are flagged, never replaced. - **Migrations** — `migrate.sh` reads the marker's `schema_version`, applies each intervening migration (dry-run by default; `--apply` to perform moves/deletes), and stamps the marker. `0 → 1` removed the old in-vault control docs (`CLAUDE.md` / `BOOTSTRAP.md` / `STRUCTURE.md` / `index.md`); `1 → 2` folded `reviews/` into `journal/` + `_agent/health/`. --- ## Skill triggers | Skill | Triggers | |-------|----------| | `echo-memory` | "remember that", "save to memory", "what do you know about me", "load my profile", "check my notes", "log this decision", "add to my inbox" — and proactively at the start of substantive conversations | --- ## Safety rules (operating contract) - Do not fabricate facts, relationships, or prior decisions. - Do not mass-restructure the vault unless explicitly asked. - Do not delete notes unless deletion is explicitly requested and clearly safe. - Never store secrets or API keys inside a vault note. - Default to additive updates and explicit status changes over destructive edits. - Write in **third person** about Jason ("Jason prefers X"). Do not cross-write with other vaults (Bryan's `goldbrain` is separate). If the API returns a connection error, timeout, or `502` (usually Obsidian / the REST plugin not running), tell Jason once that the vault is unreachable and proceed without memory — don't retry repeatedly. --- ## Version history | Version | Highlights | |---------|-----------| | **0.3.0** | Source promoted from zip-only to a tracked tree (`echo-memory.plugin.src/`); `.plugin` becomes a build artifact. All 7 skill-improvement items applied: search-first before writes, resilient daily Agent Log, `created:` semantics, project lifecycle + folder↔status rule, canonical HHMM session filenames, read-most-recent-N sessions, `source_notes` defined as backward links. | | **0.4.0** | Efficiency + robustness pass: parallel cold-start loading, idempotent POST (read-before-append), doc-map-before-first-PATCH, scoped `updated:` bump, Inbox Triage, Scope Switching, monthly Vault Health, Rules-vs-Observations split, formal deprecation of `decisions/by-project/`, heartbeat pointer. | | **0.4.1** | Bugfix: daily-note Agent Log heading detection now greps raw markdown for `^## Agent Log` instead of the `::`-delimited doc-map JSON (which never matched and appended duplicate headings). Added Scope Switching cold-start test harness. | | **0.5.0** | Self-bootstrap + control-logic-in-plugin. Plugin becomes the single source of truth: bundled `scaffold/` (8 templates, 3 anchor seeds, thin vault README, marker) bootstraps an empty vault with no external/local-path dependency. New `operating-contract.md` (principles + safety from the old in-vault `CLAUDE.md`); `bootstrap.md` rewritten as a portable bootstrap/repair/migrate manifest. Cold-start probe moved from `/vault/BOOTSTRAP.md` to `_agent/echo-vault.md` (carries `schema_version`). Live vault migrated to data-only. | | **0.5.1** | Routing-doc consistency pass: decision-mirror heading unified to `## Key Decisions`; stale `Current status` PATCH examples corrected to `Status`; vault-layout inline project example refreshed to the real template. All 17 `projects/active/` notes normalized losslessly to the canonical template heading set; `android-mqtt-shell` moved to `incubating/` (was broken `status: upcoming` in active). Plugin repackaged (21 files). | | **0.6.0** | Schema 2. **#8 Inbox auto-fire:** the Loading procedure adds an inbox-depth GET and a load-time *Reconcile* step (inbox triage + scope-drift), so triage self-fires. **#10 Routing:** `reviews/` retired — weekly/monthly/quarterly/annual rollups fold into `journal/{weekly,monthly,quarterly,annual}/`, vault-health moves to `_agent/health/`; new `references/routing-map.md` is the complete audited endpoint→logic map. **Recs:** heartbeat pointer operationalized (read first at load, written at session end); new `scripts/vault-lint.sh` mechanically checks vault invariants. Dead refs pruned (`archive/`, `_agent/outputs/`, `resources/source-material`). Migration `1 → 2` in `bootstrap.md`. | | **0.7.1** | **Scope-drift fix.** Scope is the most churn-prone state (several sessions/day) and had no freshness signal, so sessions silently ran under stale scope (same failure class as #8). Added a `scope_updated:` frontmatter timestamp (maintained automatically), an `echo.sh scope show` / `scope set` command (atomic switch: archive prior → replace → stamp), and a `vault-lint.sh` **drift check** (flags when ≥ `SCOPE_STALE_SESSIONS`, default 3, session logs are dated after `scope_updated`) — making drift mechanically *evaluable* via `/echo-health`. Tightened the SKILL load-reconcile to *state and confirm* scope every session and switch before working. (Also fixed a bash nested-quote parse bug found while building `scope`, where `show` could fall through into `set`.) | | **0.7.0** | Schema 2 (unchanged layout). Hardening pass — gave the prose-and-curl skill an executable spine. **S2** `scripts/echo.sh`: one validated client wrapping every verb with auth, HTTP-status checking (failed writes exit non-zero instead of looking like success), one bounded retry on 5xx, read-back-verified PUT, and idempotent `append`. **S3** `scripts/routing.json`: canonical machine-readable route manifest; `vault-lint.sh` enforces it (flags unknown/retired paths). **S4** deterministic `scripts/bootstrap.sh` + `scripts/migrate.sh` (idempotent, dry-run, probe-before-write; fixes the old CWD-relative `@scaffold/...` empty-body bug). **S5** cooperative advisory lock (`_agent/locks/vault.lock`) + documented multi-writer model. **M1/M2/M5** linter rewrite: real YAML parsing, injected clock (`ECHO_TODAY`), exits `3` (not "clean") on an un-bootstrapped vault, plus routing-membership + frontmatter-integrity checks. **M3** status-check guidance throughout. **M4** four slash commands (`/echo-load`, `/echo-save`, `/echo-triage`, `/echo-health`). Added a credential-free A/B `eval/` harness (mock REST API + fault injection): isolates a **−76% generated-token** I/O layer and **4 → 0 silent write failures** vs 0.6. |