feat(agents-vault): back up curated agent memory to a host-scoped vault repo #126
+35
-13
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user