Skip to main content
An integration exposes operations: its tools (tool/<name>) and its actions (action/<name>). The contract is the sealed, machine-readable statement of them. It is a contract.json at the integration’s root, derived from your defineIntegration result, listing for each operation its input and output as JSON Schema, whether it reads or writes, an example of its output, and the connection it needs. An experience never imports your code. It pins your contract, and the pinned contract is all it is typed and checked against. That is why a change to the contract has rules.

Derive the contract

First, declare a contract version in the integration’s ekanos.json. The scaffold does not write one:
Then describe each operation completely. For the contract an operation needs:
  • a description (required);
  • a typed output, so an experience can bind to it: output as a zod schema on a tool or an action (without one the output is opaque and an experience cannot use the operation);
  • whether it reads or writes: effect: 'read' | 'write'. An operation with no effect is treated as a write, with a warning;
  • an example of its output, for the harness and for tests: outputExample, or a recorded one (below). A read operation without one is an error.
Run the command from the integration’s folder:
It loads your definition, derives the contract with deriveContract from @ekanos/sdk/contract, and writes contract.json. Commit it: it is reviewed like code, and it ships in the published package.

The three modes

boolean
Derives in memory and fails if contract.json differs. It writes nothing. Use it in CI and as the integration’s prepack script: "prepack": "ekanos contract --check". The checks/contract-emit stage of ekanos validate, and so of ekanos publish, makes the same freshness check for an integration that declares a contract version.
boolean
Fills in missing output examples under contracts/examples/. Without --op, it writes a placeholder synthesized from the output schema for each operation that has no example. The command says so, and the harness serves those placeholders in fixtures mode until you replace them. With --op, it records a real example from a run (below).
boolean
Rewrites contract.version in ekanos.json to the smallest version that satisfies the rules for this change.
Recording a real example from a run:
--op takes one or more operations, comma separated. --input takes JSON for a single --op; without it the input is synthesized from the input schema. A recording is checked against the output schema before anything is written, and live data may contain real records, so review the file before you commit it. A write is never recorded live. To record a write, add a scenario fixture at fixtures/<integration>/action-create-draft.default.json holding a real-shaped response, then record it with --from fixtures; nothing is called. --check cannot be combined with --record or --bump.

Versions and compat lines

A contract has its own version, contract.version, independent of the package version, and a compat line derived from it: For a 0.x contract the line is 0.<minor>; from 1.0 it is the major. An experience pins a line ("version": "0.2" in its ekanos.json), so everything on that line has to stay compatible with what the experience was built against. Operations carry the line in their id: acme-cms@0.2/tool/list_posts.

What is a breaking change

Every change between two contracts is classified. Consumers send inputs and read outputs, so the same edit is judged in opposite directions on each side. Descriptions, titles, defaults, examples, and whether an object allows extra keys are ignored: documentation is not a contract, and a consumer’s tolerance of extra response keys is what makes “add an optional output field” safe.

What ekanos contract enforces

  • A breaking change on the same compat line is an error. Start a new line instead: on a 0.x contract, bump the minor (0.2.0 to 0.3.0); from 1.0, bump the major.
  • Any change needs a higher contract.version. If the surface changed and the version did not, the command fails. If the version went down, it fails.
  • From 1.0, an additive change needs a minor bump. On a 0.x contract the minor is the line, so an additive change is a patch bump.
  • A change to the package only (no change to the operations) leaves the contract untouched.
ekanos contract --bump works out the right bump for you and writes it to ekanos.json. A breaking change that opens a new line is allowed: that is what a line is for. Experiences pinned to the old line keep working against it.

Pins in an experience

ekanos add acme-cms@0.2 copies the contract it found into the experience’s contracts/ folder and records its digest in ekanos.lock.json. From then on:
  • ekanos check fails if the pinned line has no such operation, or if the stored contract no longer matches the lock;
  • ekanos sync accepts a new digest within the pinned line (that is, compatible changes), and ekanos sync --check reports drift without writing;
  • moving to a new line is a deliberate edit to version in ekanos.json, followed by ekanos sync.

Next steps