HORS

HORS is CORS for AI agents—private function sharing governed by human origin

HORS

Created At

ETHGlobal Lisbon 2026

Winner of

World

World - AgentKit New Use Cases 1st place

Project Description

HORS (Human-Origin Resource Sharing) is a working protocol and developer toolkit for safely connecting a person’s private agents across wallets, vendors, models, devices, and runtimes.

CORS gives a web API an origin policy: it decides which website origin may access a protected route. HORS brings the same mental model to the agent web: it gives every private MCP function a policy describing which human origin may invoke it and what additional assurance or execution guarantees that invocation requires.

Personal-agent systems currently face a bad trade-off. One super-agent can access email, calendars, money, credentials, wallets, and memory, making it highly composable but creating a system-wide blast radius if it is compromised. Fully isolated agents reduce that risk, but they cannot help one another. A travel agent cannot ask a work agent for a meeting window, a purchasing agent cannot request a private budget decision, and a freshly installed coding agent cannot ask the owner’s wallet agent for deployment gas.

Existing technologies solve important parts of this problem. A2A standardizes agent communication, MCP standardizes tool invocation and protected-resource authorization, OAuth works well when every participant shares an authorization ecosystem, and smart accounts coordinate agents beneath a common wallet. What remains missing is a portable private security boundary for agents from unrelated vendors that retain independent wallets while still belonging to the same anonymous human.

HORS completes that boundary by combining three properties: independent agents, private state, and composable functions. A new agent can discover another service, prove which anonymous human backs its wallet, invoke one explicitly exposed function, and receive only the permitted answer or action. The private data remains inside the service.

The core principle is simple: share functions, not secrets. A finance service does not give another agent its bank balance, transaction history, income, credentials, or private policy. It exposes a narrow function such as canCommit(amount, purpose). A calendar service exposes findCompatibleWindow() rather than its complete event database. A wallet service exposes borrowTokens() without revealing its private key. Service owners decide the exact unit of composition.

Developers protect an existing MCP handler with one declarative policy:

service.hors("finance.canCommit", { origin: "same-human", assurance: "selfie", executor: "0g" }, handler);

Policies can admit agents backed by the same human, any verified human, or public callers. They can require no additional assurance, a live World Selfie Check, or a World Identity Check; require local or verified 0G execution; or mark a function as completely unavailable to agents. Same-human origin is therefore a prerequisite, not blanket access to everything a person owns.

Every protected invocation is verified server-side because, unlike a web browser, an arbitrary agent cannot be trusted to enforce the policy itself. The agent signs the exact service domain, function, recursively hashed arguments, policy version, nonce, expiration time, and unique call ID. HORS recomputes and verifies those values, rejects replayed or modified requests, resolves the wallet’s human origin, evaluates the function policy, and only then permits execution.

ENS answers “Where is the service?” HORS uses ENSIP-26 records on Ethereum Sepolia to resolve a name such as openagents.eth into its live MCP endpoint and service context. The name also publishes a HORS service ID, which the client verifies against the canonical HORSRegistry before trusting or caching the endpoint. Because discovery is owner-controlled, the service can move between providers without requiring every agent to be manually reconfigured.

World answers “Who backs this agent, and is the human present for this call?” World AgentKit verifies the resource-bound agent request and resolves the independent wallet through AgentBook to an anonymous humanId. Different wallets registered by the same person can therefore enter the same human security origin without sharing a vendor account or root wallet.

Sensitive functions can then require meaningful step-up assurance. Selfie Check proves that a live person is present for a specific invocation; it is not used as generic login, reputation, or KYC. Identity Check protects a higher-risk function with document-backed assurance. The current example deliberately requests no name, nationality, date of birth, document number, raw document image, or other identity attribute. HORS uses MCP’s inputRequired flow to pause the exact call, display a World App QR in a human-visible terminal, verify the response, and automatically resume the original invocation with one-use request state.

