Documentation
Technical documentation
What you need to run a Gnodi AI node: supported hardware, the models the network serves, and how a node connects. Everything here applies to all three node types.
System requirements
A node serves inference from a local engine, so the card decides what you can run and therefore what you can earn. The daemon measures your VRAM on start and offers only the models that fit — it will not advertise something it cannot serve.
| Component | Minimum | Recommended |
|---|---|---|
| GPU | NVIDIA RTX 3060 Ti or RTX 4060 — 8 GB VRAM | NVIDIA RTX 4070 or better — 12–24 GB VRAMAn RTX 3060 Ti, RTX 4060 or RTX 2080 (8 GB) is the entry point and serves the 7B–8B models. An RTX 3060 12GB or RTX 4070 adds the 12B model; an RTX 3090, RTX 4090 or L4 (24 GB) adds the 31B and 32B models. Datacenter cards work the same way — a T4 or A4000 sits with the consumer cards of its size. A GPU is effectively required: CPU-only inference is slow enough that jobs time out and route elsewhere, which costs you success rate without earning anything. Apple silicon works, but VRAM cannot be auto-detected — set it by hand. |
| System RAM | 8 GB | 16 GBThe daemon itself is a single static binary with a small footprint; the memory goes to the inference engine. |
| Free disk | 25 GB | 50 GB+Quantized weights run roughly 0.6 GB per billion parameters, and the node pulls every catalog model your card can hold. A full root volume breaks the engine in confusing ways. |
| Network | 5 Mbps up, stable | 25 Mbps upOutbound HTTPS/WSS only — no inbound ports, no port forwarding, no public address. Token streaming itself is tiny; bandwidth matters for the initial model downloads. |
| Operating system | Linux, macOS 13+, Windows 10/11 | Ubuntu 22.04 LTSx86_64 or arm64. Linux and macOS install a service automatically; on Windows, download the .exe from the releases page. |
| Uptime | — | 24/7Rewards are weighted by uptime × success rate, so an always-on node earns disproportionately more than one that is online half the time. |
What your card unlocks
The catalog threshold is per model, not cumulative. To keep several models resident at once — which is how a node serves more of the network's traffic — your VRAM needs to cover the sum of them.
8 GB
RTX 3060 Ti, RTX 4060, RTX 2080
- gnodi/llama3.1-8b
- gnodi/mistral-7b
12 GB
RTX 3060 12GB, RTX 4070
- + gnodi/gemma3-12b
16 GB
RTX 4060 Ti 16GB, RTX 5060 Ti, A4000
- + gnodi/qwen2.5-14b
24 GB
RTX 3090, RTX 4090, L4
- + gnodi/gemma4-31b
- + gnodi/qwen2.5-32b
48 GB+
A6000, 2× 24 GB, A100
- + gnodi/llama3.1-70b
Thresholds are read against what the driver actually reports, which is a little under the number on the box — a 16 GB Tesla T4 reports 15 GB and therefore sits in the 12 GB band, not the 16 GB one. The daemon logs the figure it detected on every start, and VRAM_GB overrides it if you need to.
No inbound ports
The node dials the network; nothing dials the node. That is what lets a machine behind residential NAT or CGNAT serve traffic with no port forwarding, no DNS name and no TLS certificate — and it means there is no listening service for anyone to attack. The only local surface is a status dashboard bound to 127.0.0.1, for you rather than for the network.
Models
The network publishes a signed catalog that pins every model id to an exact engine reference and content digest. A node fetches it on start, verifies the signature against a public key compiled into the binary, selects what its card can hold, downloads what is missing, and checks each digest before serving.
That is what makes gnodi/llama3.1-8b mean identical weights on every node in the network. Without it, two operators could serve the same name at different quantizations for the same price, and the router could not tell them apart. All current entries are q4_K_M quantizations running on Ollama.
| Network id | Params | Min VRAM | Engine reference |
|---|---|---|---|
| gnodi/llama3.1-8b | 8B | 8 GB | llama3.1:8b-instruct-q4_K_M |
| gnodi/mistral-7b | 7B | 8 GB | mistral:7b-instruct-q4_K_M |
| gnodi/gemma3-12b | 12B | 12 GB | gemma3:12b-it-q4_K_M |
| gnodi/qwen2.5-14b | 14B | 16 GB | qwen2.5:14b-instruct-q4_K_M |
| gnodi/gemma4-31b | 31B | 24 GB | gemma4:31b-it-q4_K_M |
| gnodi/qwen2.5-32b | 32B | 24 GB | qwen2.5:32b-instruct-q4_K_M |
| gnodi/llama3.1-70b | 70B | 48 GB | llama3.1:70b-instruct-q4_K_M |
Completions are capped at 4,096 tokens. The catalog is versioned and signed, so models are added network-wide without an operator touching anything: the node picks up the new catalog, pulls what its card can hold, and starts serving it.
Models no operator serves
The API answers for more than the catalog above. These are fulfilled commercially and earn operators nothing — they are listed here because an operator who sees them in the chat product would otherwise reasonably assume a node could be earning from them.
gnodi/gpt-5-nanoFast and inexpensive, reads images, 400k contextgnodi/gemini-flash-lite1M context, reads images, video and audiognodi/deepseek-chatStrong reasoning, text only, 164k contextgnodi/image-1Image generation, billed per pictureEvery response names what served it in a served_by field — your licence key when your node took the job, fallback when it did not — so which of the two answered is never a guess.
They exist so that someone using the chat product does not need a second subscription elsewhere. The catalog above is where the network's own capacity is, and where an operator earns. gnodi/image-1 is the case worth watching: no diffusion engine ships in the node software yet, and a 16 GB card can run one — bringing image generation onto operator hardware is active work.
Agent protocol
The wire contract between a node and the gateway. Two independent implementations read it, so the specification in the node repository is the normative source rather than either codebase. It is public on purpose: a protocol whose security depends on secrecy is not secure, and anyone running a licensed node observes all of it anyway.
Transport
The node dials wss://<gateway>/v1/agent and holds the connection open. One JSON object per WebSocket text frame; every frame carries a t type field, and unknown types are ignored rather than fatal, so the protocol can grow without a flag day.
Liveness is WebSocket ping/pong every 30 seconds, gateway-initiated. The socket being open is the heartbeat — and how long it stays open across an epoch is the uptimeBps term in the reward formula below.
Handshake
{"t":"hello","protocol":1,"nonce":"<hex>","heartbeatSec":30}{"t":"auth","protocol":1,"licenseKey":"…","pubkey":"<b64>","sig":"<b64>","nodeVersion":"…"}{"t":"ready","nodeId":"…"} — or {"t":"error",…} and close{"t":"capabilities","models":[…],"maxConcurrency":N,"vramGb":N,"engine":"ollama"}sig is Ed25519 over the raw bytes of the hex-decoded nonce, by the key whose public half is pubkey. The licence key crosses the wire only inside the TLS session during this handshake, and never again after ready.
Key binding is trust-on-first-use
The first connection for a licence key binds its public key; later connections must present the same one. A stolen licence key alone is therefore not enough to impersonate an operator who is already enrolled — it gets key_mismatch and is closed. It does not protect a licence that has never connected; closing that requires registering the public key at activation time, which is a follow-up rather than part of v1.
This is why STATE_DIR is worth backing up. It holds the device key, losing it means re-enrolling, and anyone who can read it can impersonate your node and collect its work.
Job lifecycle
| Direction | Frame | Fields |
|---|---|---|
| GW → Node | job.offer | jobId, model, messages, maxTokens, deadlineMsAn offer, not an assignment. |
| Node → GW | job.accept | jobIdThe node has capacity and the model. |
| Node → GW | job.reject | jobId, reasonbusy | no_model | draining |
| Node → GW | job.chunk | jobId, seq, deltaseq starts at 0, strictly increasing. |
| Node → GW | job.done | jobId, promptTokens, completionTokens, durationMsTerminal. This is what gets billed. |
| Node → GW | job.error | jobId, code, messageTerminal. |
| GW → Node | job.cancel | jobIdCaller vanished or the deadline passed. Advisory. |
| Node → GW | status | accepting, inFlightUnsolicited, any time. Used to drain. |
Why offer and accept, rather than a bare push. A node may have just started another job or unloaded a model. An explicit reject lets the gateway re-offer elsewhere in milliseconds instead of discovering the failure through a timeout — which is the difference between a request that is a little slower and one that appears to hang.
Backpressure. The node advertises maxConcurrency and the gateway never exceeds it. Sending status with accepting: false drains the node before a planned restart, so you can reboot without failing live requests.
Failure and reconnect
A dropped socket abandons every in-flight job on both sides: the gateway re-routes the whole job, the node discards its work, and the caller sees a small delay. Mid-stream resume is deliberately not attempted — re-running is cheap and the bookkeeping is not. The node reconnects with exponential backoff plus jitter.
Re-offering follows the streaming rule: a node that fails before its first chunk is retried elsewhere, while one that fails after ends the request — the caller already holds partial text that a second node would not continue.
Error codes
An error frame from the gateway is fatal and is followed by a close.
bad_protocolThe node speaks a protocol version the gateway does not.unauthorizedMissing fields, or a signature that does not match the nonce.unknown_licenseNo node is registered for this licence key.key_mismatchA different device key is already bound to this licence.duplicate_connectionThis licence is already connected; the incumbent keeps serving.Reward formula
Operator payouts are computed off-chain once per epoch and settled on chain against a Merkle root. The split is proportional, so what decides your share is your weight relative to every other operator's that epoch.
weight_i = tierPoints(tier_i) × creditsServed_i × quality_i operatorPool = the epoch's emission, set by the DAO allocation_i = operatorPool × weight_i / Σ weight_j
Tier points
Your licence tier sets your earning capacity — a multiplier on the work you actually do, not an entitlement to anything on its own.
| Tier | Licence type | Points |
|---|---|---|
| Full | gnodi.node | 100 |
| Half | gnodi.node.half | 50 |
| Quarter | gnodi.node.quarter | 25 |
| Mini | gnodi.node.mini | 10 |
Credits served
The billable value of the inference your node actually delivered during the epoch — not requests offered, not requests accepted. This ties pay directly to value delivered: a node that serves nothing earns nothing, whatever its tier.
Quality
Quality is a factor between 0 and 1, computed off-chain from what the gateway observed:
quality_i = uptimeFraction × successRateFraction
Uptime is how long your socket was open across the epoch. Success rate is served ÷ (served + failed), as the gateway saw it. An eligibility floor applies on top: a node whose reputation falls below the threshold earns quality = 0 for that epoch and is paid nothing, so quality is enforced rather than merely scaled.
Taken together: tier sets capacity, credits served drive the share, and quality gates and scales it. A Full node that serves nothing or is unreliable earns roughly nothing; a healthy Mini that serves a lot still earns, but a healthy Full serving the same work earns more.
A worked epoch
Two operators, one epoch. Operator A holds a Full licence and served 70% of the epoch's credits at 99% uptime and 100% success. Operator B holds a Mini licence and served the other 30% at 100% uptime and 98% success.
A: 100 × 700,000 × 0.99 = 69,300,000 → 95.9% of the pool B: 10 × 300,000 × 0.98 = 2,940,000 → 4.1% of the pool
A served a little over twice as much work as B but takes 23× the payout, because the tier multiplier compounds with it. That is the intended shape: the licence is what you bought, and the work is what you did.
Epochs and claiming
An epoch is one day (86,400 seconds). The current epoch is still accruing and cannot be settled — settling it would freeze partial usage. Once an epoch closes, the settlement job pulls per-node usage, computes every allocation, builds a Merkle tree of {operator, amount} leaves, and funds the epoch on chain with its root.
Operators then pull their own rewards with claim(epoch, operator, amount, proof) — a standard pull pattern, so nothing depends on the network successfully pushing a payment to you. Unclaimed allocations are swept back to the treasury after a 90-day claim window. The node dashboard shows each settled epoch as claimed, claimable, or unknown — the last meaning the chain could not be read, which is not the same as unclaimed.
Current status
The formula and the settlement pipeline are implemented and tested, and the distribution contract is written. Epochs are not being funded on mainnet yet — the DAO sets the per-epoch emission, and that decision is still open. Nodes serving traffic today are proving the routing and metering path; no operator rewards have been distributed through it so far. This page will say otherwise when that changes.
Getting started
Install the engine, then the node. The installer verifies the binary against published checksums, writes a 0600 config, and installs a service that restarts on boot.
# 1. the inference engine curl -fsSL https://ollama.com/install.sh | sh # 2. the node daemon curl -fsSL https://get.gnodi-ai.com | LICENSE_KEY=XXXX-XXXX-XXXX-XXXX sh
Models are pulled automatically from the signed catalog on first start. Expect the first boot to take a few minutes: the daemon loads every model it intends to serve before advertising it, so the network never routes work to a node that is still warming up.
More reference material lands here as it is written — the agent protocol, the reward formula, and the API reference for developers calling the network.