Skip to main content

State Machines

This guide covers how to define state machines in the dialai library. For the conceptual foundation, see Sessions.

Why State Machines?

Every agentic AI system is a state machine: the agent occupies a state, takes an action, and transitions to a new state. DIAL makes the state machine explicit so that each transition becomes a measurable decision point.

This doesn't limit what you can model; it clarifies where decisions happen so they can be calibrated. You place decision points at the boundaries where delegation risk matters. An agent's internal tool-call loop can remain opaque—DIAL measures the outcomes at the states you care about.

Open-ended tasks fit naturally:

  • Document generation: Proposals are the candidate documents. Specialists propose drafts, the human picks or edits the winner.
  • Agentic workflows: The goal state is the agent's normal operating mode. It transitions out for decisions that need deliberation and back when resolved.
  • Research and exploration: Model as a loop—the agent explores, then a decision determines whether findings are sufficient or more exploration is needed.

Defining a Machine

A MachineDefinition defines:

  • machineName: identifies the type
  • initialState: where sessions start
  • goalState: the rest state where the session is headed; no action needed when reached
  • states: a record of state names to their configuration

For a complete JSON Schema definition, see schema.json.

Example

import type { MachineDefinition } from "dialai";

const myMachine: MachineDefinition = {
machineName: "my-task",
initialState: "idle",
goalState: "done",
states: {
idle: {
prompt: "The system is idle. What should happen next?",
transitions: {
start: "working",
configure: "configuring",
},
},
configuring: {
prompt: "Configuration in progress. Apply or cancel?",
transitions: {
apply: "working",
cancel: "idle",
},
},
working: {
prompt: "The system is working. Should it continue or finish?",
transitions: {
finish: "done",
reconfigure: "configuring",
},
},
done: {},
},
};

Machine Definition as JSON

Machines can also be defined as plain JSON files, useful with the CLI:

{
"machineName": "simple-task",
"initialState": "pending",
"goalState": "done",
"states": {
"pending": {
"prompt": "Should we complete this task?",
"transitions": { "complete": "done" }
},
"done": {}
}
}

Run with the CLI:

npx dialai my-machine.json

This is a minimal example. Machine JSON files can also include embedded specialist and arbiter configuration—see Full Machine JSON Structure below.

State Configuration

Each state in the states record can have:

prompt (optional)

A string describing the decision to be made in this state. This prompt guides specialists in choosing which transition to propose. If omitted, a generic default prompt is used: "Choose a transition for state '{stateName}'." with the available transitions listed.

states: {
reviewing: {
prompt: "Review the document. Approve if quality standards are met, otherwise request changes.",
transitions: { approve: "approved", request_changes: "needs_revision" },
},
}

transitions (optional)

A record mapping transition names to target state names. If omitted, the state has no outgoing transitions (terminal state).

transitions: {
approve: "approved", // transition "approve" → state "approved"
request_changes: "needs_revision",
}

proposers (optional)

A record of proposer specialists registered for this state. Each proposer can be configured with one of four execution modes.

arbiter (optional)

Arbiter configuration for this state, including consensus threshold and optional reasoning synthesis.

Full Machine JSON Structure

Machines can include embedded AI specialist and arbiter configuration at the state level. Human specialists are registered separately—not in the machine JSON:

{
"machineName": "example-task",
"initialState": "pending",
"goalState": "done",
"states": {
"pending": {
"prompt": "Should we complete this task?",
"transitions": { "complete": "done" },

"proposers": {
"ai-proposer-1": {
"strategyFn": "async (ctx) => ({ transitionName: 'complete', reasoning: 'Task is ready' })"
},
"llm-proposer-2": {
"modelId": "openai/gpt-4o-mini"
}
},

"arbiter": {
"alignmentMargin": 2
}
},
"done": {}
}
}

State-Level Fields

FieldTypeDescription
promptstringDecision prompt for this state
transitionsRecord<string, string>Map of transition names to target states
proposersRecord<string, SpecialistConfig>Proposers registered for this state
arbiterArbiterConfigArbiter configuration for this state

Specialist Configuration in JSON

Each proposer supports the same four execution modes as programmatic registration. In JSON, function values are represented as strings:

