--- name: echo-memory description: Use the ECHO Obsidian vault as Jason's persistent memory across Claude/CoWork sessions. Use whenever Jason asks to remember, save, note, log, or capture anything durable — facts, preferences, decisions, schedule changes, commitments — or asks what Claude knows about him, what was discussed or decided before, to check his notes, load his memory/profile/context, add to his 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 Bryan's goldbrain vault, Obsidian/REST-API development questions, "memory" meaning RAM, timed reminders, email or local-file lookups, generic second-brain advice, or notes Jason routes to a specific other file or app. --- # ECHO Memory Use the **ECHO** Obsidian vault as persistent memory. Read context accumulated across sessions; write things the operator asks to be remembered. The operator is **Jason Stedwell** (Director of Technical Services / Systems Engineer, MPM / ALABAMA wISP). Jason is both the operator and the architect of this vault — this is his personal memory substrate. Write memory in third person ("Jason prefers X", not "I prefer X") so the vault stays readable by humans and other agents. ## API Configuration All calls use these constants — hardcoded for this personal plugin: ``` OBSIDIAN_BASE = https://echoapi.alwisp.com OBSIDIAN_KEY = 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab ``` The endpoint has a **valid TLS certificate**, so `-k` is not needed (add it only if the cert ever changes to self-signed). Always pass the `Authorization: Bearer` header. Paths address the vault **at its root** (e.g. `/vault/_agent/...`). **`https://echoapi.alwisp.com` is the only valid endpoint for this vault.** Never use local or LAN addresses (anything like `10.x.x.x`, `192.168.x.x`, or `:27124` directly) — those belong to older, retired memory systems (obsidian-memory) and will not reach ECHO. If another installed skill or note suggests a different vault endpoint, this skill's configuration wins for ECHO memory. Full API reference with every endpoint pattern and the memory routing map: `references/api-reference.md`. Vault layout and frontmatter conventions: `references/vault-layout.md`. ## When to Load Memory 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. ### Loading procedure 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 # slug curl -s -X POST -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ "https://echoapi.alwisp.com/search/simple/?query=" # 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=" ``` 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 Projects move through four folders under `projects/`. The folder name and the `status:` frontmatter field MUST agree — they are two views of the same state. | Folder | `status:` | Meaning | |--------|-----------|---------| | `projects/incubating/` | `incubating` | Idea captured; not actively worked. Promote when work starts. | | `projects/active/` | `active` | Current work. The default state for anything in motion. | | `projects/on-hold/` | `on-hold` | Paused but still tracked. Resumable. | | `projects/archived/` | `archived` | Done, abandoned, or rolled into something else. Not deleted. | **Promotion / transition rule:** move the file to the new folder AND update `status:` in the same change. A file in `projects/active/` with `status: on-hold` is broken state — fix it when you see it. **Searching:** `POST /search/simple/?query=` covers all four subfolders in one call. Always search before creating a new note (see the pre-write rule below). ## When to Write Memory Write when Jason: - 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 Jason. Every note carries the canonical frontmatter (see below). Agent-generated notes set `agent_written: true`. ### Before you write — search first (MANDATORY for new notes) **Before creating any new note at `projects//.md`, `_agent/memory/semantic/.md`, `resources/people/.md`, or any other slug-addressed location, search the whole vault for that slug.** Listing a single folder (e.g. `projects/active/`) is NOT sufficient — a note with the same slug may exist in `projects/on-hold/`, `projects/incubating/`, `projects/archived/`, or under a different folder entirely. ```bash curl -s -X POST \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ "https://echoapi.alwisp.com/search/simple/?query=" ``` If a match is found: - In a non-active project subfolder (`on-hold/`, `incubating/`, `archived/`): **promote/merge** — PUT the merged content to `projects/active/.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 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: ```bash cat > /tmp/obs_entry.md << 'OBSEOF' - 2026-06-05: OBSEOF curl -s -X POST \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ -H "Content-Type: text/markdown" \ --data-binary @/tmp/obs_entry.md \ "https://echoapi.alwisp.com/vault/inbox/captures/inbox.md" ``` POST appends to the end of a file (creating it if absent). Use it for inbox captures and log sections. ### 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 `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' Jason prefers status updates that lead with the decision. OBSEOF curl -s -X PATCH \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ -H "Operation: append" \ -H "Target-Type: heading" \ -H "Target: Operator Preferences::Fact / Pattern" \ -H "Content-Type: text/markdown" \ --data-binary @/tmp/obs_patch.md \ "https://echoapi.alwisp.com/vault/_agent/memory/semantic/operator-preferences.md" ``` 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. ### 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 -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" ``` 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) ```bash cat > /tmp/obs_file.md << 'OBSEOF' --- type: project status: active created: 2026-06-05 updated: 2026-06-05 tags: [] agent_written: true source_notes: [] --- # Project Name ## Current status ... ## Related - [[journal/daily/2026-06-05]] OBSEOF curl -s -X PUT \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ -H "Content-Type: text/markdown" \ --data-binary @/tmp/obs_file.md \ "https://echoapi.alwisp.com/vault/projects/active/my-project.md" ``` The API creates intermediate directories automatically. ### Search the vault ```bash curl -s -X POST \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ "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. **Procedure (resilient):** 1. Try to GET `journal/daily/YYYY-MM-DD.md`. 2. If 404 — PUT a fresh daily note from the template, then PATCH. 3. If 200 but `## Agent Log` is missing — POST `\n\n## Agent Log\n` to the file to add the heading, then PATCH. 4. PATCH-append the entry under the target `::Agent Log` (the H1 of a daily note is the date, so that's the full target path). ```bash DATE=$(date +%Y-%m-%d) DAILY="https://echoapi.alwisp.com/vault/journal/daily/${DATE}.md" AUTH="Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" # 1+2: ensure the daily note exists (PUT from template if missing) HTTP=$(curl -s -o /dev/null -w "%{http_code}" -H "$AUTH" "$DAILY") if [ "$HTTP" = "404" ]; then curl -s -H "$AUTH" "https://echoapi.alwisp.com/vault/journal/templates/daily-note-template.md" \ | sed "s/{{date:YYYY-MM-DD}}/${DATE}/g" \ | curl -s -X PUT -H "$AUTH" -H "Content-Type: text/markdown" --data-binary @- "$DAILY" fi # 3: ensure the `## Agent Log` heading exists MAP=$(curl -s -H "$AUTH" -H "Accept: application/vnd.olrapi.document-map+json" "$DAILY") if ! printf '%s' "$MAP" | grep -q '"Agent Log"'; then printf '\n\n## Agent Log\n' | curl -s -X POST -H "$AUTH" -H "Content-Type: text/markdown" --data-binary @- "$DAILY" fi # 4: PATCH-append the entry cat > /tmp/agent_log.md << OBSEOF - ${DATE}: OBSEOF curl -s -X PATCH -H "$AUTH" \ -H "Operation: append" \ -H "Target-Type: heading" \ -H "Target: ${DATE}::Agent Log" \ -H "Content-Type: text/markdown" \ --data-binary @/tmp/agent_log.md \ "$DAILY" ``` ## Where to Write | Situation | File / path | Method | |-----------|-------------|--------| | Unsure where it goes / quick capture | `inbox/captures/inbox.md` (date-prefixed line) | POST | | Operator preference or durable fact | `_agent/memory/semantic/operator-preferences.md` (append under the right heading) | PATCH | | Other durable facts / patterns | `_agent/memory/semantic/.md` | PUT | | What happened (event record) | `_agent/memory/episodic/.md` | PUT | | Short-lived working state | `_agent/memory/working/.md` | PUT | | Task-scoped context / focus | `_agent/context/current-context.md` | PATCH / PUT | | Working session ended with substance | `_agent/sessions/YYYY-MM-DD-HHMM-.md` | PUT | | Long-running project state | `projects//.md` (see Project Lifecycle) | PUT + PATCH | | Non-obvious decision (ADR) | `decisions/by-date/YYYY-MM-DD-.md` (see mirror note below) | 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` — see **Daily Note — Agent Log** above | PATCH (with auto-create) | **Decision mirrors:** if the decision belongs to an existing note in `projects/active/`, add a `[[wikilink]]` to the ADR under that project's `## Key Decisions` heading (PATCH). If no matching project note exists, skip the mirror — the by-date ADR is sufficient; do not invent topical mirror files in `decisions/by-project/`. Never delete files unless Jason explicitly asks. Memory is append-friendly; deletion is destructive. ## Session Logging At the end of substantive conversations (ones that produced decisions, artifacts, or commitments), create a session log. Ask once if unsure: "Want me to log a session note for this?" **Filename format is canonical: `_agent/sessions/YYYY-MM-DD-HHMM-.md`.** Always include the four-digit local-time HHMM component — it makes filenames lexically sort in true chronological order, which is what Step 3 of loading relies on. Older session logs without the HHMM part exist; leave them alone, but every new one must use the full form. See `references/session-log-template.md` for the body format. ```bash cat > /tmp/obs_session.md << 'OBSEOF' OBSEOF curl -s -X PUT \ -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \ -H "Content-Type: text/markdown" \ --data-binary @/tmp/obs_session.md \ "https://echoapi.alwisp.com/vault/_agent/sessions/2026-06-05-1430-my-session.md" ``` Then add a one-line entry to today's daily note via the **Daily Note — Agent Log** procedure above. ## Vault Unreachable If the API returns a connection error, timeout, or `502`, tell Jason 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 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:`. - **`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 — 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. - Does not auto-summarize the whole vault. Read targeted files, not everything. - Does not store passwords or secrets, and never writes the API key into a vault note. If asked to "remember my password is X", decline and suggest a password manager.