feat(agents-vault): sync the vault from the claude and agy wrappers

Both wrappers stay behind the C1 guard, so disabling
__fish_config_op_aliases still passes straight through to the real binary.

Launch commits but never pushes, keeping the network and any credential
prompt off the critical path; pushing is left to the Claude Code SessionEnd
hook. agy has no such hook, so its memory lands one launch later.
This commit is contained in:
2026-09-03 18:56:46 -04:00
parent 11f4551fa5
commit 2ad5bf75d2
6 changed files with 131 additions and 9 deletions
+9 -4
View File
@@ -475,8 +475,12 @@ function agents-vault --description 'track curated agent memory in a host-scoped
# named: git add refuses a pathspec that matches nothing --
# which one of the two always is, once it has been moved
# back -- and then stages none of them, not even the one
# that did match.
git -C "$vault" add -A -- projects 2>/dev/null
# that did match. Its stderr is deliberately not swallowed:
# projects/ provably exists here, so the only thing a
# redirect could hide is a real failure -- a held
# index.lock, say -- after which the index stays
# half-applied while nothing says so.
git -C "$vault" add -A -- projects
return 1
end
end
@@ -498,8 +502,9 @@ function agents-vault --description 'track curated agent memory in a host-scoped
# because even the git-native rollback leaves it out of line:
# the forward `git mv` overwrote the stashed entry's index
# entries, and moving the files back does not bring them back.
# See the same call above for why projects/ is named whole.
git -C "$vault" add -A -- projects 2>/dev/null
# See the same call above for why projects/ is named whole,
# and why its stderr is left visible.
git -C "$vault" add -A -- projects
echo "$c_err""agents-vault: could not relink $claude_root/$mangled/memory; $cur was left as it was$c_reset" >&2
return 1
end
+10 -4
View File
@@ -8,7 +8,7 @@
# aliases/dev-tools
#
# DEPENDENCIES
# agents-init
# agents-init, agents-vault
#
# SYNOPSIS
# agy [ARGS...]
@@ -18,9 +18,14 @@
# sub-repository is initialized and any agent-made changes are committed
# before launch. Delegates all scaffold and commit logic to agents-init
# --quiet (full setup), which ensures AGENTS/ is scaffolded and CLAUDE.md
# is symlinked to AGENTS/AGENTS.md in the current project. Arguments are
# forwarded verbatim to the real agy binary, except for -r/--resume which
# are translated to -c/--continue.
# is symlinked to AGENTS/AGENTS.md in the current project.
#
# Also syncs the host-scoped agent memory vault (agents-vault). agy has
# no session-end hook, so its memory is captured on the next launch
# rather than at session end.
#
# Arguments are forwarded verbatim to the real agy binary, except for
# -r/--resume which are translated to -c/--continue.
#
# Opinionated component (C1): when disabled via __fish_config_op_aliases
# (or the __fish_config_opinionated master), the command is passed through
@@ -44,6 +49,7 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su
end
agents-init --quiet
agents-vault --quiet
for i in (seq (count $argv))
if test "$argv[$i]" = "-r"
+8 -1
View File
@@ -8,7 +8,7 @@
# aliases/dev-tools
#
# DEPENDENCIES
# agents-init
# agents-init, agents-vault
#
# SYNOPSIS
# claude [ARGS...]
@@ -19,6 +19,12 @@
# Delegates all scaffold and commit logic to agents-init --quiet (full
# setup), which ensures AGENTS/ is scaffolded and CLAUDE.md is symlinked
# to AGENTS/AGENTS.md in the current project.
#
# Also syncs the host-scoped agent memory vault (agents-vault), which
# tracks curated memory living outside the project tree. The vault
# commits on launch but does not push; pushing happens from the Claude
# Code SessionEnd hook or an explicit agents-vault --push.
#
# All arguments are forwarded verbatim to the real claude binary.
#
# Opinionated component (C1): when disabled via __fish_config_op_aliases
@@ -42,6 +48,7 @@ function claude --wraps=claude --description 'claude wrapper: auto-links AGENTS.
end
agents-init --quiet
agents-vault --quiet
command claude $argv
end
+28
View File
@@ -58,6 +58,34 @@ function test_greeting_function_defined
functions -q fish_greeting
end
function test_agents_vault_defined
for f in agents-vault _agents_vault_dir _agents_repo_slug \
_agents_repo_ensure_symlink _agents_repo_sync \
_agents_repo_install_tools
if not functions -q $f
echo " missing function: $f"
return 1
end
end
end
function test_wrappers_call_agents_vault
functions -q claude; or return 1
functions claude | string match -q '*agents-vault*'; or return 1
functions -q agy; or return 1
functions agy | string match -q '*agents-vault*'
end
function test_vault_dir_honors_override
set -l saved
set -q __fish_agent_vault_dir; and set saved $__fish_agent_vault_dir
set -g __fish_agent_vault_dir /tmp/vault-override-check
set -l got (_agents_vault_dir)
set -e __fish_agent_vault_dir
test (count $saved) -gt 0; and set -g __fish_agent_vault_dir $saved
test "$got" = /tmp/vault-override-check
end
function functional_test_main
set -l names (functions -a | string match 'test_*' | sort)
set -l failed 0
+14
View File
@@ -11,6 +11,8 @@
# and loads it as an isolated interactive session.
# 3. Runs the functional checks in tests/functional.fish inside that
# loaded session.
# 4. Runs tests/test-agents-vault.fish as its own process; that suite
# builds its own throwaway repos and needs no loaded config.
#
# Usage: fish tests/run-tests.fish
@@ -86,4 +88,16 @@ if test $functional_status -ne 0
set overall_failed 1
end
# ---- Phase 3: hermetic vault helper tests --------------------------------
# Run as its own fish process rather than inside the sandboxed session:
# the suite builds its own throwaway git repos and binds the vault, claude
# and agy roots to them, so it needs no loaded config and must never see
# the real ~/.claude.
echo ""
echo "== Vault helper tests =="
fish $repo_root/tests/test-agents-vault.fish
if test $status -ne 0
set overall_failed 1
end
exit $overall_failed
+62
View File
@@ -958,6 +958,68 @@ popd >/dev/null
check "stash adopt rollback: ordinary run fabricated no entry" "$sf_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
check "stash adopt rollback: ordinary run kept the memory reachable" stashfail-precious (cat $croot9/$sfmang/memory/keep.md 2>/dev/null)
# The other rollback in --adopt: the *forward* move failing outright,
# before the relink is ever reached. A plain file sitting where the target
# entry would go makes `git mv` refuse ("destination already exists") and
# the coreutils fallback refuse too ("cannot overwrite non-directory"), so
# the worktree needs no repair -- but the index does, and that is the half
# of the rollback nothing else pins. With a real git there is no way for a
# test to leave a genuinely half-applied rename here, so the divergence
# stands in for one: the index is desynced by hand first, and the check
# that matters is that the way out re-read projects/ and healed it.
# Without that setup the repair is an invisible no-op, and a refactor can
# drop it with the suite still green.
set -l ff (new_repo https://git.rootiest.dev/rootiest/fwd-fail.git)
set -l ffslug git.rootiest.dev-rootiest-fwd-fail
set -l ffmang (string replace -a '/' '-' -- $ff | string replace -a '.' '-')
mkdir -p $croot9/$ffmang/memory
echo fwdfail-precious >$croot9/$ffmang/memory/keep.md
pushd $ff >/dev/null
agents-vault --silent
popd >/dev/null
# The blocker must be tracked and committed, so that the only thing dirty
# at adopt time is the divergence staged just below.
echo occupied >$vroot9/agent-vault/projects/fwdfail-target
pushd $ff >/dev/null
agents-vault --silent
popd >/dev/null
check "fwd-fail adopt: the blocking file is tracked" true (git -C $vroot9/agent-vault ls-files --error-unmatch projects/fwdfail-target >/dev/null 2>&1; and echo true; or echo false)
check "fwd-fail adopt: vault clean before the divergence" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
git -C $vroot9/agent-vault rm -q --cached projects/$ffslug/origin >/dev/null
set -l ff_dirty (git -C $vroot9/agent-vault status --porcelain | string join ',')
check "fwd-fail adopt: index diverges before the adopt" true (string match -q "*D projects/$ffslug/origin*" -- "$ff_dirty"; and echo true; or echo false)
set -l ff_entries (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
set -l ff_head (git -C $vroot9/agent-vault rev-list --count HEAD)
set -l ff_link (path resolve $croot9/$ffmang/memory)
pushd $ff >/dev/null
set -l ffrc (agents-vault --adopt=fwdfail-target --silent 2>/dev/null; echo $status)
popd >/dev/null
check "fwd-fail adopt: returns 1" 1 "$ffrc"
check "fwd-fail adopt: entries unchanged" "$ff_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
check "fwd-fail adopt: source entry intact" fwdfail-precious (cat $vroot9/agent-vault/projects/$ffslug/claude/memory/keep.md 2>/dev/null)
check "fwd-fail adopt: blocking file untouched" occupied (cat $vroot9/agent-vault/projects/fwdfail-target 2>/dev/null)
check "fwd-fail adopt: no origin note appended" false (string match -q '*adopted:*' -- (cat $vroot9/agent-vault/projects/$ffslug/origin); and echo true; or echo false)
check "fwd-fail adopt: nothing committed" "$ff_head" (git -C $vroot9/agent-vault rev-list --count HEAD)
check "fwd-fail adopt: live link never removed" "$ff_link" (path resolve $croot9/$ffmang/memory)
check "fwd-fail adopt: memory still reachable live" fwdfail-precious (cat $croot9/$ffmang/memory/keep.md 2>/dev/null)
check "fwd-fail adopt: no stash left behind" false (test -e $vroot9/agent-vault/.git/agents-vault-adopt-stash -o -e $vroot9/agent-vault/.adopt-stash; and echo true; or echo false)
# The pin: projects/ was re-read on the way out, so git describes the
# files as they actually are rather than as the abandoned rename left them.
check "fwd-fail adopt: index re-read to match the worktree" "" (git -C $vroot9/agent-vault status --porcelain | string join ',')
check "fwd-fail adopt: nothing left staged against HEAD" "" (git -C $vroot9/agent-vault diff HEAD --name-only | string join ',')
# And the ordinary run afterwards still finds the original entry.
pushd $ff >/dev/null
agents-vault --silent
popd >/dev/null
check "fwd-fail adopt: ordinary run fabricated no entry" "$ff_entries" (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
check "fwd-fail adopt: ordinary run kept the memory reachable" fwdfail-precious (cat $croot9/$ffmang/memory/keep.md 2>/dev/null)
set -e __fish_agent_vault_dir
set -e __fish_agent_vault_claude_root
set -g __fish_agent_vault_claude_home $HERMETIC_HOME/claude