exequiel-sosa
alal
← Back to blog
[ai]July 30, 2026· 4 min read

How Finite State Machines Keep Your AI Publishing Agents Reliable

Discover why adding a finite state machine to AI agents prevents missed steps, loops, and interruptions—using a real dev.to publishing bot as a practical example.

#state-machines#ai-agents#frontend#nextjs#publishing

Why a Prompt‑Only Flow Breaks Down

When you tell an LLM to "step 1, then step 2, then step 3," you assume the model will obey the order. In practice the model can skip a step, repeat one, or abort mid‑run if the execution environment resets. For a publishing bot that runs twice a day in a disposable sandbox, that uncertainty translates into missed posts, duplicate content, or quota errors.

Enter the Finite State Machine (FSM)

An FSM gives you an explicit state and a set of transitions. Each transition is guarded by a condition, so the bot can only move forward when the previous step succeeded. If the sandbox crashes, the bot can read its persisted state and resume exactly where it left off.

Real‑World Example: Publishing to dev.to

My dev.to publishing agent follows a five‑step workflow:

  1. Check today's API quota.
  2. Fetch trending topics.
  3. Generate a draft.
  4. Render markdown.
  5. Submit the article.

Initially I encoded these steps in a single prompt. After a few weeks I started seeing duplicate posts and quota‑exceeded errors. The fix? Wrap the flow in an FSM that persists state to a tiny JSON file in the sandbox's /tmp directory.

type Step = 'quota' | 'topics' | 'draft' | 'render' | 'publish';
interface State { current: Step; data: Record<string, any>; }

const fsm = {
  quota: async (s: State) => {
    const ok = await checkQuota();
    if (!ok) throw new Error('Quota exceeded');
    return { ...s, current: 'topics' };
  },
  topics: async (s: State) => {
    const topics = await fetchTrending();
    return { ...s, data: { ...s.data, topics }, current: 'draft' };
  },
  // ... other steps omitted for brevity
};

async function run(state: State) {
  while (state.current) {
    const next = await fsm[state.current](state);
    await persistState(next);
    state = next;
  }
}

The persistState function writes the JSON to disk after each transition, guaranteeing resumability.

How to Integrate an FSM into Your Next.js or Vercel AI Project

Most AI SDKs (Vercel AI SDK, OpenAI, Anthropic) expose a stream or completion call. Treat each call as a step in the FSM. Here’s a minimal Next.js API route that loads state, runs the next transition, and returns the updated state to the client.

import type { NextApiRequest, NextApiResponse } from 'next';
import { readFile, writeFile } from 'fs/promises';
import { run } from '../../lib/fsm';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const raw = await readFile('/tmp/publish-state.json','utf8').catch(()=>'{"current":"quota","data":{}}');
  const state = JSON.parse(raw);
  try {
    await run(state);
    res.status(200).json({ status: 'ok', state });
  } catch (e) {
    res.status(500).json({ error: (e as Error).message });
  }
}

This pattern works whether you run the agent on a Vercel cron job, a Cloudflare worker, or a local Docker container.

Trade‑offs and Gotchas

  • Persistence overhead: writing to disk after every step adds latency. In high‑frequency bots you might batch state writes.
  • Complexity creep: a full FSM can become a state‑chart with dozens of nodes. Keep it flat—only model real branching points.
  • Testing: unit‑test each transition in isolation. Mock the AI SDK calls so you can verify state progression without hitting the API.

In short, an FSM isn’t a magic bullet, but it gives you a concrete safety net that pure prompting lacks.

Bottom Line for Front‑End Engineers

When you embed LLM calls in a UI workflow—think “generate article → preview → publish”—treat the UI steps as an FSM. Persist the UI state in localStorage or a server‑side store, and guard each transition with validation. That way your app won’t lose track if the user reloads the page or the network hiccups. The same principle that saved my dev.to bot will keep your AI‑enhanced front‑ends robust.

// related

find me in:
linkedin
X
facebook