Stop Blaming the LLM: Why Rogue AI Agents Are Just Bad State Machines

Stop Blaming the LLM: Why Rogue AI Agents Are Just Bad State Machines

The industry narrative around production AI is broken. When a multi-agent system loops infinitely, hallucinates an invalid database operation, or drifts into unhandled tool calls, developers shrug and blame the model. They call it "agent drift" or talk about the model "losing its system prompt."

It is a convenient excuse for sloppy software architecture.

While building JobHunt.ai—an orchestration layer that relies on 6 concurrent Gemini agents via FastAPI, the Google ADK, and AlloyDB—I realized that treating an LLM as an autonomous agent without hard architectural boundaries is professional malpractice. Large language models are non-deterministic text generators. They are not databases, they are not application servers, and they are definitely not state managers.

If your AI agent goes rogue, it is not because the weights shifted. It is because you built a bad state machine.

Here is how we fixed non-deterministic failures in production by replacing "agent autonomy" with strict bounded contexts and explicit state validation.


Why Do Developers Rely on Prompt Engineering Instead of Bounded Contexts?

The default pattern for building agentic workflows is lazy: write a sprawling system prompt, hand over full tool-calling capabilities to the LLM, and wrap the execution in a while loop until it returns a success token.

This approach violates fundamental distributed systems principles. You are delegating control flow to a probabilistic function. When an LLM fails to format a JSON payload, it isn't a mystical intelligence failure; it's a validation error at an unconstrained boundary.

In JobHunt.ai, we manage resume parsing, skill matching, gap analysis, and tailored outreach across 6 specialized Gemini agents. If Agent A (the parser) hands raw, unvalidated text to Agent B (the matchmaker) without a hard schema check, the entire pipeline collapses by step three.

The fix was stripping away the illusion of agent autonomy. Agents do not decide what to do next. A deterministic state machine dictates the transition, and the LLM merely executes the payload generation within that strict box.


How to Implement Deterministic State Machines in FastAPI for Multi-Agent Workflows

To make agents reliable in production, you must separate reasoning from orchestration. The LLM handles reasoning (generating text based on context). FastAPI and your state machine handle orchestration (validating inputs, managing database transactions, and enforcing state transitions).

Here is a simplified pattern of how we enforce strict schema validation and deterministic state handoffs using Pydantic and FastAPI:

from enum import Enum
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()

class AgentState(str, Enum):
    INITIALIZED = "initialized"
    PARSED = "parsed"
    ANALYZED = "analyzed"
    FAILED = "failed"

class PipelineContext(BaseModel):
    user_id: str
    current_state: AgentState = AgentState.INITIALIZED
    raw_resume_text: str
    structured_data: dict = Field(default_factory=dict)

def validate_transition(current: AgentState, target: AgentState) -> bool:
    valid_transitions = {
        AgentState.INITIALIZED: [AgentState.PARSED, AgentState.FAILED],
        AgentState.PARSED: [AgentState.ANALYZED, AgentState.FAILED],
        AgentState.ANALYZED: [],
        AgentState.FAILED: []
    }
    return target in valid_transitions.get(current, [])

@app.post("/agent/transition")
def execute_agent_step(context: PipelineContext, next_state: AgentState):
    if not validate_transition(context.current_state, next_state):
        raise HTTPException(
            status_code=400, 
            detail=f"Invalid transition from {context.current_state} to {next_state}"
        )
    
    # State transition is valid; safe to invoke LLM reasoning here
    context.current_state = next_state
    return {"status": "success", "new_state": context.current_state}

By enforcing state validation before the model touches the data, you eliminate 90% of the runtime exceptions that developers mistakenly attribute to "rogue" agent behavior.


What Happens When You Stop Trusting the LLM Completely?

When you treat LLM outputs as untrusted user input—the exact way you treat payloads from an untrusted frontend client—your entire debugging paradigm shifts.

Instead of tweaking prompts for three hours to stop an agent from skipping a step, you write a unit test for the state machine edge case.

  1. Input validation: Every tool call output is run through a strict Pydantic model. If a field fails type validation, the agent execution halts immediately, logs the payload to AlloyDB, and triggers a fallback deterministic path.
  2. Explicit state serialization: We store agent execution steps as explicit database states rather than trusting the conversation history context window to remember where it is.
  3. Local fallback strategies: For workloads requiring low latency and absolute privacy—like running local inference workflows via Ollama and n8n on an RTX 5060 setup—keeping context size small and state transitions rigid prevents GPU memory bloat and infinite generation loops.

The Takeaway

AI engineering is not prompt engineering. It is systems engineering wrapped around a probabilistic core.

If your multi-agent architecture is flaky, stop tuning your system prompts. Build a better state machine, enforce strict input/output validation, and treat your LLMs like untrusted microservices.

If you liked this post, check out how we structure asynchronous workflows in my other posts, or subscribe to the newsletter for deep dives into production mobile and AI infrastructure.