Parent context
Follow-up surfaced by @neo-gpt PR #12076 cycle-2 review (Epic #12065 Sub 8 / #12074). The keep-alive-probe.mjs benchmark cannot characterize native Ollama keep_alive control because of an SDK-level payload-shape gap; this ticket owns the SDK fix.
V-B-A finding (banked from PR #12076 cycle-2)
ai/provider/Ollama.mjs:
| Line |
Observation |
91-92 (in preparePayload()) |
Arbitrary remaining options are nested under payload.options (e.g. provider.stream(messages, {keep_alive: '1h'}) → payload.options.keep_alive = '1h') |
108 (in generate()) |
payload.keep_alive = "1h" is set TOP-LEVEL after preparePayload() returns — bypasses the option-nesting |
271 (in stream()) |
Serializes the unmodified preparePayload() result — no top-level keep_alive injection |
Native Ollama /api/chat reads keep_alive at TOP LEVEL of the request body. The nested payload.options.keep_alive is silently ignored by Ollama's REST API. This makes caller-supplied keep_alive via provider.stream() unobservable on the native Ollama path.
Prescription
Mirror Ollama.generate() line 108's pattern in Ollama.stream():
async *stream(input, options = {}) {
const payload = this.preparePayload(input, options, true);
if (options.keep_alive !== undefined) {
payload.keep_alive = options.keep_alive;
if (payload.options) delete payload.options.keep_alive;
}
}(Final shape may also extract keep_alive from payload.options to remove the nested duplicate.)
Acceptance Criteria
Avoided Traps
- Don't expand scope to a generic provider-options-promotion pattern —
keep_alive is the documented Ollama-specific request-body field; other payload.options.* fields are correctly Ollama's options model-parameters object (temperature, top_p, etc.)
- Don't change
Ollama.generate() line 108 — it already does the right thing; this ticket only adds parity to the stream() path
- Don't bundle with broader provider refactor — Epic #12065 has Sub 3 (executeRemCycle) which may touch provider lifecycle; this ticket is a focused SDK fix
Related
- Epic #12065 — Orchestrator-as-SSOT REM pipeline
- #12074 / PR #12076 — gemma4 benchmark Sub 8 (this ticket unblocks AC3 native-Ollama keep_alive characterization)
- @neo-gpt cycle-2 review on PR #12076 (2026-05-27) — original V-B-A surface
learn/agentos/gemma4-rem-benchmark.md — substrate V-B-A findings table
Parent context
Follow-up surfaced by @neo-gpt PR #12076 cycle-2 review (Epic #12065 Sub 8 / #12074). The
keep-alive-probe.mjsbenchmark cannot characterize native Ollama keep_alive control because of an SDK-level payload-shape gap; this ticket owns the SDK fix.V-B-A finding (banked from PR #12076 cycle-2)
ai/provider/Ollama.mjs:preparePayload())optionsare nested underpayload.options(e.g.provider.stream(messages, {keep_alive: '1h'})→payload.options.keep_alive = '1h')generate())payload.keep_alive = "1h"is set TOP-LEVEL afterpreparePayload()returns — bypasses the option-nestingstream())preparePayload()result — no top-levelkeep_aliveinjectionNative Ollama
/api/chatreadskeep_aliveat TOP LEVEL of the request body. The nestedpayload.options.keep_aliveis silently ignored by Ollama's REST API. This makes caller-suppliedkeep_aliveviaprovider.stream()unobservable on the native Ollama path.Prescription
Mirror
Ollama.generate()line 108's pattern inOllama.stream():async *stream(input, options = {}) { const payload = this.preparePayload(input, options, true); // Promote top-level keep_alive (mirror of generate() line 108) so streaming // callers can control KV-cache retention. Without this, keep_alive nested // under payload.options is silently ignored by Ollama's /api/chat. if (options.keep_alive !== undefined) { payload.keep_alive = options.keep_alive; if (payload.options) delete payload.options.keep_alive; } // ... existing fetch logic }(Final shape may also extract
keep_alivefrompayload.optionsto remove the nested duplicate.)Acceptance Criteria
Ollama.stream()promotes caller-suppliedkeep_alivefrom nestedpayload.optionsto top-levelpayload.keep_alivebefore serializationpayload.keep_aliveis set when caller passesoptions.keep_alive)ai/scripts/benchmark/keep-alive-probe.mjsOllama early-exit (added in PR #12076 cycle-2) is REVERTED — probe can now characterize native Ollama keep_alive control empiricallylearn/agentos/gemma4-rem-benchmark.md"Substrate V-B-A findings" table row forOllama.mjs:91-92updated to reflect the fixOpenAiCompatible.stream()does NOT have the same nesting bug — current cycle-2 V-B-A showsOpenAiCompatible.preparePayload()line 105 already usesObject.assign(payload, clonedOptions)so top-level IS the natural shape. AC5 may close as "no-op confirmed" once verifiedAvoided Traps
keep_aliveis the documented Ollama-specific request-body field; otherpayload.options.*fields are correctly Ollama'soptionsmodel-parameters object (temperature, top_p, etc.)Ollama.generate()line 108 — it already does the right thing; this ticket only adds parity to thestream()pathRelated
learn/agentos/gemma4-rem-benchmark.md— substrate V-B-A findings table