Skip to content

State

The State is the single source of truth for a running workflow. Every node reads from it, writes to it, and the engine persists it after each step for crash recovery.

import { state } from '@cycgraph/orchestrator';
const initialState = state({
workflowId: graph.id,
goal: 'Research and summarize quantum computing',
constraints: ['Under 500 words'],
maxExecutionTimeMs: 120_000,
});

State is held in a single flat object, grouped below by concern. The first six groups hold the run’s own data: identity and input, control flow, retry and resilience, waiting state, cost, and memory.

The last two hold engine-owned fields that the runner manages and agents never see. The runner populates and maintains everything else.

Set when the run is created and fixed for its lifetime.

Field Type Default Description
workflow_id string (UUID) required Graph definition this run belongs to.
run_id string (UUID) auto-generated Unique identifier for this execution.
goal string required High-level objective for the workflow.
constraints string[] [] Rules the workflow must respect.

Drive the run’s lifecycle and which node executes next.

Field Type Default Description
status WorkflowStatus 'pending' Current lifecycle status.
current_node string Node currently being executed.
iteration_count number 0 Total reducer dispatches so far. The loop guard.
max_iterations number 50 Hard cap: the run fails if exceeded.
started_at Date When the run was first invoked.
max_execution_time_ms number 3600000 (1h) Wall-clock timeout for the entire run.

Track node-level retries and the compensating actions for saga rollback.

Field Type Default Description
retry_count number 0 Retries on the current node so far.
max_retries number 3 Maximum retries before the node fails permanently.
last_error string Error message from the most recent failure.
compensation_stack CompensationEntry[] [] Stack of typed compensating actions for saga rollback. Each entry has action_id and compensation_action: { type, payload }.

Populated while the run is paused in the waiting status.

Field Type Default Description
waiting_for WaitingReason Why the workflow is paused (e.g. human_approval).
waiting_since Date When the workflow entered the waiting state.
waiting_timeout_at Date Deadline after which the wait times out.

Running totals, and the ceilings that fail the run when breached.

Field Type Default Description
total_tokens_used number 0 Cumulative tokens consumed across all LLM calls.
max_token_budget number If set, the run fails when token usage exceeds this.
total_input_tokens number 0 Cumulative prompt tokens.
total_output_tokens number 0 Cumulative completion tokens.
total_cost_usd number 0 Cumulative estimated cost in USD.
budget_usd number Per-run cost budget. Run fails when exceeded.
model_breakdown Record<string, Spend> {} The same totals split by model id.
node_breakdown Record<string, Spend> {} The same totals split by the node that incurred them.

Spend is { input_tokens, output_tokens, cost_usd, calls }. node_breakdown answers which step is expensive, which the run totals cannot: a retrying node’s failed attempts are attributed too, so the cost of the retries is visible rather than folded into the whole.

The shared blackboard, plus the execution-history bookkeeping the engine keeps.

Field Type Default Description
memory Record<string, unknown> {} Shared key-value store. See Memory below.
visited_nodes string[] [] Node IDs visited in execution order.
supervisor_history object[] [] Routing decisions made by supervisor nodes. Used for debugging.
created_at Date now When this run was created.
updated_at Date now Last state mutation timestamp.

These fields hold data the engine trusts and agents never see. They live beside memory rather than inside it, so state slicing excludes them structurally. No node’s state view ever contains them, and no memory write can touch them. Reducers keep the two registries append-only.

