agentleFS
jayantakjayantak/vaultmem/AGENTS.md

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.

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
  1. Distinctive:

    Read the script top-to-bottom before making non-trivial changes — that's the point of it being one file (see README § Design notes).

    line 29
  2. Distinctive:

    No build step (bash script, nothing to compile).

    line 34

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

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 — copies vaultmem to ~/.local/bin (or symlinks skills/ 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), and vault-curate (what to write next / vault health). They drive the memory workflow on top of the CLI.
  • .claude-plugin/ — projects skills/ into an installable Claude Code plugin/marketplace.
  • ext/jev/ — the optional jev extension (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 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), 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 how vaultmem which routes 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>/, then ext/<name>/ beside the symlink-resolved script. Never $PATH. Not found: one stderr line, exit 3. It exports VAULTMEM_BIN (absolute, symlink-resolved) and VAULTMEM_CONFIG; the extension reads vault data only by calling "$VAULTMEM_BIN", never by parsing the registry.
  • Args after jev pass to the extension verbatim: the arg loop stops parsing core flags (--vault, --format, -n, -h) once the first positional is jev.
  • _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] enabled is not true or the vault lacks jev = 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 _jev dispatch word exists only so bats can reach the helper.
  • vaultmem jev config is 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, then vault.<id>.jev=true|false for every registry vault with a path. It must work with no config file and no vault (jev is in the pre-dispatch allowlist). The extension's tests stub VAULTMEM_BIN with 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>] jev must 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, and doctor reports a deprecated key as a WARN without 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 next arr[$path]=1 as an indexed assignment with an arithmetic subscript — so doctor --deep sprayed syntax errors per note and still exited 0. mapfile inside a process substitution doesn't trip set -e, which left subcommand-lint.sh passing on an empty list. Neither is visible on Homebrew bash 5, and neither is detectable by shellcheck. Run the suite with a bash → /bin/bash symlink first on PATH (the command above) + ./tests/bash32-lint.sh. BASH=/bin/bash bats … does nothing: bash resets $BASH at startup, so the suite still runs on bash 5. See docs/development.md.
  • doctor --drift is informational and must never affect the exit code. DRIFT rows print under their own heading; doctor --drift exits exactly what doctor exits, so a probabilistic lint never breaks CI or a hook.
  • shellcheck disables at the top of vaultmem are load-bearing, not boilerplate — SC2016 (backticks in the usage/help text are literal, not command substitution) and SC2012 (the MOC listing intentionally uses ls over plain filenames). Don't blanket-remove them to "clean up" a lint pass.
  • shfmt --diff is part of CI, separate from shellcheck — a change that passes shellcheck can still fail CI on formatting. Run shfmt -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 when updated: is missing/unparseable.
  • This repo is generated payload for some consumers. skills/*/SKILL.md files 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 pass subcommand-lint.sh), but know a downstream sync process — not this repo — is their canonical origin.

Where deeper truth lives

TopicDoc
Registry/config keys, TOML subset, routing algorithmdocs/config.md
Vault folder layout, frontmatter contract, Agent-Index grammarSCHEMA.md
SessionStart hook wiring (Claude Code + Codex), directive customizationdocs/hooks.md
Claude Code plugin packaging, skill discovery, version syncdocs/plugin.md
Install paths, quickstart, full command reference, design rationaleREADME.md
Jev extension: setup, egress rules, set files, exit codesdocs/jev.md
Jev extension design, gateway findings, build orderdocs/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 conventionsdocs/development.md

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.