KLEOS
Testnet

Give your agent a track record.

Add Kleos to an existing agent runtime in minutes.

Kleos does not install anything inside your agent. Your agent's runtime learns to speak Kleos: it gets a key, the owner authorizes that key onchain, and every paid action the runtime makes becomes a stamp.

What Kleos is

A portable reputation layer for AI agents. An agent is an ERC-8004 identity on Monad, owned by a human's passkey. Its runtime (the software that actually runs it) is registered as the agent's wallet. Every x402 payment the runtime makes produces a signed receipt whose hash is anchored on Monad next to the settlement. Those anchors are the agent's track record: verified actions, USDC settled, days active, tools and platforms used. Anyone can recompute them from chain. No platform owns them.

The model
  1. Youa passkey
    owns
  2. Agentan ERC-8004 identity with a track record
    runs through
  3. Runtimethe software that runs the agent and spends its wallet
    pays
  4. Tool / platforman x402 service the agent buys from
    settles
  5. Stampa portable record: receipt signed, payment anchored, anyone can verify
    End
  • A stamp exists only when a payment settled. It cannot be faked without paying.
  • The receipt body (the answer) is encrypted to the owner's passkey-derived key. Kleos cannot read it.
  • Authorization is ERC-8004's own agentWallet. Revoking is standard; old stamps stay verifiable.
  • There is no score. There are numbers anyone can check.
  • Tools can act on those numbers. With pay later, an agent whose record meets a tool's rule is served first and invoiced onchain; unpaid invoices stay visible.

Add a track record in 10 minutes

1. Register an agent at kleos.finance/me with a passkey. That mints the ERC-8004 identity and writes the owner's encryption key next to it.

2. In the terminal where your agent runs, pair the runtime:

npx kleos-sdk pair <agentId>
# prints a pairing code, valid 4 minutes

Paste the code in Kleos → your agent → Connect runtime. The owner submits ERC-8004's setAgentWallet; the registry verifies the runtime's signature itself.

3. In your runtime, stamp every paid call:

import { Stamper, payingFetch, callAndStamp, loadOrCreateRuntimeKey, networks } from "kleos-sdk";

const account = loadOrCreateRuntimeKey();            // ~/.kleos/runtime.json
const stamper = new Stamper({ agentId: 2233n, account });

const { call, stamp } = await callAndStamp({
  fetch: payingFetch("monad-testnet", account),      // pays x402 402s with the runtime key
  url: "https://example.com/paid-tool",
  tool: "example/paid-tool",
  platform: "example",
  amount: 10_000n,                                    // 0.01 USDC, atomic units
  asset: networks["monad-testnet"].usdc,
  stamper,
});

// stamp.receiptHash is anchored on Monad; stamp.receipt is the signed public part.
// If the payment did not settle, stamp is null and nothing is recorded.

Already have an x402-paying fetch? Skip payingFetch and call stamper.stamp() yourself after each settled response, passing the settlement transaction from the PAYMENT-RESPONSE header.

What a stamp is

Two halves. The public receipt, whose keccak256 is the stamp's id. Version 1, signed by the runtime alone:

{
  "v": 1,
  "network": "monad-testnet",
  "agentId": "2233",
  "wallet": "0x3539…8b68",          // runtime signer, must equal ERC-8004 agentWallet
  "tool": "kleos-demo/monad-block",
  "platform": "kleos-demo",
  "amount": "10000",                // atomic units
  "asset": "0x534b…43A3",           // USDC
  "settlementTx": "0x2a4b…31b72",   // the x402 settlement on Monad
  "answerHash": "0xb79b…cac1",      // keccak256(salt || answer); the salt is in the sealed body
  "timestamp": 1790001139,
  "signature": "0x…"                // EIP-191 by wallet over the receipt hash
}

Version 2 is the same receipt with the tool's side added (see Tool countersignature):

{
  "v": 2,
  …                                 // every version 1 field
  "payTo": "0x3082…fE34",           // where the payment went: the tool
  "paymentNonce": "0x…",            // the x402 payment's EIP-3009 nonce, seen in the settlement
  "answerCommit": "0x…",            // the tool's salted commitment to the answer it served
  "toolSignature": "0x…"            // EIP-191 by payTo over the countersign digest
}

And the private body (request, answer, a random 32-byte salt, metadata), sealed to the owner's per-agent public key with ECIES over secp256k1 (ephemeral ECDH → HKDF-SHA256 → AES-256-GCM). The store only ever holds ciphertext. The salt is why the public answerHash reveals nothing: without it, a short or predictable answer could be found by hashing candidates.

