Edgent

When your edge AI agent runs out of compute, Edgent finds more, pays for it, and finishes the job.

Edgent

Created At

Open Agents

Project Description

Edgent is a peer-to-peer compute delegation daemon for edge AI agents. When an agent running on resource-constrained hardware hits an OOM error mid-inference, Edgent takes over completely and autonomously resolves the resource failure without any human intervention.

The networking layer is built on Gensyn AXL -- an encrypted P2P mesh network built on the Yggdrasil stack that runs entirely in userspace with no TUN device, no port forwarding, and no central coordinator. Every single message in Edgent flows exclusively over AXL: resource discovery broadcasts, capability advertisements, task delegation, inference results, zero-knowledge proof delivery, and payment signaling. Nothing touches a central broker. The Dell G15 runs as the bootstrap hub node listening on TLS port 9001, and the Jetson Orin Nano peers into it outbound. AXL is built from source using Go on each machine and spawned as a child process by the daemon.

The payment flow implements the x402 payment protocol over the AXL mesh instead of HTTP. After delivering the inference result, the provider sends a payment_request message over AXL containing the amount, currency, wallet address, and output commitment. The requester intercepts this signal, verifies the zero-knowledge proof, and triggers payment -- the same handshake as x402 but with an encrypted mesh as transport.

The zero-knowledge proof system uses a Circom circuit compiled with 486 non-linear constraints and a Groth16 trusted setup via snarkjs. The circuit uses Poseidon hash -- a ZK-native hash function with far fewer constraints than SHA256. The provider proves two things: that they know the preimage of the output commitment (they actually ran the job and cannot swap the result after seeing payment), and that they control the wallet receiving payment. This is the ZKaggle pattern applied to compute delegation. Proof is generated on the provider after inference and verified on the requester before a single USDC moves.

KeeperHub handles guaranteed onchain execution of the escrow release. After ZK proof verification passes, the daemon calls KeeperHub POST /api/execute/contract-call with the EdgentEscrow contract address on Base Sepolia, the release function, and the jobId and outputHash as bytes32 arguments. KeeperHub's managed wallet -- set as the operator in the contract constructor at deploy time -- signs and submits the transaction with retry logic and gas optimization. The daemon polls GET /api/execute/{executionId}/status until the transaction is confirmed, then logs the transaction hash and the basescan explorer link. KeeperHub organization wallet is funded with Base Sepolia ETH for gas and USDC for operations.

The EdgentEscrow contract is deployed on Base Sepolia at 0x8633049775ef7952DD6C169865D426D7818Fd505. It is USDC ERC20 based. The requester calls USDC.approve() followed by stake(jobId, providerAddress, amount) before sending the task. The provider calls getStake() to verify funds are locked before starting any work. Release only happens after ZK proof is verified and KeeperHub executes the call. claimTimeout lets the provider claim funds if the requester ghosts after one hour. refund lets the requester reclaim if the provider never delivers. The contract has 20 Hardhat tests passing with a MockUSDC contract.

ENS identity is fully implemented in ens.ts using viem connected to Ethereum Sepolia. Every node has an ENS name configured in its environment -- dell-g15.edgent.eth and jetson-orin.edgent.eth. These names travel in every resource_ad message across the AXL mesh and appear in the dashboard and payment logs. ENS text records are designed to carry node capabilities including the AXL public key, wallet address, available models, and version. The resolveENS() and lookupENS() functions are implemented and tested. Mainnet registration is post-hackathon.

The full demo runs on NVIDIA Jetson Orin Nano (Ubuntu 22.04, ARM64) as the requesting agent and Dell G15 (Ubuntu 20.04, x86_64) as the compute provider. Both are physically separate machines on real network interfaces. The Jetson has a funded USDC wallet on Base Sepolia. The Dell runs ollama with tinyllama. The full flow -- peer discovery, USDC staking, task delegation, inference, ZK proof generation, proof verification, KeeperHub payment execution, and onchain confirmation -- completes end to end with a real transaction hash on Base Sepolia.

How it's Made

Edgent is built in TypeScript running on Node.js with tsx for zero-build-step execution. The entire codebase is a monorepo with the daemon, agent CLI, escrow interactions, ZK proof system, KeeperHub integration, ENS resolution, and dashboard all as separate modules under src/core/.

The networking foundation is Gensyn AXL, a Go binary built from source on each machine. The daemon spawns AXL as a child process using Node.js child_process.spawn, pipes its stdout and stderr with a prefix for clean logging, and communicates with it via localhost HTTP on port 9002. AXL exposes three endpoints we use: GET /topology for peer discovery, POST /send with an X-Destination-Peer-Id header and raw binary body for outbound messages, and GET /recv which returns 204 when empty and 200 with the message body and an X-From-Peer-Id header when a message is waiting. Messages are serialized as Buffer.from(JSON.stringify(message)) on send and JSON.parse on receive. The daemon polls /recv every 500ms in a setInterval loop and routes incoming messages through a switch statement on the type field.

