Add the optional local inference gateway for request tracking #13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of RAM-11 so the coordination core could land verified (PR #12) without holding the card open on a second project.
The serving coordination core is in
breadram-core/src/serving: request leases and the drain, sleep, wake lifecycle, with the contract indocs/SERVING-COORDINATION.md. Nothing feeds it from real traffic yet, so the only activity evidence an adapter can offer today is the provider's own answer, and a backend whose idleness cannot be proven is reported unsupported.Scope
SleepDecision,DrainOutcome, andWakeHealthrather than by the gateway's own timer.Acceptance criteria
Depends on the coordination core (merged in PR #12) and the provider adapters for sleep and wake (RAM-12, RAM-13, RAM-14).
Dependency note from RAM-48 (provider execution), which is now implemented and in review.
Provider relief can now be carried out, but it cannot fire in a real deployment until request visibility exists, because of how idleness is proven:
OllamaAdapter::activityreturnsIdlefrom request leases only whenTrackingCompleteness::Completewas declared withwith_tracking. Otherwise a loaded model reportsActive, whatever its expiry says, which is the correct conservative direction.AdapterRegistry::from_configtherefore leaves every adapter atUnknown.So the current behaviour is deliberate and, stated plainly: a resident model is never released until a gateway reports the requests that reach it (or a deployment explicitly declares tracking through a documented configuration field, which does not exist yet). Whoever picks this up should decide which of those two it is, rather than having the release path stay unreachable by accident.
Triage (backlog sweep): not starting yet. The gateway core is well specified and the coordination core it needs is merged, but the card depends on the sleep/wake adapters RAM-13 and RAM-14, which are still unstarted. Starting the gateway now would force stubbing the exact provider calls it must drive. Proposed order: implement RAM-13 and RAM-14 first, then build the gateway against the real SleepDecision, DrainOutcome, and WakeHealth paths. Leaving in backlog.
Triage during the breadram blocker sweep (session ses_ed5126ae3ffeORjTfoQf89tltt). Not implemented here.
State: the serving coordination core this card needs is merged (PR #12), and the sleep and wake provider calls it must drive now have PRs: llama.cpp as RAM-13 (#31) and vLLM as RAM-14 (#29). Neither is verified or merged yet, so the exact provider calls the gateway would drive are still in review.
Per the implementer rule against speculative PRs, this card is left in backlog. Once RAM-13 and RAM-14 are verified and merged, the gateway's scope (fronting one or more backends, admitting every request through a lease, streaming and cancellation and retry semantics, demand-driven wake, and no prompt or completion logging) can be built against a settled provider contract rather than stubs. Recommend starting it immediately after those two land.
Started the gateway. First piece published on
feature/ram-45-gateway-core.PR: #33
What landed: the request-path routing core (
breadram-core/src/serving/gateway.rs), which admits every request through a lease before dispatch, begins a demand-driven wake when a request arrives at a sleeping backend and refuses it with a retry hint, releases a stream's lease on disconnect, joins or replays an idempotent retry without double counting, and refuses an untracked backend rather than serving through a path that could be slept from under it. It stores no body. Tests and docs are included.Still to come: the socket server preserving native and OpenAI-compatible request and response semantics including streaming and cancellation end to end, wiring the provider sleep and wake commands to SleepDecision, DrainOutcome, and WakeHealth, and the operator configuration. The PR body says Part of RAM-45, so the card stays open.
More gateway core on the same branch (PR #33 head
4be2dc8):Still to come: the socket server that preserves native and OpenAI-compatible request and response semantics including streaming and cancellation end to end, wiring the provider sleep and wake commands into the lifecycle on the request path, and the operator configuration that points a client at the gateway.
More gateway on the same branch (PR #33 head
4e751e0, rebased onto main44a23a5).What landed this pass is the socket server the card asks for (serving/server.rs): every request admitted through a lease before dispatch over loopback TCP, heads parsed for routing metadata only with bodies relayed byte for byte, streams relayed until either side closes, disconnects releasing leases at once including while waiting on a slow backend, and 503 plus Retry-After / 502 / 404 / 400 / 413 semantics. Plus a response-head parser in http.rs (a status line is not a request line; found because the first proxy test returned 502).
Still to come: performing the provider wake behind WakeThenRetry through the adapter sleep/wake commands, sharing one in-flight response between joined requests, and the operator configuration that points a client at the gateway. The PR body says Part of RAM-45, so the card stays open.
Verification: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo build --workspace --locked, cargo test --workspace --locked all pass (24 suites ok).
More gateway on the same branch (PR #33 head
9cec265, main at44a23a5).What landed this pass is the operator configuration that points a client at the gateway:
Still to come: performing the provider wake behind WakeThenRetry through the adapter sleep/wake commands, and sharing one in-flight response between joined requests. The PR body says Part of RAM-45, so the card stays open.
Verification: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo build --workspace --locked, cargo test --workspace --locked all pass (24 suites ok), with zero /tmp leftovers across consecutive runs.
More gateway on the same branch (PR #33 head
fdf8133, rebased onto main19ab3dc).What landed this pass is the provider wake on the request path, which the card names as wiring SleepDecision, DrainOutcome, and WakeHealth:
The sleep side stays provider work by design: the gateway only answers arrivals and never decides to sleep a backend, so SleepDecision and DrainOutcome remain the adapters' to drive. The PR body says Part of RAM-45.
Verification: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo build --workspace --locked, cargo test --workspace --locked all pass.
Follow-up on the same branch (PR #33 head
92b2653).A live probe caught a real gap the tests missed: the daemon seeds every backend Awake, and nothing ever asked the provider what it actually was. Pointing breadramd at a router whose model was already unloaded forwarded the first request straight through (200) instead of waking it, because routing only wakes a backend it believes is asleep.
Fixed: before the listener opens, and only then, each route with a wake spec is probed once through the same structured listing the wake verifies against, and the backend is reconciled. loaded means serving, any other reported status means asleep, unverified when the provider cannot be read. Reconcile drops leases, which is why it runs before serving and never again. Pinned by startup_reconcile_finds_a_backend_that_is_already_asleep.
Verified live against llama-server build 11146 in router mode with Qwen3 8B, context capped at 4096: first request to the gateway answered 503 with the wake reason, the gateway called POST /models/load itself, the next request answered 200 with the model loaded. Device memory about 6.3GB loaded against 0.4GB after unload. The operator's own server on port 8080 was not touched.
Full workspace suite green.
Follow-up on the same branch (PR #33 head
67ca1e5).The gateway tracked leases, but the planner never saw them: the monitor pass built activity only from the adapter registry, and the gateway task shared nothing with it. A request in flight therefore could not stop an unload.
Fixed: the gateway task publishes the scopes that currently hold a lease, and the monitor pass treats a published scope as active evidence on its own, ahead of whatever the provider last answered. A request in flight now keeps its backend loaded, and the planner still never learns that a gateway exists. Pinned by a_lease_in_flight_is_reported_as_an_active_scope, which checks the scope appears while a lease is held and disappears once it finishes.
Full workspace suite green.
The sleep half of the loop, on the same branch (PR #33 head
5318955).The daemon already unloads a confirmed-idle model through the adapter, but the gateway never learned it had happened, so the next request was forwarded into a backend that was no longer serving.
Fixed: a confirmed UnloadIdleModel is handed to the gateway, which marks the backend asleep with Backend::unloaded, and the next arrival wakes it on the request path. An unload that races a live lease loses: the request finishes and the backend stays awake, pinned by an_unload_that_races_a_request_does_not_strand_it. an_unload_of_an_idle_backend_puts_it_to_sleep checks the backend then wakes on the next request.
That completes the loop the card asks for: a quiet backend is unloaded, the gateway records the sleep, and the next request wakes it and is served after the retry hint rather than failing. Full workspace suite green. Moving to review.
Reviewed and merged PR #33 (head
5318955, merge58bc71b). The gateway admits every request through a lease, wakes a sleeping or unverified backend on the request path with an exact model-id match, reconciles an already-asleep backend before the listener opens, publishes in-flight leases as active evidence, and records a confirmed idle unload so the next request wakes it. CI was green and the PR was mergeable.