The embedder is one shared service for the whole platform: it turns text into a vector, and Harvester, Knowledge Store, Query Engine, and Agent Engine all call it. The load-bearing decision was already made on the model registry side: the embedder is factored out as a separate network service (OpenAI-compatible, weights loaded lazily), not baked into the backend. So scale here is an operations question, not a redesign.
Four sources of load converge on one shared runtime.
search primitive
in the agent loop — an intent vector
| Path | Volume | Latency | Waiting |
|---|---|---|---|
| Search · QE | a small request | user awaits the answer | critical |
| Search · Agent | a small request | background, but small | tolerates · online |
| Ingest · Harvester | large batches | background | tolerates |
| Re-embedding · KS | a bulk run, rare | background | tolerates |
Per-source unevenness is not the embedder's concern. The chunk stream no longer carries a source: the embedder cares only about the total volume, while the pace and steadiness of ingestion per source are held by Harvester's ingestion workers.
The risk is not the volume itself but the different shape of load across the paths. A big reindex of hundreds of thousands of chunks can occupy the runtime entirely — and a user's query queues up behind it, search slows down. It's a matter of priority, not of hardware count.
search primitive as a user's: queueing it behind a
reindex would freeze the loop for minutes for the sake of a
millisecond task. So it shares the online path with search,
not the bulk limit. It adds no volume: the pace of agent requests is capped
from above by the
agent concurrency cap, not by the embedder's throughput.
MODEL_TOO_LARGE;
the preflight runs before anything commits) instead of surfacing later
as an OOM kill. The per-model phase (loading / ready / error) is
served on a dedicated status endpoint — the Admin screens show
“loading weights” and “re-indexing” as distinct stages — while
/healthz stays pure liveness: a container busy loading
weights is alive, and the autoheal watchdog must not shoot it.
The service has two capacity levers. Batching — the runtime accumulates requests for a few milliseconds and computes them as one batch; it gives a multiple throughput gain on a single container and comes free with a batching-capable runtime. Replicas behind a load balancer — N copies: embedding is stateless (text in, vector out, no state between requests), so any copy can serve any request. Squeeze batching first, then replicas.
base_url): growing the pool means changing the address's target; the code of
Harvester, Knowledge Store, and Query Engine doesn't change.