Skip to main content

Quick Start

Build your first DIAL state machine with specialists. This guide focuses on the code—for the concepts behind what you're building, see Concepts.

What We'll Build

A trivially simple machine that asks "Should we complete this task?" and transitions from pending to done:

Step 1: Define the Machine

Save this as examples/simple-machine.json:

{
"machineName": "simple-task",
"initialState": "pending",
"goalState": "done",
"states": {
"pending": {
"prompt": "Should we complete this task?",
"transitions": { "complete": "done" }
},
"done": {}
}
}
  • initialState: where the session starts (pending)
  • goalState: the rest state where the session is headed (done); no action needed when reached
  • prompt: the question specialists answer when the session is in that state
  • transitions: the available answers and what state each leads to

Only one transition (complete) leads to done, so the machine always resolves in one cycle.

Or define the same thing in TypeScript:

import type { MachineDefinition } from "dialai";

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

Step 2: Run It

The quickest way to run a machine is with runSession, which registers a built-in proposer (firstAvailable) and a built-in arbiter (firstProposal) to drive the machine to its goal state:

import { runSession } from "dialai";

const session = await runSession(machine);

console.log(session.currentState); // "done"

That's it. One cycle, done.

Step 3: Walk the Decision Cycle Manually

Let's register specialists, submit proposals, and run arbitration step by step.

import {
createSession,
getSession,
registerProposer,
registerArbiter,
submitProposal,
submitArbitration,
} from "dialai";

// Create a session - starts in "pending"
const session = await createSession(machine);
console.log(session.currentState); // "pending"
console.log(session.currentRoundId); // "e5f6g7h8-..."

// Register two proposers and an arbiter
await registerProposer({
specialistId: "ai-specialist",
machineName: "simple-task",
strategyFnName: "firstAvailable",
});

await registerProposer({
specialistId: "contrarian-ai",
machineName: "simple-task",
strategyFnName: "firstAvailable",
});

await registerArbiter({
specialistId: "consensus-arbiter",
machineName: "simple-task",
strategyFnName: "alignmentMargin",
});

// Two AI specialists each submit a proposal
const proposalA = await submitProposal({
sessionId: session.sessionId,
specialistId: "ai-specialist",
roundId: session.currentRoundId,
transitionName: "complete",
reasoning: "The task is ready to complete",
metaJson: { source: "automated-check" },
});

const proposalB = await submitProposal({
sessionId: session.sessionId,
specialistId: "contrarian-ai",
roundId: session.currentRoundId,
transitionName: "complete",
reasoning: "I agree, let's complete it",
});

// Submit arbitration - checks for consensus (both propose "complete")
const result = await submitArbitration({ sessionId: session.sessionId, roundId: session.currentRoundId });
console.log(result.executed); // true (both proposers agreed)
console.log(result.toState); // "done"

// Fetch the updated session (the original variable is stale after transition)
const updated = await getSession(session.sessionId);
console.log(updated.currentState); // "done"
console.log(updated.history); // [{ transitionName: "complete", reasoning: "...", ... }]

Human primacy means that when AI cannot reach consensus, a human can force a decision by calling submitArbitration with an explicit transition. A human proposal always wins.

Step 4: Use the CLI

Run a machine definition from the command line:

npx dialai examples/simple-machine.json

Output:

Machine:       simple-task
Initial state: pending
Goal state: done
Final state: done
Session ID: a1b2c3d4-...

What's Happening Under the Hood

  1. Session created in initialState (pending) with a fresh currentRoundId
  2. Proposers solicited: each returns a proposed transition (complete)
  3. Arbitration submitted: guards checked, alignment margin consensus evaluated
  4. Transition executed: currentState moves to done, currentRoundId regenerated
  5. Cycle repeats until currentState === goalState (already there, done)

Next Steps