# v0 implementation plan: static routing, health, streaming proxy > **For the implementing model:** do not work from this file. The owner gives you one task file at > a time (`01-…` to `05-…`). This file is the index for the owner and the reviewer. **Goal:** a `crossbar` binary that reads a TOML file, polls each host's `/health` and `/v1/models`, and forwards `/{route}/v1/*` to the first healthy host on that route's preference list, streaming responses chunk by chunk, marking a host down when a proxied request fails, and showing the health table at `/_crossbar/hosts`. This is `PLAN.md` §10 v0: no leases, no SQLite, no `/slots`, no queueing. **Architecture:** four small packages under `internal/` — `config` (TOML + validation), `health` (poller + table), `proxy` (route → host → `httputil.ReverseProxy`), `admin` (read-only JSON) — and two binaries under `cmd/`: `crossbar` and the given `fakeupstream`. Behaviour is pinned by the tests in `files/`, which were run against a private reference implementation at each task's end state; the reference is not in this repository and the implementer must not look for it. **Tech stack:** Go 1.26, standard library, `github.com/BurntSushi/toml` v1.6.0. Nothing else. ## Global constraints - Everything in `AGENTS.md`. - Branch `v0`. One task, one fresh OpenCode session, one commit. `gofmt -w` before the gate. - No source file over 400 lines. Library code never panics on input, never logs a body. - The given files are copied and never edited. If a copied test fails, the code is wrong. ## Tasks | # | File | Delivers | Tests that define it | |---|---|---|---| | 01 | `01-module-gate-config.md` | `go.mod`, the gate, `internal/config` | `internal/config/config_test.go` + `testdata/` | | 02 | `02-health.md` | `internal/health`: poller and table | `internal/health/health_test.go` | | 03 | `03-proxy.md` | `internal/proxy`: routing reverse proxy | `internal/proxy/proxy_test.go` | | 04 | `04-admin-main.md` | `internal/admin`, `cmd/crossbar`, the given `cmd/fakeupstream` | `internal/admin/admin_test.go`, a start/stop check | | 05 | `05-smoke-readme-deploy.md` | `tools/smoke.sh` run, `README.md`, `deploy/crossbar.service` | `make smoke` | At the end: `make gate` prints `gate: ok`, `make smoke` prints `smoke: ok (stream spread N ms)`. ## For the owner: running a task From a clean checkout on `master`: ```sh tools/run-plan.sh docs/plans/v0 # all tasks, each in a fresh `opencode run` session tools/run-plan.sh docs/plans/v0 03 # from task 03 ``` The default model is `llama.cpp/ornith-1.5-35b-a3b` (override with `CROSSBAR_MODEL`). The driver stops at the first task that does not end with a commit, a clean tree and a `done` row in `docs/implementer-log.md`. Keep the OpenCode TUI closed while it runs. ## For the reviewer: after task 05 1. `git log --oneline master..v0`: five commits with the `Implemented-By` trailer. 2. Copied files are unchanged: `for f in $(cd docs/plans/v0/files && find . -type f); do cmp "docs/plans/v0/files/$f" "$f"; done` 3. `git diff master..v0 --stat -- PLAN.md AGENTS.md docs/plans` is empty. 4. `make gate` and `make smoke` on straylight. 5. Read every source file against its task. Probe from outside with inputs the tests do not contain: a route with query strings and encoded characters, a host that hangs after headers, a 3 MB request body, `/_crossbar/hosts` while a poll is in flight, SIGTERM during a stream. 6. Write findings under "Reviews" in `docs/implementer-log.md`.