1
0
forked from jason/echo

Remove ECHO back-compat shims — CHORUS is a clean break

Decision: CHORUS deploys as fresh vaults on new machines for different
users, so no ECHO adoption path is needed. Removed the Phase-1 shims:
- chorus_config: legacy ECHO_* env aliasing and the read-only
  ~/.claude/echo-memory/config.json fallback
- chorus_queue / chorus_hook_stop: ~/.echo-memory state-dir fallback
- load/doctor/bootstrap/vault_lint/sweep/migrate: the _agent/echo-vault.md
  marker dual-probe; routing drops agent-marker-legacy (36 routes)
- .gitignore, README, CHANGELOG, CHORUS-PLAN.md updated (decision #6;
  Phase 3 no longer migrates the live ECHO vault — fresh bootstrap only)

All suites green: 25/25 unit, scaffold, routing-sync, 4 mock e2e,
run_eval metrics unchanged. Rebuilt chorus-memory.plugin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 13:42:14 -05:00
parent d366ca4032
commit d920f8821f
16 changed files with 58 additions and 165 deletions
@@ -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/<slug>.md` | A durable fact/pattern that isn't an operator-preference | Semantic note | Timeless fact, vs episodic (time-stamped event) and working (transient) | PUT |
@@ -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).")
@@ -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) =====")
@@ -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_<X> is adopted as CHORUS_<X>
# whenever CHORUS_<X> 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"
@@ -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}")
@@ -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"
@@ -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:
@@ -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:
@@ -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" },
@@ -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
@@ -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: