Skip to content

TypeScript SDK & API Reference

Complete TypeScript API reference for @invariant/sdk primitives, workflow nodes, control-flow operators, agent decision layer, session management, and runtime contracts.


1. Application Setup (invariant())

The invariant() function creates the root application container configured with storage adapters, model registries, and session schemas.

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

export const app = invariant({
  models: {
    default: openAI({ model: "gpt-4o" }),
    fast: openAI({ model: "gpt-4o-mini" }),
  },
  session: {
    contextSchema: z.object({
      customerId: z.string(),
      accountTier: z.enum(["FREE", "PRO", "ENTERPRISE"]),
    }),
    hydrate: async ({ userId, sessionId }) => {
      const customer = await crm.getCustomer(userId);
      return { customerId: customer.id, accountTier: customer.tier };
    },
  },
});

Application Lifecycle & Configuration (InvariantAppConfig)

ts
// Start runtime workers & recovery scanner:
await app.start();

// Graceful shutdown of workers and locks:
await app.stop();
Property / MethodTypeDescription
modelsRecord<string, ModelAdapter>Model registry mapping keys (default, fast) to provider adapters.
sessionSessionConfig<TSessionContext>Configuration for contextSchema and hydrate callback.
storeStorageAdapterPersistence adapter (PostgreSQL, SQLite, Redis, or Memory).
app.start()() => Promise<void>Starts outbox background workers and crash recovery scanner.
app.stop()() => Promise<void>Stops background workers and releases active process leases gracefully.

2. Workflows & Fragments

app.workflow()

Creates a compiled, type-safe workflow graph builder with an optional input schema.

ts
export const refundWorkflow = app.workflow("customer-refund", {
  description: "Processes customer refund requests with policy evaluation.",
  inputSchema: z.object({
    orderId: z.string(),
    reason: z.string(),
  }),
});
OptionTypeDescription
descriptionstringNatural language description used for Agent intent routing.
inputSchemaZodSchema | TypeBoxSchemaSchema validating initial workflow parameters.

app.fragment()

Creates a reusable, composable subgraph fragment.

ts
const issueRefundFragment = app.fragment("issue-refund-subgraph")
  .capability("issue-stripe-refund", issueStripeRefund)
  .step("build-receipt", buildReceipt)
  .capability("send-receipt-email", sendReceiptEmail);

Automatic Subgraph Namespacing: During build-time compilation, app.fragment() automatically flattens subgraphs and namespaces inner node IDs (e.g. refund-decision.approved.issue-stripe-refund) to guarantee global node ID uniqueness across execution traces and event logs.


3. Workflow Graph Nodes

.step()

Defines a pure, synchronous, in-process computation node.

ts
workflow.step("check-policy", {
  fallback: "manual-review",
  handler: ({ state }) => ({
    eligible: Boolean(state.customer?.active && state.customer?.refundWindowOpen),
  }),
});
OptionTypeDescription
fallbackstringOptional node ID to transition to on internal computation error (e.g. JSON parse error).
handlerfunctionPure, synchronous computation handler ({ input, state }) => Partial<TState>.

Steps perform deterministic in-process computation without external side-effects; the runtime does not apply retry policies to steps.


.reason()

Delegates a bounded semantic decision task to an LLM inside an explicit context and output schema boundary.

ts
workflow.reason("classify-intent", {
  instruction: "Classify the support request into a category and urgency tier.",
  context: ({ input }) => ({ message: input.message }),
  schema: z.object({
    category: z.enum(["REFUND", "DELIVERY", "ACCOUNT", "OTHER"]),
    urgency: z.enum(["LOW", "MEDIUM", "HIGH"]),
  }),
  retry: {
    maxAttempts: 3,
    backoffMs: 1000,
  },
  fallback: "fallback-classification",
});
OptionTypeDescription
instructionstringExplicit instruction prompt directing the LLM for this bounded task.
contextfunctionPure selector function ({ input, state }) => object isolating context passed to LLM.
schemaZodSchemaOutput Zod schema enforced via structured outputs / JSON schema validation.
retryRetryPolicyOptional retry configuration (maxAttempts, backoffMs, retryOn).
fallbackstringNode ID to transition to on exhausted retries or model failure.

.capability()

Defines an external side-effect boundary (API call, database write, email dispatch, queue delivery).

ts
workflow.capability("issue-stripe-refund", {
  idempotencyKey: ({ execution }) => `refund:${execution.id}`,
  retry: {
    maxAttempts: 5,
    backoffMs: 2000,
  },
  fallback: "notify-ops-failure",
  handler: async ({ state, execution }) => {
    return await stripe.refunds.create(
      { charge: state.chargeId, amount: state.refundAmount },
      { idempotencyKey: `refund:${execution.id}` }
    );
  },
});
OptionTypeDescription
idempotencyKeyfunctionGenerates a stable key ({ execution, state }) => string across retries.
retryRetryPolicyRetry configuration (maxAttempts, backoffMs, retryOn).
fallbackstringNode ID to transition to on exhausted retries or capability failure.
handlerfunctionAsync capability execution handler async ({ state, input, execution }) => object.