Onchain, KleosStamps.stamp() records the receipt hash, settlement tx, tool, platform, amount and signer, and reverts unless msg.sender is the agent's agentWallet and the settlement is non-zero.

Why not ERC-8004's Reputation Registry?

ERC-8004 answers "who is this agent". Its Reputation Registry lets a client leave feedback about an agent that served it, and it explicitly rejects feedback from the agent's own owner or operators ("prevent self-feedback"). A Kleos receipt is produced by the agent's own runtime, about the agent's own purchase. Under the standard that is self-feedback, and it reverts.

So Kleos keeps the identity in ERC-8004 and adds one contract for what the standard does not model: a settled-payment-backed execution receipt. KleosStamps stores structured fields (settlement transaction, amount, tool, platform, signer) instead of a score or free text, requires a non-zero settlement, and only accepts the wallet the registry lists as the agent's. The stamp is not the agent saying "I did this"; it is a pointer to a payment anyone can see onchain.

The two compose. A tool that was paid can still leave standard ERC-8004 feedback about the agent ("paid, answered") from its own address. That is a natural next step for OnchainRouter and other platforms.

Tool countersignature

A plain stamp is signed by the buyer only. When the tool countersigns, the receipt becomes two-sided: the runtime says "I paid", the tool says "I was paid for this call, and this is the answer I served". Such receipts are version 2.

The tool signs (EIP-191) the payer, payTo, asset, amount, the x402 payment's EIP-3009 nonce, the tool id and a salted commitment to its answer, and returns it in the X-Kleos-Countersign response header. No registry of tools is needed: a countersignature counts only if it was signed by the address that received the payment, and only if the settlement transaction used that nonce (AuthorizationUsed) and moved that amount from the payer to payTo(Transfer). It cannot be reused for another payment.

For an x402 tool it is one call in the handler:

import { countersignPaidCall } from "kleos-sdk";

const answer = JSON.stringify(result);
const res = new Response(answer, { headers: { "content-type": "application/json" } });
// account = the key behind payTo
const cs = await countersignPaidCall({ account, headers: req.headers, tool: "my-tool/v1", answer });
if (cs) res.headers.set(cs.name, cs.value);
return res;

On the runtime side nothing changes: callAndStamp reads the header and writes a v2 receipt when the countersignature matches the call (payer, amount, asset, tool and answer). The tool's salt goes into the sealed body, so the owner can check the tool committed to the exact answer they paid for. Stamps made before countersigning stay valid; for them the check is "not applicable".

Pay later and Kleos Engine v0

A track record is worth something when it changes how a tool treats an agent. With pay later, a tool serves an agent before it pays, because the agent's record says it pays. The credit is onchain too: an invoice on KleosCredit, opened with the agent's consent and closed by the stamp of its payment together with the tool's signed acknowledgement. Try it runs the whole flow on testnet.

  1. The agent calls the tool without paying. The tool answers 402 with its credit terms: amount, due time and its rule.
  2. The agent's runtime signs consent to be invoiced on those terms (EIP-712) and sends it in X-Kleos-Credit.
  3. Kleos Engine v0 reads the agent's record from Monad and applies the tool's rule.
  4. If the record qualifies, the tool opens the invoice on KleosCredit and serves the answer right away, with the invoice in X-Kleos-Invoice.
  5. The agent pays the invoice over x402. The payment is stamped and countersigned like any other, and the tool's response carries its signed "I was paid for this invoice" (X-Kleos-Paid).
  6. The stamp and that acknowledgement close the invoice on KleosCredit. The record gains one more verified action and a paid invoice.

Kleos Engine v0

The engine turns the record into a decision. In v0 that decision is a rule the tool sets, applied to facts read from chain: how many verified actions the agent has, and how many of its invoices are overdue. Kleos supplies the facts; the bar is the tool's. An agent without a record asking the same tool the same thing is declined and pays first, like any unknown customer.

import { readAgentRecord, decideCredit } from "kleos-sdk";

const record = await readAgentRecord(publicClient, "monad-testnet", 2233n);
// { verifiedActions: 32, invoices: { total: 4, open: 0, overdue: 0, paidLate: 0 } }

const { granted, reason } = decideCredit(record, { minVerifiedActions: 20, maxOverdueInvoices: 0 });
// granted: true, reason: "32 verified actions, no unpaid invoices"

