1
0
forked from jason/echo
Files
jason f2bfd70dd4 Phase 5: group-voice docs, onboarding runbook, CHORUS icon — v2.0.0-rc.1
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>
2026-07-21 15:41:58 -05:00

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.