Skip to main content
Creates a reusable contract that validates LLM outputs against a schema and rules.

Signature

function defineContract<T>(
  config: ContractConfig<T>
): DefinedContract<T>

ContractConfig<T>

FieldTypeRequiredDefaultDescription
schemaZodType<T>YesZod schema defining the expected output structure
rulesRule<T>[]No[]Domain correctness rules. Each rule is (data: T) => true | string
retryRetryOptionsNo{ maxAttempts: 3 }Retry behavior on failure
repairsRepairOverridesNoCustom repair strategies per failure category
instructions{ suffix?: string }NoAppend text to auto-generated schema prompt
onAttemptAttemptHookNoCalled after each attempt
loggerContractLogger<T>NoHook into execution lifecycle events
debugbooleanNofalseShorthand for enabling createConsoleLogger()

DefinedContract<T>

The returned contract object has one method:

.accept(run, overrides?)

contract.accept(
  run: RunFn,
  overrides?: Partial<ContractConfig<T>>
): Promise<ContractResult<T>>
Executes your LLM call inside the contract boundary. Returns a ContractResult<T>. The optional overrides let you override any config option at call time. Definition-time options are the defaults; runtime options take precedence.

RetryOptions

type RetryOptions = {
  maxAttempts?: number    // default: 3
  backoff?: "none" | "linear" | "exponential"  // default: "none"
  baseMs?: number         // default: 200
}
StrategyDelay pattern
"none"No delay between retries
"linear"baseMs * attemptNumber
"exponential"baseMs * 2^attemptNumber

RepairOverrides

Override or disable repair for specific failure categories:
type RepairOverrides = Partial<Record<
  FailureCategory,
  RepairFn | false
>>

type RepairFn = (detail: AttemptDetail) => Message[]
Set to false to skip retrying for that category. Provide a function to generate custom repair messages.

Example

import { z } from "zod";
import { defineContract } from "@boundary/contract";

const contract = defineContract({
  schema: z.object({
    tier: z.enum(["hot", "warm", "cold"]),
    score: z.number().min(0).max(100),
    reason: z.string(),
  }),
  rules: [
    (d) => d.tier === "hot" ? d.score > 70 : true,
    (d) => d.reason.length > 0 || "reason cannot be empty",
  ],
  retry: { maxAttempts: 3 },
  repairs: {
    REFUSAL: false,  // don't retry on refusal
  },
});

const result = await contract.accept(async (attempt) => {
  const res = await openai.responses.create({
    model: "gpt-4.1",
    input: ["Score this lead", ...attempt.repairs],
    text: { format: { type: "json_schema", schema } },
  });
  return res.output_text;
});

Runtime overrides

Override config per-call without changing the contract definition:
// use more retries for this specific call
const result = await contract.accept(run, {
  retry: { maxAttempts: 5 },
});