0G answers “What policy was published, and was the protected decision verifiably executed?” HORSRegistry on 0G Galileo commits the service owner, ENS name, policy version, policy content hash, Storage root, and compact function policies. 0G Storage holds the complete content-addressed policy manifest, which clients download and verify against the on-chain hash. Functions requiring the 0G executor must return an invocation-bound receipt matching the function, arguments, call ID, provider, returned content, and TEE verification status. Missing, forged, or mismatched execution evidence fails closed.

These integrations are load-bearing rather than cosmetic: ENS finds the owner-controlled service, World proves the anonymous human origin and provides call-specific assurance, 0G publishes the policy and verifies execution, and HORS turns them into one per-function policy API.

HORS is delivered as six TypeScript packages. hors-core contains protocol types, deterministic hashing, policies, replay protection, diagnostics, and the registry ABI. hors-server provides createHORS(), service.hors(), AgentBook verification, assurance handoffs, enrollment primitives, and MCP enforcement. hors-client signs calls, discovers ENS services, reads HORSRegistry, and verifies Storage manifests. hors-assurance implements Selfie and Identity verification, signal binding, and nullifier replay protection. hors-executor-0g integrates verified 0G execution. hors-cli provides connector profiles, ENS discovery, policy inspection, protected calls, QR handoffs, automatic resume, trace rendering, and an MCP bridge.

The repository also includes a Solidity HORSRegistry deployment, complete architecture and SDK documentation, a technical specification, a Codex-compatible use-hors-cli agent skill, testing reports, and two runnable examples. hello-hors isolates the central authorization claim by admitting a same-human agent and rejecting an unrelated wallet. Call Home demonstrates the complete multi-network flow.

In the live Call Home demo, a contract is ready to deploy but Codex’s fresh wallet has no gas. The user simply asks Codex: “Set me up to talk to openagents.eth,” followed by, “Ask it to send me 0.01 0G.” The agent skill establishes a fresh human-backed connector, discovers the remote wallet service through ENS, verifies its registry entry and published policy, and invokes the protected token function. HORS confirms that the caller belongs to the owner’s human origin, pauses for a live Selfie Check, resumes the bound invocation, requires verified 0G execution, and completes a real native 0G transfer on Galileo. Codex receives the tokens it needs without receiving the vault credentials, private key, or underlying decision policy.

The same service demonstrates four policy tiers: a routine same-human function, a Selfie-protected transfer, an Identity-protected emergency transfer, and a credential-export function that remains forbidden even to an agent backed by the owner.

Both Selfie Check and Identity Check have completed end-to-end operator testing. The testing documentation records the real QR handoff, automatic call resume, one-use state, verified 0G execution, and completed transfer, together with concrete developer and user feedback. Early “Where is my QR?” feedback led to the human-visible terminal workflow. Identity testing also uncovered that World App can show a blank handoff when the account lacks a verified passport; after the credential was added, the same flow passed, and the prerequisite is now documented before scanning.

HORS is the security boundary for a future personal agent network: your agents can go anywhere, across devices, wallets, and providers, while their secrets stay home.

How it's Made

HORS is built as a Node.js 20 TypeScript monorepo managed with pnpm and Turborepo, plus a Solidity registry developed with Foundry. The repository contains six reusable packages, a command-line client, a Codex-compatible agent skill, two runnable MCP examples, testing documentation, and a deployed HORSRegistry on 0G Galileo.

The central developer API is intentionally modeled after CORS middleware. A service keeps its existing MCP handler and wraps it with a declarative policy:

service.hors("home.borrow", { origin: "same-human", assurance: "selfie", executor: "0g" }, handler);

We used the MCP TypeScript 2.0 beta client/server packages and its MCP-native inputRequired/requestState flow. hors-server wraps protected tools, evaluates policy, produces structured diagnostics, and returns ordinary MCP results. hors-client wraps an existing MCP transport and intercepts only tools/call; MCP initialization, capability negotiation, and other messages pass through unchanged.

