Skip to content

Introduction

Invariant is a TypeScript framework for building reliable AI agents and workflows.

It gives AI models room to reason while keeping application state, execution, side effects, and recovery under deterministic control.

Agents reason about what should happen.
.reason() nodes answer bounded questions inside an execution.
Workflows define what may happen.
The Runtime makes execution durable.


1. Defining a Workflow

A Workflow defines the allowed execution paths of a business process using deterministic TypeScript logic:

ts
const RefundReason = z.enum([
  "damaged",
  "not_received",
  "wrong_item",
  "duplicate_charge",
  "cancelled_order",
]);

const refundWorkflow = app.workflow("refund", {
  inputSchema: z.object({
    customerId: z.string(),
    orderId: z.string(),
    reason: RefundReason,
    description: z.string().max(500).optional(),
  }),
})
  .capability("load-customer", loadCustomer)
  .capability("load-order", loadOrder)

  .step("check-policy", ({ state }) => {
    if (!state.customer) {
      return { approved: false, reason: "CUSTOMER_NOT_FOUND" };
    }
    if (!state.order) {
      return { approved: false, reason: "ORDER_NOT_FOUND" };
    }
    if (!isRefundEligible(state.customer, state.order, state.reason)) {
      return { approved: false, reason: "NOT_ELIGIBLE" };
    }

    return { approved: true, amount: state.order.totalAmount };
  })

  .branch("refund-decision", {
    on: ({ state }) => state.approved,
    cases: {
      true: app.fragment("approved").capability("issue-refund", issueRefund),
      false: app.fragment("rejected").step("reject", ({ state }) => ({
        status: "REJECTED",
        reason: state.reason,
      })),
    },
  });

2. Where Does the AI Live?

An Agent is where the LLM enters your application. It interprets unstructured user requests and decides which allowed workflow to start:

ts
const supportAgent = app.agent("support", {
  description: "Helps customers with order inquiries and refunds.",
  workflows: [refundWorkflow],
});

const turn = await supportAgent.run({
  session,
  message: "My order arrived damaged. I want a refund.",
});

The Agent interprets the user's message and proposes starting refundWorkflow with a constrained input payload:

ts
{
  customerId: "cust_123",
  orderId: "order_456",
  reason: "damaged"
}

From there, the workflow—not the model—loads the real customer and order records, evaluates the actual refund policy, and controls execution:

text
"My order arrived damaged"


      ┌───────────┐
      │   Agent   │
      │    LLM    │
      └─────┬─────┘

     interprets intent


{ customerId, orderId, reason: "damaged" }


     Refund Workflow

    ┌───────┴────────┐
    ▼                ▼
load customer    load order
    └───────┬────────┘

       check policy
     (deterministic)

       ┌────┴────┐
       ▼         ▼
   approved    rejected


  issue refund

The model can choose how to communicate the result naturally to the user ("I couldn't locate that order number, could you double-check it?"), but it cannot convert ORDER_NOT_FOUND into REFUND_ISSUED.

"The Agent interprets intent. The Workflow constrains behavior. The Runtime controls execution."


3. Workflows Can Reason Too

The refundWorkflow above is deterministic because refund eligibility can be calculated from authoritative business rules.

However, not every task has a deterministic answer. When a workflow needs model judgment for an inherently semantic problem, it uses a .reason() node:

ts
const triageWorkflow = app.workflow("support-triage", {
  inputSchema: z.object({
    userMessage: z.string(),
  }),
})
  .reason("classify-request", {
    prompt: ({ input }) => `Classify customer request: "${input.userMessage}"`,
    schema: z.object({
      category: z.enum(["REFUND", "DELIVERY", "ACCOUNT", "OTHER"]),
      urgency: z.enum(["LOW", "MEDIUM", "HIGH"]),
    }),
  })

  .branch("triage-routing", {
    on: ({ state }) => state.category,
    cases: {
      REFUND: app.fragment("refund").capability("route-to-refund", routeToRefund),
      DELIVERY: app.fragment("delivery").capability("route-to-shipping", routeToShipping),
      ACCOUNT: app.fragment("account").capability("route-to-security", routeToSecurity),
      OTHER: app.fragment("other").capability("route-to-human", routeToHuman),
    },
  });

