跟踪一个无工具 Turn

实践目标

在不修改源码的前提下,从 CLI 单次消息跟踪到最终 Session 保存,能够亲自解释八个 TurnState 枚举值的顺序,并说明为什么 DONE 没有对应的 state handler。

前置知识

  • 已完成真实 DeepSeek 请求;
  • 已读 AgentLoop 状态机和 AgentRunner;
  • IDE 能以 python -m nanobot 启动当前源码 checkout。

需要阅读的代码

按顺序只读这些位置:

  1. nanobot/cli/commands.py::agent
  2. nanobot/agent/loop.py::AgentLoop.from_config
  3. nanobot/agent/loop.py::process_direct
  4. nanobot/agent/loop.py::_process_message
  5. nanobot/agent/loop.py::_state_restore 到 _state_respond
  6. nanobot/agent/runner.py::run
  7. nanobot/session/manager.py::save

先写下预测

运行前回答:

  • CLI 单次模式会不会启动 MessageBus.consume_inbound()?
  • Session lock 在哪里取得?
  • 无 Tool Call 时 Runner 迭代几次?
  • SAVE 在 RESPOND 之前还是之后?
  • 默认 Session key 是什么?

实践步骤

1. 设置断点

建议断点:

  • AgentLoop.process_direct() 调用 _process_message 前;
  • _process_message() 调用 state handler 前;
  • AgentRunner._request_model();
  • _state_save();
  • SessionManager.save()。

2. 用固定 Session 运行

假设前文已设置:

$nanobotLab = Join-Path $env:TEMP "nanobot-learning"

从 example/nanobot/code-EN 以调试方式运行等价命令:

python -m nanobot agent `
  --config "$nanobotLab\config.json" `
  --workspace "$nanobotLab\workspace" `
  --session "learning:no-tool" `
  --message "不要使用任何工具,只回复 TURN_OK"

若模型仍尝试使用 Tool,不要修改代码;换成更确定的消息再试,并在记录中说明真实模型决策不是硬保证。

3. 每个 state 记录四个字段

字段要观察什么
ctx.state当前状态
ctx.session.key是否为 learning:no-tool
ctx.history / ctx.initial_messagesSession 历史何时变为本轮模型输入
ctx.all_messages / ctx.final_contentRunner 返回后保存边界拿到了什么
ctx.stop_reason / ctx.outbound运行结果何时转成 OutboundMessage

4. 查看 trace

Turn 结束前检查 ctx.trace:

  • state 顺序;
  • duration_ms;
  • event;
  • error。

正常主路径的 trace 有七条 handler 记录;DONE 是循环退出条件,不执行 _state_done()。

5. 查看持久化文件

不要猜文件名,先列出:

Get-ChildItem -LiteralPath "$nanobotLab\workspace\sessions" -File |
  Sort-Object LastWriteTime -Descending |
  Select-Object -First 3 Name, LastWriteTime

打开最新 JSONL,确认:

  • 第一行是 metadata;
  • 后续至少有 user 和 assistant;
  • 没有凭据;
  • Session key 正确。

预期观察

主路径应为:

RESTORE → COMPACT → COMMAND → BUILD → RUN → SAVE → RESPOND → DONE

同时应看到:

  • CLI 单次模式直接调用 process_direct(),不依赖 AgentLoop 的 bus 消费循环;
  • process_direct() 仍复用 _session_locks;
  • BUILD 产生送往 Provider 的完整 messages;
  • Runner 只有一轮文本返回时不进入工具执行;
  • SAVE 先持久化,再由 RESPOND 构造响应;
  • process_direct() 的 finally 把 Runtime status 清回 idle。

完成条件

  • 画出实际调用链,至少包含 7 个函数;
  • 保存一张 state/关键字段观察表;
  • 能解释 CLI direct 与 Gateway bus path 的共同核心和不同入口;
  • 从 Session JSONL 找到此次 user/assistant 记录。

验证方法

用同一个 Session 再发一条消息:

python -m nanobot agent `
  --config "$nanobotLab\config.json" `
  --workspace "$nanobotLab\workspace" `
  --session "learning:no-tool" `
  --message "上一条消息要求你回复什么标记?只回复标记。"

若回答能利用上一轮,说明 Session replay 生效;再从 BUILD messages 证明它如何进入模型上下文。

复盘问题

  1. 为什么 direct path 仍需要 Session lock?
  2. trace 的 RUN 耗时能否完全等同于 Provider latency?
  3. ctx.messages 与 session.messages 是否是同一用途?
  4. 若在 SAVE 前崩溃,哪些内容可能只存在于内存/checkpoint?

下一篇:跟踪 Tool Call 与 Session