For every protected call, hors-client builds a SIWE-compatible AgentKit message using @worldcoin/agentkit-core. The signed URI is hors://<service>/<function>, and the message resources contain a recursively canonicalized arguments hash, the published policy content hash, and a random call ID. It also includes a nonce, issue time, expiry time, wallet address, chain identifier, and target domain. On the server, HORS parses the AgentKit header, validates its resource binding and freshness, recovers the signing wallet, consumes the nonce and call ID, recomputes the arguments hash from the actual MCP input, checks the policy hash, and rejects any mismatch before the handler can run.

World AgentKit is the root of the human-origin model. hors connect --fresh generates a new connector wallet, launches the AgentKit registration CLI, displays the registration flow to the human, polls AgentBook, and saves the profile only after lookupHuman(wallet) returns an anonymous humanId. The server performs the same AgentBook lookup for every protected request. For origin: "same-human", the caller’s humanId must equal the service owner’s humanId. This makes AgentKit responsible for real authorization and execution rights—not reputation, content generation, discounts, or a cosmetic proof-of-human badge.

Policies are deterministic TypeScript objects with four independent controls: origin can be same-human, any-human, or public; assurance can be none, selfie, or identity; executor can be local or 0g; and agentCallable: false makes a function unavailable to every agent, including one backed by the owner. This lets the same service expose routine, sensitive, exceptional, and human-only functions without sharing its underlying private database.

World Selfie Check and Identity Check are implemented in hors-assurance using @worldcoin/idkit-core. When a function requires step-up assurance, hors-server does not run the handler. It returns an MCP inputRequired response containing an RP context, World action, expected signal, and HMAC-authenticated requestState. That state binds the caller wallet, humanId, function, arguments hash, call ID, assurance type, action, and signal hash. The CLI renders the World connector URI as a QR code in a visible terminal, polls for completion, and automatically resubmits the original call with the proof and one-use request state. The server verifies the accepted credential result, action, signal hash, and nullifier before resuming.

One particularly hacky part was making World’s browser-oriented IDKit handoff work reliably inside a packaged Node CLI. Early versions returned challenge JSON or showed a loading state but gave the human no usable QR. We added the World app context to the MCP challenge, load IDKit’s packaged WASM directly from disk, render the connector URI with qrcode-terminal, and keep the exact invocation alive across the human handoff. We also had to treat Selfie and Identity as different credential protocols: Selfie uses signal, while the installed Identity flow uses legacy_signal and credential-specific results. That work came directly from live testing rather than mocked responses.

Identity Check is used as a risk gate for the higher-value emergency function. The example intentionally supplies an empty identityAttributes list, so it requests only document-backed attestation. It does not request a name, age, nationality, issuing country, document number, document image, or other personal attribute. Testing also revealed a real UX edge case: scanning a valid Identity QR with no verified passport produced a blank World App screen with no explanation. After adding and verifying the passport, the same flow passed, so the runbook now states the credential prerequisite before scanning.

ENS provides live, owner-controlled discovery. hors-client uses viem against Ethereum Sepolia to resolve agent-endpoint[mcp], agent-context, and the HORS-specific hors.service-id from names such as openagents.eth. It does not fall back to a hard-coded endpoint. The client parses the service ID, reads the corresponding record from HORSRegistry on 0G Galileo, and verifies that the ID equals keccak256(owner, keccak256(normalizedEnsName)) before caching the endpoint.

The ENS setup script writes the real text records and then resolves them back through the same production discovery path. Another hackathon-specific fix was necessary here: generic resolver lookup on Sepolia could return a Universal Resolver that cannot perform setText for the name. Our writer therefore reads the name’s actual resolver directly from the ENS Registry, writes each record to that resolver, waits for confirmation, and then performs the registry-bound discovery check. ENS therefore gives HORS service mobility and verifiable discovery rather than merely displaying a readable name.

The HORSRegistry contract is written in Solidity 0.8.24 and deployed on 0G Galileo, chain ID 16602, at 0x86B773d98d3A7dfE6Cc785CA8F76f7A7Ca85f7b9. Each service record stores its owner, policy version, World human origin, 0G Storage root, policy content hash, and update timestamp. Compact per-function entries store the function hash plus origin, assurance, and executor enums. The contract supports registration, atomic policy updates, ownership transfer, and policy reads, and uses packed structs and custom errors to reduce gas.

