self-route routes "use my own machine, for free" requests through the coordinator (the only rendezvous point, since the provider is an outbound-only WebSocket client behind NAT). Direct mode removes the relay entirely for the case where the client can reach the Mac itself — same machine, LAN, or tailnet:
- Lower latency — localhost/LAN, no WAN round-trip to the coordinator.
- Works offline — your own inference keeps running with no internet.
- Bytes never leave your network — stronger than E2E-through-relay.
The provider already ships an OpenAI-compatible HTTP server backed by the same
MLX engine (StandaloneServer); direct mode makes it secure (a local API
key) and discoverable, and adds a client that prefers it with automatic
fallback to the relayed self-route.
darkbloom start --local # local server ONLY (no coordinator)
darkbloom start --local --port 8080 # custom port
darkbloom start --local --bind 100.x.y.z # bind a tailnet IP for same-account devices
darkbloom start --local --no-auth # disable the API key (trusted/airgapped only)
# Unified mode: serve the public fleet AND a local endpoint at once, off the
# SAME loaded models (weights load once; local + coordinator requests share one
# continuous-batching engine and KV budget):
darkbloom start --local-endpoint # coordinator + local on :8000
darkbloom start --local-endpoint --port 8080 --bind 100.x.y.z--local runs the OpenAI server only (no coordinator connection).
--local-endpoint runs it alongside the coordinator connection. Both mint a
persistent bearer token (~/.darkbloom/local_token, 0600); --local also
writes a discovery record (~/.darkbloom/local.json, 0600).
These flags are implemented in provider-swift/Sources/darkbloom/StartCommand.swift.
darkbloom local # prints base URL + API key + ready-to-paste examples
darkbloom local --json # machine-readable discovery recordPoint any OpenAI client at it:
export OPENAI_BASE_URL=http://127.0.0.1:8000/v1
export OPENAI_API_KEY=dk-local-… # from `darkbloom local`from openai import OpenAI
client = OpenAI() # picks up OPENAI_BASE_URL / OPENAI_API_KEY
client.chat.completions.create(model="…", messages=[{"role": "user", "content": "hi"}])import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
export function discoverLocalEndpoint() {
try {
const info = JSON.parse(readFileSync(join(homedir(), ".darkbloom", "local.json"), "utf8"));
return { baseURL: info.base_url as string, apiKey: info.api_key as string | undefined };
} catch {
return null; // local mode not running
}
}The recommended client pattern: prefer the local endpoint and fall back to the coordinator self-route on a connection failure (the Mac is asleep, you're away, or local mode isn't running). Fallback should fire only on a connection-level error — a reachable-but-erroring local server returns its own error rather than silently rerouting. Both paths are free.
The console UI does not ship this helper today (a ready-made
chatCompletionWithFallback prototype lived at console-ui/src/lib/localFirst.ts
until it was removed as unwired code — recover it from git history if useful).
The pattern is a few lines:
async function chatCompletionWithFallback(body: object, local: { baseURL: string; apiKey?: string } | null) {
if (local) {
try {
return { via: "local", response: await postChat(`${local.baseURL}/v1/chat/completions`, body, local.apiKey) };
} catch (err) {
if (!isConnectionError(err)) throw err; // reachable-but-erroring: surface it
}
}
// Coordinator fallback MUST self-route to stay free: without the
// X-Darkbloom-Route: self header the request goes to the public paid fleet.
return {
via: "coordinator",
response: await postChat("/api/chat", body, coordinatorApiKey, {
"X-Darkbloom-Route": "self",
}),
};
}- API key, not just loopback. A loopback server with no auth is reachable by
any local process and — because it sends
Access-Control-Allow-Origin: *— by a hostile web page. The bearer token is the boundary. Every inference route requiresAuthorization: Bearer <token>;OPTIONS(CORS preflight) andGET /health/GET /are exempt. Comparison is constant-time; the 401 carries a CORS header so browsers can read it. --bindexposes the server to the network (still token-gated). Prefer a tailnet IP over0.0.0.0. The discovery record always advertises a dialable loopback URL when bound to a wildcard.- The token persists across restarts (so existing clients keep working) and is
written atomically at
0600(no umask window). The discovery file is removed on graceful shutdown; because a Ctrl-C/SIGKILL/crash skips that cleanup, the record carries the serverpidanddarkbloom local(viareadLiveInfo) treats a stale file whose process is gone as "not running" rather than advertising a dead endpoint.
| Direct (local) | Self-route (relayed) | |
|---|---|---|
| Path | client → your Mac | client → coordinator → your Mac |
| Best for | same machine / LAN / tailnet | remote, away from your Mac |
| Coordinator needed | no | yes |
| Auth | local API key | your Darkbloom API key + X-Darkbloom-Route: self |
| Cost | free | free |
| Code-identity gate | N/A — no coordinator | applies once enforced |
They are complementary modes a client picks by reachability — the local-first fallback pattern above does exactly that.
--local-endpoint is the unified mode: the provider keeps its coordinator
connection (serving the public fleet) and exposes the local OpenAI endpoint
off the same loaded models. There is no double-load — both front-ends
dispatch through ONE shared BatchScheduler registry and GlobalKVCacheBudget,
so a local request and a coordinator request feed the same continuous-batching
engine and count against the same capacity the coordinator sees. Local in-flight
requests hold a reservation that keeps the idle monitor / load-gate from evicting
a model mid-stream. The HTTP layer is identical to --local (shared builder), so
auth, CORS, and error mapping behave the same.
--localand--local-endpointmint the same bearer token, but only--localwrites the~/.darkbloom/local.jsondiscovery record today; for unified mode the endpoint URL is printed at startup. Writing the discovery record from unified mode (sodarkbloom localfinds it too) is a small follow-on.- The hosted browser console can't read
~/.darkbloom/local.json; a settings field to paste thedarkbloom localURL + token (then prefer it via the local-first fallback pattern above) is a natural follow-on.