Running in production

A production agent is one always-on Mac mini supervised by launchd, with a single persistent daemon owning all scheduled work. This page covers keeping it alive, watching it, stopping it, and upgrading it.

Install the launchd jobs

scripts/setup/init-agent.sh      # deps → state dirs → generate-plists.sh → launchctl load
launchctl list | grep ai.maestro

Expect the daemon plus the cadence trigger jobs. The daemon plist is KeepAlive {SuccessfulExit:false, Crashed:true} — it restarts on crash but respects a clean stop. Generated plists live at scripts/local-triggers/plists/ and install under ~/Library/LaunchAgents/ai.maestro.*.

How scheduled work actually runs

Cadence ticks (every 5/10/15/30 minutes, daily, weekly, monthly, quarterly) do not spawn a model session each. Each launchd tick runs a ~10 ms enqueue script that drops an event into state/cadence-bus/inbox/; the persistent daemon's consumer services the queue with per-cadence policy:

  • inline — handled entirely in-process (heartbeats, housekeeping, stale-claim sweeps); no model session spawned.
  • guarded — a cheap pre-check (queues empty? inbox empty?) escalates only when there is substantive work.
  • escalate — spawns an isolated sub-session running that cadence's trigger prompt.

The bus is safe across daemon downtime — events accumulate in inbox/ and drain on the next start — and honours the emergency stop at both producer and consumer. The full lifecycle of every event (received → claimed → processed | escalated | failed | dlq) is recorded under logs/cadence-bus/<date>.jsonl.

Watch it

| Where | What you see | | --- | --- | | scripts/healthcheck.sh | The scheduled health check (also wired via launchd every 15 minutes) | | state/cadence-bus/health.json | Daemon heartbeat — doctor flags it stale when older than 60 s while the daemon should be running | | logs/audit/ | Per-action JSONL rows from both the native executor and the MCP plane | | logs/daemon/<date>-sessions.jsonl | Session lifecycle plus routed events from the model router | | logs/cadence-bus/ | Cadence event lifecycle | | state/dashboards/ | Executive summary, cost summary and queue dashboards the agent maintains |

From the workspace side, Cohort → Fleet is the org-level view: one row per agent Mac with live health, the AI-native blocked states (re-auth needed, rate limit, disk full, powered down), per-seat remediation, and a lifecycle drawer.

The Fleet page listing agent Macs with live health, temperature and spend columns
Cohort → Fleet — per-machine health for the whole workforce

Stop it

  • Emergency stop: touch .emergency-stop in the repo root (or npm run emergency-stop). The daemon exits cleanly, so launchd's KeepAlive does not fight you, and both cadence producer and consumer stand down. This flag is deliberately human-only: the recovery watchdog throttles or restarts on memory pressure rather than ever writing it.
  • Resume: scripts/resume-operations.sh (or npm run resume).
  • Email-only stop: delete or rename config/orgmail.yaml and restart — the mailbox plane skips while everything else keeps running. Server-side, an admin can flip the mailbox to Disabled in Settings → Email.
  • Org kill switch: an admin can deactivate the agent centrally; its next presence beat carries a halt directive and its token is revoked. See Pairing and credentials.

Upgrade

Two things version independently: the SDK (the CLI and framework source) and the framework files vendored into the agent repo.

npm i -g @cohortapp/agent-sdk@latest   # 1. the SDK itself
npm run upgrade                        # 2. maestro upgrade — copy framework files in
maestro init                           # 3. run any deferred feature-init steps
git diff && git add -A && git commit -m "Upgrade framework"

upgrade classifies every file and never touches agent-owned content (config/, CLAUDE.md, knowledge/, memory/, state/, logs/, outputs/, .env):

| Classification | Behaviour | | --- | --- | | added / updated | New or unmodified framework file → copied | | same | Already byte-identical → skipped | | preserved | Has uncommitted local edits → yours kept; upstream lands at .maestro/incoming/<path> for manual diff | | mergeKept | Under agents/ → custom agents never overwritten | | ignored | Matches .maestroignore → never touched | | forced | Only with --force-overwrite; backup at .maestro/backup/<path> |

Use --dry-run to preview. Upgrades are idempotent — a second run on a current repo is a clean no-op — and legacy launchd plists are auto-migrated to the cadence-bus architecture with the exact launchctl commands printed for any live job you need to roll.

When to upgrade

A protocol drift warning from cohort doctor — the server reporting a newer org-protocol version than the SDK vendors — is the upgrade cue for the machine.

Fleet hygiene

  • maestro audit runs the security-posture audit (--fix applies safe remediations) and produces a hash-locked attestation.
  • Rotating an org provider key is one action at the workspace — the fleet converges on the next lease with nothing to push.
  • When several agents share Slack channels, keep config/known-agents.json current so @-mentions route to exactly one daemon.
  • maestro global-setup wires collective memory into the machine-wide Claude config so every session on the box contributes to the agent's long-term memory; it is additive, idempotent, and run automatically by init-agent.sh.