For a while we ran a multi-agent pipeline that looked like the diagrams everyone posts: a planner, a researcher, a writer, a critic, sometimes a "publisher" that was one enthusiastic prompt away from doing something irreversible.
It demoed well. It failed in the ways that matter for a studio that ships software — opaque retries, inconsistent quality, and a bill that scaled with conversation theater rather than completed work.
We replaced most of it with a state machine you can read in one sitting (~200 lines of orchestration, plus ordinary functions). This is not a manifesto against agents. It is a field note on when swarms become the product instead of the problem.
What the swarm was supposed to do
The pipeline supported internal content and review workflows adjacent to how we run the Rayaxis site: research a topic, draft Markdown, check SEO fields, stop for human approval. Specialists felt natural. One agent "did research," another "did voice," another "did critique."
In practice, each hop:
- Re-summarized the same brief with new hallucinations
- Lost hard constraints ("status stays draft," "no fake client claims")
- Added latency without adding decision quality
- Made debugging a forensic exercise across four transcripts
When something went wrong, nobody could point to a single transition and say "this state should not have advanced."
What the state machine actually is
A short list of named states and allowed transitions, for example:
intake → research → draft → seo_pass → human_review → (revise | ready)
Each state runs one deterministic job or one model call with a structured output schema. The critic is not a free-roaming agent; it is a function that returns { ok, issues[] }. If issues exist, we transition to revise with those issues attached — not to a fourth personality with its own system prompt.
Rough shape in Python (illustrative):
STATES = {"intake", "research", "draft", "seo_pass", "human_review", "ready"}
def step(state, ctx):
if state == "research":
ctx.notes = research(ctx.topic) # tools + retrieval, not a chat persona
return "draft", ctx
if state == "draft":
ctx.body = draft_markdown(ctx)
return "seo_pass", ctx
if state == "seo_pass":
ctx.seo = fill_seo(ctx)
issues = validate(ctx)
return ("human_review" if not issues else "draft"), ctx
...
The important part is not the language. Laravel jobs with a status column work the same way. So does a React admin that only enables "Approve" when state === 'human_review'.
Why this shipped better
Control flow you can test
You can unit-test illegal transitions. You cannot unit-test "the critic agent felt generous today."
Cheaper tokens, clearer logs
One model call per stage with structured outputs beats a debate club. Logs become state=draft duration=… instead of four overlapping conversations.
Humans stay in the real loop
Our blog tooling already assumes human approve-by-default: drafts insert with status=draft, never auto-publish. The state machine makes that rule a transition gate, not a hope buried in a prompt.
Fits how we build client features
Client AI work — support drafts, document Q&A, admin helpers — needs the same properties: predictable stages, auditable decisions, and a UI that shows "waiting on review." A 200-line orchestrator maps cleanly onto Laravel queues and Python workers. A swarm maps cleanly onto a conference talk.
When multi-agent still makes sense
We did not ban the idea. Parallel specialists can help when:
- Tasks are truly independent (e.g., translate the same approved copy into three locales)
- Failure isolation matters more than shared context
- You have evaluation harnesses, not vibes, for each role
Even then, we prefer a supervisor that is a state machine (or workflow engine) and "agents" that are just tools with prompts — not peers negotiating in natural language.
Anthropic's own guidance on building effective agents lands in a similar place: start with the simplest workflow that works; add autonomy only when the problem demands it.
The Rayaxis version of the lesson
We like tools that make shipping boring. Multi-agent pipelines optimized for looking autonomous. The state machine optimized for:
- Knowing what step failed
- Keeping SEO fields and voice rules attached to the artifact
- Stopping before publish
- Porting the same pattern into client Laravel/React products later
If your agent graph needs a legend, try deleting half the agents and writing the transitions down. You may find, as we did, that the product was never "more agents." It was a short list of states, a few structured calls, and a human at the only door that matters.
Sources
- Anthropic — Building effective agents
- Statecharts / state machines as UI & workflow models — useful mental model beyond AI