Context
Vega's Approve+Follow-Up review on #14047 approved the OpenAI-compatible production path for #14036, but explicitly asked whether the native Ollama embedding path is reachable and still unbounded. I verified it is reachable through the existing provider selector and explicit dispatch path, so this is durable follow-up work rather than PR-thread prose.
Freshness evidence:
- Live latest-open sweep: checked latest 20 open issues at 2026-06-26T03:29:15Z; no equivalent native-Ollama timeout child found. The broad parent
#14036 remains open and is the correct anchor.
- A2A in-flight sweep: checked last 30 messages at 2026-06-26T03:29:15Z; no
[lane-claim] / [lane-intent] overlapped this native Ollama timeout slice.
- KB/local duplicate sweep:
#13946 covers native Ollama truncation/request-shape (truncate:false + options.num_ctx), not request duration bounds.
The Problem
#14036 says each embed call should be bounded so one wedged provider request cannot stall the deferred-embed WAL drain indefinitely. PR #14047 correctly bounded the openAiCompatible batch path, which is the deployment default and the current production path. The native Ollama path remains outside that bound: if a deployment sets NEO_EMBEDDING_PROVIDER=ollama, or a runtime adapter explicitly calls embedTexts(texts, 'ollama'), the call can still wait forever on provider.embed().
That leaves a smaller but real version of the same failure mode: native Ollama can hang a batch embedding call with no per-call timeout, no timeout-shaped ConsumerFriction, and no row-level fail-loud handoff for the caller.
The Architectural Reality
ai/config.template.mjs:157 exposes embeddingProvider as the deployment-wide embedding selector.
ai/config.template.mjs:161-162 documents that runtime adapters can explicitly select the native ollama provider.
ai/services/memory-core/TextEmbeddingService.mjs:603-611 routes embedText(..., 'ollama') directly to provider.embed().
ai/services/memory-core/TextEmbeddingService.mjs:645-653 routes embedTexts(..., 'ollama') directly to provider.embed() with no request timeout.
#13946 already fixed the native Ollama truncation/request-shape risk by passing truncate:false and num_ctx; this ticket is the separate duration-bound parity gap.
The Fix
Add the symmetric timeout bound for the native Ollama embedding path at the use site, preserving ADR-0019:
- Resolve the timeout through
AiConfig at the native Ollama embedding call site.
- Bound both
embedText(..., 'ollama') and embedTexts(..., 'ollama') provider calls.
- On timeout, throw a fail-loud timeout error that callers can route through existing retry/unrecoverable paths.
- Emit timeout-shaped
ConsumerFriction or the nearest existing diagnostic signal with provider ollama, matching the operational visibility shipped for openAiCompatible.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
TextEmbeddingService.embedText(text, 'ollama') |
TextEmbeddingService provider dispatch + #14036 per-call timeout contract |
Native Ollama single-text embedding is bounded by an AiConfig leaf and fails loud on timeout |
Throw timeout error; caller handles existing failure path |
JSDoc/comment if needed |
Unit with hanging fake Ollama provider |
TextEmbeddingService.embedTexts(texts, 'ollama') |
TextEmbeddingService provider dispatch + #14036 per-call timeout contract |
Native Ollama batch embedding is bounded by an AiConfig leaf and fails loud on timeout |
Throw timeout error; caller handles existing failure path |
JSDoc/comment if needed |
Unit with hanging fake Ollama provider |
| Timeout diagnostic/friction signal |
#14047 openAiCompatible timeout-friction precedent |
Timed-out native Ollama request emits provider-scoped timeout signal |
Error propagation remains fail-loud if signal emission is unavailable |
None |
Unit captures timeout signal payload |
| Config leaf |
ADR 0019 AiConfig Provider SSOT |
Timeout is read from the owning AiConfig surface at use site; no env re-read/pass-through hidden default |
Invalid value fails loud |
Config template JSDoc |
Config/template unit if a new leaf is added |
Decision Record impact
Aligned with ADR 0019. The implementation must keep AiConfig as the reactive provider SSOT: no direct env re-read, no copied config pass-through across modules, and no defensive optional-chain fallback that masks invalid config.
Acceptance Criteria
Out of Scope
- OpenAI-compatible batch timeout behavior already delivered by PR
#14047.
- Native Ollama truncation/request-shape parity already tracked by
#13946.
- CPU-saturation-with-no-throughput heuristics from
#14036; this ticket is duration-bound parity only.
- Changing the default embedding provider.
Avoided Traps
- Do not treat native Ollama as unreachable just because
openAiCompatible is the default. The selector and explicit dispatch remain supported surfaces.
- Do not add a provider-agnostic config pass-through object to satisfy this; that would violate ADR 0019's AiConfig boundary.
- Do not bundle CPU-saturation detection into this leaf. The release-critical parity gap is the bounded call.
Related
Origin Session ID: 35f83031-f1a6-41a7-9c3b-089b87307db9
Handoff Retrieval Hints: query_raw_memories("native Ollama embedTexts timeout ConsumerFriction #14047 follow-up"); source anchors: ai/services/memory-core/TextEmbeddingService.mjs:603-653, ai/config.template.mjs:151-170, PR #14047 Vega Approve+Follow-Up review.
Authored by Euclid (GPT-5, Codex Desktop).
Context
Vega's Approve+Follow-Up review on
#14047approved the OpenAI-compatible production path for#14036, but explicitly asked whether the native Ollama embedding path is reachable and still unbounded. I verified it is reachable through the existing provider selector and explicit dispatch path, so this is durable follow-up work rather than PR-thread prose.Freshness evidence:
#14036remains open and is the correct anchor.[lane-claim]/[lane-intent]overlapped this native Ollama timeout slice.#13946covers native Ollama truncation/request-shape (truncate:false+options.num_ctx), not request duration bounds.The Problem
#14036says each embed call should be bounded so one wedged provider request cannot stall the deferred-embed WAL drain indefinitely. PR#14047correctly bounded theopenAiCompatiblebatch path, which is the deployment default and the current production path. The native Ollama path remains outside that bound: if a deployment setsNEO_EMBEDDING_PROVIDER=ollama, or a runtime adapter explicitly callsembedTexts(texts, 'ollama'), the call can still wait forever onprovider.embed().That leaves a smaller but real version of the same failure mode: native Ollama can hang a batch embedding call with no per-call timeout, no timeout-shaped
ConsumerFriction, and no row-level fail-loud handoff for the caller.The Architectural Reality
ai/config.template.mjs:157exposesembeddingProvideras the deployment-wide embedding selector.ai/config.template.mjs:161-162documents that runtime adapters can explicitly select the nativeollamaprovider.ai/services/memory-core/TextEmbeddingService.mjs:603-611routesembedText(..., 'ollama')directly toprovider.embed().ai/services/memory-core/TextEmbeddingService.mjs:645-653routesembedTexts(..., 'ollama')directly toprovider.embed()with no request timeout.#13946already fixed the native Ollama truncation/request-shape risk by passingtruncate:falseandnum_ctx; this ticket is the separate duration-bound parity gap.The Fix
Add the symmetric timeout bound for the native Ollama embedding path at the use site, preserving ADR-0019:
AiConfigat the native Ollama embedding call site.embedText(..., 'ollama')andembedTexts(..., 'ollama')provider calls.ConsumerFrictionor the nearest existing diagnostic signal with providerollama, matching the operational visibility shipped foropenAiCompatible.Contract Ledger Matrix
TextEmbeddingService.embedText(text, 'ollama')TextEmbeddingServiceprovider dispatch +#14036per-call timeout contractTextEmbeddingService.embedTexts(texts, 'ollama')TextEmbeddingServiceprovider dispatch +#14036per-call timeout contract#14047openAiCompatible timeout-friction precedentDecision Record impact
Aligned with ADR 0019. The implementation must keep
AiConfigas the reactive provider SSOT: no direct env re-read, no copied config pass-through across modules, and no defensive optional-chain fallback that masks invalid config.Acceptance Criteria
embedText(..., 'ollama')is bounded by a native Ollama embedding timeout.embedTexts(..., 'ollama')is bounded by a native Ollama embedding timeout.Out of Scope
#14047.#13946.#14036; this ticket is duration-bound parity only.Avoided Traps
openAiCompatibleis the default. The selector and explicit dispatch remain supported surfaces.Related
Origin Session ID: 35f83031-f1a6-41a7-9c3b-089b87307db9
Handoff Retrieval Hints:
query_raw_memories("native Ollama embedTexts timeout ConsumerFriction #14047 follow-up"); source anchors:ai/services/memory-core/TextEmbeddingService.mjs:603-653,ai/config.template.mjs:151-170, PR#14047Vega Approve+Follow-Up review.Authored by Euclid (GPT-5, Codex Desktop).