diff --git a/.gitignore b/.gitignore index c846f2d..f03b6fb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,24 +8,20 @@ __pycache__/ .venv/ venv/ -# Local CHORUS/ECHO state (never commit — holds the offline queue + cached vault reads) -.echo-memory/ +# Local CHORUS state (never commit — holds the offline queue + cached vault reads) .chorus-memory/ # Eval output eval/results/*.json !eval/results/.gitkeep -# CHORUS/ECHO config: the filled-in key file is yours to distribute, never commit it -/echo-memory.config.json +# CHORUS config: the filled-in key file is yours to distribute, never commit it /chorus-memory.config.json /dist/ # Build artifacts. Per-user BAKED artifacts (build.py --bake-key) carry a live vault -# bearer token — NEVER commit them; they belong in dist/ (above). As of the CHORUS -# fork (Phase 0), versioned artifacts are no longer tracked in the tree either — -# they ship as Gitea releases (per ROADMAP-2.0). Only the current pointer -# (echo-memory.plugin / chorus-memory.plugin) stays tracked. Historical ECHO -# artifacts remain at the echo-fork-point tag and in the parent jason/echo repo. -echo-memory-*.plugin +# bearer token — NEVER commit them; they belong in dist/ (above). Versioned artifacts +# are not tracked in the tree — they ship as Gitea releases. Only the current pointer +# (chorus-memory.plugin) stays tracked. Historical ECHO artifacts remain at the +# echo-fork-point tag and in the parent jason/echo repo. chorus-memory-*.plugin diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b574a..89ced88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,23 +15,16 @@ CHORUS is a group-based fork of ECHO (`jason/echo`, v1.5.1, schema 4 — preserv bootstrap marker to `_agent/chorus-vault.md`; manifest version → 2.0.0-alpha.1. - Docs' endpoint references now `chorusapi.mpm.to`. -### Back-compat (one major version) -- Legacy `ECHO_*` env vars are adopted as `CHORUS_*` at import when the new name is unset. -- A legacy `~/.claude/echo-memory/config.json` is read (never written) when no CHORUS - config exists; `config`/`doctor` report the legacy source. -- A legacy `~/.echo-memory/` state dir is honored when `~/.chorus-memory/` doesn't exist - yet, so queued offline writes aren't stranded. -- The bootstrap probe (`load`, `doctor`, `bootstrap`, `vault_lint`, `sweep`, `migrate`) - falls back to a pre-fork `_agent/echo-vault.md` marker; bootstrap's repair pass leaves - a legacy marker in place (the schema migration will rename it) rather than writing a - second marker. `routing.json` gains a read-only `agent-marker-legacy` route. +### No ECHO back-compat — clean break +CHORUS deploys as fresh vaults on fresh machines for new users, so it carries **no** +ECHO compatibility: `ECHO_*` env vars, `~/.claude/echo-memory/config.json`, +`~/.echo-memory/`, and a vault's `_agent/echo-vault.md` marker are all ignored. +(Shims for these were built during Phase 1 and then removed by decision.) ### Verified - 25/25 client unit tests, scaffold checks, routing-doc sync, and all four mock end-to-end suites green; `run_eval.py` metrics unchanged (0 dupes / 0 false blocks / - 0 silent failures / 0 lost offline writes). Six new shim smoke tests (legacy env, - legacy config path, chorus-over-legacy precedence, legacy-marker load/doctor, fresh - vault) pass. + 0 silent failures / 0 lost offline writes). ## 1.5.1 diff --git a/CHORUS-PLAN.md b/CHORUS-PLAN.md index ed4b226..fca0daf 100644 --- a/CHORUS-PLAN.md +++ b/CHORUS-PLAN.md @@ -57,7 +57,7 @@ on two machines but not for N people. its own Obsidian + Local REST API instance exposed there (separate from ECHO's `echoapi.alwisp.com`; the personal ECHO vault is untouched). The endpoint stays machine-local/baked config, never in source, same as ECHO. - `owner` → `group` (with `owner` accepted as a legacy alias). Resolution order + `owner` → `group`. Resolution order unchanged: baked > env (`CHORUS_MEMBER` etc.) > config file. Baked per-user builds (`build.py --bake-key --label alice`) already exist and become the primary **onboarding** mechanism: one artifact per member, member id baked in. @@ -158,10 +158,10 @@ theirs alone). What remains shared-write: | Commands | `/echo-{load,save,recall,reflect,triage,sweep,health,doctor}` | `/chorus-*` | | Scripts | `echo*.py` (16 modules + imports everywhere) | `chorus*.py` | | Exception | `EchoError` | `ChorusError` | -| Env vars | `ECHO_OWNER/BASE/KEY/CONFIG/TODAY/VERIFY/LOCK_TTL/TIMEOUT/WORKERS/STATE_DIR/CLIENT/DUP_GATE/REFLECT_MIN_TURNS/FRESH_HALF_LIFE` | `CHORUS_*` (+ new `CHORUS_MEMBER`); `ECHO_*` read as fallback aliases in `chorus_config` for one major version | -| Config file | `~/.claude/echo-memory/config.json` | `~/.claude/chorus-memory/config.json` (import path: `config import` accepts an old echo config) | +| Env vars | `ECHO_OWNER/BASE/KEY/CONFIG/TODAY/VERIFY/LOCK_TTL/TIMEOUT/WORKERS/STATE_DIR/CLIENT/DUP_GATE/REFLECT_MIN_TURNS/FRESH_HALF_LIFE` | `CHORUS_*` (+ new `CHORUS_MEMBER`) | +| Config file | `~/.claude/echo-memory/config.json` | `~/.claude/chorus-memory/config.json` | | State dir | `~/.echo-memory/` | `~/.chorus-memory/` | -| Marker | `_agent/echo-vault.md` (hardcoded in 8 files + routing.json) | `_agent/chorus-vault.md`; probe checks new name **then** old (adoption path) | +| Marker | `_agent/echo-vault.md` (hardcoded in 8 files + routing.json) | `_agent/chorus-vault.md` | | Hooks | `hooks.json` paths ×2 incl. CoWork fallback glob | updated paths | | Docs/branding | README, SKILL.md, references, scaffold seeds, icons | rewrite pass (cosmetic but large) | @@ -177,11 +177,14 @@ Repo hygiene on the fork: strip stale versioned `.plugin` artifacts from the tree (move to Gitea releases per ROADMAP-2.0), confirm `eval/` suites green as-is, tag the fork point. -### Phase 1 — Mechanical rename (1–2 days) -Everything in §3, with back-compat shims (env aliases, marker dual-probe, -config import from echo paths). All existing tests pass under the new names. -No behavior change. **Deliverable:** installable `chorus-memory` plugin that is -ECHO-with-a-new-name. +### Phase 1 — Mechanical rename (1–2 days) — DONE +Everything in §3. All existing tests pass under the new names. No behavior +change. **Deliverable:** installable `chorus-memory` plugin that is +ECHO-with-a-new-name. *(Decided post-Phase-1: CHORUS is a **clean break** — +fresh vaults on fresh machines for new users, so no ECHO back-compat: legacy +`ECHO_*` env vars, echo-memory configs, `~/.echo-memory` state, and +`_agent/echo-vault.md` markers are all ignored. Shims briefly existed and were +removed.)* ### Phase 2 — Identity plumbing (1–2 days) `member` in config (+ `CHORUS_MEMBER`, baked-tier support, `config set --member`), @@ -198,12 +201,10 @@ if the vault exists but your namespace doesn't — new members self-onboard), `cmd_load`/`cmd_scope`/heartbeat/inbox/triage/reflect retargeted to member paths, session logs under `sessions//`, member-prefixed Agent Log lines, `routing.json` route rows (with `` pattern segment) + retired -old paths, lint updates, `migrate.py` 4→5 (moves Jason's existing -operator-preferences/current-context/heartbeat/inbox/sessions into -`members/jason/…`, stamps `author: jason` vault-wide via sweep). Docs: -vault-layout.md, routing-map.md, bootstrap.md rewritten. -**Deliverable:** a real multi-member vault; the live ECHO vault migrates in -place. +old paths, lint updates, `migrate.py` 4→5 (for any alpha CHORUS vaults stood +up between phases — there is no ECHO-vault adoption path; CHORUS vaults start +fresh). Docs: vault-layout.md, routing-map.md, bootstrap.md rewritten. +**Deliverable:** a real multi-member vault, bootstrapped from empty. ### Phase 4 — Shared-write hardening (1–2 days) Required lock on index writes with bounded retry/backoff, gate messages carry @@ -246,3 +247,8 @@ retrieval/durability metrics. New icon. `_agent/members//inbox.md`; a shared `inbox/captures/group.md` holds unowned items (member-prefixed lines, idempotent appends), routed by whoever triages first. Both are probed at load and covered by triage. +6. **Clean break from ECHO** *(added post-Phase-1)*. CHORUS vaults are fresh + vaults on fresh machines for new users — no ECHO adoption path, no legacy + `ECHO_*` env/config/state/marker compatibility. Jason's personal ECHO vault + continues unchanged on its own plugin and endpoint. If ECHO history is ever + wanted in CHORUS, it would be a one-off import exercise, out of scope. diff --git a/README.md b/README.md index ceac51b..a0fa23c 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ > Obsidian vault serving a group of members instead of a single operator. The conversion > plan and settled design decisions live in [CHORUS-PLAN.md](CHORUS-PLAN.md). The pre-fork > ECHO v1.5.1 state is preserved at the `echo-fork-point` tag. **Phase 1 (mechanical -> rename, back-compat shims) is done**; the body of this README below the fold still -> describes the inherited single-operator behavior and is rewritten in Phase 5. -> Deployment endpoint: `https://chorusapi.mpm.to`. Legacy `ECHO_*` env vars, an existing -> `~/.claude/echo-memory/config.json`, and a vault's old `_agent/echo-vault.md` marker -> all keep working for one major version. +> rename) is done**; the body of this README below the fold still describes the +> inherited single-operator behavior and is rewritten in Phase 5. +> Deployment endpoint: `https://chorusapi.mpm.to`. CHORUS is a **clean break** from +> ECHO — fresh vaults on fresh machines; it carries no ECHO back-compat and never +> reads ECHO configs, state dirs, or vault markers. Persistent memory for Claude / CoWork sessions via the **CHORUS** 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/chorus.py`). The whole toolchain is **pure Python** (stdlib only), so it runs identically on Windows, macOS, and Linux — no bash, no platform-specific `date`. diff --git a/chorus-memory.plugin b/chorus-memory.plugin index ca9eeb2..5f32fb1 100644 Binary files a/chorus-memory.plugin and b/chorus-memory.plugin differ diff --git a/chorus-memory.plugin.src/skills/chorus-memory/references/routing-map.md b/chorus-memory.plugin.src/skills/chorus-memory/references/routing-map.md index 07aa28d..0b961fb 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/references/routing-map.md +++ b/chorus-memory.plugin.src/skills/chorus-memory/references/routing-map.md @@ -79,7 +79,6 @@ Lifecycle folders; `status:` frontmatter MUST equal the folder name (the linter | Path | Trigger | What lands | Distinct because | Method | |------|---------|------------|------------------|--------| | `_agent/chorus-vault.md` | Bootstrap / schema migration only | Marker: `schema_version`, bootstrap date | Plugin-owned probe; never hand-edited | GET / PUT | -| `_agent/echo-vault.md` | Pre-fork ECHO vault adoption (read-only probe) | Legacy ECHO marker, honored until the rename migration | CHORUS is a fork of ECHO; adopted vaults still carry the old marker | GET | | `_agent/context/current-context.md` | Active scope changes; task focus shifts | `## Scope`, `## Scope History`, priorities | Single *live* scope pointer, vs episodic which is a *past* record | PATCH / PUT | | `_agent/memory/semantic/operator-preferences.md` | A preference/pattern about the operator | Append under `## Observations`; promote to `## Fact / Pattern` when stable | The one curated profile; distinct from ad-hoc semantic notes | PATCH | | `_agent/memory/semantic/.md` | A durable fact/pattern that isn't an operator-preference | Semantic note | Timeless fact, vs episodic (time-stamped event) and working (transient) | PUT | diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/bootstrap.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/bootstrap.py index da937ca..ce41dec 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/bootstrap.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/bootstrap.py @@ -85,17 +85,14 @@ def main(argv: list[str] | None = None) -> int: print(f"bootstrap: scaffold not found at {SCAFFOLD}", file=sys.stderr) return 1 - legacy_marker = (not exists(chorus.MARKER_PATH)) and exists(chorus.LEGACY_MARKER_PATH) - if exists(chorus.MARKER_PATH) or legacy_marker: - probe = chorus.LEGACY_MARKER_PATH if legacy_marker else chorus.MARKER_PATH - status, body = chorus.request("GET", chorus.vault_url(probe)) + if exists(chorus.MARKER_PATH): + status, body = chorus.request("GET", chorus.vault_url(chorus.MARKER_PATH)) version = "unknown" if status == 200: version = next((ln.split(":", 1)[1].strip() for ln in body.decode(errors="replace").splitlines() if ln.startswith("schema_version:")), "unknown") - note = " [legacy ECHO marker]" if legacy_marker else "" - print(f"bootstrap: marker present{note} (schema_version={version}). Running repair pass (fills only missing files).") + print(f"bootstrap: marker present (schema_version={version}). Running repair pass (fills only missing files).") for folder in LEAVES: leaf_readme(folder, args.dry_run) @@ -109,13 +106,7 @@ def main(argv: list[str] | None = None) -> int: seed("_agent/context/current-context.md", SCAFFOLD / "anchors" / "current-context.seed.md", args.dry_run) seed("inbox/captures/inbox.md", SCAFFOLD / "anchors" / "inbox.seed.md", args.dry_run) seed("README.md", SCAFFOLD / "README.vault.md", args.dry_run) - if legacy_marker: - # Adopted pre-fork ECHO vault: leave the legacy marker as the single - # authoritative probe — the schema migration renames it. Two markers - # would make "which is authoritative" ambiguous. - print("bootstrap: legacy ECHO marker present — not writing the CHORUS marker (migrate.py renames it).") - else: - seed(chorus.MARKER_PATH, SCAFFOLD / "chorus-vault.md", args.dry_run) # marker LAST + seed(chorus.MARKER_PATH, SCAFFOLD / "chorus-vault.md", args.dry_run) # marker LAST print(f"bootstrap: done ({'DRY-RUN ' if args.dry_run else ''}{chorus.today()}).") print("bootstrap: next: create today's daily note + a bootstrap session log + heartbeat (see SKILL.md).") diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus.py index b92a96e..4a92eb0 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus.py @@ -101,21 +101,6 @@ MAX_WORKERS = max(1, int(os.environ.get("CHORUS_WORKERS", "8"))) LOCK_PATH = "_agent/locks/vault.lock" MARKER_PATH = "_agent/chorus-vault.md" -# Pre-fork ECHO vaults carry the old marker name. It is honored read-only as a -# bootstrap probe (vault-adoption path); the schema migration renames it. -LEGACY_MARKER_PATH = "_agent/echo-vault.md" - - -def marker_get(): - """GET the bootstrap marker: the CHORUS name first, then the legacy ECHO - name. Returns (path, status, body) — on a double miss, the CHORUS path with - its 404 (or error) result.""" - status, body = request("GET", vault_url(MARKER_PATH)) - if status == 404: - l_status, l_body = request("GET", vault_url(LEGACY_MARKER_PATH)) - if l_status == 200: - return LEGACY_MARKER_PATH, l_status, l_body - return MARKER_PATH, status, body class ChorusError(RuntimeError): @@ -655,10 +640,7 @@ def cmd_load() -> int: heartbeat_absent = False offline = False for label, path in targets: - if label == "marker": - path, status, body = marker_get() # falls back to a legacy ECHO marker - else: - status, body = request("GET", vault_url(path)) + status, body = request("GET", vault_url(path)) if status == 200: chorus_queue.cache_put(path, body) # refresh last-known-good for offline fallback print(f"===== {label}: {path} (HTTP 200) =====") diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_config.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_config.py index 291b787..c56d46b 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_config.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_config.py @@ -54,26 +54,6 @@ DEFAULT_OWNER = "" DEFAULT_BASE = "" DEFAULT_KEY = "" -# ---------------------------------------------------------------- legacy shim -# CHORUS is a fork of ECHO. For one major version, legacy ECHO_* environment -# variables keep working: at import time each ECHO_ is adopted as CHORUS_ -# whenever CHORUS_ is not itself set. Scripts only ever read CHORUS_*. -_LEGACY_ENV_SUFFIXES = ( - "OWNER", "BASE", "KEY", "CONFIG", "TODAY", "VERIFY", "LOCK_TTL", "TIMEOUT", - "WORKERS", "STATE_DIR", "CLIENT", "DUP_GATE", "REFLECT_MIN_TURNS", - "FRESH_HALF_LIFE", -) - - -def _alias_legacy_env() -> None: - for suffix in _LEGACY_ENV_SUFFIXES: - new, old = f"CHORUS_{suffix}", f"ECHO_{suffix}" - if not os.environ.get(new) and os.environ.get(old): - os.environ[new] = os.environ[old] - - -_alias_legacy_env() - TEMPLATE = { "owner": "Full Name — how the agent should refer to the vault owner (third person)", "endpoint": "https://your-obsidian-rest-endpoint.example.com", @@ -87,32 +67,15 @@ def claude_dir() -> Path: def config_path() -> Path: - """Absolute path to the machine-local config file (the CHORUS path — this is - where writes always land).""" + """Absolute path to the machine-local config file.""" override = os.environ.get("CHORUS_CONFIG") if override: return Path(override).expanduser() return claude_dir() / "chorus-memory" / "config.json" -def legacy_config_path() -> Path: - """The pre-fork ECHO config location, honored read-only as a fallback.""" - return claude_dir() / "echo-memory" / "config.json" - - -def read_config_path() -> Path: - """The config file to READ: the CHORUS path when overridden or present, - else a legacy ECHO config if one exists (fork adoption path). Writes never - go to the legacy path — `config set`/`import` land at config_path().""" - p = config_path() - if os.environ.get("CHORUS_CONFIG") or p.exists(): - return p - legacy = legacy_config_path() - return legacy if legacy.exists() else p - - def _from_file() -> dict: - p = read_config_path() + p = config_path() if not p.exists(): return {} try: @@ -203,9 +166,8 @@ def source(field: str) -> str: env = {"owner": "CHORUS_OWNER", "endpoint": "CHORUS_BASE", "key": "CHORUS_KEY"}[field] if os.environ.get(env): return f"{env} env" - rp = read_config_path() - if rp.exists() and _from_file().get(field): - return "config file" if rp == config_path() else "config file (legacy echo-memory path)" + if config_path().exists() and _from_file().get(field): + return "config file" if baked: return "baked-in (plugin)" return "unset" diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_doctor.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_doctor.py index d7ed9e6..b1dd3a8 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_doctor.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_doctor.py @@ -59,8 +59,7 @@ def run() -> int: return 1 # 3. reachability + auth + marker, in one GET of the bootstrap marker - # (marker_get falls back to a pre-fork ECHO marker on adopted vaults) - marker_path, status, body = chorus.marker_get() + status, body = chorus.request("GET", chorus.vault_url(chorus.MARKER_PATH)) if status == 0: line(False, "vault reachable", body.decode(errors="replace")[:120]) print("\ndoctor: endpoint unreachable — is Obsidian + the Local REST API running?") @@ -72,8 +71,7 @@ def run() -> int: text = body.decode(errors="replace") ver = next((ln.split(":", 1)[1].strip() for ln in text.splitlines() if ln.startswith("schema_version:")), "?") - legacy = " (legacy ECHO marker — run migrate.py to adopt)" if marker_path == chorus.LEGACY_MARKER_PATH else "" - line(True, "vault bootstrapped", f"schema_version={ver}{legacy}") + line(True, "vault bootstrapped", f"schema_version={ver}") else: line(status < 400, "marker fetch", f"HTTP {status}") diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_hook_stop.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_hook_stop.py index 754d7df..da8b353 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_hook_stop.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_hook_stop.py @@ -38,17 +38,7 @@ REASON = ( def state_marker(session_id: str) -> Path: - # Mirrors chorus_queue.state_dir() (this hook runs standalone, so the - # ECHO->CHORUS env aliasing in chorus_config may not have run): env first - # (either name), else ~/.chorus-memory, honoring a pre-fork ~/.echo-memory - # when the new dir doesn't exist yet. - override = os.environ.get("CHORUS_STATE_DIR") or os.environ.get("ECHO_STATE_DIR") - if override: - base = Path(override) - else: - new = Path.home() / ".chorus-memory" - legacy = Path.home() / ".echo-memory" - base = legacy if (not new.exists() and legacy.exists()) else new + base = Path(os.environ.get("CHORUS_STATE_DIR") or (Path.home() / ".chorus-memory")) return base / "hook-state" / f"{session_id}.reflect-nudged" diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_queue.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_queue.py index c3c9e1c..872c1ce 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_queue.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/chorus_queue.py @@ -37,16 +37,7 @@ MUTATING = {"PUT", "POST", "PATCH", "DELETE"} def state_dir() -> Path: - # Env first (chorus_config aliases a legacy ECHO_STATE_DIR at import); the - # default is ~/.chorus-memory, but an existing pre-fork ~/.echo-memory is - # honored when the new dir doesn't exist yet — so queued offline writes - # from an ECHO install aren't stranded by the rename. - override = os.environ.get("CHORUS_STATE_DIR") or os.environ.get("ECHO_STATE_DIR") - if override: - return Path(override) - new = Path.home() / ".chorus-memory" - legacy = Path.home() / ".echo-memory" - return legacy if (not new.exists() and legacy.exists()) else new + return Path(os.environ.get("CHORUS_STATE_DIR") or (Path.home() / ".chorus-memory")) def outbox_path() -> Path: diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/migrate.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/migrate.py index 5e1a804..354ecbe 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/migrate.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/migrate.py @@ -79,17 +79,8 @@ def do_or_show(apply: bool, desc: str, func=None) -> None: print(f"migrate: PLAN {desc}") -def find_marker() -> str: - """The vault's marker path: the CHORUS name, else a legacy pre-fork ECHO - marker (the rename itself is a future migration step).""" - for p in ("_agent/chorus-vault.md", "_agent/echo-vault.md"): - if get_text(p) is not None: - return p - return "_agent/chorus-vault.md" - - def marker_schema() -> int: - marker = get_text(find_marker()) + marker = get_text("_agent/chorus-vault.md") if marker is None: print("migrate: marker missing — vault not bootstrapped. Run bootstrap.py, not migrate.py.") raise SystemExit(3) @@ -155,9 +146,8 @@ def main(argv: list[str] | None = None) -> int: print("migrate: [3->4] _agent/index/ already exists (schema 3); no moves needed.") print("migrate: [3->4] then run `sweep.py --apply` to build _agent/index/recall-index.json.") - marker_path = find_marker() - do_or_show(args.apply, f"set {marker_path} schema_version -> {CURRENT_SCHEMA}", - lambda: chorus.cmd_fm(marker_path, "schema_version", str(CURRENT_SCHEMA))) + do_or_show(args.apply, f"set _agent/chorus-vault.md schema_version -> {CURRENT_SCHEMA}", + lambda: chorus.cmd_fm("_agent/chorus-vault.md", "schema_version", str(CURRENT_SCHEMA))) if args.apply: print(f"migrate: migration complete -> schema {CURRENT_SCHEMA}. Run sweep.py --apply, then vault_lint.py.") else: diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/routing.json b/chorus-memory.plugin.src/skills/chorus-memory/scripts/routing.json index 14f2657..efb58be 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/routing.json +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/routing.json @@ -31,7 +31,6 @@ { "id": "decisions-template", "pattern": "^decisions/decision-template\\.md$", "method": "PUT", "trigger": "Bootstrap seed only", "distinct_because": "Template, not a decision" }, { "id": "agent-marker", "pattern": "^_agent/chorus-vault\\.md$", "method": "PUT", "trigger": "Bootstrap / schema migration only", "distinct_because": "Plugin-owned probe; never hand-edited" }, - { "id": "agent-marker-legacy", "pattern": "^_agent/echo-vault\\.md$", "method": "GET", "trigger": "Pre-fork ECHO vault adoption (read-only probe)", "distinct_because": "Legacy ECHO marker honored until the rename migration" }, { "id": "agent-context", "pattern": "^_agent/context/[^/]+\\.md$", "method": "PATCH", "trigger": "Active scope changes / task bundles", "distinct_because": "Single live scope pointer + bundles" }, { "id": "agent-semantic", "pattern": "^_agent/memory/semantic/[^/]+\\.md$", "method": "PUT", "trigger": "A durable fact/pattern (incl. operator-preferences.md)", "distinct_because": "Timeless fact" }, { "id": "agent-episodic", "pattern": "^_agent/memory/episodic/[^/]+\\.md$", "method": "PUT", "trigger": "A record of what happened, when", "distinct_because": "Anchored to an event in time" }, diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/sweep.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/sweep.py index 9b5a16b..c18220a 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/sweep.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/sweep.py @@ -109,7 +109,7 @@ def main(argv: list[str] | None = None) -> int: apply = args.apply tag = "APPLY" if apply else "PLAN " - if get("_agent/chorus-vault.md") is None and get("_agent/echo-vault.md") is None: + if get("_agent/chorus-vault.md") is None: print("sweep: marker missing — vault not bootstrapped. Run bootstrap.py first.", file=sys.stderr) return 3 @@ -232,16 +232,12 @@ def main(argv: list[str] | None = None) -> int: links.add_one(tp, sp) # ---- 5. stamp schema ------------------------------------------------------ - marker_path = "_agent/chorus-vault.md" - marker = get(marker_path) - if marker is None: # adopted pre-fork ECHO vault — stamp the marker it has - marker_path = "_agent/echo-vault.md" - marker = get(marker_path) or "" + marker = get("_agent/chorus-vault.md") or "" cur = next((int(m) for m in re.findall(r"(?m)^schema_version:\s*(\d+)", marker)), 0) if cur < CURRENT_SCHEMA: print(f"sweep: {tag} schema_version {cur} -> {CURRENT_SCHEMA}") if apply: - chorus.cmd_fm(marker_path, "schema_version", str(CURRENT_SCHEMA)) + chorus.cmd_fm("_agent/chorus-vault.md", "schema_version", str(CURRENT_SCHEMA)) print(f"\nsweep: {'done — run vault_lint.py to confirm invariants' if apply else 'dry-run complete. Re-run with --apply to write.'}") return 0 diff --git a/chorus-memory.plugin.src/skills/chorus-memory/scripts/vault_lint.py b/chorus-memory.plugin.src/skills/chorus-memory/scripts/vault_lint.py index 51acf80..db451f4 100644 --- a/chorus-memory.plugin.src/skills/chorus-memory/scripts/vault_lint.py +++ b/chorus-memory.plugin.src/skills/chorus-memory/scripts/vault_lint.py @@ -145,7 +145,7 @@ def route_matchers(): def main() -> int: try: - if get("_agent/chorus-vault.md") is None and get("_agent/echo-vault.md") is None: + if get("_agent/chorus-vault.md") is None: print("vault-lint: marker missing — vault not bootstrapped (run bootstrap.py).", file=sys.stderr) return 3 except Exception as exc: