Deterministic AI / field note 11

Deterministic AI: The Agent Factory's Spec-Driven Development, State Machines, and require_approval Architecture

The AI Agent Factory's Spec-Driven Development methodology pairs non-deterministic LLM reasoning with deterministic state machines, typed output contracts, idempotent tool calls, and the require_approval human gate for zero-hallucination enterprise workflows.

Deterministic AI / Technical field notes by Hamza Sajid

1. The Spec-Driven Development Methodology

The AI Agent Factory framework teaches Spec-Driven Development (SDD) as the primary methodology for building reliable AI Workers. The core principle:

Define what the system must do with complete precision before any model touches the task.

This is fundamentally at odds with how most developers use AI today — giving the model an ambiguous prompt and iterating on the output until it seems right. In production systems, "seems right" is not a valid delivery criteria.

Spec-Driven Development requires:

  1. A fully defined Agent Specification (Agent Spec)
  2. A testable Definition of Done (DoD)
  3. A typed output contract the model must satisfy
  4. A policy map of what the agent can and cannot do
  5. Human gates at every point where the risk profile changes

2. The Non-Determinism Problem in Critical Workflows

Language models are probabilistic engines. Given the same input twice, a model may choose different tool orderings, different reasoning paths, or different output formats.

In enterprise workflows — finance, healthcare, legal, operations — this variability is unacceptable. If a payment approval requires Step 1 (verify balance), Step 2 (check fraud signals), Step 3 (execute transfer), an AI system must never skip to Step 3 because it looked plausible.

The Agent Factory book addresses this through the principle of Connector-Native App Architecture combined with a Governing State Machine:

stateDiagram-v2
    [*] --> SPECIFIED
    SPECIFIED --> CONTEXT_LOADED: Load SoC + Skills
    CONTEXT_LOADED --> REASONING: Validate state
    REASONING --> HUMAN_GATE: High-risk action detected
    REASONING --> EXECUTING: Low-risk approved action
    HUMAN_GATE --> EXECUTING: Human approves
    HUMAN_GATE --> ABORTED: Human rejects
    EXECUTING --> VERIFYING: Action complete
    VERIFYING --> COMPLETE: DoD satisfied
    VERIFYING --> REASONING: DoD not met, continue
    VERIFYING --> ESCALATED: Cannot satisfy DoD

3. The Deterministic AI Pattern: LLM Inside a State Machine

The hybrid pattern that the Agent Factory advocates combines two things that are usually treated as opposites:

  • Deterministic: A strict state machine controls what transitions are valid, what permissions are active, and when work can proceed.
  • Adaptive: A language model operates inside specific state nodes to handle fuzzy reasoning, natural language interpretation, or tool selection.

The LLM never controls the state machine. The state machine controls when the LLM can act.

from enum import Enum
from pydantic import BaseModel
from typing import Optional

class WorkflowState(str, Enum):
    SPECIFIED = "SPECIFIED"
    CONTEXT_LOADED = "CONTEXT_LOADED"
    REASONING = "REASONING"
    HUMAN_GATE = "HUMAN_GATE"
    EXECUTING = "EXECUTING"
    VERIFYING = "VERIFYING"
    COMPLETE = "COMPLETE"
    ESCALATED = "ESCALATED"
    ABORTED = "ABORTED"

VALID_TRANSITIONS: dict[WorkflowState, list[WorkflowState]] = {
    WorkflowState.SPECIFIED: [WorkflowState.CONTEXT_LOADED],
    WorkflowState.CONTEXT_LOADED: [WorkflowState.REASONING],
    WorkflowState.REASONING: [WorkflowState.HUMAN_GATE, WorkflowState.EXECUTING, WorkflowState.ESCALATED],
    WorkflowState.HUMAN_GATE: [WorkflowState.EXECUTING, WorkflowState.ABORTED],
    WorkflowState.EXECUTING: [WorkflowState.VERIFYING],
    WorkflowState.VERIFYING: [WorkflowState.COMPLETE, WorkflowState.REASONING, WorkflowState.ESCALATED],
}

