feat(agents-vault): back up curated agent memory to a host-scoped vault repo #126

Merged
rootiest merged 19 commits from feat/agent-memory-vault into main 2026-09-03 23:09:57 +00:00
2 changed files with 94 additions and 13 deletions
Showing only changes of commit 4c7334c46e - Show all commits
+35 -13
View File
@@ -84,6 +84,12 @@
# it defaults to off so a backgrounded push can never hang or prompt
# invisibly underneath a starting agent.
#
# The agy knowledge copy is merge-only. Files are copied into the vault
# but are never removed from it, so a fact deleted upstream from agy's
# knowledge store persists in the vault indefinitely, and a restore or a
# fresh clone brings it back. Prune such an entry from the vault by hand
# if it must really be gone.
#
# Three further variables exist only so the test suite can run against
# throwaway directories instead of the real home, and are not meant for
# everyday use. __fish_agent_vault_claude_root overrides Claude's
@@ -288,20 +294,36 @@ function agents-vault --description 'track curated agent memory in a host-scoped
# it). Never out of thin air -- ~/.claude/memory does not exist by
# default, and fabricating it would invent state Claude never asked
# for and permanently claim the path.
if test -d "$glive"; or test -L "$glive"; or test (count $gvault_content) -gt 0
# -e rather than -d on the live side so a *broken* global path (a stray
# regular file where the directory belongs) is noticed and reported on
# every run, instead of being silently skipped and mistaken for the
# absent-by-default case.
if test -e "$glive"; or test -L "$glive"; or test (count $gvault_content) -gt 0
# Best-effort, exactly like the agy copy above. Global memory is
# optional and frequently absent, and this block runs before the
# per-project link, so a fault here must warn and continue rather
# than return: aborting would let a stray file or a permission
# problem at ~/.claude/memory kill the per-project memory backup
# and its commit for every project, on every agent launch. The
# secondary feature must not take the primary one down with it.
#
# Continuing is safe: _agents_repo_ensure_symlink validates and
# refuses before it mutates anything, so its failure window is the
# same whether the caller returns or carries on. $changed stays
# untouched unless the link actually succeeded, and nothing is
# recorded to make a later run believe the global memory is linked
# when it is not -- the next run re-enters this block and retries.
if not mkdir -p "$gvault"
echo "$c_err""agents-vault: could not create $gvault$c_reset" >&2
return 1
end
set -l gmsg (_agents_repo_ensure_symlink "$glive" "$gvault")
set -l grc $status
if test $grc -ne 0
echo "$c_err""agents-vault: could not link $glive$c_reset" >&2
return 1
end
if test -n "$gmsg"
set changed 1
test $verbose -eq 1; and echo "$c_ok$gmsg$c_reset"
echo "$c_warn""agents-vault: could not create $gvault; skipping global memory$c_reset" >&2
else
set -l gmsg (_agents_repo_ensure_symlink "$glive" "$gvault")
set -l grc $status
if test $grc -ne 0
echo "$c_warn""agents-vault: could not link $glive; global memory not backed up$c_reset" >&2
else if test -n "$gmsg"
set changed 1
test $verbose -eq 1; and echo "$c_ok$gmsg$c_reset"
end
end
end
+59
View File
@@ -620,6 +620,65 @@ set -e __fish_agent_vault_claude_root
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
# ────────────── a failed global link must not abort the run ────────────
# The global block runs before the per-project link and before the commit,
# and global memory is optional and frequently absent. A fault there must
# never take the primary per-project backup down with it: otherwise a
# stray file or a permission problem at ~/.claude/memory would break
# memory backup for every project, on every agent launch.
echo ""
echo "== agents-vault (global link failure is non-fatal) =="
set -l vroot8 (mktemp -d); set -ga TMPDIRS $vroot8
set -l croot8 (mktemp -d); set -ga TMPDIRS $croot8
set -l chome8 (mktemp -d); set -ga TMPDIRS $chome8
set -l agy8 (mktemp -d); set -ga TMPDIRS $agy8
set -g __fish_agent_vault_dir $vroot8/agent-vault
set -g __fish_agent_vault_claude_root $croot8
set -g __fish_agent_vault_claude_home $chome8
set -g __fish_agent_vault_agy_root $agy8
# A regular FILE where the global memory directory belongs.
# _agents_repo_ensure_symlink refuses to replace a non-directory, so the
# global link cannot succeed here.
echo not-a-directory >$chome8/memory
# ... while this project has perfectly good memory waiting to be backed up.
set -l fp (new_repo https://git.rootiest.dev/rootiest/globalfail.git)
set -l fslug git.rootiest.dev-rootiest-globalfail
set -l fmangled (string replace -a '/' '-' -- $fp | string replace -a '.' '-')
mkdir -p $croot8/$fmangled/memory
echo project-memory >$croot8/$fmangled/memory/p.md
set -l ferr (mktemp); set -ga TMPDIRS $ferr
pushd $fp >/dev/null
set -l frc (agents-vault --silent 2>$ferr; echo $status)
popd >/dev/null
set -l fwarn (cat $ferr)
check "global link failure: exits 0" 0 "$frc"
check "global link failure: warns on stderr" true (string match -q "*$chome8/memory*" -- "$fwarn"; and echo true; or echo false)
check "global link failure: live file left alone" not-a-directory (cat $chome8/memory)
# The finding itself: the primary, per-project backup must still happen.
check "global link failure: per-project link still created" true (test -L $croot8/$fmangled/memory; and echo true; or echo false)
check "global link failure: per-project memory reached the vault" project-memory (cat $vroot8/agent-vault/projects/$fslug/claude/memory/p.md)
check "global link failure: vault still committed" true (test (git -C $vroot8/agent-vault rev-list --count HEAD) -ge 1; and echo true; or echo false)
check "global link failure: per-project memory is committed" true (git -C $vroot8/agent-vault ls-files --error-unmatch projects/$fslug/claude/memory/p.md >/dev/null 2>&1; and echo true; or echo false)
# A failed global link must not be recorded as done: the next run has to
# re-enter the block and warn again, not treat the vault as correct.
pushd $fp >/dev/null
set -l ferr2 (mktemp); set -ga TMPDIRS $ferr2
agents-vault --silent 2>$ferr2
popd >/dev/null
check "global link failure: retried on the next run" true (string match -q "*$chome8/memory*" -- (cat $ferr2); and echo true; or echo false)
set -e __fish_agent_vault_dir
set -e __fish_agent_vault_claude_root
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude
set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
# ──────────────────────── hermeticity assertion ────────────────────────
# The whole suite must never have touched the real global agent state. The
# failure this guards is specific: a global-memory sync with no test