Next versions replace the fixed rule with a score from the same record: how many different tools an agent has paid, whether invoices were paid on time, how far back the record goes, and self-dealing patterns such as payments that always go to wallets the agent's owner controls.

Tool side

grantCredit runs the engine, checks the request matches your terms and opens the invoice from your key. Declined, it opens nothing.

import { grantCredit, decodeCreditRequest, encodeCredit, CREDIT_HEADER, INVOICE_HEADER } from "kleos-sdk";

const terms = {
  v: 1, network: "monad-testnet", creditor: account.address, tool: "my-tool/v1",
  amount: "10000", asset: USDC, dueInSeconds: 300,
  rule: { minVerifiedActions: 20, maxOverdueInvoices: 0 },
};

const request = decodeCreditRequest(req.headers.get(CREDIT_HEADER));
if (!request) return Response.json({ kleosCredit: terms }, { status: 402 });

const payUrl = `https://my-tool.xyz/invoice/${request.invoiceId}`;   // an x402 route for the amount
const { decision, invoice } = await grantCredit({ account, network: "monad-testnet", terms, request, payUrl });
if (!invoice) return Response.json({ kleosCredit: terms, declined: decision.reason }, { status: 402 });

return new Response(answer, { headers: { [INVOICE_HEADER]: encodeCredit(invoice) } });

On the invoice's x402 route (payUrl), acknowledge the payment. KleosCredit closes an invoice only with this signature from its creditor, and x402 only delivers the response once the payment settled:

import { acknowledgePayment } from "kleos-sdk";

const ack = await acknowledgePayment({ account, network: "monad-testnet", invoiceId, amount: invoice.amount });
res.headers.set(ack.name, ack.value);   // X-Kleos-Paid

Agent side

import { requestCredit, payInvoice, encodeCredit, decodeInvoiceNotice, payingFetch, CREDIT_HEADER, INVOICE_HEADER } from "kleos-sdk";

const { kleosCredit: terms } = await (await fetch(url)).json();          // the 402 with terms
const request = await requestCredit({ account, agentId: 2233n, terms });  // signed consent

const res = await fetch(url, { headers: { [CREDIT_HEADER]: encodeCredit(request) } });
const answer = await res.text();                                          // served before payment
const invoice = decodeInvoiceNotice(res.headers.get(INVOICE_HEADER));

// Later, before invoice.dueAt: pay, stamp, close (with the tool's X-Kleos-Paid acknowledgement).
await payInvoice({
  fetch: payingFetch("monad-testnet", account), invoice, stamper, account,
  network: "monad-testnet", tool: terms.tool, platform: "my-tool", asset: terms.asset,
});

What KleosCredit enforces

  • An invoice opens only with a consent signed by the agent's current agentWallet, an EOA or an ERC-1271 contract wallet (checked with OpenZeppelin's SignatureChecker). A tool cannot invoice an agent on its own.
  • The due time must be in the future, and an invoice id can be used once.
  • pay(invoiceId, receiptHash, creditorSig) closes it only with both halves of the payment: a KleosStamps stamp of the same agent, for at least the invoice amount, anchored after the invoice opened; and the creditor's EIP-712 PaymentReceived(invoiceId, amount). An agent cannot close an invoice with a payment that went somewhere else. Each stamp pays one invoice.
  • Anyone may call pay: the signatures are the proof, not the caller.

On the profile an invoice shows as Open until it is due and, past due and unpaid, as Unpaid. A paid one appears as the stamp that closed it, marked as paid later (and whether that was after the due date). Unpaid invoices stay visible to every tool that reads the record, and they fail a rule like the one above.

Spending rules with Privy

A runtime spends money on its owner's behalf, so it needs limits it cannot lift itself. A runtime can be a Privy server wallet: privyAccount makes it a standard viem account, so stamping and x402 payments work unchanged, and Privy checks every signature against the wallet's policy before it signs. Anything the policy does not allow is refused.

import { Stamper, privyAccount, secretFromPkcs8 } from "kleos-sdk";

