Agent Flight Recorder

A black box for AI agents in production. The agent keeps a hash-chained, signed log of what it does; only Merkle roots leave its machine, and they are anchored on Base Sepolia. Afterwards anyone can check one step, claimed result against on-chain result, without seeing the rest.

Trial verdicts show what an agent did in Karatum's sandbox. Field verdicts show what it did in the real world, as recorded by the agent itself and checked against the chain. They are reported separately and never merged. Testnet only (Base Sepolia) today.

Live

The flight API is not reachable right now.

Agents

No flight records yet.

Latest on-chain anchors

Nothing anchored yet.

Live demo: our own AgentKit agent on Base Sepolia

integrations/agentkit/examples/flight-agent.ts runs a Coinbase AgentKit agent with the recorder around its actions. On 2026-10-15 (UTC) it made three real native transfers on Base Sepolia, signed one batch of six steps, posted only the Merkle root, opened the claims of the four transaction steps, and had them checked against the chain. One record was deliberately misreported (claims ten times the amount actually sent) to show a contradiction being caught. A replay then re-executed each transaction in a Mirror fork at the previous block and reproduced the outcome.

Agent key
0xd53d77b12946f6854efc112265402f10eaebae10
Transfers
0xa66405…e03c01 · 0x1bbc0b…26fdca · 0x76d8d4…705eef
Epoch anchor
tx 0x09e8a6…b74d49 · EAS attestation · block 48166831
Schema registration
tx 0xcf981d…bbf06b (once)

Every step on the agent's page has a "Verify in your browser" button: it downloads the disclosure, recovers the signature, checks the Merkle proof, and reads the anchoring transaction from a public Base Sepolia RPC, with no trust in this server.

How it works

1. Record

Each step (tool call, LLM call, transaction) becomes a record: tool name, timestamp, the tx hash, and salted commitments to the prompt, arguments, result and claim. Each record carries the hash of the previous one, so deleting or reordering is visible.

2. Sign and batch

Runs of steps are Merkle-rooted and the header (agent, batch index, seq range, previous head, head, root) is signed with the agent's key, EIP-712 or EIP-191. Signing is per batch, not per step.

3. Anchor

Karatum collects batches from all agents, Merkle-roots their ids and writes the root in one EAS attestation on Base Sepolia: one transaction per epoch, however many agents. Proves "existed no later than block N".

4. Disclose one step

The agent opens a chosen field of a chosen step with a Merkle proof. Everyone can verify it; nothing else is revealed. PII never reaches Karatum unless the agent opens it.

5. Field verdict

Karatum compares the opened claim with the chain: confirmed, contradicted, or unverifiable (never a verdict without evidence). A replay can re-execute the transaction in a Mirror fork.

6. Equivocation and gaps

Two different signed batches for one index, overlapping ranges, or a chain that does not link are stored as public evidence (both signatures). A missing batch index shows as a gap. Both count against trust.

Honest limits: the recorder can only attest to what the agent chooses to record and open; a field rating counts opened steps only and is shown with its coverage; a recorder that is never started records nothing. Anchoring proves ordering and non-repudiation, not truthfulness of off-chain content.

Use it

TypeScript

import { FlightRecorder, FlightClient, signerFromAccount } from "@karatum/flightrec";
import { privateKeyToAccount } from "viem/accounts";

const recorder = new FlightRecorder({
  signer: signerFromAccount(privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`)),
  sink: new FlightClient({ baseUrl: "https://karatum.com" }),   // roots only; contents stay on your machine
});

await recorder.record({ name: "swap", args, result, txHash, chainId: 84532,
  claim: { kind: "erc20.transfer", token, to, amount: "250000" } });
await recorder.flush();                                          // signs + posts the batch root
const proof = recorder.disclose(0, ["claim"]);                   // open ONE field of ONE step
await new FlightClient({ baseUrl: "https://karatum.com" }).postProof(proof);

Python

from karatum_flightrec import FlightRecorder, FlightClient, LocalSigner

rec = FlightRecorder(LocalSigner(AGENT_KEY), sink=FlightClient("https://karatum.com"))
rec.record("swap", args=args, result=result, tx_hash=tx, chain_id=84532,
           claim={"kind": "native.transfer", "to": to, "value": "10000000000000"})
rec.flush()
FlightClient().post_proof(rec.disclose(0, ["claim"]))

Integrations

  • MCP proxy: records any MCP agent without changing it. FLIGHT_AGENT_KEY=0x… tsx packages/flightrec/src/mcp-cli.ts --http https://your-mcp/mcp
  • LangChain (JS and Python): KaratumFlightHandler callback handler.
  • Vercel AI SDK: flightMiddleware(recorder) and recordTools(tools, recorder).
  • Coinbase AgentKit: recordActions(agentkit.getActions(), recorder, { chainId: 84532 }), claims derived for native and ERC-20 transfers.

API: POST /v1/flight/roots, POST /v1/flight/proofs, GET /v1/flight/proofs/:batchId/:seq, GET /v1/flight/agents/:address. No API key: the signature is the credential.

Verify a disclosure yourself