2.0.0 — packaging & structure major: skills-only plugin, release-based artifacts

Memory behavior unchanged; the major signals a distribution/install break.

- Legacy commands/ directory DELETED (skills-only; the 1.6.0 skills were verified
  on desktop + CoWork and already took precedence). Pre-skills clients install
  1.6.0 from the release history.
- Artifact policy: the repo tracks only the echo-memory.plugin pointer; the 15
  historical versioned zips leave the tree (git history retains them); versioned
  builds ship as Gitea release assets, one release per v<version> tag, starting
  v2.0.0. .gitignore: *.plugin except the pointer.
- README: post-2.0 repository layout; "packaging for other agent runtimes" note
  (ports are build targets from the canonical tree, never a second source tree).
- ROADMAP-2.0 items ticked; manifest -> 2.0.0.

All suites green (25/25 unit + 4 e2e suites); artifact rebuilt skills-only
(79 entries, no commands/).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jason Stedwell
2026-07-28 22:12:26 -05:00
parent ab1e514f78
commit 70532aaf2c
30 changed files with 63 additions and 201 deletions
+7 -5
View File
@@ -20,8 +20,10 @@ eval/results/*.json
/echo-memory.config.json /echo-memory.config.json
/dist/ /dist/
# Per-user BAKED artifacts (build.py --bake-key, named echo-memory-<ver>-<label>.plugin) # Build artifacts (2.0 policy): the repo tracks ONLY the echo-memory.plugin pointer.
# carry a live vault bearer token — NEVER commit them. They belong in dist/ (above); # Versioned builds (echo-memory-<ver>.plugin) are published as Gitea release assets
# this also guards any that get built at the repo root. The version-only pointer # on git.alwisp.com/jason/echo, one release per tag — not committed. Per-user BAKED
# (echo-memory.plugin) and version-only artifacts (echo-memory-<ver>.plugin) stay tracked. # artifacts (echo-memory-<ver>-<label>.plugin) carry a live vault bearer token and
echo-memory-*-*.plugin # must NEVER be committed anywhere (they belong in dist/, also ignored above).
*.plugin
!echo-memory.plugin
+31
View File
@@ -1,5 +1,36 @@
# Changelog # Changelog
## 2.0.0
**Packaging & structure major — nothing about memory behavior changes.** The major
version signals "reinstall, re-clone expectations": how the plugin is distributed
and installed is what breaks.
### Removed — the legacy `commands/` directory (BREAKING for pre-skills clients)
The plugin is **skills-only**. The eight `/echo-*` entry points live exclusively at
`skills/<name>/SKILL.md` (introduced 1.6.0, verified on desktop + CoWork
2026-07-28, where they already took precedence). A Claude Code old enough to lack
skills support loses the slash commands — install 1.6.0 from the release history
instead.
### Changed — artifact policy (BREAKING for anyone pulling zips from the repo)
- The repo tracks **only** `echo-memory.plugin` (the current installable). The 15
historical `echo-memory-<version>.plugin` zips are deleted from the tree (git
history retains them).
- Versioned builds are published as **Gitea releases** on
`git.alwisp.com/jason/echo` — one release per `v<version>` tag, artifact
attached, starting with `v2.0.0`.
- `.gitignore` now ignores `*.plugin` except the pointer.
### Added — "packaging for other agent runtimes" README note
The retired Codex tree stays retired; the README now documents what a port needs
(manifest shape, skill entry point, script invocation + hooks) and the rule that a
port must be a **build target** from the canonical source tree, never a second
hand-maintained copy.
## 1.6.0 ## 1.6.0
### Changed — the eight slash commands migrated to the skills format ### Changed — the eight slash commands migrated to the skills format
+8 -6
View File
@@ -1,4 +1,4 @@
# echo-memory — v1.6.0 # echo-memory — v2.0.0
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). The skill makes direct REST calls through a bundled validated client (`scripts/echo.py`). The whole toolchain is **pure Python** (stdlib only), so it runs identically on Windows, macOS, and Linux — no bash, no platform-specific `date`. 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). The skill makes direct REST calls through a bundled validated client (`scripts/echo.py`). The whole toolchain is **pure Python** (stdlib only), so it runs identically on Windows, macOS, and Linux — no bash, no platform-specific `date`.
@@ -67,7 +67,8 @@ echo-v.05/
├── docs/history/ ← historical inputs (e.g. the 1.5.0 frontmatter field report) ├── docs/history/ ← historical inputs (e.g. the 1.5.0 frontmatter field report)
├── build.py ← deterministic .plugin builder (--bake-key/--strip-key/--label/--outdir) ├── build.py ← deterministic .plugin builder (--bake-key/--strip-key/--label/--outdir)
├── echo-memory.plugin ← built, installable plugin (zip artifact, rebuilt on version bump) ├── echo-memory.plugin ← built, installable plugin (zip artifact, rebuilt on version bump)
├── echo-memory-<version>.plugin ← versioned build artifacts (history; moves to Gitea releases in 2.0) │ — the ONLY tracked artifact; versioned builds are Gitea release
│ assets (one release per v<version> tag), not committed (2.0 policy)
├── dist/ ← per-user baked artifacts (secret-bearing) — GITIGNORED, never committed ├── dist/ ← per-user baked artifacts (secret-bearing) — GITIGNORED, never committed
├── eval/ ← credential-free eval + test harness; not bundled ├── eval/ ← credential-free eval + test harness; not bundled
│ ├── mock_olrapi.py ← deterministic mock of the REST API + fault injection │ ├── mock_olrapi.py ← deterministic mock of the REST API + fault injection
@@ -80,11 +81,9 @@ echo-v.05/
├── .claude-plugin/plugin.json ← manifest (name, version, description) ├── .claude-plugin/plugin.json ← manifest (name, version, description)
├── README.md ← plugin-level README ├── README.md ← plugin-level README
├── hooks/hooks.json ← session hooks: SessionStart auto-load · Stop reflection nudge ├── hooks/hooks.json ← session hooks: SessionStart auto-load · Stop reflection nudge
├── commands/ ← LEGACY slash commands (kept through 1.6 for compatibility;
│ shadowed by skills/, deleted in 2.0)
├── skills/echo-{load,save,recall,triage,health,sweep,reflect,doctor}/ ├── skills/echo-{load,save,recall,triage,health,sweep,reflect,doctor}/
│ ← the eight commands in the skills format (1.6.0): per-skill │ ← the eight slash commands as skills (skills-only since 2.0):
│ allowed-tools; sweep/triage are operator-only per-skill allowed-tools; sweep/triage are operator-only
└── skills/echo-memory/ └── skills/echo-memory/
├── SKILL.md ← operating procedure (authoritative) ├── SKILL.md ← operating procedure (authoritative)
├── references/ ├── references/
@@ -123,6 +122,8 @@ echo-v.05/
└── templates/ ← 8 canonical note templates └── templates/ ← 8 canonical note templates
``` ```
**Packaging for other agent runtimes** (from the retired Codex tree, deleted 2026-07-03): if a port to another runtime (Codex, etc.) is ever wanted, implement it as a **build target** generated from the canonical `echo-memory.plugin.src/` tree (`build.py --target <runtime>` or similar) — never as a second hand-maintained source tree; that's how the original Codex copy drifted. A port needs three adaptations: the manifest shape (`.claude-plugin/plugin.json` → the runtime's equivalent), the skill entry point (`skills/*/SKILL.md` frontmatter + body conventions), and the script-invocation convention (`${CLAUDE_PLUGIN_ROOT}` resolution + the session-hook wiring in `hooks/hooks.json`). The Python toolchain itself is runtime-agnostic (pure stdlib, env/config-driven).
**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. **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.
--- ---
@@ -420,6 +421,7 @@ From the credential-free harness (`eval/run_eval.py` against the deterministic m
| Version | Highlights | | Version | Highlights |
|---------|-----------| |---------|-----------|
| **2.0.0** | **Packaging & structure major — memory behavior unchanged.** Skills-only: the legacy `commands/` directory is deleted (breaking for pre-skills clients; the 1.6.0-verified skills are the sole entry points). Artifact policy: the repo tracks only the `echo-memory.plugin` pointer; the 15 historical versioned zips leave the tree and versioned builds ship as **Gitea releases** (one per `v<version>` tag) from `v2.0.0` on; `.gitignore` blocks `*.plugin` except the pointer. README gains the "packaging for other agent runtimes" port note (build-target rule, never a second source tree). |
| **1.6.0** | **Skills-format migration + lint blind-spot fix.** The eight slash commands gain `skills/<name>/SKILL.md` twins (same names, byte-identical bodies; a same-name skill takes precedence, so `commands/` coexists until its 2.0 deletion): per-skill `allowed-tools` end the permission prompts, `disable-model-invocation: true` makes `/echo-sweep` + `/echo-triage` operator-only. `vault_lint` checks retired patterns **before** routes, so a seed README inside a retired tree (`archive/…`) is flagged instead of being absolved by the leaf-README route (+ regression test). `plugin.json` gains `homepage`/`repository`. | | **1.6.0** | **Skills-format migration + lint blind-spot fix.** The eight slash commands gain `skills/<name>/SKILL.md` twins (same names, byte-identical bodies; a same-name skill takes precedence, so `commands/` coexists until its 2.0 deletion): per-skill `allowed-tools` end the permission prompts, `disable-model-invocation: true` makes `/echo-sweep` + `/echo-triage` operator-only. `vault_lint` checks retired patterns **before** routes, so a seed README inside a retired tree (`archive/…`) is flagged instead of being absolved by the leaf-README route (+ regression test). `plugin.json` gains `homepage`/`repository`. |
| **1.5.1** | **Duplicate-gate precision.** Live use caught the gate blocking a decision note because it shared the single vault-common token "echo" with an archived project (min-normalized scoring gives any single-token entity a 1.0 match). New `gate_candidates()` blocks only on **same-kind** candidates (cross-kind name collisions — a decision titled after its project — warn instead), and a **lone shared token blocks only when unique to that entity** (vault df == 1); multi-token overlaps still block. Advisory warnings (`fuzzy_candidates`) unchanged; exit-76/`--merge-into`/`--force` unchanged. +5 tests; verified against the live vault both ways (false positive creates cleanly, true lookalike still gates). | | **1.5.1** | **Duplicate-gate precision.** Live use caught the gate blocking a decision note because it shared the single vault-common token "echo" with an archived project (min-normalized scoring gives any single-token entity a 1.0 match). New `gate_candidates()` blocks only on **same-kind** candidates (cross-kind name collisions — a decision titled after its project — warn instead), and a **lone shared token blocks only when unique to that entity** (vault df == 1); multi-token overlaps still block. Advisory warnings (`fuzzy_candidates`) unchanged; exit-76/`--merge-into`/`--force` unchanged. +5 tests; verified against the live vault both ways (false positive creates cleanly, true lookalike still gates). |
| **1.5.0** | **Six-improvement pass: retention, routing, and self-firing memory.** (1) **Capture-update keeps the whole body** — the update path previously appended only the first line of a multi-line body (silent data loss); now the full body rides under the dated bullet. (2) **Frontmatter completeness** — capture stamps a kind-default `status` and kind-seeded `tags` (`--tags` enriches); `fm` is create-or-replace (missing keys are surgically inserted instead of 400-failing); `vault_lint` gains a kind-scoped `incomplete-frontmatter` check; `sweep --apply` backfills (fixes the 18/71-notes field-audit drift, one data source: `KIND_STATUS`/`KIND_REQUIRED_FM`). (3) **Pre-write duplicate gate** — a strong fuzzy candidate stops `capture` (exit 76 + candidates) before the duplicate exists; `--merge-into <slug>` updates the canonical entity, `--force` overrides. (4) **Recall corpus + ranking** — sessions and journal notes join the BM25 corpus (down-weighted); scores fuse freshness (half-life on `updated:`) and status (`active` boosted, `archived` demoted); hits show `updated:`/`status:`; `recall --json`; recall-index schema 2 (auto-rebuilds). (5) **Session hooks** — SessionStart auto-loads memory into context, Stop nudges reflection once per substantive session; fail-safe, CoWork-fallback-aware. (6) **One-tap triage**`echo.py triage` lists the inbox structured, then classifies → previews → routes accepted items via `capture` with automatic processing-log audit lines; `--json` also lands on `link`/`scope show`. +22 mock end-to-end tests; all suites green. | | **1.5.0** | **Six-improvement pass: retention, routing, and self-firing memory.** (1) **Capture-update keeps the whole body** — the update path previously appended only the first line of a multi-line body (silent data loss); now the full body rides under the dated bullet. (2) **Frontmatter completeness** — capture stamps a kind-default `status` and kind-seeded `tags` (`--tags` enriches); `fm` is create-or-replace (missing keys are surgically inserted instead of 400-failing); `vault_lint` gains a kind-scoped `incomplete-frontmatter` check; `sweep --apply` backfills (fixes the 18/71-notes field-audit drift, one data source: `KIND_STATUS`/`KIND_REQUIRED_FM`). (3) **Pre-write duplicate gate** — a strong fuzzy candidate stops `capture` (exit 76 + candidates) before the duplicate exists; `--merge-into <slug>` updates the canonical entity, `--force` overrides. (4) **Recall corpus + ranking** — sessions and journal notes join the BM25 corpus (down-weighted); scores fuse freshness (half-life on `updated:`) and status (`active` boosted, `archived` demoted); hits show `updated:`/`status:`; `recall --json`; recall-index schema 2 (auto-rebuilds). (5) **Session hooks** — SessionStart auto-loads memory into context, Stop nudges reflection once per substantive session; fail-safe, CoWork-fallback-aware. (6) **One-tap triage**`echo.py triage` lists the inbox structured, then classifies → previews → routes accepted items via `capture` with automatic processing-log audit lines; `--json` also lands on `link`/`scope show`. +22 mock end-to-end tests; all suites green. |
+15 -14
View File
@@ -20,12 +20,12 @@ The repo root tracks every historical build (`echo-memory-0.6.0.plugin` …
`echo-memory-1.5.1.plugin`) plus the `echo-memory.plugin` pointer. 2.0 changes the `echo-memory-1.5.1.plugin`) plus the `echo-memory.plugin` pointer. 2.0 changes the
policy — **breaking for anyone who pulls artifacts straight from the repo**: policy — **breaking for anyone who pulls artifacts straight from the repo**:
- [ ] Track only `echo-memory.plugin` (the current installable); delete the versioned - [x] Track only `echo-memory.plugin` (the current installable); delete the versioned
zips from the tree. zips from the tree**done 2.0.0** (15 zips removed; history keeps them).
- [ ] Publish versioned builds as **Gitea releases** on `git.alwisp.com/jason/echo` - [x] Publish versioned builds as **Gitea releases** on `git.alwisp.com/jason/echo`
instead (one release per tag, artifact attached). instead (one release per tag, artifact attached)**v2.0.0 onward**.
- [ ] `.gitignore`: `*.plugin` except the pointer. - [x] `.gitignore`: `*.plugin` except the pointer**done 2.0.0**.
- [ ] Tag releases going forward (`v2.0.0`, …) so the release page is the version - [x] Tag releases going forward (`v2.0.0`, …) so the release page is the version
history for artifacts, and the README table stays the narrative history. history for artifacts, and the README table stays the narrative history.
## 2. Complete the skills-format migration (finishes the 1.6 work) ## 2. Complete the skills-format migration (finishes the 1.6 work)
@@ -33,10 +33,11 @@ policy — **breaking for anyone who pulls artifacts straight from the repo**:
1.6 migrates the eight slash commands to `skills/*/SKILL.md` with both formats 1.6 migrates the eight slash commands to `skills/*/SKILL.md` with both formats
coexisting (see `TODO-1.6.md`). 2.0 finishes it: coexisting (see `TODO-1.6.md`). 2.0 finishes it:
- [ ] **Delete the legacy `commands/` directory**the packaging break that partly - [x] **Delete the legacy `commands/` directory****done 2.0.0** (1.6.0's install
motivates the major bump. verification on both surfaces cleared the gate; skills had precedence anyway).
- [ ] Re-verify desktop + CoWork installs of the skills-only artifact. - [ ] Re-verify desktop + CoWork installs of the skills-only artifact (operator step,
- [ ] Update SKILL.md / README / command docs that reference `commands/`. post-2.0.0-install).
- [x] Update SKILL.md / README / command docs that reference `commands/`**done 2.0.0**.
## 3. Codex packaging (from MAINTENANCE Canonical source tree) ## 3. Codex packaging (from MAINTENANCE Canonical source tree)
@@ -47,9 +48,9 @@ regenerated after 2.0 if needed). What remains for 2.0:
- [ ] If Codex support returns: implement it as a **build target** generated from the - [ ] If Codex support returns: implement it as a **build target** generated from the
canonical `echo-memory.plugin.src/` tree (`build.py --target codex` or similar) — canonical `echo-memory.plugin.src/` tree (`build.py --target codex` or similar) —
never as a second hand-maintained source tree (that's how the drift happened). never as a second hand-maintained source tree (that's how the drift happened).
- [ ] Otherwise: a short "packaging for other agent runtimes" note in the README - [x] Otherwise: a short "packaging for other agent runtimes" note in the README
documenting what a Codex/other-runtime port needs (manifest shape, skill entry documenting what a Codex/other-runtime port needs (manifest shape, skill entry
point, script invocation). point, script invocation)**done 2.0.0** (README after Repository layout).
## 4. Eval refresh — publish current-version metrics (from MAINTENANCE Docs freshness; ROADMAP-1.0 H4 leftover) — ✅ DONE ## 4. Eval refresh — publish current-version metrics (from MAINTENANCE Docs freshness; ROADMAP-1.0 H4 leftover) — ✅ DONE
@@ -67,8 +68,8 @@ the README. Re-run it per release and refresh the README table.
## 5. Repo hygiene odds & ends ## 5. Repo hygiene odds & ends
- [x] Retire `ROADMAP-1.0.md` (fully shipped; in git history) — **done 2026-07-03**. - [x] Retire `ROADMAP-1.0.md` (fully shipped; in git history) — **done 2026-07-03**.
- [ ] Root README "Repository layout" section updated for the post-2.0 tree - [x] Root README "Repository layout" section updated for the post-2.0 tree
(no versioned zips, no codex tree, skills-only plugin). (no versioned zips, no codex tree, skills-only plugin)**done 2.0.0**.
- [x] `echo-improvements-prompt.md` (the 1.5.0 field report) moved to `docs/history/` - [x] `echo-improvements-prompt.md` (the 1.5.0 field report) moved to `docs/history/`
**done 2026-07-03**. **done 2026-07-03**.
+1 -1
View File
@@ -2,7 +2,7 @@
"""build.py — package the echo-memory plugin source into a .plugin artifact. """build.py — package the echo-memory plugin source into a .plugin artifact.
Zips the CONTENTS of echo-memory.plugin.src/ at the archive root (the layout the plugin Zips the CONTENTS of echo-memory.plugin.src/ at the archive root (the layout the plugin
loader expects: .claude-plugin/plugin.json, commands/, skills/ all at top level), excluding loader expects: .claude-plugin/plugin.json, hooks/, skills/ all at top level), excluding
dev cruft. The version is read from the manifest, so the output is named automatically. dev cruft. The version is read from the manifest, so the output is named automatically.
Usage: Usage:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
{ {
"name": "echo-memory", "name": "echo-memory",
"version": "1.6.0", "version": "2.0.0",
"homepage": "https://git.alwisp.com/jason/echo", "homepage": "https://git.alwisp.com/jason/echo",
"repository": "https://git.alwisp.com/jason/echo", "repository": "https://git.alwisp.com/jason/echo",
"description": "Persistent memory via the ECHO Obsidian vault over the Obsidian Local REST API. Cross-platform Python client: one-call capture/resolve/recall/link/triage over an entity index, hybrid BM25 + graph recall spanning entities + sessions/journal (recency/status-aware), a pre-write duplicate gate, complete-frontmatter capture, session hooks that self-fire load/reflect, offline write-ahead queue, lock-guarded concurrency, linter-enforced routing, and /echo-* commands.", "description": "Persistent memory via the ECHO Obsidian vault over the Obsidian Local REST API. Cross-platform Python client: one-call capture/resolve/recall/link/triage over an entity index, hybrid BM25 + graph recall spanning entities + sessions/journal (recency/status-aware), a pre-write duplicate gate, complete-frontmatter capture, session hooks that self-fire load/reflect, offline write-ahead queue, lock-guarded concurrency, linter-enforced routing, and /echo-* commands.",
@@ -1,20 +0,0 @@
---
description: Check ECHO readiness — Python, vault reachability, auth, bootstrap/schema, and key source
---
Use the **echo-memory** skill to run a one-call readiness check before relying on memory.
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py"
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" doctor
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
It prints green/red for: Python version, vault reachability, auth accepted, vault
bootstrapped (+ `schema_version`), and the **config source** for owner/endpoint/key
(per-field: env override `ECHO_OWNER`/`ECHO_BASE`/`ECHO_KEY`, then the machine-local
`~/.claude/echo-memory/config.json` — or `baked-in (plugin)` on a per-user baked build,
which is authoritative and reported as such). Exits non-zero if anything is red — if the config is
missing (or still the placeholder template), ask the operator for their echo-memory config file and install it with `echo.py config import <path>` (or `config set --owner … --endpoint … --key …`). For the full vault-invariant lint, run
`/echo-health`.
@@ -1,17 +0,0 @@
---
description: Run the ECHO vault-health linter and summarize any invariant violations
allowed-tools: Bash(python3 *vault_lint.py*), Bash(python *vault_lint.py*), Bash(ls /sessions/*), Bash(dirname *)
---
Run the bundled, read-only vault linter and report findings. Pass the conversation's current date (`ECHO_TODAY`) so stale/aging math uses the same clock the agent writes with:
```bash
SDIR="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts"
[ -d "$SDIR" ] || SDIR=$(dirname "$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/vault_lint.py 2>/dev/null | head -1)") # CoWork sandbox fallback
ECHO_TODAY=<currentDate> python3 "$SDIR/vault_lint.py"
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
Exit codes: `0` clean · `1` violations (printed, grouped by check) · `2` vault unreachable · `3` vault not bootstrapped (run `bootstrap.py`).
Summarize the violations grouped by category and propose fixes, but **do not auto-fix** without the operator's go-ahead. If this is the first substantive session of a calendar month, offer to write the findings to `_agent/health/YYYY-MM-vault-health.md` (per the skill's **Vault Health** section).
@@ -1,18 +0,0 @@
---
description: Load ECHO memory — cold-start context read (profile, scope, latest session, today, inbox)
---
Use the **echo-memory** skill to load memory now. Run the cold-start **Loading procedure** from `SKILL.md`: the six orientation reads (marker, operator-preferences, current-context, heartbeat, today's daily note, inbox) in **one call**, then do the load-time **reconcile** (inbox-depth + scope-drift) and surface it in a single line.
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py"
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" load
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
`load` prints all six sections (404s on today's note / inbox are shown as absent, not errors) and flags an un-bootstrapped vault. Follow up with `echo.py scope show` if you need the sessions-since-switch count, and a project search if a specific project is in play.
**If `load` prints `NOT CONFIGURED` (exit 78)**, this machine has no usable key file yet. Don't proceed with memory — follow **First run** in `SKILL.md`: tell the operator ECHO isn't configured, ask for their echo-memory config file (owner/endpoint/key), install it with `echo.py config import <path>` (or `config set`), then re-run `load`.
Do not narrate the reads. End with a one-line orientation: who/what/where the active scope is, and any reconcile prompt.
@@ -1,21 +0,0 @@
---
description: Recall a topic from ECHO memory — matching notes plus their linked neighbourhood
argument-hint: "[topic, person, or project]"
---
Use the **echo-memory** skill to recall context for:
> $ARGUMENTS
Run the one-call recall — it resolves the term against the entity index, searches, and expands one hop along `## Related` links and `source_notes`, so you get the connected web (linked decisions, people, prior sessions), not an isolated note:
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py"
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" recall "$ARGUMENTS"
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
The corpus spans the entity graph **plus session logs and journal notes**, ranked by relevance × freshness × status — each hit shows its `updated:`/`status:`, so prefer fresh/active hits and treat stale ones as history. Add `--json` for structured hits (`path/score/type/updated/status/excerpt`) when you need to act on the results programmatically.
Then answer from the assembled context. If you only need the canonical path for one entity, use `echo.py resolve "<title>"` instead. Don't narrate the retrieval.
@@ -1,31 +0,0 @@
---
description: Reflect on this session — extract durable memories and propose them for one-tap capture
argument-hint: "[optional focus, e.g. 'just decisions']"
---
Use the **echo-memory** skill to run **session reflection** (H5). Scan this conversation for
things worth remembering across sessions, then propose them — don't write blindly.
1. **Extract** durable items from the conversation: new facts, preferences, decisions,
commitments, people/companies/projects introduced, and anything the operator said to remember.
Skip the ephemeral. Anchor relative dates on the conversation's `currentDate`.
2. **Emit a JSON array** of proposals (one per item), each:
`{"title","kind","body","aliases","tags","sources","confidence"}``kind`
`person, company, concept, reference, meeting, project, area, semantic, episodic,
working, skill, decision`; set `"inbox": true` when the home is genuinely unknown;
`confidence` 01 (items below 0.6 are dropped — send those to the inbox instead).
Write the array to a file with the Write tool (cross-platform; no heredoc).
3. **Preview, then apply.** Dry-run first (writes nothing) and show the operator the plan; only
apply after they confirm.
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" # python3 (Windows: python / py -3)
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" reflect proposals.json # dry-run: validate + classify + preview
python3 "$ECHO" reflect proposals.json --apply # write — routes each via capture (indexed, linked, logged)
```
`reflect` dedups every proposal against the entity index (so it shows create-vs-update),
skips below-confidence items, and routes `inbox` proposals to the capture inbox. Each
applied item goes through the normal `capture` path — canonical frontmatter, auto-linking,
and the recall index — and the writes are lock-guarded. $ARGUMENTS
@@ -1,28 +0,0 @@
---
description: Save to ECHO memory — route content to its canonical home (search-first, idempotent)
argument-hint: "[what to remember]"
---
Use the **echo-memory** skill to persist this to the ECHO vault:
> $ARGUMENTS
Prefer the one-call **`capture`** — it routes (via the entity index), derives the canonical path, stamps complete frontmatter (kind-default `status`, the kind seeded into `tags`), indexes, auto-links mentioned entities, and writes the Agent-Log line. Pick the `--kind` from the content (`person, company, concept, reference, meeting, project, area, semantic, episodic, working, skill, decision`); add `--tags` when obvious; use `--inbox` only when the home is genuinely unknown. Write multi-line bodies to a file with the Write tool (cross-platform, no heredoc) — updates to an existing entity preserve the whole body, so don't pre-trim it.
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py" # run with: python3 "$ECHO" ... (Windows: python / py -3)
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" capture "<title>" <bodyfile> --kind <kind> [--aliases a,b] [--tags t1,t2] [--source p1,p2]
python3 "$ECHO" capture "<title>" --inbox # unknown home -> idempotent inbox line
```
**If capture exits `76` (duplicate gate):** the title strongly resembles an existing entity — do NOT retry blindly. Show the operator the printed candidates and either update the existing note (`capture ... --merge-into <slug>`) or, only after they confirm it's genuinely distinct, re-run with `--force`.
Drop to the low-level verbs only for shapes `capture` doesn't model (a project `## Status` replace, an ADR mirror, a daily-note edit):
```bash
python3 "$ECHO" put <routed/path>.md <bodyfile> # create/overwrite (verifies)
python3 "$ECHO" patch <path>.md append heading "<H1::Sub>" <bodyfile> # targeted append
```
Write in third person about the vault owner; never put `[[wikilinks]]` in frontmatter. `capture` handles `agent_written`, `source_notes`, indexing, and linking for you; if you write by hand, `resolve "<title>"` first to avoid duplicates and `bump` `updated:` only on substantive changes.
@@ -1,16 +0,0 @@
---
description: Bring the ECHO vault up to the current feature spec (entity index + cross-links)
allowed-tools: Bash(python3 *sweep.py*), Bash(python *sweep.py*), Bash(ls /sessions/*), Bash(dirname *)
---
Use the **echo-memory** skill to bring the vault up to the current spec. Run the sweep **dry-run first**, show the operator the plan, and only `--apply` on their go-ahead:
```bash
SDIR="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts"
[ -d "$SDIR" ] || SDIR=$(dirname "$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/sweep.py 2>/dev/null | head -1)") # CoWork sandbox fallback
python3 "$SDIR/sweep.py" # plan (read-only)
python3 "$SDIR/sweep.py" --apply # write
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
The sweep (1) creates `_agent/index/`, (2) rebuilds the entity index from existing notes, (3) symmetrizes `## Related` cross-links (adds only the missing reciprocal direction — never invents new links), and (4) stamps the marker `schema_version`. It is idempotent and read-only without `--apply`. If `schema_version` is behind, run `migrate.py` first. After applying, run `/echo-health` to confirm invariants.
@@ -1,23 +0,0 @@
---
description: Triage the ECHO inbox — route aging captures to their canonical homes and log the moves
---
Use the **echo-memory** skill to run **one-tap Inbox Triage** via the `triage` verb (it reuses the reflect pipeline: classify against the entity index → preview → apply via `capture`, and writes the audit log for you).
```bash
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.py"
[ -f "$ECHO" ] || ECHO=$(ls /sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/echo.py 2>/dev/null | head -1) # CoWork sandbox fallback
python3 "$ECHO" triage --list --json
# Windows: use `python` or `py -3` if `python3` is not on PATH.
```
1. **List** — the JSON gives each capture as `{line, date, text, age_days}`. Surface the aging ones (≥ ~7 days) to the operator and ask which to route.
2. **Propose** — for accepted items, write a proposals JSON with the Write tool (reflect schema: `{"title","kind","body",...}` **plus `"line"`: the original inbox line**, echoed into the audit log). Route per the map: preference/pattern → `semantic` (or a direct PATCH to `operator-preferences.md::Observations`); project idea → `project` with `--status incubating`; durable fact → `semantic`; person fact → `person`.
3. **Preview, then apply** with the operator's go-ahead:
```bash
python3 "$ECHO" triage proposals.json # dry-run: classify + preview, writes nothing
python3 "$ECHO" triage proposals.json --apply # route via capture + log each move
```
`--apply` records every move in `inbox/processing-log/YYYY-MM-DD.md` (`- <original> → <destination>`) automatically. Do **not** delete the original captures unless the operator explicitly asks — the processing log is the audit trail. A proposal that stops at the duplicate gate (exit note in the summary) should be re-proposed with the existing entity's title.