diff --git a/README.md b/README.md index d3ab4e9..d947719 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Source for the official World developer documentation, published at **[docs.worl - **World ID** (`world-id/`) — IDKit SDKs (JS, React, Swift, Kotlin, Go), Credentials, Selfie Check, on-chain verification, API reference. - **Mini Apps** (`mini-apps/`) — building applications that run inside World App, including commands, sharing, growth, and the MiniKit reference. - **World Chain** (`world-chain/`) — quick start, provider integrations, tokens, and developer reference for the chain. -- **AgentKit and agents** (`agents/`) — distinguishing human-backed agents from bots, plus Hats and human-in-the-loop flows. - **MCP servers** (`mcp/`) — World's Model Context Protocol integrations (developer portal, world-docs). - **OpenAPI specs** (`openapi/`) — machine-readable specs for the World ID and Developer Portal APIs. diff --git a/agents/agent-kit/ecosystem.mdx b/agents/agent-kit/ecosystem.mdx deleted file mode 100644 index f607b8d..0000000 --- a/agents/agent-kit/ecosystem.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Ecosystem" -description: "Projects and services that integrate AgentKit." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - -Find places to use AgentKit in the [AgentBook registry](https://github.com/andy-t-wang/agentbook). To check whether an agent wallet is registered, run `npx @worldcoin/agentkit-cli status `. -To add your project, open a PR to the registry. - -If you build an agent that calls x402 APIs, use [`createAgentkitClient`](/agents/agent-kit/sdk-reference#createagentkitclientoptions) and call `agentkit.fetch` so the agent tries AgentKit verification before paying. If you cannot change the agent's HTTP client, add the `agentkit-x402` skill: - -```bash -npx skills add worldcoin/agentkit agentkit-x402 -``` - - - - Register your agent with World ID to enable human-backed agent benefits. - - - Use `agentkit.fetch` before payment fallback. - - diff --git a/agents/agent-kit/integrate.mdx b/agents/agent-kit/integrate.mdx deleted file mode 100644 index 2dcc009..0000000 --- a/agents/agent-kit/integrate.mdx +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: "Integrate AgentKit" -description: "AgentKit Beta extends x402, allowing websites to distinguish human-backed agents from bots and scripts." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"og:description": "AgentKit Beta extends x402, allowing websites to distinguish human-backed agents from bots and scripts." -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - -AgentKit Beta extends x402 allowing websites to distinguish human-backed agents from bots and scripts. -Enable agentic traffic to access api endpoints while blocking malicious actors, scalpers and spam. - -This quickstart follows a default implementation path: - -- Agent-side x402 calls use `agentkit.fetch` from `createAgentkitClient` -- Accepts payments on both World Chain and Base -- Agent registration on World Chain -- AgentBook lookup always resolves on World Chain (caller side is chain-agnostic) -- `free-trial` mode with 3 uses -- Hono plus `@x402/hono` as the reference server example - -# Step 1: Install AgentKit - -```bash -npm install @worldcoin/agentkit -``` - - -# Step 2: Register the agent in AgentBook - -Register the wallet address your agent will sign with: - -```bash -npx @worldcoin/agentkit-cli register -``` - -Check whether a wallet is already registered: - -```bash -npx @worldcoin/agentkit-cli status -``` - -By default, the CLI registers on World Chain and submits through the hosted relay. AgentBook lookup always resolves against the canonical World Chain deployment. - -During registration, the CLI: - -1. Looks up the next nonce for the agent address -2. Prompts the World App verification flow -3. Submits the registration transaction - -Once the wallet is registered, AgentKit can resolve it to an anonymous human identifier at request time. - - - -

*Example of the registration flow*

- -# Step 3: Wrap x402 calls in the agent - -Use this in agents that call paid x402 APIs. Without this client, agents may go straight to payment instead of using their AgentKit registration. - -```typescript -import { createAgentkitClient } from '@worldcoin/agentkit' - -const agentkit = createAgentkitClient({ - signer: { - address: agentWallet.address, - chainId: 'eip155:8453', - type: 'eip191', - signMessage: message => agentWallet.signMessage(message), - }, -}) - -const response = await agentkit.fetch('https://api.example.com/data') -``` - -Use `agentkit.fetch` anywhere the agent would otherwise call `fetch` for x402-protected APIs. It tries AgentKit first, then leaves the normal x402 payment fallback in place. - -If you cannot change the agent's HTTP client, add the fallback skill: - -```bash -npx skills add worldcoin/agentkit agentkit-x402 -``` - -# Step 4: Wire the hooks-based server flow - -The example below shows the maintained Hono wrapper path. AgentKit itself is not Hono-only: Express and Next.js route handlers can use the same hooks and low-level helpers from the [SDK Reference](/agents/agent-kit/sdk-reference). - -```typescript -import { Hono } from 'hono' -import { serve } from '@hono/node-server' -import { HTTPFacilitatorClient } from '@x402/core/http' -import { ExactEvmScheme } from '@x402/evm/exact/server' -import { - paymentMiddlewareFromHTTPServer, - x402HTTPResourceServer, - x402ResourceServer, -} from '@x402/hono' -import { - agentkitResourceServerExtension, - createAgentBookVerifier, - createAgentkitHooks, - declareAgentkitExtension, - InMemoryAgentKitStorage, -} from '@worldcoin/agentkit' - -const WORLD_CHAIN = 'eip155:480' -const BASE = "eip155:8453"; -const WORLD_USDC = '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1' -const payTo = '0xYourAddress' - -const facilitatorClient = new HTTPFacilitatorClient({ - url: 'https://x402-worldchain.vercel.app/facilitator', -}) - -const evmScheme = new ExactEvmScheme() - // Register a money parser to accept USDC payments on WorldChain. - .registerMoneyParser(async (amount, network) => { - if (network !== WORLD_CHAIN) return null - - return { - amount: String(Math.round(amount * 1e6)), - asset: WORLD_USDC, - extra: { name: 'USD Coin', version: '2' }, - } - }) - -const agentBook = createAgentBookVerifier() -const storage = new InMemoryAgentKitStorage() - -const hooks = createAgentkitHooks({ - agentBook, - storage, - mode: { type: 'free-trial', uses: 3 }, -}) - -const resourceServer = new x402ResourceServer(facilitatorClient) - .register(WORLD_CHAIN, evmScheme) - .register(BASE, new ExactEvmScheme()) - .registerExtension(agentkitResourceServerExtension) - -const routes = { - 'GET /data': { - // Accept payments on both World Chain and Base - accepts: [ - { - scheme: 'exact', - price: '$0.01', - network: WORLD_CHAIN, - payTo, - }, - { - scheme: 'exact', - price: '$0.01', - network: BASE, - payTo, - }, - ], - extensions: declareAgentkitExtension({ - statement: 'Verify your agent is backed by a real human', - mode: { type: 'free-trial', uses: 3 }, - }), - }, -} - -const httpServer = new x402HTTPResourceServer(resourceServer, routes) - .onProtectedRequest(hooks.requestHook) - -const app = new Hono() -app.use(paymentMiddlewareFromHTTPServer(httpServer)) - -app.get('/data', c => { - return c.json({ message: 'Protected content' }) -}) - -serve({ fetch: app.fetch, port: 4021 }) -``` - -This example accepts payments on both World Chain and Base. AgentBook lookup automatically resolves against the canonical World Chain deployment. - -# Step 5: Configure the default mode and storage - -This guide uses `free-trial` mode so registered human-backed agents get 3 free requests before the normal x402 payment flow resumes. `InMemoryAgentKitStorage` is fine for local testing, but production should persist both usage counters and nonces. - -```typescript -import type { AgentKitStorage } from '@worldcoin/agentkit' - -class DatabaseAgentKitStorage implements AgentKitStorage { - async tryIncrementUsage(endpoint: string, humanId: string, limit: number) { - return db.tryIncrementUsage(endpoint, humanId, limit) - } - - async hasUsedNonce(nonce: string) { - return db.hasUsedNonce(nonce) - } - - async recordNonce(nonce: string) { - await db.recordNonce(nonce) - } -} - -const hooks = createAgentkitHooks({ - agentBook, - storage: new DatabaseAgentKitStorage(), - mode: { type: 'free-trial', uses: 3 }, -}) -``` - -Need `discount` mode, custom AgentBook deployments, or the low-level validation helpers? Continue to the [SDK Reference](/agents/agent-kit/sdk-reference). diff --git a/agents/agent-kit/sdk-reference.mdx b/agents/agent-kit/sdk-reference.mdx deleted file mode 100644 index f099668..0000000 --- a/agents/agent-kit/sdk-reference.mdx +++ /dev/null @@ -1,314 +0,0 @@ ---- -title: "SDK Reference" -description: "Reference for AgentKit modes, APIs, EVM signatures, and low-level helpers." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"og:description": "Reference for the AgentKit SDK surface and advanced integration behavior." -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - -Use this page when you need the full AgentKit surface area. For the shortest path to a working setup, start with [Integrate AgentKit](/agents/agent-kit/integrate). - -## Access modes - -Usage counters are tracked per human per endpoint. Two agents backed by the same human share the same counter. - -| Mode | Fields | Behavior | -| ------------ | ------------------------------------------------------ | -------- | -| `free` | `{ type: "free" }` | Registered human-backed agents always bypass payment. | -| `free-trial` | `{ type: "free-trial"; uses?: number }` | Registered human-backed agents bypass payment the first `N` times. Default `uses` is `1`. | -| `discount` | `{ type: "discount"; percent: number; uses?: number }` | Registered human-backed agents can underpay by the configured percentage for the first `N` times. Omitting `uses` applies the discount forever. | - -`discount` mode requires `verifyFailureHook` to be registered on the facilitator. Without it, discounted underpayments fail settlement verification. - -Unlike `free-trial`, omitting `uses` doesn't default to a single use — always set it explicitly in production, or every verified human-backed agent gets a permanent discount on that endpoint. - -## Agent client APIs - -### `createAgentkitClient(options)` - -Use this client in agents that call paid x402 APIs. `agentkit.fetch` inspects `402 Payment Required` responses and retries once with a signed `agentkit` header when the response advertises `extensions.agentkit`. - -```typescript -import { createAgentkitClient } from '@worldcoin/agentkit' - -const agentkit = createAgentkitClient({ - signer: { - address: agentWallet.address, - chainId: 'eip155:8453', - type: 'eip191', - signMessage: message => agentWallet.signMessage(message), - }, -}) -``` - -| Option | Type | Description | -| --------- | ------------------------------------- | ----------- | -| `signer` | `AgentkitSigner` | Agent wallet identity and SIWE signing function. Required. | -| `fetch` | `typeof fetch` | Optional base fetch implementation. Defaults to `globalThis.fetch`. | -| `onEvent` | `(event: AgentkitFetchEvent) => void` | Optional callback for logging and debugging. | - -The client does not create x402 payments. It returns the original response unchanged when AgentKit is unavailable or cannot be used, so your existing x402 payment client can handle fallback. - -### `AgentkitSigner` - -```ts -type AgentkitSigner = { - address: string - chainId: string - type: 'eip191' | 'eip1271' - signMessage(message: string): Promise -} -``` - -Use `eip191` for EOAs and `eip1271` for smart contract wallets. - -The returned client exposes: - -| Field | Type | Description | -| ----- | ---- | ----------- | -| `fetch` | `typeof fetch` | Fetch-compatible function that retries AgentKit-enabled 402 responses once. | -| `createHeader` | `(extension: AgentkitExtension) => Promise` | Creates the base64 `agentkit` HTTP header for custom HTTP clients. | - -## Core server APIs - -### `declareAgentkitExtension(options?)` - -Use this helper in your x402 route config to declare the `agentkit` extension that should be returned in a `402 Payment Required` response. - -| Parameter | Type | Description | -| ------------------- | -------------------- | ----------- | -| `domain` | `string` | Server hostname. Usually auto-derived from the request URL. | -| `resourceUri` | `string` | Full protected resource URI. Usually auto-derived from the request URL. | -| `network` | `string \| string[]` | CAIP-2 network or list of networks. If omitted, the extension derives them from `accepts[].network`. | -| `statement` | `string` | Human-readable signing purpose. | -| `version` | `string` | CAIP-122 version. Defaults to `"1"`. | -| `expirationSeconds` | `number` | Challenge lifetime in seconds. | -| `mode` | `AgentkitMode` | Access mode clients should expect after verification. | - -Returns a record keyed by `agentkit` that can be attached directly to an x402 route declaration. - -### `agentkitResourceServerExtension` - -Register this extension once on your x402 resource server. It turns the declaration returned by `declareAgentkitExtension(...)` into a full 402 challenge by: - -- generating the nonce and timestamps -- inferring `domain` and `resourceUri` from the incoming request when you omit them -- expanding each supported EVM network into `eip191` and `eip1271` signature types - -### `createAgentkitHooks(options)` - -Creates the request-time verification hooks used by the golden path integration. - -| Option | Type | Description | -| ----------- | ------------------------------------ | ----------- | -| `agentBook` | `AgentBookVerifier` | Verifier used to resolve the agent wallet to a human identifier. Required. | -| `mode` | `AgentkitMode` | Access mode. Defaults to `{ type: "free" }`. | -| `storage` | `AgentKitStorage` | Required for `free-trial` and `discount`. Optional for `free`. | -| `rpcUrl` | `string` | Custom EVM RPC used during signature verification. | -| `rpcUrls` | `Record` | Custom RPC URLs keyed by CAIP-2 chain ID, e.g. `{ "eip155:8453": "https://base.example" }`. Use this instead of `rpcUrl` when verifying signatures across multiple chains. | -| `onEvent` | `(event: AgentkitHookEvent) => void` | Optional logging/debug callback. | - -Returns: - -| Field | Type | Description | -| ------------------- | --------------------- | ----------- | -| `requestHook` | `function` | Runs before payment settlement and can grant access for `free` or `free-trial`. | -| `verifyFailureHook` | `function \| undefined` | Present only for `discount` mode. Register it on the facilitator so discounted underpayments can be recovered. | - -`requestHook` expects a context shaped like: - -```ts -{ - adapter: { - getHeader(name: string): string | undefined - getUrl(): string - } - path: string -} -``` - -That is why Express and Next.js are compatible even though the docs use Hono for the concrete example: you can adapt any server framework to this minimal contract. - -### `AgentkitHookEvent` - -`onEvent` receives one of these event shapes: - -| Event type | Fields | -| --------------------- | ------ | -| `agent_verified` | `resource`, `address`, `humanId` | -| `agent_not_verified` | `resource`, `address` | -| `validation_failed` | `resource`, `error?` | -| `discount_applied` | `resource`, `address`, `humanId` | -| `discount_exhausted` | `resource`, `address`, `humanId` | - -`agent_not_verified` also fires when the AgentBook lookup itself fails (RPC timeout, dropped connection, public RPC outage) — not only when the wallet is genuinely unregistered. There's no separate event for a failed lookup; see [AgentBook lookup](#agentbook-lookup) for how to tell the two apart. - -## AgentBook lookup - -### `createAgentBookVerifier(options?)` - -Creates the verifier used to resolve a wallet address to an anonymous human identifier. Lookup **always** resolves against the canonical AgentBook deployment on World Chain (`eip155:480`). - -Canonical deployment: - -- World Chain mainnet: `0xA23aB2712eA7BBa896930544C7d6636a96b944dA` - -| Option | Type | Description | -| ----------------- | ------------------- | ----------- | -| `rpcUrl` | `string` | Custom World Chain RPC URL. Defaults to the chain's default public RPC. Ignored if `client` is provided. | -| `contractAddress` | `` `0x${string}` `` | Custom AgentBook contract address on World Chain. Defaults to the canonical deployment. | -| `client` | `PublicClient` | Advanced override. Inject a fully custom viem public client (useful for tests or non-standard deployments). | - -In the common case call it with no arguments: - -```typescript -const agentBook = createAgentBookVerifier() -``` - -The returned object exposes: - -```ts -lookupHuman(address: string): Promise -``` - -`lookupHuman` returns `null` both when the wallet is unregistered and when the lookup itself fails (RPC timeout, network error, public RPC outage) — failures are swallowed internally and indistinguishable from "not registered" by return value alone. To bound lookups and surface RPC failures separately, pass a custom viem `client` with a timeout, e.g. `createPublicClient({ transport: http(rpcUrl, { timeout: 5_000 }) })`, and monitor that RPC endpoint. - -## Storage and replay protection - -### `AgentKitStorage` - -`AgentKitStorage` is the persistence interface used for free-trial counters, discount counters, and optional nonce replay protection. - -| Method | Description | -| ----------------------------------- | ----------- | -| `tryIncrementUsage(endpoint, humanId, limit)` | Atomically increment usage if below the limit. Returns `true` when the use is recorded. | -| `hasUsedNonce?(nonce)` | Optional replay check. Return `true` if the nonce has already been seen. | -| `recordNonce?(nonce)` | Optional replay recorder. Persist the nonce after validation succeeds. | - -### `InMemoryAgentKitStorage` - -`InMemoryAgentKitStorage` is the reference implementation exported by the package. - -- Good for local development, demos, and tests -- Not appropriate for production because usage counters and nonce history disappear on restart - -## Validation and verification helpers - -### `parseAgentkitHeader(header)` - -Parses the base64-encoded `agentkit` header into a structured payload. It throws if the header is not valid base64, is not valid JSON, or does not match the expected schema. - -### `validateAgentkitMessage(payload, resourceUri, options?)` - -Validates message binding, freshness, and optional replay checks. - -| Option | Type | Description | -| ------------ | ---------------------------- | ----------- | -| `maxAge` | `number` | Maximum age for `issuedAt` in milliseconds. Defaults to 5 minutes. | -| `checkNonce` | `(nonce: string) => boolean \| Promise` | Optional replay validation hook. | - -Returns: - -```ts -{ valid: boolean; error?: string } -``` - -### `verifyAgentkitSignature(payload, options?)` - -Verifies the cryptographic signature and returns the recovered address on success. - -| Option | Type | Description | -| --------- | ------------------------- | ----------- | -| `rpcUrl` | `string` | Optional custom RPC endpoint for EVM verification. | -| `rpcUrls` | `Record` | Custom RPC URLs keyed by CAIP-2 chain ID, e.g. `{ "eip155:8453": "https://base.example" }`. Use this instead of `rpcUrl` when verifying across multiple chains. | - -Behavior: - -- `eip155:*` payloads are reconstructed into a SIWE message and verified with viem -- unsupported chain namespaces return `{ valid: false, error: ... }` - -Returns: - -```ts -{ valid: boolean; address?: string; error?: string } -``` - -### `buildAgentkitSchema()` - -Returns the JSON schema used in 402 challenge payloads. - -## Chain utilities - -### EVM - -| Export | Description | -| ---------------------- | ----------- | -| `formatSIWEMessage` | Reconstruct the SIWE message used for EVM signing and verification. | -| `verifyEVMSignature` | Verify an EVM signature for the reconstructed SIWE message. | -| `extractEVMChainId` | Convert a CAIP-2 `eip155:*` chain ID to its numeric chain ID. | -| `resolveAgentkitSignatureRpcUrl` | Return the configured `rpcUrls[chainId]` or `rpcUrl` for a CAIP-2 chain ID, or `undefined`. The signature verifier selects the chain's default public RPC downstream. | -| `getDefaultPublicRpcUrl` | Return the default public RPC URL for a numeric chain ID, such as `480`, or `undefined` if no default is configured. | - -EVM verification uses viem's `verifyMessage`, which covers EOAs and ERC-1271 smart wallets. Counterfactual wallets can still represent their signature scheme with `signatureScheme: "eip6492"` in the payload schema. - -## Supported chains and signature behavior - -- Chain namespace: `eip155:*` -- Payload `type`: `eip191` or `eip1271` -- Optional `signatureScheme`: `eip191`, `eip1271`, or `eip6492` -- Message format: SIWE - -## Manual usage example - -Use the low-level helpers directly when you are not using the x402 Hono wrapper or when you want full control over request handling: - -```typescript -import { - AGENTKIT, - createAgentBookVerifier, - declareAgentkitExtension, - parseAgentkitHeader, - validateAgentkitMessage, - verifyAgentkitSignature, -} from '@worldcoin/agentkit' - -const extensions = declareAgentkitExtension({ - domain: 'api.example.com', - resourceUri: 'https://api.example.com/data', - network: 'eip155:480', - statement: 'Verify your agent is backed by a real human', -}) - -const agentBook = createAgentBookVerifier() - -async function handleRequest(request: Request) { - const header = request.headers.get(AGENTKIT) - if (!header) return - - const payload = parseAgentkitHeader(header) - - const validation = await validateAgentkitMessage(payload, 'https://api.example.com/data') - if (!validation.valid) { - return { error: validation.error } - } - - const verification = await verifyAgentkitSignature(payload) - if (!verification.valid || !verification.address) { - return { error: verification.error } - } - - const humanId = await agentBook.lookupHuman(verification.address) - if (!humanId) { - return { error: 'Agent is not registered in the AgentBook' } - } - - return { humanId } -} -``` - -## Production notes - -- Treat `InMemoryAgentKitStorage` as a demo-only implementation. -- If you need limited free uses, persistent storage is part of the integration, not an optional enhancement. -- If you need `discount` mode, wire `verifyFailureHook` into the facilitator before you ship. -- Use Hono as a reference example, not as a framework restriction. The package surface is generic enough to be adapted to Express or Next.js handlers. diff --git a/agents/hats/index.mdx b/agents/hats/index.mdx deleted file mode 100644 index c950640..0000000 --- a/agents/hats/index.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "[Sold Out] Claim Your Free Hat" -description: "Prove you're human-backed with AgentKit and claim an exclusive free hat." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"og:description": "Prove you're human-backed with AgentKit and claim an exclusive free hat." -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - - -All hats have been claimed. Thanks for the incredible response — stay tuned for future drops. - - -The [Human Required](https://humanrequired.shop/) store is a Shopify store demo that only sells to agents verified as human-backed through [AgentKit](/agents/agent-kit/integrate). Agents registered in [AgentBook](https://github.com/andy-t-wang/agentbook) (check with `npx @worldcoin/agentkit-cli status `) can unlock a 100% discount and claim the hat for free. Discount codes are unique per human — each person can generate one. - - - Human in the Loop Hat - - -Install the AgentKit Shopify plugin and let your agent handle the rest: - - -```bash Claude Code -/plugin marketplace add worldcoin/agentkit-shopify-demo -/plugin install agentkit-shopify@worldcoin-agentkit -/reload-plugins - -# Then ask Claude: -# "Help me purchase this product: https://humanrequired.shop/products/human-in-the-loop-hat" -``` - -```bash Manual -curl -LsSf https://astral.sh/uv/install.sh | sh - -git clone https://github.com/worldcoin/agentkit-shopify-demo.git -cd agentkit-shopify-demo - -# Generate an agent key -uv run --with eth-account python3 -c \ - "from eth_account import Account; a = Account.create(); print(a.key.hex())" > .agent-key - -# Register in AgentBook (opens QR for World ID verification) -npx --registry https://registry.npmjs.org @worldcoin/agentkit-cli register - -# Get the discount code -PRIVATE_KEY=$(cat .agent-key) ./skills/shopify-agent-discount/get-coupon.py \ - https://humanrequired.shop/products/human-in-the-loop-hat -``` - diff --git a/agents/human-in-the-loop/integrate.mdx b/agents/human-in-the-loop/integrate.mdx deleted file mode 100644 index a7ec720..0000000 --- a/agents/human-in-the-loop/integrate.mdx +++ /dev/null @@ -1,224 +0,0 @@ ---- -title: "Integrate" -description: "Add human approval workflows to AI agents using World ID." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"og:description": "Add human approval workflows to AI agents using World ID." -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - - - -

*AI agent pauses for World ID approval before booking a flight*

- -Human-in-the-loop lets an AI agent **pause mid-execution** and wait for a real, verified human to approve an action before continuing. Every approval is cryptographically bound to the action via [World ID](/world-id/overview) — no bots, no spoofing, no replay. - -Built on the [Workflow SDK](https://useworkflow.dev) and the [Vercel AI SDK](https://ai-sdk.dev). - -## Install - -```bash -# Server — human-in-the-loop + peer dependencies -npm install @worldcoin/human-in-the-loop ai@^6 workflow @workflow/ai zod - -# Client — React bindings + peer dependencies -npm install @worldcoin/human-in-the-loop-react @worldcoin/idkit ai@^6 react -``` - -## Environment variables - -```bash -# Server — used by @worldcoin/human-in-the-loop -WORLD_RP_ID=your_rp_id -WORLD_SIGNING_KEY=your_signing_key - -# Client — used by the component (optional if passing appId prop) -NEXT_PUBLIC_WORLD_APP_ID=app_... -``` - -Get these from the [World developer portal](https://developer.world.org) by creating an app. - -## Step 1: Define the workflow - -```ts -// src/workflows/chat/index.ts -import { DurableAgent } from '@workflow/ai/agent' -import { getWritable } from 'workflow' -import { openai } from '@workflow/ai/openai' -import type { ModelMessage, UIMessageChunk } from 'ai' -import { tools } from './steps/tools' - -export async function chatWorkflow(messages: ModelMessage[]) { - // Durable workflow — can pause for hours/days and resume where it left off - 'use workflow' - - const writable = getWritable() - const agent = new DurableAgent({ - model: openai('gpt-5.4'), - tools, - instructions: - 'You are a helpful assistant. Before performing any sensitive action, call approveAction first, then pass its returned result as the `approval` argument to the action tool — never call the action tool without it.', - }) - - await agent.stream({ messages, writable }) -} -``` - -## Step 2: Register the approval tool - - -The default `action` is the unique `toolCallId`. For a sensitive operation, your backend must bind the approval to the intended operation and its parameters, independently verify the proof, and consume the approval once before performing the side effect. A required `approval` input is not proof of authorization — tool inputs are LLM-generated. See the [flight booking example](https://github.com/worldcoin/human-in-the-loop/tree/main/examples/flight-booking) for proof verification and parameter binding. - - -```ts -// src/workflows/chat/steps/tools.ts -import { requestHumanAuthorization } from '@worldcoin/human-in-the-loop/workflows' -import { z } from 'zod' - -const approvalInputSchema = z.object({ - summary: z.string(), - flightNumber: z.string(), -}) - -const bookingInputSchema = z.object({ - flightNumber: z.string(), - // One credential, so its nullifier identifies the approval below. Cap it at - // 32 bytes: the 3.0 verifier reads only the first 32, so a longer encoding - // would still verify but produce a new key. - approval: z - .object({ - action: z.string(), - responses: z - .array(z.object({ nullifier: z.string().regex(/^0x[0-9a-fA-F]{1,64}$/) }).passthrough()) - .length(1), - }) - .passthrough(), -}) - -// Atomically records an approval key; returns false if it was already used. -// Back it with durable storage (e.g. a unique database key), not memory. -declare function consumeApproval(key: string): Promise - -export const tools = { - approveAction: { - description: 'Request human approval via World ID before a sensitive action.', - inputSchema: approvalInputSchema, - - // Pauses the workflow, streams approval context to the client, - // waits for World ID proof, verifies it, then resumes. - // Bind this example's approval to the flight number. - execute: requestHumanAuthorization>({ - action: ({ input }) => `booking:${input.flightNumber}`, - }), - }, - bookFlight: { - description: 'Book the flight using the IDKitResult from approveAction as `approval`.', - // Requiring an object does not establish that its proof is authentic. - inputSchema: bookingInputSchema, - // Never trust that approveAction ran just because this tool was called: - // check the binding, re-verify the proof, and consume it once. - execute: async ({ flightNumber, approval }: z.infer) => { - 'use step' - - const expectedAction = `booking:${flightNumber}` - if (approval.action !== expectedAction) { - throw new Error(`approval does not match this booking (expected action: ${expectedAction})`) - } - - const rpId = process.env.WORLD_RP_ID - if (!rpId) throw new Error('WORLD_RP_ID is required to verify approvals') - const res = await fetch(`https://developer.world.org/api/v4/verify/${rpId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - // The approval is untrusted input: pin the environment so it can't - // select "staging" or "sandbox", which accept test proofs. - body: JSON.stringify({ ...approval, environment: 'production' }), - signal: AbortSignal.timeout(10_000), - }) - if (!res.ok) { - throw new Error(`approval failed World ID verification (${res.status}): ${await res.text()}`) - } - - // One-time use, keyed on the proof's nullifier (bound by 3.0 and 4.0 - // proofs). Parse it as the verifier does, so "0x01" and "0x1" share a - // key. Nullifiers repeat per person and action, so each person can book - // a flight number once; add a unique booking ID to the action to allow more. - const nullifier = BigInt(approval.responses[0].nullifier).toString(16) - if (!(await consumeApproval(`${expectedAction}:${nullifier}`))) { - throw new Error('approval already used') - } - - // ...book the flight - }, - }, - // ...your other tools -} -``` - -## Step 3: Render the approval on the client - -This example uses the `` component, if you want to customize the UI you can use the `useHumanApproval` hook instead. - - -```tsx HumanApproval component -import { HumanApproval } from '@worldcoin/human-in-the-loop-react' - -// Match on the tool name from Step 2. renders the World ID -// widget and POSTs the proof back to the server automatically. -{message.parts.map(part => { - if (part.type === 'tool-approveAction' && 'toolCallId' in part) { - return ( - - ) - } - // ...your other part renderers -})} -``` - -```tsx useHumanApproval hook -import { useHumanApproval } from '@worldcoin/human-in-the-loop-react' -import { IDKitRequestWidget, orbLegacy } from '@worldcoin/idkit' -import { useState } from 'react' - -function MyApproval({ message, part }) { - const [open, setOpen] = useState(false) - // ready: true once the server streams the approval context - // verify: POSTs the World ID proof to the server webhook - const { ready, action, rpContext, state, verify } = useHumanApproval(message, part) - - if (state.status === 'verified') return

Approved.

- - return ( - <> - - {state.status === 'error' &&

{state.error.message}

} - - {ready && ( - {}} - handleVerify={verify} - app_id={process.env.NEXT_PUBLIC_WORLD_APP_ID as `app_${string}`} - action={action!} - rp_context={rpContext!} - preset={orbLegacy()} - allow_legacy_proofs={false} - /> - )} - - ) -} -``` -
- -### Flight booking example - -Check out [Flight booking example](https://github.com/worldcoin/human-in-the-loop/tree/main/examples/flight-booking) for a complete implementation of a human-in-the-loop workflow with World ID approval. diff --git a/agents/human-in-the-loop/sdk-reference.mdx b/agents/human-in-the-loop/sdk-reference.mdx deleted file mode 100644 index 468260f..0000000 --- a/agents/human-in-the-loop/sdk-reference.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: "SDK Reference" -description: "API reference for @worldcoin/human-in-the-loop and @worldcoin/human-in-the-loop-react." -"og:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" -"og:description": "API reference for @worldcoin/human-in-the-loop and @worldcoin/human-in-the-loop-react." -"twitter:image": "https://raw.githubusercontent.com/worldcoin/developer-docs/main/images/docs/docs-meta.png" ---- - -## Server — `@worldcoin/human-in-the-loop` - -### `requestHumanAuthorization(options?)` - -Import from `@worldcoin/human-in-the-loop/workflows`. Returns a tool `execute` function that pauses the workflow until a World ID proof is received. Returns `Promise`. - -| Option | Type | Default | Description | -| --- | --- | --- | --- | -| `action` | `string \| ((ctx) => string)` | `toolCallId` | Unique action string bound to this verification. When a function, receives `{ toolCallId, input }`. | -| `signingKey` | `string` | `WORLD_SIGNING_KEY` env | Hex-encoded RP signing key. | -| `rpId` | `string` | `WORLD_RP_ID` env | Relying-party ID for verification. | - ---- - -## Client — `@worldcoin/human-in-the-loop-react` - -### `` - -Drop-in component that renders the World ID widget and posts the proof back automatically. Ships unstyled. - -| Prop | Type | Default | Description | -| --- | --- | --- | --- | -| `message` | `UIMessage` | required | The message containing the tool part. | -| `part` | `UIMessagePart` | required | The tool-call part (must include `toolCallId`). | -| `appId` | `` `app_${string}` `` | `NEXT_PUBLIC_WORLD_APP_ID` env | World app ID. | -| `preset` | `Preset` | `orbLegacy()` | IDKit verification preset. | -| `allowLegacyProofs` | `boolean` | `false` | Accept v3 World ID proofs. | -| `triggerLabel` | `ReactNode` | `'Verify with World ID'` | Button label. | -| `successContent` | `ReactNode` | `'Approved with World ID.'` | Shown after verification. | -| `className` | `string` | — | CSS class for the outer wrapper. | - -### `useHumanApproval(message, part)` - -Headless hook for custom approval UI. - -| Field | Type | Description | -| --- | --- | --- | -| `ready` | `boolean` | `true` once the approval context has streamed from the server. | -| `action` | `string \| undefined` | Action string. Pass to `IDKitRequestWidget`. | -| `rpContext` | `RpContext \| undefined` | Pass to `IDKitRequestWidget`'s `rp_context`. | -| `webhookUrl` | `string \| undefined` | Webhook URL. Handled by `verify` automatically. | -| `state` | `HumanApprovalState` | `'idle' \| 'verifying' \| 'verified' \| 'error'` | -| `verify` | `(proof: IDKitResult) => Promise` | Wire to `IDKitRequestWidget.handleVerify`. | - -```ts -type HumanApprovalState = - | { status: 'idle' } - | { status: 'verifying' } - | { status: 'verified' } - | { status: 'error'; error: Error } -``` diff --git a/docs.json b/docs.json index e81233a..e58ba1c 100644 --- a/docs.json +++ b/docs.json @@ -264,27 +264,6 @@ } ] }, - { - "tab": "Agents", - "groups": [ - { - "group": "AgentKit", - "pages": [ - "agents/agent-kit/integrate", - "agents/agent-kit/sdk-reference", - "agents/agent-kit/ecosystem", - "agents/hats/index" - ] - }, - { - "group": "Human in the Loop", - "pages": [ - "agents/human-in-the-loop/integrate", - "agents/human-in-the-loop/sdk-reference" - ] - } - ] - }, { "tab": "World Chain", "groups": [ diff --git a/index.mdx b/index.mdx index 7d2a0f0..09024a4 100644 --- a/index.mdx +++ b/index.mdx @@ -26,13 +26,27 @@ import { DeveloperStories } from "/snippets/developer-stories.jsx";
-
+

World Developer Docs

Integrate World ID and deploy on World Chain with official developer documentation.
+
+
+ +
+
+ Coming soon +
World ID + Agents
+

+ The next generation of using agents with World ID is coming. Let AI agents act on behalf of + verified humans, and tell human-backed agents apart from bots and scripts, without giving up privacy. +

+
+
+ - -
- -
-
-
AgentKit
-
Distinguish human-backed agents from bots and scripts
-
-
-
@@ -184,9 +188,10 @@ import { DeveloperStories } from "/snippets/developer-stories.jsx"; Integrate World ID and deploy on World Chain with official developer documentation. + **Coming soon: World ID + Agents** — The next generation of World ID is built for agents. Let AI agents act on behalf of verified humans, and tell human-backed agents apart from bots and scripts, without giving up privacy. + - [Integrate World ID](/world-id/overview) — Anonymous Proof of Human for your website or app. - [Create a Mini App](/mini-apps) — Mini applications distributed in World App. - - [AgentKit](/agents/agent-kit/integrate) — Distinguish human-backed agents from bots and scripts. - [World Chain](/world-chain) — Build directly on World’s blockchain infrastructure. **Developer stories** — how developers build trusted, human-centered apps with World: diff --git a/tailwind.css b/tailwind.css index ba391c7..f4539ee 100644 --- a/tailwind.css +++ b/tailwind.css @@ -83,7 +83,7 @@ } #navbar .nav-tabs-item { height: 32px !important; - padding: 6px 10px; + padding: 6px 10px !important; border-radius: 42px; color: #181818 !important; transition: background-color 0.15s ease; @@ -93,7 +93,7 @@ background-color: #F3F2F0; } #navbar .nav-tabs-item[data-active="true"] { - padding: 6px 14px; + padding: 6px 14px !important; background-color: #181818; color: #F9F9F8 !important; text-shadow: none; @@ -371,6 +371,77 @@ filter: invert(1); } + /* Landing page — "World ID + Agents" coming-soon banner above the cards */ + .landing-coming-soon { + display: flex; + align-items: center; + gap: 32px; + padding: 32px; + border-radius: 32px; + border: 1px solid rgba(0, 124, 251, 0.18); + background: + radial-gradient(120% 140% at 100% 0%, rgba(25, 183, 219, 0.28) 0%, rgba(25, 183, 219, 0) 60%), + linear-gradient(120deg, #EEF6FF 0%, #D9EBFF 55%, #CDEFF8 100%); + } + .landing-coming-soon .product-card-tile { + background-color: rgba(255, 255, 255, 0.7); + } + .dark .landing-coming-soon { + border-color: rgba(0, 124, 251, 0.35); + background: + radial-gradient(120% 140% at 100% 0%, rgba(25, 183, 219, 0.22) 0%, rgba(25, 183, 219, 0) 60%), + linear-gradient(120deg, #0B1626 0%, #0A2140 55%, #08303A 100%); + } + .dark .landing-coming-soon .product-card-tile { + background-color: rgba(255, 255, 255, 0.06); + } + .landing-coming-soon-badge { + display: inline-block; + padding: 4px 12px; + border-radius: 999px; + font-size: 12px; + line-height: 1.4; + font-weight: 500; + letter-spacing: 0.48px; + text-transform: uppercase; + color: #FFFFFF; + background-color: #0068D6; + } + .landing-coming-soon-title { + margin-top: 12px; + font-size: 32px; + line-height: 1.2; + letter-spacing: -0.32px; + font-weight: 300; + color: #181818; + } + .dark .landing-coming-soon-title { + color: #F3F2F0; + } + .landing-coming-soon-desc { + margin: 10px 0 0; + max-width: 640px; + font-size: 16px; + line-height: 1.5; + font-weight: 300; + color: #4A5568; + } + .dark .landing-coming-soon-desc { + color: rgb(158, 162, 166); + } + @media (max-width: 600px) { + .landing-coming-soon { + flex-direction: column; + align-items: flex-start; + gap: 20px; + padding: 24px; + border-radius: 24px; + } + .landing-coming-soon-title { + font-size: 24px; + } + } + /* Landing page — section headers (Developer stories / World Tech Updates) */ .landing-section-title { font-size: 48px; @@ -678,17 +749,24 @@ } } - /* Landing page — hero */ + /* Landing page — hero. !important because Mintlify's unlayered h1 styles + (and docs.json's heading weight 600) otherwise beat this @layer rule. */ .landing-hero-title { - font-size: 64px; - line-height: 1.1; - letter-spacing: -1.28px; - font-weight: 300; + font-size: 56px !important; + line-height: 1.1 !important; + letter-spacing: -1.12px !important; + font-weight: 300 !important; color: #181818; } .dark .landing-hero-title { color: #F3F2F0; } + @media (max-width: 900px) { + .landing-hero-title { + font-size: 46px !important; + letter-spacing: -0.92px !important; + } + } .landing-hero-sub { margin-top: 24px; max-width: 487px; @@ -703,8 +781,8 @@ } @media (max-width: 600px) { .landing-hero-title { - font-size: 40px; - letter-spacing: -0.8px; + font-size: 36px !important; + letter-spacing: -0.72px !important; } .landing-hero-sub { font-size: 18px; diff --git a/world-id/yc.mdx b/world-id/yc.mdx index 743042d..d53884e 100644 --- a/world-id/yc.mdx +++ b/world-id/yc.mdx @@ -32,12 +32,6 @@ World ID is a privacy-preserving trust layer for companies that need to know whe **Explore Identity Check** - - Distinguish people from agents, and make human approval an explicit boundary - for consequential actions. - - **Integrate AgentKit** -