Backups (encrypted, R2)¶
Everything tau knows lives on the hub. That is the point of the design, and it makes backups your job. This guide shows what to back up, how to encrypt it with age so that Cloudflare only ever holds ciphertext, and how to upload and restore it. The manual flow works today; the scheduled job is planned.
Planned (roadmap issue 018): the backup job
A tau backup command and a scheduled hub service will run the flow on this page automatically, keep a rotation, and document the restore. Until then, the commands below are the backup. They are the same commands the job will run.
What is in the data directory¶
TAU_DATA_DIR (default ~/.local/share/tau) holds everything tau writes:
~/.local/share/tau/
├── sessions/<id>/ one directory per session, kept forever
│ ├── session.json the Strands session record
│ ├── meta.json channel, profile, title, role, compaction links
│ ├── events.jsonl agent events: tool calls, approvals, limits, errors
│ └── agents/tau/ agent state and one file per message
├── hub/ hub state: lock, heartbeat, crash reports, launchd logs
└── logs/tau.log the log file
Later layers add settings, memory and the knowledge base under the same root, so a backup of TAU_DATA_DIR stays complete.
Two things live outside it and are also worth keeping:
| Path | Why |
|---|---|
<TAU_HOME>/persona/user.md |
the facts about you; it is not tracked anywhere |
<TAU_HOME>/tau.toml |
your role and component choices; harmless, convenient |
<TAU_HOME>/.env holds credentials. Keep those in your password manager, not in the backup bundle; a restore then needs one paste instead of trusting the bundle with every key you own. If you do include it, the bundle is encrypted anyway, but the blast radius of a lost age key grows.
The key¶
Backups are encrypted with age, a small modern file encryption tool, to a key pair. The private key stays on the hub and in your password manager. It never goes to Cloudflare.
mkdir -p ~/.config/tau && chmod 700 ~/.config/tau
age-keygen -o ~/.config/tau/backup.key
chmod 600 ~/.config/tau/backup.key
age-keygen prints the public key, a line like Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p. Copy it somewhere handy; you encrypt with it. Then put the whole backup.key file into your password manager. Without it, every backup is noise.
Warning
Losing backup.key loses every backup. Losing the hub does not, as long as the key is in the password manager. Test the restore below once, now, while nothing is at stake.
Make a backup¶
One bundle, streamed through age, so the plaintext archive never touches the disk:
export TAU_DATA_DIR="${TAU_DATA_DIR:-$HOME/.local/share/tau}"
export TAU_HOME="${TAU_HOME:-$HOME/.tau}"
export TAU_BACKUP_PUBKEY='age1...' # from age-keygen
stamp=$(date -u +%Y%m%dT%H%M%SZ)
tar -C / -czf - \
"${TAU_DATA_DIR#/}/sessions" "${TAU_DATA_DIR#/}/hub" \
"${TAU_HOME#/}/persona/user.md" "${TAU_HOME#/}/tau.toml" \
| age -r "$TAU_BACKUP_PUBKEY" -o "/tmp/tau-$stamp.tar.gz.age"
ls -l "/tmp/tau-$stamp.tar.gz.age"
hub/hub.lock is included and harmless. logs/ is left out on purpose; add it if you want it.
Upload to a private R2 bucket¶
R2 is Cloudflare's object storage. A private bucket has no public URL; only your API token can read it, and even then it only reads ciphertext.
- Create a Cloudflare API token with R2 read and write on your account (
Account → R2 → Manage R2 API tokens, or a user token scoped to Workers R2 Storage: Edit). Store it in~/.config/tau/remote.envasCLOUDFLARE_API_TOKEN=...or runnpx wrangler loginonce. -
Create the bucket and upload:
npx wrangler r2 bucket create tau-backups npx wrangler r2 object put "tau-backups/$stamp.tar.gz.age" \ --file "/tmp/tau-$stamp.tar.gz.age" --remote rm "/tmp/tau-$stamp.tar.gz.age"--remotemakes wrangler talk to the real bucket instead of a local emulation. -
Check what is there:
Large bundles: rclone
wrangler r2 object put is fine for bundles up to a few hundred megabytes. For more, or for incremental sync, configure rclone against R2's S3-compatible endpoint with an R2 access key pair, then rclone copy /tmp/tau-$stamp.tar.gz.age r2:tau-backups/. The encryption step is the same; only the upload changes.
Restore¶
On the hub, or on a new machine that will become the hub:
npx wrangler r2 object get "tau-backups/<stamp>.tar.gz.age" --file /tmp/restore.age --remote
age -d -i ~/.config/tau/backup.key /tmp/restore.age | tar -C / -xzf -
rm /tmp/restore.age
tau doctor
tar -C / restores the exact paths that were archived, so TAU_DATA_DIR and TAU_HOME should be the same on the new machine. If they differ, extract into a scratch directory and move the pieces:
mkdir -p /tmp/restore && age -d -i ~/.config/tau/backup.key /tmp/restore.age | tar -C /tmp/restore -xzf -
rsync -a /tmp/restore/<old data dir>/ "$TAU_DATA_DIR"/
cp /tmp/restore/<old home>/persona/user.md "$TAU_HOME"/persona/user.md
Then tau chat --resume: the last session comes back with its history.
Schedule it¶
Until issue 018 lands, a cron line or a launchd agent that runs the backup script nightly is enough:
crontab -e
# 03:30 every night; the script is the "Make a backup" block plus the upload
30 3 * * * /path/to/tau-backup.sh >> "$HOME/.local/share/tau/logs/backup.log" 2>&1
Keep the script outside the repository if it holds the bucket name and token path, or read those from ~/.config/tau/remote.env.
Rules¶
- Never upload plaintext. If a step fails before
age, nothing goes up. - The key is never in Cloudflare, not in a secret, not in a Worker, not in a bucket.
- Test a restore after the first backup and after every change to the script.
- R2 holds no personal data in clear. D1, Vectorize and AI Search are not used for tau's data at all (ADR 0006).