Imported from jayantak/vaultmem (
AGENTS.md). Install upstream withnpx skills add jayantak/vaultmem. Copyright stays with the author.
AGENTS.md — vaultmem
Cold-agent map for this repo. CLAUDE.md is a symlink to this file; Codex,
Cursor, Copilot, and Gemini read AGENTS.md directly.
What this is
vaultmem is a single POSIX/bash script (./vaultmem, ~2800 lines) that
turns a plain-markdown Obsidian vault into agent
memory: search (ripgrep-backed), a curated Agent Index, a wikilink graph, and
a Project→Session lifecycle tier with a Task backlog feeding it. No daemon, no
database, no build step — the script is the artifact. This repo also ships:
install.sh— copiesvaultmemto~/.local/bin(or symlinksskills/into a harness skills dir with--skills <dir>).skills/— four Claude-Code-flavored agent skills, split by agent job:vault-recall(check the vault before re-deriving),session(cross-session working memory),vault-capture(write durable knowledge, incl. repo onboarding), andvault-curate(what to write next / vault health). They drive the memory workflow on top of the CLI..claude-plugin/— projectsskills/into an installable Claude Code plugin/marketplace.tests/— bats unit tests + a skills↔CLI drift lint.
Read the script top-to-bottom before making non-trivial changes — that's the point of it being one file (see README § Design notes).
Build / test / validate
No build step (bash script, nothing to compile). Before committing:
bats tests/vaultmem.bats # unit tests over the CLI (registry, search, graph, lifecycle)
BASH=/bin/bash bats tests/vaultmem.bats # …again under macOS system bash 3.2 (see below)
./tests/subcommand-lint.sh # every `vaultmem <cmd>` referenced in skills/**/*.md must exist in the dispatch table
./tests/bash32-lint.sh # no bash-4-only constructs (shellcheck cannot see these)
shellcheck vaultmem install.sh tests/subcommand-lint.sh tests/bash32-lint.sh
shfmt --diff vaultmem install.sh tests/subcommand-lint.sh tests/bash32-lint.sh # must produce no diff
This is exactly what CI (.github/workflows/ci.yml) runs, split into four jobs:
lint (shellcheck + shfmt + the bash-3.2 construct lint), test (bats, on
Ubuntu + macOS), bash32 (bats under macOS /bin/bash 3.2), and
subcommand-lint. All four must pass before merge.
Write for bash 3.2, not for the bash on your $PATH. macOS ships bash
3.2.57 as /bin/bash (frozen 2007; bash 4+ is GPLv3) and that is what the
SessionStart hooks run under, but most contributors have Homebrew bash 5.x —
so declare -A/local -A, mapfile/readarray, ${x^^}, |&, &>>, and
coproc pass locally and break on macOS. Both instances that reached main
failed silently (a doctor --deep that exited 0 finding nothing; a lint that
passed vacuously). shellcheck cannot detect this — it targets a dialect, not a
bash version. Full rationale, the banned list, and portable (faster) substitutes:
docs/development.md.
Tests never touch a real vault — tests/vaultmem.bats builds a throwaway
two-vault registry per test via VAULTMEM_CONFIG pointed at a bats
tempdir. Follow that pattern (isolated VAULTMEM_CONFIG + fixture vaults
under $BATS_TEST_TMPDIR) for new tests; never point a test at
~/.config/vaultmem/config.toml or a real vault path.
The CLI subcommand surface
vaultmem -h (or the top-of-file comment block, lines ~12–40) is the
authoritative usage summary — read it before adding or renaming a
subcommand. Broad shape: search/index (<query>, index, mocs), the
wikilink graph (resolve/links/backlinks/neighbors/dangling), the
router (vaults/path/which), the lifecycle tier
(sessions/projects/project/next/task/groom/status), hygiene
(doctor), and setup (init).
Adding usage lines? Bump the sed range. The usage block is printed by a
hardcoded sed -n '4,Np' "$0" in two places (the -h branch and the no-args
branch). Add lines to the comment block without bumping N and the help output
is silently truncated — no error, no test failure unless you pin it. A bats test
("usage output ends with the final usage comment line") asserts the last line
still renders; keep it pointed at whatever the real last line is.
Subcommands dispatch from the final case "${ARGS[0]:-}" in block at the
bottom of the script. Any subcommand a skill's markdown references by name
must exist there — tests/subcommand-lint.sh enforces this in CI, so a
renamed or removed subcommand that skills still mention fails the build, not
a user's session.
The config/registry model
Two related but distinct contracts — don't conflate them:
- Registry (
~/.config/vaultmem/config.toml, a restricted TOML subset parsed by hand-rolled awk) — which vaults exist and howvaultmem whichroutes a repo/cwd to one. Full key reference, accepted/rejected TOML shape, and the routing algorithm: docs/config.md. - Schema (SCHEMA.md) — what a compliant vault looks like
inside: folder layout, frontmatter fields the tool reads, the status
vocabulary (
active/parked/done), status-glyph filename convention, and the Agent-Index row grammar.
The config parser is deliberately not full TOML — vaultmem doctor hard-errors
on anything outside the accepted subset (arrays, nested tables, unquoted
strings, etc.) so a malformed config fails loudly instead of silently
mis-routing. groom moves files between folders on disk; a silent mis-route
there is the failure mode the lint exists to prevent. If you touch
_parse_config/_lint_config in vaultmem, keep both docs and the bats
doctor hard-errors on … tests in sync with the accepted-subset rules.
The hooks model
vaultmem status and vaultmem sessions are designed to run from an agent
harness's SessionStart hook (Claude Code settings.json, Codex
hooks.json) and are fail-quiet by contract: an unmounted/unconfigured
vault makes them exit 0 printing nothing, so a hook wired with a
command -v vaultmem >/dev/null && guard can never break a session start.
Full wiring examples for both harnesses, and how to customize the printed
directive_file line: docs/hooks.md. Never change
cmd_status/cmd_sessions to error on a missing vault — that contract is
load-bearing for every downstream hook config.
Skills → Claude Code plugin
skills/ ships three agent skills; .claude-plugin/{marketplace.json,plugin.json}
project them into an installable Claude Code plugin (/plugin marketplace add jayantak/vaultmem) where each skill appears namespaced (vaultmem:session,
etc.). Non-Claude-Code harnesses skip the manifests and use
install.sh --skills <dir> to symlink skills/<name>/ directly. How
discovery works, what each manifest file is for, and how skill content stays
version-locked to the CLI: docs/plugin.md.
Non-obvious gotchas
- The bash floor is 3.2 (macOS), and violations fail SILENTLY. bash 3.2
errors on
local -A, then parses the nextarr[$path]=1as an indexed assignment with an arithmetic subscript — sodoctor --deepsprayed syntax errors per note and still exited 0.mapfileinside a process substitution doesn't tripset -e, which leftsubcommand-lint.shpassing on an empty list. Neither is visible on Homebrew bash 5, and neither is detectable byshellcheck. RunBASH=/bin/bash bats tests/vaultmem.bats+./tests/bash32-lint.sh; see docs/development.md. shellcheckdisables at the top ofvaultmemare load-bearing, not boilerplate — SC2016 (backticks in the usage/help text are literal, not command substitution) and SC2012 (the MOC listing intentionally useslsover plain filenames). Don't blanket-remove them to "clean up" a lint pass.shfmt --diffis part of CI, separate from shellcheck — a change that passes shellcheck can still fail CI on formatting. Runshfmt -w vaultmem(or the specific file) before committing if unsure.- Status glyphs are presentation, never identity. A Session's
project:frontmatter field and a Project's session-index[[wikilink]]are always the plain name; the leading emoji (🟢/💤/✅/🔴/🟡) lives only on filenames and H1s. Matching logic strips the glyph before comparing — see SCHEMA.md § Status-glyph invariants. updated:frontmatter, not file mtime, drives staleness math (cold-parked detection, sort order) — file mtime gets rewritten by cloud-drive re-sync and would corrupt it. Mtime is only a fallback whenupdated:is missing/unparseable.- This repo is generated payload for some consumers.
skills/*/SKILL.mdfiles carry a<!-- GENERATED from the private dotfiles source repo -->marker — they're synced in from an external private source, not hand-authored here. Edit them here for this repo's purposes (they must stay accurate and passsubcommand-lint.sh), but know a downstream sync process — not this repo — is their canonical origin.
Where deeper truth lives
| Topic | Doc |
|---|---|
| Registry/config keys, TOML subset, routing algorithm | docs/config.md |
| Vault folder layout, frontmatter contract, Agent-Index grammar | SCHEMA.md |
| SessionStart hook wiring (Claude Code + Codex), directive customization | docs/hooks.md |
| Claude Code plugin packaging, skill discovery, version sync | docs/plugin.md |
| Install paths, quickstart, full command reference, design rationale | README.md |
| Skill content itself (workflows, conventions each skill teaches) | skills/<name>/SKILL.md |
| bash 3.2 floor, banned constructs, portable substitutes, test conventions | docs/development.md |