Agent state
This page defines the current contract for agent state, lifecycle, and runtime projection in Holon. It is verified against implementation and tests as of the last review date noted below.
Last verified: 2026-05-27 against
src/types.rsAgentState,AgentStatus,AgentIdentityView,AgentSchedulingPosture,AgentPostureProjection,ClosureDecision,ContinuationResolution,RuntimePosture, andAgentSummary;src/storage.rsagent_posture_projection;src/runtime/lifecycle.rsagent_summary;src/runtime/closure.rsclosure derivation; andsrc/tool/tools/agent_get.rs.
Source RFCs
- Agent State Model And Runtime Projection
- Agent Lifecycle Control Posture
- Agent Control Plane Model
- Agent Profile Model
- Agent Initialization and Template
Authoritative records vs projections
Agent state is derived from authoritative runtime records, not stored as a single opaque status field. The key distinction is:
| Layer | What | Authority |
|---|---|---|
| Identity | agent_id, kind, visibility, ownership, profile preset | Agent registry record |
| Lifecycle status | AgentStatus — Booting, AwakeIdle, AwakeRunning, AwaitingTask, Asleep, Stopped | Scheduler executor (single writer) |
| Scheduling posture | AgentSchedulingPosture — derived from queue, WorkItems, tasks, wait state | Scheduler derive_posture projection |
| Runtime posture | RuntimePosture — Awake or Sleeping | Closure decision at turn end |
| Continuation | ContinuationResolution — how the agent was reactivated | Ingress/dispatch at turn start |
| User-facing summary | AgentSummary — stable projection for API/UI/model display | AgentGet tool + HTTP /agents |
AgentSummary is a display projection, not the source of truth for
scheduling decisions. The scheduler must derive posture from queue, WorkItem,
task, and wait state, not from summary fields.
Current implementation anchors:
RuntimeHandle::agent_summaryassemblesAgentSummaryon read from the currentAgentState, identity view, model state, execution snapshot, active waits, children, external triggers, andAppStorage::agent_posture_projection.AgentGetreturns that assembledAgentSummarydirectly; it does not write lifecycle, scheduling, or wait state./agentsand/agents/{id}use the same runtime summary/list projection path.AgentListEntryis a compact list projection and is not a scheduler input.AppStorage::agent_posture_projectionderives the exposedAgentSchedulingPosturefrom persisted/runtime records. No scheduler-sensitive path should readAgentSummary.scheduling_postureback as authority.
Agent lifecycle status (AgentStatus)
┌─────────────┐
│ Booting │
└──────┬──────┘
│ daemon_start / Start
▼
┌─────────────┐
┌─────►│ AwakeIdle │◄─────────────┐
│ └──────┬──────┘ │
│ │ turn starts │
│ ▼ │
│ ┌──────────────┐ │
│ │ AwakeRunning │ │
│ └──────┬───────┘ │
│ │ turn closure │
│ ▼ │
│ ┌─────────────┐ ┌────────┴──────┐
├──────│ Asleep │────►│ AwaitingTask │
│ └─────────────┘ └────────┬──────┘
│ wake / resume │ task result
│ │
└────────────────────────────────────┘
Stop ──► ┌──────────┐
│ Stopped │
└──────────┘
| Status | Meaning |
|---|---|
Booting | Agent is initializing; not yet handed to the scheduler |
AwakeIdle | Agent is awake but no model turn is in progress |
AwakeRunning | A model turn is currently executing |
AwaitingTask | Transitional label for an awake agent blocked on a non-terminal task result |
Asleep | Runtime accepted turn closure and no model turn is running |
Stopped | Agent lifecycle is stopped; scheduler will not start new turns |
Key contract:
Asleepis a runtime posture reached only when the scheduler accepts rest after turn closure. It is not an authoritative "idle" declaration.WaitForrecords explicit wait state before yielding; it is the preferred path when a WorkItem or agent is waiting on task, external, or operator input.- An
Asleepagent can have runnable WorkItems.Asleepdoes not mean idle or empty. AwaitingTaskis a transitional lifecycle label used by current runtime, TUI, daemon, and waiting projections while a non-terminal task (command, child agent) blocks further model reentry. It may later collapse intoAwakeIdleplus task-wait scheduling posture, but remains current contract until that migration happens.Stoppedis a hard lifecycle boundary. The scheduler will not start new turns for a stopped agent. Stopped agents release runtime-owned execution resources.- Status transitions flow through scheduler-owned helpers; no module should
directly mutate
AgentState.statuswithout going through the scheduler executor.
Scheduling posture (AgentSchedulingPosture)
The scheduler derives a scheduling posture from current state. This is a projection, not stored state. The current reduced agent-level projection uses this precedence:
| Posture | Condition |
|---|---|
Archived | Agent lifecycle is stopped (AgentStatus::Stopped) |
ActiveTurn | AgentState.current_run_id is set |
HasQueuedInput | Queue contains a pending queued entry for the agent |
HasRunnableWork | Current or queued WorkItem is runnable |
WaitingForTask | A WorkItem has an active task wait condition |
WaitingForExternal | A WorkItem has an active external waiting intent |
WaitingForOperator | WorkItem plan_status=needs_input or active operator wait |
Blocked | WorkItem has blocked_by set or an active timer/system/non-operator wait |
Idle | No queued input, no runnable work, no blocking conditions |
Unknown | Default before first projection; not part of the stable contract |
Key contract:
- Posture is derived from queue depth, WorkItem readiness, waiting state, task blocking state, and external triggers.
- Posture is snapshot-derived; it is not persisted as durable state.
- Stopped lifecycle wins the exposed projection (
Archived) before transient run, queue, work, or wait facts. - Queue and runnable work outrank passive sleep posture. An
Asleepagent with queued input or runnable WorkItems is projected asHasQueuedInputorHasRunnableWork, notIdle. - WorkItem-level
WaitingTimerandWaitingSystemremain distinct scheduler wait states, but the reduced agent-level posture currently reports them asBlocked; scheduler idle-boundary decisions still inspect the WorkItem wait state to emit the timer or system-tick action. AgentSummary.scheduling_postureexposes this projection; consumers should not treat it as an authoritative scheduling input.
Closure and continuation
At the end of each turn, the closure decision determines next posture:
ClosureOutcome | Effect |
|---|---|
Completed | Work completed; agent ready for next work |
Continuable | Work continues; same WorkItem remains active |
Failed | Turn failed; agent can recover or escalate |
Waiting | Agent is waiting for operator, external, task, or timer |
When a waiting agent is reactivated, ContinuationResolution records:
| Field | Meaning |
|---|---|
trigger_kind | OperatorInput, TaskResult, ExternalEvent, TimerFire, InternalFollowup, SystemTick |
class | ResumeExpectedWait, ResumeOverride, LocalContinuation, LivenessOnly |
model_reentry | Whether the model should be re-entered with context |
matched_waiting_reason | Whether the trigger matched the prior waiting reason |
Closure derivation is separate from the display posture. It uses scheduler
projection facts and current turn facts to choose a ClosureOutcome,
WaitingReason, and RuntimePosture. In current implementation:
- explicit operator waits outrank other wait conditions for closure reason;
- blocking tasks are metadata unless represented by a current work wait;
- active work-item or agent waiting intents map to external wait;
- timers map to timer wait;
- runnable work can prevent an unrelated agent-level waiting intent from becoming the closure reason.
User-facing projection (AgentSummary)
AgentSummary is the stable projection returned by AgentGet and
GET /agents. It includes:
identity— agent identity badge (visibility/ownership/profile)agent— coreAgentStateincluding status, pending count, turn indexscheduling_posture— derived posture snapshotlifecycle— lifecycle hint (not authoritative)model— current model selection and token usageclosure— last closure decisionexecution— execution snapshot (run id, cwd, workspace)active_children— visible child agent summariesactive_waiting_intents/active_wait_conditions— current wait stateactive_external_triggers— provisioned external ingress capabilities
Key contract:
AgentSummaryis assembled on read from authoritative records.- Fields are added conservatively; do not use
AgentSummaryas a dumping ground for internal state. - The model receives
AgentSummary(viaAgentGet) as display information, not as a scheduling instruction. - API consumers must not depend on summary field ordering or presence of default/empty fields.
Lifecycle control
Agent lifecycle control is Start / Stop:
Starthands the agent to the scheduler. It does not directly start a model turn; the scheduler decides whether the agent should be idle or process queued input.Stopaborts the current run, releases runtime-owned execution resources, and marks the agent as not runnable. Queued messages and durable records are preserved.- There is no
Pause/Resume.Stop+Startis the contract.
Validation findings
This page was validated against the RFCs and implementation areas listed in issue #1367.
| Area | Finding | Classification | Current handling |
|---|---|---|---|
AgentSummary / AgentGet derivation | Summary is assembled on read; AgentGet is read-only and does not mutate state. | Contract matches implementation | Documented above. |
| Projection as scheduler input | The user-facing AgentSummary.scheduling_posture is derived from storage/runtime facts. Scheduler-sensitive closure and run-loop paths derive from queue, WorkItems, waits, tasks, and turn state rather than reading the summary back. | Contract matches implementation | Covered by storage and runtime tests. |
| Lifecycle labels | Current implementation preserves Booting, AwakeIdle, AwakeRunning, AwaitingTask, Asleep, and Stopped; Paused only deserializes as legacy alias for Stopped. | Contract matches implementation with transitional label | Documented as current contract and known migration gap. |
| Agent-level timer/system waits | WorkItem scheduling keeps WaitingTimer and WaitingSystem distinct, while reduced AgentSchedulingPosture reports them as Blocked. | Intentional reduced projection | Documented; tests cover the projection. |
Archived posture | AgentSchedulingPosture::Archived is used for stopped agents. | Stale prior spec wording | Corrected in this page. |
| Durable state vs runtime projection | AgentState, queue entries, WorkItems, tasks, wait conditions/intents, external triggers, and audit/transcript records remain authoritative; AgentSummary remains display/API projection. | Contract matches implementation | Documented as layer table and anchors. |
Known gaps
AgentStatusstill includes transitionary states (Booting,AwaitingTask) that may collapse as the scheduler model matures. See follow-up ifAwaitingTaskbecomes fully subsumed byAwakeIdle+ task blocking.AgentLifecycleHintcarries lifecycle delivery hints such as whether external messages are accepted and optional operator guidance. Deprecated Pause/Resume projection fields are not part of the contract.AgentSummaryincludes some fields (recent_operator_notifications,recent_brief_count) whose contract is not yet hardened.AgentStatus::Asleepremains a lifecycle/display projection, but scheduler idle-boundary decisions inspect wait and work facts before treating an already-asleep agent as idle.AgentStatus::AwaitingTaskremains a transitional status in code even though it is not in the long-term target status set (agent-lifecycle-control-posture.md).AgentSchedulingPosture::Archivedis used (forStoppedagents) contrary to the previous spec claim of "Not currently used".
