Bridge
The HTTP face of the inference engine: what is loaded, who contributed, completions — and the market a contributing device joins.
The bridge is the one thing the settlement gateway talks to for inference. It is an OUTER in p4 terms: it installs a session across the model's stages, submits a request to the head stage, gathers the token stream, and reports what each node contributed. It owns no placement, no scheduling and no state beyond a catalog of what is loaded — deliberately small, because everything it does not decide is something that cannot drift.
Four routes, one token
| Route | What it answers |
|---|---|
/api/controllers | which models are loaded, and every stage's state |
/api/runtime | the operator wallet and the machines behind it |
/api/contributions | per-node rows, units, requests, throughput |
/c/<model>/v1/chat/completions | inference |
Every route except /api/health requires a shared service token, sent as X-Kvasir-Service-Token. That token is the only thing standing between the open internet and free use of the ring, which is why the bridge binds loopback and is reached through a tunnel rather than published.
Two jobs the engine will not do
- The chat template. p4 hands the stage server an opaque prompt and applies no turn format of its own. The bridge renders the model's — read from the GGUF and named in the catalog as
prompt_format. Skip it and an instruct model continues your text instead of answering it, never emits its end-of-turn token, and runs to the token limit every time. - The reasoning block. A reasoning model opens its reply by thinking. The bridge returns that as
reasoning_content, separate fromcontent, and honoursenable_thinking: falseby closing the block in the prompt — otherwise a long thinking pass can consume the whole budget and hand the caller an empty answer it has already paid for.
The half that was missing
A device that wants to contribute does not have a service token and should not be given one. It proves a wallet instead: the bridge issues a single-use nonce, the device signs it, and an ed25519 check returns a bearer token scoped to participation and nothing else. From there it asks what is under-covered, claims a window, and opens a relay — because a phone behind carrier NAT cannot be dialled, so both ends dial the bridge and it splices them.
| Route | What it answers |
|---|---|
/api/auth/challenge → /api/auth/node-token | a wallet signature becomes a 30-day participation token |
/api/expert-demand | which expert windows are short of replicas |
/api/expert-volunteer | the scarcest window this device should take |
/api/expert-coverage | what it has taken — and this is what wires its relay session |
ws /api/expert-relay, ws /api/ring-relay | a WebSocket spliced to a TCP endpoint |
Order matters, and silently. The coverage POST is what creates the relay target the device then dials. Reverse them and the socket still connects and still carries bytes — it simply credits nobody. Two more traps worth naming: the ring's one-byte role preamble (P predecessor, N successor) is payload to the relay and must cross untouched; and participation eligibility is not operator eligibility — the retired control plane gated this on holding a minimum balance, which refused every phone that ever asked.
Why it answers 409
Asking the bridge to serve a model returns 409. Which layers sit on which GPU, at which load generation, comes from a placement plan an operator wrote and loaded; there is no remote reload to perform. The gateway's ring watchdog learns this once and stops asking rather than retrying something that cannot work.
Contribution counters live in memory. A bridge restart loses whatever the gateway had not yet polled — it polls every 30 seconds — and the gateway rebaselines rather than double-counting when a counter goes backwards. A node whose owner the bridge does not know is skipped silently, so an unset operator wallet reads as "these machines earned nothing".