Skip to content

Quick Start

Build and execute your first durable workflow in less than 5 minutes.


1. Install

Install Invariant with embedded local SQLite storage (no Docker or external database required):

bash
npm install @invariant-tech/sdk@beta @invariant-tech/sqlite@beta zod

2. Define and Run Your First Workflow

Create src/index.ts to initialize Invariant with embedded storage, define a durable order-processing workflow, and execute it inside a durable session:

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

export const app = invariant({
  storage: sqlite("./data/invariant.db"),
});

export const orderWorkflow = app
  .workflow("order-processing", {
    inputSchema: z.object({
      orderId: z.string(),
      amount: z.number(),
      userTier: z.enum(["STANDARD", "VIP"]),
    }),
  })
  .step("calculate-discount", ({ input }) => ({
    discount: input.userTier === "VIP" ? input.amount * 0.2 : 0,
    finalTotal: input.userTier === "VIP" ? input.amount * 0.8 : input.amount,
  }))
  .capability("charge-customer", {
    idempotencyKey: "charge:{{runId}}",
    handler: async ({ state }) => {
      console.log(`💳 Charging $${state.finalTotal} for order ${state.orderId}`);
      return {
        paymentId: `pay_${Date.now()}`,
        status: "COMPLETED",
      };
    },
  });

async function main() {
  const session = await app.sessions.loadOrCreateSession("sess_quickstart", "usr_101");
  const run = await session.startWorkflow(orderWorkflow, {
    orderId: "ord_9981",
    amount: 150,
    userTier: "VIP",
  });

  console.log(`🚀 Workflow started: ${run.runId}`);
  console.log("Execution Result:", session.snapshot().projectedState);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

3. Run the Workflow

Execute the file directly with npx tsx:

bash
npx tsx src/index.ts

Output:

text
💳 Charging $120 for order ord_9981
🚀 Workflow started: run_order-processing_1787141932160
Execution Result: {
  orderId: 'ord_9981',
  amount: 150,
  userTier: 'VIP',
  discount: 30,
  finalTotal: 120,
  paymentId: 'pay_1787141932185',
  status: 'COMPLETED'
}

Embedded Durability from Day One

No database servers. No Redis. No Docker setup. SQLite stores execution history, events, session context, and outbox commands locally with WAL journaling.


4. Add Probabilistic AI Reasoning (.reason())

Now introduce LLM reasoning into the workflow by installing @invariant-tech/anthropic (or @invariant-tech/openai / @invariant-tech/google):

bash
npm install @invariant-tech/anthropic@beta

Export your API key:

bash
export ANTHROPIC_API_KEY=your_anthropic_api_key

Add the model provider to your runtime and attach a .reason() step:

ts
import { invariant } from "@invariant-tech/sdk";
import { sqlite } from "@invariant-tech/sqlite";
import { anthropic, ANTHROPIC_MODELS } from "@invariant-tech/anthropic";
import { z } from "zod";

type TicketClassification = {
  category: "BILLING" | "TECHNICAL" | "ACCOUNT" | "OTHER";
  sentiment: "POSITIVE" | "NEUTRAL" | "NEGATIVE";
};

export const app = invariant({
  storage: sqlite("./data/invariant.db"),
  models: {
    default: anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
      defaultModel: ANTHROPIC_MODELS.CLAUDE_SONNET_5,
    }),
  },
});

export const supportWorkflow = app
  .workflow("support-ticket", {
    inputSchema: z.object({
      message: z.string(),
      userTier: z.enum(["STANDARD", "VIP"]),
    }),
  })
  .reason<TicketClassification>("classify-ticket", {
    instruction: "Classify the support message in execution context by category and sentiment.",
    schema: {
      type: "object",
      properties: {
        category: { type: "string", enum: ["BILLING", "TECHNICAL", "ACCOUNT", "OTHER"] },
        sentiment: { type: "string", enum: ["POSITIVE", "NEUTRAL", "NEGATIVE"] },
      },
      required: ["category", "sentiment"],
      additionalProperties: false,
    },
  })
  .step("set-priority", ({ state, input }) => ({
    priority: input.userTier === "VIP" || state.sentiment === "NEGATIVE" ? "HIGH" : "NORMAL",
  }))
  .capability("create-ticket", {
    idempotencyKey: "support-ticket:{{runId}}",
    handler: async ({ state }) => {
      console.log(`🎫 Creating ticket [${state.priority}] (${state.category})`);
      return {
        ticketId: `tkt_${Date.now()}`,
        status: "OPEN",
        assignedTeam: state.category === "BILLING" ? "Finance" : "Support",
      };
    },
  });

async function main() {
  const session = await app.sessions.loadOrCreateSession("sess_support_1", "usr_202");
  await session.startWorkflow(supportWorkflow, {
    message: "I was double charged on my subscription and need an urgent refund!",
    userTier: "VIP",
  });

  console.log("Support Ticket Result:", session.snapshot().projectedState);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

The Execution Model in Action

text
User message

  reason()       ──►  classify-ticket (AI interprets sentiment & category)

   step()        ──►  set-priority (Code calculates priority based on VIP tier & sentiment)

 capability()    ──►  create-ticket (External system creates ticket with idempotency)
  1. .reason() delegates a bounded semantic question to the LLM.
  2. .step() executes deterministic business logic without model involvement.
  3. .capability() executes external side-effects backed by the Transactional Outbox.
  4. The configured store persists committed execution state and event history. A Session coordinates the current run and can be restored at a registered .wait() boundary; it is not an arbitrary-runId command-recovery handle.

Switching Durable Storage to PostgreSQL

You can swap the embedded SQLite store for PostgreSQL without changing workflow definitions or schemas. This changes persistence and coordination primitives; it does not add the missing public command-claim, execution-attach, or background recovery host APIs. See the Recovery Contract Matrix.

bash
npm install @invariant-tech/postgres@beta
ts
import { invariant } from "@invariant-tech/sdk";
import { postgres } from "@invariant-tech/postgres";

export const app = invariant({
  storage: postgres({
    connectionString: process.env.DATABASE_URL!,
  }),
});

Next Steps

  • Mental Model — Understand how Workflows, Agents, and Executions fit together.
  • Agents & Actions — Learn how an app.agent() evaluates user intent to drive workflows automatically.
  • Crash Recovery Primitives — Understand what survives a crash and which public orchestration APIs are still missing.

Invariant Durable Execution Engine.