quicksilver
Persistent memory for Claude via an Obsidian vault over the 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
- Requirements
- Install
- First-run setup
- Usage
- How it works
- Vault layout
- Building from source
- Security
- 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.mdmarker) and upgrades in place
Requirements
- Obsidian running with the Local REST API plugin 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.
- Download
quicksilver.pluginfrom this repository's latest commit (or from whoever shared it with you). - In CoWork → Settings → Plugins, click Install Plugin and select the file.
- The skill
quicksilverbecomes available. It will self-configure on first use.
The
.pluginfile is a ZIP archive with forward-slash separators — do not rebuild it with PowerShellCompress-Archiveon Windows, which produces backslash paths that violate the plugin spec. Use Pythonzipfileinstead (see 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)
{
"schema": 1,
"endpoint": "https://obsidian.example.com",
"api_key": "<bearer-token>",
"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/<slug>.md |
PUT |
| Project state | projects/<lifecycle>/<slug>.md |
PUT + PATCH |
| Decisions (ADR-style) | decisions/by-date/YYYY-MM-DD-<slug>.md |
PUT |
| Session logs | _agent/sessions/YYYY-MM-DD-HHMM-<slug>.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:
- 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
- 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)
- 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; prevents400 invalid-targeterrors from stale heading assumptions
Loading order
At session start, five reads fire in parallel:
- Vault marker (
_agent/quicksilver-vault.md) — "is it set up? which schema?" - Operator preferences (
_agent/memory/semantic/operator-preferences.md) - Current context (
_agent/context/current-context.md) - Session directory listing → read the ~5 most recent by filename (lex = chrono)
- 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-<slug>.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-<slug>.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.
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-<slug>.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; sessioncreated:may useYYYY-MM-DDTHH:mm [[wikilinks]]go only in note bodies (## Relatedsections), never in frontmatter — YAML parses them as nested listssource_notesfrontmatter 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 → probablyon-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.