1
0
forked from jason/echo

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>
This commit is contained in:
2026-07-21 15:41:58 -05:00
parent 1a7690426f
commit f2bfd70dd4
36 changed files with 324 additions and 807 deletions
@@ -1,11 +1,11 @@
---
name: chorus-memory
description: Use the CHORUS Obsidian vault as the operator's persistent memory across Claude/CoWork sessions. Use whenever the operator asks to remember, save, note, log, or capture anything durable — facts, preferences, decisions, schedule changes, commitments — or asks what Claude knows about them, what was discussed or decided before, to check their notes, load their memory/profile/context, add to their inbox, or to track a new project. Trigger even without memory phrasing when the request implies recalling or persisting state across sessions ("pick up where we left off", "anything on X before my meeting?"). Also use proactively at the start of substantive work sessions to load context and at the end to log outcomes. Do NOT use for a different owner's vault, Obsidian/REST-API development questions, "memory" meaning RAM, timed reminders, email or local-file lookups, generic second-brain advice, or notes the operator routes to a specific other file or app.
description: Use the shared CHORUS Obsidian vault as the group's persistent memory across Claude/CoWork sessions — one vault, many members, every write attributed to the configured member. Use whenever the member asks to remember, save, note, log, or capture anything durable — facts, preferences, decisions, schedule changes, commitments — or asks what Claude knows about them or the team, what was discussed or decided before (by anyone in the group), to check their notes, load their memory/profile/context, add to their or the group's inbox, to see what other members have been working on, or to track a new project. Trigger even without memory phrasing when the request implies recalling or persisting state across sessions ("pick up where we left off", "anything on X before my meeting?", "did anyone already look into this?"). Also use proactively at the start of substantive work sessions to load context and at the end to log outcomes. Do NOT use for vaults outside this group, Obsidian/REST-API development questions, "memory" meaning RAM, timed reminders, email or local-file lookups, generic second-brain advice, or notes the member routes to a specific other file or app.
---
# CHORUS Memory
Use the **CHORUS** Obsidian vault as persistent memory. Read context accumulated across sessions; write things the operator asks to be remembered.
Use the **CHORUS** Obsidian vault as persistent memory. Read context accumulated across sessions; write things the member asks to be remembered.
The vault is shared **group memory**: one vault, many members. Each machine is configured with the **group** (who shares the vault) and this machine's **member id** (who is writing — stamped as `author:` on every agent write), in `~/.claude/chorus-memory/config.json`. Write memory in third person about people ("Jason prefers X", not "I prefer X") so the vault stays readable by every member and agent.
@@ -109,7 +109,7 @@ python3 "$CHORUS" lock "cc-<session-id>" # exit 75 if another session hold
python3 "$CHORUS" unlock "cc-<session-id>"
```
Use a stable `<session-id>` for both calls (any unique string for this session). The lock is read-back-confirmed (after PUT, `chorus.py` re-reads and backs off if another writer won the race) and cooperative (a stale lock past `CHORUS_LOCK_TTL`, default 15 min, is reclaimable); it lives at `_agent/locks/vault.lock`. It is a courtesy, not a hard mutex — if you can't take it, tell the operator another session may be active rather than racing it.
Use a stable `<session-id>` for both calls (any unique string for this session). The lock is read-back-confirmed (after PUT, `chorus.py` re-reads and backs off if another writer won the race) and cooperative (a stale lock past `CHORUS_LOCK_TTL`, default 15 min, is reclaimable); it lives at `_agent/locks/vault.lock`. It is a courtesy, not a hard mutex — if you can't take it, tell the member another session may be active rather than racing it.
### Slash commands
@@ -120,7 +120,7 @@ Use a stable `<session-id>` for both calls (any unique string for this session).
The plugin ships `hooks/hooks.json`, so the two most drift-prone behaviors no longer depend on remembering them:
- **SessionStart** runs `chorus.py load` and injects the output as context — cold-start loading is deterministic. Still do the **reconcile** (inbox depth, scope confirm) on that injected context before working.
- **Stop** fires **once per substantive session** when nothing has been reflected or session-logged yet: it blocks with a reminder to run the `/chorus-reflect` flow and write the session log + heartbeat. Reflection still previews and asks the operator before writing — the hook only ensures the question gets asked. If nothing durable emerged, just finish; never invent memories to satisfy the nudge.
- **Stop** fires **once per substantive session** when nothing has been reflected or session-logged yet: it blocks with a reminder to run the `/chorus-reflect` flow and write the session log + heartbeat. Reflection still previews and asks the member before writing — the hook only ensures the question gets asked. If nothing durable emerged, just finish; never invent memories to satisfy the nudge.
If the hooks are disabled or unavailable, the manual procedures below are unchanged and still authoritative.
@@ -137,7 +137,7 @@ Full contract (principles, agent role, memory model): `references/operating-cont
## When to Load Memory
Load at the start of any substantive conversation — anything beyond a single quick factual question. The signal: the operator is starting work, planning, asking for help with something that has state, or referencing prior discussions.
Load at the start of any substantive conversation — anything beyond a single quick factual question. The signal: the member is starting work, planning, asking for help with something that has state, or referencing prior discussions.
### Loading procedure
@@ -159,9 +159,9 @@ Do not read every session log — older sessions are reachable via `POST /search
**Reconcile at load (do this every cold start, after the batch returns).** The batch already fetched everything needed for a cheap self-check — run it before diving into the work so memory maintains itself instead of drifting:
1. **Inbox depth (Inbox Triage).** If your inbox (GET #7) or the group inbox (GET #8) holds dated capture lines older than ~7 days that were never routed, surface the count once and offer to triage — see **Inbox Triage** below. This is the load-time trigger that makes triage self-firing rather than something you only run when asked.
2. **Scope drift (state it, don't just check it).** Scope is the most churn-prone state — the operator runs several sessions a day across different topics, so the recorded `## Scope` is frequently stale at load. **Silently working under a stale scope is the default failure mode.** To prevent it, at load read the active scope and its freshness in one call — `python3 "$CHORUS" scope show` (prints `## Scope`, `scope_updated`, and how many sessions have been logged since) — and form a one-line judgment: *does this session's request match the recorded scope?* If it diverges, switch **before** doing the work via `python3 "$CHORUS" scope set "<new scope>"` (see **Scope Switching**). If `scope show` reports several sessions logged since the last switch, treat the recorded scope as suspect and confirm with the operator rather than trusting it.
2. **Scope drift (state it, don't just check it).** Scope is the most churn-prone state — the member runs several sessions a day across different topics, so the recorded `## Scope` is frequently stale at load. **Silently working under a stale scope is the default failure mode.** To prevent it, at load read the active scope and its freshness in one call — `python3 "$CHORUS" scope show` (prints `## Scope`, `scope_updated`, and how many sessions have been logged since) — and form a one-line judgment: *does this session's request match the recorded scope?* If it diverges, switch **before** doing the work via `python3 "$CHORUS" scope set "<new scope>"` (see **Scope Switching**). If `scope show` reports several sessions logged since the last switch, treat the recorded scope as suspect and confirm with the member rather than trusting it.
Keep the reconcile to a single short line to the operator (e.g. "3 inbox captures from last week are still un-routed — triage now?"); don't let it crowd out the actual request.
Keep the reconcile to a single short line to the member (e.g. "3 inbox captures from last week are still un-routed — triage now?"); don't let it crowd out the actual request.
**If a specific topic/project/person is in play**, pull its connected context in one call:
@@ -191,7 +191,7 @@ python3 "$CHORUS" triage proposals.json # dry-run: classify + previe
python3 "$CHORUS" triage proposals.json --apply # route via capture + log each move
```
Routing targets are the usual homes (a preference/pattern about YOU → your own `_agent/members/<member>/preferences.md` under `Preferences::Observations` via a direct PATCH; project idea → `project` with `--status incubating`; durable group fact → `semantic`; person fact → `person`). Never route another member's preference into your own preferences file — put it in theirs only if their own session confirms it, else `resources/people/`. `--apply` records each move in `inbox/processing-log/YYYY-MM-DD.md` (`- <original line> → <destination path>`) automatically. Originals are **never deleted** unless the operator explicitly asks — the processing log is the audit trail.
Routing targets are the usual homes (a preference/pattern about YOU → your own `_agent/members/<member>/preferences.md` under `Preferences::Observations` via a direct PATCH; project idea → `project` with `--status incubating`; durable group fact → `semantic`; person fact → `person`). Never route another member's preference into your own preferences file — put it in theirs only if their own session confirms it, else `resources/people/`. `--apply` records each move in `inbox/processing-log/YYYY-MM-DD.md` (`- <original line> → <destination path>`) automatically. Originals are **never deleted** unless the member explicitly asks — the processing log is the audit trail.
## Project Lifecycle
@@ -210,14 +210,14 @@ Projects move through four folders under `projects/`. The folder name and the `s
## When to Write Memory
Write when the operator:
Write when the member:
- States a fact, preference, or commitment worth keeping ("I prefer X", "we use uv not pip", "standup is Tuesday at 10")
- Makes a non-obvious decision worth recording
- Says "remember that", "save this", "log this", "add to memory", "note that"
- Finishes a meaningful working session future sessions should pick up
Write in third person about the operator. Every note carries the canonical frontmatter (see below). Agent-generated notes set `agent_written: true` and `author: <member>` (your configured member id).
Write in third person about the member. Every note carries the canonical frontmatter (see below). Agent-generated notes set `agent_written: true` and `author: <member>` (your configured member id).
**Default write path: `capture`.** For a routed memory (a fact about a person/company/project/concept/etc.), prefer `python3 "$CHORUS" capture "<title>" <bodyfile> --kind <kind>` — it does the search-first, path derivation, frontmatter, indexing, auto-linking, and Agent-Log line in one call (see **High-level memory ops**). Drop to the lower-level `put`/`patch`/`append` verbs below only for shapes `capture` doesn't model (a project `## Status` replacement, an ADR mirror, a daily-note edit) or to inspect/repair.
@@ -233,9 +233,9 @@ python3 "$CHORUS" search <slug> # fallback when the index isn't trusted
If a match is found:
- In a non-active project subfolder (`on-hold/`, `incubating/`, `archived/`): **promote/merge** — PUT the merged content to `projects/active/<slug>.md` with `status: active`, then DELETE the old location. Preserve the earliest `created:` date.
- In the same folder you intended to write: **update in place** (PATCH or merged PUT). Never silently overwrite — fold the existing content in first.
- Elsewhere (e.g. a stale duplicate under `resources/`): tell the operator and ask which should be canonical before writing.
- Elsewhere (e.g. a stale duplicate under `resources/`): tell the member and ask which should be canonical before writing.
**Search both the slug AND any human title** the operator used (e.g. slug `chorus-memory` and title `CHORUS plugin`). Slug-only searches miss notes filed under a different naming scheme. Two cheap `POST /search/simple/?query=...` calls beat one expensive cleanup pass later.
**Search both the slug AND any human title** the member used (e.g. slug `chorus-memory` and title `CHORUS plugin`). Slug-only searches miss notes filed under a different naming scheme. Two cheap `POST /search/simple/?query=...` calls beat one expensive cleanup pass later.
Only after the search comes back empty (or you've decided to merge) is it safe to create a new note. This rule prevents the most common duplication bug: a note exists in `on-hold/` but the agent only checked `active/` and created a parallel record.
@@ -276,7 +276,7 @@ Every PUT body needs the canonical YAML frontmatter (see `references/vault-layou
## Scope Switching (`current-context.md`)
`_agent/members/<member>/current-context.md` tracks a single active scope. The operator routinely shifts scope within a day (one project → another → docs), so this is high-churn — switch deliberately, every time the work changes topic.
`_agent/members/<member>/current-context.md` tracks a single active scope. The member routinely shifts scope within a day (one project → another → docs), so this is high-churn — switch deliberately, every time the work changes topic.
**Preferred — one command:**
@@ -310,7 +310,7 @@ A weekly/monthly rollup is a **light digest** — open threads across `projects/
## Vault Health (monthly)
On the first substantive session of a calendar month, run a quick health pass and write findings to `_agent/health/YYYY-MM-vault-health.md`. This is **agent self-maintenance, not a journal entry** — it lives under `_agent/` because it's about the vault's mechanical integrity, not the operator's work narrative. Don't auto-fix without asking.
On the first substantive session of a calendar month, run a quick health pass and write findings to `_agent/health/YYYY-MM-vault-health.md`. This is **agent self-maintenance, not a journal entry** — it lives under `_agent/` because it's about the vault's mechanical integrity, not the member's work narrative. Don't auto-fix without asking.
Run the bundled linter first — it mechanically checks the invariants below so you don't eyeball them. **Pass `CHORUS_TODAY` = the conversation's `currentDate`** so stale/aging math uses the same clock you write with (not the runner's machine date):
@@ -346,7 +346,7 @@ python3 "$SWEEP" --apply # write
Run it after `migrate.py` (which handles structural/schema changes) — or any time `/chorus-health` reports index drift. It is idempotent; re-running is safe.
The pass is cheap and pays for itself by catching drift before it requires a reorg. Write the findings as a digest; act on them only with the operator's go-ahead.
The pass is cheap and pays for itself by catching drift before it requires a reorg. Write the findings as a digest; act on them only with the member's go-ahead.
**Performance.** Full-vault scripts (`sweep.py`, `vault_lint.py`, recall rebuild) are connection-pooled (keep-alive) and read the whole vault concurrently via `chorus.read_many`, so they finish in about a second on a few-hundred-note vault instead of timing out on hundreds of serial TLS handshakes. Tune with `CHORUS_WORKERS` (default 8, bulk-read concurrency) and `CHORUS_TIMEOUT` (default 30s, per-request). For a vault large enough that even the concurrent pass nears the tool timeout, run the script with the harness's background-execution option rather than blocking the turn.
@@ -397,11 +397,11 @@ python3 "$CHORUS" patch journal/daily/<currentDate>.md append heading "<currentD
**Routing triggers — areas / meetings / skills:**
- **Area** (`areas/`): a standing responsibility the operator maintains with no finish line. Decision rule: has an end state → `projects/`; never "done" → `areas/`. Subfolder is the domain (`business`/`personal`/`learning`/`systems`); create it on first write. `type: area`.
- **Area** (`areas/`): a standing responsibility the member maintains with no finish line. Decision rule: has an end state → `projects/`; never "done" → `areas/`. Subfolder is the domain (`business`/`personal`/`learning`/`systems`); create it on first write. `type: area`.
- **Meeting** (`resources/meetings/`): notes, recaps, or action items tied to a specific meeting or call. Mirror decisions to `decisions/by-date/` and commitments to the relevant project. `type: meeting`.
- **Skill** (`_agent/skills/`): the operator authors, installs, or retires a skill/plugin and wants it tracked as a capability — distinct from `projects/`, which tracks the build effort. Active entries in `active/`, retired ones in `archived/`. `type: skill`.
- **Skill** (`_agent/skills/`): the member authors, installs, or retires a skill/plugin and wants it tracked as a capability — distinct from `projects/`, which tracks the build effort. Active entries in `active/`, retired ones in `archived/`. `type: skill`.
Never delete files unless the operator explicitly asks. Memory is append-friendly; deletion is destructive.
Never delete files unless the member explicitly asks. Memory is append-friendly; deletion is destructive.
## Session Logging
@@ -427,11 +427,11 @@ python3 "$CHORUS" put _agent/members/<member>/heartbeat.md <bodyfile>
## Vault Unreachable
If the API returns a connection error, timeout, or `502`, tell the operator once that the memory vault is unreachable (a `502` usually means Obsidian/the REST plugin is not running on the backend), then proceed without memory. Do not retry repeatedly.
If the API returns a connection error, timeout, or `502`, tell the member once that the memory vault is unreachable (a `502` usually means Obsidian/the REST plugin is not running on the backend), then proceed without memory. Do not retry repeatedly.
## Style Rules
- Write in third person about the operator: "the operator prefers X", not "I prefer X".
- Write in third person about the member: "the member prefers X", not "I prefer X".
- This vault holds the GROUP's shared memory — every member reads and writes it, and everything in it is visible to the whole group. Do not edit another member's `_agent/members/<id>/` namespace (their preferences/scope/heartbeat/inbox belong to their sessions); facts about them as a person go in `resources/people/`. Do not cross-write with vaults outside this group.
- **Anchor relative dates on the conversation's `currentDate`** before writing. "Today" → `currentDate`. "Thursday" / "next week" → resolve to an absolute `YYYY-MM-DD`. Never guess from training-data knowledge of the current year.
- Every memory file has canonical YAML frontmatter — see `references/vault-layout.md`.
@@ -440,15 +440,15 @@ If the API returns a connection error, timeout, or `502`, tell the operator once
- **`source_notes` lists the note(s) that *triggered* or *supplied content for* this one** — e.g. the session log that produced a project update, or the daily note where a captured fact originated. It is a *backward* link to inputs. Forward links (this note → other notes it references) belong in the `## Related` section in the note body, never in frontmatter.
- **Never put `[[wikilinks]]` in frontmatter** — YAML parses them as nested lists and the links break in Obsidian's reading view. Put all cross-references in a `## Related` section in the note **body** as a bulleted list of `[[links]]`.
- Use Obsidian wiki links (`[[note name]]`) freely in the note **body** for cross-references.
- Keep entries short and focused. Fewer, sharper entries beat many noisy ones — the operator explicitly prefers concision.
- About to write something large or sensitive? Show the operator the content first and confirm.
- Keep entries short and focused. Fewer, sharper entries beat many noisy ones — the member explicitly prefers concision.
- About to write something large or sensitive? Show the member the content first and confirm.
## operator-preferences.md — Rules vs Observations
## preferences.md — Rules vs Observations
`_agent/members/<member>/preferences.md` separates two kinds of content:
Each member's `_agent/members/<member>/preferences.md` separates two kinds of content:
- `## Fact / Pattern`**promoted, deduped rules.** No date prefix. These are timeless: "the operator prefers concise communication." Append here only when a rule is stable.
- `## Observations`**timestamped raw observations.** Date-prefixed: `- 2026-06-06: the operator chose X over Y because Z.` This is where new evidence goes by default.
- `## Fact / Pattern`**promoted, deduped rules.** No date prefix. These are timeless: "the member prefers concise communication." Append here only when a rule is stable.
- `## Observations`**timestamped raw observations.** Date-prefixed: `- 2026-06-06: the member chose X over Y because Z.` This is where new evidence goes by default.
During monthly Vault Health or when an observation stabilizes, promote it from `## Observations` into `## Fact / Pattern` (drop the date) and remove the duplicate from Observations. Trim Observations to the last ~30 entries when it grows past that — the rest live in session logs.