A .reason() node creates an explicit boundary where the workflow intentionally delegates a semantic question to a model, receives a validated structured output, and continues deterministic execution.


4. Orchestration Reasoning vs. Task Reasoning

Invariant distinguishes between two different roles for model reasoning:

DimensionAgent (app.agent)Reasoning Step (.reason())
RoleOrchestration ReasoningTask Reasoning
Question"What should happen next?""What is the answer to this bounded task?"
ScopeApplication / Session levelOne specific workflow node
Contextprojected runtime context & available actionsexplicit prompt & node input
OutputRuntime Action (start_workflow, submit_input)Validated Zod schema result
AuthorityRuntime validates action against fresh stateWorkflow controls subsequent execution
text
Agent Reasoning (Orchestration)
"What does the user want?" → "What workflow should I start?" → Runtime Action


Workflow (Execution Graph)
load authoritative data → apply deterministic rules → .reason() node (Task Reasoning: "Classify this text")


                                                       validated structured result


                                                       deterministic execution continues

"Use model reasoning where the problem is inherently semantic—not where authoritative application logic can determine the answer."
"Probabilistic reasoning can exist wherever it is useful. Execution authority remains with the runtime."


5. What Invariant Owns

Once execution enters Invariant, the model does not need to remember or control the application lifecycle.

Invariant owns:

  • Durable Workflow State: Persisted after every step in PostgreSQL.
  • Execution Position: The runtime always knows where execution is.
  • Side-Effect Boundaries: External operations cross explicit .capability() boundaries with idempotency keys.
  • Durable Suspension: Workflows can .wait() for hours or days without keeping workers alive.
  • Crash Recovery: Server crashes (kill -9) resume automatically from the exact point of interruption without duplicate side-effects.
text
Models
reason about uncertain questions



Invariant
controls execution & durability



Your Application
remains authoritative

6. What This Architecture Gives You

Building an AI application with Invariant fundamentally changes what the model is responsible for.

Smaller model context

The model does not need to reconstruct the application lifecycle from a growing history of raw messages and tool calls. Invariant keeps execution state in the runtime and projects only the context required for the current decision. This can substantially reduce input-token usage in long-running interactions by avoiding repeated transmission of execution history and irrelevant application state.

text
Traditional agent:
conversation history + tool history + instructions + app state → LLM (Heavy Token Load)

Invariant:
durable application state → relevant context projection → LLM (Focused Context)

Constrained model authority (blast radius)

Invariant does not prevent LLMs from hallucinating; instead, it reduces how much application authority depends on model outputs. The model may misunderstand user intent, but invented customer data cannot become authoritative application state simply because the model produced it.

"The goal isn't to make probabilistic models deterministic. It's to make fewer correctness guarantees depend on probabilistic reasoning."

Safer AI actions

Giving an unconstrained LLM a raw refundCustomer() tool grants significant business authority to a probabilistic engine. In Invariant, the model can only request start_workflow("refund", payload). The application remains authoritative over whether execution may proceed.

text
AI can refund customer              ✗ (Too risky)
AI can request the refund process    ✓ (Controlled intent)
Application determines validity     ✓ (Authoritative truth)

Invariant is designed to keep model reasoning separate from application authority.

Execution visibility

When an error occurs, Invariant separates what the model decided from what the runtime executed. You can inspect where an execution was, which prompt produced an intent, which capability was attempted, whether it was retried, and where an issue occurred.

You don't just know that an agent failed. You can know where, when, and at which boundary it failed.


7. Workflows Don't Require Agents

Workflows define durable, reliable execution paths. Agents are simply one way to drive them.

  • A background Cron job can run a durable workflow entirely without an LLM.
  • A document processing pipeline can contain .reason() nodes without having an Agent.
  • A customer assistant can use an Agent to coordinate several durable Workflows.

Next Steps

Invariant Durable Execution Engine.