AdapterContract

Scaffold, lint, register, and discover the typed/validated signal layer between framework adapters and the acceptance gate.

Layer boundary. AdapterContract types, validates, versions, and allow-lists adapter-produced signals. AcceptancePolicy is where the user/counterparty defines whether those validated signals add up to acceptable work. AiGentsy enforces the user's policy deterministically against type-validated inputs; it does not decide what counts as good work.

What it is

An AdapterContract is a versioned, hash-bound declaration that tells the AiGentsy runtime which fields a framework adapter produces, which of those fields may enter acceptance-policy context, and which validator types them. The runtime gates every adapter-backed proof through the contract before the user-authored policy evaluates.

Every adapter-backed bundle freezes the full contract declaration inside proof.evidence.adapter_evaluation.adapter_contract. A third party with the bundle can re-derive contract_hash and input_schema_hash from the declaration alone, confirm normalized_policy_inputs are a subset of allowed_policy_fields, and see which validator (name + version) ran — with no runtime lookup.

What it is not

Schema

/specs/adapter-contract.schema.json — JSON Schema draft-07. The required fields:

adapter_contract_version   "1.0.0"
adapter_id                 reverse-DNS (e.g. acme.eval_suite.v0)
adapter_version            semver
input_schema_version       semver
input_schema_hash          sha256 hex (recomputable)
output_fields              snake_case array (the adapter promises these)
allowed_policy_fields      subset of output_fields (these may enter policy)
validator_name             snake_case
validator_version          semver
contract_hash              sha256 hex (recomputable)

Scaffold a new contract

$ aigentsy adapter scaffold \
    --id acme.eval_suite.v0 \
    --version 0.1.0 \
    --validator boolean \
    --fields all_tests_passed,coverage_met,regression_check_passed
{
  "ok": true,
  "adapter_id": "acme.eval_suite.v0",
  "out": "./adapter_contract.json",
  "contract_hash": "...",
  "input_schema_hash": "...",
  "next": [
    "aigentsy adapter lint ./adapter_contract.json",
    "edit output_fields / allowed_policy_fields to match your adapter",
    "drop the file into AIGENTSY_ADAPTER_CONTRACTS_DIR on the runtime"
  ]
}

Lint a contract

Pure-Python, no network, no credentials. Validates the JSON, recomputes both hashes, checks allowed_policy_fields ⊆ output_fields, warns on unknown validators.

$ aigentsy adapter lint adapter_contract.json
{
  "ok": true,
  "errors": [],
  "warnings": [],
  "adapter_id": "acme.eval_suite.v0",
  "contract_hash": "...",
  "input_schema_hash": "..."
}

Register

Drop validated *.adapter_contract.json files under the directory pointed at by AIGENTSY_ADAPTER_CONTRACTS_DIR (default: data/adapter_contracts/). The runtime loads every valid contract at startup. The built-in aigentsy.settlement_native_mcp.starter stays registered out of the box; file-loaded contracts are additive.

One bad file does not crash the runtime — it is reported in startup logs and skipped. A file whose adapter_id collides with a built-in contract is also skipped (built-ins win).

Discover

Public, no auth. Same data a third party reads from any exported bundle's adapter_evaluation:

GET https://aigentsy-ame-runtime.onrender.com/protocol/adapter-contracts
GET https://aigentsy-ame-runtime.onrender.com/protocol/adapter-contracts/{adapter_id}

Fail-closed defaults

Framework example contracts

Five small example contracts ship under data/adapter_contracts/examples/ on the runtime and are auto-loaded at startup. They are shape-and-discovery examples — the validator under v0 is the same boolean family, replace with your own validator for production:

Honest scope. These framework contracts ship as authoring examples that lint clean and appear in the discovery endpoint. They are not production-certified integrations; substitute your own validator when wiring a framework adapter into a live workflow.

Artifact binding

When a proof is created with adapter_id set, the runtime attaches the full AdapterEvaluation — including the embedded contract declaration — to proof.evidence.adapter_evaluation. The export bundle binds it via bundle_hash; the signed Merkle tree head signs the bundle root. Tampering anywhere in the chain breaks aigentsy-verify offline.

Versioning, distribution, and replay independence

The registry is a discovery surface, not a replay dependency. Bundles carry their own truth. aigentsy-verify validates the embedded adapter_contract declaration against the bundled (pure-Python) schema and recomputes the two canonical hashes without calling the runtime registry, the public manifest, the docs site, or any other network resource. Old bundles remain replayable forever from their embedded declarations — even if contracts are deprecated, revoked, or removed from this registry.

The canonical contract_hash covers exactly these fields:

{adapter_contract_version, adapter_id, adapter_version,
 input_schema_version, output_fields (sorted),
 allowed_policy_fields (sorted),
 validator_name, validator_version}

The lifecycle fields below are discovery-only and are explicitly outside the canonical hash key set:

