Short description
The map-memories dreamer task divides its whole task deadline evenly across the remaining batches. With MAP_BATCH_SIZE = 80 (hardcoded) and the schema-default timeout_minutes: 20, per-batch prompt budget = 1200s ÷ ceil(backlog/80). Any backlog above ~880 memories (≥12 batches) drops the budget to ≤100s. An agentic model executing map-memories through tool calls needs minutes per batch. Result: every batch times out, nothing is ever committed, and the loop repeats until the deadline decays — a zero-progress feedback loop that worsens as the backlog grows.
What happened?
Summary
The map-memories dreamer task divides its whole task deadline evenly across the remaining batches:
// dist/index.js (@cortexkit/opencode-magic-context@0.38.0), mapMemories():
const deadline = startedAt + config.timeoutMinutes * 60 * 1000;
...
const remainingMs = Math.max(0, args.deadline - Date.now());
const batchesRemaining = batches.length - i;
const sliceMs = Math.max(1, Math.floor(remainingMs / batchesRemaining));
// → promptSyncWithValidatedOutputRetry(..., { timeoutMs: sliceMs })
With MAP_BATCH_SIZE = 80 (hardcoded) and the schema-default timeout_minutes: 20, per-batch prompt budget = 1200s ÷ ceil(backlog/80). Any backlog above ~880 memories (≥12 batches) drops the budget to ≤100s. An agentic model executing map-memories through tool calls needs minutes per batch. Result: every batch times out, nothing is ever committed, and the loop repeats until the deadline decays — a zero-progress feedback loop that worsens as the backlog grows.
This is the out-of-the-box configuration. At reproduction time our config contained no timeout_minutes key at all (config-at-starvation-repro.jsonc); we verified against upstream source that,
(a) the official setup flow never asks about or writes this key — dreamer-setup.ts persists only model and per-task schedule — and
(b) the effective default is plugin-internal (installed v0.38.0: schema .default(20); upstream HEAD: runtime ?? 20 in task-config.ts).
The only code path that writes it into a user config is a legacy v1→v2 migration gated on an old key our config never had. No user action produces this state; every fresh install lands in it.
Environment
| Component |
Value |
| Harness |
OpenCode TUI, local server (opencode --port 5012) |
| OpenCode version |
1.18.21 |
| Plugin |
@cortexkit/opencode-magic-context@0.38.0 (pinned in opencode.jsonc "plugin" array) |
| Dreamer/sidekick/historian model |
nvidia/nvidia/nemotron-3-ultra-550b-a55b (NVIDIA NIM, free tier) |
| OS |
Linux (x86_64) |
| Backlog at observation |
remaining=916→921 unmapped inputs ⇒ batches=12 (ceil(921/80)=12, matches logs) |
Steps to reproduce
- Fresh install / default config: do not set
timeout_minutes (the official setup flow never writes it — verified in dreamer-setup.ts; the starving default is what every new user gets).
- Accumulate a memory backlog ≥ ~880 items (⇒ ≥12 batches at MAP_BATCH_SIZE=80 ⇒ ≤100s slices at the default budget; observed: 916–921 items → 12 batches).
- Run the dreamer on a model whose per-batch tool-loop cost exceeds
deadline/batches (~100s here).
- Observe
map-memories batch failed: "prompt timed out after <sliceMs>" repeating every tick with no committed mappings.
Actual behavior (primary evidence)
Plugin log (magic-context.log), all lines from one day, verbatim timestamps and slice values:
[2026-08-22T02:01:40Z] batch failed: "prompt timed out after 99984ms"
[2026-08-22T02:03:21Z] batch failed: "prompt timed out after 99915ms"
[2026-08-22T02:05:02Z] batch failed: "prompt timed out after 99804ms"
[2026-08-22T02:06:43Z] batch failed: "prompt timed out after 99712ms"
[2026-08-22T02:08:23Z] batch failed: "prompt timed out after 99621ms"
[2026-08-22T02:10:03Z] batch failed: "prompt timed out after 99523ms"
[2026-08-22T02:11:44Z] batch failed: "prompt timed out after 99237ms"
[2026-08-22T02:13:24Z] batch failed: "prompt timed out after 99059ms"
[2026-08-22T02:15:04Z] batch failed: "prompt timed out after 98658ms"
[2026-08-22T02:16:44Z] batch failed: "prompt timed out after 98336ms"
[2026-08-22T02:18:23Z] batch failed: "prompt timed out after 97914ms"
[2026-08-22T02:20:00Z] batch failed: "prompt timed out after 96230ms"
Twelve consecutive attempts, slices monotonically decaying 100s→96s exactly as the formula predicts (the shared 1200s deadline depletes while batchesRemaining stays constant because no batch ever succeeds). Task summaries throughout: map-memories: mapped=0 ... complete=false.
Backlog-growth proof of the feedback loop: remaining=916 (01:54Z) → remaining=921 (02:20Z) — the backlog grew during hours of retries because new memories were indexed while none were ever mapped.
Knob verification (proves the mechanism, not coincidence): after setting timeout_minutes: 60 and restarting, the very next timeout was 299979ms — i.e., 3600000ms ÷ 12 batches = 300000ms minus 21ms of pre-slice elapsed time. Byte-for-byte match with the traced formula.
Natural experiment corroborating the batch-count dependence: two days earlier, the same task with a small backlog ran as a single batch and completed cleanly:
[2026-08-20T20:40:54Z] map-memories: mapped=21 independent=9 batches=1 remaining=0 complete=true
With batchesRemaining = 1, the slice equals the entire remaining task window — ample time. Same model class of workload, only the batch count differs: 1 batch → success; 12 batches → 12 consecutive ~100s timeouts.
Expected behavior
Per-batch budgets should be derived from expected batch cost (or adapted), and/or the system should detect sliceMs < plausible batch cost early and fail loudly / reduce scope instead of silently burning the entire task window on doomed prompts. At minimum, progress made inside a timed-out batch should not be wholly discarded.
Suggested fixes (any one suffices to close the loop)
- Cost-aware slicing: estimate per-batch cost (rolling average of completed batch durations) and set
timeoutMinutes guidance or split batches so cost_estimate ≤ slice.
- Adaptive batching: if measured pace exceeds slice, halve the batch (re-chunk the 80 inputs) instead of failing.
- Early-exit with warning: when
remainingMs / batchesRemaining < MIN_PLAUSIBLE_BATCH_MS, abort the run up-front with an actionable message ("backlog too large for task budget; raise timeout_minutes").
- Docs: surface
MAP_BATCH_SIZE / per-task budget interplay in the schema description for timeout_minutes.
Workaround used (works, but is manual)
~/.config/cortexkit/magic-context.jsonc:
This eliminated timeouts entirely (verified live: previous 300s slices produced 299979ms timeouts; after the change the first batch ran ~8 min and reached validation). But it only shifts the cliff — the starvation returns whenever backlog × per-batch cost outgrows the window again. Note that the existence of this workaround does not downgrade the issue: the defect is that the default configuration (key absent, which is what setup produces and what most users will run) has no viable path once the backlog outgrows the silent 20-minute budget.
Upstream tracker check (no duplicate)
no prior issue covers even-slice allocation, timeout_minutes adequacy, or batch-count starvation. The formula is still present at master HEAD (map-memories.ts:181-182) and in the latest release v0.38.1 (2026-08-21, changelog does not touch it). Tangentially related, not duplicates: #272 (dreamer progress visibility), #270 (sub-agent empty result).
Notes for maintainers
- Two larger slices observed under default config (
353823ms, 390045ms) are also formula-consistent: those runs evidently had fewer eligible inputs (≤3 batches ⇒ ~240 inputs), implying loadUnmappedInputs eligibility varies between runs. Worth documenting alongside the fix.
Attachment manifest (this package)
Diagnostics
Plugin version
0.36.0
OpenCode version
1.18.20
Platform
linux x64
Client
OpenCode TUI (CLI)
Log output (optional)
Short description
The
map-memoriesdreamer task divides its whole task deadline evenly across the remaining batches. WithMAP_BATCH_SIZE = 80(hardcoded) and the schema-defaulttimeout_minutes: 20, per-batch prompt budget =1200s ÷ ceil(backlog/80). Any backlog above ~880 memories (≥12 batches) drops the budget to ≤100s. An agentic model executing map-memories through tool calls needs minutes per batch. Result: every batch times out, nothing is ever committed, and the loop repeats until the deadline decays — a zero-progress feedback loop that worsens as the backlog grows.What happened?
Summary
The
map-memoriesdreamer task divides its whole task deadline evenly across the remaining batches:With
MAP_BATCH_SIZE = 80(hardcoded) and the schema-defaulttimeout_minutes: 20, per-batch prompt budget =1200s ÷ ceil(backlog/80). Any backlog above ~880 memories (≥12 batches) drops the budget to ≤100s. An agentic model executing map-memories through tool calls needs minutes per batch. Result: every batch times out, nothing is ever committed, and the loop repeats until the deadline decays — a zero-progress feedback loop that worsens as the backlog grows.This is the out-of-the-box configuration. At reproduction time our config contained no
timeout_minuteskey at all (config-at-starvation-repro.jsonc); we verified against upstream source that,(a) the official setup flow never asks about or writes this key —
dreamer-setup.tspersists onlymodeland per-taskschedule— and(b) the effective default is plugin-internal (installed v0.38.0: schema
.default(20); upstream HEAD: runtime?? 20intask-config.ts).The only code path that writes it into a user config is a legacy v1→v2 migration gated on an old key our config never had. No user action produces this state; every fresh install lands in it.
Environment
opencode --port 5012)@cortexkit/opencode-magic-context@0.38.0(pinned inopencode.jsonc"plugin"array)nvidia/nvidia/nemotron-3-ultra-550b-a55b(NVIDIA NIM, free tier)remaining=916→921unmapped inputs ⇒batches=12(ceil(921/80)=12, matches logs)Steps to reproduce
timeout_minutes(the official setup flow never writes it — verified indreamer-setup.ts; the starving default is what every new user gets).deadline/batches(~100s here).map-memories batch failed: "prompt timed out after <sliceMs>"repeating every tick with no committed mappings.Actual behavior (primary evidence)
Plugin log (
magic-context.log), all lines from one day, verbatim timestamps and slice values:Twelve consecutive attempts, slices monotonically decaying 100s→96s exactly as the formula predicts (the shared 1200s deadline depletes while
batchesRemainingstays constant because no batch ever succeeds). Task summaries throughout:map-memories: mapped=0 ... complete=false.Backlog-growth proof of the feedback loop:
remaining=916(01:54Z) →remaining=921(02:20Z) — the backlog grew during hours of retries because new memories were indexed while none were ever mapped.Knob verification (proves the mechanism, not coincidence): after setting
timeout_minutes: 60and restarting, the very next timeout was299979ms— i.e.,3600000ms ÷ 12 batches = 300000msminus 21ms of pre-slice elapsed time. Byte-for-byte match with the traced formula.Natural experiment corroborating the batch-count dependence: two days earlier, the same task with a small backlog ran as a single batch and completed cleanly:
With
batchesRemaining = 1, the slice equals the entire remaining task window — ample time. Same model class of workload, only the batch count differs: 1 batch → success; 12 batches → 12 consecutive ~100s timeouts.Expected behavior
Per-batch budgets should be derived from expected batch cost (or adapted), and/or the system should detect
sliceMs < plausible batch costearly and fail loudly / reduce scope instead of silently burning the entire task window on doomed prompts. At minimum, progress made inside a timed-out batch should not be wholly discarded.Suggested fixes (any one suffices to close the loop)
timeoutMinutesguidance or split batches socost_estimate ≤ slice.remainingMs / batchesRemaining < MIN_PLAUSIBLE_BATCH_MS, abort the run up-front with an actionable message ("backlog too large for task budget; raisetimeout_minutes").MAP_BATCH_SIZE/ per-task budget interplay in the schema description fortimeout_minutes.Workaround used (works, but is manual)
~/.config/cortexkit/magic-context.jsonc:This eliminated timeouts entirely (verified live: previous 300s slices produced
299979mstimeouts; after the change the first batch ran ~8 min and reached validation). But it only shifts the cliff — the starvation returns whenever backlog × per-batch cost outgrows the window again. Note that the existence of this workaround does not downgrade the issue: the defect is that the default configuration (key absent, which is what setup produces and what most users will run) has no viable path once the backlog outgrows the silent 20-minute budget.Upstream tracker check (no duplicate)
no prior issue covers even-slice allocation,
timeout_minutesadequacy, or batch-count starvation. The formula is still present at master HEAD (map-memories.ts:181-182) and in the latest release v0.38.1 (2026-08-21, changelog does not touch it). Tangentially related, not duplicates: #272 (dreamer progress visibility), #270 (sub-agent empty result).Notes for maintainers
353823ms,390045ms) are also formula-consistent: those runs evidently had fewer eligible inputs (≤3 batches ⇒ ~240 inputs), implyingloadUnmappedInputseligibility varies between runs. Worth documenting alongside the fix.Attachment manifest (this package)
timeout_minuteskey → default 20-min budget)doctor --issueoutput (auto-collected diagnostics: config paths, plugin cache v0.37.0-cached/v0.38.1-latest, storage, sanitized log tail)Diagnostics
Plugin version
0.36.0
OpenCode version
1.18.20
Platform
linux x64
Client
OpenCode TUI (CLI)
Log output (optional)