4 min readPart 2

Why Agents Need a Deterministic Runtime

Probabilistic models are great for reasoning, but terrible at execution. Here is why infrastructure must enforce the execution boundary.

J

José Vásquez

Founder & Lead Engineer @ Invariant

#AI Agents#Runtime#Durable Execution#Architecture
Article SeriesReliable Agent Architecture

In software engineering, we have spent five decades constructing deterministic systems: relational databases with ACID guarantees, compilers with strict type systems, and distributed orchestrators built around at-least-once delivery and idempotency.

The rise of Large Language Models introduces non-determinism into the application core.

Non-determinism is valuable when understanding natural language, formulating hypotheses, and evaluating open-ended options. But when non-determinism leaks into execution—such as invoking APIs, managing transactions, and handling state transitions—software becomes unreliable.

Core Thesis

Models reason probabilistically. Infrastructure executes deterministically. The fundamental role of an agent runtime is to enforce this boundary.


The Fragility of Unconstrained Tool Calling

In conventional agent architectures, the LLM is directly attached to tool implementations via an unconstrained loop:

[User Request] ──> [LLM Prompt] ──> [Direct Tool Execution] ──> [Repeat]

When an LLM executes tools directly, three fatal problems occur:

1. Unvalidated Authority

The model decides both what to do and when to execute it. If a model hallucinates an invalid parameter or calls a destructive mutation out of order, the runtime has no policy engine to intercept or reject the call before it causes irreversible damage.

2. Missing Idempotency & Partial Failure Vulnerability

What happens when tool #3 in a 5-step sequence fails with a network timeout?

  • Did the third-party webhook actually fire?
  • If you retry the LLM call, will it generate the exact same payload, or will it mutate slightly and create a duplicate charge?

Without transactional primitives, retrying an LLM agent loop is essentially rolling dice with side effects.

3. Inability to Suspend for Human Review

Real-world systems require human-in-the-loop approvals for high-stakes actions (e.g., transfers over $10,000). If your agent runtime is a synchronous while(true) loop running in an ephemeral container, you cannot durably suspend execution for 48 hours waiting for an executive's signature without holding open costly compute resources.

The Probabilistic vs. Deterministic BoundaryInvariant Architecture
PROBABILISTIC REASONING (LLM)Non-Deterministic
  • Intent interpretation & goal formulation
  • Action proposal (structured schema)
  • Reasoning over hydrated session view
  • Should NOT execute side effects directly
  • Should NOT hold persistent state authority
DURABLE CONTROL PLANE (INVARIANT)100% Deterministic
  • Schema & permission boundary validation
  • Transactional Outbox + Idempotent execution
  • Immutable Event Sourced State (PostgreSQL)
  • Deterministic session hydration & crash replay
  • Human-in-the-loop durable suspensions
Authority & Execution Boundary
Figure 1: Decoupling model reasoning from deterministic infrastructure execution.

Architectural Requirements of an Agent Runtime

A production-grade agent runtime requires four structural pillars:

1. The Validated Boundary (Action Proposals)

The model must never invoke side effects directly. Instead, the model emits an Action Proposal. The runtime intercepts this proposal, validates it against a typed schema and organizational policy, and determines whether it can proceed, requires human authorization, or must be rejected.

2. Transactional Outbox Pattern

Execution state, durable events, and side-effect intent are committed atomically before the capability is dispatched. The current Beta dispatcher runs in process; a new process cannot portably claim pending commands or attach to the execution. Provider idempotency and application-owned recovery code are required across crashes.

3. Deterministic Replay & Hydration

Recorded event history can reconstruct committed runtime truth without re-running model calls that already produced durable events (Durable Execution). Reconstruction does not by itself attach a new host to the execution or redispatch pending commands.

4. Durable Suspensions

The runtime can persist a .wait() boundary and accept matching input through the live Session. Portable cross-process attach and automatic timer scheduling are not part of the current Beta host.

| Dimension | Traditional Prompt / Loop Approach | Invariant Control Plane Architecture | | :--- | :--- | :--- | | Tool Calling | Direct invocation from LLM loop | Action Proposal → Validated Boundary → Transactional Outbox | | Crash Recovery | Lost progress or rerun entire prompt (duplicate effects) | Durable replay primitives; application-owned cross-process continuation in Beta | | Human-in-the-Loop | Blocking memory loops or custom ad-hoc tables | Persisted wait boundary; live-Session input in Beta | | Auditability | Unstructured string logs | Immutable, structured event stream with full trace lineage |


Implementing with Invariant

With Invariant, you write TypeScript workflows and agents that cleanly enforce this boundary:

import { invariant } from '@invariant-tech/sdk';
import { sqlite } from '@invariant-tech/sqlite';
import { z } from 'zod';

const app = invariant({ storage: sqlite('./data/transfers.db') });

// 1. Define a durable workflow with explicit wait and capability nodes
export const transferWorkflow = app.workflow('transfer-funds', {
  description: 'Executes payroll bank transfers with executive review above $10,000',
  inputSchema: z.object({ transferId: z.string(), amount: z.number() }),
})
  .step('check-amount', ({ input }) => ({
    requiresApproval: input.amount > 10_000,
  }))
  .branch('approval-branch', ({ state }) => (state.requiresApproval ? 'REQUIRE_REVIEW' : 'AUTO_EXECUTE'), {
    REQUIRE_REVIEW: app.fragment('review').wait('await-executive-approval', {
      schema: z.object({ approved: z.boolean(), approvedBy: z.string() }),
      presentation: { prompt: 'Transfer exceeds $10,000. Approval required.' },
    }),
    AUTO_EXECUTE: app.fragment('pass').step('bypass', () => ({ approved: true })),
  })
  .capability('execute-transfer', {
    idempotencyKey: 'transfer:{{runId}}',
    handler: async ({ input }) => {
      return await paymentGateway.charge(input);
    },
  });

// 2. The agent proposes actions; the runtime validates revision and schema before execution
export const financeAgent = app.agent('finance-assistant', {
  workflows: [transferWorkflow],
});

Summary

We don't need models to become deterministic. We need our infrastructure to stop pretending they are.

By shifting execution, state management, and authority out of the prompt and into a durable runtime, we gain the full reasoning capability of LLMs with the reliability of enterprise infrastructure.

In Part 3, we will look at why building this yourself in prompt frameworks is the wrong abstraction: Agent Frameworks Aren't Enough for Production Execution.

J

José Vásquez

Founder & Lead Engineer @ Invariant

Building the durable execution engine for TypeScript AI applications. Keep reasoning probabilistic. Make execution predictable.