Field Type Meaning
statusenumactive (default) / deprecated / revoked
supersedesstringadapter_id@semver — the version this release replaces
replaced_bystringadapter_id@semver — the migration target for deprecated/revoked rows
published_atISO-8601publish timestamp
min_verifier_versionsemverminimum aigentsy-verify recommended for this contract
distribution_channelenumaigentsy.builtin / aigentsy.examples / developer.local / third_party

Coexistence and collision rules

Identity is (adapter_id, adapter_version). Two versions of the same adapter_id may coexist freely. get_contract(adapter_id) returns the highest-semver active version (the “active head”); get_contract(adapter_id, adapter_version) returns an exact-key match.

Manifest and pinning

The public manifest at /specs/adapter-contracts.manifest.json lists each (adapter_id, adapter_version) with its contract hash, status, distribution channel, and declaration URL. The manifest is for developer discovery — aigentsy-verify never reads it at replay time.

Developer commands:

aigentsy adapter list                          # read the manifest
aigentsy adapter list --manifest ./local.json  # offline read
aigentsy adapter pin --id X --version Y --hash Z
                                               # write ./pinned_adapters.json
aigentsy adapter bump path.json --version X.Y.Z
                                               # bump version + recompute hashes
aigentsy adapter diff old.json new.json        # structural diff
                                               # (lifecycle changes flagged
                                               #  as hash-irrelevant)

Pinning is downstream: an adopter who has reviewed (adapter_id, adapter_version, contract_hash) commits a pinned_adapters.json to their project so subsequent reviewers can confirm the exact contract bytes were authoritative. The runtime does not require a pin file to register or evaluate; the pin file is policy at the developer's edge.

Contract publication attestation

Publication is adoption provenance, not replay truth. Each entry in the public manifest is signed with the canonical aigentsy_log_signer_v1 Ed25519 key — the same key already trusted for signed-tree-head, OutcomeReceipt, and Passport. There is no second signing model. aigentsy-verify still does not call the manifest, the publisher document, or any network resource during bundle replay.

Per-entry attestation fields

The signed manifest at /specs/adapter-contracts.manifest.json carries:

Top-level fields: manifest_schema_version, manifest_hash (sha256 over the ordered entry_hash list plus publisher fields), manifest_signature (Ed25519 over manifest_hash), public_key_base64, and verify_url pointing at /protocol/merkle/public-key.

Publisher document

The verification key is published at /.well-known/aigentsy-publisher.json. A developer can either pin the key locally or fetch it through this document. The key is the same one served by the runtime at /protocol/merkle/public-key — the publisher document is a stable mirror so the public key is discoverable from the static frontend.

Adoption workflow

# Author a contract; AiGentsy attests it; pin it in your project
aigentsy adapter lint adapter_contract.json
aigentsy adapter attest adapter_contract.json
# (Send the attested entry to AiGentsy for inclusion in the manifest.)

# Once published:
aigentsy adapter manifest verify \
    https://aigentsy.com/specs/adapter-contracts.manifest.json \
    --fetch-publisher           # uses /.well-known/aigentsy-publisher.json

aigentsy adapter pin \
    --id developer.foo.v0 --version 0.1.0 --hash <contract_hash> \
    --manifest https://aigentsy.com/specs/adapter-contracts.manifest.json \
    --public-key-base64 <the key from the publisher document>
# Pin file records entry_hash + publisher_id +
# entry_signature_status — never silently treats unverified as trusted.

Deprecated, superseded, and revoked contracts

Lifecycle status affects new adoption, not historical replay. When a contract's status changes in the runtime registry or the public manifest, old bundles that already embedded that contract continue to verify byte-identically — aigentsy-verify reads the embedded declaration only and does not consult the current status.

Status New artifact creation CLI pin behavior Legacy bundle replay
activeAllowedPin normallyUnaffected
active + replaced_by set (superseded)Allowed; lifecycle_warnings[kind=superseded] surfaced in adapter_evaluation with replacement pointerPin allowed; NOTICE printed to stderr with replacementUnaffected
deprecatedAllowed; lifecycle_warnings[kind=deprecated] surfaced in adapter_evaluation with replacement pointerPin allowed; WARNING printed to stderr; lifecycle_advice recorded in the pin fileUnaffected
revokedBlocked. evaluate_adapter routes to unknown_adapter with empty normalized_policy_inputs → AcceptancePolicy falls through to default_action: require_review. lifecycle_warnings[kind=revoked] surfaced for visibilityPin BLOCKED by default (exit code 3). --allow-revoked-for-legacy-inspection permits the pin for forensic / archive purposes only and records explicit inspection-only adviceUnaffected

The lifecycle_warnings field rides on proof.evidence.adapter_evaluation in the bundle. Like the embedded declaration, it is frozen into the artifact and visible to any third party. It is not a hashed field — it does not enter contract_hash and cannot be used to silently invalidate historical bundles.

What signed publication does NOT do

Related