'Read and write to the knowledge base vault. Use when the user says /vault, asks to remember/save knowledge, wants to recall/query stored knowledge, asks to extract learnings from a project, or wants to log a session. Also use proactively at session end to capture non-obvious learnings.'
Install
npx skillscat add uhop/vault-storage/vault Install via the SkillsCat registry.
Knowledge Base
Persistent knowledge base, accessed via a REST API (vault-storage). The LLM writes and maintains all content.
Connection
Requires two environment variables (set in ~/.env, which is sourced by .bashrc):
VAULT_API_URL— base URL of the vault REST API (vault-storage; e.g.,http://host:8123)VAULT_API_TOKEN— bearer token for authentication
Use vault-curl — don't hand-roll curl
There is a vault-curl wrapper on $PATH (installed under ~/.local/bin/vault-curl). Prefer it over raw curl — it prepends $VAULT_API_URL and the Authorization: Bearer $VAULT_API_TOKEN header, checks the env vars, and forwards every remaining flag straight to curl.
Quick check before the first vault op in a session:
command -v vault-curl >/dev/null || { echo "vault-curl missing — falling back to curl"; }vault-curl itself exits with a clear error if VAULT_API_URL or VAULT_API_TOKEN is unset, so no separate guard is required. Only fall back to raw curl if vault-curl isn't installed on the machine.
Never grep dotfiles for the credentials. vault-curl resolves VAULT_API_URL / VAULT_API_TOKEN from the already-sourced env (~/.env via .bashrc). If a call fails for missing creds, report it — do not scan ~/.bashrc / ~/.env / other dotfiles to find them. The auto-mode classifier flags systematic dotfile credential-scanning as Credential Exploration and denies it (correctly).
API endpoints (invoked via vault-curl <path> [curl-options...]):
- Read:
vault-curl /vault/{path} -s - Write (JSON — THE write path):
vault-curl /vault/{path} -X PUT -H 'Content-Type: application/json' --data-binary @payload.json- Body shape:
{"frontmatter": {...}, "body": "..."}— the server takes the FM object directly, skips YAML parse, and serializes safely (auto-quoting colon-space, leading-special-char, hex/bool/date-shadow strings). Always use this when authoring or modifying frontmatter values. - Construct the payload with
jqand--rawfileto safely embed a body that contains arbitrary characters — write scratch under aWORK=$(mktemp -d)dir, not a hardcoded/tmpname (CLAUDE.md § "Scratch files"):jq --null-input --rawfile body "$WORK/body.md" '{frontmatter: {title: "X", ...}, body: $body}' > "$WORK/payload.json". - Prose FM values (
title,agent.summary) go via--arg, never as inline jq literals. An apostrophe in an inline literal closes the single-quoted jq program (yields a bashsyntax error near unexpected token).--rawfilealready covers the body;--arg name "$VALUE"(apostrophe-safe inside double quotes, referenced as$namein the filter) covers FM strings the same way. Hit 2026-06-15 on a session-logagent.summarycontaining "JS's". - Same downstream FM merge / closed-enum validation / auto-managed-key rejection /
created-updatedindexer-override as the markdown mode.
- Body shape:
- Write (markdown — round-trip only):
vault-curl /vault/{path} -X PUT -H 'Content-Type: text/markdown' --data-binary @file.md- Never hand-author YAML through this mode — that's the recurring quoting-trap failure class (colon-space, leading
@/*/-/?, hex/bool/date shadows), and per the 2026-06-11 decision it is reserved for the UI editor and for verbatim round-trips: GET a server-emitted file, text-edit the body only, PUT it back. The YAML you re-send was machine-serialized, so it's safe. Any FM change → use the JSON path above. - Add
-o /dev/null -w "%{http_code}\n"to confirm a 204 without flooding stdout (works for either Content-Type).
- Never hand-author YAML through this mode — that's the recurring quoting-trap failure class (colon-space, leading
- Conditional writes (
If-Match, use for read-modify-write on shared docs):GET /vault/{path}returns anETagheader (sha256 of the served bytes); send it back as-H 'If-Match: <etag>'on the PUT (either Content-Type) and the write lands only if the document hasn't changed in between — otherwise 412precondition_failedwithdetails.current_etag, meaning another writer got there first: re-GET, re-apply your edit to the fresh copy, retry with the new tag. Adopt this for any flow that GETs a shared doc (queue.md, learnings.md, archives), modifies it, and PUTs it back — it converts silent last-writer-wins clobbering into a visible, retryable conflict. Capture the ETag with-D-or-o /dev/null -D- | grep -i etag; successful PUTs (204) return the newETagso chained conditional edits don't need a re-GET.If-Matchnever creates files (412 on a missing path); plain unconditional PUT remains valid for docs only one session touches. - Supersede (replace a note, archiving the old):
vault-curl /vault/supersede -X POST -H 'Content-Type: application/json' --data-binary @payload.jsonwith{old_path, new_path?, frontmatter, body}— the successor in the standard JSON write shape;new_pathdefaults toold_path(supersede-in-place: the successor takes over the path, so inbound wikilinks resolve to the replacement). Use this — never DELETE+PUT or a wholesale overwrite — whenever a write replaces a note rather than evolving it: the old note moves to<dir>/archive/<YYYY>/<name>with its record id intact (edges/embeddings/suggestions survive) and getsstatus: superseded; the successor's body is auto-appended a> Supersedes [[<archived-path>]].footer that backs the typedsupersedesedge (don't add your own). Validation-first — a rejected request (bad FM, occupiednew_path/archive slot) mutates nothing. Routine edits to an existing note stay plain PUTs; supersession is for replacement semantics. - List:
vault-curl /vault/{path}/ -s(trailing slash →{"files": [...]}) - Delete:
vault-curl /vault/{path} -X DELETE— for junk with zero history value; a note retired in favor of other content should be superseded (or moved to an archive folder), not deleted. - Search:
vault-curl /search/simple/ -X POST -G --data-urlencode 'query=...'- The vault REST API expects
queryas a URL parameter on a POST;-G --data-urlencodeproduces the right form.
- The vault REST API expects
Pagination — page by items.length, never by your requested limit
Paginated reads (/sections, /suggestions) return an envelope{items, offset, limit, total} that echoes the effective offset/limit —
the server caps limit (currently ≤ 100) and reports the value it actually
used, alongside total. So advance by what you got, not what you asked
for: step offset += items.length each page and stop whenitems.length === 0 (or offset >= total). Requesting limit=200 returns
only 100; stepping offset by 200 then silently skips records 100–199 of every
page (the failure that under-counted a coverage scan 800/1513 and a suggestion
fetch 235/435). Never guess the page size — read the envelope, or just use the
returned array length. (Folder lists /vault/{path}/ → {files} are not
paginated; they return everything in one shot.) Envelope-design rationale —
echo effective offset/limit, optional total, else a last flag or cursor:~/Open/articles/design/web-apps-client-server-api-design.md § "Lists and
paging".
Guard jq pipes in parallel Bash batches
When you fire several calls as parallel Bash tool calls in one message — as/vault resume, /vault wrap, and /vault sweep all do — never let avault-curl … | jq … pipe exit non-zero. If the API returns an unexpected
shape, jq exits non-zero → the Bash call exits non-zero → the harness
cancels its in-flight parallel siblings (the classic casualty ischeck-drift.sh sharing a batch with these reads). Append || true to any
such pipe (or guard it as if vault-curl …; then jq …; fi) so a malformed
response degrades to empty output instead of taking down the whole batch.
Bare vault-curl … -s reads with no jq stage are already safe and need no
guard.
check-drift.sh is also a canceller, not just a casualty. It exits 1
whenever it detects drift — the common case, not an error. Co-batched as a
parallel Bash sibling, that exit-1 cancels the other calls (reindex, lint,
suggestions, agent-workflow reads). Run check-drift.sh in its own Bash
invocation, sequentially, before the parallel read batch — never inside it.
Read the drift report from stdout; the exit code is not the signal.
Fallback: raw curl
If vault-curl is unavailable, verify env vars explicitly:
[[ -z "${VAULT_API_URL:-}" || -z "${VAULT_API_TOKEN:-}" ]] && { echo "Error: VAULT_API_URL and VAULT_API_TOKEN must be set in ~/.env"; exit 1; }Then use curl -H "Authorization: Bearer $VAULT_API_TOKEN" "$VAULT_API_URL/<path>" with the same endpoints listed above.
Vault structure
raw/ # unprocessed source material
topics/ # compiled wiki notes (1 concept = 1 note)
projects/ # per-project knowledge (subfolder per project)
{project}/
decisions.md # architecture & design decisions
learnings.md # gotchas, patterns, what worked
stack.md # tech stack & dependencies
queue.md # outstanding work
state.md # baseline snapshot for vault-check-drift
queries/ # filed Q&A research outputs
logs/ # session logs
_index.md # archived 2026-04-29 — kept for inbound wikilinks; do not updateDiscovery is dynamic via the live API, not via a curated index file:
| Question | Tool |
|---|---|
| What topics exist? | vault_list_folder("topics/") |
| What projects? | vault_list_folder("projects/") |
| Recent logs / queries | vault_list_pieces(type=log, updated_since=…) |
| Find a note about X | vault_search(X, mode=semantic) |
| What links to / from X? | vault_backlinks(X) / vault_neighborhood(X) |
| Tag taxonomy | vault_list_tags, vault_records_by_tag |
| Top items across the fleet | vault_queue_top(limit=N) |
| One project's open queue | vault_queue_by_project(name) |
| One project's archive | vault_queue_project_archive(name) |
| Everything at priority N (Backlog) | vault_queue_by_priority(n) |
| Fleet-wide Active / Watching | vault_queue_by_section(section) |
Queue endpoints are backed by queue_items, a derivative the watcher keeps in
sync with each project's queue.md and queue-archive.md. Markdown stays
source of truth — see [[topics/project-queue-convention]] for the shape and
[[projects/vault-storage/design/queue-items-table]] for schema + identity
model. Call vault_queue_reindex after a multi-machine pull to repopulate
slices the watcher didn't witness.
Note format
Every note MUST have YAML frontmatter:
---
title: Note Title
tags: [topic1, topic2]
created: YYYY-MM-DD
updated: YYYY-MM-DD
status: active
type: permanent | fleeting | project | query | log
related: ['[[other-note]]']
---Rules:
- Filenames in kebab-case:
auth-flow.md - Use wikilinks:
[[note-name]](not markdown links) for internal references - 1 concept per topic note (atomicity)
- Minimum 2 wikilinks per note (dense linking)
- Every note starts with a 1-2 sentence summary paragraph
Commands
/vault ingest
Compile ready raw notes into the wiki. Drafts (no ready: true)
are skipped — the user is still iterating on them.
- Pull the ready list.
vault-curl /maintenance/raw-inbox -s | jq
returns{ready: [...], drafts: [...]}. Process onlyready. If
that array is empty, report "no ready notes; N drafts waiting" and
stop. (The user flipsready: truein FM when a note is ripe.) - For each ready note, read the content via
vault-curl /vault/{path} -s. - Extract concepts — create or update topic notes in
topics/,
project notes inprojects/<name>/, or queue items inprojects/<name>/queue.mdper the content's nature. When a
compilation replaces an existing topic outright (the old note is
being retired, not extended), usePOST /vault/supersedeinstead of
overwriting in place — the predecessor is archived with its record id
and a typedsupersedesedge instead of silently vanishing into a
PUT. - Add wikilinks, backlinks, and tags on the derived notes.
- Enrich at capture. When creating a new topic note (or materially
rewriting an existing one), write theagent:block in the same PUT
— born-enriched is cheaper than a later backfill pass through/vault-enrich-all. Field shape and quality guidance:~/.claude/skills/vault-enrich-all/SKILL.md§ "Per-noteagent:
block shape" + § "Generate enrichment fields". Setderived_from_hash: "auto"— the server stamps the hash of the body
it writes plusderived_at(2026-07-09; on an older server computesha256(body)locally). Use
the JSON write path (Content-Type: application/jsonwith{frontmatter: {...}, body: "..."});agent.summaryregularly
contains colon-space prose that 500s through the markdown path's
YAML parser, and JSON sidesteps that whole class of authoring trap
(the hash value also doesn't need explicit quoting under JSON — the
value is a string in the JSON object, and the server'syaml.stringifyemits the right YAML for it). The indexer picks
the block up on import and folds the summary into the chunk-prefix
at embed time. - Archive the source. After successful ingestion of a single raw
note, in this order:- PUT the source with
readyremoved andprocessed: trueadded
(and a> Ingested YYYY-MM-DD → [[derived/note]]footer pointing
at the primary derived target if there is one). POST /vault/movefromraw/<name>.mdtoraw/archive/<YYYY-MM-DD>-<name>.mdso the inbox surfaces only
pending material.
Process notes one-at-a-time end-to-end: derived note created →
source updated → moved to archive. A failure mid-ingest leaves
earlier notes archived and the rest still pending — safe to retry/vault ingestto resume.
- PUT the source with
/vault learn
Extract learnings from the current project/session.
- Identify the current project from git remote, directory name, or ask
- Read existing project notes if they exist (
projects/{name}/) — in one
call viavault-curl "/system/resume-bundle?project=<name>&logs=0&project_bodies=learnings,decisions,stack,queue" -X POST -s
(full bodies for the dedup pass; feedback.md always included; on an older
server fall back to individual reads) - Analyze recent work: git log, changed files, decisions made
- Create or update
projects/{name}/learnings.md,decisions.md,stack.md - Extract cross-project patterns into
topics/notes (e.g., "api-rate-limiting", "docker-networking"). Propose-then-write: before creating, check neighbours withPOST /vault/propose(search-before-write); if an existing note already covers the concept, extend it, and if the new write would replace it wholesale, usePOST /vault/supersederather than minting a near-duplicate. When creating a new topic note here, enrich at capture per the/vault ingeststep 5 procedure — write theagent:block in the same PUT. - Promote this session's new durable local memories into the vault. During the session the agent's auto-memory writes land in per-machine local memory (
~/.claude/projects/<hash>/memory/), which is not fleet-shared. For eachfeedback_*.md/project_*.mdwritten or materially updated this session, route it by theprojects/agent-workflow/decisionsD1 table — feedback rules →projects/<name>/feedback.md, project facts / deferred options →decisions.md/queue.mdBacklog — deduped and propose-then-confirm, never a blind copy. Most candidates are already captured elsewhere: verify againstdecisions.md/learnings.md/ globalCLAUDE.md/ existingtopics/first (pertopics/project-feedback-md-convention). The vault is the durable source of truth; where a local memory is promoted, leave a thin local pointer rather than a duplicate fact (double-writing the same rule to both stores reintroduces drift). This makes the local→vault migration continuous so per-machine memories stop accumulating. This is the write path that pairs with/vault resume's read offeedback.md.
/vault query {question}
Research a question against the vault.
- Use
vault_search(orPOST /search/simple/) to find candidate notes — trymode=semanticfor conceptual queries,mode=lexicalfor verbatim phrases. - Read the most relevant notes (use
vault_neighborhoodorvault_backlinksto expand context if a single note isn't enough). - Synthesize an answer.
- Optionally file the answer into
queries/YYYY-MM-DD-{slug}.mdif substantive — wikilinks back to the source notes used.
/vault lint
The hygiene lint, implemented as its own skill — /vault-lint
(~/.claude/skills/vault-lint/vault-lint.mjs). It reads every indexed record
via /sections and reports five categories — FRONTMATTER (required keys, date
sanity), WIKILINKS (broken body targets), DENSITY (topic notes < 2 outbound;
isolated project notes), CURRENCY (per-type retention), DUPLICATES
(near-identical folders / titles) — against the thresholds intopics/vault-hygiene-policy.md. Exit 0 clean, 1 on findings. Read-only:
it reports, never fixes. Full docs + flags + v1 limitations:~/.claude/skills/vault-lint/SKILL.md.
~/.claude/skills/vault-lint/vault-lint.mjs # full report
~/.claude/skills/vault-lint/vault-lint.mjs --quiet # tab-separated data lines (pipe/grep)Do not confuse it with the server-side integrity lint, which is a different
tool (embeddings / orphans / temporal anomalies / tag aliases — not hygiene):
vault-curl /system/lint -s # integrity, NOT hygieneOn findings, decide (the linter won't):
- Fix legitimate issues directly (frontmatter backfill, broken-link rewrites).
- For per-type retention findings (e.g., logs > 90 days), move to
logs/archive/<YYYY>/rather than delete; archival preserves content while
removing it from the default/vault resumereading set. - For duplicate-folder candidates, decide canonical and bulk-rewrite inbound
wikilinks (the 2026-04-27tape6/→tape-six/dedup is the procedural
template — seeprojects/tape-six/decisions.md§ Project name).
/vault log {description}
Save a session log.
- Create
logs/YYYY-MM-DD-{description}.md - Record: what was done, decisions made, pending items, key files touched
- Add wikilinks to relevant topic/project notes
- Enrich at capture. Write the
agent:block in the same PUT that
creates the log — born-enriched, so the log is searchable-sharp while it's
hot (theagent.summarybecomes a HyDE prefix at embed time), with no later
backfill. Logs are append-only, so the block never re-stales. Use the JSON
write path ({frontmatter: {agent: {...}}, body: "..."}); setcomplexity: log-entry; setderived_from_hash: "auto"— the server
replaces the sentinel with the hash of the body it writes and stampsderived_attoo (2026-07-09; on an older server computesha256(body)
locally).
Field shape + quality guidance:~/.claude/skills/vault-enrich-all/SKILL.md. Don't backfill old logs —
enrichment value is largest at capture: a log is already self-describing
(dated title + sections), so a retroactive summary adds little, and logs age
out tologs/archive/at 90d. Born-enrich the new one; leave the old ones. - Refresh the drift baseline. Run
~/.claude/skills/vault-check-drift/check-drift.sh --updatefrom the project
directory so the next/vault resumestarts from a clean baseline (the
session's commits / tags /npm publishare typically done by the time
you're logging). Bootstrapsstate.mdif the project has no baseline yet.
Skip only when there's no project working directory in scope (rare —
logging cross-project work, vault-only sessions).
/vault resume
Rebuild context from the vault. Several steps below run as parallel Bash
calls; guard every vault-curl … | jq … pipe with || true per § "Guardjq pipes in parallel Bash batches" so a malformed response can't cancelcheck-drift.sh running in the same batch.
- Drift check first. Run
~/.claude/skills/vault-check-drift/check-drift.sh
from the current project directory (see thevault-check-driftskill for
details) as its own Bash call — not inside the parallel batch in
steps 2–5. It exits1whenever it detects drift (the common case), which
would cancel co-batched siblings; read the report from stdout. If drift is
detected, surface it at the top of the resume output before reading logs —
the vault's view of the project may be stale, and the recorded logs reflect
that stale view. - One-shot bundle — call
vault-curl "/system/resume-bundle?project=<name>&logs=3" -X POST -s
with<name>= the current project (from git remote or directory
name). The server runs the incremental reindex first, then packages
what used to be five separate reads. Surface each block per the old
rules:reindex— quiet on a no-op (changedFiles: 0); report counts when
something got reindexed; mentionfellBack: true(the full-reindex
path — history loss or first run).lint— pre-filtered to non-zero checks. Ifok=false, surface the
categories with counts and first samples at the top of the resume
output. These are bug indicators — report, don't auto-fix.suggestions—{total, by_kind}of pendings. Iftotal > 0, one
summary line; the dedicated review skills handle decisions.workflow—activeis the agent-workflow Active section: surface
verbatim under aWorkflow:heading when non-null. Ifclarify_pending > 0, one line likeClarify queue: N pending (/clarify to drain). Nulls mean the
surface isn't scaffolded — omit silently.logs— the most recent session logs as theiragent.summary
lines. Skim the summaries; fetch a full body
(vault-curl /vault/{path} -s) only when a summary is missing or
the session directly continues that log's work.project—feedback.mdarrives with its full body: surface its
rules near the top of the resume output (this is the read path for
fleet-shared project feedback — the vault is pull-only, not
auto-loaded like local memory; seetopics/project-feedback-md-convention). The other files
(queue/decisions/learnings/stack) come assummary+body_bytes;
fetch bodies only as needed.
- Fallback (pre-bundle server). A 404 from the bundle endpoint means
an older server — run the individual calls instead:POST /maintenance/incremental-reindex,GET /system/lint,GET /suggestions/summary, the two agent-workflow file reads
(projects/agent-workflow/queue.md§ Active,clarify-queue.mdpending count), the 3 most recentlogs/entries,
and the project's notes includingfeedback.md— guarding everyvault-curl … | jq …pipe with|| trueper § "Guardjqpipes in
parallel Bash batches". - Summarize current state and what's left to do. If
check-driftflagged
new commits / tags / publishes that aren't reflected inprojects/<name>
notes, update those notes to match (or at minimum flag the divergence in
the summary). - After syncing, run
check-drift --updateso the baseline captures the
refreshed view and the next resume starts from a clean slate.
/vault wrap [optional log slug]
Close the session cleanly — symmetric counterpart to /vault resume. Bundles
learning extraction, session log, and drift baseline refresh into one step so
nothing the session produced gets lost.
- Run the
/vault learnworkflow above — extract learnings intoprojects/{name}/{learnings,decisions,stack}.mdand surface cross-project
patterns intotopics/notes. - Run the
/vault logworkflow above with the supplied slug (or derive one
from the session's primary subject if the user didn't supply it). Step 4 of/vault logrefreshes the drift baseline as its closing action — no
separatecheck-drift --updateinvocation needed here. - Report a short summary of what was saved: project notes touched, log file
path, baseline refreshed.
Use this when ending a session that produced shipped work, decisions, or
cross-project learnings worth preserving. Skip when a session ends with
nothing worth preserving — don't write stub logs to be ceremonial.
/vault check [--update]
Run the drift check standalone. Typically used mid-session to re-sync
after a user-driven commit, push, or publish.
~/.claude/skills/vault-check-drift/check-drift.sh # report only
~/.claude/skills/vault-check-drift/check-drift.sh --update # report + refresh baselineThe skill file at ~/.claude/skills/vault-check-drift/SKILL.md documents
the signal sources, baseline file format, and report shape.
In multi-writer setups (the host pulls vault-data from a remote that
another machine pushed to), follow the project drift check with an
incremental reindex so the local DB catches up to the new HEAD:
vault-curl /maintenance/incremental-reindex -X POST -s | jqSkip when working solo or when no git pull has happened recently —
the watcher already kept the DB in sync with local edits. A no-op call
is fast (a few ms) but unnecessary. The endpoint reports{fromCommit, toCommit, changedFiles, imported, deleted, renamed, fellBack, durationMs}; surface anything non-zero, otherwise stay
quiet.
/vault sweep [options]
Drain every safely-automatable maintenance queue in one pass. Orchestrates
the existing review/cleanup skills + endpoints; loops each queue with--auto --limit=100 until the count hits zero or stops dropping.
/vault sweep # safe defaults
/vault sweep --dry-run # report what would run; no writes
/vault sweep --include=edge_type,new_tag
/vault sweep --exclude=tag_suggestion
/vault sweep --include-destructive # also runs duplicate review + compaction
/vault sweep --max-passes=N # loop cap per kind (default 5)Safe set (default)
Listed in execution order (see § Ordering constraints):
| Source | Action |
|---|---|
lint.orphan_embeddings + lint.orphan_doc_embeddings |
POST /maintenance/cleanup-lint |
lint.records_without_embeddings + lint.embedding_hash_drift |
POST /maintenance/embed-pending |
coverage: enrichable knowledge notes with no agent: block — canonical source is vault-storage's GET /system/lint → coverage.enrichment (ENRICHABLE_TYPES = permanent/project/design/research/query; the headline already excludes operational types, empty bodies, and archived). Read the count there; /vault-enrich-all § Enrichable set drives the enumeration. Not a suggestion kind, so invisible to /suggestions/summary. |
/vault-enrich-all --auto --limit=100 (backfill missing) |
suggestions.agent_enrichment_stale |
/vault-enrich-all --auto --stale --limit=100 (refresh drifted) |
suggestions.new_tag |
/vault-review-tags --auto --limit=100 |
suggestions.tag_suggestion |
/vault-review-tags --auto --kind=tag_suggestion --limit=100 |
suggestions.edge_type |
/vault-review-edges --auto --limit=100 |
Opt-in (--include-destructive)
| Source | Action |
|---|---|
suggestions.duplicate |
/vault-review-duplicates --auto --limit=100 (merge can delete) |
suggestions.compaction_candidate |
/vault-compact <folder> per candidate |
Always skipped
raw_inbox.ready—/vault ingestis a separate workflow; the user
flipsready: truewhen a draft is finished, not the sweep.suggestions.archive_candidate— per-record retention judgment.suggestions.inefficiency_detected/infrastructure_upgrade—
reports-only, no action surface.
Ordering constraints
Some kinds mutate state that other kinds read. Process them in declared
order; never dispatch two ordered kinds as parallel sub-agents.
- Enrichment before the tag and edge passes. Both enrichment passes
(the missing-block backfill/vault-enrich-all --auto --limit=100and the--stalerefresh) writeagent:blocks, and a freshly written block makes
the server file newtag_suggestion(fromagent.tags_suggested),
sometimesnew_tag(an unknown suggested tag), and can feededge_type
(fromagent.edge_classifications). Run both enrichment passes first,
thennew_tag→tag_suggestion→edge_type, so the suggestions
enrichment generates drain in the same sweep instead of surfacing as
residue. (Before this ordering enrichment ran last, and thetag_suggestion
items it filed were always left for the next sweep — observed 2026-06-21.) new_tagbeforetag_suggestion.new_tagmints canonical tags
and aliases into the taxonomy;tag_suggestion's accept/reject logic
reads the canonical-taxonomy state at decision time. Parallel
dispatch races — atag_suggestionagent can reject a suggestion
whose tag the concurrentnew_tagagent is about to canonicalize.
Drainnew_tagto zero (or to a stuck pass) before startingtag_suggestion. The 2026-05-13 instance was harmless (the tag was
already on the record's FM, so the rejection didn't drop intent),
but the failure mode generalizes.
Kinds with no declared ordering have no inter-kind dependency; they
may run sequentially in arbitrary order. Default to sequential
per-kind processing — concurrent sub-agents complicate failure
attribution and the per-kind passes are already cheap.
Procedure
- Baseline. Pull
vault-curl /system/lint -s,vault-curl /suggestions/summary -s, the enrichment-coverage count
(enrichable knowledge notes lacking anagent:block — from the/vault-enrich-allcoverage scan; this is not a suggestion kind, so/suggestions/summarynever shows it), and (cheap) record counts.
Compute the action set from the safe defaults plus--include/--exclude/--include-destructive. - Dry-run. If
--dry-run, print the planned action set with
per-kind counts and stop. - One-shot endpoints first. Run
cleanup-lintandembed-pending
(in parallel, both POST). Each completes in seconds; together they
tighten the lint baseline before the suggestion-driven passes
touch records. - Per-kind drain loop. Process the actions sequentially, one fully
drained before the next starts, in the order set by § Ordering constraints:
the two enrichment passes first (missing-block backfill, then--stale), thennew_tag→tag_suggestion→edge_type. Never dispatch
two kinds as concurrent sub-agents — even pairs with no declared ordering,
since concurrent FM writes muddy failure attribution. For each action, for
at most--max-passesiterations (default 5):- Dispatch the corresponding skill with
--auto --limit=100. - Re-measure that action's backlog:
/suggestions/summaryfor the
suggestion kinds; the coverage scan (notes lacking anagent:block)
for the missing-block backfill. - Stop when the count reaches 0, or when it didn't decrease since the
previous pass (stuck — sub-agent deferred, or items need human
judgment).
- Dispatch the corresponding skill with
- Final summary. Print before/after counts per kind, total time,
and any kind that stopped above zero with a note about why
(max-passes reached vs. stuck vs. skipped).
A stuck kind isn't a failure — some suggestions legitimately need user
input, and the sub-agent's --auto mode is conservative on ambiguity.
The summary surfaces the residue so the user can decide whether to
hand-triage or leave it for the next sweep.
The action set is computed once from the baseline at step 1. With enrichment
now running first, its downstream tag/edge suggestions drain in the same
sweep; any suggestion that still emerges after its own pass has already
run is reported as residue, not chased. This keeps each invocation bounded;
a second /vault sweep picks them up.
/vault (no subcommand)
Show vault status: note counts per folder, recently updated notes, any lint warnings.
Proactive behavior
This skill should be used proactively when:
- The user discovers a non-obvious pattern, gotcha, or decision worth preserving
- A debugging session reveals something that would save time in the future
- Cross-project knowledge is generated (e.g., "this Docker networking trick works everywhere")
- The user says "remember this", "save this", "note this down"
When in doubt, ask: "Want me to save this to the vault?"