const account = privyAccount({ appId, appSecret, walletId, address, signerKey: secretFromPkcs8(signerKey) });
const stamper = new Stamper({ agentId: 2254n, account });   // every signature goes through Privy
  • The owner's passkey owns the rules. The wallet and its policy are owned by a P-256 key derived from the owner's passkey (never stored). The server signs as an additional signer held to the policy: it can pay and stamp within the rules, not change them.
  • Changing a rule takes Touch ID. The owner edits it on the agent's profile; the browser signs the Privy request with the passkey-derived key (kleos-sdk/privy-auth) and the server only relays it.
  • The rules are public. Agent #2254's profile shows them, read live from Privy (GET /api/agents/:id/rules).

Verifying a record

The profile page's "Verify this profile" runs in your browser against Monad. For each stamp:

  1. Recompute the receipt hash from the public fields (fixed key order, no whitespace).
  2. Recover the EIP-191 signer and check it equals the receipt's wallet.
  3. Read KleosStamps.getStamp(hash): agent id, settlement, amount and wallet must match.
  4. Fetch the settlement transaction receipt: it must have succeeded and touched the asset contract.
  5. Version 2 only: recover the tool's countersignature, check it is from payTo, and find its nonce and transfer in the settlement.

Kleos serves the receipts, but none of these checks trusts it: each one reads Monad. A receipt Kleos altered or invented fails them. Kleos does not decide whether a record is valid.

import { verifyReceiptSignature, receiptHash } from "kleos-sdk";

const ok = await verifyReceiptSignature(stamp.receipt);   // signature by wallet
const hash = receiptHash(publicPart);                     // compare with the anchor

Package API

kleos pair <agentId>
CLI. Creates the runtime key if needed and prints a pairing code: an EIP-712 AgentWalletSet signature for the agent's current owner, valid 4 minutes.
kleos whoami
CLI. Prints the runtime address and key path.
loadOrCreateRuntimeKey(key?)
The runtime's account: an explicit key or KLEOS_RUNTIME_KEY (no file written), else ~/.kleos/runtime.json, created once.
new Stamper({ agentId, account, network?, kleosUrl?, ownerPublicKey? })
One per agent. Reads the owner's encryption key from ERC-8004 metadata when not given.
stamper.stamp({ tool, platform, amount, asset, settlementTx, answer, request?, meta?, countersign? })
Signs, seals, stores, anchors. With the tool's countersign it writes a version 2 receipt. Returns { receiptHash, anchorTx, receipt }.
stamper.isPaired()
True while this runtime is the agent's agentWallet.
payingFetch(network, account, { maxPerCall? })
A fetch that pays x402 402s on Monad with the runtime key. Only this network's USDC, capped per call (default 0.05).
callAndStamp({ fetch, url, tool, platform, amount, asset, stamper })
Call a paid tool and stamp it, countersigned when the tool sends one. No settlement, no stamp.
readCountersign(response)
The tool's countersignature from a paid response, or null.
countersignPaidCall({ account, headers, tool, answer })
Tool side. Countersigns a paid call; returns the header to set, or null when the request carried no payment.
readAgentRecord(publicClient, network, agentId)
Verified actions and invoice history (open, overdue, paid late), read from chain.
decideCredit(record, rule)
Kleos Engine v0: the tool's rule applied to a record. Returns { granted, reason }.
grantCredit({ account, network, terms, request, payUrl })
Tool side. Runs the engine and, if granted, opens the invoice on KleosCredit.
requestCredit({ account, agentId, terms })
Agent side. Signs consent to be invoiced on the tool's terms (EIP-712).
privyAccount({ appId, appSecret, walletId, address, signerKey? })
A Privy server wallet as the runtime account. Privy checks every signature against the wallet's policy; with signerKey it signs as a signer held to that policy.
authorizationSignature(key, request) · privyPublicKey(key)
From kleos-sdk/privy-auth (browser-safe): sign a Privy request with a P-256 key, e.g. one derived from the owner's passkey, and get the public key Privy registers as owner.
acknowledgePayment({ account, network, invoiceId, amount })
Tool side. Signs "I was paid for this invoice" for the paid invoice response; KleosCredit needs it to close the invoice.
payInvoice({ fetch, invoice, stamper, account, network, tool, platform, asset })
Agent side. Pays the invoice over x402, stamps it, closes it on KleosCredit with the tool's acknowledgement.
verifyReceiptSignature(receipt) · receiptHash(pub) · answerHash(answer, salt)
Pure helpers for verifiers.
seal(bytes, publicKey) · open(sealed, privateKey)
The receipt-body encryption, for runtimes and owners.

HTTP API

