Skip to main content
ContractLogger gives you a hook into every phase of a contract run — from the first attempt through parsing, verification, repair, retry, and terminal outcome. It’s a structural type (every hook optional), so you only implement the ones you need.

Event flow

Every hook receives contractName and runHandle. Use contractName to group by contract and runHandle to correlate hooks from the same run.

All 10 hooks

onRunStart

Called once per contract.accept(...) invocation, before any attempt runs.

onAttemptStart

Called before each attempt. On attempt 1, repairs is empty. On later attempts, it contains the repair messages generated from prior failures.

onRawOutput

The raw string your RunFn returned, before any cleaning or parsing.

onCleanedOutput

The result of clean(raw) — JSON extracted from fences, de-prose’d, etc. Not yet validated.

onVerifySuccess

The attempt passed schema validation and all rules. The overall run will succeed.

onVerifyFailure

The attempt failed. category is the FailureCategory. issues is the list of violations. On RULE_ERROR, ruleIssues includes structured rule names and fields.

onRepairGenerated

Fires after a failure when a repair message has been built. Won’t fire for categories you’ve disabled via repairs: { CATEGORY: false }.

onRetryScheduled

Fires after repair, before the backoff delay. delayMs is the computed delay for this retry based on your retry.backoff strategy.

onRunSuccess

Terminal success. attempts is the total number of attempts (including the successful one).

onRunFailure

Terminal failure — all retries exhausted. category is the last failure’s category (may be undefined if the run errored before any verify).

Recipes

Custom metrics

OpenTelemetry spans

Structured debug logging

Use the built-in console logger for human-readable traces:
Or use the shorthand debug: true for default verbosity.

Combining multiple loggers

You can only pass one logger to defineContract. To fan out to both Boundary and your metrics system, compose them with a tiny helper:
The proxy forwards every hook to every logger, skipping null (the dev-mode SDK fallback) and skipping any logger that doesn’t implement that hook. Drop it into your codebase as-is.

Constraints

  • Hooks are synchronous from the contract loop’s point of view. Returning a promise doesn’t delay the next phase — the loop moves on. Push heavy work into a batch (like createBoundaryLogger does) or a queue.
  • Exceptions inside hooks are caught and swallowed. Your logger cannot break a contract run.
  • Hook order is guaranteed per attempt: onAttemptStartonRawOutputonCleanedOutputonVerifySuccess | (onVerifyFailureonRepairGeneratedonRetryScheduled).

See also

SDK Overview

createBoundaryLogger is a ContractLogger

Engine primitives

Skip the loop, use the pieces directly