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.rs AgentState, AgentStatus, AgentIdentityView, AgentSchedulingPosture, AgentPostureProjection, ClosureDecision, ContinuationResolution, RuntimePosture, and AgentSummary; src/storage.rs agent_posture_projection; src/runtime/lifecycle.rs agent_summary; src/runtime/closure.rs closure derivation; and src/tool/tools/agent_get.rs.

Source RFCs

Authoritative records vs projections

Agent state is derived from authoritative runtime records, not stored as a single opaque status field. The key distinction is:

LayerWhatAuthority
Identityagent_id, kind, visibility, ownership, profile presetAgent registry record
Lifecycle statusAgentStatus — Booting, AwakeIdle, AwakeRunning, AwaitingTask, Asleep, StoppedScheduler executor (single writer)
Scheduling postureAgentSchedulingPosture — derived from queue, WorkItems, tasks, wait stateScheduler derive_posture projection
Runtime postureRuntimePosture — Awake or SleepingClosure decision at turn end
ContinuationContinuationResolution — how the agent was reactivatedIngress/dispatch at turn start
User-facing summaryAgentSummary — stable projection for API/UI/model displayAgentGet 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:

Agent lifecycle status (AgentStatus)

                ┌─────────────┐
                │   Booting   │
                └──────┬──────┘
                       │ daemon_start / Start
                       ▼
                ┌─────────────┐
         ┌─────►│  AwakeIdle  │◄─────────────┐
         │      └──────┬──────┘              │
         │             │ turn starts         │
         │             ▼                     │
         │      ┌──────────────┐             │
         │      │ AwakeRunning │             │
         │      └──────┬───────┘             │
         │             │ turn closure        │
         │             ▼                     │
         │      ┌─────────────┐     ┌────────┴──────┐
         ├──────│    Asleep    │────►│  AwaitingTask │
         │      └─────────────┘     └────────┬──────┘
         │         wake / resume              │ task result
         │                                    │
         └────────────────────────────────────┘

                          Stop ──► ┌──────────┐
                                   │  Stopped  │
                                   └──────────┘
StatusMeaning
BootingAgent is initializing; not yet handed to the scheduler
AwakeIdleAgent is awake but no model turn is in progress
AwakeRunningA model turn is currently executing
AwaitingTaskTransitional label for an awake agent blocked on a non-terminal task result
AsleepRuntime accepted turn closure and no model turn is running
StoppedAgent lifecycle is stopped; scheduler will not start new turns

Key contract:

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:

PostureCondition
ArchivedAgent lifecycle is stopped (AgentStatus::Stopped)
ActiveTurnAgentState.current_run_id is set
HasQueuedInputQueue contains a pending queued entry for the agent
HasRunnableWorkCurrent or queued WorkItem is runnable
WaitingForTaskA WorkItem has an active task wait condition
WaitingForExternalA WorkItem has an active external waiting intent
WaitingForOperatorWorkItem plan_status=needs_input or active operator wait
BlockedWorkItem has blocked_by set or an active timer/system/non-operator wait
IdleNo queued input, no runnable work, no blocking conditions
UnknownDefault before first projection; not part of the stable contract

Key contract:

Closure and continuation

At the end of each turn, the closure decision determines next posture:

ClosureOutcomeEffect
CompletedWork completed; agent ready for next work
ContinuableWork continues; same WorkItem remains active
FailedTurn failed; agent can recover or escalate
WaitingAgent is waiting for operator, external, task, or timer

When a waiting agent is reactivated, ContinuationResolution records:

FieldMeaning
trigger_kindOperatorInput, TaskResult, ExternalEvent, TimerFire, InternalFollowup, SystemTick
classResumeExpectedWait, ResumeOverride, LocalContinuation, LivenessOnly
model_reentryWhether the model should be re-entered with context
matched_waiting_reasonWhether 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:

User-facing projection (AgentSummary)

AgentSummary is the stable projection returned by AgentGet and GET /agents. It includes:

Key contract:

Lifecycle control

Agent lifecycle control is Start / Stop:

Validation findings

This page was validated against the RFCs and implementation areas listed in issue #1367.

AreaFindingClassificationCurrent handling
AgentSummary / AgentGet derivationSummary is assembled on read; AgentGet is read-only and does not mutate state.Contract matches implementationDocumented above.
Projection as scheduler inputThe 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 implementationCovered by storage and runtime tests.
Lifecycle labelsCurrent implementation preserves Booting, AwakeIdle, AwakeRunning, AwaitingTask, Asleep, and Stopped; Paused only deserializes as legacy alias for Stopped.Contract matches implementation with transitional labelDocumented as current contract and known migration gap.
Agent-level timer/system waitsWorkItem scheduling keeps WaitingTimer and WaitingSystem distinct, while reduced AgentSchedulingPosture reports them as Blocked.Intentional reduced projectionDocumented; tests cover the projection.
Archived postureAgentSchedulingPosture::Archived is used for stopped agents.Stale prior spec wordingCorrected in this page.
Durable state vs runtime projectionAgentState, queue entries, WorkItems, tasks, wait conditions/intents, external triggers, and audit/transcript records remain authoritative; AgentSummary remains display/API projection.Contract matches implementationDocumented as layer table and anchors.

Known gaps