Operator configuration reference

A selling node, its adapters and a private node, plus the facilitator, bundler, relay, indexer, billing, deposit service, staking node, agent runner and monitor that complete the stack, are configured through environment variables. Each default is the one the code reads, with the reading file named, so you never open the source to learn a setting. All are optional on the local development network: with nothing set, the stack targets Anvil at http://127.0.0.1:8545 with deterministic test accounts. offchain/.env.example is the copy-and-edit template.

Two files every runtime depends on are not environment variables. deployments.json holds the contract addresses for your chain, loaded from the working directory (offchain/): npm run deploy writes it for the local development network; a live chain uses that network's addresses file (deploying, networks.json). A runtime refuses to start without it; the Docker image mounts it read-only. services.json is the multi-service runtime's catalogue (node quickstart); any header or key written env:NAME resolves from the environment at load time and fails loudly if missing (FAL_KEY for the fal.ai adapter, say).

Booleans are the literal string 1 (or 0 where noted). Durations are milliseconds unless the name says otherwise. Token amounts are base units: USDC has 6 decimals, ZAI and ETH 18.

Chain, keys, and live-chain mode — src/config.ts

Settings every runtime shares: which chain it talks to, which key it signs with, and whether it may behave like a development network at all.

VariableDefaultWhat it does
RPC_URLhttp://127.0.0.1:8545JSON-RPC endpoint every runtime talks to.
CHAIN_ID31337Chain id used for EIP-712 domains and sanity checks.
REAL_CHAINunset1 flips live-chain mode. Setting DEPLOYER_PRIVATE_KEY or USDC_ADDRESS flips it too. In this mode no mock tokens are deployed, nothing is minted, confirmation defaults rise to 2, and no runtime will fall back to the public Anvil mnemonic — it throws instead.
OPERATOR_KEYSTORE_FILEunsetPath to an encrypted JSON keystore holding the operator key. Preferred production form.
OPERATOR_KEYSTORE_PASSWORD / OPERATOR_KEYSTORE_PASSWORD_FILEunsetThe keystore password, inline or read from a file (secret-manager friendly). Required with the keystore.
OPERATOR_PRIVATE_KEYunsetRaw hex operator key. Precedence: keystore, then this, then the local development network's Anvil index.
AIOUM_DEPLOYMENTSdeployments.jsonThe manifest a runtime reads, relative to offchain/. deploy.ts writes to AIOUM_DEPLOYMENTS_OUT; point both elsewhere to run a throwaway chain beside a live one.
RELAYER_PRIVATE_KEYunsetShared gas-paying key for the facilitator and bundler on a live chain.
FACILITATOR_PRIVATE_KEY / BUNDLER_PRIVATE_KEYunsetPer-service override of RELAYER_PRIVATE_KEY.
FACILITATOR_RELAYER_INDEX / BUNDLER_RELAYER_INDEX3 / 21Local development network: which Anvil account each relayer uses, so they never fight the operator over nonces.
FACILITATOR_URL / NODE_URLhttp://127.0.0.1:8547 / http://127.0.0.1:8546Where the demo and SDK clients look for the facilitator and the node.
LOG_LEVELinfodebug, info, warn or error (src/logger.ts).

Service node — src/node/server.ts (single service) and src/node/multinode.ts (catalogue)

Where the node listens, where it keeps receipts (vouchers, in the code) and transcripts, who may settle, how much load it accepts, and which environment names the adapters read upstream keys from.

