Session
Agent memory over an Obsidian vault — bash + ripgrep, no database. A restricted-TOML vault registry, a curated Agent Index, a wikilink graph, and a Projects→Sessions lifecycle. Ships the vaultmem CLI + memory skills as a Claude Code plugin.
npx -y skills add jayantak/vaultmem --skill sessionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 27 days oldThe repository was created 27 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Always-active cross-session working memory. Auto-activates every conversation to offer resuming or starting a session. Also triggers on "start a session", "resume", "park this", "save this", "wrapping up", or multi-session/multi-repo work. Routes work to the correct Obsidian vault, groups sessions under a parent Project (epic), and distills durable insights up into the Project note and MOCs at stopping points.
SKILL.md
18.9 KB, as published. Nobody here has run it
session — cross-session working memory
The hot layer above the durable Obsidian vaults. Sessions are per-thread
worklogs that survive /clear. The conversation is disposable; the session
file is the state. Pairs with vaultmem (search/registry/router),
vault-recall (finding what's already written), and vault-capture (durable
capture).
The vault layout, frontmatter fields, status vocabulary, and glyph convention
this skill writes are the normative contract in SCHEMA.md (run vaultmem vaults for the live registry). The templates below are the operational shapes;
SCHEMA.md is the reference when they disagree.
Always-on activation
The SessionStart hook prints a picker line (vaultmem sessions) ending
in an AGENT DIRECTIVE — the trailing prompt that tells the agent what to do
with the picker. Its wording is configurable (the registry's directive_file);
the default asks the agent to route session handling through this skill. That
directive is binding: invoke this skill FIRST, every
conversation, before any other skill, tool call, or reply. Memory lookups
(vault-recall, vaultmem) do NOT substitute — even when they read the
same session file, route through this skill first, then continue. Be concise;
never block the user's actual task. If the user skips, do NOT re-ask this
conversation.
Trivial one-shot exception: if the first user message is a quick question or a single read-only lookup answerable in one reply, skip this skill silently and just answer. The moment the thread turns into real work (edits, a task, a second substantive turn), invoke the skill then. Never announce the skip.
Vault routing (never hardcode a vault)
Always resolve the vault through the router:
- Guess:
vaultmem which(prints the routed vault id). - Confirm with one line: "New session in the <vault> vault — ok? (or say the other)".
- Registry facts (paths, sessions root, default MOC):
vaultmem vaults.
Resolve <vault_path> with vaultmem path <vault> (bare path on
stdout — use it directly in scripts as V="$(vaultmem path <vault>)";
vaultmem vaults still prints the full registry).
A session is one task, not one project
A session = one focused unit of work (a feature, a bug, an investigation).
The tier above a session is a Project (Projects/<name>.md) — that is
where cross-session state accumulates. When the unit of work closes, park the
session and open a fresh one under the same Project; do not keep one session
open as a stand-in for the whole project (that is what makes _index.md
balloon). Tiers: MOC (domain map) → Project (epic, owns repos +
cross-session state) → Session (one thread).
List projects with vaultmem projects; see one with
vaultmem project <name> (its repos, linear pointer, MOC, and sessions
by status).
Create a session
- Route + confirm vault (above).
- Attach to a Project. Resolve the parent epic:
- Run
vaultmem whichfor the vault, thenvaultmem projectsto see candidates. If the cwd is a repo, prefer the Project whoserepos:lists it. - If exactly one fits, use it. If ambiguous or none, ask one line: "Which
project does this belong to —
<a>/<b>/ new / none?". - If a new Project is needed, create
Projects/🟢 <name>.mdfrom the Project shape (below) withtype: project,status: active,repos:, andaliases: ["<name>"](glyph on filename + H1; see Status glyphs below).
- Run
- Pick a short kebab
<thread>from the task. - Write
<vault_path>/Sessions/<thread>/_index.mdfrom the template, stampingproject:,aliases: [<thread>](so the Project's back-link resolves), + inheritingrepos:from the Project, and auto-linking MOCs/repos/people inferred from the opening context. - Register the session on the Project. Append a line to the Project note's
## Sessionsindex:- [[<thread>]] — <one-line> (status: active)(+ the tracker issue id if the project has one, e.g.(PROJ-1234, active)). This[[<thread>]]only resolves because step 4 gave the session analiases: [<thread>]entry.
_index.md template
---
thread: <thread>
project: <project-or-blank>
vault: <vault-id>
status: active
aliases: [<thread>] # REQUIRED — so [[<thread>]] back-links resolve (the file is _index.md, basename ≠ thread)
updated: <YYYY-MM-DD HH:MM>
---
# 🟢 <thread>
**Goal:** <one line> **Project:** [[<project>]] **MOCs:** [[<moc>]] **People:** [[<person>]]
**Repos:** [[<repo>]] (branch <branch>) **Related:** [[<prior-session>]]
## Bookmark
Last: <what just happened> · Next: <next action> · Open: <unresolved>
## Pinned
## Work log
## Decisions
## Git state
| Repo | Branch / worktree | PR | State |
|---|---|---|---|
Parent Project. project: is the session's parent epic — a Projects/<name>.md
note. The picker groups sessions by it, and distill promotes durable bits up to
it. Leave it blank only for genuinely one-off work with no home. repos: default-
inherit from the Project note; restate them in the header (override only if this
session touches a different repo).
The spine is fixed; topical sections are free-form. Bookmark, Pinned,
Work log, Decisions, Git state are the required skeleton. Add your own
## <topic> working sections as the task needs them (an analysis in progress, a
decomposed plan, a verdict) — real sessions grow these, and distill collapses
them. Don't force everything into the work log.
## Pinned = load-bearing constants that must survive distill. The handful
of facts you'd be annoyed to re-derive: the stage/env gotcha, the one binding
constraint, the non-obvious command, the canonical file to edit, the worktree
layout. Keep it short (≤6 lines); it is re-read first on resume. Distill never
strips it — correct it in place when a constant changes. (Leave the heading empty
until you have a constant worth pinning.)
Link targets must resolve. The [[<moc>]] / [[<repo>]] / [[<person>]]
placeholders are not free text — a wikilink works only if a note's exact
basename or alias matches. Link MOCs by filename [[MOC - <Topic>]] (or a
declared alias like [[Payments]]); for a repo/person, link only if a real note
exists (vaultmem mocs / index to check), else use plain text. Never
wikilink repo artifacts (ADR IDs, file paths, PR numbers). Full rules:
vault-capture § Linking Rules.
The Project note (Projects/<name>.md)
The long-lived epic. Frontmatter:
---
type: project
status: active # active | parked | done
vault: <vault-id>
repos: [<repo>] # sessions inherit this
linear: <url> # optional, if the project tracks tickets (read for context; never a worklog)
moc: "[[MOC - <Topic>]]" # optional — the domain map above this project
updated: <YYYY-MM-DD>
---
Body spine: ## Sessions (the index, grouped by status) · ## Pinned
(cross-session load-bearing constants) · ## Decisions (durable cross-session
decisions) · free-form topical sections. Keep it lean the same way _index.md
is — distill (below) is what keeps it that way.
Status glyphs (sidebar self-sorts by state)
Every Project and Session carries a status glyph so the Obsidian file-tree
sidebar shows state at a glance. The glyph maps 1:1 to the status: field:
| status | glyph |
|---|---|
| active | 🟢 |
| parked | 💤 |
| done / shipped | ✅ |
- Session (
_index.md): glyph goes on the H1 only (# 🟢 <thread>). NEVER glyph the session folder name —vaultmemkeys sessions by folder basename == thethread:field, so a glyphed folder desyncs them and breaks the picker/groom. Sessions churn fast and their status shows in the picker anyway, so the H1 glyph is enough. - Project (
Projects/<name>.md): glyph goes on both the filename and the H1 (🟢 <name>.md/# 🟢 <name>) — the filename is what the sidebar sorts. Renaming a flat project file is safe as long as you first add the plain name as a frontmatteraliases:entry (aliases: ["<name>"]) so every existing[[<name>]]link keeps resolving.vaultmemstrips the leading glyph when matching a project to its sessions, so sessions keep the plainproject:name — never write the glyph into a session'sproject:. - Keep it in sync on every transition. When a status flips (active→parked at
park, →done at retire), update the glyph in the same write: the H1 for a
session; the H1 and the filename for a project (re-
mv+ keep the alias). A stale glyph is a lie the sidebar tells — treat it like a stale## Bookmark. - Non-status notes (references, reviews,
2026 Review) take no status glyph.
Indexed mode (auto-persist, no prompts)
Once a session is active, after every few tool calls (or whenever you produce durable content — decisions, findings, plans):
First edit of the conversation: Read _index.md before editing it (a
section read via offset/limit satisfies this). The Edit tool rejects writes
to never-read files — this is the top recorded tool error in past sessions.
The same applies to the Project note and MEMORY.md.
vaultmem cat/bookmark do NOT satisfy that precondition. They are separate
processes; the Edit tool only counts a file as read when you read it with the
Read tool. Use them for cheap inspection (resume, re-anchoring, checking a
section before deciding), and Read the file itself before the first edit.
- Append a timestamped bullet to
## Work log. Lead it with a status marker when it helps scanning — DONE / BLOCKED / DECISION / NEXT. - Rewrite
## Bookmark(Last / Next / Open) — always current, never stale. - If git state moved (branch, PR, worktree, merge/push), refresh
## Git statein the same write. The work log is the source of truth for what happened; the table is just the at-a-glance index — never let it contradict the log. - Add or correct a
## Pinnedconstant if this turn surfaced one (a gotcha, a binding decision, the canonical command/path). - Keep header wikilinks current (
[[MOC]],[[repo]],[[person]],[[prior-session]]); bumpupdated:.
Never keep durable results only in conversation memory. The write is atomic: content + Bookmark (+ Git state if it moved), same turn. Corrections edit the existing section in place rather than appending.
Re-anchor periodically. On a long thread, every ~5–6 substantive turns
re-read your own ## Bookmark + ## Pinned (vaultmem bookmark <thread>) before deciding the next move — it
keeps you on the task instead of drifting with the conversation, and it surfaces
when the file has bloated enough to checkpoint (below).
Resume
Run vaultmem bookmark <thread> — it prints exactly the ## Bookmark +
## Pinned blocks, not the whole file. Those two are the resumable state (what
just happened / what's next / the load-bearing constants). Read further sections
only if the next action needs them, with vaultmem cat <note> --section '## <Heading>'.
The picker's number/name selects the thread; resolve its _index.md path.
If the bookmark already declares completion (Next: none / done / shipped, or
the only Open item is a tracker link), don't resume it as if there's work left —
flip status: done + ✅ right then and tell the user, rather than reopening a
finished unit.
Checkpoint (distill in place, keep going)
Checkpoint is the pressure-release valve that keeps _index.md lean without
needing a stopping point. Trigger it on the FIRST of these — don't wait for the
user to ask:
- the user says "checkpoint" / "distill but keep going"; OR
_index.mddrifts past ~150 lines (the work log is dense — it bloats fast); OR- the conversation has run long / context is filling AND you've produced durable content (a decision, a finished analysis, a shipped change) since the last distill — checkpoint at the next turn boundary rather than riding context to the edge. Catching it early beats an emergency compaction that loses the trail.
Then load references/distill.md and run the
checkpoint flow: promote durable bits to the vault, collapse the promoted
work-log entries to → promoted to [[note]] pointers, leave ## Pinned intact,
and keep status: active. No _meta.md event, no clear — the conversation
continues. It is the lighter alternative to park when there's no stopping point
yet. A one-line confirm is enough; don't interrupt the user's flow to ask
permission for an in-place checkpoint.
Park / end + distill
When the user says "park", "wrapping up", "let's clear", OR you detect a natural
stopping point (task list drained AND a coherent unit closed AND context
growing), load references/distill.md and run the park
flow (checkpoint + _meta.md event + clear-safe). Offer: "Natural stopping point
— distill durable bits into the vault and clear?"
At park time, decide done vs parked — don't default to parked. If the unit
of work is complete (the bookmark says shipped/done/nothing next, or the only
remainder is tracked on GitHub/Linear), set status: done + ✅ immediately in
the same write, not "parked" as a placeholder for later cleanup. parked/💤 is
ONLY for work that will genuinely resume — a real next action you expect to
pick up. Sessions are not ticket trackers: an open PR review, a follow-up
issue, or a "waiting on review" state tracked on GitHub/Linear does not keep a
session alive on its own — leave a pointer to the tracker item and retire the
session as done.
Lifecycle & grooming
A session moves active → parked → done, then is archived — archived is a
location, not a status: archived sessions live under Sessions/_archive/<thread>/
and drop out of every listing surface (picker, projects, project). Wikilinks
resolve by basename, so [[<thread>]] keeps working after the move. The picker
shows active + parked only (resumable work); done is hidden, awaiting groom.
On each transition, update the session H1 glyph (🟢→💤→✅) in the same write —
and when a Project's own status changes, re-mv its file to the new glyph
(💤 when parked, ✅ when shipped) and fix its H1, keeping the plain-name alias.
The SessionStart picker appends a ⚠ … run vaultmem groom nudge when a
vault has done sessions (ready to archive), parked sessions untouched past
OBSIDIAN_SESSION_COLD_DAYS (default 21), or active sessions untouched past
VAULTMEM_STALE_ACTIVE_DAYS (default 7). vaultmem status surfaces the same
nudge in short form. When you see it:
vaultmem groommechanically moves everydonesession into_archive/and flips its parent Project's## Sessionsline toarchived. Safe to run anytime (done was already distilled at park). Scope with-v <vault>. It also archives everydoneProject intoProjects/_archive/— unless a session outside_archive/still points at it, in which casegroomprints a warning naming the blocking session(s) instead of moving it. Clear those first (archive or reassign the session) and re-rungroom.- It also lists cold-parked sessions for triage — it never auto-retires them.
For each, drive the decision with the user: resume (open it, status back to
active), or retire. To retire: confirm it was distilled at its last park (it has a_metapark event /→ promotedpointers — cold means untouched since park, so normally yes); if not, run the distill loop (references/distill.md) first; then setstatus: doneso the nextgroomarchives it. - It also lists stale-active sessions for triage — an
activesession that has gone untouched this long likely stalled. Triage it the same way as cold-parked, just from the other side of the fence: either the work actually finished (a bookmark that was never flipped — setstatus: doneper the park-time rule above, so the nextgroomarchives it), or it is genuinely still live (park it if paused but not abandoned, or touchupdated:and keep itactive). A stale-active entry is usually the symptom this whole rule set exists to catch — the park-time done/parked decision above is what prevents it from recurring.
Project retirement
Projects get the same active → done → archived lifecycle as sessions, with one
added precondition: a Project can only retire once its goal is met and every
session under it is done or already archived — a Project with a live
session is not done, no matter how old it looks (groom enforces this too: it
refuses to archive a done Project a live session still points at, and names
the blockers). When that holds:
- Distill any remaining durable content (open
## Pinnedconstants, unresolved## Decisions) up to the vault's MOC tree, same as a session distill — the Project note disappears from active listings after this, so nothing should be left only there. - Set
status: donein frontmatter. - Glyph both the filename and the H1 (✅), keeping the plain-name
aliases:entry so existing[[<name>]]links keep resolving — same rule as any other Project status transition (see Status glyphs above). vaultmem groommoves it intoProjects/_archive/on its next run. Archived is a location, not a status, exactly like sessions — wikilinks keep resolving via basename/alias after the move.
Frugality rule
Never read a full file when a section will do — vaultmem cat <note> --section '## <Heading>' extracts one heading block without guessing offsets, and
vaultmem bookmark <thread> is the pre-cut Bookmark+Pinned pair (measured on a
real vault: 2568 bytes vs 11950 for the whole _index.md, 4.7x cheaper). Both
are read-only inspection — they do not satisfy the Edit tool's read-before-write
precondition (see Indexed mode). The _index.md IS the state;
keep it ~100–150 lines — when it grows past that, checkpoint (see above) to
promote and collapse rather than letting it bloat. Search across everything with
vaultmem <query> — run 2–3 pattern variants (exact term, synonym, adjacent
concept), and treat match lines as leads: read the note (or its section) before
answering, never answer from search output alone. Follow [[links]] between notes with
vaultmem links / backlinks (see vault-recall § Researching by
following wikilinks) instead of reading whole folders.