.wait()

Suspends workflow execution durably until a matching external event or user input arrives.

ts
workflow.wait("await-approval", {
  schema: z.object({
    approved: z.boolean(),
    approverId: z.string(),
  }),
  options: ({ state }) => [
    { label: "Approve Refund", value: true },
    { label: "Reject Refund", value: false },
  ],
  timeout: "24h",
});
OptionTypeDescription
schemaZodSchemaSchema validating external event payload or user input.
optionsfunctionOptional dynamic choices builder ({ state }) => Array<{label, value}>.
timeoutstringDuration string (e.g. "24h", "30m") after which TIMER_FIRED triggers timeout.

4. Control-Flow & Composition Operators

.branch()

Deterministically routes execution between subgraph branches based on a selector function:

ts
workflow.branch("route-decision", ({ state }) => (state.eligible ? "APPROVED" : "REVIEW"), {
  APPROVED: autoRefundFragment,
  REVIEW: manualReviewFragment,
});

.repeat()

Iterates a subgraph fragment durably for bounded loops, UI automation, or polling routines:

ts
workflow.repeat("automation-loop", {
  maxIterations: 50,
  do: app.fragment("loop-step")
    .capability("check-status", checkStatus)
    .branch("done-check", ({ state }) => (state.complete ? "EXIT" : "CONTINUE"), {
      EXIT: app.fragment("exit").break(),
      CONTINUE: app.fragment("continue"),
    }),
});

.child()

Spawns a separate child workflow execution with its own runId and event history:

ts
workflow.child("fraud-check-child", fraudReviewWorkflow, {
  input: ({ state }) => ({ userId: state.customerId, amount: state.refundAmount }),
});

5. Direct Workflow Executions (Workflow.start() & Execution)

Workflows can be started directly by application code without an Agent or conversation:

ts
// Start workflow execution directly (returns an Execution handle):
const execution = await refundWorkflow.start({
  input: { orderId: "ord_99", reason: "defective" },
  session, // optional session scope
});

console.log(execution.id); // "run_refund_171829200"

// Query current execution status:
const status = await execution.status(); // "RUNNING" | "SUSPENDED" | "COMPLETED" | "FAILED"

// Wait for final state result (or catch ExecutionError on failure):
try {
  const finalState = await execution.result();
  console.log(finalState.refundId);
} catch (error) {
  if (error instanceof ExecutionError) {
    console.error(`Execution ${error.runId} failed at node ${error.failedNodeId}: ${error.message}`);
  }
}

// Pause / Resume / Cancel execution:
await execution.pause();
await execution.resume();
await execution.cancel("Cancelled by administrator");

6. Session Management & Context API (app.sessions & Session)

A Session is the durable continuity boundary for identity, hydrated context, channels, and related executions.

ts
// Retrieve or initialize a durable session:
const session = await app.sessions.getOrCreate("sess_101", "usr_441", {
  preferredLanguage: "en", // optional initialContext fallback/defaults
});

// Read hydrated session context:
console.log(session.context.customerId);

// Update context via explicit application mutation:
await session.updateContext({ preferredLanguage: "es" });

// Rehydrate context from authoritative external sources:
await session.rehydrate();

// Start a workflow scoped to this session:
await session.startWorkflow(refundWorkflow, { orderId: "ord_99" });

// Submit user input to an active .wait() boundary:
await session.submitInput({ approved: true });

Session Creation & Hydration Semantics

  • New Session (getOrCreate): If no session exists with sessionId, Invariant creates it, passes initialContext if provided as seed defaults, and invokes hydrate({ userId, sessionId }) to construct initial context.
  • Existing Session (getOrCreate): If a session with sessionId exists in durable storage, Invariant loads its stored context without calling hydrate().
  • session.rehydrate(): Explicitly invokes hydrate({ userId, sessionId }) against external sources to refresh context.

7. Agents (app.agent() & Agent.run())

Agents interpret user intent and propose constrained Runtime Actions over registered workflows.

ts
export const supportAgent = app.agent("customer-support", {
  instructions: `
    Help customers resolve order and refund inquiries.
    Prefer driving registered workflows over unconstrained answers.
    Never claim a refund succeeded unless the Runtime reports completion.
  `,

  workflows: [refundWorkflow, cancelSubscriptionWorkflow],

  context: ({ session }) => ({
    customerId: session.context.customerId,
    tier: session.context.accountTier,
  }),

  model: "fast",
});

