36 KiB
name, description
| name | description |
|---|---|
| echo-memory | 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.
The plugin is the single source of truth for ECHO. The vault holds data only — no CLAUDE.md / BOOTSTRAP.md / STRUCTURE.md / index.md control docs live there. All structure and procedure ship here:
- Durable principles, memory model, and safety rules:
references/operating-contract.md - Bootstrapping an empty vault, repair, and schema migrations:
references/bootstrap.md - Vault layout and frontmatter conventions:
references/vault-layout.md - Complete endpoint→logic routing map (every write destination, its trigger, and why it's distinct):
references/routing-map.md - Full API reference with every endpoint pattern and the memory routing map:
references/api-reference.md
Executable logic ships under scripts/:
scripts/echo.sh— the validated API client; prefer it over hand-builtcurl(below)scripts/routing.json— the canonical, machine-readable route manifest (the routing map's source of truth; the linter enforces it)scripts/vault-lint.sh— read-only invariant checker (Vault Health)scripts/bootstrap.sh/scripts/migrate.sh— deterministic vault setup/repair and schema migration
Bundled Tooling (prefer over raw curl)
All paths below are under ${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/.
scripts/echo.sh — use this for every read/write. It centralizes auth, HTTP-status checking (a failed write exits non-zero instead of looking like success), one bounded retry on 5xx/connection errors, idempotent append, correct :: heading targets, and frontmatter patches. The raw curl recipes later in this file are the underlying mechanics / fallback — reach for them only if echo.sh is unavailable, and if you do, check the HTTP status yourself (the PATCH-heading 400 invalid-target failure silently loses writes otherwise).
ECHO="${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/echo.sh"
"$ECHO" get <path> # 404 -> exit 44
"$ECHO" ls <dir> ; "$ECHO" map <path> # listing / document-map
"$ECHO" search <terms...>
"$ECHO" put <path> <file> # create/overwrite (read-back verified)
"$ECHO" append <path> "<line>" # idempotent: skips if the exact line exists
"$ECHO" patch <path> append heading "<H1::Sub>" <file>
"$ECHO" fm <path> updated '"2026-06-19"' ; "$ECHO" bump <path> # frontmatter
"$ECHO" lock <session-id> ; "$ECHO" unlock <session-id>
Bootstrap / migrate are scripts now, not hand-run curl loops: scripts/bootstrap.sh [--dry-run] (idempotent, probe-before-write, never overwrites) and scripts/migrate.sh [--apply] (reads the marker's schema_version and applies migrations; dry-run by default). See references/bootstrap.md.
Concurrency — the vault is shared, so coordinate writes
ECHO is read/written by multiple clients (Claude Code and CoWork sessions). The single-line files (heartbeat/last-session.md, current-context.md::Scope, inbox.md) assume a single writer at a time. Before a burst of writes in a session that may overlap another, take the advisory lock, and release it at session end:
"$ECHO" lock "cc-$(date +%s)" # exit 75 if another session holds a fresh lock
# ... do the writes ...
"$ECHO" unlock "cc-$(date +%s)"
The lock is cooperative (a stale lock past ECHO_LOCK_TTL, default 15 min, is reclaimable) and lives at _agent/locks/vault.lock. It is a courtesy, not a hard mutex — if you can't take it, tell Jason another session may be active rather than racing it.
Slash commands
/echo-load (cold-start read), /echo-save <text> (route + persist), /echo-triage (drain the inbox), /echo-health (run the linter). These are explicit entry points to the procedures below.
Operating Contract & Safety
The vault is the system of record for long-term memory, not a scratchpad. Default to additive updates, explicit status changes, and traceable summaries. Keep agent-managed content (agent_written: true + source_notes) separable from human-authored content. Non-negotiable safety rules:
- Do not fabricate facts, relationships, or prior decisions.
- Do not mass-restructure the vault unless explicitly asked.
- Do not delete notes unless deletion is explicitly requested and clearly safe.
- Never store secrets or API keys inside a vault note.
Full contract (principles, agent role, memory model): references/operating-contract.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 5–6 GETs), not sequentially. Parallel loading is ~3× faster wall-clock for the same call count.
| # | GET | Notes |
|---|---|---|
| 1 | /vault/_agent/echo-vault.md |
The bootstrap marker. 404 → vault not set up; follow references/bootstrap.md. 200 → check its schema_version and migrate if older. |
| 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/heartbeat/last-session.md → then /vault/_agent/sessions/ |
Read the heartbeat first — a one-line pointer (<session-log-path> @ <ISO-timestamp>) written at the end of the previous session. It's an O(1) jump to the latest log, so you can skip or shortcut the full listing. Fall back to the sessions/ listing only if the pointer is missing or looks stale; then pick the ~5 most recent by reverse lex sort (filenames YYYY-MM-DD-HHMM-<slug>.md, so lex == chrono) and read only the relevant ones. |
| 5 | /vault/journal/daily/YYYY-MM-DD.md |
Today's note; 404 is fine — it's created on first agent activity |
| 6 | /vault/inbox/captures/inbox.md |
Inbox depth probe — feeds the load-time reconcile below. 404 is fine (empty inbox). |
Do not read every session log — older sessions are reachable via POST /search/simple/?query=... when needed.
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:
- Inbox depth (Inbox Triage). If
inbox/captures/inbox.md(GET #6) 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. - Scope drift (state it, don't just check it). Scope is the most churn-prone state — Jason runs several sessions a day across different topics, so the recorded
## Scopeis 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 —echo.sh 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 viaecho.sh scope set "<new scope>"(see Scope Switching). Ifscope showreports several sessions logged since the last switch, treat the recorded scope as suspect and confirm with Jason rather than trusting it.
Keep the reconcile to a single short line to Jason (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 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:
# slug
curl -s -X POST -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \
"https://echoapi.alwisp.com/search/simple/?query=<slug>"
# 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=<human+title>"
Then read whichever match lives at projects/<lifecycle>/<slug>.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/<slug>.md - Durable fact → PUT
_agent/memory/semantic/<slug>.md - Person fact → PUT/PATCH
resources/people/<name>.md
Then record the move in inbox/processing-log/YYYY-MM-DD.md (one line per item: - <original line> → <destination path>). 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=<slug> 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/<lifecycle>/<slug>.md, _agent/memory/semantic/<slug>.md, resources/people/<name>.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.
curl -s -X POST \
-H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \
"https://echoapi.alwisp.com/search/simple/?query=<slug>"
If a match is found:
- In a non-active project subfolder (
on-hold/,incubating/,archived/): promote/merge — PUT the merged content toprojects/active/<slug>.mdwithstatus: active, then DELETE the old location. Preserve the earliestcreated: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 Logsection - a
## Fact / Pattern/## Observations/## Logheading
…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:
cat > /tmp/obs_entry.md << 'OBSEOF'
- 2026-06-05: <your entry here>
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:
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:
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::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.
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/<slug>.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)
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
## 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
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), so this is high-churn — switch deliberately, every time the work changes topic.
Preferred — one command:
"$ECHO" scope set "<new scope summary>"
scope set does the whole switch atomically and correctly: it archives the prior scope to ## Scope History (dated, truncated), replaces ## Scope with the new text, and stamps the scope_updated: frontmatter timestamp. That timestamp is the freshness signal — it's what echo.sh scope show and the vault-lint.sh drift check read to tell whether the recorded scope still reflects current work. Always switch through scope set so scope_updated stays honest; a hand-edited ## Scope that skips the stamp reintroduces silent drift.
Manual fallback (only if echo.sh is unavailable): PATCH prepend the prior scope to ## Scope History, PATCH replace ## Scope, then PATCH the frontmatter scope_updated: (and updated:) to today. Note scope_updated must already exist in frontmatter — a PATCH replace on a missing field returns 400 invalid-target; run bootstrap.sh repair to add it.
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.
Drift backstop: vault-lint.sh flags when ≥ SCOPE_STALE_SESSIONS (default 3) session logs are dated after scope_updated — i.e. work happened without a scope switch. It's advisory (surfaced in Vault Health / /echo-health), the mechanical safety net under the load-time judgment above.
Journal Rollups (the journal is one continuum)
The journal is a single append-only chronological stream. Rollups are just coarser-grained journal entries over the same timeline, so they all live under journal/ — there is no separate reviews/ tree. One place to read the whole time-series story, daily through annual.
| Cadence | Path | Trigger | Type |
|---|---|---|---|
| Daily | journal/daily/YYYY-MM-DD.md |
first agent activity that day | daily-note |
| Weekly | journal/weekly/YYYY-Www.md (e.g. 2026-W24.md) |
first substantive session of a new ISO week — opt-in, offer first | weekly-note |
| Monthly | journal/monthly/YYYY-MM.md |
first substantive session of a new calendar month — offer alongside the Vault Health pass | monthly-note |
| Quarterly | journal/quarterly/YYYY-Qn.md |
manual / on request only | review |
| Annual | journal/annual/YYYY.md |
manual / on request only | review |
All filenames lex-sort chronologically. Detect the weekly trigger with date +%G-W%V and check whether that week's note already exists; monthly with date +%Y-%m.
A weekly/monthly rollup is a light digest — open threads across projects/active/, inbox items aging past ~7 days, and the period's ## Scope History changes from current-context.md. It is not a vault-health audit (that's an agent-maintenance artifact — see below).
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 Jason'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 ECHO_TODAY = the conversation's currentDate so stale/aging math uses the same clock you write with (not the runner's machine date):
ECHO_TODAY=<currentDate> "${CLAUDE_PLUGIN_ROOT}/skills/echo-memory/scripts/vault-lint.sh"
Exit codes: 0 clean · 1 violations · 2 unreachable · 3 not bootstrapped. Checks (the linter asserts each and prints violations):
- Stale active projects — for each note in
projects/active/, checkupdated:>30 days. Likely belongs inon-hold/. - Unprocessed inbox — GET
inbox/captures/inbox.md. List items older than 14 days that never moved through the triage protocol. - Duplicate slugs across lifecycle folders — any slug appearing in more than one of
active/,incubating/,on-hold/,archived/is broken state. - Folder ↔
status:mismatch — anyprojects/<lifecycle>/note whosestatus:frontmatter disagrees with its folder. - Wikilinks in frontmatter — any
[[...]]inside a YAML frontmatter block (breaks Obsidian reading view), swept across all folders. - Duplicate
## Agent Logheadings — any daily note with more than one. - Unknown / retired paths — any vault file that matches no route in
scripts/routing.json(or sits at a retired path). - Frontmatter integrity — missing required fields,
updated<created, futureupdated, and[[wikilinks]]leaking intosource_notes.
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 Jason's go-ahead.
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):
- Try to GET
journal/daily/YYYY-MM-DD.md. - If 404 — PUT a fresh daily note from the template, then PATCH.
- If 200 but
## Agent Logis missing — POST\n\n## Agent Log\nto the file to add the heading, then PATCH. Detect the heading by grepping the raw markdown for an anchored^## Agent Logline — do NOT grep the document-map JSON, whose headings are full::-delimited paths (e.g."2026-06-07::Agent Log"); a bare"Agent Log"match fails there and a duplicate heading gets appended. - PATCH-append the entry under the target
<YYYY-MM-DD>::Agent Log(the H1 of a daily note is the date, so that's the full target path).
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.
# Grep the RAW markdown for an anchored heading line. Do NOT grep the document-map
# JSON: its headings are full "::"-delimited paths (e.g. "2026-06-07::Agent Log"), so a
# bare '"Agent Log"' match never matches and a DUPLICATE heading gets appended each time.
if ! curl -s -H "$AUTH" "$DAILY" | 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}: <one-line entry here>
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/<slug>.md |
PUT |
| What happened (event record) | _agent/memory/episodic/<slug>.md |
PUT |
| Short-lived working state | _agent/memory/working/<slug>.md |
PUT |
| Task-scoped context / focus | _agent/context/current-context.md |
PATCH / PUT |
| Working session ended with substance | _agent/sessions/YYYY-MM-DD-HHMM-<slug>.md |
PUT |
| Long-running project state | projects/<lifecycle>/<slug>.md (see Project Lifecycle) |
PUT + PATCH |
| Ongoing area of responsibility (standing domain, no end state) | areas/<domain>/<slug>.md (domain: business/personal/learning/systems) |
PUT |
| Non-obvious decision (ADR) | decisions/by-date/YYYY-MM-DD-<slug>.md (see mirror note below) |
PUT |
| Person context | resources/people/<name>.md |
PUT / PATCH |
| Company / organization context | resources/companies/<slug>.md |
PUT / PATCH |
| Concept / reference note | resources/concepts/ or resources/references/ |
PUT |
| Meeting notes / call recap | resources/meetings/YYYY-MM-DD-<slug>.md |
PUT |
| Skill / plugin capability entry (catalog, not the build work) | _agent/skills/active/<slug>.md (→ _agent/skills/archived/ when retired) |
PUT |
| Daily activity / Agent Log | journal/daily/YYYY-MM-DD.md — see Daily Note — Agent Log above |
PATCH (with auto-create) |
| Weekly / monthly rollup | journal/weekly/YYYY-Www.md · journal/monthly/YYYY-MM.md — see Journal Rollups |
PUT |
| Quarterly / annual review | journal/quarterly/YYYY-Qn.md · journal/annual/YYYY.md (manual / on request) |
PUT |
| Vault-health audit (agent self-maintenance) | _agent/health/YYYY-MM-vault-health.md — see Vault Health |
PUT |
| Session-end orientation pointer | _agent/heartbeat/last-session.md (one line: <session-log-path> @ <ISO-timestamp>) |
PUT |
The complete, audited routing map lives in
references/routing-map.md— every write destination with its trigger, what lands there, and why it's distinct from its neighbors. This table is the quick-reference; the map is the authority. If a path isn't in the map, it shouldn't be written to.
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/.
Routing triggers — areas / meetings / skills:
- Area (
areas/): a standing responsibility Jason 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 todecisions/by-date/and commitments to the relevant project.type: meeting. - Skill (
_agent/skills/): Jason authors, installs, or retires a skill/plugin and wants it tracked as a capability — distinct fromprojects/, which tracks the build effort. Active entries inactive/, retired ones inarchived/.type: skill.
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-<slug>.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.
cat > /tmp/obs_session.md << 'OBSEOF'
<session log content — see references/session-log-template.md>
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.
Finally, update the heartbeat pointer so the next session can orient in one GET (this is what closes the loop with loading-procedure Step 4 — a pointer nobody writes is a pointer nobody can read):
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '%s @ %s\n' "_agent/sessions/${SESSION_FILENAME}" "$NOW" \
| curl -s -X PUT -H "Authorization: Bearer 241265fbe6830934a9a4ad3e69335f64a42153b663aa5b0017cb1ea1217b2bab" \
-H "Content-Type: text/markdown" --data-binary @- \
"https://echoapi.alwisp.com/vault/_agent/heartbeat/last-session.md"
last-session.md is a single-line pointer overwritten (PUT) each session end — never appended, so it can't grow or duplicate.
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
currentDatebefore writing. "Today" →currentDate. "Thursday" / "next week" → resolve to an absoluteYYYY-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: trueon agent-generated notes and listsource_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. promotingon-hold/→active/), preserve the earliestcreated:and only bumpupdated:.source_noteslists 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## Relatedsection 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## Relatedsection 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.