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.
@@ -120,7 +120,7 @@ curl -s -X PUT \
```bash
cat > /tmp/obs_patch.md << 'OBSEOF'
The operator prefers concise status updates — lead with the decision.
The member prefers concise status updates — lead with the decision.
OBSEOF
curl -s -X PATCH \
@@ -195,12 +195,12 @@ curl -s -X DELETE \
"$CHORUS_BASE/vault/inbox/imports/old-note.md"
```
Only on explicit operator request. Deletion is destructive.
Only on explicit member request. Deletion is destructive.
> **DELETE removes files only — never directories.** Deleting the last file in a folder
> leaves the empty folder orphaned on disk; the REST listing then 404s (nothing left to
> list), which falsely reads as "the tree is gone", but Obsidian's file explorer still
> shows it. After removing a folder's last file, tell the operator the empty folder
> shows it. After removing a folder's last file, tell the member the empty folder
> needs manual deletion in Obsidian — do not claim the tree is gone from a 404 listing.
> (Learned 2026-07-03 removing the retired `archive/` and `_agent/outputs/` trees.)
@@ -35,7 +35,7 @@ curl -s -o /dev/null -w "%{http_code}" -H "$AUTH" "$BASE/vault/_agent/chorus-vau
```
- **200** → bootstrapped. Read the marker's `schema_version`; if it is **less than** the plugin's current schema (4), run a migration pass (see *Migrations* below), otherwise proceed straight to the loading procedure in `SKILL.md`.
- **404** → empty/unconfigured vault. Run **Fresh bootstrap** below. (If you expected an existing vault, confirm with the operator once that the REST API is pointed at the right vault before seeding.)
- **404** → empty/unconfigured vault. Run **Fresh bootstrap** below. (If you expected an existing vault, confirm with the member once that the REST API is pointed at the right vault before seeding.)
---
@@ -137,7 +137,7 @@ Substitute `{{DATE}}`, then PUT `scaffold/chorus-vault.md` → `/vault/_agent/ch
### 6. First-run trace
Create today's daily note from `journal/templates/daily-note-template.md` (resolve the `{{date:…}}` tokens to today), append a one-line `## Agent Log` entry noting the bootstrap, and write a session log in `_agent/sessions/YYYY-MM-DD-HHMM-bootstrap.md`. Tell the operator briefly what was created.
Create today's daily note from `journal/templates/daily-note-template.md` (resolve the `{{date:…}}` tokens to today), append a one-line `## Agent Log` entry noting the bootstrap, and write a session log in `_agent/sessions/YYYY-MM-DD-HHMM-bootstrap.md`. Tell the member briefly what was created.
---
@@ -153,6 +153,6 @@ Run the same steps 15, but GET-probe each path first and **only create what i
When the marker's `schema_version` is older than the plugin's, apply the migration steps for each intervening version, then PATCH the marker's `schema_version` frontmatter to the new value.
- **0 → 1** (control-docs-in-plugin): the vault previously carried root control docs (`CLAUDE.md`, `BOOTSTRAP.md`, `STRUCTURE.md`, `index.md`). Back them up outside the vault, DELETE them, PUT the thin `scaffold/README.vault.md` over the old verbose `README.md`, write the `_agent/chorus-vault.md` marker, and scrub now-dangling `[[CLAUDE]]`/`[[BOOTSTRAP]]`/`[[STRUCTURE]]`/`[[index]]` links from the `## Related` sections of `operator-preferences.md` and `current-context.md` (leave historical session logs alone). Confirm with the operator before deleting.
- **1 → 2** (reviews-folded-into-journal): the `reviews/` tree is retired. (a) For each note under `reviews/weekly/` and `reviews/monthly/`, MOVE it into `journal/weekly/` (rename `YYYY-Www-review.md``YYYY-Www.md`) and `journal/monthly/` respectively, preserving the earliest `created:`. (b) Move any `reviews/monthly/YYYY-MM-vault-health.md` to `_agent/health/`. (c) Move `reviews/quarterly|annual/` artifacts to `journal/quarterly|annual/`. (d) Update inbound `[[reviews/...]]` wikilinks in `## Related` sections to the new paths. (e) DELETE the now-empty `reviews/` tree. Confirm with the operator before deleting; leave historical session logs alone. *(This step covers any vault bootstrapped under schema 1.)*
- **0 → 1** (control-docs-in-plugin): the vault previously carried root control docs (`CLAUDE.md`, `BOOTSTRAP.md`, `STRUCTURE.md`, `index.md`). Back them up outside the vault, DELETE them, PUT the thin `scaffold/README.vault.md` over the old verbose `README.md`, write the `_agent/chorus-vault.md` marker, and scrub now-dangling `[[CLAUDE]]`/`[[BOOTSTRAP]]`/`[[STRUCTURE]]`/`[[index]]` links from the `## Related` sections of `operator-preferences.md` and `current-context.md` (leave historical session logs alone). Confirm with the member before deleting.
- **1 → 2** (reviews-folded-into-journal): the `reviews/` tree is retired. (a) For each note under `reviews/weekly/` and `reviews/monthly/`, MOVE it into `journal/weekly/` (rename `YYYY-Www-review.md``YYYY-Www.md`) and `journal/monthly/` respectively, preserving the earliest `created:`. (b) Move any `reviews/monthly/YYYY-MM-vault-health.md` to `_agent/health/`. (c) Move `reviews/quarterly|annual/` artifacts to `journal/quarterly|annual/`. (d) Update inbound `[[reviews/...]]` wikilinks in `## Related` sections to the new paths. (e) DELETE the now-empty `reviews/` tree. Confirm with the member before deleting; leave historical session logs alone. *(This step covers any vault bootstrapped under schema 1.)*
- **2 → 3** (entity index + cross-linking): adds the `_agent/index/` folder for the machine-maintained entity registry. `migrate.py` creates the folder; the **data back-fill is `sweep.py`** — after migrating, run `python3 scripts/sweep.py --apply` to build `_agent/index/entities.json` from the notes already in the vault and to symmetrize existing `## Related` cross-links (it adds only the missing reciprocal direction, never invents links). `sweep.py` is idempotent and dry-run by default; re-run it any time `/chorus-health` reports index drift.
@@ -20,7 +20,7 @@ You are an agent operating against an Obsidian vault that functions as a shared,
- **Working** → `_agent/memory/working/` — transient, time-boxed.
- **Episodic** → `_agent/memory/episodic/` — what happened, when.
- **Semantic** → `_agent/memory/semantic/` — durable facts, patterns, preferences (`operator-preferences.md`).
- **Semantic** → `_agent/memory/semantic/` — durable group facts and patterns (incl. `group-profile.md`); each member's preferences live in their own `_agent/members/<member>/preferences.md`.
- **Context bundles** → `_agent/context/` — task-focused reading lists and active state.
## Safety rules
@@ -21,7 +21,7 @@ Views of the same truth: `scripts/routing.json` is the **machine-readable canoni
| `inbox/imports/<slug>.md` | Raw external material dropped in wholesale (export, paste, dump) | The raw artifact, unedited | Holds un-triaged *bulk*, vs captures which hold single lines | PUT |
| `inbox/processing-log/YYYY-MM-DD.md` | An inbox item is routed to its real home | One line: `<original> → <destination path>` | Audit trail of moves — never holds memory itself, only the record of routing | POST |
Captures and imports are temporary by contract. Triage drains them into the homes below and logs the move; the original is left until the operator okays deletion.
Captures and imports are temporary by contract. Triage drains them into the homes below and logs the move; the original is left until the member okays deletion.
## journal/ — the one append-only time-series stream
@@ -53,7 +53,7 @@ Lifecycle folders; `status:` frontmatter MUST equal the folder name (the linter
| Path | Trigger | What lands | Distinct because | Method |
|------|---------|------------|------------------|--------|
| `areas/<domain>/<slug>.md` | Ongoing domain the operator maintains indefinitely (`<domain>`: business/personal/learning/systems) | Area note | No end state — the one thing that disqualifies it from `projects/` | PUT |
| `areas/<domain>/<slug>.md` | Ongoing domain the member maintains indefinitely (`<domain>`: business/personal/learning/systems) | Area note | No end state — the one thing that disqualifies it from `projects/` | PUT |
## resources/ — reference material about the world
@@ -61,7 +61,7 @@ Lifecycle folders; `status:` frontmatter MUST equal the folder name (the linter
|------|---------|------------|------------------|--------|
| `resources/people/<name>.md` | A fact about a specific person | Person note (kebab-case slug) | Keyed to a person, not a topic or event | PUT / PATCH |
| `resources/companies/<slug>.md` | A fact about an organization (client, vendor, partner, employer) | Company note (kebab-case slug) | Keyed to an organization, not an individual (`people/`) or an external source (`references/`) | PUT / PATCH |
| `resources/concepts/<slug>.md` | A reusable concept/idea the operator wants on file | Concept note | An idea, vs a `reference` which is an external source | PUT |
| `resources/concepts/<slug>.md` | A reusable concept/idea the member wants on file | Concept note | An idea, vs a `reference` which is an external source | PUT |
| `resources/references/<slug>.md` | An external source/link worth keeping | Reference note | Points outward (a source), vs `concepts` (an idea) | PUT |
| `resources/meetings/YYYY-MM-DD-<slug>.md` | Notes/recap tied to a specific meeting or call | Meeting note; mirror decisions to `decisions/by-date/`, commitments to the project | Event-anchored to a meeting, vs a project's ongoing thread | PUT |
@@ -90,7 +90,7 @@ Lifecycle folders; `status:` frontmatter MUST equal the folder name (the linter
| `_agent/sessions/<member>/YYYY-MM-DD-HHMM-<slug>.md` | A substantive session ends (decisions/artifacts/commitments) | Session log (see template) | Per-session record, namespaced by member; the unit loading's heartbeat fallback scans | PUT |
| `_agent/health/YYYY-MM-vault-health.md` | First substantive session of a month (Vault Health pass) | Health-audit findings | Agent self-maintenance about vault integrity — NOT a work narrative, so not in `journal/` | PUT |
| `_agent/templates/` | Bootstrap only (seeded from plugin masters) | Canonical note templates | Holds templates, not memory; never a runtime routing target | PUT (seed) |
| `_agent/skills/active/<slug>.md` | The operator authors/installs a skill and wants it catalogued | Skill capability entry | Catalogs a *capability*, vs `projects/` which tracks the *build effort* | PUT |
| `_agent/skills/active/<slug>.md` | The member authors/installs a skill and wants it catalogued | Skill capability entry | Catalogs a *capability*, vs `projects/` which tracks the *build effort* | PUT |
| `_agent/skills/archived/<slug>.md` | A catalogued skill is retired | Skill entry (moved from `active/`) | Terminal state of the skill catalog | PUT |
| `_agent/locks/vault.lock` | Advisory multi-writer lock acquire/release | One line: `<owner> @ <ISO-timestamp>` | Concurrency coordination, not memory; managed only by `chorus.py lock/unlock` | PUT / DELETE |
| `_agent/index/entities.json` | Rebuilt on every `capture` and on `sweep` | Slug→{path, kind, title, aliases, last_seen} registry | Machine-maintained routing/recall index, not a note; the one JSON in the vault | PUT |
@@ -127,7 +127,7 @@ Listed so they are recognised as dead and never recreated. Any one of these appe
## Routing decision tree (the calls that get mis-made)
1. **Destination unknown right now?**`inbox/captures/`. Known? → route directly; never park a known fact in the inbox.
2. **Is it about the operator's work over time?**`journal/` (pick the grain by cadence). **About the vault's mechanical health?**`_agent/health/`. These two look similar monthly but answer different questions.
2. **Is it about the member's work over time?**`journal/` (pick the grain by cadence). **About the vault's mechanical health?**`_agent/health/`. These two look similar monthly but answer different questions.
3. **Does the effort have an end state?**`projects/` (folder = `status:`). **No finish line?**`areas/`.
4. **A fact about the world:** timeless → `semantic/`; a thing that happened → `episodic/`; needed only for now → `working/`. A fact about a *person*`resources/people/`; a fact about an *organization*`resources/companies/`.
5. **A decision:** always `decisions/by-date/`; mirror into a project only if one already exists.
@@ -80,7 +80,7 @@ client: claude-code
Build and package the chorus-memory CoWork plugin against the live vault.
## Notes Read
- [[BOOTSTRAP]], [[STRUCTURE]], [[_agent/memory/semantic/operator-preferences]]
- [[BOOTSTRAP]], [[STRUCTURE]], [[_agent/members/<member>/preferences]]
## Actions Taken
Verified the REST API end-to-end, confirmed the scaffold copied into the live vault,
@@ -119,18 +119,18 @@ change `updated:` or `status:`, use PATCH with `Target-Type: frontmatter`.
## File-Specific Conventions
### operator-preferences.md (`_agent/memory/semantic/`)
### preferences.md (`_agent/members/<member>/`)
The profile analog. Canonical headings:
Each member's profile analog — how the agent should work FOR that member. Group-visible by design (one vault, one key); only that member's own sessions edit it. Canonical headings:
- `## Operator` — who the vault owner is (one paragraph)
- `## Member` — who this member is (one paragraph)
- `## Fact / Pattern`**promoted, deduped rules.** No date prefix. Timeless.
- `## Observations`**timestamped raw observations.** Date-prefixed lines (`- 2026-06-06: ...`). Default landing zone for new evidence.
- `## Evidence` — citations/links supporting the rules
- `## Recommendation or Implication` — how the rules should shape behavior
- `## Review Notes` — confidence / last review date
Append observed facts under `## Observations` by default. Promote to `## Fact / Pattern` (dropping the date) once a pattern stabilizes. "The operator" is the vault owner — refer to them generically and in the third person (the display name is the runtime `owner` config value).
Append observed facts under `## Observations` by default. Promote to `## Fact / Pattern` (dropping the date) once a pattern stabilizes. Write in third person by name ("Jason prefers X"), so the note reads correctly to every member and agent.
### projects/active/\<slug\>.md
@@ -189,11 +189,11 @@ ADR-style: Context → Decision → Consequences. If the decision belongs to an
### companies/\<slug\>.md
`type: company`. An organization the vault owner works with — client, vendor, partner, or employer (e.g. `acme.md`). Distinct from `people/` (individuals, who *belong to* companies) and `references/` (external sources). Lowercase kebab-case slug.
`type: company`. An organization the group works with — client, vendor, partner, or employer (e.g. `acme.md`). Distinct from `people/` (individuals, who *belong to* companies) and `references/` (external sources). Lowercase kebab-case slug.
---
## Cross-References
Use Obsidian wiki links freely: `[[note-name]]` or `[[folder/note]]`. The REST API
doesn't resolve them, but Obsidian does when the operator browses the vault.
doesn't resolve them, but Obsidian does when the member browses the vault.