> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Repair Loop

> How Boundary turns failures into fixes

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 RULE_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 plain `{ role, content }` JSON objects. Most chat-style providers accept this shape directly or with a small mapping step.

```typescript theme={null}
type Message = { role: string; content: string }
type RepairList = Message[]
```

On a retry, `attempt.repairs` looks like this:

```json theme={null}
[
  { "role": "user", "content": "tier is \"hot\" but score is 25 (minimum 70 for hot)" }
]
```

Repair messages are always `role: "user"` — they carry the failure text from your rule's `check` (or the schema/parse error) so the model sees exactly what to fix on the next attempt.

```typescript theme={null}
const result = await contract.accept(async (attempt) => {
  // attempt.repairs is empty on first try
  // on retries, it contains targeted fix instructions
  const response = await callYourLLM({
    messages: [
      {
        role: "user",
        content: [
          "Score this lead as JSON.",
          attempt.instructions,
          leadSummary,
        ].join("\n\n"),
      },
      ...attempt.repairs,  // violations from prior failure
    ],
  });

  return response.text;
});
```

On the first attempt, `repairs` is empty. On subsequent attempts, it contains the failure messages from the previous attempt — one entry per rule or schema violation that fired.

## Retry configuration

By default, Boundary retries up to **3 attempts** with no delay between them.

```typescript theme={null}
const contract = defineContract({
  schema,
  rules,
  retry: {
    maxAttempts: 3,              // default: 3
    backoff: "none",             // "none" | "linear" | "exponential"
    baseMs: 200,                 // delay base in milliseconds
  },
});
```

| Strategy        | Delay 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:

| Category           | Meaning                     | Default repair                           |
| ------------------ | --------------------------- | ---------------------------------------- |
| `EMPTY_RESPONSE`   | Model returned nothing      | Re-prompt with schema instructions       |
| `REFUSAL`          | Model refused the task      | Re-prompt emphasizing the task is valid  |
| `NO_JSON`          | Response contained no JSON  | Ask for JSON output specifically         |
| `TRUNCATED`        | JSON was cut off            | Ask for complete, shorter response       |
| `PARSE_ERROR`      | Malformed JSON              | Send the parse error details             |
| `VALIDATION_ERROR` | Valid JSON, failed schema   | Send schema violations                   |
| `RULE_ERROR`       | Passed schema, failed rules | Send rule violations (your domain logic) |
| `RUN_ERROR`        | Your RunFn threw an error   | No retry by default                      |

## Custom repair overrides

You can override or disable repair for specific categories:

```typescript theme={null}
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 `maxAttempts` is exhausted without a valid output, the contract returns `{ ok: false, error }`. The full per-attempt history is on `error.attempts` — including the failure category, the raw model output, and the typed `ruleIssues` on `RULE_ERROR` attempts.

```typescript theme={null}
if (!result.ok) {
  console.log(`Failed after ${result.error.attempts.length} attempts`);
  for (const [i, attempt] of result.error.attempts.entries()) {
    console.log(`  attempt ${i + 1} [${attempt.category}]:`);
    for (const ruleIssue of attempt.ruleIssues ?? []) {
      // Per-rule attribution — name maps to the dashboard's rule_key.
      console.log(`    ${ruleIssue.rule.name}: ${ruleIssue.message}`);
    }
  }
}
```

Rejected data stays out of the success path. The contract never returns a partial or "best-effort" success value — either every rule passes or you get the structured error.

If a particular contract is hitting `maxAttempts` regularly, that's a signal: the rule may be too strict, the prompt may be missing context, or the model may be wrong for the job. Local logs can show this while you build; the hosted SDK can aggregate it across production traffic. See [Results](/concepts/results#handling-permanent-failure) for handling patterns.

## Next steps

<CardGroup cols={2}>
  <Card title="Results" icon="check" href="/concepts/results">
    The ContractResult type
  </Card>

  <Card title="Guarantees" icon="shield-check" href="/concepts/guarantees">
    What Boundary promises
  </Card>
</CardGroup>

## Observability

Start with [local development logging](/guides/local-development) to see repairs and failed attempts in your console. Add [production observability](/guides/production-observability) when you need aggregate retry and rule-failure trends across real traffic.
