skills/daemon/SKILL.md
Continuous autonomous operation mode. Keeps campaigns running 24/7 by chaining Claude Code sessions via RemoteTrigger. Each session picks up from the campaign's continuation state, works until context runs low or the phase completes, then schedules the next session. Auto-stops on campaign completion or budget exhaustion. The thing that makes Citadel run overnight.
npx skillsauth add SethGammon/Citadel daemonInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use when: running campaigns overnight or unattended -- chains sessions automatically until a ceiling or budget is hit. Don't use when: a single autonomous session is enough (use /archon); you want manual control between cycles (use /loop).
/daemon start does NOT call RemoteTrigger by default. The local
runner is the default. Only pass --remote to use Anthropic's routine
system, and only after explicit user confirmation.
Why: RemoteTrigger counts against the account-wide 15 routine runs /
24h cap. A single overnight run can exhaust the quota and pause every other
routine on the account (including unrelated ones). See
docs/ROUTINE-QUOTA.md.
/daemon start (no --remote flag)daemon.json).RemoteTrigger. Leave chainTriggerId
and watchdogTriggerId as null in the state file.Daemon state created: .planning/daemon.json
Campaign: {slug}
Budget: ${N}
To start the tick loop, run in a separate terminal:
npm run daemon:local
Leave that terminal open. It spawns `claude -p "/do continue"` each
session, respects daemon.json status, and consumes zero Anthropic
routine quota. Stop with Ctrl+C or `/daemon stop`.
For true unattended background operation (machine sleeps, user away):
/daemon start --remote (uses RemoteTrigger, counts against 15/day cap)
In Codex, prefer a Codex Automation for durable unattended daemon ticks when available:
node scripts/codex-automation.js plan --type daemon --command "/daemon tick" --cadence "<interval>" --target background-worktree --write
Use the returned prompt in the Codex app automation surface. Each run must still read and update .planning/daemon.json; Codex owns the scheduling, Citadel owns the budget/status gates and run log.
/daemon start --remoteOnly when the user has explicitly passed --remote:
RemoteTrigger,
which counts against your 15 routine runs / 24h quota. A single overnight
daemon can exhaust it. Continue? (y/N)"| Command | Behavior |
|---|---|
| /daemon start | Default: create state file, prompt user to run npm run daemon:local (zero routine cost) |
| /daemon start --remote | Use RemoteTrigger instead (counts against 15/day routine quota — requires confirmation) |
| /daemon start --campaign {slug} | Target a specific campaign |
| /daemon start --budget {N} | Set budget cap in dollars (default: $50) |
| /daemon start --budget unlimited | Explicitly disable budget cap |
| /daemon start --interval {N}m | Set watchdog interval (default: 30m) |
| /daemon start --cooldown {N}s | Set delay between sessions (default: 60s) |
| /daemon start --cost-per-session {N} | Override per-session cost estimate (default: $3) |
| /daemon stop | Stop the daemon, tear down triggers |
| /daemon status | Show daemon state, session count, budget remaining |
| /daemon log | Show recent daemon session history |
| /daemon tick | Internal: heartbeat handler fired by triggers. Not user-facing. |
Step 1: Validate prerequisites
.planning/ exists. If not: "No planning directory found. Run /do setup first."--campaign {slug} provided: read .planning/campaigns/{slug}.md.planning/campaigns/ (excluding completed/) for files with
status: active in frontmatter/archon first."--campaign flag: list them, ask user to specify$50--budget unlimited: set budget to Infinity, warn: "No budget cap. You will not
be protected from runaway costs. Monitor usage at your Anthropic dashboard."--budget {N}: parse as number, must be > 0--cost-per-session {N} provided: use that valueestimated_cost_per_loop field in frontmatter
(improve campaigns set this to 12): use that value$3Step 2: Check for existing daemon
.planning/daemon.json if it existsstatus: "running"):
/daemon stop first, then continueStep 3: Create triggers
A. Chain trigger — one-shot, fires after cooldown, command: "/daemon tick". Save ID as chainTriggerId.
B. Watchdog trigger — recurring, fires every --interval, command: "/daemon tick --watchdog". Save ID as watchdogTriggerId.
Both use type: scheduled/recurring, project_path: {absolute project root}, description: "Daemon: {slug} tick/watchdog".
Step 4: Write state file
Write .planning/daemon.json:
{
"status": "running",
"campaignSlug": "{slug}",
"budget": 50,
"costPerSession": 3,
"estimatedSpend": 0,
"sessionCount": 0,
"interval": "30m",
"cooldown": "60s",
"chainTriggerId": "{id from step 3A}",
"watchdogTriggerId": "{id from step 3B}",
"startedAt": "{ISO timestamp}",
"lastTickAt": null,
"lastTickStatus": null,
"stoppedAt": null,
"stopReason": null,
"log": []
}
Step 5: Log and confirm
Log: daemon-start event with budget and interval. Output confirmation: campaign slug, budget (estimated sessions), cooldown, watchdog interval, state file path. Suggest /daemon status and /daemon stop.
.planning/daemon.json. If missing or not running: "No daemon is running."status: stopped, stoppedAt, stopReason: user.daemon-stop event. Output: sessions completed, estimated spend, campaign status.Output: status, campaign (slug + phase), sessions, budget (spent/cap/remaining), cost/session source, last tick (time + status), running duration, watchdog interval, state file path.
If paused-level-up: add instructions to review proposals at .planning/rubrics/{target}-proposals.md and set campaign status: active to resume.
For improve campaigns: add loops completed/total, current level, last axis attacked.
.planning/daemon.jsonlog array, most recent first, formatted as:
[{timestamp}] Session #{N}: {status} -- {summary}
Phase: {phase} | Duration: {duration} | Est. cost: ${cost}
This is the heartbeat handler. It runs in a fresh Claude Code session spawned by RemoteTrigger. It is not user-facing.
Step 1: Gate checks
.planning/daemon.json"running" and not "paused-level-up" -- exit silently. The daemon was stopped.
"paused-level-up": read the campaign file. If campaign status is now
active (human approved the level-up), update daemon.json status: "running",
clear pauseReason, log daemon-resume with reason level-up-approved, and
continue to Step 2 (acquire lock). If campaign is still level-up-pending: exit
silently (still waiting for human).lastTickAt is within the last 2 minutes and lastTickStatus is
"running" -- another session is active. Exit silently.estimatedSpend >= budget -- stop the daemon:
status: "stopped", stopReason: "budget-exhausted"daemon-stop with reason budget-exhaustedstatus: "stopped", stopReason: "no-active-work"daemon-stop with reason no-active-workstatus: completed or status: failed -- stop the daemon:
status: "stopped", stopReason: "campaign-{status}"daemon-stop with reason campaign-completed or campaign-failedstatus: parked -- stop the daemon:
stopReason: "campaign-parked"status: level-up-pending -- pause the daemon (do not stop):
status: "paused-level-up", pauseReason: "Improve hit distribution saturation. Human approval required for level-up proposals."daemon-pause with reason level-up-pending"Paused: level-up triggered. Approve proposals at .planning/rubrics/{target}-proposals.md and set campaign status to active to resume."Step 2: Acquire lock
Update daemon.json:
lastTickAt: current ISO timestamplastTickStatus: "running"Step 3: Execute
Run /do continue -- this routes to Archon, which reads the campaign's Continuation
State and picks up where the last session left off.
Archon will work until:
Step 4: Record session
After /do continue returns (or the session is winding down):
Read the campaign file again to get updated status and phase
No-work gate: If the campaign status is completed, failed, parked, or
the campaign file no longer exists -- stop the daemon immediately:
status: "stopped", stopReason: "no-active-work",
stoppedAt: "{ISO timestamp}"daemon-stop with reason no-active-workUpdate daemon.json:
sessionCount: increment by 1estimatedSpend: add costPerSessionlastTickStatus: "completed"log array:
{
"session": {sessionCount},
"timestamp": "{ISO timestamp}",
"status": "completed",
"phase": "{current_phase}",
"summary": "{brief description of what happened}",
"estimatedCost": {costPerSession}
}
Run a safe memory consolidation pass when the session produced planning changes:
node scripts/memory-compile.js compile
If it fails, record the failure in daemon.json log and continue shutdown or
scheduling; memory compile failures must not create overlapping daemon ticks.
Step 5: Schedule next tick
Re-read daemon.json. If still running and estimatedSpend + costPerSession <= budget: create new chain trigger (one-shot, cooldown delay), update chainTriggerId. If budget would be exceeded: stop daemon (budget-exhausted), delete watchdog, log daemon-stop.
Step 6: Exit
Session ends cleanly. PreCompact hook saves campaign state. The next tick will start a fresh session with full context budget.
Same as /daemon tick but with an additional check at Step 1:
After the standard gate checks pass, check whether the chain is alive:
lastTickAt from daemon.jsonlastTickAt is more than 2 * interval ago AND lastTickStatus is not "running":
"Watchdog: chain appears dead. Last tick at {lastTickAt}. Restarting chain."lastTickAt is recent (within 2 * interval): the chain is healthy. Exit silently.The daemon's primary continuation mechanism is the init-project.js SessionStart hook,
not RemoteTrigger prompt injection. On every session start, the hook:
.planning/daemon.jsonstatus: running: checks the lock (no overlap), budget (can afford), and campaign (still active)[daemon] Active daemon detected. Campaign: {slug}. Run: /do continue/do continueRemoteTrigger's role is reduced to scheduling session starts. The hook handles everything else. If RemoteTrigger is unavailable, an OS cron job or manual restart achieves the same result.
Primary: Read latest entry from .planning/telemetry/session-costs.jsonl (written by session-end hook) for real cost. Use override_cost if present, else estimated_cost.
Fallback: costPerSession flat estimate (default $3). Each tick adds it to estimatedSpend.
Stop when estimatedSpend >= budget or estimatedSpend + costPerSession > budget (preemptive).
Overrides: --budget {N} | --budget unlimited (explicit, warns) | --cost-per-session {N}
*/30 * * * * cd ~/project && claude -p '/do continue'.planning/: "Run /do setup first."/archon once interactively to establish it./daemon start fresh.2 * interval./daemon tick called manually: works, gate checks apply. Warn it's internal.--budget {higher}."level-up-pending, set paused-level-up, keep watchdog alive for human-resume detection./do Tier 1 stop. All write stopReason: no-active-work.Always disclose, regardless of trust level:
/daemon stop."/daemon stop, no work is lost--budget unlimited -- no automatic cost protectionRed actions (unlimited budget) require explicit confirmation at ALL trust levels.
Before starting, verify daemon is warranted:
improve and no rubric exists: block -- rubric requires human approval firstRead trust level from harness.json:
unlimited to bypass)start: confirmation output, no HANDOFFstop: stop summary, no HANDOFFtick: no user output (headless); updates daemon.json, schedules or stopsstatus/log: output requested infodevelopment
First-run experience for the harness. Three modes: Recommended (guided, ~3 min), Full Tour (guided + skill walkthrough, ~8 min), and Express (zero questions, ~30 sec). Installs hooks first, detects stack, configures harness.json, runs a live demo on real code, and prints a reference card.
development
Knowledge compiler. Extracts patterns, decisions, and anti-patterns from completed campaigns and evolve cycles, then compiles them into structured wiki pages that integrate with existing knowledge rather than appending isolated files. Implements flush→compile→lint pipeline. Auto-triggered by /postmortem and /evolve Phase 6.
tools
Unified router that auto-routes user intent to the right orchestrator or skill. Classifies input by scope, complexity, persistence needs, and parallelism, then dispatches to the cheapest path that can handle it: direct command, skill, marshal, archon, or fleet. Single entry point for all work.
data-ai
Real-time harness observability dashboard. Reads campaigns, fleet sessions, telemetry, and pending queues to present a snapshot of harness state at a glance. Invoked by /dashboard, /do status, or phrases like "what's happening" and "show activity".