2026-06-25 17:54:03 -05:00
|
|
|
|
# Changelog
|
|
|
|
|
|
|
2026-07-21 15:16:13 -05:00
|
|
|
|
## 2.0.0-alpha.3 — Phase 3 (per-member namespacing — schema 5)
|
|
|
|
|
|
|
|
|
|
|
|
The core of the group conversion: one shared vault, per-member namespaces for all
|
|
|
|
|
|
churn-prone state. A CHORUS vault now bootstraps multi-member from empty.
|
|
|
|
|
|
|
|
|
|
|
|
### Added — vault layout (schema 5)
|
|
|
|
|
|
- **`_agent/members/<member>/`** — each member's `preferences.md`,
|
|
|
|
|
|
`current-context.md` (scope + history), `heartbeat.md` (last-session pointer,
|
|
|
|
|
|
written at session end), and `inbox.md`. Members never contend on each other's
|
|
|
|
|
|
churn-prone files.
|
|
|
|
|
|
- **`_agent/sessions/<member>/YYYY-MM-DD-HHMM-<slug>.md`** — member-namespaced
|
|
|
|
|
|
session logs (no same-minute collisions, filterable per member).
|
|
|
|
|
|
- **`_agent/memory/semantic/group-profile.md`** — shared group facts, conventions,
|
|
|
|
|
|
and a `## Members` roster (members are rostered automatically at onboarding).
|
|
|
|
|
|
- **`inbox/captures/group.md`** — shared inbox for unowned member-tagged items;
|
|
|
|
|
|
whoever triages first routes them.
|
|
|
|
|
|
|
|
|
|
|
|
### Added — behavior
|
|
|
|
|
|
- **Self-onboarding**: `chorus.py load` detects a bootstrapped vault that lacks THIS
|
|
|
|
|
|
member's namespace and stands it up automatically (anchors + sessions dir +
|
|
|
|
|
|
roster line). New members install their baked plugin and just start.
|
|
|
|
|
|
- `load` is now 8 orientation reads: marker → group profile → my preferences /
|
|
|
|
|
|
context / heartbeat → today's shared daily note → my inbox → group inbox; the
|
|
|
|
|
|
heartbeat fallback lists MY sessions dir.
|
|
|
|
|
|
- `scope show/set` operates on the member's own current-context; drift counts only
|
|
|
|
|
|
that member's sessions.
|
|
|
|
|
|
- **Member-tagged shared writes**: daily `## Agent Log` lines and entity-update
|
|
|
|
|
|
dated blocks are written as `- YYYY-MM-DD [member]: …`; `capture --inbox` lands
|
|
|
|
|
|
in the member's own inbox; triage lists BOTH inboxes (tagged mine/group) and
|
|
|
|
|
|
writes member-attributed audit lines.
|
|
|
|
|
|
- `bootstrap.py` seeds group anchors + the configured member's namespace
|
|
|
|
|
|
(`member_bootstrap()` is reusable); the marker is schema 5; `migrate.py` gains a
|
|
|
|
|
|
4→5 step for alpha vaults (moves pre-5 anchors into the configured member's
|
|
|
|
|
|
namespace; no ECHO adoption path).
|
|
|
|
|
|
- `routing.json`: new `member-anchors`, `inbox-group`, member-segmented sessions
|
|
|
|
|
|
routes; the old single-user anchor paths (shared inbox, operator-preferences,
|
|
|
|
|
|
current-context, heartbeat, flat sessions) are **retired**. `vault_lint` runs
|
|
|
|
|
|
aging-inbox and scope-drift checks per member across all members found in the
|
|
|
|
|
|
vault. Docs (SKILL, vault-layout, routing-map, bootstrap) updated to match.
|
|
|
|
|
|
|
|
|
|
|
|
### Verified
|
|
|
|
|
|
- All suites green (25/25 unit, scaffold, routing-sync ×36 routes, 4 mock e2e,
|
|
|
|
|
|
run_eval metrics unchanged) plus a 19-check two-member end-to-end smoke:
|
|
|
|
|
|
jason bootstraps → alice self-onboards via load → interleaved member-tagged
|
|
|
|
|
|
daily log → cross-member entity update attributed → independent scopes →
|
|
|
|
|
|
triage separates personal vs group items → lint clean.
|
|
|
|
|
|
|
2026-07-21 14:00:59 -05:00
|
|
|
|
## 2.0.0-alpha.2 — Phase 2 (identity plumbing)
|
|
|
|
|
|
|
|
|
|
|
|
Every write is now attributed to a member. Vault layout unchanged (that's Phase 3).
|
|
|
|
|
|
|
|
|
|
|
|
### Added
|
|
|
|
|
|
- **Config schema is now `{group, member, endpoint, key}`** (was `{owner, endpoint,
|
|
|
|
|
|
key}`). `member` is REQUIRED — a kebab-case slug (`^[a-z0-9][a-z0-9-]{0,31}$`)
|
|
|
|
|
|
identifying who this machine writes as; `group` is the descriptive display name for
|
|
|
|
|
|
the team sharing the vault. Env: `CHORUS_GROUP` / `CHORUS_MEMBER`. `config set`
|
|
|
|
|
|
gains `--group`/`--member` (with slug validation); `config`/`doctor` report both
|
|
|
|
|
|
with sources; an unconfigured member fails loud (`NOT CONFIGURED`, exit 78).
|
|
|
|
|
|
- **`author:` frontmatter on every agent write.** `capture` (`_build_note`) stamps
|
|
|
|
|
|
the configured member; bootstrap stamps seeded anchors via a new `{{MEMBER}}`
|
|
|
|
|
|
substitution; all 8 scaffold templates gain an `author:` line; the canonical
|
|
|
|
|
|
frontmatter block (vault-layout.md, SKILL.md) documents it.
|
|
|
|
|
|
- **`missing-author` lint check** — `/chorus-health` flags any `agent_written: true`
|
|
|
|
|
|
note without an `author:` (the plugin-owned bootstrap marker is exempt).
|
|
|
|
|
|
- **Member-attributed concurrency**: the advisory-lock owner id is now
|
|
|
|
|
|
`<member>-<client>-<pid>` (`auto_owner`), and offline-queue outbox records carry
|
|
|
|
|
|
a `member` field for audit.
|
|
|
|
|
|
- **Per-member baked builds**: `build.py --bake-key` now bakes group/member/endpoint/
|
|
|
|
|
|
key (`--group`/`--member` flags; member required + slug-validated), so each
|
|
|
|
|
|
delivered artifact knows who it writes as.
|
|
|
|
|
|
|
|
|
|
|
|
### Verified
|
|
|
|
|
|
- All suites green (25/25 unit incl. new config/member-validation checks, scaffold,
|
|
|
|
|
|
routing-sync, 4 mock e2e incl. a capture-stamps-author assertion, run_eval metrics
|
|
|
|
|
|
unchanged) plus 8 end-to-end smoke checks (bootstrap stamping, lint flagging,
|
|
|
|
|
|
doctor reporting, NOT-CONFIGURED without member, slug rejection).
|
|
|
|
|
|
|
2026-07-21 13:35:51 -05:00
|
|
|
|
## 2.0.0-alpha.1 — CHORUS fork, Phase 1 (mechanical rename)
|
|
|
|
|
|
|
|
|
|
|
|
CHORUS is a group-based fork of ECHO (`jason/echo`, v1.5.1, schema 4 — preserved at the
|
|
|
|
|
|
`echo-fork-point` tag). Phase 1 is the identity rename with no behavior change; see
|
|
|
|
|
|
`CHORUS-PLAN.md` for the full conversion plan.
|
|
|
|
|
|
|
|
|
|
|
|
### Changed
|
|
|
|
|
|
- Plugin renamed `echo-memory` → `chorus-memory`; skill dir, all 16 `echo*.py` modules
|
|
|
|
|
|
(`chorus.py`, `chorus_config.py`, …), `EchoError` → `ChorusError`, the eight slash
|
|
|
|
|
|
commands (`/echo-*` → `/chorus-*`), hook script paths, and all docs/scaffold seeds.
|
|
|
|
|
|
- Env vars renamed `ECHO_*` → `CHORUS_*`; config moves to
|
|
|
|
|
|
`~/.claude/chorus-memory/config.json`; local state dir to `~/.chorus-memory/`;
|
|
|
|
|
|
bootstrap marker to `_agent/chorus-vault.md`; manifest version → 2.0.0-alpha.1.
|
|
|
|
|
|
- Docs' endpoint references now `chorusapi.mpm.to`.
|
|
|
|
|
|
|
2026-07-21 13:42:14 -05:00
|
|
|
|
### 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.)
|
2026-07-21 13:35:51 -05:00
|
|
|
|
|
|
|
|
|
|
### 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 /
|
2026-07-21 13:42:14 -05:00
|
|
|
|
0 silent failures / 0 lost offline writes).
|
2026-07-21 13:35:51 -05:00
|
|
|
|
|
2026-07-03 13:03:07 -05:00
|
|
|
|
## 1.5.1
|
|
|
|
|
|
|
|
|
|
|
|
### Fixed — duplicate gate over-firing on vault-common tokens and cross-kind names
|
|
|
|
|
|
|
|
|
|
|
|
Live 1.5.0 use surfaced a false positive: creating the decision "echo-memory 1.5.0
|
|
|
|
|
|
improvement set" was blocked by the archived project `echo-v-05` (score 1.0), because
|
|
|
|
|
|
`score = overlap / min(|tokens|)` lets any entity whose single distinctive token
|
|
|
|
|
|
appears in the new title score 1.0 — and "echo" appears across many entities in an
|
|
|
|
|
|
echo-centric vault.
|
|
|
|
|
|
|
|
|
|
|
|
New `echo_index.gate_candidates()` (backed by `token_df()`) applies two precision
|
|
|
|
|
|
rules before blocking; `capture` gates through it. `fuzzy_candidates()` — the advisory
|
|
|
|
|
|
warning list — is unchanged:
|
|
|
|
|
|
|
|
|
|
|
|
- **Same-kind only.** A cross-kind name collision (a decision titled after its
|
|
|
|
|
|
project, a meeting named for a company) is intentional naming, not a duplicate.
|
|
|
|
|
|
Cross-kind lookalikes still warn, never block.
|
|
|
|
|
|
- **No single-common-token blocks.** A lone shared token gates only when it is unique
|
|
|
|
|
|
to that entity (vault df == 1). Multi-token overlaps still gate even when the
|
|
|
|
|
|
individual tokens are common.
|
|
|
|
|
|
|
|
|
|
|
|
Exit-76 / `--merge-into` / `--force` semantics unchanged. Tests: 3 new offline unit
|
|
|
|
|
|
tests (`gate_candidates`), end-to-end gate cases restructured + 2 new (cross-kind
|
|
|
|
|
|
no-gate, common-token no-gate); verified against the live vault (the original false
|
|
|
|
|
|
positive now creates cleanly; a same-kind multi-token lookalike still gates).
|
|
|
|
|
|
|
2026-07-03 12:57:52 -05:00
|
|
|
|
## 1.5.0
|
|
|
|
|
|
|
|
|
|
|
|
Six improvements from the July 2026 review + the frontmatter-drift field report.
|
|
|
|
|
|
|
|
|
|
|
|
### Fixed — capture-on-update kept only the first body line (silent data loss)
|
|
|
|
|
|
|
|
|
|
|
|
`capture` on an existing entity appended `- <date>: <first line>` and **discarded the
|
|
|
|
|
|
rest of the body**. It now appends the whole body as a dated block — first line on the
|
|
|
|
|
|
bullet, continuation lines indented under it. Idempotency (bullet-per-day) unchanged.
|
|
|
|
|
|
|
|
|
|
|
|
### Added — frontmatter completeness (prevent → tool → detect → remediate)
|
|
|
|
|
|
|
|
|
|
|
|
A field audit found 18/71 entity notes missing `status`/`tags`; the drift was created
|
|
|
|
|
|
at write time and invisible to the tooling. Four coordinated changes, driven by one
|
|
|
|
|
|
data source (`KIND_STATUS` / `KIND_REQUIRED_FM` in `echo_index.py`):
|
|
|
|
|
|
|
|
|
|
|
|
- **`capture`** stamps a kind-appropriate default `status` (all kinds, not just
|
|
|
|
|
|
projects) and seeds `tags` with the note's kind; new `--tags a,b` enriches it.
|
|
|
|
|
|
Reflect/triage proposals accept a `"tags"` field.
|
|
|
|
|
|
- **`echo.py fm`** is now **create-or-replace**: on `400 invalid-target` (missing key)
|
|
|
|
|
|
it surgically inserts the one line into the frontmatter block and re-PUTs, verified —
|
|
|
|
|
|
it previously failed on exactly the notes that needed repair.
|
|
|
|
|
|
- **`vault_lint.py`** gains a kind-scoped `incomplete-frontmatter` check
|
|
|
|
|
|
(missing/empty `status`/`tags` on entity notes; block-style lists handled).
|
|
|
|
|
|
- **`sweep.py --apply`** backfills flagged notes (kind-default status + baseline tag).
|
|
|
|
|
|
|
|
|
|
|
|
### Added — pre-write duplicate gate in capture
|
|
|
|
|
|
|
|
|
|
|
|
A strong fuzzy candidate (score ≥ 0.6, env `ECHO_DUP_GATE`) now **stops** `capture`
|
|
|
|
|
|
before it creates a near-duplicate (exit `76` + candidate list) instead of warning
|
|
|
|
|
|
after the file exists. `--merge-into <slug>` routes the capture as an update to the
|
|
|
|
|
|
existing entity (mention learned as an alias); `--force` creates anyway. Weak
|
|
|
|
|
|
resemblances still create + warn as before.
|
|
|
|
|
|
|
|
|
|
|
|
### Changed — recall corpus + ranking (recall-index schema 2)
|
|
|
|
|
|
|
|
|
|
|
|
- The BM25 corpus now includes **session logs and journal notes** (down-weighted 0.5 /
|
|
|
|
|
|
0.4 so entity notes win ties) — past discussions are findable without having been
|
|
|
|
|
|
promoted into an entity note. `echo.py put` keeps the index current for corpus paths.
|
|
|
|
|
|
- Ranking fuses BM25 with **freshness** (half-life decay on `updated:`, floored at 0.6,
|
|
|
|
|
|
env `ECHO_FRESH_HALF_LIFE`) and **status** (`active` ×1.1, `on-hold` ×0.9,
|
|
|
|
|
|
`archived` ×0.75) — a stale archived note no longer outranks the live one.
|
|
|
|
|
|
- Hits print `updated:`/`status:`; `recall --json` emits structured results.
|
|
|
|
|
|
- The recall index carries per-doc meta (schema 2); an old index is discarded and
|
|
|
|
|
|
rebuilt automatically on the next recall/sweep.
|
|
|
|
|
|
|
|
|
|
|
|
### Added — session hooks (self-firing load & reflect)
|
|
|
|
|
|
|
|
|
|
|
|
New `hooks/hooks.json` + two hook scripts. **SessionStart** runs `echo.py load` and
|
|
|
|
|
|
injects the output as context (skips resume/compact; NOT-CONFIGURED degrades to an
|
|
|
|
|
|
instruction, never an error). **Stop** nudges once per substantive session (≥5 user
|
|
|
|
|
|
turns, env `ECHO_REFLECT_MIN_TURNS`) when nothing was reflected or session-logged —
|
|
|
|
|
|
reflection itself still previews and asks before writing. Both use the 1.4.2 CoWork
|
|
|
|
|
|
path fallback and always exit 0.
|
|
|
|
|
|
|
|
|
|
|
|
### Added — one-tap inbox triage + `--json` on read verbs
|
|
|
|
|
|
|
|
|
|
|
|
New `echo.py triage`: `--list --json` parses the inbox into `{line, date, text,
|
|
|
|
|
|
age_days}`; a proposals file (reflect schema + `"line"`) is classified → previewed →
|
|
|
|
|
|
applied via `capture`, with the `inbox/processing-log/` audit line written per move
|
|
|
|
|
|
(originals never deleted). `/echo-triage` rewritten around it. Also: `--json` for
|
|
|
|
|
|
`recall`, `link`, and `scope show` (`resolve`/`capture` already had it).
|
|
|
|
|
|
|
|
|
|
|
|
Tests: +22 feature cases (mock end-to-end incl. hooks), patch-semantics updated for
|
|
|
|
|
|
the new `fm` contract; all suites green. Docs: SKILL.md, README, command docs updated.
|
|
|
|
|
|
|
2026-06-26 20:05:47 -05:00
|
|
|
|
## 1.4.2
|
|
|
|
|
|
|
|
|
|
|
|
### Fixed — CoWork sandbox plugin-path resolution
|
|
|
|
|
|
|
|
|
|
|
|
In a remote CoWork sandbox, `${CLAUDE_PLUGIN_ROOT}` can resolve to a host path the
|
|
|
|
|
|
sandbox can't reach (e.g. `/var/folders/…`), while the plugin is actually mounted under
|
|
|
|
|
|
`…/mnt/.remote-plugins/…`. Every `python3 "${CLAUDE_PLUGIN_ROOT}/…"` invocation then
|
|
|
|
|
|
failed until the agent manually located the real path. (Root cause is the harness env
|
|
|
|
|
|
var, not the plugin — the scripts always used the variable correctly, never a hardcoded
|
|
|
|
|
|
path — but the plugin now self-heals.)
|
|
|
|
|
|
|
|
|
|
|
|
- SKILL.md and all eight slash commands resolve the scripts path with a fallback:
|
|
|
|
|
|
prefer `${CLAUDE_PLUGIN_ROOT}`, else locate the copy under
|
|
|
|
|
|
`/sessions/*/mnt/.remote-plugins/*/skills/echo-memory/scripts/`. Reused via
|
|
|
|
|
|
`$ECHO`/`$LINT`/`$SWEEP`/`$SDIR`; on a normal host the primary path always wins, so
|
|
|
|
|
|
behaviour is unchanged.
|
|
|
|
|
|
- `echo-health` / `echo-sweep` `allowed-tools` broadened to match the resolved
|
|
|
|
|
|
(variable-dir) invocation and the `ls`/`dirname` resolver steps.
|
|
|
|
|
|
|
2026-06-26 15:39:17 -05:00
|
|
|
|
## 1.4.1
|
|
|
|
|
|
|
|
|
|
|
|
### Fixed — baked credentials now win unconditionally
|
|
|
|
|
|
|
|
|
|
|
|
A per-user baked artifact could still be prompted for credentials on install. The
|
|
|
|
|
|
baked `DEFAULT_*` tier was lowest priority, so a stale `ECHO_*` env var or a
|
|
|
|
|
|
leftover/placeholder `~/.claude/echo-memory/config.json` shadowed the baked key —
|
|
|
|
|
|
even an unedited `"<placeholder>"` value won over it, flipping `is_configured()` to
|
|
|
|
|
|
false and triggering the first-run "ask the operator" flow despite valid baked creds.
|
|
|
|
|
|
|
|
|
|
|
|
- `echo_config.load()` now treats a **complete** baked set (endpoint + key both
|
|
|
|
|
|
present) as authoritative: it wins over env vars and the config file and cannot be
|
|
|
|
|
|
shadowed. The generic (unbaked) plugin keeps its env → file → first-run flow.
|
|
|
|
|
|
- Added `baked_complete()`; `source()` reports `baked-in (plugin)` for that tier.
|
|
|
|
|
|
|
|
|
|
|
|
### Security — untracked leaked baked artifacts
|
|
|
|
|
|
|
|
|
|
|
|
- Two labeled baked artifacts (`echo-memory-1.4.0-jason.plugin`,
|
|
|
|
|
|
`echo-memory-1.4.0-gretchen.plugin`), each carrying a live vault bearer token, had
|
|
|
|
|
|
been committed at the repo root. They are now `git rm --cached`-removed and
|
|
|
|
|
|
`.gitignore` gains `echo-memory-*-*.plugin` so labeled builds can't be re-committed
|
|
|
|
|
|
(the version-only pointer `echo-memory.plugin` and `echo-memory-<ver>.plugin` stay
|
|
|
|
|
|
tracked). Tokens remain in prior history — rotate if the repo was ever shared.
|
|
|
|
|
|
|
2026-06-25 17:54:03 -05:00
|
|
|
|
## 1.4.0
|
|
|
|
|
|
|
|
|
|
|
|
### Added — per-user baked-key builds (CoWork zero-touch)
|
|
|
|
|
|
|
|
|
|
|
|
The plugin is the only thing reliably mounted into a CoWork session (the sandbox's
|
|
|
|
|
|
`~/.claude` is synthetic, not your real one), so the config now *can* travel inside
|
|
|
|
|
|
the artifact. `echo_config` gains a lowest-priority fallback tier — the
|
|
|
|
|
|
`DEFAULT_OWNER` / `DEFAULT_BASE` / `DEFAULT_KEY` constants — **empty in source**, so
|
|
|
|
|
|
the committed tree still ships zero credentials.
|
|
|
|
|
|
|
|
|
|
|
|
- `build.py --bake-key --from <config.json> [--label <name>]` substitutes a single
|
|
|
|
|
|
user's owner/endpoint/key into those constants, producing a per-user artifact that
|
|
|
|
|
|
needs **no config file and no per-session paste**. Values may also come from
|
|
|
|
|
|
`--owner/--endpoint/--key` or `ECHO_OWNER/ECHO_BASE/ECHO_KEY`.
|
|
|
|
|
|
- Baked builds default to `dist/` (gitignored) and never touch the shared
|
|
|
|
|
|
`echo-memory.plugin` pointer.
|
|
|
|
|
|
- `build.py --strip-key` now force-blanks the same constants (guaranteed token-free).
|
|
|
|
|
|
- `config show` / `doctor` report `[baked-in default]` when a field resolves from the
|
|
|
|
|
|
baked tier.
|
|
|
|
|
|
|
|
|
|
|
|
### Changed — artifact hygiene
|
|
|
|
|
|
|
2026-06-26 15:39:17 -05:00
|
|
|
|
- `.gitignore` excludes `dist/` and the filled-in `echo-memory.config.json`, where
|
|
|
|
|
|
baked builds and the key file are meant to live. A baked artifact carries a vault
|
|
|
|
|
|
key and must be **delivered directly to that one user, never committed, pushed, or
|
|
|
|
|
|
published.** (Note: labeled baked artifacts built at the repo *root* were not yet
|
|
|
|
|
|
guarded — see 1.4.1.)
|
2026-06-25 17:54:03 -05:00
|
|
|
|
|
|
|
|
|
|
### Resolution order (unchanged for hosts)
|
|
|
|
|
|
|
|
|
|
|
|
`env` → `$ECHO_CONFIG` file → canonical `~/.claude/echo-memory/config.json` →
|
|
|
|
|
|
**baked `DEFAULT_*`**. On a normal desktop the empty defaults mean behaviour is
|
|
|
|
|
|
identical to 1.3.x; the baked tier only matters in per-user artifacts.
|