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_):
backupIntervalMs_: DEFAULT_BACKUP_INTERVAL_MS
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:
- List
.neo-ai-data/backups/ directories matching pattern backup-<ISO>
- Sort by mtime descending
- Prune entries past index 30 (configurable via
BACKUP_RETENTION_CAP env, default 30)
- 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
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.
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.mjsexists 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:backupis operator-triggered only — no scheduled cadencebackup.mjs:173calls[7/7] Applying retention sweep...→ line 327applies retention policyis the implementation site, but perbackup.mjs:51documentation: "Not implemented in this commit" — retention sweep is STUBBEDai/daemons/Orchestrator.mjs(PR #11016 merged 2026-05-09) supportstaskDefinitionsmap (buildTaskDefinitions()at line 73-92) for shelling out to maintenance scripts; today onlysummary+kbSyncregistered. Nobackuptask.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
backuptobuildTaskDefinitions()inai/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 = 24hPlus module-level
export const DEFAULT_BACKUP_INTERVAL_MS = 86400000;. Env override viaNEO_ORCHESTRATOR_BACKUP_INTERVAL_MSparsed through existingparseInterval()shim.Site 3: Implement the stubbed retention sweep. Currently
backup.mjs:327is documented but stubbed. Implement:.neo-ai-data/backups/directories matching patternbackup-<ISO>BACKUP_RETENTION_CAPenv, default 30)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 therecordTaskOutcomepattern already in PR #11016).Acceptance Criteria
Orchestrator.taskDefinitions.backupregistered with shell-out tobackup.mjsvia the samenodeBin+scriptDirresolution path used forsummary/kbSyncbackupIntervalMs_reactive config +DEFAULT_BACKUP_INTERVAL_MSconstant +NEO_ORCHESTRATOR_BACKUP_INTERVAL_MSenv overridebackup.mjs:327retention sweep IMPLEMENTED withBACKUP_RETENTION_CAPenv (default 30); replace the documented-stubtest/playwright/unit/buildScripts/ai/backup.mjs.spec.mjsor new): retention sweep with 35 dummy backup dirs prunes oldest 5, keeps 30 newest by mtimelearn/agentos/MemoryCore.md(or backup.mjs JSDoc) documents the orchestrator-driven cadence modelAvoided Traps
Provenance
ai/daemons/Orchestrator.mjsshipped via PR #11016 (sub of #11009)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.