class DeterministicAIWorker:
    def __init__(self, spec: AgentSpec):
        self.state = WorkflowState.SPECIFIED
        self.spec = spec
        self.history: list[tuple[WorkflowState, str]] = []

    def transition(self, new_state: WorkflowState, reason: str) -> None:
        if new_state not in VALID_TRANSITIONS.get(self.state, []):
            raise InvalidTransitionError(
                f"Illegal transition: {self.state} → {new_state}. "
                f"Valid: {VALID_TRANSITIONS.get(self.state, [])}"
            )
        self.history.append((self.state, reason))
        self.state = new_state
        print(f"[STATE] {self.history[-1][0]} → {self.state}: {reason}")

4. Idempotence: The Engineering Requirement for Safe Retries

Every tool action an AI Worker executes must be idempotent: calling the same tool with the same arguments multiple times produces the same final system state without creating duplicates or side effects.

The Agent Factory book frames idempotence as a reliability requirement, not just a nice-to-have. When an LLM API times out mid-workflow, the harness must be able to retry the last step without creating a second payment, a second email, or a second database entry.

// Idempotent tool implementation with idempotency keys
async function createPayment(params: {
  customerId: string;
  amount: number;
  currency: string;
  idempotencyKey: string; // Required: derived from run_id + step_id
}): Promise<PaymentResult> {
  return await stripe.paymentIntents.create(
    { amount: params.amount, currency: params.currency, customer: params.customerId },
    { idempotencyKey: params.idempotencyKey } // Stripe deduplicates on this key
  );
}

// Harness generates idempotency key deterministically
function makeIdempotencyKey(runId: string, stepId: string, toolName: string): string {
  return `${runId}:${stepId}:${toolName}`;
}

5. The Human-in-the-Loop Architecture: require_approval

The Agent Factory book — specifically the OpenAI Agents SDK crash course — demonstrates the require_approval pattern as the primary mechanism for human-in-the-loop governance.

Every tool that the book classifies as high-risk (write operations, financial transactions, external communications, data deletion) must be decorated with require_approval. The harness pauses execution, queues the action for human review, and only proceeds when an authorized operator approves.

from agents import Agent, function_tool
from agents.approvals import require_approval

# Low-risk: read-only, no approval needed
@function_tool
async def query_customer_record(customer_id: str) -> dict:
    """Read customer data for analysis."""
    return await db.customers.find_one({"id": customer_id})

# High-risk: writes to external system, requires human approval
@function_tool
@require_approval
async def send_customer_email(customer_id: str, subject: str, body: str) -> str:
    """Send an email to a customer. Requires operator approval."""
    await email_service.send(customer_id, subject, body)
    return f"Email queued for {customer_id}"

@function_tool  
@require_approval
async def process_refund(transaction_id: str, amount: float) -> str:
    """Process a customer refund. Requires operator approval."""
    return await payment_gateway.refund(transaction_id, amount)

6. Choosing Agentic Architectures

The Agent Factory book's crash course on Choosing Agentic Architectures gives engineers a decision tree for when to use each architecture:

ArchitectureUse WhenRisk Level
Single AgentOne specialized task, bounded scopeLow
Sequential PipelineMultiple steps, clear order, minimal branchingMedium
Supervisor + SpecialistsComplex jobs requiring different expert domainsMedium-High
Parallel AgentsIndependent sub-tasks that can run concurrentlyMedium
Graph-based WorkflowArbitrary dependencies, conditional pathsHigh (requires graph engineering)

The book's key guidance: start simple, promote complexity only when the workflow genuinely requires it. Most enterprise workflows fit in a well-specified single agent or sequential pipeline.


7. Key Takeaways

  • Use Spec-Driven Development: define the job completely before any model executes it.
  • Implement a state machine to govern valid workflow transitions.
  • Let the LLM reason within state nodes — never let it control state machine transitions.
  • Idempotence is required for every tool call that touches external state.
  • Use require_approval for all high-risk operations to maintain human accountability.
  • Choose the simplest architecture that satisfies the workflow's genuine requirements.

Continue reading

Next field note

AI Reliability Engineering: The Agent Factory's Eval-Driven Development, Maker-Checker Pattern, and 'Leaving the Laptop' Standard