看似无限循环的 nanobot 运行实际上是有界的:在发布的 v0.3.5 源码中,每次代理回合都受 agents.defaults.maxToolIterations 限制,其默认值为 200;维护者也明确记录说明,这并非字面意义上的无限循环。 真正的问题是令牌用量,因为达到该上限的运行会记录 201 个助手回合,并且每次都重新发送不断增长的提示词。要判断你遇到的是哪一种情况,需要回答四个问题;有证据支持的修复方案不是修改某个配置值。
先排除最明显的线索。上游报告是 issue #5781,其标题指向 dream.maxIterations;PR #5782 的标题是“fix(dream): enforce configured iteration limit”。该键在 v0.3.5 中不存在,并且该 PR 于 2026 年 9 月 16 日关闭,未合并。基于它编写的教程实际上没有配置任何内容。
先澄清一点,因为搜索结果会显示错误的跟踪项目。本页讨论的是 HKUDS/nanobot,采用 MIT 许可证,安装包是 PyPI 包 nanobot-ai——版本 0.3.5,要求 Python 3.11 或更高版本,于 2026 年 9 月 15 日上传。obot-platform/nanobot 是另一个 Go 项目,其 README 声明该项目处于维护模式并禁用了 issue;它的 issue 编号在此不构成证据。另一个没有后缀的 pip install nanobot 会安装无关的机器人导航包。
报告了什么,以及针对哪个版本
Dream 是 nanobot 的定时记忆整合任务,由网关作为名为 dream 的 cron 任务运行。它不是嵌入或向量索引任务:Kunavo 不提供嵌入模型,Dream 也不需要嵌入模型,因为 nanobot 的持久记忆是工作区中的普通文件,而 Dream 运行就是普通的聊天流量。
BrianMwangi21 于 2026 年 9 月 15 日针对 nanobot v0.3.0 提交了 Issue #5781,运行环境为 Python 3.12,使用通过 OpenRouter 接入的推理模型。症状是:Dream 任务在对 memory/history.jsonl 执行 read_file 和对同一个 SKILL.md 执行 read_file 之间交替了数十次,而推理轨迹反复重新斟酌该将一条简单事实归档到何处。在约 26 小时内记录的七次运行,耗时从约 25 分钟到约 111 分钟不等;工具调用次数接近 200 次的运行触及了全局上限,而一个会话检查点显示 runtime_checkpoint.iteration: 164,处于 tools_completed 阶段,仍停留在同一个 read_file 调用上。
这些数字需要同时附带三个范围限制。它们是单个用户自报的网关日志,使用单个模型,运行在 v0.3.0 上——没有维护者复现过,也没有人在 v0.3.5 上重新测试过。该 issue 标记为 enhancement 和 priority: p2,而不是 bug,目前仍处于开放状态。这种情况此前也出现过:2026 年 4 月 12 日的 issue #3073 报告了一个对 history.jsonl 执行 read_file 的几乎相同的循环,最终以 not planned 关闭。这些信息都不能支持把 nanobot 视为普遍昂贵;它们支持的是检查你自己的安装是否正在发生这种情况。
已弃用的键,以及当前真正生效的边界
整个混淆源于版本差异,而源码解决了这个问题。于 2026 年 9 月 21 日读取两个发布标签时:
| 配置键 | v0.3.0 中 | v0.3.5 中 | 现在应如何处理 |
|---|---|---|---|
dream.maxIterations | 默认值 15,标记为 # Deprecated: no longer used | 已从 schema 中移除 | 不要写入;它不会配置任何内容 |
dream.maxBatchSize | 默认值 20,相同的弃用注释 | 已移除 | 不要写入 |
dream.annotateLineAges | 默认值 true,相同的弃用注释 | 已移除 | 不要写入 |
dream.enabled | 默认值 true | 默认值 true | 可在 WebUI 运行时设置中编辑 |
dream.intervalH | 默认值 2 | 默认值 2 | 编辑 config.json;不是 WebUI 叶节点 |
dream.cron | Null;旧版覆盖值 | Null;旧版覆盖值 | 设置后优先于 intervalH |
dream.modelOverride | 已声明,并注释为 pending implementation | 已实现 | 只能使用预设名称,不能使用原始模型 ID |
agents.defaults.maxToolIterations | 200 | 200 | 唯一的上限,并且作用于整个进程 |
有两点很容易让人误读这张表。200 这个数字同时成立于两个层面——它既是报告者配置中的值,也是 nanobot/config/schema.py 第 129 行发布的默认值;在 v0.3.5 标签和 main 上都相同,因此即使读者从未设置过它,仍然是 200。另一个事实是,它没有文档记录:2026 年 9 月 21 日抓取 nanobot 自己的 0.3.5 配置参考时,返回了 488,608 字节的 HTML,其中出现 maxToolIterations 的次数为零;该标签下仓库的 docs/configuration.md 中也没有它。这个数字可以从源码和维护者评论中证明,而不是从文档页面中证明——因此应对任何引用不同数字的教程保持怀疑。
modelOverride 是唯一带有版本门槛的补救措施。在 v0.3.0 中,该字段存在,注释为 pending implementation,但完全没有解析代码;2026 年 7 月 27 日合并的 PR #5107 在 v0.3.5 中实现了它。官方 0.3.5 记忆页面说明,它会从 model_presets 中选择一个命名条目供 Dream 使用,只接受预设名称,不支持原始模型标识符。该页面记录了三个 Dream 键,但完全没有提及迭代上限。以下是当前完整的配置项;它所依赖的 providers 块已在 nanobot 设置页面中介绍:
{
"modelPresets": {
"dream-cheap": {
"provider": "kunavo",
"model": "claude-haiku-4-5",
"maxTokens": 8192
}
},
"agents": {
"defaults": {
"maxToolIterations": 200,
"dream": {
"enabled": true,
"intervalH": 2,
"modelOverride": "dream-cheap"
}
}
}
}注意该文件中没有什么:没有任何专门限制 Dream 的方式。AgentLoop 会使用进程默认值中的 max_iterations 构造一次,而 Dream 的 cron 路径和手动 /dream 路径都会在不传入迭代参数的情况下调用 process_direct(...),子代理也是如此。降低该上限会同时降低聊天、Dream、heartbeat 和子代理的上限。
按以下顺序进行诊断
先回答这四个问题再更改任何内容,因为其中三个无需成本,第四个会告诉你前三个是否重要。日志字符串是 v0.3.5 中的字面文本;大括号表示运行时值。
| 问题 | 查看位置 | 答案意味着什么 |
|---|---|---|
| 1. 运行是自行停止的,还是触及了上限? | 网关日志:Max iterations (200) reached,来自 agent/loop.py | 存在表示运行触及了上限,大约有 201 个助手回合。不存在表示运行在达到上限前结束——可能是模型已经收敛,也可能是运行直接失败并记录了 Dream cron job failed |
| 2. Dream 游标是否前进? | cli/gateway_runtime.py 中的三条不同日志:Dream cron job completed, cursor advanced to …;… completed with no memory changes; cursor advanced to …;Dream cron job did not complete (…); cursor remains at … | 第三行表示 v0.3.5 的故障安全机制正在工作:未完成的运行会留下该批次,以便重试。这也意味着同一个批次会在下一次 tick 中再次返回 |
| 3. 是否在重复使用相同参数调用相同工具? | 两个标记之间的 Tool call: 行 | 工具及参数反复完全相同,说明模型没有收敛,这也是上游得出的结论。v0.3.5 中唯一的重复防护会匹配 web_fetch 和 web_search;重复的 read_file 不会被捕获 |
| 4. 这花费了多少? | 配置目录中的 llm_usage.sqlite3,按日期和来源汇总;在网关侧,则按 usage 中的键和日期汇总 | Dream 的标签是 dream。heartbeat 的标签是 cron,因此筛选“heartbeat”不会找到任何内容 |
无需等待两个小时的 cron tick 才能复现。/dream 命令会按需运行同一个任务,并在聊天中报告相同的区别:成功时为 Dream completed in Ns. 或 Dream completed in Ns; no memory changes.,未完成时则针对 Dream did not complete after Ns (reason); memory cursor was not advanced.。在寻找证据时,还要注意 v0.3.5 的另一项变化:会话 JSONL 文件已移至配置目录的 sessions/<workspace-id>/ 树下,因此 issue 讨论中引用的 v0.3.0 路径并不是你的检查点所在位置。
五个手段,以及每个手段真正有何依据
| 杠杆 | 支持它的证据 | 适用场景 | 代价 |
|---|---|---|---|
| 将 v0.3.0 升级到 v0.3.5 | 4e2640f 提交在 v0.3.5 中存在而 v0.3.0 中不存在;它只会在停止原因是 completed 时推进游标 | 你的症状是记忆被跳过,而不是花费过高 | 它不会缩短不收敛的循环,也没有人在 0.3.5 上重新测试 #5781 |
| 更换 Dream 使用的模型 | 这是唯一有前后对比的补救措施:在报告者的审计中,一个模型花费 91 分钟仍未完成的批次,在下一次运行中由另一个模型用约一分钟完成,使用相同的提示词、相同的历史记录和相同的工具,并进行了 6 次工具调用 | 聊天质量必须继续使用你的昂贵模型 | 需要 v0.3.5 和已定义的预设;在 v0.3.0 中,该字段解析为空 |
降低 maxToolIterations | 可在 WebUI 运行时设置中编辑,每个 webui/settings_runtime.py 的最小值为 1 | 你希望在诊断期间设置最坏情况上限 | 聊天、Dream、heartbeat 和子代理共用一个旋钮;而且触及上限的运行不会推进游标,因此同一个批次会在下一次 tick 中重试 |
| 减慢或禁用 Dream | intervalH,或在 WebUI 中使用 dream.enabled;合并的 PR #5407 会在禁用时停用持久化任务 | 在你的工作负载下,记忆整合不值得付出相应成本 | 你会失去记忆整合功能,而这正是该功能本身 |
| 进行路由,让重新发送的成本更低 | 在 v0.3.5 中,恰好有两个 provider spec 设置 supports_prompt_caching:anthropic 和 openrouter;该字段默认为 false | 你接受长时间运行会发生,并希望降低其成本 | 取决于你配置的协议路径,也取决于你自行核验返回的 usage |
报告者提到的两个模型 ID 是他通过 OpenRouter 写下的 deepseek/deepseek-v4-flash-0731 和 openai/gpt-5.6-luna;此处未核验这些 ID 及其价格,因此应将该比较理解为模型决定是否收敛的证据,而不是对任一模型的推荐。上游得出了相同结论:在 issue #5781 中宣布关闭 PR #5782 时,chengyongru 写道,固定的 Dream 迭代上限无法解决底层的、取决于模型的收敛问题,并且可能导致本来可行的运行被反复重试。
一次触及上限的运行成本:示例算术
这些是示例性的令牌算术,不是实测任务成本,也不是账单上限。nanobot 没有公布 Dream 每次运行的令牌数量,因此这里的每个输入都是假设,你应以自己的测量结果替换。假设一次运行达到上限,即 201 个助手回合——这是报告者审计中的数量——每个回合都重新发送一个固定为 25,000 个令牌的提示词;这是他对自己工作区的描述,并非已发布的数字。总计为 5.03M 个输入令牌。不计输出令牌,而且真实提示词会随着每次工具结果而增长,因此这会同时从两个方向低估真实运行。费率采用实时的 Kunavo 目录价格。
| 模型 | 每 1M 个输入令牌 | 每 1M 个缓存读取令牌 | 一次触及上限的运行,不使用缓存 | 同一次运行,从缓存读取重新发送内容 |
|---|---|---|---|---|
| Claude Haiku 4.5 | $0.70 | $0.07 | $3.52 | $0.37 |
| GPT-5.6 Terra | $0.70 | $0.07 | $3.52 | $0.37 |
| Claude Sonnet 5 | $1.40 | $0.14 | $7.04 | $0.74 |
最后一列假设了一个这里没有测试过的最佳情况:第一次发送按普通输入费率计费,所有 200 次重新发送都按缓存读取计费。缓存写入按其自身费率计费,在某些模型上该费率高于普通输入费率;缓存条目会过期;而不断增长的提示词会被重新写入,而不是重新读取——因此应将最后两列之间的差额视为潜在收益的大小,而不是报价。这里只想说明,在一次漫长且重复的运行中,花钱的主要来源是重新发送,而不是模型本身。
缓存标记是否真正传输到线路上,取决于你的 nanobot 配置,发布的源码中也能看到这一点。providers 下一个虚构的 provider key 会被视为普通的 OpenAI 兼容 provider,并让 supports_prompt_caching 保持默认值 false;因此,即使模型 ID 具有 Claude 形式,nanobot 客户端也不会在该路径上发送 cache_control 标记。保留内置 anthropic provider 中的预设并覆盖 providers.anthropic.apiBase,则会保留这些标记。这说明的是 nanobot 客户端发送什么,而不是任何端点自身如何处理;并且Kunavo 未对 nanobot 进行运行时测试。在将周期性任务按缓存计入预算之前,先读取一次真实调用返回的 usage——缓存文档展示了正常工作的缓存是什么样,基础 URL 参考则给出了两种端点约定。
Kunavo 目录金额是计费下限,而不是上限:上游报告其费用后,账单取目录成本与上游成本乘以适用加价两者中的较大值。最低充值额为预付余额 $10——这是资金最低要求,不是任务费用,也不是订阅费用。请参阅计费详情,以及成本优化,了解替代上述假设的测量方法。
通过一次运行验证修复
只更改一项内容,然后运行一次 /dream,不要等待计划任务,并按顺序检查三个标记:没有 Max iterations (200) reached 警告;出现 cursor advanced to … 行而不是 cursor remains at …;中间的工具调用数量看起来合理。然后在 usage 中,按键和日期读取你的账户在该时间窗口记录的费用;nanobot 的存储统计的是令牌,而不是金额。如果你是第一次配置端点,快速入门和 nanobot 设置页面涵盖两种协议路径,而创建 Kunavo 账户是为密钥充值之前的步骤。若要比较运行时间,nanobot 与 OpenClaw会并列展示两种后台运行频率,代理 API 目录则按线路协议索引客户端。
常见问题
nanobot 真的陷入无限循环了吗?
不是,维护者也已公开说明这一点。chengyongru 于 2026 年 8 月 10 日评论 issue #5324 时写道,nanobot 的代理运行器受 agents.defaults.maxToolIterations 限制,默认值为 200,因此这不是字面意义上的无限循环——但他补充说,较长的有限循环仍可能造成极高的累计令牌使用量,所以实际影响确实存在。已发布的 v0.3.5 源码与此一致:nanobot/config/schema.py 中 max_tool_iterations 的默认值为 200;当一次运行达到该上限时,agent/loop.py 会记录警告“Max iterations (200) reached”。人们所谓的无限循环,实际上是一次达到该上限的运行;在一名报告者的日志中,这意味着助手进行了 201 个轮次,反复读取同两个文件。
为什么设置 dream.maxIterations 没有作用?
因为该字段已经不存在。在 nanobot v0.3.0 中,Dream 配置包含 max_iterations、max_batch_size 和 annotate_line_ages,源码中每个字段都标有“已弃用:不再使用”的注释;在 v0.3.5 标签中,三者全部消失,类中只定义了 enabled、intervalH、cron 和 modelOverride。维护者 chengyongru 于 2026 年 9 月 15 日表示,Dream 转移到常规代理循环时,dream.maxIterations 被有意标记为弃用且不再使用,之后已从 main 中移除。试图恢复它的拉取请求 #5782 次日关闭,未合并。在 v0.3.5 配置中写入该键,只会写入一个模式未定义的键。
如何仅限制 nanobot Dream 任务的令牌?
在 nanobot v0.3.5 中,不能将其作为累计预算。已发布代码不会统计一次运行的令牌总数,也不会在达到某个数值时停止;agents.defaults.maxToolIterations 是唯一的迭代上限,而且作用于整个进程,同时适用于普通聊天轮次、Dream、心跳和子代理。你可以通过 agents.defaults.dream.modelOverride 仅针对 Dream 设置两项内容:模型,以及该预设自身的每次调用限制,因为 ModelPresetConfig 包含 maxTokens 和 contextWindowTokens,而 dream_runtime() 会将命名预设解析为 Dream 运行所使用的运行时环境。这些限制作用于每次单独调用,而不是整个运行,因此在较低 maxTokens 下进行 200 次调用,仍然是 200 次调用。调度也可以通过 intervalH 单独设置范围。累计预算是上游目前拒绝添加的功能:2026 年 9 月 16 日 issue #5781 上、也就是关闭 PR #5782 的当天,chengyongru 写道,后台任务的令牌或资源预算需要更详细的设计,涵盖预算单位和范围、终止语义、重试和游标行为、可观测性,以及与不同模型的交互;目前他们不计划推进这项工作。
如何停止已经开始运行的 nanobot Dream 任务?
根据对 v0.3.5 源码的阅读,不能通过 /stop 停止。文档说明 /stop 会取消此聊天的活动代理轮次,其实现方式是取消注册在该聊天会话键下的任务;而 Dream 运行使用形如 dream:YYYYMMDD-HHMMSS 的独立临时键创建。这是对代码路径的解读,不是测试结果——这里没有对正在运行的 Dream 任务执行 /stop。已有文档说明的控制手段是 /restart、关闭 agents.defaults.dream.enabled,或停止网关进程。v0.3.5 中已合并的 PR #5407 使禁用操作真正撤销持久化系统任务,而不是让它继续处于已调度状态。
如何查看 nanobot 后台任务使用了多少令牌?
读取本地使用量存储,而不是使用斜杠命令。nanobot v0.3.5 会记录每次模型调用,其 source 字段类型为 user、api、cron、dream 或 system,并将数据写入配置目录中的 llm_usage.sqlite3——默认位置为 ~/.nanobot——其中包含 input、output、cache-read 和 cache-write 令牌列,还会按日期和来源分组汇总。需要注意两点。没有 /insights 或 /cost 命令:提出这两个命令的拉取请求 #3735 和 #3921 均已关闭且未合并;v0.3.5 内置命令列表是 /new、/compact、/stop、/restart、/status、/model、/history、/goal、/trigger、/dream、/dream-log、/dream-restore、/dream-prompt、/evaluator-prompt、/skill、/help 和 /pairing。标签也不对称:Dream 支出标记为 dream,但心跳支出标记为 cron,因为心跳会话键字面上就是“heartbeat”。这些是令牌数量,不是金额,因此应与提供商自己的账本进行核对。
升级到 nanobot v0.3.5 能解决循环问题吗?
没有人说它能,本页面也不会这样说。issue #5781 针对 v0.3.0 提出,目前仍处于开放状态,标签为 enhancement 和 priority p2;报告者升级后没有重新测试,维护者也没有复现。v0.3.5 修复的范围更窄,但仍值得升级:提交 4e2640f 会阻止未完成的运行推进 Dream 游标并静默跳过历史记录;已合并的 PR #5442 会说明未完成运行未完成的原因;已合并的 PR #5325 使 edit_file 在无操作编辑时返回“Error: new_text must be different from old_text.”,而不是报告成功。最后一项修复针对 issue #5324 的读取后编辑循环,而不是 #5781 的只读循环。v0.3.5 也没有提供通用的重复工具调用保护。已发布源码中唯一的重复保护 repeated_external_lookup_error 位于 nanobot/utils/runtime.py:它会在两次尝试后阻止相同的 web_fetch 或 web_search,但不匹配其他工具名称,因此重复的 read_file 会继续运行直到达到迭代上限。提出通用保护的五个拉取请求——#3077、#4522、#5344、#3701 和 #4154——均未合并。
于 2026 年 9 月 21 日核查:HKUDS/nanobot 的 GitHub API,以及 issue #5781、#5324、#3073 和 pull request #5782、#5107、#5325、#5442、#5407、#3077、#4522、#5344、#3701、#4154、#3735、#3921、#4622 的 GitHub API;nanobot-ai 0.3.5 的 PyPI 记录;nanobot 的 0.3.5 记忆和配置文档;以及 v0.3.5 标签中的发布源码,用于核对上文引用的每个默认值、日志字符串和代码路径,并在两个版本存在差异的地方与 v0.3.0 进行比较。Kunavo 未安装或运行 nanobot,此处没有任何行为经过 Kunavo 测试,且没有任何人在 v0.3.5 上重新测试 issue #5781 的循环。令牌费率来自实时目录,每个美元金额都是基于所述假设的示例性计算。