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.

The contract

Four routes, one token

RouteWhat it answers
/api/controllerswhich models are loaded, and every stage's state
/api/runtimethe operator wallet and the machines behind it
/api/contributionsper-node rows, units, requests, throughput
/c/<model>/v1/chat/completionsinference

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.

What p4 leaves to it

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 from content, and honours enable_thinking: false by 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.
Participation

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.

RouteWhat it answers
/api/auth/challenge → /api/auth/node-tokena wallet signature becomes a 30-day participation token
/api/expert-demandwhich expert windows are short of replicas
/api/expert-volunteerthe scarcest window this device should take
/api/expert-coveragewhat it has taken — and this is what wires its relay session
ws /api/expert-relay, ws /api/ring-relaya 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.

Placement is not its job

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".