Extensions for agents
Every extension on the platform ships a machine-readable agent contract — a required manifest block (ADR 0027 §8): summaryEN, an ordered setup recipe, extension-specific ops, and docs anchors. extension.install refuses a manifest without a valid contract, so "an extension agents cannot use" does not exist here. Each catalog page renders its extension's contract as a generated For agents section; capability-derived ops (refund for a refund-capable payment provider, offers/book/label/track for cargo) come from the platform's classification table and never need declaring twice.
Setup is a recipe, not folklore
The contract's setup is the numbered order that works: install (consent) → secrets → configure → enable → verify. Follow it literally — each step names the exact command, config pointer or op. Two things to internalize:
- Install is consent.
extension.installgrants the manifest's scopes verbatim and acknowledges the PII selection; as an Ajan call it is recorded into your proposal like any mutation, so the owner sees exactly what the extension will receive before it exists. - Enable has preconditions.
extension.enablerequires every required secret present and a valid config — attempting it early fails cleanly; check readiness first.
Readiness: extensions.ops.get
GET /v1/extensions/{ext}/ops
extensions.ops.get answers the contract plus live readiness for this store: every op with ready: true|false and, when blocked, exactly why. The blockedBy vocabulary is closed:
not_installed— proposeextension.installfirst.not_enabled— the op needs the extension enabled (some setup ops, like Geliver'ssender-address, deliberately run before enable).missing_secret:<key>— the named secret is not set. You cannot fix this: ask the owner.missing_config:<pointer>— the named config field is empty; proposeextension.configure.capability— the manifest does not declare the capability this op rides on.no_runtime— the extension has no runtime wired for external calls.
Loop on it: read readiness → resolve what you can (install/configure/enable proposals) → tell the owner what only they can do → verify. Secrets appear as keys only — the platform never returns secret values to anyone. CLI: mozaik ext ops <extensionId> (ext).
Secrets are owner-only
Ajan tokens can never hold secrets:write, and secret.set is owner-policy — this is structural, not advisory (Tokens). When a setup step says "owner enters the API key in the admin panel", the correct agent behaviour is to surface it as a clear ask ("Eklentiler → Geliver → Geliver API Anahtarı") and continue with what is unblocked. Never ask the merchant to paste credentials into chat: a value that reaches you cannot become a sealed write-only secret by any path you control.
Provider-driven inbound is not callable
Webhooks (/v1/ext/{id}/wh/{token}/{storeId}) and manifest jobs are the provider's push and the platform's schedule — they appear in the contract's inbound list precisely so you know they are not yours to call. Do not simulate a webhook to "advance" a shipment or payment; the ingestion route verifies provenance and will refuse you. When an order seems stuck waiting on a provider, the agent-shaped move is the polling op the contract gives you (orders.shipment.update for cargo) or a note to the owner — not forged inbound traffic.
Worked example
mozaik.geliver — install (PII consent: buyer name/phone/address flow to Geliver) → owner sets cargo.geliver.api_token → run op sender-address via ext.cargo.sender (works while still disabled; records the sender profile into config) → enable → verify with the offers op (orders.cargo.offers, a quote — free to call, mutates nothing). After that, book proposes the actual shipment.