This document explains daemon mode, proactive ticking, and scheduled/remote trigger behavior from the public surfaces that are visible in the product code.
Some of the deepest internal daemon implementations were not available in the extracted snapshot used for this analysis. That means this document is strongest on externally visible contracts, lifecycle, and orchestration behavior, and weaker on the exact internal worker registry implementation.
Because of that, some answers below are directly verified and some are inferred from public types, comments, and surrounding integration contracts.
Self-contained reading note:
- file/path references are provenance only
- the operational descriptions below are intended to stand on their own
Best summary:
- "Daemon mode" is an internal long-lived supervisor process.
- The supervisor appears to own durable infrastructure like scheduling and remote-control connectivity.
- Individual daemon workers are spawned as
claude --daemon-worker <kind>. - The worker subprocesses are intentionally lightweight and only initialize what they need.
Operational implication:
- The product is designed so the always-on coordination logic can live separately from an interactive terminal session.
- That separation is what makes scheduled triggers, remote wakeups, and background control surfaces plausible without forcing a full UI session to stay attached.
Not fully recoverable from the extracted snapshot.
Directly verified:
- The dispatch point exists:
runDaemonWorker(kind)insrc/entrypoints/cli.tsx. connectRemoteControl()accepts an optionalworkerType.- Bridge metadata uses worker types like:
claude_codeclaude_code_assistant
Strong inference:
- There is at least a remote-control/assistant-oriented worker, because the SDK types explicitly document daemon-owned remote control and assistant mode.
- There is likely a cron/scheduled-task worker, because the SDK exposes
watchScheduledTasks()specifically "for daemon architectures that own the scheduler externally and spawn the agent via query()".
What is missing:
- the concrete daemon worker registry implementation, so the exact
runDaemonWorkerswitch cases cannot be enumerated here - the document therefore captures the recoverable worker categories, not a definitive exhaustive list
Directly verified behavior:
- Proactive mode is enabled by
--proactiveorCLAUDE_CODE_PROACTIVE. main.tsxinjects a proactive system prompt telling the model it will receive periodic<tick>prompts and should either do useful work or callSleep.Sleepis only included when proactive/KAIROS is active.- In headless mode,
runHeadless()injects a hidden meta<tick>message whenever:- proactive is active
- proactive is not paused
- the queue is empty
- In the REPL,
useProactive(...)submits or queues the tick as a hidden meta prompt. - Tick prompts are hidden from the transcript via
isMeta: true.
Model-facing prompt rules:
src/constants/prompts.tssays<tick>means "you're awake, what now?"- On the first wake-up, greet briefly and ask what the user wants.
- On later wake-ups, look for useful work.
- If nothing useful exists, the model must call
Sleep.
Sleep tool semantics:
src/tools/SleepTool/prompt.tsexplicitly says:- use it when waiting / idle
- user input can interrupt it
- it is preferred over shell
sleep - ticks are periodic check-ins
Verified:
CronCreateCronDeleteCronList
Behavior:
- Durable tasks persist to
.claude/scheduled_tasks.json - Session-only tasks live only in in-memory bootstrap state
- One-shot tasks auto-delete after firing
- Recurring tasks auto-expire after 7 days by default unless marked permanent
- A per-project lock file prevents two Claude sessions from double-firing the same disk-backed cron
Key files:
src/tools/ScheduleCronTool/CronCreateTool.tssrc/tools/ScheduleCronTool/CronDeleteTool.tssrc/tools/ScheduleCronTool/CronListTool.tssrc/utils/cronTasks.tssrc/utils/cronScheduler.tssrc/utils/cronTasksLock.ts
Verified:
RemoteTriggersupports:listgetcreateupdaterun
Behavior:
- It calls the claude.ai CCR trigger API under
/v1/code/triggers - OAuth is handled in-process
- The bundled
scheduleskill documents these as scheduled remote agents that run in Anthropic cloud environments - Deletion is explicitly not supported from the tool
Key files:
src/tools/RemoteTriggerTool/RemoteTriggerTool.tssrc/tools/RemoteTriggerTool/prompt.tssrc/skills/bundled/scheduleRemoteAgents.ts
Partially verified from the shared registry, but the command handlers themselves are missing.
Verified:
claude ps|logs|attach|killare wired as fast paths insrc/entrypoints/cli.tsx- Session metadata is stored under
~/.claude/sessions/<pid>.json - The registry records:
pidsessionIdcwdstartedAtkind=interactive | bg | daemon | daemon-workerentrypoint- optional
messagingSocketPath - optional
name - optional
logPath - optional
agent - optional
bridgeSessionId - optional activity state (
status,waitingFor,updatedAt)
Runtime behavior:
registerSession()writes the PID file and registers cleanup to remove it on exit.updateSessionActivity()keeps status fresh forclaude ps.updateSessionName()andupdateSessionBridgeId()enrich what peer/session listings can show.- In
--bgsessions,/exit, Ctrl+C, and Ctrl+D detach the tmux client instead of terminating the process.
What is missing:
src/cli/bg.js, so the exact implementation ofps,logs,attach, andkillis not present here.
Key files:
src/entrypoints/cli.tsxsrc/utils/concurrentSessions.tssrc/commands/exit/exit.tsxsrc/screens/REPL.tsx
There are two tmux stories here.
Verified:
--tmuxrequires--worktree- it is rejected on Windows
- it verifies tmux is installed
- there is an early fast path
execIntoTmuxWorktree(args)before the full CLI loads
Behavior:
- creates or reuses a worktree
- creates or attaches to a tmux session named from repo + worktree branch
- sets
CLAUDE_CODE_TMUX_SESSION,CLAUDE_CODE_TMUX_PREFIX, andCLAUDE_CODE_TMUX_PREFIX_CONFLICTS - when launched from iTerm2 and not already inside tmux, it prefers
tmux -CCunless--tmux=classicis used - when already inside tmux, it creates a detached sibling session and
switch-clients into it rather than nesting
Key files:
src/main.tsxsrc/utils/worktree.tssrc/setup.ts
Verified:
src/utils/tmuxSocket.tscreates an isolated Claude-specific tmux socket so Claude-issued tmux commands do not mutate the user's own tmux server.
Verified path:
-p/--printis the headless/non-interactive moderunHeadless(...)is the core runner- In headless mode:
- there is no Ink/React terminal tree
- plugins are installed via headless-specific code
- cron tasks are run by
createCronScheduler(...) - proactive ticks are injected by the headless loop
- UDS inbox messages can wake the loop
- permission prompts are avoided; headless agents either use PermissionRequest hooks or auto-deny
Important details:
main.tsxexplicitly says trust is implicit in non-interactive--printmodeSessionStarthooks can synthesize the initial user turn when stdin is empty- headless
kairosEnabledis preserved so scheduled tasks and async agent/tool calls do not accidentally become synchronous - headless/team mode adds a reminder that the team must shut down before the final response
Key files:
src/main.tsxsrc/cli/print.tssrc/utils/permissions/permissions.tssrc/utils/forkedAgent.ts
KAIROS is the assistant-mode bundle.
Verified signals:
settings.assistantis described as: "Start Claude in assistant mode (custom system prompt, brief view, scheduled check-in skills)"--assistantis hidden and documented as "Agent SDK daemon use"- KAIROS gates:
- assistant mode
- brief mode integration
- proactive/Sleep integration
- extra assistant commands
- channel/push features
- assistant-specific system prompt addenda
fullRemoteControl = remoteControl || getRemoteControlAtStartup() || kairosEnabled
Important nuance:
- Cron scheduling is not purely a KAIROS feature. The local cron system is gated by
AGENT_TRIGGERSand is designed to ship independently. - KAIROS seems to be the higher-level "assistant persona / perpetual assistant / remote-control assistant" package layered on top of those lower-level mechanisms.
Key files:
src/main.tsxsrc/tools.tssrc/commands.tssrc/utils/settings/types.ts
Verified:
- CLI entrypoint sees
claude daemon ... - It enables configs and logging sinks
- It dispatches to
daemonMain(...) - The supervisor can spawn worker subprocesses as
claude --daemon-worker <kind>
Missing:
- The internal
daemonMainandworkerRegistryimplementations are not in this checkout.
Direct evidence points to three work sources:
-
Remote-control inbound prompts
connectRemoteControl()returnsinboundPrompts(),controlRequests(), andpermissionResponses()- comments say user messages typed on claude.ai are read from
inboundPrompts()and fed intoquery()
-
Scheduled tasks
watchScheduledTasks()is documented as the daemon-oriented watcher for.claude/scheduled_tasks.jsoncreateCronScheduler(...)is the concrete scheduler implementation used by headless/REPL paths
-
Local message injection
- headless mode can be kicked by UDS inbox messages
- queued commands are the common async boundary for system-generated work
Two different persistence models exist:
Strongly inferred from public SDK types:
- the parent daemon owns the remote-control WebSocket/session
- if the child agent subprocess crashes, the parent respawns it
- claude.ai keeps the same session
- this is called out explicitly in the
connectRemoteControl()docs
Verified:
- background sessions register themselves in
~/.claude/sessions kinddistinguishesbg,daemon, anddaemon-worker- bg sessions detach from tmux instead of exiting, so they survive terminal disconnects
Verified from public bridge handle docs:
- Agent output is streamed into the remote-control handle via
write(msg) - End-of-turn is signaled with
sendResult() - Remote-control handle exposes:
sessionUrlenvironmentIdbridgeSessionId
- The bridge/transport code writes result events before teardown so the remote side sees the final state
For local background sessions:
- results are also reflected via session registry state, transcript/log persistence, and the
logs/attachcommand surface, though those command bodies are not present in this checkout.
What is fully visible here:
- the daemon entrypoints
- the headless runner
- the proactive tick/Sleep contract
- the local cron scheduler
- the remote trigger API tool
- the session registry used by background/daemon processes
- the worktree/tmux integration
- the permission model for headless agents
What is not visible here:
- the concrete daemon supervisor
- the worker dispatch table
- the proactive module implementation itself
- the assistant module implementation
- the
ps/logs/attach/killcommand implementations
So the architecture is clear, but the final daemon-specific glue is missing from this repo snapshot.