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:
+6
-10
@@ -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
|
||||
|
||||
+6
-13
@@ -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
|
||||
|
||||
|
||||
+21
-15
@@ -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>/`, member-prefixed Agent Log
|
||||
lines, `routing.json` route rows (with `<member>` 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/<id>/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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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,12 +106,6 @@ 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
|
||||
|
||||
print(f"bootstrap: done ({'DRY-RUN ' if args.dry_run else ''}{chorus.today()}).")
|
||||
|
||||
@@ -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,9 +640,6 @@ 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))
|
||||
if status == 200:
|
||||
chorus_queue.cache_put(path, body) # refresh last-known-good for offline fallback
|
||||
|
||||
@@ -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