VariableDefaultWhat it does
AIOUM_NODE_PORT8546Listen port of the single-service and multi-service runtimes.
AIOUM_NODE_DIR~/.aioum-nodeDurable state: accepted vouchers, transcripts, the instance lock, the shared SQLite store, the billing ledger. Wipe it after resetting the local development network (session ids restart).
AIOUM_NODE_TOKENunsetBearer token required on POST /settle and /metrics. Unset means those endpoints are open and the node prints a warning at boot. Set it before exposing a node. /mcp stays public; it is paid per call.
AIOUM_NODE_RATE_MAX / AIOUM_NODE_RATE_WINDOW_MS120 / 60000Per-IP request budget per window before 429.
AIOUM_NODE_MAX_INFLIGHT50Concurrent /mcp + /settle requests before the node sheds load with 503.
AIOUM_NODE_BODY_LIMIT / AIOUM_NODE_MCP_BODY_LIMIT64kb / 16mbMaximum request body, and the larger one a tool call may carry (a vision judge's frames ride inline). A body over the limit is answered as a JSON-RPC error naming it.
AIOUM_NODE_TRUST_PROXYunset1 behind a reverse proxy (see deploy/Caddyfile) so rate limiting keys on X-Forwarded-For. Never set it on a directly exposed node.
AIOUM_HEALTH_RPC_TIMEOUT_MS3000How long /health waits on the RPC before reporting it down.
AIOUM_SELF_STAKEunsetZAI of your own to stake at first boot (single-service runtime; a listing's stakeZai plays the role in services.json). Unset: 10,000 when the operator wallet holds that much, so the node is live at once; else 100, enough to list, live once backers bring the total to 10,000.
AIOUM_SETUP_MAX_ATTEMPTS / AIOUM_SETUP_BACKOFF_MS5 / 1000Boot-time retries for stake and registration transactions (multi-service runtime).
AIOUM_SETUP_CONCURRENCY8How many listings the multi-service runtime registers at once at boot, per operator.
AIOUM_ADMIN_ALLOW_ADDunset1 lets the admin API list new services on a live chain (POST /admin/services: the Earn page's Go live now, aioum sell add, aioum sell skill, the importer's go-live, a published agent); the local development network always allows it.
AIOUM_ADMIN_OPERATOR_INDEX24Local development network: the Anvil account that signs for listings added through the admin API.
AIOUM_PREVIEW_TIMEOUT_MS45000How long POST /admin/preview (the Earn page's test call, aioum sell preview) waits for the upstream.
AIOUM_NODE_SKILLS_DIR / AIOUM_NODE_SKILL_BODY_LIMIT<AIOUM_NODE_DIR>/skills / 24mbWhere POST /admin/skills writes an uploaded skill's folder (200 files, 1 MB each, 8 MB in all), and the largest upload it takes.
AIOUM_DOCKERdockerThe docker binary a compute listing and the mcp verify check run.
AIOUM_TEMPLATES_DIRoffchain/templatesWhere npm run templates, the generators, the bootstrap and the runner's pipelines read the gallery (the OpenAPI importer writes its own set elsewhere and points this at it to verify).
AIOUM_CATALOGUE_MCPunsetnpm run catalogue:bootstrap: remote lists the MCP templates whose servers run elsewhere (http: nothing of them runs on this machine), verified the ones whose check passed, all every keyless one; unset, none. A template with hostAccess is never in a bootstrap catalogue.
AIOUM_MCP_SANDBOXdocker when docker answersHow the node serves a stdio MCP server, a stranger's code: docker runs it inside a container with nothing of the host mounted, host runs it on the machine on the operator's word. With neither, it is not served and the reason names both. AIOUM_DOCKER_BIN names the docker binary.
AIOUM_HARDWARE_FILE.deploy/hardware.jsonWhere npm run hardware:check writes the machine's report and the bootstrap reads it to gate local templates by their requires.
HF_TOKENunsetRead when a model's size is looked up on the Hugging Face Hub (the optimizer and the private node's install check) and by train.export to Hugging Face.
GITHUB_TOKEN / GH_TOKENunset, then gh auth tokenThe skills index generator's GitHub API token: without one, 60 calls an hour and no code search.
SMITHERY_API_KEYunsetLets the MCP generator read Smithery's registry; without it that source is skipped and said so.
AIOUM_PRICING_TICK_MS5000How often the multi-service runtime checks each listing with a pricing block for an elapsed window (below).
AIOUM_NODE_PUBLIC_URLhttp://127.0.0.1:<port>The URL the node is reached at (behind TLS: https://node.<domain>, which scripts/deploy-network.sh sets). Media URLs use it, and the node announces it to the directory at boot and every AIOUM_ANNOUNCE_MS (POST <AIOUM_INDEXER_URL>/refresh { nodeUrl }); GET /identity?nonce=&url= answers with each operator's signature over the nonce and this URL, and only this URL.
AIOUM_ANNOUNCE_MS600000 (10 min)How often the node re-announces its public URL to the directory; 0 disables the timer (the boot-time announcement stays).
AIOUM_SERVICES_CONFIGunsetPath to services.json. Unset, the multi-service runtime serves the built-in demo catalogue.
AIOUM_COMMIT_RESULTSon0 stops the single-service runtime recording call transcripts and anchoring the Merkle results root before settlement. Leave it on: services flagged commitRequired cannot settle without a root.
AIOUM_TRANSCRIPT_RETENTION_MS604800000 (7 days)How long a session's transcript is kept after its last call. Keep it longer than settleChallengeWindow so evidence outlives the dispute window.
FAL_KEYunsetRead by the fal.ai adapter when services.json says "apiKeyEnv": "FAL_KEY" (the default). Any other env:NAME reference works the same way.
APIFY_TOKENunsetDefault token env for the apify adapter: an Apify API token or an x402-purchased prepaid token.
BRIGHTDATA_API_KEYunsetDefault key env for the brightdata adapter (Web Unlocker, SERP and Web Scraper APIs).

Three services.json fields per listing, beside priceUsdc and stakeZai:

FieldDefaultWhat it does
templatethe gallery template whose service key matches, else noneNames the template the listing runs. Shown in /services, the index and on the service page, where Run your own copy and the CLI/MCP one-liners come from. npm run templates -- add, aioum sell add --template, aioum_sell_add and the Earn page stamp it.
pricingunset (fixed price){ "base", "max", "targetInflight", "window", "step" } in USDC, USDC, calls, seconds, fraction. Defaults: base = priceUsdc, targetInflight 2, window 300, step 0.25. Once per window the node compares the average calls in flight over the window with the target and moves the on-chain price one step up (at or above) or down toward base (below), never outside base–max, logged as [pricing] <key>: <old> → <new> (<reason>). /services reports pricing (current, base, max, last decision) and, for every listing, load (in flight, calls in the window, calls in the last hour). priceUsdc must sit between base and max; after a restart the chain's price is kept, clamped to the band. CLI --price-max, MCP pricingMax.
providerunset{ "domain": "api.example.com" }: the listing is the provider's own. The node reports it in /services; the indexer checks on every refresh that the domain names the listing's operator, by a DNS TXT record aioum-verify=<operator address> on the domain or https://<domain>/.well-known/aioum.json containing { "operators": ["0x…"] }, and marks the listing official. GET /admin/verify-domain?domain= on the node prints both records for its operator and what each check finds now (aioum sell verify-domain, aioum_sell_verify_domain). The OpenAPI importer sets it from the spec's server host.
allowHost (on an mcp tool)unsetAn MCP server whose template carries hostAccess reads files, runs commands or drives a browser on the node it runs on. The node refuses to load it, "set "allowHost": true on the listing to run it here anyway", until the listing says so; never in a bootstrap catalogue.
pricing (on a skill tool){ "mode": "per-use" }A skill listing's terms: { "mode": "per-use" | "one-time", "priceUsdc" }. Per use, every skill.get is a paid call; one-time, an agent pays once and keeps the files. /services reports mode and priceUsdc under pricing.

Templates in the gallery (offchain/templates/*.json) add verified (set only by npm run templates -- verify), generated: { source, at } on generator-written ones, alsoOn (other templates serving the same model), local (needs Ollama or Docker on the box), requires: { gpu?, vramGb?, ramGb?, cpus? } (the bootstrap lists it only on a machine that meets it) and hostAccess (Ready-made listings).

Settlement loop — src/node/settle-loop.ts

A crash-safe background loop settles earned receipts on-chain and never drops an entry: past the deadline an entry is parked and retried at a slower cadence until it lands. These settings tune how eagerly it tries and how far fees may climb.

VariableDefaultWhat it does
AIOUM_SETTLE_INTERVAL_MS15000How often the loop scans for sessions to settle.
AIOUM_SETTLE_JITTER_MS30000Privacy: random delay before an auto-settle's first attempt, so settle timing does not fingerprint call timing. 0 disables. Forced settles are never jittered.
AIOUM_SETTLE_WAIT_MS60000How long one attempt waits for its transaction to mine before fee-bumping.
AIOUM_SETTLE_FEE_BUMP_PCT125Fee multiplier per bump (125 = +25%, compounding).
AIOUM_SETTLE_MAX_FEE_BUMPS10Caps how far fees escalate, not how many retries happen.
AIOUM_SETTLE_DEADLINE_MS1800000 (30 min)Fast-retry window per pending settlement.
AIOUM_SETTLE_PARKED_RETRY_MS300000 (5 min)Retry cadence for parked entries after the deadline.
AIOUM_SETTLE_CONFIRMATIONS2 live chain, 1 local development networkConfirmations before a redeemed voucher is forgotten. A reorg keeps the entry queued.
AIOUM_NODE_IDLE_SESSION_S120The idle close (catalogue node): a session the node holds a zero voucher for (the client handed it at open, POST /vouchers) and that served no call for this long is settled for 0 by the loop, so the client's deposit returns and the operator's session slot frees without waiting out the inactivity timeout. A session with a paid voucher is never closed this way. 0 turns it off.

Multi-instance (single-box HA) — src/node/sqlite-store.ts

When one process is not enough, several instances serve the same operator key from one box, sharing state through SQLite and electing one to settle.

VariableDefaultWhat it does
AIOUM_STOREunsetsqlite switches vouchers, transcripts and the settle queue to one shared SQLite database (WAL) under AIOUM_NODE_DIR, so N instances can run under one operator key. Unset, each instance keeps its own JSON files and the instance lock allows exactly one process per data dir.
AIOUM_SETTLE_LEASE_MS20000TTL of the settle-leader lease. A killed leader is replaced within this time; followers accept /settle and enqueue.

Privacy relay — src/relay/server.ts

A relay is an operator whose service is "proxy any other AIOUM service", so a buyer's calls never link on-chain to the node that served them. It shares the node's auth, rate and body settings.

VariableDefaultWhat it does
AIOUM_RELAY_PORT8553Listen port (8548 belongs to the bundler).
AIOUM_RELAY_TARGETShttp://127.0.0.1:8546Comma-separated node URLs the relay is willing to proxy to.
AIOUM_RELAY_MARGIN_BPS3000Per-call markup over the target's price. Break-even is about 1770 bps at the default 5% fee + 10% delegator split.
AIOUM_RELAY_DEPOSIT50000000 (50 USDC)Deposit per downstream session the relay opens on your behalf.
AIOUM_RELAY_KEY_INDEX4Local development network: the Anvil account the relay operates from.
AIOUM_RELAY_PRIVATE_KEYunsetLive chain: the relay's own key, required there; it stakes and settles like a node, so it is never the catalogue node's key.
AIOUM_RELAY_CATALOG_TTL_MS30000How long the relay caches each target's /services catalogue.

Facilitator — src/facilitator/server.ts

The facilitator relays a buyer's signed payment authorization on-chain so the agent never needs gas of its own. It holds only a gas key; these settings are its guard rails.

VariableDefaultWhat it does
FACILITATOR_API_KEYunsetIf set, POST /open/eip3009 requires Authorization: Bearer <key> (or an X-API-Key: <key> header).
FACILITATOR_ASSETSunsetExtra comma-separated payment assets to relay; USDC is always allowed.
FACILITATOR_MAX_AMOUNT1000000000000 (1,000,000 USDC)Per-request ceiling in base units.
FACILITATOR_MAX_INFLIGHT25Concurrent relays before 503.
FACILITATOR_RATE_MAX / FACILITATOR_RATE_WINDOW_MS60 / 60000Per-IP request budget before 429.
FACILITATOR_RELAYER_MIN_WEI100000000000000000 (0.1 ETH)/health reports degraded below this relayer balance.
FACILITATOR_REPLAY_TTL_MS3600000 (1 h)How long a seen authorization is remembered for replay rejection.
FACILITATOR_STUCK_TX_MS20000Wait before a same-nonce fee-bump replacement.
FACILITATOR_MAX_FEE_BUMPS5Fee-bump replacements before giving up with 504.
FACILITATOR_HEALTH_RPC_TIMEOUT_MS3000RPC timeout for /health.
FACILITATOR_TRUST_PROXYunset1 behind a reverse proxy, as for the node.

Bundler (ERC-4337 relay) — src/bundler/server.ts

The bundler submits smart-account operations for agents that run on session keys, sponsoring their gas within a per-account budget.

VariableDefaultWhat it does
BUNDLER_PORT8548Listen port.
BUNDLER_RATE_MAX / BUNDLER_RATE_WINDOW_MS120 / 60000Per-IP request budget before 429.
BUNDLER_MAX_INFLIGHT25Concurrent handleOps submissions before 503.
BUNDLER_EPOCH_MS / BUNDLER_EPOCH_GAS_CAP3600000 / 8000000Sponsored-gas budget per account per epoch, counted in gas used. A paid call from the command line opens a session, about 420,000 gas: 8 million is nineteen calls an hour. The local stack starts the bundler at 40 million.
BUNDLER_BASEFEE_BUFFER_BPS10000 (1.00×)Markup applied to the observed base fee when pricing an op.
BUNDLER_RELAYER_MIN_WEI100000000000000000 (0.1 ETH)Degraded-health threshold for the relayer balance.
BUNDLER_PAYMASTER_MIN_DEPOSIT_WEI50000000000000000 (0.05 ETH)Degraded-health threshold for the paymaster's EntryPoint deposit.
BUNDLER_STUCK_TX_MS / BUNDLER_MAX_FEE_BUMPS20000 / 5Stuck-transaction replacement policy, as for the facilitator.
BUNDLER_MAX_FEE_CAP_BPS20000 (2×)Ceiling on fee escalation, as bps of the op's own maxFee.
BUNDLER_CONFIRMATIONS2 live chain, 1 local development networkConfirmations before a handleOps success is reported.
BUNDLER_REPLAY_TTL_MS900000 (15 min)Replay-guard memory for submitted ops.
BUNDLER_HEALTH_RPC_TIMEOUT_MS3000RPC timeout for /health.

Discovery indexer — src/indexer/server.ts

The indexer is the directory behind discovery: it rescans the chain for listings, probes live nodes for their catalogues, and is what an agent searches when it finds tools on its own mid-run. It probes the nodes in its seed list and every node that announced itself: POST /refresh { "nodeUrl": "https://node.example.com" } makes it ask that URL for GET /identity and, when every operator the node claims has signed the challenge, keep the node (nodes.json in its data dir), probe it on every refresh and map to it only the listings of the operators it proved. GET /nodes lists the seeds and the announced nodes. A listing on-chain whose node is neither a seed nor announced has no endpoint in the directory, and no agent can call it.

VariableDefaultWhat it does
AIOUM_INDEXER_PORT8549Listen port.
AIOUM_INDEXER_REFRESH_MS30000How often the chain is rescanned and seeds re-probed.
AIOUM_INDEXER_SEEDShttp://127.0.0.1:8546,http://127.0.0.1:8553Node and relay URLs to probe for live catalogues; nodes that announce themselves with proof are probed as well (above).
AIOUM_INDEXER_PROBE_TIMEOUT_MS4000Per-seed HTTP timeout.
AIOUM_INDEXER_DIR~/.aioum-indexerHolds demand.json: every listing's price history (from ServiceRegistered and ServiceUpdated events) and the sessions settled in the last seven days, kept across restarts; emptied when the chain resets.
AIOUM_INDEXER_FROM_BLOCK0 on the local network, else the tip minus 50,000First block of the event scan on the first run; later runs continue from the last scanned block.
AIOUM_INDEXER_SCAN_CHUNK5000Blocks per eth_getLogs request.
AIOUM_INDEXER_EXPERIENCE_DAYS / AIOUM_INDEXER_EXPERIENCE_MAX30 / 20000How long, and how many, experience records are kept in experience.json (below).

Each record in /services carries template (from the node, or the listing key when the node is unreachable), pricing and load as the node reports them, and demand: the price change over 24 h, calls per hour with its basis (node: the node's own count of calls served in the last hour; settled: sessions settled on-chain in the last hour, settled amount ÷ the price in force), and how many live listings run the same template. GET /services/:id/demand adds the price history, the last settled sessions and every live copy of the template, cheapest first. A listing whose node reports provider.domain carries provider (domain, verified, method: dns or well-known, checkedAt, detail in plain words) and official: true once the domain names its operator; a verified pair is rechecked after an hour, a failed one after ten minutes. Ranking puts official listings first among the live and reachable; ?official=1 filters to them.

Each record also carries experience: per need (the kind of job, data:crypto, image:gen), what agent runners' runs found with the listing: count (one record per session per tool), okRate (each record weighed by what it paid; a failed call one call at the listing's price), avgJudge (the judge's mean, 0-100, when judged), p50LatencyMs, avgCostUsdc and the latest five notes; GET /services/:id/experience has it per tool with the latest records. Runners post the records to POST /experience, signed with the wallet that paid; the indexer takes one only when the signature recovers to the client of that session on chain, the session is with that service and has settled (an error, which charges nothing, on the open session), the signer is not the listing's operator, and the session and tool are not recorded yet; it keeps them in experience.json, pruned by age and count.

Managed billing — src/billing/server.ts and src/billing/store.ts

Managed billing lets a person pay by card and have an agent's gas and deposits sponsored from that balance; these settings name its admin token, signers and price basis.

VariableDefaultWhat it does
AIOUM_BILLING_PORT8550Listen port.
BILLING_ADMIN_TOKENunsetBearer token for the admin API (accounts, grants, plans, renewals) and the top-up webhook. Unset disables admin routes.
SPONSOR_SIGNER_KEYlocal development signerKey that signs AIOUMVerifyingPaymaster sponsorships. Must match the paymaster's sponsorSigner() on-chain.
SPONSOR_VALID_SECONDS600Validity window stamped into each sponsorship signature.
BILLING_TREASURY_KEYlocal development account 0Key that funds user deposits from the treasury. Required on a live chain for /fund-deposit.
ETH_USD_CENTS300000 ($3,000)Price used to convert sponsored gas into credits.
The ledger database lives under AIOUM_NODE_DIR.

Deposit service — src/deposits/server.ts

The deposit service funds an AI wallet from any chain in any major asset: it quotes a route to USDC on this network from the route provider, hands out the wallet's deposit address, keeps the ledger and sweeps what arrives (the conversion fee to the treasury, the rest to the wallet). It starts in one of two modes: route when the provider delivers to this chain, and direct on any other chain, the local development network (chain 31337) among them, where it serves only the deposit address and the sweep of USDC sent straight to it; there /chains, /tokens and /quote answer 501 with "Routes from other chains deliver to Base; on this network send USDC to your deposit address and it is swept to your AI wallet". The fee is depositFeeBps from deployments.json (DEPOSIT_FEE_BPS at deploy).

VariableDefaultWhat it does
AIOUM_DEPOSITS_PORT / AIOUM_DEPOSITS_HOST8551 / 127.0.0.1Bind address. Loopback by default; bind elsewhere only behind TLS.
AIOUM_DEPOSITS_DIR~/.aioum-depositsThe ledger (deposits.json): every deposit with its source, status, what was expected, credited and taken as fee, and the sweep transaction; and wallets.json, every wallet that asked for its deposit address, whose address the loop sweeps from then on.
LIFI_API_URLhttps://li.quest/v1The route provider's API.
LIFI_API_KEYunsetSent as the provider's API key header on every request when set.
AIOUM_DEPOSITS_SWEEP_MS30000How often the loop refreshes pending deposits against the provider and sweeps every known deposit address that holds USDC.
DEPOSITS_PRIVATE_KEYunsetLive chain: the sweeper, the key that pays gas for sweepFor, and required there. It holds no user funds; a forwarder pays only the treasury and the wallet.
DEPOSITS_ACCOUNT_INDEX26Local development network: the Anvil account the sweeper uses.
AIOUM_DEPOSITS_PUBLIC_URLhttp://127.0.0.1:<port>The address a card partner's webhook reaches the service at (/card/webhook/<partner>); GET /card/partners prints the URLs to register.
CDP_API_KEY_ID (or CDP_API_KEY_NAME) / CDP_API_KEY_SECRETunsetPay by card through Coinbase Onramp.
TRANSAK_API_KEY / TRANSAK_API_SECRET / AIOUM_CARD_RETURN_URLunsetPay by card through Transak; TRANSAK_ENV=staging for its test environment.
MOONPAY_API_KEY / MOONPAY_SECRET_KEYunsetPay by card through MoonPay; MOONPAY_WEBHOOK_KEY verifies its webhooks.
STRIPE_SECRET_KEYunsetPay by card through Stripe; STRIPE_WEBHOOK_SECRET verifies its webhooks.
AIOUM_CARD_ABANDON_HOURS24A card deposit still pending after this long is marked abandoned. Card payments exist on Base (chain 8453) only; the partners deliver USDC there.

Staking node — src/backer/server.ts

The network's staking node backs listings so a seller goes live with 100 ZAI of their own: POST /requests {serviceId} starts a review (the listing on-chain and on its node, the operator's own floor, slashes and disputes, the price against the gallery, the pool's cap, a sample call through the node, then a model against the rubric); on a yes the pool delegates the gap to minNodeStake plus headroom, makes one paid call through the node, and the listing is live. A loop re-checks every backed node and takes the ZAI back, with the reason, when the node stops answering, a dispute is raised or the operator is slashed. GET /requests/:id, GET /requests?operator=, GET /health (the pool's balance, what it holds behind operators, the reviewer) and GET /rubric.

VariableDefaultWhat it does
AIOUM_BACKER_PORT / AIOUM_BACKER_HOST8552 / 127.0.0.1Bind address.
AIOUM_BACKER_DIR~/.aioum-backerThe ledger (requests.json): every request with its states and their times, checks, review, reasons, stake and transactions.
BACKER_PRIVATE_KEYunsetLive chain: the pool's wallet, required there. It holds the ZAI it delegates, a little USDC for the sample call it makes through each node, and gas.
BACKER_ACCOUNT_INDEX / BACKER_POOL_ZAI25 / 100000Local development network: the Anvil account the pool uses, and the ZAI minted to it at boot.
BACKER_MAX_PER_OPERATOR_ZAI20000The most the pool puts behind one operator, counting what it already holds there.
BACKER_HEADROOM_SESSIONS2Sessions of capacity above the node floor, tokensPerSession each, added to the delegation when the cap allows.
BACKER_RUBRICsrc/backer/rubric.mdWhat the review model reads.
BACKER_REVIEW_SERVICEunsetA judge listing's id (<id> or <id>:<tool>) to review through, paid per review. Without it the review uses ANTHROPIC_API_KEY (BACKER_REVIEW_MODEL, else AIOUM_AGENTS_MODEL), then AIOUM_AGENTS_OPENAI_BASE_URL with its key and model, then a local Ollama (BACKER_OLLAMA_MODEL, else its first model). No reviewer, no approvals.
BACKER_REVIEW_MS60000How often the loop re-checks every backed node.
BACKER_UNHEALTHY_STRIKES3Failed health checks in a row before the backing is withdrawn.
BACKER_LOOKBACK_BLOCKS100000How far back the slash and dispute checks look at review time.
BACKER_NODE_SEEDSunsetNode URLs to ask for a listing when the directory (AIOUM_INDEXER_URL) has no endpoint for it.
AIOUM_NODE_TOKENunsetSent to a node's /admin/preview and /settle when its admin API is gated; a closed admin API leaves the sample to the paid call after backing.

Monitor and paymaster tooling — src/monitor/watch.ts, src/tools/paymaster.ts

npm run monitor polls the chain and the services' /health endpoints, exposes Prometheus metrics, and raises alerts. npm run paymaster reports the paymaster's EntryPoint deposit and runway and can refill it.

VariableDefaultWhat it does
MONITOR_PORT8549Listen port. Same default as the indexer — set one of them when both run on one box.
MONITOR_HOST127.0.0.1Loopback by default because /metrics and /health are unauthenticated. Bind elsewhere only with a scrape token.
MONITOR_SCRAPE_TOKENunsetRequired, and gates /metrics, whenever the monitor is bound off loopback.
ALERT_WEBHOOK_URLunsetWhere alerts are POSTed. Unset means log-only.
MONITOR_SLASH_ALERT_WEI1000000000000000000000 (1,000 ZAI)Alert on any slash at least this large.
MONITOR_SESSION_VOLUME_ALERT100Alert when more sessions than this open in one poll window.
MONITOR_PAYMASTER_MIN_DEPOSIT_WEI100000000000000000 (0.1 ETH)Alert when the paymaster's EntryPoint deposit drops below this.
MONITOR_HEALTH_POLL_MS30000How often the services' /health endpoints are probed.
MONITOR_CONFIRMATIONS2 live chain, 0 local development networkReorg tolerance: a block is inspected for reverted settles only once it is buried this deep (the local development network inspects the tip).
TIMELOCK_ADDRESSunsetWhen set, the monitor also watches Timelock queue and execute events.
AIOUM_PAYMASTER_MIN_ETH0npm run paymaster exits non-zero when the deposit is below this many ETH, so a cron job can alert. 0 disables.

Deploy, handover, and the safety gate — src/deploy.ts, src/handover.ts, src/verify-deploy.ts

Read once, by the deploy and governance tools, not by the long-running services. You set them in order when deploying the stack.

VariableUsed byWhat it does
DEPLOYER_PRIVATE_KEYdeployFunded EOA that deploys. Mandatory on a live chain; ownership moves to the Timelock afterwards.
USDC_ADDRESS, ZAI_ADDRESS, PERMIT2_ADDRESS, ENTRYPOINT_ADDRESSdeployCanonical infrastructure to reuse instead of deploying mocks. ENTRYPOINT_ADDRESS is required on a live chain. Per-chain values are catalogued in networks.json.
TREASURY_ADDRESS / ARBITRATOR_ADDRESSdeployProtocol-fee sink and the DisputeManager's arbitrator. Both required on a live chain.
USE_VERIFYING_PAYMASTER / SPONSOR_SIGNER_ADDRESSdeployDeploy the signature-gated paymaster — set 1 to opt in on the local development network; a live chain deploys it when SPONSOR_SIGNER_ADDRESS is set and skips it otherwise — and the address allowed to sign sponsorships. Live-chain deploys ship it unfunded.
ESCROW_CAP_USDC / SLASH_BPSdeployDeploy-time economics: escrow cap per staked ZAI, and the proportional slash in bps. See economics.
MIN_SELF_STAKEdeployAn operator's own stake floor in ZAI (100); the node floor of total stake, own + delegated, is 10,000. Both governance-settable afterwards (setParams).
AIOUM_DEPLOYMENTS_OUTdeployWhere deploy.ts writes the manifest (deployments.json in offchain/); point it elsewhere to try a deploy on a throwaway chain without touching the live one.
DEPOSIT_FEE_BPSdeployThe conversion fee on deposits from other chains and assets, in bps (100 = 1%, the default; at most 1000). Baked into the DepositForwarderFactory and written to deployments.json as depositFeeBps.
DAO_OWNER, TIMELOCK_ADDRESS, CONFIRM_HANDOVER, ALLOW_EOA_OWNERhandoverTransfers every governance contract to the DAO. Irreversible, so the target is validated and CONFIRM_HANDOVER=1 is required; ALLOW_EOA_OWNER=1 is for test networks only.
TIMELOCK_ADDRESS / DAO_OWNERverify-deployThe owner every contract must report for npm run verify-deploy to pass. TIMELOCK_ADDRESS wins when both are set.

Private node — src/private-node/*.ts

The self-hosted personal runtime (Your private node) keeps secrets, a local context store and scheduled workflows on your own machine, and pays marketplace nodes from a budget-capped agent wallet.

VariableDefaultWhat it does
PRIVATE_NODE_PASSPHRASEunsetscrypt key for the AES-256-GCM secret store. Unset, the node generates a 0600 keyfile under PRIVATE_NODE_DIR instead and warns you to back it up: losing it loses your secrets.
PRIVATE_NODE_PORT / PRIVATE_NODE_HOST8770 / 127.0.0.1Bind address. 0.0.0.0 on a server the app reaches from another machine by address (every call carries the node's token; open the port to that machine alone, or put TLS in front with deploy/Caddyfile). Bound beyond loopback, the node answers to its own IP or PRIVATE_NODE_PUBLIC_HOST as host; bound to loopback it refuses every foreign host.
PRIVATE_NODE_PUBLIC_HOSTunsetThe name a server node is reached by, when it has one (an IP needs no setting).
~/.aioum-private/envwritten by install-node.shThe node's own settings, one KEY=VALUE a line, read before the environment at every start: the chain (AIOUM_DEPLOYMENTS, REAL_CHAIN, CHAIN_ID, RPC_URL), the directory and the faucet (AIOUM_INDEXER_URL, AIOUM_FAUCET_URL), how it binds. The environment wins; a key, token or passphrase named in the file is ignored and said so.
AIOUM_FAUCET_URLunsetThe testnet faucet the node claims from for its wallet (GET/POST /wallet/faucet): ZAI and the testnet dollar once per wallet, the proof of work solved on the node.
PRIVATE_NODE_DIR~/.aioum-privateWhere secrets.enc, the bearer token, jobs and the local brain live.
PRIVATE_NODE_TOKENgeneratedBearer token for the API; unset, one is generated and stored in PRIVATE_NODE_DIR.
PRIVATE_NODE_TRUST_LOOPBACKunset1 lets local non-browser callers (loopback requests that carry no Origin header) skip the bearer token; browser calls always need it.
PRIVATE_NODE_ALLOWED_ORIGINhttp://localhost:3000CORS origin allowed to call the private node (the app).
PRIVATE_NODE_RUNNER / PRIVATE_NODE_RUNNER_PORTon / the port after the node's (8771)Your own runner, a child of the node on 127.0.0.1: 0 starts the node without it. Its records are in <PRIVATE_NODE_DIR>/runner, its wallet is the one connected on the node.
PRIVATE_NODE_SELLING_PORT / PRIVATE_NODE_SELLING_URLthe node's port + 10 (8780) / http://127.0.0.1:<port>Your selling node, started by the private node when you go live with no other node to list on: its port, and the address buyers reach it at (the listing carries it).
PRIVATE_NODE_OWNERthe wallet's addressThe one owner of everything your own runner holds; a request that names another is refused.
PRIVATE_NODE_MAX_EGRESS_BYTES4096Cap on private context bytes a workflow step may send to a paid external service per call; over the cap is refused, never truncated.
MARKET_NODE_URLhttp://127.0.0.1:8546The marketplace node the executor pays for external tools.
AGENT_ACCOUNT_INDEX16Local development network: the Anvil account used as the scheduler's budget-capped agent wallet.
SCHEDULER_LEASE_MS300000 (5 min)Lease so exactly one runner executes recurring workflows.
SCHEDULER_CATCHUP_GRACE_MS120000 (2 min)Grace period in which runs missed during downtime are still executed.
MEMORY_MAX_ITEMS5000Cap on local-brain items, bounding disk and memory growth.
PRIVATE_NODE_URL / BACKUP_PASSPHRASEhttp://<host>:<port> / unsetUsed by npm run private-node:backup: which node to back up and the passphrase that encrypts the archive.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETunsetOAuth client for connecting Gmail and Google Calendar; the connect flow is disabled without them. The ad, analytics and publishing accounts take their OAuth app's client id and secret on the card at connect time and keep them encrypted on the node.
PRIVATE_NODE_PUBLIC_URLhttp://<host>:<port>The address the node is reached at: the OAuth callback (/oauth/<provider>/callback) and workspace file URLs (/workspace/<path>) are built on it. Set it behind TLS.
PRIVATE_NODE_WORKSPACE<PRIVATE_NODE_DIR>/workspaceThe workspace root: ws.* refuses any path outside it.
PRIVATE_NODE_MODELS / PRIVATE_NODE_TRAIN<PRIVATE_NODE_DIR>/models / <PRIVATE_NODE_DIR>/trainWhere Hugging Face installs (the Python venv, the model cache, the worker log) and training jobs keep their state.
OLLAMA_HOST127.0.0.1:11434Where hf.install pulls an LLM to and hf.run runs it (a host:port or a URL).
HF_WORKER_PORT / HF_WORKER_DEVICE8790 / autoThe pipeline worker's port on loopback (a free one when 8790 is taken) and the device it loads models on.
TRAIN_IMAGE_<KIND>pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtimeThe trainer image for train.lora per kind (LLM, IMAGE, AUDIO, VIDEO).
PLAYWRIGHT_MODULEplaywrightWhere ws.screenshot and ws.browse resolve Playwright from, when it is not in offchain/node_modules.
ZEROX_API_KEY / ZEROX_API_URLunset / https://api.0x.orgwallet.swap's quote source.
AIOUM_AGENTS_URLthe node's own runnerThe runner skills.install fetches an index skill's files from when the agent did not hand them over.

Runner — src/agents/server.ts

A runner keeps agents, runs and projects, plans an assistant's turns, pays their calls, streams runs live and keeps the spend rollups. Your own runs inside your private node, started with it, and pays from the wallet connected there. A shared runner is a listing people sign in to; their own accounts pay. It does not start without a mode.

VariableDefaultWhat it does
AIOUM_AGENTS_MODEunsetown (one person's, behind a token; a private node sets it for its runner), shared (people sign in with their wallets), or open (tests only; refused on a real chain).
AIOUM_AGENTS_TOKEN / AIOUM_AGENTS_TOKEN_FILE / AIOUM_AGENTS_OWNERunsetAn own runner's token, or a file only you read that holds it; the one owner whose requests it answers.
AIOUM_AGENTS_KEYS_SECRET / AIOUM_AGENTS_KEYS_SECRET_FILEunsetA shared runner's secret that seals people's spending keys at rest (16 characters or more).
AIOUM_AGENTS_SESSION_TTL_S / AIOUM_AGENTS_FEE_SERVICE86400 / unsetHow long a sign-in on a shared runner lasts; the listing its fee goes to.
AIOUM_AGENTS_REVIEWERunset1: the runner bonds ZAI and reviews the listings the chain draws it for (AIOUM_REVIEWER_PRIVATE_KEY or AIOUM_REVIEWER_KEY_INDEX, AIOUM_REVIEWER_BOND_ZAI, AIOUM_REVIEWER_POLL_S).
AIOUM_AGENTS_PORT / AIOUM_AGENTS_URL8560 / http://127.0.0.1:<port>Listen port when started by hand, and the address it gives a published agent's listing. A private node starts its own runner on 8771 and answers for it at /runner.
AIOUM_AGENTS_HOSTevery interfaceThe address the runner binds to. A private node starts its own runner on 127.0.0.1.
AIOUM_AGENTS_REVIEW_WATCHoff1: the runner reviews the network's new listings unasked, from its own wallet. Off, a review is made on request only.
AIOUM_AGENTS_DIR / AIOUM_AGENTS_MAX_RUNS~/.aioum-agents when started by hand; ~/.aioum-private/runner for your own; ~/.aioum-shared-runner for the local stack's shared one / 400Agents, runs, projects, the spend rollups (spend.json), the bought-skill cache (skill-cache/<agent>/) and the runner's copy of what its runs learned about tools (experience.json); how many runs are kept.
AIOUM_AGENTS_EXPERIENCE_MAX2000Experience records kept on the runner, so a search ranks by what it learned when the directory is out of reach.
AIOUM_AGENTS_PRIVATE_KEY / AIOUM_AGENTS_KEY_INDEXunset / 30The runner's wallet. Your own runner gets the wallet connected on your node, handed over as it starts. A shared runner's own pays its gas and the reviews it makes, and nothing of a person's; required on a live chain; on the local development network an Anvil account of its own, so its session opens never share a nonce counter with a node's settles (the account map is in offchain/src/config.ts; the deploy funds its gas), which tops itself up with test USDC.
AIOUM_INDEXER_URL / AIOUM_AGENTS_CATALOG_TTL_MShttp://127.0.0.1:8549 / 30000The directory the runner reads its tool catalogue from, and how long it keeps it.
AIOUM_MULTINODE_URL / AIOUM_NODE_TOKENhttp://127.0.0.1:8556 / unsetThe catalogue node that lists a published agent, and its admin token when it has one.
AIOUM_PRIVATE_NODE_URL / AIOUM_PRIVATE_NODE_TOKENhttp://127.0.0.1:8770 / the file <PRIVATE_NODE_DIR>/tokenYour private node, whose connected tools every agent gets as Your node (PRIVATE_NODE_TOKEN is read too). AIOUM_AGENTS_LOCAL_TTL_MS (15000) caches its tool list; AIOUM_AGENTS_LOCAL_CALL_TIMEOUT_MS (600000) bounds a local call.
AIOUM_SKILLS_DIR / AIOUM_TEMPLATES_DIRoffchain/skills / offchain/templatesThe skills index the runner serves and searches, and the gallery its pipelines count templates from.
ANTHROPIC_API_KEY / AIOUM_AGENTS_MODELunset / claude-opus-5The runner's own planner; an agent's own pick under Model (plannerModel) comes first.
AIOUM_CLAUDE_CLIfoundWhere Claude Code is on the runner's machine, when the runner should not look for it itself (it looks on PATH, in the usual install folders and at the editor extension's bundled binary, and takes the newest). Your Claude plan plans through it once you are signed in (Settings, or claude auth login; Claude Code 2 or later).
AIOUM_AGENTS_CLAUDE_MODELopusThe model your Claude plan is asked for: an alias Claude Code knows (opus, sonnet) or a Claude model id. An agent's own pick under Model comes first.
AIOUM_AGENTS_MAX_TOKENS / AIOUM_AGENTS_CONTINUATIONS8192 / 3The longest reply one planning turn asks for (never past the model family's documented ceiling), and how many times a reply cut off at that length is continued before the run gives up.
AIOUM_AGENTS_RENTAL_IDLE_MIN30How long a paused project keeps the GPU the setup rented for it before it is torn down by its id; a resume within that time finds it up, a later one rents again.
AIOUM_AGENTS_RENTAL_WAIT_MIN6How long the setup waits for a rented GPU by the clock: this long for the provider to say it runs, three times it for a bootstrapped pod's job server to answer. Past it the pod is terminated by its id and the setup goes on without it.
AIOUM_AGENTS_PLAN_PROBE_MS120000How often the runner reads your Claude plan's sign-in again: a new login on the machine, or a limit that passed, is seen without a restart, and a project that paused waiting for a planner starts itself. A key planner whose account is out of credit is set aside for an hour and the next planner is used.
AIOUM_AGENTS_MAX_TURNS60Planning turns one run may take (a turn is one model call; its tool calls run together). The budget and the run's deadline are the caps that matter.
AIOUM_AGENTS_SAMPLE_ROUNDS10How many times a project's sample stage is made (the stage as proposed, then sample rounds, each changing the approach when the last did not improve) before the project pauses rather than build anything long on a sample that did not reach the bar.
AIOUM_AGENTS_TURN_RETRIES / AIOUM_AGENTS_TURN_BACKOFF_MS / AIOUM_AGENTS_STAGE_RETRIES5 / 5000 / 2A planning turn the model does not answer (a timeout, a dropped connection, an overloaded or rate-limited model) is asked again after a wait that doubles from the backoff up to a minute, this many times, each try a status line in the run; a project stage that still fails that way runs again by itself this many times before the project pauses for it.
AIOUM_AGENTS_ASKunsetThe starting value of Ask me before continuing under Settings → Defaults, which overrides it once set there. A job never stops to ask you: a stage that fails for good is routed around (the stages after it read its error and deliver what they can, the project ends done naming what was lost), a sample nobody scored is taken as it is, and a job nothing live can serve ends with the answer (what is missing, the key that unlocks it). Set to 1 to bring the pauses back: edit the remaining stages and resume, add the key and press Continue.
AIOUM_AGENTS_PARALLEL_CALLS / AIOUM_AGENTS_SESSIONS_PER_SERVICE8 / 3How many of one planning turn's tool calls run at once (calls that do not depend on each other go in one turn and run together), and how many sessions a run holds open with one service at once: one call at a time per session keeps each receipt's running total exact, so a second call in flight on a service takes another session, and past this many the call waits for one to free.
AIOUM_AGENTS_TEST_INFLIGHT / AIOUM_AGENTS_TEST_INFLIGHT_NEED / AIOUM_AGENTS_TEST_INFLIGHT_FAMILY16 / 4 / 3How many of the setup's tool tests run at once: across every need, per need, per upstream (one provider's key). Every candidate starts as soon as a slot frees.
AIOUM_AGENTS_TEST_AHEAD1How many candidates beyond what a need still needs may be in flight at once (a media need wants two at the bar): the next start waits for a verdict, so a need about to be met does not pay for tests it will not use.
AIOUM_AGENTS_TEST_VARIANTS / AIOUM_AGENTS_TEST_VARIANT_MIN / AIOUM_AGENTS_TEST_VARIANTS_PER_NEED2 / 40 / 4Usage rewrites: how many ways to use a below-bar tool better the planner proposes from the judge's reasons (each tested at once as a variant), from what score up a rewrite is worth paying for, and how many variant tests a need may run in all. 0 turns them off.
AIOUM_AGENTS_TEST_JUDGE_BATCH4How many landed results one judge call scores at once when they land while a judge call is busy.
AIOUM_AGENTS_OPENAI_BASE_URL / AIOUM_AGENTS_OPENAI_MODEL / AIOUM_AGENTS_OPENAI_API_KEYunset / gpt-4o-mini / unsetAny OpenAI-compatible planner.
AIOUM_AGENTS_OLLAMA_URL / AIOUM_AGENTS_OLLAMA_MODELhttp://127.0.0.1:11434 / llama3.2, else the first local modelA local planner beside the runner.
AIOUM_AGENTS_RUN_TIMEOUT_MS / AIOUM_AGENTS_CALL_TIMEOUT_MS / AIOUM_AGENTS_HIRE_TIMEOUT_MS1200000 / 90000 / 1500000A run (20 min, plus one hire's wait per part it may hire), one paid call (a listing that states its own timeout is waited on that long), one hire of another agent (25 min: a part whose single calls take minutes).
AIOUM_AGENTS_TEST_MAX_MIN / AIOUM_AGENTS_TEST_WINDOW_S / AIOUM_AGENTS_ACCEPT_SCORE8 / 60 / 80The setup's tool tests: the most one test runs, how long one is waited on before the next candidate starts, and the judge's score a test output and the deliverable must reach.
AIOUM_AGENTS_MAX_CALLS / AIOUM_AGENTS_MIN_DEPOSIT_USDC200 / 0.5Paid calls per run, and the least an agent sets aside for a session (twenty-five calls' worth otherwise, never past the budget).
AIOUM_AGENTS_MAX_SERVICE_RUNS8Runs the published agents serve at once (a lead's turn hires its parts at the same time); past it a caller gets "this agent is busy serving other callers".
AIOUM_AGENTS_PAY_FROMnativePay in any token: what the wallet on your node sells for USDC when a session needs more than the runner holds (on by default per agent, payAcross). On the local development network the runner tops itself up with test USDC instead.
AIOUM_AGENTS_SSE_HEARTBEAT_MS15000The ping on GET /runs/:id/events and GET /projects/:id/events.

Keys console — src/tools/keys-console.ts

Every key above and every listing key, as a checklist on your machine that writes .env at the repository root and reads what is live from the running services (Add your keys).

VariableDefaultWhat it does
AIOUM_KEYS_PORT8590Listen port, on 127.0.0.1 only (scripts/devstack.sh review starts it as keys; KEYS_PORT there). Requests whose Host or Origin is not this machine are refused; a value pasted on the page is written to .env with mode 0600 and never sent back. AIOUM_INDEXER_URL is where it reads which listings are live.

Ports at a glance

ServicePortVariable
Anvil (local development chain)8545—
Service node8546AIOUM_NODE_PORT
Facilitator8547—
Bundler8548BUNDLER_PORT
Indexer8549AIOUM_INDEXER_PORT
Billing8550AIOUM_BILLING_PORT
Deposits8551AIOUM_DEPOSITS_PORT
Staking node8552AIOUM_BACKER_PORT
Relay8553AIOUM_RELAY_PORT
Your own runner, inside the private node8771PRIVATE_NODE_RUNNER_PORT
Your selling node, started by the private node8780PRIVATE_NODE_SELLING_PORT
Shared runner (local stack)8562SHARED_RUNNER_PORT
Paid reviewers (local stack)8661–8663REVIEWERS_PORT
A runner started by hand8560AIOUM_AGENTS_PORT
Private node8770PRIVATE_NODE_PORT
Keys console (loopback)8590AIOUM_KEYS_PORT
Hugging Face worker (private node, loopback)8790HF_WORKER_PORT
dApp (dev server)3000—

All of it assumes a deployments.json that names the contracts on your chain. On the local development network npm run deploy writes it for you. When the chain is your own, that file comes from the deploy itself, the handover of ownership, and the verification gate that must pass before anything is funded.

Next: Run every part yourself — every service the network runs on, started on your own machine and pointed at from the app, the command line and Claude.