Animus Recovery Guide¶
Steps for the predictable disaster cases. Not a substitute for the
broader ../reference/threat-model.md which describes what each
gate defends against and how.
At-rest-encrypted memory: rollback to unencrypted¶
Performed by scripts/setup-gocryptfs-vault.sh. The script's Phase 5
renames the original dirs to chroma.unencrypted-backup.YYYYMMDD-HHMMSS/
and memory.unencrypted-backup.YYYYMMDD-HHMMSS/ before installing
symlinks to the encrypted view. Those backup dirs are not removed
automatically. Until you remove them (a separate manual step), full
rollback is one command per dir.
# 1. Stop the daemon
systemctl --user stop animus.service
# 2. Remove the symlinks
rm -f ~/.animus/chroma ~/.animus/memory
# 3. Restore the originals (use the most recent backup suffix from `ls`)
mv ~/.animus/chroma.unencrypted-backup.* ~/.animus/chroma
mv ~/.animus/memory.unencrypted-backup.* ~/.animus/memory
# 4. Unmount the vault (best-effort)
fusermount -u ~/.animus/_secure 2>/dev/null
# 5. Revert the systemd unit
cp ~/.config/systemd/user/animus.service.bak.* ~/.config/systemd/user/animus.service
systemctl --user daemon-reload
# 6. Restart
systemctl --user start animus.service
The vault dir at ~/.animus/_vault/ can stay (it's just data the daemon
no longer touches) or be removed to free disk — rm -rf ~/.animus/_vault.
TPM unseal failed (after motherboard change, BIOS reset, etc.)¶
The TPM-sealed credential is bound to this machine's TPM. A new
TPM or BIOS-reset PCR state will break the unseal. Symptoms: daemon
fails to start, journal shows systemd-creds errors about unseal.
# Confirm the diagnosis
systemd-creds decrypt --name=animus-vault ~/.config/credstore.encrypted/animus-vault
# → expected: prints a passphrase
# → broken: "Failed to decrypt ... TPM2 error"
# Recovery path: rebuild the credential, re-encrypt the vault contents
# with a fresh passphrase. Source data IS still encrypted under the OLD
# passphrase; without TPM unseal you cannot recover memories.
#
# Mitigation: if you anticipate hardware changes, run setup with
# --with-key=host+tpm2 instead of --with-key=tpm2. Then a host-only
# decrypt remains possible if the TPM goes away, at the cost of disk-
# seizure resilience.
If you don't have a fresh passphrase and the TPM is gone: restore from Restic.
Restic restore (full memory dir from snapshot)¶
The three repos at ~/backups-local/:
- animus-state — ~/.animus/ (excluding hot caches), hourly
- animus-memory — ~/.claude/projects/.../memory/ (Claude memory), hourly
- animus-chroma — ~/.animus/chroma/ quiesced (daemon stopped), daily
# Source the env (sets RESTIC_PASSWORD_FILE + repo paths)
set -a; source ~/.config/restic/animus.env; set +a
# List snapshots in chroma repo
export RESTIC_REPOSITORY="$RESTIC_REPO_CHROMA"
restic snapshots
# Restore a specific snapshot to a recovery dir (NOT directly over live)
mkdir -p /tmp/animus-restore
restic restore <snapshot-id> --target /tmp/animus-restore
# Compare, then move into place
diff -r /tmp/animus-restore/home/arete/.animus/chroma/ ~/.animus/chroma/
# If satisfied:
systemctl --user stop animus.service
mv ~/.animus/chroma ~/.animus/chroma.pre-restore-backup
cp -a /tmp/animus-restore/home/arete/.animus/chroma ~/.animus/chroma
systemctl --user start animus.service
Repeat with RESTIC_REPO_STATE / RESTIC_REPO_MEMORY as needed.
Daemon won't start after a security feature change¶
Triggered when a new hardening change (integrity baseline, redaction pattern, MCP gate, …) breaks the daemon's boot path. The integrity checker is the most common culprit: if you edited a security-critical file and didn't regenerate the baseline, it refuses to boot.
# Check journal for the error
journalctl --user -u animus.service -n 50 --no-pager
# Common case: integrity drift
# Symptom: "IntegrityMismatchError: sha256 differs for ..."
# Fix: regenerate the baseline
ANIMUS_INTEGRITY_OVERRIDE=1 python -c "
from animus.integrity.checker import regenerate_baseline
regenerate_baseline()
"
# Then restart
systemctl --user start animus.service
If the integrity check itself is broken (rare), comment out
from animus.integrity.checker import verify_or_raise in the boot
path temporarily, restart, fix the underlying file, regenerate.
Red-team finds a HIGH bypass mid-flight¶
animus-redteam.timer exits non-zero on new HIGH findings. The systemd
default behavior is just "the timer logs and moves on" — no auto-page.
Active monitoring options:
# Read the latest dashboard
tail -50 ~/.animus/audit/standing-redteam-dashboard.md
# Read every recent HIGH finding from the JSONL ledger
jq -c 'select(.severity == "high" and .is_novel == true)' \
~/.animus/audit/standing-redteam-ledger.jsonl | tail -20
# Manually run the find→fix→regression loop
python -m animus.redteam.driver --n-per-category 5 --output-dir /tmp/redteam-investigate
# Inspect findings, patch packages/core/animus/memory/redaction.py
# Add regression test in packages/core/tests/test_redaction.py
# Re-run until 30/30 OK
Backup repos drift / can't decrypt¶
If the Restic password file is lost or the repos are corrupted, the backups are unrecoverable — there's no key escrow. To prevent this:
# Verify the password file exists + has the expected entropy
test -f ~/.config/restic/password && \
wc -c ~/.config/restic/password
# Expected: 45 bytes (per ls -la output)
# Confirm each repo is healthy (subset check, not the full --read-data)
set -a; source ~/.config/restic/animus.env; set +a
for repo_var in RESTIC_REPO_STATE RESTIC_REPO_MEMORY RESTIC_REPO_CHROMA; do
export RESTIC_REPOSITORY="${!repo_var}"
echo "=== $repo_var ==="
restic check --read-data-subset=5% 2>&1 | tail -5
done
When you migrate to B2 (memory's open item), re-test the integrity from B2 before retiring the local repos.
When to suspect something deeper¶
Three signs the rollback procedures above won't help:
- Repeated TPM unseal failures across reboots — TPM chip drift or firmware corruption. Replace passphrase + run setup again.
- Multiple novel HIGH findings in a single sweep, all in the same category — the gate has regressed wholesale, not just one shape. Check git log of the relevant module.
- Integrity check drift across security files we didn't touch —
possible filesystem corruption or an unauthorized modification.
Compare files against
git show HEAD:<path>and the most recent Restic state snapshot.