Context
Live external-plane evidence, 2026-08-13 19:59Z, read through the orchestrator deployment-state bridge (inspect_deployment). The plane has four configured tenant repos. Exactly one has ever ingested. The other three carry lastIngestedRev: null with 40, 56 and 62 consecutive failures and sit at the 2-hour backoff cap. Credentials and config are clean — accessReadiness: ready 4/4, degraded 0, config: repoCount 4, disabledCount 0, errors [] — so the failure is entirely at the embed step.
The orchestrator log shows the work arriving correctly and dying at the last moment:
apps-ide materialized: envelopeFiles=1586 ingested=2260 embeddings=0 errors=1
apps-ide deferred: embedding incomplete, checkpoint held at none
codes=KB_VECTOR_EMBED_CONNECTION_REFUSED ingested=2260 embeddings=0 (streak held at 62)
apps-suite ingested=197 embeddings=02,260 items materialized from one repo, zero embedded, checkpoint held at none, nothing committed. This has been the steady state for weeks on that plane.
The Problem
providerActivity aggregates from the same snapshot, one engine, same model (qwen3-embedding-8b, openAiCompatible):
| operationStage |
priority |
calls |
avgQueueWaitMs |
maxQueueWaitMs |
avgExecutionMs |
embedding-canary (knowledge-base) |
interactive |
253 |
0.43 |
2 |
12,808 |
embedding-canary (memory-core) |
interactive |
253 |
2.06 |
384 |
12,926 |
kb-tenant-ingestion-embedding |
batch |
169 |
166,894 |
1,049,806 |
183,065 |
Two independent health canaries — one per server — dispatch at interactive priority against the same engine that tenant ingestion must share. They are admitted essentially instantly. Real ingestion is batch and waits 2.8 minutes on average and up to 17.5 minutes before it starts.
The arithmetic is the defect. Ingestion's own execution (183s average) fits inside the 300s deadline. Queue wait (167s) plus execution (183s) is 350s, which does not. Ingestion therefore times out on a deadline it would otherwise meet, and the timeout is attributed to the provider (KB_VECTOR_EMBED_FAILED / KB_VECTOR_EMBED_CONNECTION_REFUSED) rather than to the queue that caused it.
The health probe consumes the capacity it exists to measure. 506 canary calls against 169 ingestion calls: the instrument is three times the volume of the work. Both canaries poll continuously and independently, neither is aware the other exists, and neither yields to batch work that has already been waiting minutes.
Each failed ingestion increments the per-repo failure counter, which drives backoffCapMs to its 2-hour ceiling — so the lane is suppressed for hours after each preemption, and a repo can go weeks without a single successful sweep.
Relationship to existing tickets — this is the opposite direction
#17048 documents the same engine and same plane in the other direction: one wide multi-input batch fans out across every n_parallel slot and starves the canaries queued behind it. Its fix caps batch width and separates per-class deadlines.
This ticket is the inverse and is not covered by that work: nothing protects batch work from interactive work. #17048 leaves a slot interleavable for canaries; no mechanism bounds how much of the engine the canaries themselves consume, nor prevents a batch item from being overtaken indefinitely. Both starvations are real on the same plane on the same day, which is why fixing only #17048 would not have moved this number.
Adjacent but distinct: #16972 (timed-out batch retried at identical size — compounds this by re-buying the same doomed request); #16853 (early abort strands provider work — explains why abandoned attempts keep burning engine capacity).
Architectural Reality
ai/services/shared/providerActivityLedger.mjs owns the activity records and the nativeAdmission accounting the ordering reads.
- The canary is emitted by the KB and MC health surfaces; it is a liveness probe, so its result is only meaningful as "can the provider answer at all".
- Ingestion dispatch is
priority: batch by correct intent — it is bulk work — but priority here decides admission order with no aging, so "lower priority" degenerates into "indefinitely deferred" whenever a higher-priority producer polls on a fixed cadence.
The Fix (shape, not prescription)
- Age batch work into contention. A queued item's effective priority must rise with wait time, so a
batch item that has waited past a bound cannot be overtaken again by a fresh interactive arrival. Starvation-freedom is the property; the exact ordering policy is open.
- Bound canary cost against the lane it measures. Two servers independently polling one engine is the amplifier. Options: share one canary result across KB and MC within a freshness window; skip the canary entirely while a known batch is in flight and report
busy-behind-batch (the class #17048 already proposes); or cap canary share of engine capacity.
- Do not charge queue wait to the provider deadline — or, if the deadline is end-to-end by design, report the timeout with its queue-wait component so a queue-induced failure is never recorded as a provider fault. Today the ledger has the evidence and the error code discards it.
Acceptance Criteria
Out of Scope
- Batch-width capping and per-class deadlines (#17048 owns those).
- Retry-size adaptation (#16972).
- Provider-side stranded runners (#16853).
- Any change to embedding model choice, engine tuning, or host resource allocation — this is an ordering defect and reproduces regardless of how much capacity the engine has.
Avoided Traps
- "Give the engine more CPU." Rejected. The lane is not compute-bound; it is order-bound. More capacity would be consumed in the same 3:1 ratio and would move the threshold without removing the starvation.
- "The provider is at fault." The error codes say provider failure; the ledger says the request waited 2.8 minutes before it was allowed to start. Treating the symptom as a provider problem is what kept this open.
- "#17048 covers it." It covers the mirror image. Both directions are live on the same plane.
Related
- #17048 — engine-slot monopoly, opposite direction, same plane and day
- #16972 — identical-size retry after timeout
- #16853 — early abort strands provider work
- #17044 — provider-lane health signal family
Context
Live external-plane evidence, 2026-08-13 19:59Z, read through the orchestrator deployment-state bridge (
inspect_deployment). The plane has four configured tenant repos. Exactly one has ever ingested. The other three carrylastIngestedRev: nullwith 40, 56 and 62 consecutive failures and sit at the 2-hour backoff cap. Credentials and config are clean —accessReadiness: ready 4/4, degraded 0,config: repoCount 4, disabledCount 0, errors []— so the failure is entirely at the embed step.The orchestrator log shows the work arriving correctly and dying at the last moment:
apps-ide materialized: envelopeFiles=1586 ingested=2260 embeddings=0 errors=1 apps-ide deferred: embedding incomplete, checkpoint held at none codes=KB_VECTOR_EMBED_CONNECTION_REFUSED ingested=2260 embeddings=0 (streak held at 62) apps-suite ingested=197 embeddings=02,260 items materialized from one repo, zero embedded, checkpoint held at
none, nothing committed. This has been the steady state for weeks on that plane.The Problem
providerActivityaggregates from the same snapshot, one engine, same model (qwen3-embedding-8b,openAiCompatible):embedding-canary(knowledge-base)embedding-canary(memory-core)kb-tenant-ingestion-embeddingTwo independent health canaries — one per server — dispatch at
interactivepriority against the same engine that tenant ingestion must share. They are admitted essentially instantly. Real ingestion isbatchand waits 2.8 minutes on average and up to 17.5 minutes before it starts.The arithmetic is the defect. Ingestion's own execution (183s average) fits inside the 300s deadline. Queue wait (167s) plus execution (183s) is 350s, which does not. Ingestion therefore times out on a deadline it would otherwise meet, and the timeout is attributed to the provider (
KB_VECTOR_EMBED_FAILED/KB_VECTOR_EMBED_CONNECTION_REFUSED) rather than to the queue that caused it.The health probe consumes the capacity it exists to measure. 506 canary calls against 169 ingestion calls: the instrument is three times the volume of the work. Both canaries poll continuously and independently, neither is aware the other exists, and neither yields to batch work that has already been waiting minutes.
Each failed ingestion increments the per-repo failure counter, which drives
backoffCapMsto its 2-hour ceiling — so the lane is suppressed for hours after each preemption, and a repo can go weeks without a single successful sweep.Relationship to existing tickets — this is the opposite direction
#17048 documents the same engine and same plane in the other direction: one wide multi-input batch fans out across every
n_parallelslot and starves the canaries queued behind it. Its fix caps batch width and separates per-class deadlines.This ticket is the inverse and is not covered by that work: nothing protects batch work from interactive work. #17048 leaves a slot interleavable for canaries; no mechanism bounds how much of the engine the canaries themselves consume, nor prevents a
batchitem from being overtaken indefinitely. Both starvations are real on the same plane on the same day, which is why fixing only #17048 would not have moved this number.Adjacent but distinct: #16972 (timed-out batch retried at identical size — compounds this by re-buying the same doomed request); #16853 (early abort strands provider work — explains why abandoned attempts keep burning engine capacity).
Architectural Reality
ai/services/shared/providerActivityLedger.mjsowns the activity records and thenativeAdmissionaccounting the ordering reads.priority: batchby correct intent — it is bulk work — but priority here decides admission order with no aging, so "lower priority" degenerates into "indefinitely deferred" whenever a higher-priority producer polls on a fixed cadence.The Fix (shape, not prescription)
batchitem that has waited past a bound cannot be overtaken again by a freshinteractivearrival. Starvation-freedom is the property; the exact ordering policy is open.busy-behind-batch(the class #17048 already proposes); or cap canary share of engine capacity.Acceptance Criteria
batchembedding item that has waited past a defined bound is admitted ahead of a newly-arrivinginteractivecanary; a fixture with a continuously-polling canary and one batch item proves the batch completes.Out of Scope
Avoided Traps
Related