Who's behind an agent changes terms, not price. Bots prepay; humans get deposit & pay later.


AI agents are about to book, buy, and negotiate for us. When one walks up to a business, the business has two bad options: treat it as fraud and make it prepay everything, or trust it and let bots farm the place. The missing piece is who is behind the request.
BookerBob is a hotel-booking gateway where the answer changes the risk terms, never the sticker price. Same rooms, same nightly rate; what changes is collateral, inventory access, and whether settlement can wait until checkout.
Anonymous bot lane: a short list, 100% prepay, and a real x402 pay-per-query on Hedera testnet (0.01 HBAR per search, a live HashScan tx).
Accountable lane: World proves a real human stands behind the agent (AgentKit verified against AgentBook on World Chain; a browser World ID / Selfie path for people proving themselves). The Graph reads that human's consented onchain history as coarse bands (Messari lending, DEX and perps), never raw numbers, and feeds an underwriting engine. A stronger history earns fuller inventory, a deposit instead of prepay, and rate-lock-now / pay-later as a real Hedera Scheduled Transaction, created at prebook and open on HashScan. Privy binds "this is my wallet" so address consent is proven, not just typed.
North star: package identity plus consented onchain context so an agent books under underwritten terms instead of guessing. BookerBob is the vertical cut: underwriting at the desk, the agent executes.
Live demo (same prompt, two lanes, the term delta live): https://lisbonhack.world/
Notable
Feedback: World App reports success while the browser relying-party gets failure (Selfie / non-orb)
Witnessed by the World team at the ETHGlobal Lisbon booth, who asked us to submit this.
Make the app-side and browser-side outcomes agree, and surface a clear reason (e.g. credential_unavailable) to the relying party when Selfie/Face is not yet live, so an integrator does not chase a phantom failure that only reproduces for non-orb users.
How it's made Monorepo (apps/gateway, apps/web, packages/context-bands-mcp, packages/hedera-schedule). Gateway is Hono on Node 20 / Fly; web is Vite + React 18, same-origin in prod so fetch('/offers') hits the API without a separate CORS host. TypeScript everywhere; no Postgres — process-local Maps for spent ledger, settlement index, World ID rate limits, context cache.
Request path. GET /offers is the desk. Middleware order matters: credential resolve → optional x402 wall → handler.
Header agentkit: @worldcoin/agentkit validateAgentkitMessage + createAgentBookVerifier against World Chain RPC (eip155:480). Fail closed to missing; never return forge-helpful detail to the client. Header world-id: HMAC session from /world-id/verify (IDKit proof → Developer Portal); combines with AgentKit via combine(). Query ?credential=1 with no header → explicit stand_in (browser demo only; cannot become verified by construction). Query ?address= (+ optional Authorization: Bearer Privy access token): jose JWKS verify → linked wallets; only then we treat the address as owned. Address goes to context-bands-mcp over long-lived MCP stdio (HTTP /mcp also exists for curl). Messari subgraphs via The Graph Studio key → bands {activity,tenure,breadth,scale} + repayment signal; thresholds in SKILL.md, raw USD never leaves the MCP. Pure terms.ts: signals in → {tier, inventory, payment} out. No network. Narration strings are the single fact that decided the row. Inventory: booker MCP (RateHawk behind our flexrep wrapper) or fixtures/lisbon.json. If earnsRateLock: @bookerbob/hedera-schedule → @hashgraph/sdk ScheduleCreate; scheduleUrl = hashscan.io/testnet/schedule/<id>. Bot metering. Uncredentialed /offers registers x402 (@x402/hono + @x402/hedera, network hedera:testnet, facilitator Blocky402, price 0.01 HBAR). Race UI doesn’t do client-side payment UX — it POST /x402/paid-offers, gateway wraps fetch with wrapFetchWithPayment + demo LISBON2026_HEDERA_* signer, strips settle tx from PAYMENT-RESPONSE into x-bookerbob-payment-tx / -url. UI increments spent only if that header exists.
Web. Two lanes parallel: bot = metered paid-offers; backed = credentialed /offers. Machine view is a protocol transcript over the same JSON (some rows live, some declared — don’t trust the wire sketch alone). Overview opts out of metering (metered: false) so a human reading the page isn’t charged as an agent.
Partners, concretely. World → who is accountable. Graph → underwriting file without doxxing balances. Hedera → money movement (x402 per query + scheduled checkout). Privy → SIWE + server-side address bind. x402 → 402 challenge/settle without API keys for bots.
Hacky bits worth saying. (1) Demo operator keys for both HBAR payer and (script) AgentKit agent — shop-window parallel to “facilitator pays,” not user wallets. (2) World RPC death used to look like “wallet not registered”; fixed with a raw eth_blockNumber probe in world-chain.ts. (3) AgentKit maxAge is ms not seconds — we shipped a 0.3s window once; fixed after staging. (4) HashScan broke when we used #/testnet/...; current explorer wants path URLs. (5) Schedule execute exists as POST /book / CLI demo; race UI only surfaces ScheduleCreate + HashScan (create is the on-chain proof in-window).

