Install the local contract package and accept your first LLM output
This quickstart uses the free @withboundary/contract package. It does not require a Boundary account, API key, or hosted dashboard.
Using an AI coding assistant? Install the Boundary skill first so it can find the LLM output path, add the contract at the trust boundary, and verify accepted, repaired, and rejected outcomes.
Install the Boundary skill in your AI coding agent.
A contract is a schema plus rules. The schema checks the shape. Rules check the values your product cares about.
lead-contract.ts
import { z } from "zod";import { defineContract } from "@withboundary/contract";const leadSchema = z.object({ tier: z.enum(["hot", "warm", "cold"]), score: z.number().min(0).max(100), reason: z.string(),});export const leadContract = defineContract({ name: "lead-scoring", schema: leadSchema, rules: [ { name: "hot_requires_high_score", description: "Hot leads must have a score of at least 70", fields: ["tier", "score"], check: (lead) => lead.tier !== "hot" || lead.score >= 70 || `tier is "hot" but score is ${lead.score}; set tier to warm/cold or raise score to at least 70`, }, { name: "reason_required", description: "Every scoring decision must explain why", fields: ["reason"], check: (lead) => lead.reason.trim().length > 0 || "reason must not be empty", }, ], debug: true,});
debug: true turns on the built-in console logger. It prints attempts, failures, repairs, and the final result locally. No data leaves your process.
contract.accept() takes your existing LLM call as a function. Return the raw model output as a string. Boundary parses it, validates it, repairs failures when possible, and returns a typed result.
score-lead.ts
import { leadContract } from "./lead-contract";const result = await leadContract.accept(async (attempt) => { const response = await callYourLLM({ messages: [ { role: "user", content: [ "Score this lead as JSON.", attempt.instructions, "Lead: signed up two days ago, visited pricing three times, opened one email.", ].join("\n\n"), }, ...attempt.repairs, ], }); return response.text;});
On the first attempt, attempt.repairs is empty. If the output fails, Boundary builds repair messages from the schema and rule violations. Your next model call receives those messages.
When result.ok is true, result.data is typed from your schema and every rule has passed. When result.ok is false, no data is returned. You decide whether to fail the request, retry with different context, or send it to a human review queue.
With debug: true, a failed first attempt gives you the important parts:
[contract] lead-scoring attempt 1 failed RULE_ERROR[contract] hot_requires_high_score: tier is "hot" but score is 25; set tier to warm/cold or raise score to at least 70[contract] reason_required: reason must not be empty[contract] retrying with repair context[contract] lead-scoring accepted on attempt 2
That is enough to tune rules, prompts, and reject handling before you wire the hosted dashboard.