vaultmem
AGENTS.md3 starsChanged 4 months agoMITView on GitHub
Cold-agent map for this repo. CLAUDE.md is a symlink to this file; Codex, Cursor, Copilot, and Gemini read AGENTS.md directly. 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 — copies vaultmem…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- sessionSkill
- vault-captureSkill
- vault-curateSkill
- vault-recallSkill
The file
We split this AGENTS.md into the rules it gives an agent
2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 29
Read the script top-to-bottom before making non-trivial changes — that's the point of it being one file (see README § Design notes).
- Distinctive: line 34
No build step (bash script, nothing to compile).
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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.ext/jev/— the optionaljevextension (vaultmem-jev, bash 3.2 +curl+jq): typed yes/no, choice, and score answers from a remote evaluation model. A separate executable, never sourced by core. See § The extension model.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)
d=$(mktemp -d) && ln -s /bin/bash "$d/bash" && PATH="$d:$PATH" 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 diffThis 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), setup (init), and the extension shim (jev). groom --triage
annotates its cold-parked/stale-active rows with a groom-triage judgment and
is advisory only: judging runs after the scan, so it never reaches a move.
nudge adds a second Stop-hook line when capture-worthy finds a
durable decision/root cause in the transcript tail. search --rerank reorders
content hits (never curated rows) by the extension's rerank score; any
failure prints plain ripgrep order. doctor --drift adds a DRIFT section
from the index-drift set (Agent-Index rows that no longer describe their
note).
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. nudge (Stop hook) holds the
same contract, and nudge keeps it on every Jev failure path: it
prints nothing extra and exits 0.
The extension model
Core opens no socket. ./vaultmem never makes a network call and never gains
a dependency; anything that needs either lives in an extension, a separate
executable under ext/<name>/vaultmem-<name>. jev is the only one, and
jev is the only dispatch word extensions take: do not add git-style "any
vaultmem-<x> on $PATH is a subcommand", because search is the default
dispatch case and that makes every query ambiguous.
_ext_exec <name> [args]resolves the executable in a fixed order:$VAULTMEM_EXT_DIR/<name>/, then${XDG_DATA_HOME:-~/.local/share}/vaultmem/ext/<name>/, thenext/<name>/beside the symlink-resolved script. Never$PATH. Not found: one stderr line, exit 3. It exportsVAULTMEM_BIN(absolute, symlink-resolved) andVAULTMEM_CONFIG; the extension reads vault data only by calling"$VAULTMEM_BIN", never by parsing the registry.- Args after
jevpass to the extension verbatim: the arg loop stops parsing core flags (--vault,--format,-n,-h) once the first positional isjev. _jev <set-name> <vault-id> [args]is the helper integrated commands will call (no caller yet). State on stdin. It returns 3 without forking when[ext.jev] enabledis nottrueor the vault lacksjev = true, so a disabled Jev costs a hook nothing. Callers treat anything but 0/1/2 as "no opinion" and print what they print today. The undocumented_jevdispatch word exists only so bats can reach the helper.vaultmem jev configis a frozen contract, answered by core, never forwarded. One line each, values unquoted, defaults filled in, in this order:ext.jev.enabled,.model,.base_url,.zdr,.timeout_ms,.key_file(~expanded),.log,.rerank,.hooks, then any other[ext.jev]key in file order, thenvault.<id>.jev=true|falsefor every registry vault with a path. It must work with no config file and no vault (jevis in the pre-dispatch allowlist). The extension's tests stubVAULTMEM_BINwith a script printing this format, so changing it breaks the extension silently. A bats test pins it byte for byte.- Core lints
[ext.<name>]for subset shape only; key names belong to the extension (vaultmem jev doctor).[vault.<id>] jevmust be a bare boolean. - The pre-jev names (
vaultmem judge,groom/doctor/nudge --judge,[ext.judge],[vault.<id>] judge,hook_judges,calibration --judge) are accepted until the next breaking release and each print one stderr line naming the replacement. Hook paths keep stdout untouched, anddoctorreports a deprecated key as aWARNwithout moving its exit code. The old user-override dir and log path are not migrated; see the CHANGELOG migration table.
Design and rationale: docs/design/jev-extension.md.
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. Run the suite with abash→/bin/bashsymlink first onPATH(the command above) +./tests/bash32-lint.sh.BASH=/bin/bash bats …does nothing: bash resets$BASHat startup, so the suite still runs on bash 5. See docs/development.md. doctor --driftis informational and must never affect the exit code.DRIFTrows print under their own heading;doctor --driftexits exactly whatdoctorexits, so a probabilistic lint never breaks CI or a hook.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 |
| Jev extension: setup, egress rules, set files, exit codes | docs/jev.md |
| Jev extension design, gateway findings, build order | docs/design/jev-extension.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 |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

