This commit is contained in:
Jason Stedwell
2026-06-19 21:12:14 -05:00
parent a860819bfb
commit 2fc3a0a80b
26 changed files with 1559 additions and 89 deletions
+55 -15
View File
@@ -1,10 +1,12 @@
# echo-memory — v0.5
# 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.
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 **v0.5 source** of the plugin: the tracked source tree at `echo-memory.plugin.src/` and the built `echo-memory.plugin` package artifact rebuilt from it on each version bump.
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.
---
@@ -26,11 +28,11 @@ Three consequences follow:
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.
- **Auth:** a bearer token stored in the skill / `references`. The key lives only in the plugin — **never inside a vault note** (per the vault's own safety rules).
- **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`).
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.
---
@@ -46,17 +48,30 @@ Vault paths are addressed **at the root** (e.g. `GET /vault/_agent/context/curre
```
echo-v.05/
├── echo-memory.plugin ← built, installable plugin (zip artifact, rebuilt on version bump)
── echo-memory.plugin.src/ tracked source tree
── echo-memory-<version>.pluginversioned 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
│ ├── 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
@@ -64,7 +79,29 @@ echo-v.05/
└── templates/ ← 8 canonical note templates
```
**Division of responsibility:** `SKILL.md` owns day-to-day *procedure* (loading order, search-first, triage, scope switching, PATCH rules). `references/operating-contract.md` owns the durable, client-independent *principles and safety rules*. The other references are the canonical layout, API, and bootstrap specs.
**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 <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.
---
@@ -107,7 +144,8 @@ echo-v.05/
├── 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)
── 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
@@ -177,7 +215,7 @@ The journal is one append-only time-series stream; rollups are coarser-grained e
### Vault Health (monthly)
Agent self-maintenance (not a journal entry), written to `_agent/health/YYYY-MM-vault-health.md`. The bundled `scripts/vault-lint.sh` mechanically asserts the invariants — folder↔status mismatch, duplicate slugs across lifecycle folders, wikilinks in frontmatter, duplicate `## Agent Log` headings, stale active projects (`updated:` > 30 days), and aging inbox items (> 14 days). Findings are reported, not auto-fixed.
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.
---
@@ -188,6 +226,7 @@ Agent self-maintenance (not a journal entry), written to `_agent/health/YYYY-MM-
- **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.
---
@@ -273,12 +312,12 @@ Observations are promoted into Fact / Pattern (dropping the date) once a pattern
## 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.
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** (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).
- **Repair** — same steps, GET-probing each path and creating only what's missing; malformed files are flagged in the session log, never replaced.
- **Migrations** — when the marker's `schema_version` is older than the plugin's, apply each intervening migration then PATCH the marker. `0 → 1` removed the old in-vault control docs (`CLAUDE.md` / `BOOTSTRAP.md` / `STRUCTURE.md` / `index.md`) in favor of the plugin-as-source-of-truth model.
- **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/`.
---
@@ -313,3 +352,4 @@ If the API returns a connection error, timeout, or `502` (usually Obsidian / the
| **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.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. |