LearnNewsExamplesServices
Frontmatter
id11018
titleOrchestrator owns daily backup with 30-rotation retention
stateClosed
labels
enhancementaimodel-experience
assigneesneo-gemini-3-1-pro
createdAtMay 9, 2026, 5:17 PM
updatedAtMay 9, 2026, 5:46 PM
githubUrlhttps://github.com/neomjs/neo/issues/11018
authorneo-opus-4-7
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 9, 2026, 5:46 PM

Orchestrator owns daily backup with 30-rotation retention

Closedenhancementaimodel-experience
neo-opus-4-7
neo-opus-4-7 commented on May 9, 2026, 5:17 PM

Context

Operator PRIO 0 directive 2026-05-09: orchestrator must own daily backup as scheduled task with 30-rotation cap (one month coverage).

Substrate state today:

  • buildScripts/ai/backup.mjs exists as the backup primitive (verified 2026-05-09T15-13-13Z: 574MB written across kb 525MB + mc 38MB + graph 10MB + mailbox 60KB + concepts 44KB + bundle-meta.json)
  • npm run ai:backup is operator-triggered only — no scheduled cadence
  • backup.mjs:173 calls [7/7] Applying retention sweep... → line 327 applies retention policy is the implementation site, but per backup.mjs:51 documentation: "Not implemented in this commit" — retention sweep is STUBBED
  • ai/daemons/Orchestrator.mjs (PR #11016 merged 2026-05-09) supports taskDefinitions map (buildTaskDefinitions() at line 73-92) for shelling out to maintenance scripts; today only summary + kbSync registered. No backup task.

MX framing: Pairs with #10780 (manual backup-first discipline before DreamMode/Sandman). #10780 codifies the manual gate; this ticket adds the SCHEDULED daily-rotation alongside, so backup substrate is non-negotiable in steady-state.

Prescription

Single concrete change at three sites:

Site 1: Orchestrator task registration. Add backup to buildTaskDefinitions() in ai/daemons/Orchestrator.mjs:73-92:

backup: {
    label          : 'daily backup',
    command        : nodeBin,
    args           : [path.resolve(scriptDir, '../../buildScripts/ai/backup.mjs')],
    pidFileName    : 'backup.pid',
    expectedCommand: 'backup.mjs'
}

Site 2: Reactive interval config. Add to Orchestrator static config (alongside existing pollIntervalMs_, summarySweepIntervalMs_, kbSyncIntervalMs_):

/**
 * @member {Number} backupIntervalMs_=86400000
 * @reactive
 */
backupIntervalMs_: DEFAULT_BACKUP_INTERVAL_MS  // 86400000 = 24h

Plus module-level export const DEFAULT_BACKUP_INTERVAL_MS = 86400000;. Env override via NEO_ORCHESTRATOR_BACKUP_INTERVAL_MS parsed through existing parseInterval() shim.

Site 3: Implement the stubbed retention sweep. Currently backup.mjs:327 is documented but stubbed. Implement:

  1. List .neo-ai-data/backups/ directories matching pattern backup-<ISO>
  2. Sort by mtime descending
  3. Prune entries past index 30 (configurable via BACKUP_RETENTION_CAP env, default 30)
  4. Emit logger.log('[retention] pruned N old backups, kept M')

HealthService envelope: Backup task uses existing HealthService.recordTaskOutcome('backup', 'pass'|'fail', {sourceCount, bundleCount, retentionPruned}) on completion (mirroring the recordTaskOutcome pattern already in PR #11016).

Acceptance Criteria

  • AC1 — Orchestrator.taskDefinitions.backup registered with shell-out to backup.mjs via the same nodeBin + scriptDir resolution path used for summary/kbSync
  • AC2 — backupIntervalMs_ reactive config + DEFAULT_BACKUP_INTERVAL_MS constant + NEO_ORCHESTRATOR_BACKUP_INTERVAL_MS env override
  • AC3 — backup.mjs:327 retention sweep IMPLEMENTED with BACKUP_RETENTION_CAP env (default 30); replace the documented-stub
  • AC4 — HealthService.recordTaskOutcome envelope on backup completion
  • AC5 — Unit test (test/playwright/unit/buildScripts/ai/backup.mjs.spec.mjs or new): retention sweep with 35 dummy backup dirs prunes oldest 5, keeps 30 newest by mtime
  • AC6 — Integration test: orchestrator-driven backup runs end-to-end, retention sweep fires, HealthService records outcome
  • AC7 — learn/agentos/MemoryCore.md (or backup.mjs JSDoc) documents the orchestrator-driven cadence model

Avoided Traps

  • MCP-server-side backup scheduling — orchestrator owns this; MCP servers don't schedule themselves (the entire goal of the new daemon architecture is to REMOVE scheduled work from MCP servers; v13-path.md D3)
  • Separate sweep script for retention — extends existing primitive at line 327, single source of truth, no parallel implementations
  • Hardcoded retention cap — configurable via env per operator-deployment flexibility (different fleets may want different windows)
  • Block agent operations during backup — backup is parallelizable with active sessions (read-only snapshot via Chroma's atomic-export); cadence-design ticket will formalize, but for daily-rotation a single-write window during low-activity hours is the operator's stated preference

Provenance

  • Operator directive 2026-05-09 (PRIO 0): "we have a backup script. you can run it (outside the git worktree) ... auto-backups => the idea was a cap of 30, so we cover a month"
  • Pairs with #10780 (manual backup-first discipline)
  • Extends ai/daemons/Orchestrator.mjs shipped via PR #11016 (sub of #11009)
  • Empirical anchor: manual backup landed today at 2026-05-09T15-13-13Z (574MB written; verified via du -sh)

Self-Identification: @neo-opus-4-7 (Claude Opus 4.7, Claude Code) — chief-architect lane, Phase 0.B of post-quad coordination round.