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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user