Gnodi
GnodiAI Nodes

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.

ComponentMinimumRecommended
GPUNVIDIA RTX 3060 Ti or RTX 4060 — 8 GB VRAMNVIDIA 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 RAM8 GB16 GBThe daemon itself is a single static binary with a small footprint; the memory goes to the inference engine.
Free disk25 GB50 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.
Network5 Mbps up, stable25 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 systemLinux, macOS 13+, Windows 10/11Ubuntu 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 idParamsMin VRAMEngine reference
gnodi/llama3.1-8b8B8 GBllama3.1:8b-instruct-q4_K_M
gnodi/mistral-7b7B8 GBmistral:7b-instruct-q4_K_M
gnodi/gemma3-12b12B12 GBgemma3:12b-it-q4_K_M
gnodi/qwen2.5-14b14B16 GBqwen2.5:14b-instruct-q4_K_M
gnodi/gemma4-31b31B24 GBgemma4:31b-it-q4_K_M
gnodi/qwen2.5-32b32B24 GBqwen2.5:32b-instruct-q4_K_M
gnodi/llama3.1-70b70B48 GBllama3.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 context
gnodi/gemini-flash-lite1M context, reads images, video and audio
gnodi/deepseek-chatStrong reasoning, text only, 164k context
gnodi/image-1Image generation, billed per picture

Every 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

1Gateway → Node
{"t":"hello","protocol":1,"nonce":"<hex>","heartbeatSec":30}
2Node → Gateway
{"t":"auth","protocol":1,"licenseKey":"…","pubkey":"<b64>","sig":"<b64>","nodeVersion":"…"}
3Gateway → Node
{"t":"ready","nodeId":"…"}  — or {"t":"error",…} and close
4Node → Gateway
{"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

DirectionFrameFields
GW → Nodejob.offerjobId, model, messages, maxTokens, deadlineMsAn offer, not an assignment.
Node → GWjob.acceptjobIdThe node has capacity and the model.
Node → GWjob.rejectjobId, reasonbusy | no_model | draining
Node → GWjob.chunkjobId, seq, deltaseq starts at 0, strictly increasing.
Node → GWjob.donejobId, promptTokens, completionTokens, durationMsTerminal. This is what gets billed.
Node → GWjob.errorjobId, code, messageTerminal.
GW → Nodejob.canceljobIdCaller vanished or the deadline passed. Advisory.
Node → GWstatusaccepting, 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.

TierLicence typePoints
Fullgnodi.node100
Halfgnodi.node.half50
Quartergnodi.node.quarter25
Minignodi.node.mini10

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.