Field Type Default Description
taint_registry TaintRegistry {} Provenance of untrusted external data, keyed by memory key. See Taint Tracking.
lesson_provenance LessonProvenanceRegistry {} Which retrieved facts entered which node’s prompt (eval-gated learning evidence). Read with getInjectedFactIds(finalState).
pending_approval unknown Review payload for the active human-in-the-loop pause.
policy_approvals Record<string, boolean> {} Security-policy approvals granted by a human, keyed by node id.
subgraph_checkpoints Record<string, unknown> {} Paused child-run checkpoints for subgraph nodes awaiting a nested approval.
subgraph_stack string[] [] Ancestor graph ids in a child run (subgraph cycle/depth detection).
swarm_handoff_count number 0 Swarm peer-handoff counter (bounds maxHandoffs).
Field Type Default Description
state_schema_version number 1 Schema version stamped on a freshly created state. Loaded snapshots pass through hydrateWorkflowState(), which migrates older versions forward to CURRENT_STATE_SCHEMA_VERSION (v1 snapshots have their memory._* system keys lifted into the fields above) and refuses snapshots from a newer engine.
_last_event_sequence_id number Event-log high-water mark at the moment the snapshot was persisted. Resume logic uses it to decide whether a logged action’s effects are already inside the snapshot (crash-window idempotency).

These fields are managed by the runner, so don’t set them by hand.

Refs:

stateDiagram-v2
    direction LR
    pending --> scheduled
    scheduled --> running
    running --> completed
    running --> waiting
    running --> retrying
    waiting --> running
    retrying --> running
    retrying --> failed
    running --> cancelled
    running --> timeout
    failed --> cancelled: saga rollback
    timeout --> cancelled: saga rollback

Refs:

The memory object is the primary data exchange between nodes. It’s an arbitrary key-value store, so you define the keys based on your workflow’s needs. Agents write to it via their text output, which the orchestrator automatically routes to the node’s write key. Agents read from memory via their filtered state view, controlled by read keys on the node.

  • Use descriptive keys. research_notes is better than data or result.
  • Reference, don’t store. Avoid large blobs in memory; store them externally and keep a reference.
  • Keep it flat. Deeply nested objects are harder to debug.
Layer Scope Persistence Purpose
Graph State Shared across all nodes Persisted after every step Source of truth: goal, results, artifacts
Thread Context Local to a single agent Ephemeral Raw LLM conversation for the current agent

Graph State is the memory object. It’s persisted after every node execution, enabling crash recovery and time-travel debugging.

Thread Context is the raw LLM conversation history within a single agent execution. Each agent has its own thread, so agents don’t see each other’s raw messages. The orchestrator automatically captures the agent’s text output and routes it to the appropriate write key, and the thread is discarded.

Actions dispatched to the reducer use a discriminated union type ActionTypeSchema. Valid action types are:

Action Type Purpose
update_memory Write key-value pairs to the memory object
set_status Transition the workflow status
goto_node Override the next node in the graph
handoff Transfer control to another agent/workflow
request_human_input Pause for human-in-the-loop approval
resume_from_human Inject human response and resume
merge_parallel_results Combine results from parallel node execution

Refs:

  • Action: The reducer action shape and its type union.

Data entering the system from external tools, such as web search and file reads, is flagged as tainted. Taint propagates automatically. If a node reads tainted data and writes to state, the output key inherits the taint flag. This lets downstream nodes make trust decisions about their inputs.

Refs:

  • TaintMetadata: Provenance of the untrusted data behind a memory key.

Build a valid WorkflowState from authoring input.

state(input: WorkflowStateConfig): WorkflowState
createWorkflowState(input: WorkflowStateConfig): WorkflowState

The input is a WorkflowStateConfig. The common fields:

Parameter Type Default Description
workflowId string (UUID) required Graph definition this run belongs to.
goal string required High-level objective for the run.
constraints string[] [] Rules the run must respect.
maxExecutionTimeMs number 3600000 Wall-clock timeout for the whole run.
maxTokenBudget number Token ceiling. The run fails when usage exceeds it.
budgetUsd number Cost ceiling in USD.
memory Record<string, unknown> {} Seed values for the blackboard.

Parse a persisted state at a load boundary. Persisted snapshots round-trip through JSON/jsonb. Every path that loads state from storage (checkpoints, snapshots, recovery) must pass it through this function. It runs any pending schema migrations, then parses with WorkflowStateSchema.

