Boundary wraps your code, not your network. Your LLM provider relationship is unchanged.
Two products, two trust models
Boundary ships as two separate npm packages with different network behavior. You choose how much of the stack you adopt.| Package | What it does | Network behavior |
|---|---|---|
@withboundary/contract | Validates, retries, and repairs structured LLM output using your schema and rules. | None. Pure local validation. No fetch, no telemetry, no analytics. The only runtime peer is zod. |
@withboundary/sdk | Optional observability — emits structured events about each contract run. | Opt-in. Only sends data if you construct a logger with an apiKey or a custom write sink. Ships nothing automatically. |
@withboundary/contract, no data leaves your process.
Data flow
The dashed path only exists when you construct a logger. The solid path runs entirely in-process.What Boundary cloud can see
When you wire the SDK with anapiKey, every contract run produces a BoundaryLogEvent. Three optional buckets control whether sensitive content rides along — defaults are conservative:
| Bucket | Contains | Default |
|---|---|---|
repairs | Repair messages generated to nudge the model on retry | on |
inputs | The raw prompt you sent to the model | off |
outputs | The raw model response | off |
ok flag, model name, the rule names that failed, the failure category, and SDK attribution. Without those, Boundary can’t show you the run at all.
Raw prompts and completions are the most sensitive payload in an LLM pipeline — they stay off until you explicitly opt in. See Capture policy for the exact field-to-bucket mapping.
Why you might still want them on
Turninginputs or outputs on is a debugging convenience for your team, not a feature Boundary needs. When a contract fails, the dashboard’s failure detail page can show you the exact prompt that was sent and the exact response the model returned — usually the fastest way to figure out why a rule rejected an output. Without these buckets you still get the failure category, which rule failed, and the repair messages — enough to track acceptance rates and spot patterns, just not enough to read the original payload.
Boundary does not analyze, train on, or repurpose captured payloads. They are stored so you can see them in the dashboard. If your data-handling policy precludes sending raw prompts, leave both off and rely on the always-on metadata + rule failures.
Three transport modes
You decide where events go — or whether they go anywhere at all.| Mode | How | What leaves your process |
|---|---|---|
| No logger | Don’t construct a logger, or construct one without apiKey and without write. The factory returns null. | Nothing. The contract still runs; it just has no observability hook. Safe default for local dev. |
| Custom transport | Pass a write(events) function. | Only what you forward. Events go to your Datadog / Honeycomb / collector. No Boundary egress. |
| Boundary cloud | Pass an apiKey. | Events POST to https://api.withboundary.com/v1/ingest. |
Authorization: Bearer … header. It is never placed in URLs, query strings, or request bodies. The transport retries with exponential backoff and a circuit breaker so a temporary outage doesn’t turn into a retry storm.
Redaction
Even when you opt into capturing inputs or outputs, you can scrub data before it leaves the process. Three composable layers:input / output you’ve opted into is covered. The walker is cycle-safe.
See Redaction for path semantics, dropping leaves with undefined, and full examples.
API key handling
- Source:
options.apiKeyargument or theBOUNDARY_API_KEYenvironment variable. - Transport: sent only as
Authorization: Bearer ${apiKey}on requests to the ingest endpoint. Never in URL, query, or body. - Logging: never written to console, never echoed in error messages.
- Absent key: if neither
apiKeynorwriteis configured,createBoundaryLoggerreturnsnull. You can leave the call in place across environments without guarding it — local dev runs with no observability and no errors.
No hidden behavior
Boundary does not:| Modify your prompts silently | The repair instructions appended on retry are generated from your schema and rules — visible in attempt.instructions, deterministic, and emitted via the logger when repairs capture is on. |
| Call additional models or services | Only your RunFn makes LLM calls. @withboundary/contract never makes network requests. |
| Persist state between runs | Each .accept() call is independent. No global cache, no cross-run state. |
| Auto-enable telemetry | You explicitly construct a logger and pass it. No default key, no default sink. |
ContractResult:
Control stays with you
You define:- The schema — what structure the output must have.
- The rules — what “correct” means for your domain.
- The retry policy — how many attempts, what backoff.
- The capture policy — which buckets of data leave the process.
- The redaction rules — what is stripped before transmission.
- The transport — Boundary cloud, your sink, or no sink at all.
See also
Capture policy
Field-to-bucket mapping and opt-in recipes
Redaction
Fields, patterns, and per-leaf scrubbing
beforeSend
Last-chance per-event transform or drop
BoundaryLogEvent
Full wire format reference