Skip to content
39 changes: 38 additions & 1 deletion showcase/kairune-verifiable-trust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,49 @@ engine weights verified attestations fully and discounts unsigned ones (0.25×).
Backward compatible — existing unsigned submissions still work, recorded as
`unverified`.

**Since first submission — proven wallets and self-serve spend:**

- **Wallet proof (EIP-191).** An agent proves it controls the wallet it claims
by signing a server-issued challenge with `personal_sign` (chain `4663`,
domain-bound, 600s TTL). The private key never leaves the wallet, and proof
status is a public read — a payer deciding whether to release funds needs to
know whether the address it is about to pay was ever proven. Scope is
deliberately narrow: this proves wallet control, not trustworthiness.
- **Self-serve permissions.** Granting a budget, spending, scoping payees,
setting expiry and revoking no longer need a platform admin key. The trust
tier is the access control: below EMERGING (score 250) a grant is refused
with `409 tier_too_low`. An agent earns its budget rather than being handed
one, and the API is usable by agents that are not us.
- **Owner lock on spend authority.** Self-serve cut the admin key out of the
path, which also meant those routes accepted no credential at all — and a
permission id is public. An operator can now bind an agent to its wallet with
the same EIP-191 proof: once locked, every mutating permission route requires
a fresh single-use proof in `X-Owner-Proof` and refuses with `401` without
one. Reads and the spend dry-run stay open so a payment rail still gets a
go/no-go signal. It is opt-in, so an agent that never locks is still open —
making it mandatory would strand every agent registered before the feature.
- **A published SDK.** `@kairune/sdk@0.4.0` on npm, zero dependencies, native
`fetch`. Pass a `signOwnerMessage` signer once and `lockAgent()`,
`unlockAgent()` and `getOwnerLock()` are available; every mutating call that
comes back `401` because the agent turned out to be locked is signed and
retried exactly once. An unlocked agent pays nothing for this — no extra
round trip is made unless the server actually refuses.
- **A film of the refusal.** The 0:13 demo walks the whole arc, including the
part most demos leave out: the agent asks for a budget while UNRATED and is
turned down. It earns attestations, crosses 250, asks again, and gets a
ceiling it cannot exceed. No admin key appears anywhere in that path.

## Proof
- Self-serve film (0:13): https://kairune.online/assets/video/kairune-self-serve.mp4
- SDK on npm (`@kairune/sdk@0.4.0`): https://www.npmjs.com/package/@kairune/sdk
- Animated demo: `assets/kairune-verify-demo.mp4`
- Live console: https://kairune.online/app
- API meta (shows `signature_algorithm: ed25519`): https://kairune.online/api/meta
- API meta (`wallet_proof: eip191-personal-sign`, `signature_algorithm: ed25519`): https://kairune.online/api/meta
- ERC-8126 derived adapter (explicitly not compliant, `compliant: false`, `agentId: null`, ETV/MCV/SCV/WAV not implemented, WV partial via EIP-191): https://kairune.online/api/agents/voyager-07/erc8126 — also at https://kairune.online/api/erc8126/agents/voyager-07
- Example trust card: https://kairune.online/a/voyager-07
- API docs: https://kairune.online/docs
- Source: https://github.com/kairunedev/Kairune
- Wallet proof + self-serve PR: https://github.com/kairunedev/Kairune/pull/7

