Skip to content

Boundary Contracts

Nick edited this page Jun 20, 2026 · 2 revisions

Boundary Contracts

Shipped in v0.1.77–v0.1.78 (Track 3). Make the executor boundary a contract, not a convention: what the agent returns must conform, what the run may spend is bounded, and the red line (CW never reaches a model API) is enforced at compile time.

1. Output schema validation

A task may declare an optional output schema (WorkflowTaskDefinition.schema). When present, the accepted result is validated against it at intake; a mismatch throws → the drive parks the hop (fail-closed, consistent with the existing evidence/finding checks). No schema declared ⇒ no check (opt-in by declaration).

The validator (schema-validate.ts) is a dependency-free structural subset — type (incl. integer/null/type-arrays), enum, const, required, properties (nested), additionalProperties: false, items. Unsupported JSON Schema keywords ($ref, allOf, pattern, formats) are ignored, never falsely "passed". No ajv — the portability red line (node/npm/git only) holds. If full JSON Schema is ever needed, the module's impl swaps behind the same signature.

2. Token budget enforcement

limits.tokenBudget is enforced by the drive loop against recorded usage (the same deriveUsageTotals aggregation the metrics report shows — CW never measures usage itself). When recorded spend reaches the budget, the loop blocks the next spawn (blocked, not parked — the task isn't bad, the run is out of budget) and never claws back an already-accepted result. The concurrent round re-checks at round entry so it cannot overshoot by more than a batch width.

Compose with the Track 1 fail-closed telemetry policy for strict accounting: budget-against-attested-usage. See Telemetry Attestation and Tamper-Evidence.

This fail-closed limits.tokenBudget cap is also the absolute backstop for budget-aware loop scaling: a loop(...) phase with until:{kind:"budget-target", target} scales its rounds toward a token target, but can never spend past this cap.

3. The one-way boundary, welded into the type layer

The red line — CW receives only structured data from the executor, never a callable that could reach a raw model API — is no longer just enforced by review and a source-text smoke. types/boundary.ts welds it: OneWayData<T> recursively maps a type to itself iff it is plain data, poisoning any function type to never. AssertTrue<IsOneWayData<…>> pins ExecutionResultEnvelope, ResultEnvelope, and UsageRecordadding a callable to any of them breaks npm run build. unknown stays allowed as opaque data (uninvokable without an explicit cast, which the source-text smoke catches).

The one-way-boundary smoke compiles real fixtures with the repo's own tsc: a callable-bearing type and the canonical envelope smuggling a live client both fail to compile; a conforming shape compiles clean. Acceptance: an attempt to sneak a model call across the boundary has no entry point.

Still deferred (by agreement)

The data-dependent scheduling interpreter (Layer 2 reading a previous agent's output to decide the next dispatch) remains deferred — the rest of Track 3 unblocks it, but it is not yet built. See Roadmap.

Clone this wiki locally