keel
Harness

Gates and invariants

Understand what fails during configuration and what is checked during a turn.

  1. Admit

    Check the supplied envelope and root-thread ownership before execution.

    invalid envelope
    → "envelope-invalid"
  2. Fit

    Check the opening assembly and estimated input before each model request.

    input + reserve > window
    → "fit-overflow"
  3. Steps

    Bound the ordinary model loop. Check time and cancellation at execution boundaries.

    grant exhausted
    → "budget-cut"
  4. Close

    Require a clean finish, content, and any declared deliverable.

    missing required part
    → "deliverable-missing"

An invariant is a rule execution must preserve. Keel checks declarations before work starts, then checks the request and operations as the turn runs. This makes a wiring mistake distinguishable from a failure handling a request.

Check configuration first

Declaration errorWhere it fails
Missing provider model IDProvider constructor or factory
Context allocation exceeds a fallback's limitsdefineChain or defineAgent
Agent declares tools but a tier lacks tool supportdefineAgent
Read names an undeclared readerdefineAgent
Tool feeds a block outside the agent's writesdefineAgent
Delegation forwards a part outside either agent's emitsasTool or defineAgent

These are KeelError exceptions when definitions execute. Some are also TypeScript errors when enough information is known statically. Numeric capacity comparisons and dynamic configuration still require runtime checks.

Follow the four gates

Admit validates the ambient context — the core userId and threadId fields, and the app-declared scope against its defineAmbient schema; a scope that does not parse settles the turn with ambient-invalid. Three further checks are root-only: claiming the thread's single root-turn slot (via the store's claimThread, so a shared durable store serializes root turns across runtime instances), parsing the request envelope, and checking its size in UTF-8 bytes against transport.envelopeBytes. Child turns inherit the already-validated ambient and emit an admit pass with detail.root set to false.

Fit checks the opening assembly and estimates each subsequent model request, including growing tool results and schemas. A refusal returns fit-overflow before a provider call. This is approximate token accounting.

Steps bounds the ordinary model loop. Time and cancellation are checked at execution boundaries; recovery rounds have their own explicit limits.

Close checks the finish, marker completeness, content, and any required deliverable. Partial output does not establish successful completion.

Handle a refused request

const outcome = await runtime.runTurn({
  agent: assistant,
  ambient,
  envelope,
  budget: { steps: 6 },
});

if (outcome.kind === "failed") {
  console.error(outcome.cause.code, outcome.cause.message);
}

An oversized model request produces a failed outcome with fit-overflow. It is different from constructing an impossible context allocation, which throws before a turn starts. See Budgets and fit.

Validate operations inside the loop

Tool access is checked when assembling the available tools and again when executing a requested call. The executor checks the tool name and parses its input schema before running application code. A corrected call must pass those same checks.

A defect event does not necessarily terminate the turn: invalid tool input can return feedback and recover. Read the final outcome to determine whether the turn completed.

Inspect in Studio

A configuration error appears before a run can start. For a turn failure, select the turn and locate its refused gate, defect, and final outcome. Compare an invalid allocation at boot with a request that grows beyond its allocation during execution.

Next: Budgets and cancellation.