forked from jason/echo
f2bfd70dd4
The final phase of CHORUS-PLAN.md. Code- and doc-complete for group use; rc pends live deployment at chorusapi.mpm.to. - Repo README rewritten for CHORUS: group model (schema 5), concurrency posture, configuration, baked-build onboarding, layout, testing, 2.0 version history (ECHO history referenced at the fork tag). - Operator→member voice sweep across SKILL.md (incl. the trigger description, now group-aware), all references, 8 command docs, plugin README; member preferences / semantic layer / triage guidance describe the group model. - docs/ONBOARDING.md: member runbook — vault stand-up, per-member config, bake, deliver, self-onboarding first load, norms, offboarding. - docs/USAGE.md rewritten as the member usage guide. - New icon: chorus-icon.svg + 1024/512/64 PNGs (three voices, one center). - Removed ECHO-era assets (icons, spec PDF, perf briefs); ECHO planning docs retired to docs/history/. All preserved at echo-fork-point. - eval/README documents test_multimember.py. Manifest -> 2.0.0-rc.1; rebuilt chorus-memory.plugin. Verified: routing-sync, 25/25 unit, scaffold, 5 mock e2e suites, run_eval metrics green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
147 lines
12 KiB
Markdown
147 lines
12 KiB
Markdown
# chorus-memory — v2.0.0-rc.1
|
|
|
|
**CHORUS** is shared **group memory** for Claude / CoWork sessions: one Obsidian vault serving a whole team, driven over the [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api). Every member's sessions read the same knowledge graph and write to it with full attribution — where ECHO reflected one voice back, CHORUS is many voices in one performance.
|
|
|
|
Architected by **Jason Stedwell**. CHORUS is a group-based fork of [ECHO](https://git.alwisp.com/jason/echo) (the single-operator original, preserved at the `echo-fork-point` tag) and a **clean break** from it: fresh vaults on fresh machines, no ECHO back-compat. The conversion plan and settled design decisions live in [CHORUS-PLAN.md](CHORUS-PLAN.md); deployment endpoint: `https://chorusapi.mpm.to`.
|
|
|
|
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`.
|
|
|
|
This repository (`jason/chorus`) holds the plugin **source** (tracked tree at `chorus-memory.plugin.src/`), the built `chorus-memory.plugin` artifact (rebuilt on each version bump; versioned builds ship as Gitea releases), and a credential-free `eval/` test harness.
|
|
|
|
---
|
|
|
|
## The group model (schema 5)
|
|
|
|
One vault, many members. Each machine is configured with the **group** and its own **member id** (a kebab-case slug like `jason`), and every agent write is stamped `author: <member>`.
|
|
|
|
- **Shared by the group:** projects, areas, resources, decisions, the journal (one team timeline — every `## Agent Log` line is member-tagged `- YYYY-MM-DD [member]: …`), semantic/episodic/working memory including `group-profile.md` (what the team is + a `## Members` roster), a shared group inbox for unowned items, the entity/recall indexes, and the duplicate gate (two members can't create parallel notes for the same client — a blocked capture names whose entity it collided with).
|
|
- **Per member, no contention by construction:** `_agent/members/<member>/{preferences,current-context,heartbeat,inbox}.md` and `_agent/sessions/<member>/` — each member's profile, active scope, last-session pointer, personal captures, and session logs are theirs alone.
|
|
- **Self-onboarding:** a new member installs their plugin and runs a session — `load` detects the missing namespace, seeds it, and adds them to the roster.
|
|
- **Group lenses:** `recall --author <member>` filters the graph to one member's notes; `load --all-members` shows every member's scope and last session.
|
|
- **Visibility is by design:** one vault, one key — everything in it (including member preferences) is readable by the whole group. Truly private material belongs in a personal vault, not CHORUS.
|
|
|
|
## Concurrency posture
|
|
|
|
Namespacing removes most contention outright. What stays shared-write is built for interleaving: member-tagged whole-line-idempotent appends for logs/inboxes, and the machine indexes are guarded by an advisory lock with **bounded retry/backoff** (`CHORUS_LOCK_RETRIES`, default 4) plus a fresh re-read inside the critical section, so concurrent captures merge instead of clobber. Lock owner ids carry the member (`<member>-<client>-<pid>`), making contention attributable. Full model: `skills/chorus-memory/references/operating-contract.md`.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
The plugin ships **no** group, member, endpoint, or key — each machine supplies them via a machine-local JSON config (resolution in `scripts/chorus_config.py`):
|
|
|
|
- **File:** `~/.claude/chorus-memory/config.json` — honors `$CLAUDE_CONFIG_DIR`; path overridable with `$CHORUS_CONFIG`. Shape: `{ "group": …, "member": "<kebab-slug>", "endpoint": "https://…", "key": "…" }`. **`member` is required** — it attributes every write.
|
|
- **Per-field env override:** `CHORUS_GROUP`, `CHORUS_MEMBER`, `CHORUS_BASE` (endpoint), `CHORUS_KEY`.
|
|
- **Set up a machine:** `chorus.py config import <file>` adopts a config you've been handed; `chorus.py config set --group … --member … --endpoint … --key …` writes one from values; `config init` scaffolds a template; `chorus.py config` prints the resolved config (key redacted) with per-field sources.
|
|
- **First run:** an unconfigured machine reports `NOT CONFIGURED` (`load` exits `78`; `/chorus-doctor` flags it red) and the skill asks the member for their config before doing memory work. The config is never committed and never written into a vault note.
|
|
|
|
### Per-member baked builds — the onboarding mechanism
|
|
|
|
The CoWork sandbox doesn't bridge your real `~/.claude`, but the plugin itself is mounted every session — so each member's credentials travel *inside* their artifact:
|
|
|
|
```bash
|
|
python build.py --bake-key --from alice-config.json --label alice
|
|
# -> dist/chorus-memory-2.0.0-rc.1-alice.plugin (carries alice's member id + the vault key)
|
|
```
|
|
|
|
A *complete* baked set (endpoint + key + member) is **authoritative** — it can't be shadowed by stale env vars or leftover configs. The committed source ships zero credentials; baked artifacts land in `dist/` (gitignored) and are delivered directly to their one member, **never committed or published**. The full member-onboarding runbook is [docs/ONBOARDING.md](docs/ONBOARDING.md).
|
|
|
|
---
|
|
|
|
## Requirements
|
|
|
|
- **Python 3** in the session environment (stdlib only; `python3`, or `python` / `py -3` on Windows).
|
|
- Obsidian running on the backend with the Local REST API plugin enabled.
|
|
- HTTPS reachability to `https://chorusapi.mpm.to` from the Claude / CoWork session environment.
|
|
|
|
---
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
chorus/
|
|
├── README.md · CHANGELOG.md · CHORUS-PLAN.md ← this file · release log · conversion plan + decisions
|
|
├── docs/ ← ONBOARDING.md (member runbook) · USAGE.md · history/
|
|
├── build.py ← deterministic .plugin builder (--bake-key/--strip-key/--label/--outdir)
|
|
├── chorus-memory.plugin ← built, installable plugin (current pointer; versioned builds → Gitea releases)
|
|
├── chorus-memory.config.template.json ← blank config template (group/member/endpoint/key)
|
|
├── dist/ ← per-member baked artifacts (secret-bearing) — GITIGNORED, never committed
|
|
├── eval/ ← credential-free test harness; not bundled
|
|
│ ├── mock_olrapi.py · mock_olrapi_hifi.py ← deterministic REST-API mocks (+ fault injection / real PATCH semantics)
|
|
│ ├── run_eval.py ← retrieval / dedup / write-safety / durability metrics
|
|
│ ├── test_features.py · test_reflect.py · test_offline_queue.py · test_patch_semantics.py
|
|
│ ├── test_multimember.py ← 26-check two-member end-to-end suite (the group behavior)
|
|
│ └── results/latest.json
|
|
└── chorus-memory.plugin.src/ ← tracked source tree (the plugin — single canonical tree)
|
|
├── .claude-plugin/plugin.json ← manifest (name, version, description)
|
|
├── README.md ← plugin-level README (configuration, tooling)
|
|
├── hooks/hooks.json ← SessionStart auto-load · Stop reflection nudge
|
|
├── commands/ ← /chorus-{load,save,recall,triage,health,sweep,reflect,doctor}
|
|
└── skills/chorus-memory/
|
|
├── SKILL.md ← operating procedure (authoritative)
|
|
├── references/ ← operating-contract · bootstrap · vault-layout · routing-map · api-reference
|
|
├── scripts/ ← pure-Python toolchain (chorus.py client, bootstrap/migrate/sweep,
|
|
│ vault_lint, routing.json manifest, capture/recall/triage/reflect ops)
|
|
└── scaffold/ ← verbatim seeds bootstrap writes into the vault
|
|
(group-profile, group-inbox, member-* anchors, templates, marker)
|
|
```
|
|
|
|
**Division of responsibility:** `SKILL.md` owns day-to-day procedure; `references/operating-contract.md` owns durable principles, safety rules, and the N-writer concurrency model; `scripts/routing.json` is the machine-readable source of truth for what may be written where (`references/routing-map.md` is its human authority; `check_routing.py` keeps them in sync mechanically).
|
|
|
|
---
|
|
|
|
## Vault layout (data only, root-addressed)
|
|
|
|
```
|
|
/vault/
|
|
├── inbox/ ← captures/group.md (unowned, member-tagged) · imports/ · processing-log/
|
|
├── journal/ ← daily/ (shared team timeline, member-tagged Agent Log) · weekly/ monthly/ quarterly/ annual/
|
|
├── projects/ ← active/ incubating/ on-hold/ archived/ (folder == status:)
|
|
├── areas/ ← business/ personal/ learning/ systems/
|
|
├── resources/ ← people/ companies/ concepts/ references/ meetings/
|
|
├── decisions/ ← by-date/ (ADR-style)
|
|
└── _agent/
|
|
├── chorus-vault.md ← bootstrap marker (schema_version: 5, plugin-owned)
|
|
├── members/<member>/ ← preferences.md · current-context.md · heartbeat.md · inbox.md (per member)
|
|
├── sessions/<member>/ ← YYYY-MM-DD-HHMM-<slug>.md (member-namespaced session logs)
|
|
├── memory/ ← working/ · episodic/ · semantic/ (incl. group-profile.md)
|
|
├── context/ health/ templates/ skills/ index/ locks/
|
|
```
|
|
|
|
The complete map — every write destination with its trigger and why it's distinct — is `skills/chorus-memory/references/routing-map.md`. Frontmatter conventions (incl. the required `author:` on agent-written notes) are in `references/vault-layout.md`.
|
|
|
|
---
|
|
|
|
## Bundled tooling & commands
|
|
|
|
`scripts/chorus.py` is the validated client: keep-alive connection-pooled, HTTP-status-checked (failed writes exit non-zero), retrying, read-back-verified PUTs, whole-line idempotent appends. High-level ops collapse the write discipline into one call: **`capture`** (route + complete frontmatter incl. `author:` + index + auto-link + member-tagged log line, with a pre-write duplicate gate), **`resolve`**, **`recall [--author <member>]`** (hybrid BM25 + graph over the shared corpus), **`link`**, **`triage`** (both inboxes, attributed audit trail), **`load [--all-members]`** (8-read cold start + self-onboarding), plus `scope`, `lock/unlock`, `config`, `doctor`, and the vault maintenance scripts (`bootstrap.py`, `migrate.py`, `sweep.py`, `vault_lint.py`).
|
|
|
|
Slash commands: `/chorus-load`, `/chorus-save`, `/chorus-recall`, `/chorus-triage`, `/chorus-health`, `/chorus-sweep`, `/chorus-reflect`, `/chorus-doctor`. Session hooks auto-load memory at SessionStart and nudge reflection once per substantive session at Stop.
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
Everything runs offline against deterministic mocks — no credentials, no live vault:
|
|
|
|
```bash
|
|
python3 chorus-memory.plugin.src/skills/chorus-memory/scripts/test_chorus_client.py # unit + routing-sync guard
|
|
python3 chorus-memory.plugin.src/skills/chorus-memory/scripts/test_v1_scaffold.py # module interfaces + config
|
|
python3 eval/test_features.py # capture/resolve/recall/link/sweep end-to-end
|
|
python3 eval/test_multimember.py # 26-check two-member group lifecycle
|
|
python3 eval/test_offline_queue.py eval/test_patch_semantics.py eval/test_reflect.py
|
|
python3 eval/run_eval.py # retrieval / dedup / write-safety / durability metrics
|
|
```
|
|
|
|
## Version history
|
|
|
|
| Version | Highlights |
|
|
|---------|-----------|
|
|
| **2.0.0-rc.1** | Phase 5: full group-voice docs, member onboarding runbook, CHORUS icon. Plan complete — pending live deployment at `chorusapi.mpm.to`. |
|
|
| **2.0.0-alpha.4** | Phase 4 hardening: bounded lock retry on index writes, attributed duplicate gate, `recall --author`, `load --all-members`, N-writer concurrency contract, committed two-member e2e suite. |
|
|
| **2.0.0-alpha.3** | Phase 3 (the core): schema 5 per-member namespacing, group profile + group inbox, self-onboarding `load`, member-tagged shared writes, per-member lint, migrate 4→5. |
|
|
| **2.0.0-alpha.2** | Phase 2: `{group, member, endpoint, key}` config (member required), `author:` stamped on every agent write, `missing-author` lint, member-attributed locks/queue, per-member baked builds. |
|
|
| **2.0.0-alpha.1** | Phase 1: mechanical echo→chorus rename; clean break from ECHO (no back-compat). |
|
|
|
|
The pre-fork ECHO history (v0.3 → v1.5.1) lives in the [`jason/echo`](https://git.alwisp.com/jason/echo) repo and at this repo's `echo-fork-point` tag.
|