Agent Execution Flow
This document describes the deterministic execution loop that every runtime implements. The four pre-built runtimes (runtime-js, runtime-temporal, runtime-cloudflare, runtime-dbos) all use the core orchestration helpers documented here.
For the abstract framework concepts that this loop produces, see ./concepts.md. For session lifecycle and state-store contracts, see ./session-model.md.
The agent execution follows a step-based loop (see packages/runtime-js/src/js-agent-executor.ts):
Claim Session — Executor calls
stateStore.createSession()atomically before returning a handle. This persists the session with metadata (userId, tags, organizationId) and prevents concurrentexecute()calls on the same sessionId. For Temporal and Cloudflare runtimes, this happens after starting the workflow/instance but before the handle is returned, closing the race window where async initialization hasn't persisted state yet.On resume, the runtime instead uses
stateStore.compareAndSetStatus(sessionId, expectedStatuses, 'active', { expectedVersion })to claim the session — CAS-promoting the status from'paused'/'interrupted'/'failed'into a fresh execution slot. The discriminated return value ({ ok: true; newVersion }vs{ ok: false; currentStatus; currentVersion }) lets concurrent resume attempts detect each other and bail.Initialize — Create initial agent state, append user message, create run record (or, on resume, increment
resumeCountand create a new run record observing the existing session state).Step Loop — While status is
'running'or'paused':- Poll
stateStore.checkInterruptFlag(atomic check-and-clear) at the top of each iteration. Set flags from any process abort the run cleanly. - Build messages with
buildMessagesForLLM() - Call LLM via adapter with messages + tools
planStepProcessing()determines next action from step result- Execute tool calls and sub-agent calls in parallel
- Append results as messages
shouldStopExecution()checks stop conditions (maxSteps, stopWhen,__finish__tool)- HITL boundary handling (v7 stateless suspension) — If any tool is client-executed, approval-gated, or any sub-agent has not yet completed, the runtime persists durable suspension state via
saveStateAndPromoteStaging(writingpendingClientToolCalls,suspendedAwaitingChildren, orsuspendedStepIddepending on the kind) and EXITS THE RUN withRunOutcome.kind = 'suspended_*'. There is no in-memory waiter, nosetTimeout, no Durable-Object hibernation guard. Resume is driven by a separateexecutor.resume(sessionId)invocation:- JS — A new
runStepIterationinvocation drains submissions and continues. Process restart is safe; pending entries are recovered from the state store. - Temporal — A NEW workflow instance is started with workflow ID
${prefix}__${agentType}__${sessionId}__resume-${N}(single-dashresume-N, per spec §5;WorkflowIdReusePolicy.ALLOW_DUPLICATE). The new workflow'smode='resume'branch calls theapplyResultsAndReloadactivity. Note thatretry()uses a DIFFERENT convention (__retry__N, double-underscore) for backward compat. - Cloudflare Workflows — A new workflow instance is started with
mode: 'resume'; resume drains viaapplyResultsAndReload. - Cloudflare DO — A new run-loop iteration runs in the same DO instance (or a fresh DO if the original was evicted during the wait). All four paths begin with
applyResultsAndReload(Temporal/CFW) or the equivalent resume bootstrap in JS/DO, which drains queued submissions, synthesizes timeouttool_errors for expired deadlines, and resumes the step loop from durable state. See./concepts.md§Client-Executed Tools for per-runtime mechanics.
- JS — A new
- Poll
Complete — Emit output chunk, end stream.
Error handling in resume/retry: If workflow creation fails after status has been transitioned (e.g., CAS from 'failed' to 'active' in retry), all runtimes perform a best-effort rollback of the status to prevent sessions from being stranded in an unrecoverable state. The CAS options.error field carries the failure reason through the rollback so the session ends up 'failed' with a meaningful error rather than silently stuck.
Core orchestration logic lives in packages/core/src/orchestration/:
step-iterator.ts— v7 unified step iterator (runStepIteration); wraps phase-1/phase-2 tool execution, hook firing, staging promotion, and HITL suspension classification. Consumed by all four HITL-capable runtimes.step-processor.ts— Determines what to do after each LLM stepmessage-builder.ts— Creates internal Message types for conversationstate-operations.ts— State initialization and tool buildingstop-checker.ts— Stop condition evaluationsuspension-classifier.ts— Classifies a step's terminal state into aRunOutcome.kind('suspended_client_tool' | 'suspended_awaiting_children' | 'suspended_step_partial' | ...).run-outcome.ts—RunOutcomediscriminated union andSuspendedChildWaitpayload type.