Skip to main content
When an LLM output fails validation, Boundary doesn’t just retry blindly. It extracts the specific violations, classifies the failure, and sends targeted repair instructions back to the model.

How it works

Attempt 1 → LLM returns output
           → Boundary validates (schema + rules)
           → Fails: "tier is hot but score is 25"
           → Failure classified as INVARIANT_ERROR
           → Repair message generated from violation

Attempt 2 → LLM receives original prompt + repair context
           → Returns corrected output
           → Boundary validates again
           → All rules pass → ACCEPTED
This is not blind retry. Each repair message is targeted to the specific failure. The model knows exactly what went wrong and what to fix.

The attempt.repairs field

Inside your RunFn, attempt.repairs is an array of Message objects:
const result = await contract.accept(async (attempt) => {
  // attempt.repairs is empty on first try
  // on retries, it contains targeted fix instructions
  const res = await openai.responses.create({
    model: "gpt-4.1",
    input: [
      "Score this lead",
      ...attempt.repairs,  // ← violations from prior failure
    ],
  });
  return res.output_text;
});
On the first attempt, repairs is empty. On subsequent attempts, it contains messages describing exactly what failed and why.

Retry configuration

By default, Boundary retries up to 3 attempts with no delay between them.
const contract = defineContract({
  schema,
  rules,
  retry: {
    maxAttempts: 3,              // default: 3
    backoff: "none",             // "none" | "linear" | "exponential"
    baseMs: 200,                 // delay base in milliseconds
  },
});
StrategyDelay pattern
"none"No delay between retries (default)
"linear"baseMs * attemptNumber
"exponential"baseMs * 2^attemptNumber

Failure categories

Every failed attempt is classified into one of 8 categories. Each category has a default repair strategy:
CategoryMeaningDefault repair
EMPTY_RESPONSEModel returned nothingRe-prompt with schema instructions
REFUSALModel refused the taskRe-prompt emphasizing the task is valid
NO_JSONResponse contained no JSONAsk for JSON output specifically
TRUNCATEDJSON was cut offAsk for complete, shorter response
PARSE_ERRORMalformed JSONSend the parse error details
VALIDATION_ERRORValid JSON, failed schemaSend schema violations
INVARIANT_ERRORPassed schema, failed rulesSend rule violations (your domain logic)
RUN_ERRORYour RunFn threw an errorNo retry by default

Custom repair overrides

You can override or disable repair for specific categories:
const contract = defineContract({
  schema,
  rules,
  repairs: {
    // Don't retry on refusal — the model won't change its mind
    REFUSAL: false,

    // Custom repair for validation errors
    VALIDATION_ERROR: (detail) => [
      {
        role: "user",
        content: `Your output didn't match the schema. Issues: ${detail.issues.join(", ")}. Please fix.`,
      },
    ],
  },
});
Setting a category to false stops retrying for that failure type. Providing a function lets you craft custom repair messages.

When repair fails

If all attempts are exhausted without a valid output:
if (!result.ok) {
  // result.error.message — human-readable summary
  // result.error.attempts — every failed attempt with details
  console.log(`Failed after ${result.error.attempts.length} attempts`);
  console.log(result.error.message);
}
No invalid data leaks through. You always get a structured error with the full attempt history.

Next steps

Results

The ContractResult type

Guarantees

What Boundary promises