Stately
PackagesAgent

Quickstart

Install core:

pnpm add @statelyai/agent@alpha xstate@alpha zod

Add ai and a provider only when using the optional AI SDK adapter.

Define and run one artifact

Save this as agent.mts. It uses a deterministic executor, so it needs no API key or model SDK.

import { z } from "zod";
import { runAgent, setupAgent } from "@statelyai/agent";

const answerOutputSchema = z.object({ answer: z.string() });

const agent = setupAgent({
  context: z.object({ prompt: z.string(), answer: z.string().nullable() }),
  input: z.object({ prompt: z.string() }),
  output: answerOutputSchema,
  requests: {
    answer: {
      model: "fast",
      schemas: { input: z.object({ prompt: z.string() }), output: z.string() },
      prompt: ({ input }) => input.prompt,
    },
  },
});

const machine = agent.createMachine({
  context: ({ input }) => ({ prompt: input.prompt, answer: null }),
  initial: "answering",
  states: {
    answering: {
      invoke: {
        src: "answer",
        input: ({ context }) => ({ prompt: context.prompt }),
        onDone: ({ output }) => ({
          target: "done",
          context: { answer: output.result },
        }),
      },
    },
    done: {
      type: "final",
      output: ({ context }) => ({ answer: context.answer ?? "" }),
    },
  },
});

const result = await runAgent(machine, {
  input: { prompt: "Why state machines?" },
  executors: {
    generateText: async () => ({ result: "Because transitions constrain behavior." }),
  },
});

if (result.status !== "done") {
  throw new Error(`Unexpected status: ${result.status}`);
}

console.log(result.output.answer);

Run it:

npx tsx agent.mts

It prints:

Because transitions constrain behavior.

Requests have a semantic name, resolved input, model reference, schemas, prompt/messages, and tools. The machine owns control flow; the executor owns the provider call.

An executor is just a function, so the machine runs anywhere a function does. When several requests each need their own canned answer, the executor routes on request.name. See Evals.

Use a real model

Leave the machine unchanged and replace only the host executors:

import { openai } from "@ai-sdk/openai";
import { createAiSdkExecutors } from "@statelyai/agent/ai-sdk";

const executors = createAiSdkExecutors({
  models: { fast: openai("gpt-5.4-mini") },
});

const liveResult = await runAgent(machine, {
  input: { prompt: "Why state machines?" },
  executors,
});

if (liveResult.status === "done") {
  console.log(liveResult.output);
}

Continue with Choosing a run mode, Messages, and Persistence.

On this page