5.6 KiB
v1 implementation plan: leases, queueing, accounting
For the implementing model: do not work from this file. The owner gives you one task file at a time (
01-…to07-…). This file is the index for the owner and the reviewer.
Goal: PLAN.md §4–§7a. Every conversation gets a sticky lease on one host (chosen by free
slots × weight when it starts), requests queue per (host, model) instead of overflowing a
router, an operator can pin a route or drain a host, and SQLite keeps the leases and an accounting
log that answers "which session used which host and model, for how long, at what cache-hit rate".
Architecture: five new packages — store (SQLite, modernc.org/sqlite), fingerprint
(conversation key), choose (the scoring rule), limiter (per-(host, model) slots + bounded
FIFO), lease (the sticky table, persisted through store) — and v1 versions of proxy,
admin, config and cmd/crossbar. The proxy tees streamed responses through an SSE scanner
to read the final usage/timings chunk; it never buffers or alters the stream.
How this plan was made: acceptance tests first, from PLAN.md; no reference implementation.
Every given test file was compiled against a panic-only skeleton of the interfaces named in the
tasks (go vet ./... clean), and nothing else was run. If a test turns out to be wrong, that is
the owner's finding: stop and report as AGENTS.md says; do not edit it.
Tech stack: Go 1.26, stdlib, github.com/BurntSushi/toml v1.6.0, modernc.org/sqlite
v1.59.0 (pure Go; go.sum given). No other module.
Global constraints
- Everything in
AGENTS.md. Branchv1. One task, one fresh OpenCode session, one commit. - Bodies are never logged or stored. Rows carry names, counts and timings only.
- Given files (tests,
example.toml,cmd/fakeupstream/main.go,tools/smoke.sh,go.sum) are copied and never edited. Some replace v0 files of the same name; the task says so.
Tasks
| # | File | Delivers | Tests that define it |
|---|---|---|---|
| 01 | 01-store.md |
internal/store: SQLite state + accounting |
internal/store/store_test.go |
| 02 | 02-fingerprint-config.md |
internal/fingerprint; config gains db, lease_idle, retention, day suffix |
fingerprint_test.go, config_v1_test.go |
| 03 | 03-limiter-choose.md |
internal/limiter, internal/choose |
limiter_test.go, choose_test.go |
| 04 | 04-lease.md |
internal/lease: the sticky table |
lease_test.go |
| 05 | 05-proxy.md |
proxy v1: leases, queue, SSE tee, accounting, header route | proxy_test.go (replaces v0's) |
| 06 | 06-admin-main.md |
admin v1 (pin/release/drain/usage/metrics), cmd/crossbar wiring |
admin_test.go (replaces), start/stop check |
| 07 | 07-smoke-readme.md |
tools/smoke.sh v1 run, README update |
make smoke |
For the owner: running a task
tools/run-plan.sh docs/plans/v1 # from a clean checkout on master
For the reviewer: after task 07
git log --oneline master..v1: seven commits with the trailer.- Copied files byte-identical to
_files/;git diff <merge-base>..v1 --stat -- PLAN.md AGENTS.md docs/plansempty. make gate,make smoke.- Read every source file against its task. Probe outside the tests: a lease whose host is
drained and unhealthy;
lease_idleexpiry while a request is in flight; a stream cut by the client mid-way (the accounting row must still be written, with the status it had); a body with"messages"that is not an array; two crossbars on the samedbfile;Prunewhile requests are being recorded. - Every
.(type assertion ininternal/is the two-value form or on a value we constructed. - Findings under "Reviews" in
docs/implementer-log.md, by fault (model / task / test).
Changes during the run
- 2026-09-25, task 01, first attempt: three given test files under
_files/were notgofmt-clean, and the gate'sgofmt -l .walks every file, so the gate failed on files the implementer may not edit. Ornith diagnosed it (gofmt on its own code was clean) and did not touch them. Owner's fault (the compile check rango vet, notgofmt, on the given files). Fixed by formatting the given files; the plan checklist now includesgofmt -l docs/before handover. Ornith stopped correctly (astoppedrow, code left in the tree, only the log committed); a second session was told where the first had stopped and finished steps 6–7 without starting over — the boxmaker precedent (M3a/19). The driver was then resumed from task 02. - 2026-09-25, task 02: v0's given
testdata/bad-unknown-key.tomlusedlease_idleas its unknown-key example, and v1 makeslease_idlea real key, so v0'sTestBadFilesbroke — the task did not hand over replacements for the two v0 given files (tip T19). Ornith changed the example tobogus_keyin both files and logged it in Deviations; the content is right, but the files were protected and the rule was to stop. Owner's fault for the conflict; the model's deviation is noted. The corrected files now sit in_files/internal/config/as the reference copies for the reviewer's byte-exact check. - 2026-09-25, task 04: my
TestPinAndUnpinasserted the pin event at exactlylen-3and the release event atlen-1, but the task's own rules make acquires under a pin record events too, so a faithful implementation produces[new pin pin new new release]and the assertion cannot hold. Ornith spent its first ten minutes puzzling over exactly that. Test fault (mine): the assertion now checks order and content (a pin event naming beta, followed later by a release), not positions.