kyle 32ac7f549a Admin: leases, pin, release, drain, usage, metrics
cmd/crossbar/main.go calls admin.Handler with the new 6-arg signature, passing nil for the not-yet-wired leases/limiter/store/drainer (task 07 wires them) so go vet and go test ./... pass on cmd/crossbar. This is a compile fix, not the task-07 wiring; noted in the implementer-log.

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
2026-09-25 06:33:29 -07:00

crossbar

crossbar is an affinity router in front of several llama-server routers. A client's identity is the first path segment of its base URL; v0 routes each request to the first healthy host on that route's list and streams the answer back unbuffered.

Build

Build everything with make build; the binaries land in bin/. Check the work with make gate, which runs the formatter, vet, tests and line-length check with no network. When the code is ready, run make smoke, which starts two fake upstreams and exercises routing, failover, recovery and streaming over real HTTP.

Configure

crossbar reads one TOML file. This is example.toml:

# crossbar example configuration. Replace <tailnet> and the addresses with your own.
listen        = "127.0.0.1:17777"   # never 0.0.0.0 — bind the tailnet address in production
poll_interval = "1s"                # 60s in production; 1s makes the smoke run quick
queue_max     = 8

[hosts.alpha]
base_url = "http://127.0.0.1:18081"    # e.g. http://straylight.<tailnet>:11434
weight   = 1.0
models   = { "ornith-1.5-35b-a3b" = { parallel = 4 }, "small-9b" = { parallel = 6 } }

[hosts.beta]
base_url = "http://127.0.0.1:18082"    # e.g. http://titan.<tailnet>:8081
weight   = 2.0
models   = { "ornith-1.5-35b-a3b" = { parallel = 4 } }

# v0: a route is a preference list; the first healthy host that has the model wins.
[routes.opencode-a]
hosts         = ["alpha", "beta"]
default_model = "ornith-1.5-35b-a3b"

[routes.hermes-x]
hosts = ["beta", "alpha"]
Key Meaning
listen Where crossbar binds. A tailnet address, never 0.0.0.0.
poll_interval How often each host is health-checked. 60s in production; 1s makes the smoke run quick.
queue_max Reserved for v1 queueing; no effect in v0.
hosts.<name>.base_url The llama-server base URL this host serves.
hosts.<name>.weight Relative share of new routes this host receives.
hosts.<name>.models The models this host serves, with per-model parallel tuning.
routes.<name>.hosts Preference order: the first healthy host that serves the model wins.
routes.<name>.default_model Model used when a request omits one; must be served by a host in the route.

Run

Copy the binary, the config and the unit into place, reload systemd, and start it:

install -m 0755 bin/crossbar /usr/local/bin/crossbar
install -d -m 0755 /etc/crossbar
install -m 0644 crossbar.toml /etc/crossbar/crossbar.toml
install -m 0644 deploy/crossbar.service /etc/systemd/system/crossbar.service
systemctl daemon-reload
systemctl enable --now crossbar

Point clients at it

OpenCode, one provider for every project. Each instance is launched as CROSSBAR_ROUTE="$(basename "$PWD")-$$" opencode:

"provider": { "crossbar": { "npm": "@ai-sdk/openai-compatible",
  "options": { "baseURL": "http://crossbar.<tailnet>:7777/{env:CROSSBAR_ROUTE}/v1" },
  "models": { "ornith-1.5-35b-a3b": {} } } }

Hermes, in config.yaml:

custom_providers:
  - name: crossbar
    base_url: http://crossbar.<tailnet>:7777/hermes-<agent>/v1
    models: { ornith-1.5-35b-a3b: {} }

The route name in the URL must exist in [routes]; unknown routes are 404.

Inspect

GET /_crossbar/hosts reports every host's health and loaded models:

{"alpha":{"healthy":true,"loaded":["ornith-1.5-35b-a3b","small-9b"],"last_ok":"2026-09-25T09:34:18Z","last_err":""},"beta":{"healthy":true,"loaded":["ornith-1.5-35b-a3b"],"last_ok":"2026-09-25T09:34:18Z","last_err":""}}

GET /_crossbar/routes reports each route's preference order and default model:

{"hermes-x":{"hosts":["beta","alpha"],"default_model":""},"opencode-a":{"hosts":["alpha","beta"],"default_model":"ornith-1.5-35b-a3b"}}

What v0 does not do

Leases and stickiness, SQLite, /slots, queueing and wake-on-LAN are out of scope for v0; see PLAN.md.

S
Description
No description provided
Readme
823 KiB
Languages
Go 96.4%
Shell 3.4%
Makefile 0.2%