Skip to content

Backup & Restore

nodyn stores all persistent data in ~/.nodyn/. This includes run history, knowledge graph, memory, secrets vault, and configuration. The backup module provides crash-safe snapshots with optional encryption.

DataStorageBackup Method
Run history (history.db)SQLite WALVACUUM INTO (crash-safe)
Secrets vault (vault.db)SQLite WALVACUUM INTO (crash-safe)
Data store (datastore.db)SQLite WALVACUUM INTO (crash-safe)
Knowledge graph (knowledge-graph/)LadybugDB (Kuzu)Directory copy
Memory (memory/)Text filesRecursive copy
Config (config.json)JSONFile copy
Sessions (sessions/)JSON filesRecursive copy

VACUUM INTO creates an atomic, consistent snapshot of each SQLite database — even while nodyn is running with active writes. No data corruption risk.

Terminal window
/backup # Create a backup now
/backup list # List all backups with dates and sizes
/backup verify # Verify integrity of the latest backup
/backup verify NAME # Verify a specific backup
/backup prune # Remove old backups (retention policy)
/backup restore # Restore from the latest backup (with confirmation)
/backup restore NAME # Restore from a specific backup
{
"backup_dir": "~/.nodyn/backups",
"backup_schedule": "0 3 * * *",
"backup_retention_days": 30,
"backup_encrypt": true
}
FieldDefaultDescription
backup_dir~/.nodyn/backupsWhere backups are stored
backup_schedule0 3 * * *Cron schedule for automatic backups (daily 3 AM)
backup_retention_days30Days to keep old backups. 0 = never auto-delete
backup_encrypttrue (if vault key set)Encrypt backups with AES-256-GCM

All fields are PROJECT_SAFE_KEYS — can be set in project-level .nodyn/config.json.

When NODYN_VAULT_KEY is set and backup_encrypt is not explicitly false, backups are encrypted:

  • Algorithm: AES-256-GCM (same as vault and run history)
  • Key derivation: HKDF-SHA256 from vault key with backup-specific salt
  • Per-file encryption: Each file encrypted individually with unique IV
  • File format: NBAK magic header + version + IV + auth tag + ciphertext
  • Manifest stays readable: Only file contents are encrypted, not the manifest

Without a vault key, backups are unencrypted. The CLI warns about this.

~/.nodyn/backups/
20260325T030000Z/
manifest.json # Metadata, file list, checksums
history.db # VACUUM INTO copy (or encrypted)
vault.db # VACUUM INTO copy (or encrypted)
datastore.db # VACUUM INTO copy (or encrypted)
knowledge-graph/ # Directory copy
memory/ # Recursive copy
config.json # File copy

Every backup includes a manifest.json with:

{
"version": "1.0.0",
"created_at": "2026-03-25T03:00:00.000Z",
"nodyn_dir": "/home/user/.nodyn",
"encrypted": true,
"files": [
{
"path": "history.db",
"size_bytes": 524288,
"checksum_sha256": "a1b2c3...",
"type": "sqlite"
}
],
"checksum": "overall-sha256..."
}

/backup verify checks:

  1. Every file in the manifest exists
  2. File sizes match
  3. SHA-256 checksums match
  4. SQLite databases pass PRAGMA integrity_check (unencrypted backups only)

Restore always creates a safety backup first before overwriting any data:

  1. Safety backup of current state → ~/.nodyn/backups/<timestamp>/
  2. If encrypted: decrypt each file
  3. Replace current files with backup contents
  4. Prompt user to restart nodyn

If restore fails mid-way, the safety backup allows recovery. The safety backup path is shown in the output.

nodyn automatically creates a backup when it detects a version change on startup. This protects against update regressions — if a new version breaks something, the pre-update backup is already there.

[nodyn] Version changed (1.0.0 → 1.1.0) — creating pre-update backup...
[nodyn] Pre-update backup created: /home/nodyn/.nodyn/backups/20260326T030000Z

How it works:

  • On Engine.init(), the current version is compared with ~/.nodyn/.last_version
  • If the version changed → backup before anything else runs
  • The version file is updated after the check
  • First-ever run writes the version file without triggering a backup
  • If Google Drive is configured, the pre-update backup is also uploaded

No configuration needed — this is always active when the backup manager is initialized.

When backup_schedule is configured and the WorkerLoop is running (Telegram, MCP server modes), backups run automatically as background tasks:

  • Task type: backup (no LLM call — direct BackupManager operation)
  • Auto-prune: Old backups are pruned after each scheduled backup
  • Failure notification: Failed backups are reported via NotificationRouter (high priority)
  • Backups older than backup_retention_days are auto-deleted
  • The most recent backup is never deleted regardless of age
  • 0 disables auto-deletion
  • Manual pruning: /backup prune
import { BackupManager } from '@nodyn-ai/core';
import { getNodynDir } from '@nodyn-ai/core';
const manager = new BackupManager(getNodynDir(), {
backupDir: '/path/to/backups',
retentionDays: 30,
encrypt: true,
}, process.env['NODYN_VAULT_KEY'] ?? null);
// Create
const result = await manager.createBackup();
console.log(result.success, result.path);
// List
const backups = manager.listBackups();
// Verify
const check = manager.verifyBackup(backups[0].path);
// Restore (creates safety backup first)
const restore = await manager.restoreBackup(backupPath);
// Prune
const pruned = manager.pruneBackups(30);

When Google auth is configured with drive.file scope, backups are automatically uploaded to Google Drive after each local backup.

  1. Configure Google OAuth: set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
  2. Run /google auth in the CLI and grant the drive.file scope
  3. Backups will auto-upload to a nodyn-backups folder on your Google Drive
Terminal window
/backup gdrive list # List remote backups on Google Drive
/backup gdrive upload # Force upload latest local backup
/backup gdrive restore # Download and restore from Google Drive
  • A nodyn-backups folder is auto-created in your Google Drive root
  • Each backup gets a subfolder named by timestamp
  • All files (encrypted or not) are uploaded as binary
  • Upload failures are logged but never fail the local backup
  • Manifests are used to list remote backups without downloading full data
{
"backup_gdrive": true
}

Set backup_gdrive: false to disable Google Drive upload while keeping Google auth active for other features.

This is the recommended setup for pilots and production:

  1. Local backup → fast restore, survives container restart
  2. Google Drive → survives server failure, off-site disaster recovery
  3. Separate backup volume (Docker) → survives data volume corruption

Mount a backup volume for persistent storage:

Terminal window
docker run -d \
-e ANTHROPIC_API_KEY=sk-ant-... \
-e NODYN_VAULT_KEY=... \
-v ~/.nodyn:/home/nodyn/.nodyn \
-v /mnt/backups:/home/nodyn/.nodyn/backups \
nodyn

Or configure backup_dir to an external path in config.