Files
crossbar/docs/plans/v2.3/01-control-plane.md

4.2 KiB

v2.3 task 01: control-plane requests

Branch: v2.3 (git switch -c v2.3 master if it does not exist, else git switch v2.3; git status --short must be empty, otherwise stop) Commit subject: Control-plane requests follow the lease but take no slot and write no row

Goal

A client that manages its own llama-server slot makes small calls beside its chat stream: it polls GET /slots?model=X while it waits, reads GET /props?model=X, tokenizes, and sends POST /v1/chat/completions/control on a second connection while its own stream holds a slot. Today /slots and /tokenize are 404, a GET is leased under the route's default model instead of ?model=, and every call takes a limiter slot — so /control can queue behind its own stream, or get 503 when the queue is full. After this task those calls follow the lease like any request but never wait for or take a slot, skip the context guard, and write no accounting row.

Files

  • Copy: internal/proxy/control_test.go, and the replacement internal/proxy/proxy_test.go (overwrites the v1 copy: the /r/slots → 404 row becomes /r/slots/0 and /r/metrics; the v2.3 copy is now the protected one)
  • Create: internal/proxy/control.go — the control-call test and the model-from-query rule live here (proxy.go is at 325 lines)
  • Modify: internal/proxy/proxy.go, internal/proxy/forward.go, docs/implementer-log.md

Rules

  1. Model. The body's top-level "model" wins; else the query parameter model (r.URL.Query().Get("model")); else the route's default_model. This is used for the lease key and the limiter pair, exactly where the body model is used today.
  2. Paths. allowedPath also admits rest == "/slots" and rest == "/tokenize" (exact match on the path; the query string is not part of rest). /slots/0, /slots/0?action=… and anything else stay 404 {"error":"not found"}.
  3. Control calls are: method GET or HEAD (any allowed path), or method POST with rest exactly /tokenize or /v1/chat/completions/control. Everything else — in particular POST /v1/chat/completions — is not a control call.
  4. A control call is routed and leased exactly as today (same lease.Acquire, same wake path when no host is healthy), then forwarded without lim.Acquire, without the context guard, and without an accounting row (writeRecord is not called for it). Its log line is Debug, not Info (a client polls /slots every 5 s). It still gets the X-Crossbar-Host / X-Crossbar-Lease headers and still marks a host down on a transport error, like any forward.
  5. Do not duplicate forward. Pass what it needs to know (for example a control bool, or a small options struct if the parameter list gets long) and skip the row and the Info log inside it.

Facts you need

  • peekModel already reads and restores the body; GET/HEAD return "" there. The query fallback goes after it, in one place.
  • The rig in helpers_test.go passes the store as the recorder, and the row is written after the answer is sent — the tests wait for rows; do not add sleeps to production code.
  • waitUntil is defined in proxy_test.go; conversation(id, turn) builds a chat body.

Steps

  • 1. Branch as above; copy the two given files.
  • 2. See them fail: go test -count=1 ./internal/proxy/ -run 'TestGetModel|TestControl|TestChatIsNotControl' → 404 on /slots and /tokenize, 503 queue full on control calls, 4 stray rows.
  • 3. control.go: the control-call test and the model rule. 4. proxy.go: use them; branch in serveLeased (no limiter, no guard for a control call). 5. forward.go: no row, Debug log for a control call.
  • 6. gofmt -w; go test -race -count=3 ./internal/proxy/ → ok.
  • 7. make gate; make smoke. 8. Row v2.3/01-control-plane; commit.
git add internal/proxy docs/implementer-log.md
git commit

Done when

  • The new tests and every earlier proxy test pass under -race -count=3; gate and smoke ok; given files byte-identical; no file over 400 lines.

Stop and report if

  • Passing needs a change to any given test, or forward cannot skip the row without copying it.