hydrateWorkflowState(raw: unknown): WorkflowState

Check status-transition legality without running the reducer. canTransitionStatus returns false for any move out of a terminal status, with one exception: the saga-rollback move from failed or timeout to cancelled.

canTransitionStatus(from: WorkflowStatus, to: WorkflowStatus): boolean
isTerminalStatus(status: WorkflowStatus): boolean
TERMINAL_STATUSES: ReadonlySet<WorkflowStatus>

Read the append-only lesson_provenance registry after a run, for eval-gated learning.

getInjectedFactIds(state: WorkflowState): string[]
getLessonProvenance(state: WorkflowState): LessonProvenanceEntry[]

getInjectedFactIds returns the deduplicated fact IDs injected into prompts during the run, in deterministic order. This is the value to pass as fact_ids when recording the run’s outcome. getLessonProvenance returns the individual LessonProvenanceEntry records.

The schema version this engine build writes, currently 2. hydrateWorkflowState migrates older snapshots up to it and refuses snapshots from a newer engine.

CURRENT_STATE_SCHEMA_VERSION: number

The complete runtime state, persisted after every reducer dispatch. Each field is documented in the Schema reference above.

The authoring shape accepted by state.

The lifecycle status enum. See Status lifecycle for the legal transitions.

Group Values
Initial 'pending', 'scheduled'
Active 'running', 'waiting', 'retrying'
Terminal 'completed', 'failed', 'cancelled', 'timeout'

Why a workflow sits in the waiting status.

Value Meaning
'human_approval' Human-in-the-loop review.
'external_event' Waiting for a webhook or callback.
'scheduled_time' Cron or scheduled execution.
'rate_limit' API rate limiting.
'resource_limit' System resource constraints.

A reducer action: a discriminated payload keyed by action type, plus identity and observability metadata.

Field Type Description
id string (UUID) Unique action identifier.
type ActionType Which action this is.
payload type-specific Validated against the schema for type.
idempotencyKey string Deduplication key that prevents re-execution on retry or resume.
compensation { type, payload } Optional compensating action for saga rollback.
metadata object Observability metadata: node_id, timestamp, attempt, and optional duration_ms, model, token_usage.

ActionType is the string union of the seven public action types listed under Action types. Internal engine actions (prefixed _) form a separate InternalActionType union reserved for the engine.

One frame of the compensation_stack, pushed by nodes with requires_compensation and drained LIFO on saga rollback.

Field Type Description
action_id string The forward action this compensates.
compensation_action { type: string, payload: Record<string, unknown> } The compensating action to dispatch on rollback.

Provenance of the untrusted data behind one memory key. Keyed by memory key inside TaintRegistry. See Taint Tracking for the propagation model.

Field Type Description
source 'mcp_tool' | 'custom_tool' | 'tool_node' | 'agent_response' | 'derived' | 'retrieval' | 'a2a' Origin of the data.
tool_name string? Tool that produced it, for tool sources.
server_id string? MCP server, or registered A2A server, that provided it.
agent_id string? Agent that produced it, for 'agent_response'.
node_id string? Node that introduced the data.
derived_from string[]? Tainted keys this value was derived from.
bytes number? Serialized size of the value.
created_at string ISO 8601 timestamp.

TaintRegistry is Record<string, TaintMetadata>, keyed by memory key.

One record of which retrieved facts entered a node’s prompt. Accumulated append-only into lesson_provenance and read with getInjectedFactIds.

Field Type Description
node_id string Node whose prompt received the facts.
agent_id string? Agent that ran the node.
fact_ids string[] Retrieved fact IDs injected into the prompt.
retrieved_at string ISO 8601 timestamp.

LessonProvenanceRegistry is Record<string, LessonProvenanceEntry>, keyed by a per-entry UUID.

  • Agents: how agents read and write state
  • Nodes: node types and configuration