The full policy manifest is serialized as JSON and uploaded with @0gfoundation/0g-storage-ts-sdk. The resulting Storage root and keccak256 content hash are committed to HORSRegistry. When an agent inspects a service, hors-client proof-downloads the manifest from 0G Storage, hashes the exact downloaded bytes, and rejects it unless the hash matches the on-chain commitment. This gives agents inspectable policy evidence while runtime enforcement remains deterministic inside hors-server.

Verified execution is provided by hors-executor-0g. It calls the 0G Compute Router through its OpenAI-compatible API using the OpenAI TypeScript client, X-0G-Provider-Trust-Mode: verified, and verify_tee: true. The adapter extracts the provider, request ID, billing data, model usage, and tee_verified value from the 0G trace and fails closed if no provider is available or TEE verification is absent. It also supports an independent verifier callback for checking the provider, response ID, and content through a separate verification path.

We added a second server-side safeguard because returning arbitrary JSON saying “TEE verified” would be meaningless. hors-server wraps configured executors and brands genuine invocation receipts in a private WeakMap. A receipt is accepted only once and only when it matches the required executor, function name, call ID, arguments hash, provider metadata, TEE status, and hash of the actual MCP text returned to the caller. A handler cannot manufacture a valid receipt with plain JSON, reuse one from another call, change the output after inference, or silently downgrade an executor: "0g" policy to local execution.

The main working example is Call Home, a Node HTTP MCP service using @modelcontextprotocol/server, Zod v4 schemas, viem wallet clients, World assurance, ENS discovery, 0G Storage, HORSRegistry, and 0G Compute. Its private vault rules and ledger remain on the service. It exposes four policy tiers:

• home.balance — same-human origin • home.borrow — same-human + Selfie Check + verified 0G execution • home.emergency — same-human + Identity Check + verified 0G execution • home.exportCredentials — always forbidden to agents

For home.borrow, the protected arguments and private vault limits are sent to a 0G-hosted model, which returns a compact approval decision. Only after HORS validates the invocation-bound 0G receipt does the service use viem to send native 0G to the requested address and wait for the Galileo transaction receipt.

The live demo begins with a fresh Codex wallet that has no deployment gas. The user installs the included use-hors-cli skill and asks, “Set me up to talk to openagents.eth.” Codex uses the CLI to establish its World-backed human origin, resolve the service through live ENS records, verify the HORSRegistry binding, and inspect the published Storage policy. The user then asks, “Ask it to send me 0.01 0G.” HORS verifies same-human origin, pauses for a live Selfie Check, resumes the exact request, executes the private decision through verified 0G Compute, and completes a real Galileo token transfer. Codex gets the gas required to deploy without ever receiving the vault’s private key, credentials, or private rules.

We also built hello-hors as a minimal deterministic example showing that an AgentBook wallet backed by the same human is admitted while an unrelated wallet is denied. The CLI includes encrypted connector state, ENS service caching, policy inspection, diagnostics, trace rendering, automatic World handoffs, and a stdio MCP bridge. The use-hors-cli skill teaches Codex and other coding agents how to operate the flow while preserving the human boundary: the agent may prepare a sensitive call, but the World QR and approval remain visible to the person.

Selfie and Identity testing was performed end to end with World App rather than simulated proofs. The documentation records developer feedback, user feedback, real QR handoffs, automatic resume, one-use state, accepted credential parsing, verified 0G provider metadata, and completed transfer receipts.

World, ENS, and 0G are therefore load-bearing parts of one request path: ENS finds the service, World proves which anonymous human backs the caller and steps up sensitive calls, 0G publishes the policy and verifies protected execution, and HORS exposes the whole system to developers as one CORS-like function policy.

background image mobile

Join the mailing list

Get the latest news and updates