Agent runtime, scheduling, and completion
This page answers the agent/task part of the cli.js analysis: how the agent system is designed, which agent families exist, how tasks are scheduled, which patterns are used, how completion is detected, what is unusual about the runtime, and whether timed tasks exist.
It complements Agents, tasks, and subagents, Slash commands and automation, and Agent and automation architecture. Those pages list individual tools/commands; this page focuses on scheduling and lifecycle mechanics.
Short answer
- The agent system is orchestration over the existing session runtime, not a second runtime. Agents/subagents reuse the same context loop, tool permission boundary, hooks, JSONL session storage, and telemetry/debug surfaces.
- The source-visible agent families are: inline custom agents (
--agents <json>), theclaude agentscommand family, subagents created through task/delegation tools, teammate/background-agent modes, hosted review agents (ultrareview), and automation helpers such as slash commands/skills/auto-mode. - Tasks are scheduled with a task store + task message queue + stream-frame patching pattern.
TaskGetcan block until a terminal status; task updates emit patch events. - Completion is detected primarily through task status. In the SDK/task protocol, terminal statuses are
completed,failed, andcancelled; internal UI eviction also treatskilledas terminal. - Timed tasks do exist. A Kairos/cron family exposes prompt scheduling with session-only or durable tasks, recurring/one-shot cron expressions, missed-task surfacing, jitter, lock files, and a
CLAUDE_CODE_DISABLE_CRONkill switch.
Source anchors
| Semantic alias | Source | Approximate location | String or symbol | Meaning |
|---|---|---|---|---|
| AgentsCommandFamily | cli.js | line ~19550, byte 0xdcc1ac | H.command("agents") | Root command family for agents/background agents. |
| InlineAgentsFlag | cli.js | line ~19525, byte 0xdc1fd4 | --agents <json> | Inline custom-agent definitions at session start. |
| RemoteControlAgentCoordination | cli.js | line ~19550, byte 0xdcb6f2 | --remote-control [name] | Remote Control can expose/coordinate running agent sessions. |
| UltraReviewCommand | cli.js | line ~19550, byte 0xdcc963 | H.command("ultrareview [target]") | Hosted multi-agent review command family. |
| AutoModeCommand | cli.js | line ~19550, byte 0xdccb56 | H.command("auto-mode") | Permission/automation classifier inspection command. |
| TaskCreateTool | cli.js | line ~1091, byte 0x4804f6 | TaskCreate | Task creation tool constant. |
| TaskGetTool | cli.js | line ~1091, byte 0x480512 | TaskGet | Task status/result retrieval tool constant. |
| TaskListTool | cli.js | line ~1091, byte 0x480523 | TaskList | Task listing tool constant. |
| TaskUpdateTool | cli.js | line ~99, byte 0x97c03 | TaskUpdate | Task update tool constant/request family. |
| TaskUpdateWaiter | cli.js | line ~99, byte 0x97bfb | _waitForTaskUpdate | Blocking wait path used by TaskGet. |
| TaskTerminalStatusPredicate | cli.js | line ~95, byte 0x91cb6 | `function R5H(H){return H===“completed” | |
| TerminalTaskCancelGuard | cli.js | line ~99, byte 0x97ef1 | Cannot cancel task in terminal status | Cancellation is rejected for terminal tasks. |
| TaskStartedFrame | cli.js | line ~1837, byte 0x503d86 | task_started | Runtime task registration stream frame. |
| TaskUpdatedFrame | cli.js | line ~1837, byte 0x503a13 | task_updated | Patch-style task update stream frame. |
| TaskProgressFrame | cli.js | line ~2004, byte 0x519283 | task_progress | Task progress stream frame. |
| TaskNotificationContract | cli.js | line ~185, byte 0x112031 | Each stdout line is delivered to the model as a <task_notification> event | Long-running monitor notification contract. |
| SubagentStartHook | cli.js | line ~185, byte 0x10b75d | SubagentStart | Subagent lifecycle hook. |
| SubagentStopHook | cli.js | line ~185, byte 0x10b76d | SubagentStop | Subagent lifecycle hook. |
| TaskCreatedHook | cli.js | line ~185, byte 0x10b7d5 | TaskCreated | Task lifecycle hook. |
| TaskCompletedHook | cli.js | line ~185, byte 0x10b7e3 | TaskCompleted | Task lifecycle hook. |
| SubagentContextClassifier | cli.js | line ~253, byte 0x2104c1 | agentType==="subagent" | Runtime distinction for subagent context. |
| KairosCronGate | cli.js | line ~1091, byte 0x480583 | isKairosCronEnabled | Scheduled-task feature gate. |
| DisableCronEnv | cli.js | line ~1091, byte 0x4806fd | CLAUDE_CODE_DISABLE_CRON | Scheduled-task kill switch. |
| CronSchedulerRuntime | cli.js | line ~9564, byte 0xcd3ba4 | createCronScheduler | Runtime scheduled-task engine. |
| ScheduledTaskLockFile | cli.js | line ~9564, byte 0xcd383a | .claude/scheduled_tasks.lock | Scheduled-task lock file. |
| UltraReviewPreflightApi | cli.js | line ~6664, byte 0xab2665 | /v1/ultrareview/preflight | Hosted review preflight API. |
Agent families visible in the bundle
cli.js does not expose one clean static roster of every shipped agent persona. Instead, it exposes agent families and loading mechanisms.
| Family | Source-visible entry | Design role |
|---|---|---|
| Inline custom agents | --agents <json> | Session-scoped custom agent definitions, useful for scripted/headless runs. |
| Agent command family | H.command("agents") | User-facing/background-agent management and dispatch. |
| Subagents | agentType==="subagent", SubagentStart, SubagentStop | Delegated model contexts that run as projections of the same session runtime. |
| Task agents | TaskCreate, TaskUpdate, TaskGet, TaskList | Structured tasks used by the model/runtime to plan, assign, wait, and report progress. |
| Teammate/background modes | --agent-id, --agent-name, --team-name, --teammate-mode, --agent-type | Multi-agent/teammate coordination around the same CLI runtime. |
| Hosted review agents | ultrareview [target], /v1/ultrareview/preflight | Explicit hosted multi-agent review workflow. |
| Skills/slash automation | Skill, slash command metadata, keybinding command:* | Human/plugin/keybinding-triggered automation that can look agent-like but enters through commands/tools. |
| Auto-mode classifier | auto-mode, hasAutoModeOptIn, tengu_auto_mode_config | Permission/automation classifier; not an agent itself, but affects whether agents/tools can proceed without prompts. |
The important design point: these families share the same session envelope, settings/policy, MCP/plugin registry, tool-permission boundary, and transcript system.
Task scheduling model
sequenceDiagram participant Model participant TaskTool as Task tools participant Store as Task store participant Queue as Task message queue participant Stream as SDK/headless stream participant Worker as Subagent/worker
Model->>TaskTool: TaskCreate(subject, description) TaskTool->>Store: create pending task Store-->>Stream: task_started / TaskCreated Model->>TaskTool: TaskUpdate(status/owner/blocks) TaskTool->>Store: mutate task record Store-->>Stream: task_updated patch Worker-->>Queue: response/error/progress messages Model->>TaskTool: TaskGet(taskId) TaskTool->>Queue: drain queued messages TaskTool->>Store: read task status alt non-terminal TaskTool->>Store: _waitForTaskUpdate Store-->>TaskTool: wake on update else terminal TaskTool->>Store: getTaskResult TaskTool-->>Model: result + taskId metadata end| Component | Behavior |
|---|---|
TaskCreate | Creates a structured task with pending status and metadata; source prompt says to use it for complex multi-step work and plan mode. |
TaskUpdate | Updates status/description/owner/dependencies/metadata; runtime emits patch-style task_updated frames. |
TaskList | Lists current tasks with cursor support. |
TaskGet | Retrieves a task; if non-terminal, drains queued messages and waits for updates until terminal. |
| Task message queue | Carries response/error/progress messages related to a task, then clears when the terminal result is returned. |
| Stream frames | task_started, task_updated, task_progress, task_notification let UIs/SDK hosts display progress without polling raw files. |
| Hooks | TaskCreated and TaskCompleted are hook events; SubagentStart/SubagentStop are separate context lifecycle events. |
Completion detection
There are several completion signals, depending on the layer.
| Layer | Terminal/completion rule | Evidence |
|---|---|---|
| SDK/task protocol | completed, failed, cancelled | TaskTerminalStatusPredicate returns true for exactly those strings. |
| Cancellation path | Cannot cancel if already terminal | Cannot cancel task in terminal status: ${status}. |
| Internal UI eviction | completed, failed, killed can be evicted after notification/retention checks | Internal task-eviction snippet checks those statuses. |
| Hook layer | TaskCompleted | Hook event emitted when task lifecycle completes. |
| Subagent layer | SubagentStop | Runtime-context completion, distinct from task-record completion. |
| Remote bash command | onCommandLifecycle(uuid,"completed") after output/error is enqueued | Remote bridge bash_command handling. |
| Scheduled one-shot task | Fired prompt is removed/deleted after fire | createCronScheduler removes non-recurring fired tasks. |
| Scheduled recurring task | lastFiredAt persisted; task expires if aged out | isRecurringTaskAged, tengu_scheduled_task_expired. |
The hidden footgun is that task completion and subagent completion are not identical. A subagent can stop, a task record can complete, and a remote/host command can acknowledge completion through different frames.
Scheduling patterns
| Pattern | How it works | Why it matters |
|---|---|---|
| Deferred tool result | Task tools can defer and wait (shouldDefer, _waitForTaskUpdate). | Lets the model start long work and later request the result without blocking every frame. |
| Patch streaming | task_updated contains a minimal patch (status, description, end_time, error, etc.). | UIs/SDK hosts can update state incrementally. |
| Queue draining before wait | TaskGet drains task messages before checking terminal state. | Prevents progress/errors from being stranded while a caller waits. |
| Dependency/ownership metadata | Task prompts mention owner, blocks, and blockedBy. | Supports multi-agent planning without a separate workflow engine. |
| Subagent projection | agentType==="subagent" changes runtime context inside the same loop. | Avoids a second permission/model/session stack. |
| Long-running notification process | Each stdout line becomes <task_notification>. | External monitors can feed the model with a narrow text-line contract. |
| Worktree/tmux/in-process teammate modes | CLI exposes teammate identity and mode flags. | Enables multi-agent work without assuming one deployment topology. |
| Hosted preflight | ultrareview calls /v1/ultrareview/preflight before hosted work. | Hosted runs are explicit and preflighted rather than silently triggered. |
| Cron prompt injection | Scheduled tasks call onFire(prompt) or onFireTask(task). | Timed automation is implemented as prompt/task injection into the existing session. |
Timed tasks and cron
The scheduled-task family is source-visible and feature-gated.
| Surface | Behavior |
|---|---|
isKairosCronEnabled | Returns false when CLAUDE_CODE_DISABLE_CRON is set; otherwise checks a feature flag. |
isDurableCronEnabled | Separate gate for durable scheduled tasks. |
| Cron create/list/delete tools | Prompt strings describe scheduling prompts for future times, list/delete operations, and durable vs session-only tasks. |
| Standard 5-field cron | Prompt says minute/hour/day/month/day-of-week in the user’s local timezone. |
| One-shot tasks | recurring: false; fire once and auto-delete. |
| Recurring tasks | recurring: true; persist/update lastFiredAt; may age out if not permanent. |
| Jitter guidance | Prompt explicitly tells the model to avoid :00 and :30 when approximate timing allows. |
| Durable storage | Prompt says durable: true persists to .claude/scheduled_tasks.json; scheduler code uses .claude/scheduled_tasks.lock. |
| Locking | Scheduler lock records sessionId, pid, procStart, and acquiredAt; stale PID locks can be recovered. |
| Missed tasks | Missed one-shot tasks are surfaced and then removed; telemetry includes tengu_scheduled_task_missed. |
| Fire telemetry | Runtime emits tengu_scheduled_task_fire; aged recurring tasks emit tengu_scheduled_task_expired. |
The scheduler is not a separate always-on daemon in the analyzed path. It is an in-session scheduler with locking, periodic checks, and optional durable files so future/parallel processes can coordinate.
Unique runtime design choices
- Agents are runtime projections. Subagents and background agents reuse session, model, tool, permission, hook, and telemetry infrastructure.
- Tasks are model-visible tools. The model can create/update/list/get structured tasks through normal tool pathways, which makes planning auditable in the transcript.
- Completion is status-driven. Waiting is implemented by
TaskGet+_waitForTaskUpdate, not by guessing from text output. - The task stream is patch-oriented.
task_updatedcarries minimal changes, which is friendlier for SDK/TUI consumers than replaying full task lists. - Cron fires prompts, not arbitrary code. Timed automation injects prompts/tasks into the same agent loop; tool execution still goes through permissions.
- Long-running monitors speak one line at a time. The
<task_notification>stdout contract avoids embedding an arbitrary subprocess protocol inside the model loop. - Remote and local use the same envelope. Remote Control can send commands/control responses, but task and permission semantics stay aligned with local runs.
- Hosted review is explicit.
ultrareviewis a command with preflight, not an ambient background service. - Feature gates surround advanced automation. Cron, background agents, bridge behavior, auto-mode, and agent views all have feature/env/policy gates.
Diagnostics and telemetry for agents
| Signal | Meaning |
|---|---|
task_started, task_updated, task_progress, task_notification | Runtime-visible progress frames. |
TaskCreated, TaskCompleted, SubagentStart, SubagentStop | Hook-level lifecycle events. |
tengu_scheduled_task_missed, tengu_scheduled_task_fire, tengu_scheduled_task_expired | Scheduled-task telemetry. |
tengu_auto_mode_* | Auto-mode decision/fallback/denial telemetry family. |
tengu_worktree_kept, tengu_worktree_removed | Worktree teammate/task cleanup telemetry. |
Debug logs (--debug, CLAUDE_CODE_DEBUG_LOGS_DIR) | Low-level traces for scheduler locks, task updates, bridge state, and errors. |
For the broader gates and observability story, see Feature gates reference and Telemetry and tracing.
Caveats
- The source-visible agent list is a list of families/loading surfaces, not a guaranteed complete persona catalog. Some agent definitions can come from plugins, settings, marketplace data, or hosted services.
- Status names differ by layer. Do not assume
killedis part of the SDK terminal predicate;TaskTerminalStatusPredicateonly includescompleted,failed, andcancelled. - Cron behavior depends on feature gates and environment. If
CLAUDE_CODE_DISABLE_CRONis set, scheduled-task creation should be treated as unavailable. tengu_*names are opaque unless adjacent code explains them. This page only interprets names with nearby behavioral evidence.
Related docs
Created and maintained by Yingting Huang.