Spending limits your AI agents actually earn. — a working prototype: landing page + live console dashboard + REST API with a real trust score engine on top of SQLite.
No longer a mockup — you can register an agent, record its behavior (attestation), watch its spending ceiling recalculate in real time, then grant or revoke spending permission based on tier.
Note: agent trust/reputation is a crowded, fast-moving category with several teams exploring similar ideas. Kairune's specific focus is spend authorization — turning behavior into a concrete, revocable spending ceiling — not general-purpose reputation.
├── index.html / app / a / docs # product UI
├── assets/ # CSS, JS, logos in use
├── api/ + server.js + src/ # API + trust engine
├── token/metadata.json # $KAIRUNE metadata
├── virtuals/ # ACP provider (local)
├── package.json + vercel.json
└── .env.example
Every agent has a history of attestations (behavior events). The engine computes a score of 0..1000:
- Baseline 120 for a new agent (neutral).
- Positive events —
task_completed(+6),clean_payment(+8),peer_vouch(+14). - Negative events —
dispute(−40),chargeback(−70),anomaly_flag(−90). Penalties are amplified 1.15× (asymmetric: trust is hard to build, easy to lose). These require issuer attribution: a penalty is a claim about another party's conduct, so it must be signed and traceable to an issuer. Anonymous submissions may only report positive outcomes. - Volume bonus — the more clean activity, the more trusted (logarithmic, anti-spam).
- Recency decay — events decay to half their weight every 90 days.
The score maps to a tier: 0 UNRATED → 1 EMERGING → 2 ESTABLISHED → 3 TRUSTED → 4 PRIME. The tier determines the maximum spending ceiling that can be granted.
Attestations can be cryptographically signed so the data behind a score is provably authentic — not just whatever an anonymous caller posted.
- An issuer is a registered party (Admin registers it, gets a one-time API key).
- Issuers register Ed25519 public keys and sign each attestation over a canonical payload.
- The server verifies every signed submission and marks it
verifiedorunverified. - The trust engine weights verified attestations at
1.0and unverified ones atUNVERIFIED_WEIGHT_FACTOR(default0.25) — so unsigned data still counts, but less. - The existing unsigned flow keeps working unchanged (recorded as
unverified), so no integration breaks.
Signing uses Node's built-in crypto (no extra dependency). Revoked keys cause their attestations to be treated as unverified.
Replay protection: signed submissions must include an issued_at timestamp within a freshness window (REPLAY_MAX_AGE_SECONDS, default 300s, plus REPLAY_FUTURE_SKEW_SECONDS clock skew), and each signature may be used only once — a reused signature is rejected with 409.
Every approved spend is signed with the platform Ed25519 key at charge time — who paid, who was paid, how much, when. The signature is stored with the spend, and a public endpoint returns the full receipt: the signed fields, the canonical payload, the signature, and the public key.
GET /api/spends/:sid/receipt— public, no auth. Includesverified, computed on the spot.GET /api/platform-key— the current receipt-signing public key (pin it out-of-band).- Anyone can prove a charge happened without trusting any database: verify the signature against the pinned key.
- Spends recorded before receipts existed report
signed: false— never an error. - Idempotent replays return the same receipt (never re-signed). Key rotation is supported: each spend pins the
receipt_key_idthat signed it. - Signing is best-effort at charge time — a key misconfiguration never blocks a legitimate spend.
Configure a production key with RECEIPT_PRIVATE_KEY (PEM or 32-byte base64/base64url seed). Unset → an ephemeral in-memory key (dev/test), flagged ephemeral: true in the receipt and /api/platform-key.
SDK:
const receipt = await k.getReceipt(spendId) // { signed, verified, fields, canonical, signature, public_key, ... }
const key = await k.getPlatformKey() // { algorithm, purpose, public_key, ephemeral }Requires Node.js >= 18.
# 1. Install dependencies
npm ci
# 2. Copy the environment template
cp .env.example .env
# 3. Seed the database with demo data (optional but recommended)
npm run seed
# 4. Run
npm start- Landing page → http://localhost:3040
- Live console → http://localhost:3040/app
- Health check → http://localhost:3040/health
Reset & re-seed demo data any time:
npm run seed:resetFor development mode:
npm run dev| Variable | Default | Description |
|---|---|---|
PORT |
3040 |
HTTP port the server listens on |
HOST |
0.0.0.0 |
Bind address |
NODE_ENV |
development |
development | production |
RECEIPT_PRIVATE_KEY |
— | Ed25519 key for spend receipts (PEM or 32-byte base64 seed). Unset → ephemeral dev key |
.envis git-ignored. Never commit secrets.
| Method | Path | Description |
|---|---|---|
| GET | / |
Landing page |
| GET | /app |
Live console dashboard |
| GET | /docs |
API docs |
| GET | /a/:handle |
Public trust card |
| GET | /health |
Health-check JSON ({ status, uptime, ... }) |
| Method | Path | Description |
|---|---|---|
| GET | /api/meta |
Metadata (kinds, tiers, weights) |
| GET | /api/stats |
Global statistics |
| GET | /api/agents |
List agents (leaderboard) |
| POST | /api/agents |
Register a new agent |
| GET | /api/agents/:id |
Agent detail + score breakdown |
| PATCH | /api/agents/:id/status |
Suspend / activate an agent |
| DELETE | /api/agents/:id |
Delete an agent |
| GET | /api/agents/:id/attestations |
Attestation history |
| POST | /api/agents/:id/attestations |
Add attestation (triggers rescore) |
| GET | /api/agents/:id/permissions |
List permissions |
| POST | /api/agents/:id/permissions |
Grant permission (capped by tier) |
| POST | /api/permissions/:pid/revoke |
Revoke permission (instant) |
| GET | /api/permissions/:pid/spends |
Spend history (filterable) |
| GET | /api/agents/:id/spends |
Spend history across every permission |
| GET | /api/agents/:id/spend-summary |
Spend totals by permission / category / payee |
| GET | /api/spends/:sid/receipt |
Public signed receipt for a spend |
| GET | /api/platform-key |
Current receipt-signing public key |
| POST | /api/agents/:id/lock |
Owner-lock an agent (needs a wallet proof) |
| POST | /api/agents/:id/unlock |
Remove the owner lock (needs a wallet proof) |
Owner lock. By default the permission routes take no credentials, and a
permission id is public — so anyone who reads one can grant, spend, re-scope or
revoke against that agent. Locking an agent to its wallet closes that: prove
control once via /api/agents/:id/wallet-proof/challenge, then POST /api/agents/:id/lock. After that, every mutating permission route for the agent
requires a fresh EIP-191 proof in X-Owner-Proof: <nonce>:<signature> and
refuses with 401 without one. Proofs are single-use and bound to a single
agent, so a captured proof is not a standing key. Reads and
/spends/preview stay open so a payment rail can still get a go/no-go signal.
This is opt-in: unlocked agents behave exactly as they did before, and the agent
payload reports owner_locked so a payer can tell the difference.
Both spend-history endpoints accept since, until, payee and idempotency_key
filters plus limit/offset. until is exclusive so consecutive windows tile
without double-counting, and an unparseable date is a 400 rather than a filter
that silently matches everything. Summary totals cover the requested window, not
each grant's rolling ceiling window — use /api/permissions/:pid/budget for
remaining headroom.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/issuers |
Admin | Register an issuer (returns API key once) |
| GET | /api/issuers |
Admin | List issuers (no secrets) |
| POST | /api/issuers/:id/keys |
X-Issuer-Key |
Register an Ed25519 public key |
| DELETE | /api/issuers/:id/keys/:kid |
X-Issuer-Key |
Revoke a key |
A signed attestation adds issuer_id, issuer_key_id, signature (base64), and issued_at to the POST /api/agents/:id/attestations body, plus the X-Issuer-Key header.
Examples:
# Register an agent
curl -X POST localhost:3040/api/agents \
-H 'Content-Type: application/json' \
-d '{"handle":"voyager-08","wallet":"0xabc...","operator":"Helios"}'
# Record behavior → score recalculated automatically
curl -X POST localhost:3040/api/agents/<id>/attestations \
-H 'Content-Type: application/json' \
-d '{"kind":"clean_payment"}'
# Grant a spending permission
curl -X POST localhost:3040/api/agents/<id>/permissions \
-H 'Content-Type: application/json' \
-d '{"category":"compute","ceiling":100,"period":"day"}'npm testRuns unit tests (trust score engine) + integration tests (REST API, using an in-memory DB) via node:test — no extra test dependencies.
vercel env add TURSO_DATABASE_URL
vercel env add TURSO_AUTH_TOKEN
vercel --prodStatic assets on CDN; /api/* + /health as serverless. Live: kairune.online.
ACP provider bot (Virtuals jobs): see virtuals/SETUP.md — run locally, not on Vercel.
- Gzip, security headers, static caching
- Health-check + graceful shutdown
- Rate limiting on writes; soft
$KAIRUNEholder boost viaTOKEN_HOLDER_WALLETS - Optional
ADMIN_KEYfor DELETE moderation
Kairune is a working prototype — the console, REST API, and trust score engine are live and free to use, with no token required. $KAIRUNE is a live community token on Virtuals (Robinhood Chain); its on-chain utility is on the roadmap and not yet wired into the product. This is not financial advice or an investment offering — verify the contract address on the official channel (@usekairune) and do your own research before interacting with any token or granting agent permissions.