# Back up your agent's memory, not just its code

My agents' boards, logs and private memory live on one disk. A timer and each work cycle push them to a private repo per agent, checked on the remote.

Topic: Agents &amp; review
Tested: 2026-10-22
Revision: 1
By Roman Vaisman
Page: https://eng.vaisman.me/entries/back-up-agent-memory/

## In 30 seconds

- Each agent's tracker and private memory live on one machine; its code repo holds neither.
- A backup job copies both into a private repo per agent, commits and pushes.
- It succeeds only when the latest commit is on the remote after a fresh fetch.
- A timer runs it every 15 minutes, and the agent runs it at the end of each work cycle.

## Problem

My agents' working state lives where no code repo holds it: a tracker (a one-line-per-item board, an append-only log of what happened and why, task briefs) and private memory that is per machine and per project folder. On 2026-10-22 an audit found that no backup job covered the coordinating agent's memory.

## What I tried

| Attempt | What happened |
| --- | --- |
| Backing up only the project agents | The coordinator's memory had no backup until 2026-10-22, when it got its own private repo. |
| Backing up the coordinator's own checkout | Rejected: the job commits everything, and that checkout holds untracked files. |
| One shared job config per machine | One agent's failure would mark the others' backup failed; since 2026-10-10 an agent can have its own. |
| Job copies installed once per machine | On 2026-10-11 one still ran an outdated copy until reinstalled; on Macs an installer now refreshes it. |

## What worked

**One private repo per agent.** Tracker files at the top, a copy of the memory folder beside them. No secrets or customer data; env files, secret-named files and PNG images are ignored.

**Mirror, commit, push.** The job mirrors each source in, deletions included, so the copy matches the agent now, then commits with a UTC timestamp and pushes.

**Done means on the remote.** After a fresh fetch it checks each repo's latest commit is on its remote and pushes if not, which also catches commits a failed run left behind. A rebase in progress, a rejected push, a missing config or a held lock is a logged failure and a non-zero exit, so on Linux the job's service shows as a failed unit, not a quiet gap.

**Two triggers.** On my Linux boxes a timer runs it every 15 minutes and 3 minutes after boot, even with nobody logged in; on Macs a login agent runs it every 15 minutes. That's the safety net. The agent also runs the job at the end of each work cycle, so the push follows the decision, not the clock.

**Memory holds lessons, not state.** Memory keeps dated, sourced lessons and stable facts; any status or "next" goes on the board, where a morning report shows a stale line the next day. A weekly sweep enforces it.

**Restore.** On a new machine I clone the repo, copy the memory back, and have the resumed agent read the board and the last log entries first. Transcripts aren't backed up: the old session is gone, the direction isn't.

## Numbers

The sweep count, from 2026-10-18, is why memory holds no state.

| Label | Value | Kind |
| --- | --- | --- |
| Backup timer interval | 15 min | configured |
| First backup after boot | 3 min | configured |
| Stale claims in a memory sweep (2026-10-18) | 46 in 24 of 63 notes | measured |

## When it stops being true

The after-boot delay counts from machine boot, not from when my user's services start. On 2026-10-16 a slow boot spent 2 min 27 s clearing temp files; the backup fired with 26 seconds to spare, and a 2-minute timer on that machine never fired. The planned fix counts from user startup. Anything an agent keeps only in its conversation is still lost with the machine.

## Agent brief

```agent-brief
You are helping me back up my coding agents' notes and memory, not just their code.

First ask me:
- Which machines my agents use, Linux or macOS.
- Where each agent keeps its tracker files and memory folder.
- Where I can create one private git repo per agent.
- Whether those folders hold secrets or customer data.

Constraints:
- Ignore env files, secret-named files and images; if the last answer is yes, stop and ask me.
- Success means a fresh fetch shows the latest commit on the remote; anything else fails loudly.

Steps:
- Create each repo with a first commit and the ignore patterns, set a git identity, and add a push credential that never prompts, such as a per-repo deploy key.
- The job: take a lock, mirror the tracker files and memory folder in with deletions, commit, push, fetch and compare with the remote.
- Linux: a systemd user timer every 15 minutes, lingering on so it runs logged out.
- macOS: a launchd agent every 15 minutes and at load. It runs only while I'm logged in, has a minimal PATH (use full tool paths) and no flock command (use shlock or a lock directory).
- The agent runs the job at the end of each work cycle.

Pitfalls:
- One job for several agents: one failure fails all. Give each agent its own job and schedule.
- Outdated job copies: install every machine from one copy kept in git.
- A working checkout gets its untracked files committed: use a checkout only the job commits to.
- A boot-relative timer can fire before user services start: count from user startup.
- An empty or missing source, mirrored with deletions, wipes the backup: fail instead.
- Status creeping into memory: the agent keeps status on its board, lessons in memory.

Done when:
- With a remote it can't push to, the job's service shows failed in a failed-units check (macOS: its exit status and log).
- On a second machine, a clone plus the memory folder, restored where the agent looks (that can depend on the project path), lets a new session resume from the board.
```

## Related

- [CI that shares agent boxes and yields to their owner](https://eng.vaisman.me/entries/ci-that-yields-to-its-owner.md)