{
"proposers": {
"inline-strategy": {
"strategyFn": "async (ctx) => ({ transitionName: 'approve', reasoning: 'Ready' })"
},
"llm-based": {
"modelId": "openai/gpt-4o-mini"
},
"llm-with-context": {
"modelId": "openai/gpt-4o-mini",
"contextFn": "async (ctx) => `History: ${ctx.history.length} transitions`"
},
"webhook-based": {
"strategyWebhookUrl": "https://api.example.com/propose",
"webhookTokenName": "MY_API_TOKEN"
}
}
}

Note: Human specialists are registered separately via registerProposer with isHuman: true—they are not defined in the machine JSON. Human proposals always win. See Registering Specialists for the full configuration reference and Implementing Strategies for strategy function details.

Arbiter Configuration

The arbiter block controls consensus evaluation:

FieldTypeDefaultDescription
alignmentMarginnumber1Proposals the leading transition must be ahead by
modelIdstringLLM for reasoning synthesis (optional)
contextFnstringContext function for reasoning synthesis

Higher alignmentMargin values require stronger consensus. See Arbitration for details.

Design Patterns

Linear Workflow

const linear: MachineDefinition = {
machineName: "pipeline",
initialState: "step1",
goalState: "complete",
states: {
step1: { transitions: { next: "step2" } },
step2: { transitions: { next: "step3" } },
step3: { transitions: { next: "complete" } },
complete: {},
},
};

Review Loop

const reviewLoop: MachineDefinition = {
machineName: "review",
initialState: "draft",
goalState: "published",
states: {
draft: {
prompt: "Review the draft. Approve or request revisions?",
transitions: {
approve: "published",
revise: "revising",
},
},
revising: {
prompt: "Revisions made. Submit for review?",
transitions: { submit: "draft" },
},
published: {},
},
};

Branching Decisions

const branching: MachineDefinition = {
machineName: "triage",
initialState: "incoming",
goalState: "resolved",
states: {
incoming: {
prompt: "Triage this ticket: escalate, handle directly, or close?",
transitions: {
escalate: "escalated",
handle: "in_progress",
close: "resolved",
},
},
escalated: {
transitions: { resolve: "resolved" },
},
in_progress: {
transitions: { resolve: "resolved", escalate: "escalated" },
},
resolved: {},
},
};

Agentic Workflow

An agent's operating loop modeled as a DIAL machine. The goal state is the agent running normally: it transitions out when a decision needs deliberation, and back when resolved.

const agentLoop: MachineDefinition = {
machineName: "coding-agent",
initialState: "operating",
goalState: "done",
states: {
operating: {
prompt:
"The agent is working. Should it continue, use a tool, replan, or finalize?",
transitions: {
use_tool: "tool_selection",
replan: "planning",
finalize: "done",
},
},
tool_selection: {
prompt: "Which tool should the agent use for this step?",
transitions: { selected: "operating" },
},
planning: {
prompt: "The current approach isn't working. What should the new plan be?",
transitions: { resume: "operating" },
},
done: {},
},
};

Document Generation

For open-ended generation tasks, the specialist proposals are the candidate outputs. Each proposal endorses a transition, and the leading transition wins once it is alignment margin. The human can always override by proposing directly.

const docGen: MachineDefinition = {
machineName: "report-generation",
initialState: "drafting",
goalState: "published",
states: {
drafting: {
prompt:
"Generate a draft of the report. Each proposal should be a complete draft.",
transitions: {
accept: "published",
revise: "revising",
},
},
revising: {
prompt:
"Revise the draft based on feedback. Each proposal should be a revised version.",
transitions: {
accept: "published",
revise: "revising",
},
},
published: {},
},
};

Decision Prompts

Each state's prompt describes how to decide what to do next. Good prompts are:

  • Specific: List the available choices and criteria
  • Actionable: Tell the specialist what to evaluate
  • Consistent: Same instructions for all specialists (AI and human)
Good: "Review the code changes. Check for: 1) correctness, 2) test coverage,
3) documentation. Approve if all criteria met, otherwise request changes."

Bad: "Decide what to do next."