#5781·nanobot

Dream runs for 1–2 h looping on the same read_file calls; dream.maxIterations is deprecated/ignored so the global 200-iteration cap applies

Author: BrianMwangi21Created Sep 15, 2026Updated Sep 16, 2026
Labelsenhancementpriority: p2

Bug Description

Scheduled Dream consolidation runs turn into very long agent loops (25–111 minutes each, up to ~200 tool calls), during which the model alternates between re-reading the same two files dozens of times. dream.maxIterations in config has no effect: the source marks it deprecated (config/schema.py: max_iterations: int = Field(default=15, ge=1) # Deprecated: no longer used) and cmd_dream in command/builtin.py calls loop.process_direct(...) without a max_iterations, so Dream inherits the global agents.defaults.maxToolIterations (200).

Over the last ~26 hours of gateway logs:

dream 09-14 10:16  ran ~25 min   202 tool calls
dream 09-14 12:42  ran ~26 min   201 tool calls
dream 09-14 15:10  ran ~36 min   201 tool calls
dream 09-14 17:47  ran ~95 min    31 tool calls
dream 09-15 05:50  ran ~70 min    12 tool calls
dream 09-15 07:50  ran ~111 min   31 tool calls
dream 09-15 09:54  ran ~91 min   168 tool calls   (session latency_ms: 5447214)

The runs with ~200 tool calls are clearly hitting the global 200 cap. The session checkpoint for the last run shows runtime_checkpoint.iteration: 164, phase tools_completed, on the same read_file call. The reasoning_content shows the model re-deliberating where to route a single small fact (e.g. "python is not on PATH, use python3") over and over, re-reading the same 25 lines of memory/history.jsonl and the same 60 lines of one SKILL.md each time.

Steps to Reproduce

  1. Run nanobot gateway (v0.3.0) with the default Dream config (intervalH: 2, maxIterations: 15, modelOverride: null) and a workspace with a few hundred memory/history.jsonl entries and ~50 skills.
  2. Use a reasoning model as the default (here deepseek/deepseek-v4-flash-0731 via OpenRouter, temperature 0.1).
  3. Wait for the scheduled Cron: executing job 'dream' (dream).
  4. Watch the log: Tool call: read_file(...) for the same two paths repeats every ~30 s for an hour or more; the run only ends when the global maxToolIterations (200) is reached or the model eventually stops on its own.

Expected Behavior

  • A Dream-specific iteration cap that actually bounds the run (dream.maxIterations), or the key removed from the default config and the deprecation documented, so users aren't led to believe it does something.
  • Ideally a guard against repeated identical tool calls (same tool + same args N times in a row) during Dream, since consolidation is a narrow task and shouldn't need 200 iterations.

Relevant Logs

2026-09-15 09:54:17 | INFO  | - | Cron: executing job 'dream' (dream)
2026-09-15 09:54:17 | INFO  | - | Processing message from cli:user: You are a memory consolidation engine. Your sole task is to analyze conversation...
2026-09-15 09:55:09 | INFO  | - | Tool call: exec({"command": "tail -c 20000 /root/.nanobot/workspace/memory/history.jsonl 2>/dev/null | head -c 20000"})
...
2026-09-15 10:53:36 | INFO  | - | Tool call: read_file({"force": true, "limit": 25, "offset": 547, "path": "memory/history.jsonl"})
2026-09-15 10:53:48 | INFO  | - | Tool call: read_file({"limit": 60, "path": "skills/<skill-name>/SKILL.md"})
2026-09-15 10:53:54 | INFO  | - | Tool call: read_file({"force": true, "limit": 25, "offset": 547, "path": "memory/history.jsonl"})
2026-09-15 10:54:09 | INFO  | - | Tool call: read_file({"limit": 60, "path": "skills/<skill-name>/SKILL.md"})
2026-09-15 10:54:39 | INFO  | - | Tool call: read_file({"force": true, "limit": 25, "offset": 547, "path": "memory/history.jsonl"})
2026-09-15 10:55:15 | INFO  | - | Tool call: read_file({"limit": 60, "path": "skills/<skill-name>/SKILL.md"})
... (same pair repeats ~80 more times until 11:25)

Session checkpoint (sessions/dream:20260915-095417):

"runtime_checkpoint": {"phase": "tools_completed", "iteration": 164, "model": "deepseek/deepseek-v4-flash-0731",
 "assistant_message": {"role": "assistant", "content": "", "tool_calls": [{"function": {"name": "read_file",
 "arguments": "{\"force\": true, \"limit\": 25, \"offset\": 547, \"path\": \"memory/history.jsonl\"}"}}], ...

nanobot Version

0.3.0

Python Version

3.12

Operating System

Linux

Channel / Platform

Discord

LLM Provider

OpenRouter

Configuration (Optional)

agents:
  defaults:
    model: deepseek/deepseek-v4-flash-0731
    provider: openrouter
    maxTokens: 8192
    contextWindowTokens: 65536
    temperature: 0.1
    maxToolIterations: 200
    reasoningEffort: null
    dream:
      intervalH: 2
      modelOverride: null
      maxBatchSize: 20
      maxIterations: 15
      annotateLineAges: true

Additional Context

  • Each Dream burns up to ~200 LLM calls with a ~25k-token prompt, running 1–2 hours out of every 2-hour interval. On a 2-vCPU box it is the top CPU consumer and noticeably competes with live Discord replies.
  • Related but different from #5441 (cursor blocked by a tool error): here there is no error, the model just never converges.
  • Workarounds I'm considering: raising intervalH, setting dream.modelOverride to a faster/non-reasoning model, or disabling Dream. Happy to share the full Dream session jsonl (redacted) if useful.