From d3cb7229353588ca111298ce2a265164e9942941 Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 7 Jun 2026 10:58:29 -0500 Subject: [PATCH] docs: add comprehensive root README --- README.md | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..cccea34 --- /dev/null +++ b/README.md @@ -0,0 +1,314 @@ +# quicksilver + +Persistent memory for Claude via an **Obsidian** vault over the [Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api). + +Reads and writes notes across Claude/CoWork sessions using direct REST calls — no MCP server required. Each install is self-configuring: on first run the plugin prompts for the vault endpoint, API key, and operator identity, then boots the vault from a bundled scaffold. + +*Architecture by Jason Stedwell.* + +--- + +## Contents + +- [What it does](#what-it-does) +- [Requirements](#requirements) +- [Install](#install) +- [First-run setup](#first-run-setup) +- [Usage](#usage) +- [How it works](#how-it-works) +- [Vault layout](#vault-layout) +- [Building from source](#building-from-source) +- [Security](#security) +- [Development notes](#development-notes) + +--- + +## What it does + +- **Loads context** — at the start of any substantive conversation, reads the operator's preferences, current scope, and recent session logs from the vault (parallel GETs, ~3× faster than sequential) +- **Writes memory** — records facts, preferences, decisions, and project state on request or at conversation end +- **Logs sessions** — creates a session note after every meaningful conversation so future sessions can pick up where they left off +- **Bootstraps empty vaults** — carries the full vault scaffold inside the plugin; can stand up a fresh Obsidian vault from zero with no manual setup +- **Migrates existing vaults** — detects older schema versions (including the legacy `echo-vault.md` marker) and upgrades in place + +--- + +## Requirements + +- [Obsidian](https://obsidian.md/) running with the [Local REST API plugin](https://github.com/coddingtonbear/obsidian-local-rest-api) enabled +- HTTPS access to the Obsidian endpoint from wherever Claude/CoWork sessions run (self-hosted behind a reverse proxy or Tailscale works well) +- Claude or CoWork (the plugin runs as a Claude skill, not an MCP server) + +--- + +## Install + +Quicksilver ships as a single `.plugin` file. The source is kept private; distribute only the packaged artifact. + +1. Download `quicksilver.plugin` from this repository's latest commit (or from whoever shared it with you). +2. In CoWork → **Settings → Plugins**, click **Install Plugin** and select the file. +3. The skill `quicksilver` becomes available. It will self-configure on first use. + +> The `.plugin` file is a ZIP archive with forward-slash separators — do **not** rebuild it with PowerShell `Compress-Archive` on Windows, which produces backslash paths that violate the plugin spec. Use Python `zipfile` instead (see [Building from source](#building-from-source)). + +--- + +## First-run setup + +The plugin ships with **no endpoint or API key baked in**. On first invocation, the skill detects there is no local config file and prompts for: + +| Prompt | Example | +|--------|---------| +| API endpoint FQDN | `https://obsidian.example.com` | +| API key | the bearer token from the Local REST API plugin | +| Operator name | `Alex Rivera` | +| Operator role | `Network Engineer` | + +The skill verifies the endpoint (a `200` or `404` on the vault marker path is both fine — `404` just means the vault hasn't been scaffolded yet), then writes a **local config file**: + +``` +~/.quicksilver/quicksilver-config.json (macOS/Linux) +%USERPROFILE%\.quicksilver\quicksilver-config.json (Windows) +``` + +```json +{ + "schema": 1, + "endpoint": "https://obsidian.example.com", + "api_key": "", + "operator": { "name": "Alex Rivera", "role": "Network Engineer" }, + "configured_at": "YYYY-MM-DD", + "verified_at": "YYYY-MM-DD" +} +``` + +This file lives in the user home directory — outside the plugin and the vault — so it survives plugin reinstalls and is never committed anywhere. The API key is **never written into the vault**; the in-vault marker (`_agent/quicksilver-vault.md`) records only schema version, bootstrap date, and operator name. + +On all subsequent sessions the skill reads the config silently and proceeds with no prompts. + +### Vault bootstrap (automatic) + +If the vault marker is missing after credentials are confirmed, the skill runs a full bootstrap: creates the folder tree, seeds templates and anchor files (with the operator's name and role substituted in), and writes the marker last. The vault is fully usable after the first session. + +--- + +## Usage + +The skill trigger is `quicksilver`. It fires automatically at the start of substantive conversations and on explicit phrases: + +| Intent | Example phrases | +|--------|----------------| +| Load context | "pick up where we left off", "anything on X before my meeting?" | +| Save a fact | "remember that", "save this", "note that", "log this decision" | +| Check memory | "what do you know about me", "load my profile", "check my notes" | +| Capture to inbox | "add to my inbox", "save this for later" | +| Track a project | "start tracking this project", "log this as a new project" | + +### What gets written where + +| What | Where | How | +|------|-------|-----| +| Quick capture / unsorted | `inbox/captures/inbox.md` | POST (append) | +| Operator preferences | `_agent/memory/semantic/operator-preferences.md` | PATCH | +| Durable facts / patterns | `_agent/memory/semantic/.md` | PUT | +| Project state | `projects//.md` | PUT + PATCH | +| Decisions (ADR-style) | `decisions/by-date/YYYY-MM-DD-.md` | PUT | +| Session logs | `_agent/sessions/YYYY-MM-DD-HHMM-.md` | PUT | +| Daily activity | `journal/daily/YYYY-MM-DD.md` | PATCH | + +All agent-written notes carry `agent_written: true` and a `source_notes` list for traceability. + +### Inbox triage + +The inbox (`inbox/captures/inbox.md`) is the catch-all for unrouted captures. At the start of each substantive session the skill checks for items older than ~7 days and, if any are present, surfaces them once: + +> "Three captures from last week are still in the inbox — want to route them now or leave them?" + +Routing moves each item to its proper home and logs the move to `inbox/processing-log/YYYY-MM-DD.md`. + +### Project lifecycle + +Projects move through four folders that must stay in sync with the `status:` frontmatter field: + +``` +projects/incubating/ → projects/active/ → projects/on-hold/ + ↘ projects/archived/ +``` + +The skill promotes/demotes notes by moving the file **and** updating `status:` in the same operation. A file in `projects/active/` with `status: on-hold` is broken state — the skill repairs it on contact. + +--- + +## How it works + +### Two-layer "is this set up?" detection + +| Layer | File | Answers | Holds | +|-------|------|---------|-------| +| 1. Local config | `~/.quicksilver/quicksilver-config.json` | "Is this install configured?" | FQDN + API key + operator identity | +| 2. Vault marker | `_agent/quicksilver-vault.md` (in vault) | "Is this vault bootstrapped?" | schema_version, bootstrap date, operator name — **never the key** | + +The secret lives client-side because you need it before you can reach the vault, and the plugin's safety rules forbid keys in vault notes. + +### Plugin-as-source-of-truth + +All control logic ships inside the plugin under `skills/quicksilver/`. The vault holds **data only** — no `CLAUDE.md`, `BOOTSTRAP.md`, `STRUCTURE.md`, or `index.md` live in it. This means: + +- **Portable** — point the skill at any empty vault and it stands itself up +- **Updatable** — update the plugin, not the vault; existing data is unaffected +- **Repairable** — bootstrap is idempotent; run it again to fix missing structure + +### Write safety + +The skill applies three write-safety rules to prevent silent data corruption: + +1. **Search-before-create** — before writing any new note, searches the whole vault for the slug to catch notes filed in other lifecycle folders or under a different name +2. **Read-before-append** — before any POST (which appends), GETs the target file and substring-checks for the exact line about to be written; skips if already present (prevents duplicate entries from retries) +3. **Doc-map-before-PATCH** — before the first PATCH to a file in a session, GETs the document map (`Accept: application/vnd.olrapi.document-map+json`) to confirm the exact heading path; prevents `400 invalid-target` errors from stale heading assumptions + +### Loading order + +At session start, five reads fire in parallel: + +1. Vault marker (`_agent/quicksilver-vault.md`) — "is it set up? which schema?" +2. Operator preferences (`_agent/memory/semantic/operator-preferences.md`) +3. Current context (`_agent/context/current-context.md`) +4. Session directory listing → read the ~5 most recent by filename (lex = chrono) +5. Today's daily note (`journal/daily/YYYY-MM-DD.md`) + +If a project is in scope, two more searches follow (slug + human title) across all four lifecycle folders. + +--- + +## Vault layout + +``` +/vault/ +├── README.md ← thin human signpost (not read for routing) +├── inbox/ +│ ├── captures/inbox.md ← quick captures, date-prefixed lines +│ ├── imports/ +│ └── processing-log/ +├── journal/ +│ ├── daily/ weekly/ monthly/ +│ └── templates/ +├── projects/ +│ ├── active/ incubating/ on-hold/ archived/ +│ └── project-template.md +├── areas/ ← business / personal / learning / systems +├── resources/ +│ ├── concepts/ references/ meetings/ source-material/ +│ └── people/ +├── decisions/ +│ └── by-date/ ← YYYY-MM-DD-.md (ADR-style) +├── reviews/ ← weekly / monthly / quarterly / annual +├── archive/ +└── _agent/ + ├── quicksilver-vault.md ← bootstrap marker (plugin-owned) + ├── context/ + │ └── current-context.md ← active scope + scope history + ├── memory/ + │ ├── semantic/ ← operator-preferences.md, durable facts + │ ├── episodic/ ← event records + │ └── working/ ← transient, time-boxed + ├── sessions/ ← YYYY-MM-DD-HHMM-.md + ├── templates/ + └── outputs/ + ├── briefs/ drafts/ summaries/ synthesis/ +``` + +### Plugin source layout + +``` +quicksilver.plugin.src/ +├── .claude-plugin/plugin.json +├── README.md ← plugin-facing README (install & config) +└── skills/quicksilver/ + ├── SKILL.md ← operating procedure (authoritative) + ├── references/ + │ ├── operating-contract.md ← durable principles + safety rules + │ ├── bootstrap.md ← first-run / bootstrap / repair / migration + │ ├── vault-layout.md ← canonical layout + frontmatter conventions + │ ├── api-reference.md ← REST endpoint patterns + memory routing map + │ └── session-log-template.md + └── scaffold/ ← files bootstrapped verbatim into the vault + ├── quicksilver-vault.md ← marker template + ├── README.vault.md ← vault README template + ├── templates/ ← 8 note templates (daily, weekly, project, etc.) + └── anchors/ ← operator-preferences, current-context, inbox seeds +``` + +--- + +## Building from source + +Rebuild `quicksilver.plugin` from the source directory using Python `zipfile`. **Do not use PowerShell `Compress-Archive`** — it emits backslash separators in the ZIP central directory, which violates the plugin spec. + +```bash +cd /path/to/quicksilver +python3 - <<'EOF' +import zipfile, pathlib + +src = pathlib.Path("quicksilver.plugin.src") +out = pathlib.Path("quicksilver.plugin") + +with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf: + for f in sorted(src.rglob("*")): + if f.is_file(): + zf.write(f, f.as_posix()) # forward-slash paths + +print(f"Built {out} ({out.stat().st_size:,} bytes, {len(zf.namelist())} files)") +EOF +``` + +Version is in `.claude-plugin/plugin.json`. Bump it before rebuilding. + +--- + +## Security + +### API key handling + +- The API key is stored **only** in the local config file (`~/.quicksilver/quicksilver-config.json`) on the operator's machine +- It is **never** written into the vault, committed to git, or logged in session notes +- The config file should have permissions `0600` (the skill attempts this on write) + +### Git history + +If you fork or clone this repo: the original `echo-memory` predecessor committed a live API key (`241265fbe…`) in its history. **Rotate that key on the backend** regardless of whether it appears in any current source file — it is present in historical commits and should be considered compromised. + +### Endpoint + +The Local REST API should be: +- Behind HTTPS with a valid TLS certificate (the skill does not pass `-k`) +- Not exposed on a public IP without authentication (the API key is the only auth layer) +- Reachable from wherever Claude/CoWork sessions run — Tailscale or a reverse proxy work well for self-hosted setups + +--- + +## Development notes + +### Conventions + +- Slugs are kebab-case ASCII, max ~40 chars +- Session filenames are canonical: `YYYY-MM-DD-HHMM-.md` — the HHMM component is required for correct lex-sort (which equals chrono-sort for the session-loading step) +- All frontmatter dates are `YYYY-MM-DD`; session `created:` may use `YYYY-MM-DDTHH:mm` +- `[[wikilinks]]` go only in note **bodies** (`## Related` sections), never in frontmatter — YAML parses them as nested lists +- `source_notes` frontmatter values are plain relative paths (strings), not wikilinks + +### Monthly vault health + +On the first substantive session of each calendar month, the skill runs a quick health pass and writes findings to `reviews/monthly/YYYY-MM-vault-health.md`. Checks: + +- Stale active projects (`updated:` > 30 days → probably `on-hold`) +- Unprocessed inbox items (> 14 days) +- Duplicate slugs across lifecycle folders +- Key headings still present in frequently-PATCHed files + +### Schema versioning + +The vault marker's `schema_version` field drives migrations. Current schema: **1**. To add a migration, increment the version in `scaffold/quicksilver-vault.md` and add a versioned step to `references/bootstrap.md` under *Migrations*. + +### Scope switching + +When the operator changes focus mid-session, the skill updates `_agent/context/current-context.md` in three writes: prepend the prior scope to `## Scope History`, replace `## Scope` with the new one, and bump the `updated:` frontmatter field. Scope History is trimmed to the last ~10 entries.