diff --git a/echo-memory.plugin b/echo-memory.plugin index 931bea0..0f9bf93 100644 Binary files a/echo-memory.plugin and b/echo-memory.plugin differ diff --git a/echo-memory.plugin.src/.claude-plugin/plugin.json b/echo-memory.plugin.src/.claude-plugin/plugin.json index 087d60f..69ad35c 100644 --- a/echo-memory.plugin.src/.claude-plugin/plugin.json +++ b/echo-memory.plugin.src/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "echo-memory", - "version": "0.3.0", + "version": "0.4.0", "description": "Persistent memory via the ECHO Obsidian vault over the Local REST API. Reads and writes notes across Claude/CoWork sessions using direct REST calls \u2014 no MCP server required. Jason's personal memory vault.", "author": { "name": "Jason" diff --git a/echo-memory.plugin.src/skills/echo-memory/SKILL.md b/echo-memory.plugin.src/skills/echo-memory/SKILL.md index 0a7929e..fc9ea9a 100644 --- a/echo-memory.plugin.src/skills/echo-memory/SKILL.md +++ b/echo-memory.plugin.src/skills/echo-memory/SKILL.md @@ -28,58 +28,52 @@ Full API reference with every endpoint pattern and the memory routing map: `refe Load at the start of any substantive conversation — anything beyond a single quick factual question. The signal: Jason is starting work, planning, asking for help with something that has state, or referencing prior discussions. -Do NOT narrate this loading. Reading memory is expected behavior. - ### Loading procedure -**Step 1 — Confirm the vault is bootstrapped:** +The cold-start reads are independent — **issue them in parallel** (one batch of 4–5 GETs), not sequentially. Parallel loading is ~3× faster wall-clock for the same call count. + +| # | GET | Notes | +|---|-----|-------| +| 1 | `/vault/BOOTSTRAP.md` | 404 → vault not set up; follow `references/bootstrap.md` | +| 2 | `/vault/_agent/memory/semantic/operator-preferences.md` | Jason's profile | +| 3 | `/vault/_agent/context/current-context.md` | Active scope + Scope History | +| 4 | `/vault/_agent/sessions/` (listing) | Pick the ~5 most recent by reverse lex sort (filenames `YYYY-MM-DD-HHMM-.md`, so lex == chrono); only read the ones whose slugs look relevant | +| 5 | `/vault/journal/daily/YYYY-MM-DD.md` | Today's note; 404 is fine — it's created on first agent activity | + +Do not read every session log — older sessions are reachable via `POST /search/simple/?query=...` when needed. + +**If a specific project is in play**, follow up with a **search across all lifecycle subfolders** (`active/`, `incubating/`, `on-hold/`, `archived/`) — searching one folder at a time misses notes filed elsewhere. Search by **both the slug AND any human title** Jason used in this conversation: ```bash -curl -s \ - -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - "https://echoapi.alwisp.com/vault/BOOTSTRAP.md" -``` - -If this returns 404, the vault is not set up. Follow `references/bootstrap.md` before doing anything else. - -**Step 2 — Read operator preferences (the profile):** - -```bash -curl -s \ - -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - "https://echoapi.alwisp.com/vault/_agent/memory/semantic/operator-preferences.md" -``` - -**Step 3 — Read active context and the most recent session logs:** - -```bash -# Current task-scoped context bundle -curl -s -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - "https://echoapi.alwisp.com/vault/_agent/context/current-context.md" - -# List session logs — read only the last ~5 by reverse lexical sort. -# Filenames use YYYY-MM-DD-HHMM-.md, so lex sort == chrono sort. -curl -s -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - "https://echoapi.alwisp.com/vault/_agent/sessions/" -``` - -The listing endpoint returns the full session set with no pagination. Do not read all of them — pick the ~5 most recent by filename and only read the ones whose slugs look relevant. Older sessions are reachable via `POST /search/simple/?query=...` when you need them. - -**Step 4 — If a specific project is in play, SEARCH for it first across all lifecycle subfolders, then read the best match:** - -Projects can live in `active/`, `incubating/`, `on-hold/`, or `archived/`. Search by slug first so you don't miss a note in a non-active subfolder: - -```bash -curl -s -X POST \ - -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ +# slug +curl -s -X POST -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ "https://echoapi.alwisp.com/search/simple/?query=" -# Then read the match wherever it lives, e.g.: -curl -s -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - "https://echoapi.alwisp.com/vault/projects//.md" +# human title (avoids missing notes filed under a different name) +curl -s -X POST -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ + "https://echoapi.alwisp.com/search/simple/?query=" ``` -Optionally read today's daily note at `/vault/journal/daily/YYYY-MM-DD.md` for current priorities and open loops. +Then read whichever match lives at `projects//.md`. + +Do NOT narrate this loading. Reading memory is expected behavior. + +## Inbox Triage + +`inbox/captures/inbox.md` is the catch-all for "save this somewhere — figure out where later." Without triage it grows forever and durable facts get stranded there. + +At the start of a substantive session, GET `inbox/captures/inbox.md`. If it contains lines older than ~7 days that haven't been routed elsewhere, surface them once: + +> "Three captures from last week are still in the inbox — want to route them now or leave them?" + +When routing accepted items, send each to its proper home: + +- Preference / pattern → PATCH-append under `operator-preferences.md::Observations` +- Project idea → PUT `projects/incubating/.md` +- Durable fact → PUT `_agent/memory/semantic/.md` +- Person fact → PUT/PATCH `resources/people/.md` + +Then record the move in `inbox/processing-log/YYYY-MM-DD.md` (one line per item: `- `). Don't delete the original capture unless Jason explicitly asks — the processing log is the audit trail. ## Project Lifecycle @@ -122,8 +116,22 @@ If a match is found: - 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 Jason and ask which should be canonical before writing. +**Search both the slug AND any human title** Jason used (e.g. slug `echo-memory` and title `ECHO 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. +### Before you append — read first (idempotency) + +POST appends to the end of a file. It is **not idempotent** — running the same write twice (network retry, replay, re-trigger) produces duplicate lines that grow files silently. Before any POST that adds an entry to: + +- `inbox/captures/inbox.md` +- a daily note's `## Agent Log` section +- a `## Fact / Pattern` / `## Observations` / `## Log` heading + +…GET the target file (or the heading, via `/heading/...`) and substring-search for the exact line you're about to write. If present, skip the POST. The extra GET is cheap and pays for itself within a few sessions. + +This rule does **not** apply to PUT (which is fully replacing the file) or PATCH `replace` (which is overwriting a section by design). + ### Append to a file (default — additive entries) Write content to a temp file first to handle multi-line markdown cleanly, then POST: @@ -144,7 +152,19 @@ POST appends to the end of a file (creating it if absent). Use it for inbox capt ### Patch a specific heading (targeted update) -**Heading targets use the FULL heading path, `::`-delimited from the top-level heading** — a bare subheading name fails with `invalid-target`. For example, `## Fact / Pattern` under the `# Operator Preferences` H1 is targeted as `Operator Preferences::Fact / Pattern`. When unsure of the exact path, read the document map first (see below). +**Heading targets use the FULL heading path, `::`-delimited from the top-level heading** — a bare subheading name fails with `400 invalid-target` and the write is lost. For example, `## Fact / Pattern` under the `# Operator Preferences` H1 is targeted as `Operator Preferences::Fact / Pattern`. + +**Default: GET the document map first** (every first PATCH to a file in a session — cache the result mentally for subsequent PATCHes to the same file). This eliminates the most common failure mode of PATCH: + +```bash +curl -s -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ + -H "Accept: application/vnd.olrapi.document-map+json" \ + "https://echoapi.alwisp.com/vault/_agent/memory/semantic/operator-preferences.md" +``` + +Returns `{ "headings": [...], "blocks": [...], "frontmatterFields": [...] }`. Copy the heading string verbatim into `Target`. Only skip the doc-map GET if you wrote the file yourself in this session (you already know its structure). + +Then PATCH: ```bash cat > /tmp/obs_patch.md << 'OBSEOF' @@ -163,15 +183,19 @@ curl -s -X PATCH \ Use `Operation: replace` to overwrite a section entirely (e.g. a project's `Project Name::Current status`). Percent-encode non-ASCII characters in the `Target` header; spaces are fine. -**Discover exact heading paths** when unsure — GET the note with the document-map Accept header: +### Bump `updated:` after meaningful changes + +When a PATCH or PUT changes meaningful content (status update, decision recorded, current-status replacement, scope switch), also PATCH the frontmatter `updated:` field to today's date. This keeps stale-detection queries honest. ```bash -curl -s -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ - -H "Accept: application/vnd.olrapi.document-map+json" \ - "https://echoapi.alwisp.com/vault/_agent/memory/semantic/operator-preferences.md" +curl -s -X PATCH \ + -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ + -H "Operation: replace" -H "Target-Type: frontmatter" -H "Target: updated" \ + -H "Content-Type: application/json" --data '"2026-06-06"' \ + "https://echoapi.alwisp.com/vault/projects/active/.md" ``` -It returns `{ "headings": [...], "blocks": [...], "frontmatterFields": [...] }` — copy the heading string verbatim into `Target`. +Skip the bump for **routine log appends** — adding an Agent Log line, an inbox capture, or a timestamped Observations bullet doesn't constitute a meaningful content change. Bump on substance, not on heartbeat. ### Create or overwrite a file (PUT) @@ -213,6 +237,31 @@ curl -s -X POST \ "https://echoapi.alwisp.com/search/simple/?query=your+search+terms" ``` +## Scope Switching (`current-context.md`) + +`_agent/context/current-context.md` tracks a single active scope. Jason routinely shifts scope within a day (echo plugin → MPM brand → WISP docs). + +When scope changes: + +1. PATCH `prepend` a dated bullet to `## Scope History` capturing the **prior** scope (one line: `- 2026-06-06: `). If `## Scope History` doesn't exist yet, POST the heading first, same pattern as the daily-note Agent Log. +2. PATCH `replace` `## Scope` with the new scope. +3. PATCH the frontmatter `updated:` field. + +This keeps a rolling trail of recent scopes in one file instead of spawning separate stash notes. Trim Scope History to the last ~10 entries when it grows past that. + +## Vault Health (monthly) + +On the first substantive session of a calendar month, run a quick health pass and write findings to `reviews/monthly/YYYY-MM-vault-health.md`. Don't auto-fix without asking. + +Checks: + +1. **Stale active projects** — for each note in `projects/active/`, check `updated:` >30 days. Likely belongs in `on-hold/`. +2. **Unprocessed inbox** — GET `inbox/captures/inbox.md`. List items older than 14 days that never moved through the triage protocol. +3. **Duplicate slugs across lifecycle folders** — any slug appearing in more than one of `active/`, `incubating/`, `on-hold/`, `archived/` is broken state. +4. **Broken-heading risk** — sample 2–3 frequently-PATCHed files; confirm `## Agent Log`, `## Scope`, `## Fact / Pattern`, `## Observations` headings still exist. + +The pass is cheap (a few searches + a directory listing) and pays for itself by catching drift before it requires a reorg. + ## Daily Note — Agent Log After substantive activity, write a one-line entry to today's daily note's `## Agent Log` heading. The daily-note **template** (`journal/templates/daily-note-template.md`) defines this heading, but ad-hoc daily notes created without the template don't have it — so PATCH can fail with `invalid-target` if the note exists but lacks the heading. @@ -308,6 +357,7 @@ If the API returns a connection error, timeout, or `502`, tell Jason once that t - Write in third person about Jason: "Jason prefers X", not "I prefer X". - Jason is both operator and architect here — unlike goldbrain (Bryan's vault, which Jason architected). Do not cross-write: Bryan's preferences belong in goldbrain, Jason's belong here. +- **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`. - Set `agent_written: true` on agent-generated notes and list `source_notes` (plain relative paths, not links). - **`created:` is the earliest known date the entity was tracked anywhere in the vault, not "today".** When merging notes (e.g. promoting `on-hold/` → `active/`), preserve the earliest `created:` and only bump `updated:`. @@ -317,6 +367,15 @@ If the API returns a connection error, timeout, or `502`, tell Jason once that t - Keep entries short and focused. Fewer, sharper entries beat many noisy ones — Jason explicitly prefers concision. - About to write something large or sensitive? Show Jason the content first and confirm. +## operator-preferences.md — Rules vs Observations + +`_agent/memory/semantic/operator-preferences.md` separates two kinds of content: + +- `## Fact / Pattern` — **promoted, deduped rules.** No date prefix. These are timeless: "Jason prefers concise communication." Append here only when a rule is stable. +- `## Observations` — **timestamped raw observations.** Date-prefixed: `- 2026-06-06: Jason 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. + ## What This Skill Does Not Do - Does not replace reasoning. The vault is reference material — apply judgment. diff --git a/echo-memory.plugin.src/skills/echo-memory/references/api-reference.md b/echo-memory.plugin.src/skills/echo-memory/references/api-reference.md index 8f782ff..da54504 100644 --- a/echo-memory.plugin.src/skills/echo-memory/references/api-reference.md +++ b/echo-memory.plugin.src/skills/echo-memory/references/api-reference.md @@ -197,8 +197,8 @@ Only on explicit operator request. Deletion is destructive. | Short-lived, time-boxed state | `_agent/memory/working/.md` | PUT | | Task-scoped context / focus | `_agent/context/current-context.md` | PATCH / PUT | | Working-session log | `_agent/sessions/YYYY-MM-DD-HHMM-.md` | PUT | -| Long-running project state | `projects/active/.md` | PUT + PATCH | -| Non-obvious decision (ADR) | `decisions/by-date/YYYY-MM-DD-.md` (mirror only into an existing project note's Key Decisions; otherwise skip) | PUT | +| Long-running project state | `projects//.md` (lifecycle: `incubating` → `active` → `on-hold`/`archived`; folder and `status:` MUST agree) | PUT + PATCH | +| Non-obvious decision (ADR) | `decisions/by-date/YYYY-MM-DD-.md` (mirror only into an existing project note's `## Decisions`; otherwise skip) | PUT | | Person context | `resources/people/.md` | PUT / PATCH | | Concept / reference note | `resources/concepts/` or `resources/references/` | PUT | | Daily activity / Agent Log | `journal/daily/YYYY-MM-DD.md` | POST / PATCH | diff --git a/echo-memory.plugin.src/skills/echo-memory/references/session-log-template.md b/echo-memory.plugin.src/skills/echo-memory/references/session-log-template.md index c6807d3..1592c1f 100644 --- a/echo-memory.plugin.src/skills/echo-memory/references/session-log-template.md +++ b/echo-memory.plugin.src/skills/echo-memory/references/session-log-template.md @@ -2,6 +2,8 @@ Session logs go in: `_agent/sessions/YYYY-MM-DD-HHMM-.md` +**Filename format is canonical and not optional.** The four-digit local-time HHMM component is what makes session filenames lex-sort in true chronological order — the loading procedure depends on it. Before PUT-ing a new session log, validate the filename matches `^\d{4}-\d{2}-\d{2}-\d{4}-[a-z0-9-]+\.md$`. Legacy session logs without HHMM exist in the vault; do not edit their names, but every new write must use the full form. + The slug describes what the session was about in 2–5 words, kebab-case. Examples: `2026-06-05-1430-echo-plugin-build.md`, `2026-05-14-0900-q1-review-prep.md`. diff --git a/echo-memory.plugin.src/skills/echo-memory/references/vault-layout.md b/echo-memory.plugin.src/skills/echo-memory/references/vault-layout.md index ca512ba..9192204 100644 --- a/echo-memory.plugin.src/skills/echo-memory/references/vault-layout.md +++ b/echo-memory.plugin.src/skills/echo-memory/references/vault-layout.md @@ -29,9 +29,10 @@ Mirrors the canonical conventions defined in the vault's own `STRUCTURE.md` and │ ├── concepts/ references/ meetings/ source-material/ │ └── people/ ← .md ├── decisions/ -│ ├── by-date/ ← YYYY-MM-DD-.md (ADR-style) -│ ├── by-project/ ← mirror by project +│ ├── by-date/ ← YYYY-MM-DD-.md (ADR-style) — the canonical home │ └── decision-template.md +│ (decisions/by-project/ exists in the legacy scaffold but is not used — +│ mirror an ADR into a project's `## Decisions` heading instead) ├── reviews/ ← weekly / monthly / quarterly / annual ├── archive/ ← notes / projects / imports └── _agent/ @@ -44,9 +45,11 @@ Mirrors the canonical conventions defined in the vault's own `STRUCTURE.md` and ├── templates/ ← canonical note templates ├── outputs/ ← briefs / drafts / summaries / synthesis ├── skills/ ← active / archived - └── heartbeat/ + └── heartbeat/ ← single-line pointers (e.g. last-session.md → most-recent session log path) ``` +**Heartbeat:** `_agent/heartbeat/last-session.md` is a one-line pointer file an agent MAY write at session end (` @ `). Reading it at session start is cheaper than listing the sessions directory; use it as a hint, not a source of truth — fall back to the directory listing if it's missing or stale. + **Slug rules:** kebab-case, ASCII only, truncate to ~40 chars. --- @@ -97,10 +100,16 @@ not rewrite frontmatter — the append goes after existing content. To change ### operator-preferences.md (`_agent/memory/semantic/`) -The profile analog. Headings include `Operator`, `Fact / Pattern`, `Evidence`, -`Recommendation or Implication`, `Review Notes`. Append observed facts under the -right heading via PATCH. "The operator" is Jason — he is both -operator and architect of this vault. +The profile analog. Canonical headings: + +- `## Operator` — who Jason 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 Jason — he is both operator and architect of this vault. ### projects/active/\.md @@ -135,12 +144,11 @@ One paragraph, kept fresh via PATCH replace. ### sessions/YYYY-MM-DD-HHMM-\.md -See `session-log-template.md`. Note ECHO uses an **HHMM time component** in the -filename (unlike a date-only convention). +See `session-log-template.md`. ECHO uses an **HHMM time component** in the filename — this is **canonical, not optional**. The four-digit local-time component makes filenames lex-sort in true chronological order, which the loading procedure relies on. Older session logs without HHMM exist; leave them alone, but every new one must use the full `YYYY-MM-DD-HHMM-.md` form. ### decisions/by-date/YYYY-MM-DD-\.md -ADR-style: Context → Decision → Consequences. Mirror a link into `by-project/`. +ADR-style: Context → Decision → Consequences. If the decision belongs to an existing project, PATCH-append the wikilink into that project's `## Decisions` heading. Don't use `decisions/by-project/` — it's legacy scaffolding that's intentionally left empty. ### people/\.md