AppAgentOptions Specification

OptionTypeDescription
instructionsstringDeveloper-defined behavioral/system instructions for LLM orchestration.
workflowsArray<Workflow>Array of compiled workflows the Agent is permitted to drive.
contextfunctionSelector function ({ session }) => object projecting business context to model.
modelstringModel registry key ("default", "fast").

Agent Turn Execution (agent.run())

ts
const result = await supportAgent.run({
  session,
  message: "I want a refund for order ord_99",
});

console.log(result.output);   // Model conversational response
console.log(result.action);   // Proposed Runtime Action (if any)
console.log(result.execution);// Started or active Execution reference

8. Agent Runtime Contracts

Invariant enforces a strict boundary between model reasoning and runtime execution.

AgentContextProjection

The payload assembled by Invariant for each Agent turn:

ts
export interface AgentContextProjection<TContext = Record<string, unknown>> {
  /** Developer Context: Business information projected from Session Context */
  readonly context: TContext;

  /** Runtime Projection: Derived execution position, relevant workflows, and valid actions */
  readonly runtime: RuntimeProjection;
}

export interface RuntimeProjection {
  readonly revision: number;
  readonly relevantWorkflows: Array<{ id: string; description?: string }>;
  readonly validActions: Array<{ name: string; description: string }>;
  readonly activeExecution?: { runId: string; currentNodeId: string };
  readonly expectedInput?: { schema: Record<string, unknown> };
}

RuntimeAction

A structured request proposed by the model and re-validated by the Runtime against fresh durable state before execution:

ts
export type RuntimeAction =
  | {
      type: "start_workflow";
      workflowId: string;
      input: Record<string, unknown>;
      revision: number;
    }
  | {
      type: "submit_input";
      input: Record<string, unknown>;
      revision: number;
    }
  | {
      type: "cancel_workflow";
      reason?: string;
      revision: number;
    };

Validation Gate (STALE_REVISION): A RuntimeAction is a proposal, not autonomous execution authority. If the durable runtime state revision has changed between projection assembly and action execution (action.revision !== currentRevision), the action is rejected with STALE_REVISION, fresh projection is reassembled, and the turn is safely re-evaluated.


9. Type System & State Merging

Invariant provides end-to-end static type inference across workflow steps and session contexts.

ts
type Merge<A, B> = Omit<A, keyof B> & B;

When nodes return data, the compile-time type signature extends automatically:

ts
const workflow = app.workflow("typed-demo", {
  inputSchema: z.object({ orderId: z.string() }),
})
  // Node 1: state is { orderId: string }
  .capability("load-order", async ({ input }) => ({
    order: { id: input.orderId, amount: 150.00 },
  }))
  // Node 2: state is { orderId: string, order: { id: string, amount: number } }
  .step("calculate-discount", ({ state }) => ({
    discount: state.order.amount > 100 ? 20.00 : 0.00,
  }))
  // Node 3: state contains orderId, order, and discount!
  .step("final-total", ({ state }) => ({
    finalTotal: state.order.amount - state.discount,
  }));

Compile-Time vs. Runtime Parity: The compile-time state merge semantics (Merge<A, B> = Omit<A, keyof B> & B) match the runtime state reducer semantics exactly.


10. Extensibility & Adapter Contracts

Invariant is designed to be fully extensible via pluggable storage and model adapters.

StorageAdapter Interface

ts
export interface StorageAdapter {
  commitTransition(commit: TransitionCommit): Promise<void>;
  loadExecution(runId: string): Promise<DurableExecution | null>;
  loadSession(sessionId: string): Promise<DurableSession | null>;
  saveSession(session: DurableSession): Promise<void>;
  acquireLease(runId: string, workerId: string, ttlMs: number): Promise<Lease | null>;
  renewLease(runId: string, leaseId: string, ttlMs: number): Promise<boolean>;
  releaseLease(runId: string, leaseId: string): Promise<void>;
  findRunnableExecutions(input: { limit: number }): Promise<string[]>;
}

ModelAdapter Interface

ts
export interface ModelAdapter {
  complete(params: {
    system: string;
    prompt: string;
    schema?: Record<string, unknown>;
  }): Promise<{ content: string; structuredOutput?: unknown }>;
}

Ecosystem Package Mapping

  • @invariant/sdk — Core DSL, Session management, Agent definition, Execution handles.
  • @invariant/postgres — Transactional Outbox & PostgreSQL event store adapter.
  • @invariant/openai / @invariant/anthropic / @invariant/google — LLM provider adapters.
  • @invariant/live-gemini — Gemini Live WebSocket real-time transport adapter.

Invariant Durable Execution Engine.