Know Your Agent
The Agent Passport is something a person reads: a handle, a track record, a link you click. KYA is its machine-checkable sibling, and the reader it is built for is not a human at all. It is a contract, a trading desk, or another agent that has to decide — before letting an autonomous counterparty touch anything, in one call, without a Sable account:
what caps does this agent run under, what policy governs it, was its hardware attested, and what is its verified history?
A profile page cannot be consumed by a risk check. A signed payload with a fixed shape and a pure evaluator can.
KYA is built and tested. It is not on the production deployment yet, so the
calls below describe a surface you cannot reach at api.buildsable.com today.
What it asserts
Only facts Sable can substantiate from its own records.
| Block | Contents |
|---|---|
| Subject | The passport handle, and account_fp — a one-way fingerprint that binds two handles to one accountable owner without publishing the raw account id. The owner's wallet rides along only when the passport already discloses it publicly. |
| Budget | The caps that actually bind the subject key: the tightest cap in the delegation chain, plus the whole chain as evidence; the window each resets over; whether a circuit breaker is configured; delegation depth; and whether the key is frozen or expired right now. |
| Governance | The attached policy id and the sha256 of the exact rules enforced — the same hash stamped on every receipt the key mints, so a policy edit is visible in both places — and the guardrail rule set id and hash when one is attached. |
| Attestation | Whether this agent's own recent receipts carried a verified attestation, and which measurements were pinned on them. |
| History | Verified run and receipt counts, computed by folding and re-checking the receipt hash chains. |
expires_at | Because a credential asserting live configuration has to go stale. Configuration changes; a signature does not. |
It is signed with the ordinary receipt signer, so it verifies through the
existing public POST /v1/receipts/verify — no new key, no new
verification machinery, no new endpoint for a counterparty to learn.
Proven: declared configuration and provable history, as recorded by this gateway — the caps and window binding the key, the policy and guardrail rule sets attached to it, whether its own recent receipts carried a verified attestation, and run and receipt counts obtained by recomputing the hash chains.
Not proven: that the agent is competent, honest, safe, or acting in good faith. It is not a trust score, a rating, or a ranking — there is no rating field, no ordering, and nothing that would invite one.
Treat expired, revoked or superseded as a refusal, never
as an unknown. The reference evaluator has no Unknown
outcome, which is the structural form of that rule.
Why the published cap is the tightest in the chain
A child key capped at $1000 under a parent capped at $5 can spend $5: spend enforcement checks every ancestor's cap against that ancestor's subtree. Publishing the subject key's own cap would therefore overstate the budget, and a counterparty asking does this agent have at least $500 available would be told yes about an agent that has five.
So the credential publishes the smallest cap amount binding the key, names the key and window carrying it, and publishes the whole chain beside it as evidence — because caps in different windows are not strictly comparable (a $5/day cap and a $100/total cap bound different things), and a derived number should not hide the thing it was derived from. Ties break on key id so the credential is deterministic.
spend_cap_micro_usd is serialized even when null, and that is
load-bearing: an omitted field reads as this credential does not say, while
an explicit null reads as this agent runs uncapped — which is a fact, and a
fact a counterparty must be able to refuse on.
Attestation, read off the receipts
The attestation block is read from this agent's own receipts, the ones that were signed at the time, capped at the 500 most recent. It is deliberately not taken from the gateway's live attestation posture: that posture describes the gateway now, not what served this agent then.
all_attested is true only when receipts were examined and every one of
them attested. An agent with no history is not “all attested” — it
is unproven, and the field says so by being false. response_bound is counted
separately, because attestation and per-response binding are different claims
and conflating them has bitten this codebase before.
Every credential carries this note inside the signed attestation block:
Each measurement below is the value that was pinned and matched on the receipt that carried it. Where the deployment pins the base-image measurement (
mrtd) rather than the workload measurement (rt_mr3), a match proves a genuine TDX enclave running the expected base image, not the specific workload build — and any GPU flag on that receipt is recorded, not proven.
See Privacy tiers.
History is recomputed, not counted
verified_runs folds each run's receipt hash chain and re-checks it:
chain(n) = sha256(chain(n-1) ‖ sha256(receipt(n))), sequence 1..=n with no
gap, final hash equal to the stored head. A row count is byte-identical for a
ledger edited underneath it, which is the single thing a hash chain exists to
detect.
runs_examined is published alongside it. When verified_runs is below it,
some chain did not recompute — visible rather than silently dropped.
receipts_total counts every stored receipt, chained or not, because an
untagged receipt belongs to no chain and cannot be chain-verified; counting it
separately and saying so is the honest form.
Three end states, and one uniform 404
- superseded — minting at a handle retires that handle's previous credential in the same transaction as the insert, so “the current credential at this handle” is one row, always, and a reader never sees two or none.
- revoked — the owner withdrew it.
- expired — it aged out.
The public endpoint answers a uniform 404 for a handle that never had a credential and for one whose current credential was revoked or superseded, so revocation is not a probe oracle.
An expired credential is still served, flagged valid: false with its
refusal spelled out. A stale credential that simply vanished would be
indistinguishable from one that never existed — and
indistinguishable-from-never-existed is exactly what “unknown”
means.
A 404 from this endpoint means no admission-eligible credential is published at this handle. Treat it as a refusal, not as an unknown.
What a counterparty checks
The public response returns the evaluator's own checks, in the order it makes them, so a counterparty can reimplement it without guessing:
- status — kind and version known, not revoked,
now < expires_at,now >= issued_at, key not frozen, key not expired. - identity —
payload.handleis the agent you meant. - budget —
payload.budget.spend_cap_micro_usdagainst your maximum and/or minimum (a null cap satisfies neither), circuit breaker present if you require one. - governance —
payload.governance.policy_sha256equals the rules hash you require; guardrail rule set id if you require one. - attestation —
payload.attestation.all_attested, and your required measurement amongpayload.attestation.measurements. - history —
verified_runs/receipts_totalagainst your minimums.
Refusals are fixed, machine-readable and content-free — revoked, expired,
not_yet_valid, frozen, key_expired, handle_mismatch, no_spend_cap,
spend_cap_above_maximum, spend_cap_below_minimum, no_circuit_breaker,
no_policy, policy_mismatch, no_guardrails, guardrail_mismatch,
not_attested, measurement_mismatch, insufficient_verified_runs,
insufficient_receipts, unsupported_credential. Branch on the codes; they
are stable.
Status refusals apply even to a counterparty with no requirements at all: being revoked, expired, not yet valid, frozen or bound to an expired key are status, not preferences, and no counterparty ever wants them.
Quickstart
# Over a passport handle this account owns, bound to one of its keys.
curl -s https://api.buildsable.com/v1/kya/credentials \
-H "Authorization: Bearer $SESSION" \
-H "Content-Type: application/json" \
-d '{
"handle": "triage-bot",
"key_id": "<api key id>",
"expires_in_hours": 24
}'
# Or bind through a mandate instead; its constrained sub-key becomes
# the subject: {"handle": "triage-bot", "mandate_id": "…"}
# {
# "id": "…", "handle": "triage-bot",
# "account_id": "…", <- your own response only; never in the payload
# "account_fp": "9f3c1a…",
# "credential": "eyJ2IjoxLCJraW5kIjoia3lhLWNyZWRlbnRpYWwiLC…",
# "signature": "0x…", "signer": "0x…",
# "payload": { … },
# "expires_at": "2026-09-14T12:00:00+00:00",
# "public_url": "https://api.buildsable.com/v1/kya/public/triage-bot",
# "supersedes": "any previous live credential at this handle is now marked superseded"
# }Lifetime
Default 24 hours, requestable between 1 hour and 30 days, and operator-configurable. Up to 200 live credentials per account.
Short lifetimes are the point. The credential asserts live configuration, and configuration changes under a signature that cannot. Re-mint on a cadence that matches how fast your caps and policies move.
Privacy posture
This credential is metadata about configuration and counts. It contains no prompt, completion or code content, and no customer data beyond the public handle, content-free figures, and a wallet the passport already publishes.
The raw account id is deliberately not in the signed payload: what a
counterparty needs is binding and correlation, which account_fp gives, and
publishing the raw id would put a value the Solana deposit rail uses as its
attribution memo onto a public endpoint for the first time. You still see your
own account id in your own responses. See
the privacy contract.
Endpoints
| Method | Path | Auth | Notes |
|---|---|---|---|
POST | /v1/kya/credentials | session (Member+) | Mint over a handle you own, bound to key_id or mandate_id. Supersedes the handle's previous credential in the same transaction. |
GET | /v1/kya/credentials | session | Your credentials, newest first, with their real state. |
DELETE | /v1/kya/credentials/{id} | session | Revoke. |
GET | /v1/kya/public/{handle} | public | The current credential, its payload, the checks and the refusals. Uniform 404 for unknown, revoked and superseded. |
Related
- Agent Passport — the human-readable sibling this speaks for.
- Key controls and Delegation — where the caps in the chain come from.
- Mandates — the other thing a credential can bind through.
- Verifiable receipts — the signer, the verifier, and the chains the history block recomputes.