Files
echo-v.05/README.md
T
Jason Stedwell ff52706071 bump to 0.7.1
2026-06-19 22:01:07 -05:00

361 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# echo-memory — v0.7.1
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-<version>.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/scope`. 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. `scope show` reports the active scope + its freshness; `scope set "<text>"` switches it atomically (archive prior → replace → stamp `scope_updated`). |
| `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"). Includes a **scope-drift** check (flags when ≥ `SCOPE_STALE_SESSIONS` session logs postdate `scope_updated`). |
| `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 <text>` (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 <id>``_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/ ← <slug>.md — organizations (clients, vendors, partners, employers)
│ ├── concepts/ references/ meetings/
│ └── people/ ← <name>.md
├── decisions/
│ ├── by-date/ ← YYYY-MM-DD-<slug>.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-<slug>.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 56 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/<lifecycle>/<slug>.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. It is the **most churn-prone state** — several sessions a day across different topics — so without care a new session silently inherits a stale scope (the same failure class as inbox auto-fire). 0.7.1 hardens this three ways:
- **Freshness signal** — a `scope_updated:` frontmatter timestamp records when scope last changed.
- **One-command switch** — `echo.sh scope set "<new scope>"` does it atomically: archive the prior scope to `## Scope History` (dated, truncated), replace `## Scope`, and stamp `scope_updated`. (Manual fallback: prepend history → replace `## Scope` → PATCH `scope_updated`; the field must already exist or PATCH returns `400 invalid-target`.) Scope History is trimmed to the last ~10 entries.
- **Drift detection** — at load the agent runs `echo.sh scope show` (prints the scope, its `scope_updated`, and how many sessions have been logged since) and *states + confirms* scope before working, switching if it diverges. As a backstop, `vault-lint.sh` flags when ≥ `SCOPE_STALE_SESSIONS` (default 3) session logs postdate `scope_updated` — surfaced in `/echo-health`, so drift is mechanically **evaluable** rather than invisible.
### 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-<slug>.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**, **frontmatter integrity** (missing required fields, `updated` < `created`, future dates, wikilinks leaking into `source_notes`), and **scope drift** (≥ `SCOPE_STALE_SESSIONS` session logs dated after `scope_updated`). 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=<slug>` 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/<path>` | Raw markdown; `404` = absent |
| Read heading | `GET /vault/<path>/heading/<H1>::<H2>` | `::`-delimited, URL-encode spaces as `%20` |
| List dir | `GET /vault/<dir>/` | Trailing slash; returns `{files, folders}` |
| Document map | `GET` + `Accept: application/vnd.olrapi.document-map+json` | Discover exact heading / block / frontmatter targets |
| Append | `POST /vault/<path>` | Appends to end-of-file; creates if absent |
| Create / overwrite | `PUT /vault/<path>` | Auto-creates intermediate directories |
| Patch section | `PATCH /vault/<path>` | `Operation: append\|prepend\|replace`, `Target-Type: heading\|frontmatter\|block` |
| Search | `POST /search/simple/?query=<terms>` | Returns `[{filename, score, matches}]` |
| Delete | `DELETE /vault/<path>` | 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/<slug>.md` | PUT |
| Event record (what happened) | `_agent/memory/episodic/<slug>.md` | PUT |
| Short-lived, time-boxed state | `_agent/memory/working/<slug>.md` | PUT |
| Task-scoped context / focus | `_agent/context/current-context.md` | PATCH / PUT |
| Working-session log | `_agent/sessions/YYYY-MM-DD-HHMM-<slug>.md` | PUT |
| Long-running project state | `projects/<lifecycle>/<slug>.md` (folder and `status:` MUST agree) | PUT + PATCH |
| Standing area of responsibility (no end state) | `areas/<domain>/<slug>.md` (`business`/`personal`/`learning`/`systems`) | PUT |
| Non-obvious decision (ADR) | `decisions/by-date/YYYY-MM-DD-<slug>.md` (mirror into a project's `## Key Decisions`; else skip) | PUT |
| Person context | `resources/people/<name>.md` | PUT / PATCH |
| Concept / reference note | `resources/concepts/` or `resources/references/` | PUT |
| Meeting notes / call recap | `resources/meetings/YYYY-MM-DD-<slug>.md` | PUT |
| Skill / plugin capability entry | `_agent/skills/active/<slug>.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. |