## EconomyOS primitives
ACP job (4 paid offerings on Robinhood Chain via Virtuals), Agent Token
Expand Down
47 changes: 36 additions & 11 deletions showcase/kairune-verifiable-trust/showcase.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"slug": "kairune-verifiable-trust",
"title": "Kairune — Verifiable Agent Trust Layer",
"tagline": "Every agent gets a verifiable, cryptographically-signed trust score — then scoped spend by tier.",
"description": "Kairune is the trust layer for AI agents that spend. It computes a deterministic trust score (0-1000) from an agent's behavior history and grants or revokes spending permission by tier. The showcased upgrade makes the score's inputs verifiable: issuers register Ed25519 keys and sign each attestation, the server verifies every signature, and the scoring engine weights verified attestations fully while discounting unsigned ones (0.25x). Exposed as ACP offerings (lookup score, register agent, record attestation, full report) on Robinhood Chain via Virtuals.",
"tagline": "Earn a budget with signed attestations, a proven wallet, and a spend ceiling it can't cross",
"description": "Kairune is a trust layer for AI agents that spend. It's self-serve end to end: an agent earns a trust score from signed attestations, proves wallet control via EIP-191, and is granted a spend budget based on its tier — no admin key required. Below EMERGING the grant is refused (409 tier_too_low); over-ceiling charges are blocked and every decision fires an HMAC-SHA256 signed webhook. An operator can lock an agent to its wallet: mutating routes then demand a fresh single-use X-Owner-Proof and return 401 without one, which @kairune/sdk@0.4.0 mints on the caller's behalf. Exposed as live APIs, paid ACP offerings on Robinhood Chain via Virtuals, and the SDK.",
"status": "live",
"topic": "commerce",
"topics": ["identity", "commerce", "acp"],
"topics": ["agents", "commerce", "security"],
"hidden": false,
"builder": {
"name": "Kairune",
Expand All @@ -16,16 +16,16 @@
"demo": "https://kairune.online/app",
"video": "https://x.com/usekairune/status/2075911860516528241",
"share": "https://x.com/usekairune/status/2075911860516528241",
"feedback": "https://github.com/Virtual-Protocol/acp-cli-demos/issues/new?title=Feedback%3A%20Kairune%20Verifiable%20Agent%20Trust%20Layer&body=Which%20feedback%20prompt%20fits%3F%0A%0A-%20Issuer%20identity%20should%20be%20verified%20on-chain%0A-%20Attestation%20kinds%20for%20agent-to-agent%20commerce%0A-%20Replay-protection%20window%20worth%20the%20friction%3F%0A%0ANotes%3A%0A"
"feedback": "https://github.com/Virtual-Protocol/acp-cli-demos/issues/new?title=Feedback%3A%20Kairune%20Verifiable%20Agent%20Trust%20Layer&body=Which%20feedback%20prompt%20fits%3F%0A%0A-%20Should%20issuer%20identities%20be%20verified%20on-chain%20rather%20than%20by%20API%20key%3F%0A-%20Is%20EMERGING%20(score%20250)%20the%20right%20floor%20before%20an%20agent%20may%20hold%20any%20budget%2C%20or%20should%20tier%200%20get%20a%20probationary%20micro-ceiling%3F%0A-%20Should%20a%20proven%20wallet%20(EIP-191)%20be%20required%20before%20an%20agent%20can%20receive%20a%20budget%2C%20not%20just%20recorded%20alongside%20it%3F%0A%0ANotes%3A%0A"
},
"primitives": ["acp", "token", "wallet"],
"visual": {
"kind": "animated demo",
"eyebrow": "live api + acp",
"title": "verifiable attestations",
"posterUrl": "https://raw.githubusercontent.com/Virtual-Protocol/acp-cli-demos/main/showcase/kairune-verifiable-trust/assets/poster.png",
"videoUrl": "https://raw.githubusercontent.com/Virtual-Protocol/acp-cli-demos/main/showcase/kairune-verifiable-trust/assets/kairune-verify-demo.mp4",
"videoLabel": "Watch the 0:06 demo on X"
"title": "refused at tier 0, then it earns the budget",
"posterUrl": "https://kairune.online/assets/video/kairune-self-serve.jpg",
"videoUrl": "https://kairune.online/assets/video/kairune-self-serve.mp4",
"videoLabel": "Watch the 0:13 demo on X"
},
"skills": [
{
Expand All @@ -47,6 +47,11 @@
"href": "https://github.com/Virtual-Protocol/acp-cli-demos/tree/main/showcase/kairune-verifiable-trust/skills/kairune-verify-agent-trust",
"kind": "skill"
},
{
"label": "Self-serve film (0:13) — grant refused at 409 tier_too_low, then earned",
"href": "https://kairune.online/assets/video/kairune-self-serve.mp4",
"kind": "video"
},
{
"label": "Animated demo (mp4)",
"href": "https://raw.githubusercontent.com/Virtual-Protocol/acp-cli-demos/main/showcase/kairune-verifiable-trust/assets/kairune-verify-demo.mp4",
Expand All @@ -58,15 +63,30 @@
"kind": "proof"
},
{
"label": "API meta (signature_algorithm: ed25519, unverified_weight_factor: 0.25)",
"label": "API meta — wallet_proof: eip191-personal-sign, signature_algorithm: ed25519, unverified_weight_factor: 0.25",
"href": "https://kairune.online/api/meta",
"kind": "proof"
},
{
"label": "ERC-8126 derived adapter (not compliant) — 0-100 risk + per-type breakdown, EIP-191 WV partial",
"href": "https://kairune.online/api/agents/voyager-07/erc8126",
"kind": "proof"
},
{
"label": "Example public trust card",
"href": "https://kairune.online/a/voyager-07",
"kind": "proof"
},
{
"label": "API docs — wallet proof, self-serve permissions, spend enforcement, signed webhooks",
"href": "https://kairune.online/docs",
"kind": "docs"
},
{
"label": "SDK on npm — @kairune/sdk@0.4.0, zero dependencies, lockAgent() plus auto-proof retry on 401",
"href": "https://www.npmjs.com/package/@kairune/sdk",
"kind": "docs"
},
{
"label": "Public repo",
"href": "https://github.com/kairunedev/Kairune",
Expand All @@ -76,11 +96,16 @@
"label": "Verifiable attestations PR",
"href": "https://github.com/kairunedev/Kairune/pull/1",
"kind": "proof"
},
{
"label": "Wallet proof + self-serve permissions PR — EIP-191 challenge, tier gate replaces the admin key",
"href": "https://github.com/kairunedev/Kairune/pull/7",
"kind": "proof"
}
],
"feedbackPrompts": [
"Should issuer identities be verified on-chain rather than by API key?",
"What attestation kinds matter most for agent-to-agent commerce?",
"Would a replay-protection window on signed submissions be worth the added friction?"
"Is EMERGING (score 250) the right floor before an agent may hold any budget, or should tier 0 get a probationary micro-ceiling?",
"Should a proven wallet (EIP-191) be required before an agent can receive a budget, not just recorded alongside it?"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ clear the bar.
- Prefer agents whose score is backed by **verified** attestations; treat a score
built mostly on `unverified` attestations as weaker (Kairune already discounts
unverified data at 0.25×).
- Read `breakdown.boundBy`. It names which rule actually decided the score:
- `additive` — the score is what the agent's history earned.
- `misconduct-ratio` — disputes and chargebacks are a large enough share of the
record that the score was reduced proportionally, and
`breakdown.integrityFactor` is how much survived (1 = a spotless record,
0.6 = roughly 40% withheld).
- `corroboration-ceiling` — the agent earned more than it can prove. Nobody
independent has attested to it, so its score is capped regardless of volume.
Compare `breakdown.earnedScore` against `breakdown.corroborationCeiling` to
see the gap. A large gap means a long history that no third party will vouch
for; treat that as weaker than a shorter, corroborated one.
A tier alone will not tell you any of this.
- Volume is not evidence. An agent can post attestations about itself, so with
zero verified issuers a score cannot exceed 600 and cannot present as TRUSTED
or PRIME no matter how many rows exist. Each independent verified issuer lifts
that cap by 100. Read `distinct_issuers` from the trust-sources endpoint before
reading a high score as a track record.
- Re-check just before granting; scores change as behavior is attested.
- Never fabricate or infer a score that was not returned by Kairune.

Expand All @@ -66,6 +83,10 @@ Stop and defer to the caller (return `review` or `deny`) when:
- The intended amount exceeds the tier's `suggested_daily_ceiling`.
- The score is driven overwhelmingly by `unverified` attestations and the caller
required verified backing.
- `breakdown.integrityFactor` is well below 1 and the caller has not explicitly
accepted counterparties with a dispute history.
- `breakdown.boundBy` is `corroboration-ceiling` and the caller expected the
agent's standing to be independently attested.
- Kairune is unreachable or returns an error — never assume a passing score on
failure.

Expand All @@ -77,6 +98,9 @@ Reads are free and unauthenticated:
# Score, tier, breakdown (verified/unverified counts), suggested ceiling
curl -s https://kairune.online/api/agents/<handle_or_id>

# Who vouched for this agent, and the score ceiling that breadth earns
curl -s https://kairune.online/api/agents/<handle_or_id>/trust-sources

# Scoring metadata (tier thresholds, unverified_weight_factor, algorithm)
curl -s https://kairune.online/api/meta

Expand All @@ -85,8 +109,13 @@ curl -s https://kairune.online/api/meta
```

Relevant fields on the agent detail: `score`, `tier`, `label`,
`suggested_daily_ceiling`, `status`, and `breakdown.verifiedCount` /
`breakdown.unverifiedCount`.
`suggested_daily_ceiling`, `status`, `breakdown.verifiedCount` /
`breakdown.unverifiedCount`, and `breakdown.boundBy` with its companions
`breakdown.earnedScore` / `breakdown.corroborationCeiling`.

On trust-sources: `distinct_issuers`, `score_ceiling`,
`score_ceiling_next_issuer`, and `issuers_to_remove_ceiling` (how many
independent issuers it would take before the cap stops applying at all).

## ACP Command Pattern

Expand All @@ -107,10 +136,12 @@ Provider agent: https://app.virtuals.io/virtuals/100623
2. Resolve the counterparty by handle or id (live API mode) or open the matching
ACP offering (ACP mode).
3. Fetch the agent detail; confirm `status` is `active`.
4. Read `score`, `tier`, `label`, `suggested_daily_ceiling`, and the
verified/unverified attestation counts.
4. Read `score`, `tier`, `label`, `suggested_daily_ceiling`, the
verified/unverified attestation counts, and `breakdown.boundBy`.
5. Apply the caller's policy: tier ≥ minimum, amount ≤ `suggested_daily_ceiling`,
and verified-backing requirement if set.
and verified-backing requirement if set. When `boundBy` is
`corroboration-ceiling`, the history is self-reported — weigh it accordingly
rather than reading the raw score as earned standing.
6. Return a decision: `allow`, `review`, or `deny`, with the capped amount and a
one-line reason.
7. If `allow`, grant only up to the capped amount and re-check before any future
Expand All @@ -136,6 +167,8 @@ Provider agent: https://app.virtuals.io/virtuals/100623
- [ ] Tier ≥ caller's minimum acceptable tier.
- [ ] Intended amount ≤ `suggested_daily_ceiling` (else capped).
- [ ] Verified-backing requirement satisfied if the caller set one.
- [ ] `breakdown.boundBy` read, and a `corroboration-ceiling` result treated as
self-reported history rather than earned standing.
- [ ] Decision, capped amount, and reason returned.
- [ ] No secrets in output.

Expand All @@ -146,20 +179,24 @@ Return a single JSON object:
```json
{
"handle": "voyager-07",
"score": 512,
"score": 600,
"tier": 2,
"label": "ESTABLISHED",
"suggested_daily_ceiling": 150,
"verified_count": 18,
"unverified_count": 3,
"decision": "allow",
"granted_ceiling": 100,
"reason": "Tier 2 >= min tier 2; amount 100 <= ceiling 150; mostly verified."
"verified_count": 0,
"unverified_count": 2328,
"distinct_issuers": 0,
"bound_by": "corroboration-ceiling",
"decision": "review",
"granted_ceiling": 0,
"reason": "Tier 2 >= min tier 2, but 0 of 2328 attestations are signed and no independent issuer has attested, so the score is capped rather than earned."
}
```

`decision` is one of `allow`, `review`, `deny`. `granted_ceiling` is the lower of
the caller's intended amount and `suggested_daily_ceiling`.
the caller's intended amount and `suggested_daily_ceiling`, and is `0` when the
decision is not `allow`. `bound_by` carries `breakdown.boundBy` through so the
caller can see which rule decided the score.

## References

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
**Request:** grant `voyager-07` a $100/day compute permission; require tier ≥ 2
and mostly-verified backing; cap at Kairune's suggested ceiling.

This example is deliberately one that does **not** end in a clean `allow`. The
agent clears the tier gate but fails the caller's verified-backing requirement,
which is the case worth showing: the tier alone is not the whole policy.

**Live API call (free, public):**

```
Expand All @@ -16,32 +20,77 @@ GET https://kairune.online/api/agents/voyager-07
"agent": {
"handle": "voyager-07",
"status": "active",
"score": 512,
"score": 600,
"tier": 2,
"label": "ESTABLISHED",
"suggested_daily_ceiling": 150,
"breakdown": { "verifiedCount": 18, "unverifiedCount": 3 }
"breakdown": {
"verifiedCount": 0,
"unverifiedCount": 2328,
"integrityFactor": 0.668,
"earnedScore": 668,
"corroborationCeiling": 600,
"corroborationCapped": true,
"boundBy": "corroboration-ceiling"
}
}
}
```

And the companion read, which explains *why* the score stops where it does:

```
GET https://kairune.online/api/agents/voyager-07/trust-sources
```

```json
{
"verified_count": 0,
"unverified_count": 2328,
"distinct_issuers": 0,
"score_ceiling": 600,
"score_ceiling_next_issuer": 700,
"issuers_to_remove_ceiling": 4
}
```

**Decision:**

```json
{
"handle": "voyager-07",
"score": 512,
"score": 600,
"tier": 2,
"label": "ESTABLISHED",
"suggested_daily_ceiling": 150,
"verified_count": 18,
"unverified_count": 3,
"decision": "allow",
"granted_ceiling": 100,
"reason": "Tier 2 >= min tier 2; amount 100 <= ceiling 150; 18 verified vs 3 unverified."
"verified_count": 0,
"unverified_count": 2328,
"distinct_issuers": 0,
"bound_by": "corroboration-ceiling",
"decision": "review",
"granted_ceiling": 0,
"reason": "Tier 2 >= min tier 2 and amount 100 <= ceiling 150, but 0 of 2328 attestations are signed and no independent issuer has attested, so the score is capped at 600 rather than earned. Escalate rather than grant."
}
```

**Notes:** No credentials were used (reads are public). No API keys, signatures,
or private key material appear in the request or response. Values above are
illustrative of the live shape; exact numbers change as new attestations land.
or private key material appear in the request or response.

The numbers above were read from the live endpoint, so they show two things an
invented example would have hidden.

First, two separate rules are pushing this score down and only the stricter one
is reported. `integrityFactor: 0.668` means roughly a third was withheld because
disputes and chargebacks make up a measurable share of the record — that alone
put the agent at 668. But `distinct_issuers` is 0, so the corroboration ceiling
caps it at 600 and `boundBy` names that instead. `earnedScore` is where the
history would have landed; `score` is what it can actually prove.

Second, 2328 attestations produced no more standing than a few hundred would
have. Volume is not evidence when the subject can write its own record. This is
the case the skill exists to catch: a big number that looks like a track record
and isn't.

Re-read the endpoint before relying on any specific value — scores move as
attestations land, and this agent's will move the moment someone independent
vouches for it.
Loading