For applications that want to read an agent's record without a wallet library. Public, no key, CORS open, 15-second cache. Every number is computed from chain; the verify endpoint runs the same five checks the profile runs in the browser.

GET /api/agents?withRecord=1&limit=50&offset=0
Every ERC-8004 agent on the network with its Kleos stamp count. withRecord=1 keeps only agents with at least one stamp.
GET /api/agents/:id
Identity, runtime, encryption key, track record (verifiedActions, usdcSettled, daysActive, tools, platforms), invoices from KleosCredit (open, overdue, paid, paidLate and the latest ten) and the latest ten public receipts.
GET /api/agents/:id/stamps?limit=50&offset=0
Public receipts, newest first: tool, platform, amount, asset, settlementTx, answerHash, timestamp, signer, signature, receiptHash. Bodies are never served.
GET /api/verify/:receiptHash?agentId=
Recomputes the hash, recovers the signer, reads the anchor from KleosStamps and fetches the settlement and, for version 2, checks the tool's countersignature. Returns valid plus the five checks.
GET /api/agents/:id/rules
For an agent whose runtime is a Privy server wallet: the spending rules Privy enforces, read live, and whether the owner's passkey owns them.
curl https://kleos.finance/api/agents/2233
# { "agent": {...}, "trackRecord": { "verifiedActions": 79, ... }, "invoices": { "total": 19, "overdue": 0, ... }, "latestStamps": [...] }

curl "https://kleos.finance/api/verify/0xa238…ed6f?agentId=2233"
# { "valid": true, "checks": [ { "label": "receiptHashRecomputed", "ok": true }, ... ] }

Kleos publishes facts; what they are worth is the caller's decision. To serve agents first and bill them after, see Pay later and Kleos Engine v0: the same facts, read from chain by readAgentRecord, with your rule applied by decideCredit.

Architecture

  1. Human
    Passkey
    salt 1
    owner account · secp256k1 EOA · signs registry transactions
    salt 2
    encryption root · HKDF(index) · one keypair per agent
    one Touch ID, both keys, nothing stored
  2. Monad
    ERC-8004 Identity Registry
    token
    the agent · owner = the human's account
    metadata
    kleos.encryptionKey · kleos.keyIndex
    agentWallet
    the runtime's key · setAgentWallet / unsetAgentWallet
    pairing: the runtime signs, the owner submits, the registry verifies
  3. Runtime
    The software that runs the agent
    key
    its own · paired once · revocable
    any
    an x402-paying runtime with the kleos-sdk package
    privy
    or a Privy server wallet: every signature checked against rules only the owner's passkey can change
    tool call → x402 payment → settlement on Monad
  4. Tool
    The x402 service the agent pays
    countersign
    signs "I was paid for this call" · X-Kleos-Countersign · only payTo counts
    pay later
    serves first when Kleos Engine v0 grants credit · opens the invoice
    receipt, signed by both sides
  5. Kleos
    Stamp and invoice
    receipt
    signed by the runtime, countersigned by the tool (v2) · public fields, fixed order
    body
    sealed to the owner · ECIES · the store holds ciphertext only
    anchor
    KleosStamps.stamp() · only the agentWallet · settlement required
    invoice
    KleosCredit · opened with the agent's consent · closed by a stamp and the tool's signed "paid"
    read by anyone, from chain
  6. Anyone
    Track record
    explore
    the whole registry and stamp counts, from chain events via Envio HyperSync
    profile
    verified actions · USDC settled · days · tools · invoices
    verify
    recompute hash · recover signer · read anchor · fetch settlement · check the countersignature, in the browser
    decide
    Kleos Engine v0 · a tool's rule over stamps and invoices

Two things never exist: a Kleos-held key, and a stamp without a payment. The store (receipt bodies) is untrusted; the registry and KleosStamps are the source of truth.

Addresses (Monad Testnet)

ERC-8004 Identity Registry
0x8004A818BFB912233c491871b3d84c89A494BD9e
ERC-8004 Reputation Registry
0x8004B663056A597Dffe9eCcC1965A193B7388713
KleosStamps
0x1192f1EFE2cd543C24417254B7ff488Ddb6C0F04
KleosCredit
0x5D9680B8Fdf7DC3a543A2b539d410a3EFCcb9bEE
USDC
0x534b2f3A21130d7a60830c2Df862319e593943A3
x402 facilitator
https://x402-facilitator.molandak.org

Chain id 10143. Source: github.com/web3xDev/kleos. Package: npm install kleos-sdk.