The x402 implementation is notably different from the standard HTTP-based spec. We route the payment_request signal over AXL instead of HTTP. This means all communication including payment negotiation stays inside the encrypted mesh. The provider sends a payment_request AXL message after delivering task_result, the requester intercepts it in the poll loop, verifies the ZK proof, and triggers KeeperHub. To bridge the timing gap between task_result arriving and payment_request arriving, we use a pendingOutputs Map keyed by requestId to temporarily store the inference output between the two messages.

The ZK proof system required building a Circom 2.0 circuit from scratch. The circuit uses Poseidon hash from circomlibjs rather than SHA256 because Poseidon is ZK-native with far fewer constraints -- our circuit compiles to 486 non-linear constraints versus thousands for SHA256. The circuit takes outputBits as a 256-bit private input, splits it into two 128-bit chunks using Bits2Num components from circomlib, hashes them with Poseidon to produce the outputCommitment public signal, and separately hashes the provider's AXL public key split into two bigints for the walletCommitment public signal. The trusted setup uses a downloaded powers-of-tau file at ceremony level 12, sufficient for our constraint count. snarkjs groth16.fullProve() generates the proof on the provider side taking 10-30 seconds, and groth16.verify() checks it on the requester side in under a second. The verification key JSON is committed to the repo.

One notably hacky but necessary thing: AXL raw /send between two processes on the same machine fails with 502 because the gVisor internal TCP stack cannot route to mesh IPv6 addresses on loopback. This was a significant debugging challenge during development on Mac. The solution was to use separate machines for real hardware testing. The daemon catches these 502 errors gracefully with try/catch and logs them as expected on localhost. On real hardware between separate machines it works correctly.

The escrow interactions use viem with a manually defined minimal ABI -- only the four functions we actually call: stake, release, getStake, and claimTimeout. stakeForJob() does two sequential transactions: USDC.approve(escrowAddress, amount) then EdgentEscrow.stake(jobId, providerAddress, amount). Both wait for transaction receipt before proceeding. USDC has 6 decimals so we use parseUnits from viem instead of parseEther. The jobId is generated as a UUID with dashes stripped and padded to 64 hex characters to satisfy the bytes32 type.

KeeperHub integration uses their Direct Execution API with a kh_ organization API key. The functionArgs field must be a JSON array string -- not a parsed array -- which was a subtle requirement discovered from the docs. The outputHash from snarkjs publicSignals is a decimal bigint string that must be converted to 0x-prefixed 32-byte hex before passing to KeeperHub. We do this with BigInt(decimalString).toString(16).padStart(64, 0). After the initial POST returns an executionId, we poll the status endpoint every 3 seconds until status is completed or failed, with a 2-minute timeout.

The resource_ad peer selection flow uses a Node.js EventEmitter to bridge the async gap between the AXL poll loop and the HTTP /delegate endpoint. When /delegate receives a request, it broadcasts resource_request to all peers, registers a one-time event handler on the routerEvents emitter, and awaits a Promise that resolves when a resource_ad event fires. The handler filters stale ads using a requestSentAt timestamp and validates that the responding nodeId is in the known peers list to prevent spoofing.

The dashboard is a single HTML file with no framework and no build step. It polls /status and /jobs every 2 seconds using fetch, updates the DOM directly, and uses CSS custom properties for the dark theme. The /jobs endpoint serves a recentJobs array maintained in memory, populated by both sides -- the requester pushes on payment_request completion and the provider pushes on payment_confirmed receipt.

The install.sh script handles the full setup: detects Linux architecture (x86_64 or ARM64 for Jetson), installs Node.js 18+ via NodeSource if missing, installs Go 1.22.3 from go.dev/dl if missing, clones the repo, runs npm install, clones the AXL source repo separately and builds it with go build -o node ./cmd/node, copies the binary to axl-bin/, copies .env.example to .env if not present, and checks for ollama and tinyllama. The whole setup runs in a single curl pipe to bash.

The hardhat test suite uses viem instead of ethers -- we chose the TypeScript project with Mocha and Viem option during init. This required replacing ethers.id() with keccak256(toHex()) from viem, replacing ethers.parseEther() with parseUnits() for USDC decimals, and replacing the emit assertion pattern with balance diff checks since the viem hardhat toolbox does not support the chai emit matcher. MockUSDC is a minimal ERC20 with a mint function that lets tests fund wallets without needing real tokens. 20 tests cover the full contract surface including timeout and refund paths with time.increase() from hardhat-network-helpers.

background image mobile

Join the mailing list

Get the latest news and updates

Edgent | ETHGlobal