# AGENTS.md crossbar is an affinity router for the fleet's `llama-server` instances, written in Go. You are implementing it one task at a time. ## How you work 1. The owner gives you one task file, `docs/plans//NN-name.md`. Read it fully. Do that task and nothing else. Do not start the next task. 2. Do the steps in order. Where a step shows a command and its expected output, run it and compare. 3. Do not read `PLAN.md` or other task files unless the task tells you to. The task file quotes what you need. 4. If something in the task is impossible, contradictory, or fails twice in the same way, **stop**. Do not improvise, do not change a test, do not weaken a check. Add your row to `docs/implementer-log.md` with status `stopped`, say what you tried and what happened, commit only that file, and tell the owner. 5. Work only from files inside this repository. Never read another checkout (such as `~/src/crossbar-ref` or `~/src/crossbar-design`), and never search the file system for code. If you are stuck, stop as in point 4. ## Files you must never edit - `PLAN.md`, `docs/plans/`, `AGENTS.md` - Anything a task told you to copy from `docs/plans/**/_files/` — in this plan **or any earlier one** — stays protected: tests, testdata, `Makefile`, scripts, `example.toml`, `cmd/fakeupstream`. If a copied test fails, your code is wrong. If a copied test can no longer be right because the new task changes what it tested, that is the owner's error: stop and report it; the owner hands over the replacement. ## Code rules - Go 1.26 (the version in `go.mod`). Standard library plus the one dependency named in the tasks (`github.com/BurntSushi/toml`). No other module, ever. - No source file over 400 lines (`scripts/check-lines.sh`). - Library code (`internal/...`) never panics on input: no `panic`, no indexing that can go out of range on data that came from a file, a request or a peer. Check lengths, use `strconv` and `errors`. `main` packages may exit with a message. - Errors are values: return them, wrap with `fmt.Errorf("...: %w", err)`, never log-and-continue in library code. A check that cannot do its job fails; it does not return "ok". - Never log, print or store a request or response body. Log lines carry names, paths, status codes and durations only. - Anything read from the network is bounded (`io.LimitReader`, `http.MaxBytesReader`, timeouts). - Exported names, field names and JSON/TOML tags are exactly as the task gives them; the tests compile against them. - Comments say why, not what. `gofmt` decides layout; run it before the gate. ## Lessons from earlier reviews These come from defects found in review; the evidence is in `docs/implementer-log.md`. - A type assertion on a value that came from outside your package (`w.(http.Flusher)`, a decoded JSON field) uses the two-value form and handles the `false` case. An unchecked assertion is a panic waiting for a caller you did not think of. - When a rule says "every" or "everywhere", finish by listing each place it applies and checking them one by one. The task shows one place; the rule covers all of them. - Never end a turn by describing what you are about to do. Do it, then report. - Work only inside this repository. Scratch programs under `/tmp` or anywhere else are refused by the sandbox, and **a refused tool call is not a reason to end the turn**: write the experiment as a `_test.go` file inside the repository (delete it before committing), or reason it out. Two sessions have ended with a plan and no tool call right after a refusal; that leaves the owner with no commit and no `stopped` row, the worst outcome. - Never change when production code releases, flushes or records something just to make a given test's timing pass. If a given test seems to check a value before the code could settle it (a deferred release, a row written after the answer), that is the owner's test bug: stop and report it. (v2.3 task 02 released every slot at the first flushed byte to satisfy such a test, and the limiter silently stopped limiting streams.) ## The gate `make gate` must print `gate: ok` before a task is done. It runs offline: `gofmt -l`, `go vet`, `go test -race -count=1 ./...`, and `scripts/check-lines.sh`. Run `gofmt -w` on the files you touched before the gate. `go test ./internal//` runs one package. ## Git - Work on the branch the task names. One task is one commit. - Stage only the paths the task lists: `git add ...`. Never `git add -A` or `git add .`. - Never push, amend, rebase, reset, or switch to another branch. - A commit message with more than one line goes in `.state/commit-msg.txt` (inside the repository and ignored by git; `/tmp` is refused) and is committed with `git commit -F .state/commit-msg.txt`. An apostrophe inside a single-quoted `-m '…'` breaks the shell command. - Commit message: the subject line the task gives, a blank line, then this trailer: `Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)` ## The implementer log Before you commit, add one row to the table in `docs/implementer-log.md` and include the file in the commit. Be honest: the log is how the owner judges the process. | Column | What to write | |---|---| | Task | The task file name, for example `v0/02-health` | | Date | Today's date, `YYYY-MM-DD` | | Status | `done` or `stopped` | | Gate runs | How many times you ran `make gate` | | First gate | `pass` or `fail` for the first run | | Deviations | Anything you did that the task did not say, or `none`. If your Notes describe a change you made, it belongs here as well — a row that says `none` next to Notes that describe a change is wrong. | | Notes | Problems you hit and how you solved them, in one or two sentences | | Model | Write `?`. The owner fills this in. |