Goal 与 Continuation
对应源码
| 主题 | 源码 |
|---|---|
| Goal metadata 解析与模型可见状态 | nanobot/session/goal_state.py |
| continuation 决策和保存边界 | nanobot/session/turn_continuation.py::maybe_continue_turn、prepare_save_boundary |
| Goal Tools | nanobot/agent/tools/long_task.py::LongTaskTool、CompleteGoalTool |
| Runner 的 Goal 继续机制 | nanobot/agent/runner.py::AgentRunner._try_drain_injections |
| Loop 接入点 | nanobot/agent/loop.py::AgentLoop._state_run、_state_save |
学习目标
读完后,你应当能够:
- 说明 sustained goal 保存在哪里;
- 解释
long_task和complete_goal的职责; - 说明达到 iteration budget 后如何内部续跑;
- 区分 Goal、Cron 和 Subagent。
前置知识
- 理解 Session metadata;
- 理解 AgentRunner max iterations;
- 理解 pending queue。
1. Goal 是 Session metadata
当前 key:
goal_state旧 Session 可能使用兼容 key:
thread_goal读取时接受旧 key,新的写入迁移到 goal_state。
Goal 典型字段包括:
- objective;
- status;
- ui summary;
- 创建/更新时间等工具写入信息。
Goal 不是单独数据库,也不是独立 Agent 进程。
2. 两个 Tool
long_task
主要作用:
- 创建或更新 active goal;
- 写入 Session metadata;
- 清理 legacy key;
- 保存 Session;
- 发布
GoalStateChanged。
complete_goal
主要作用:
- 将 active goal 标记完成;
- 更新 metadata;
- 保存;
- 发布状态变化。
实现:
nanobot/agent/tools/long_task.py
3. Goal 如何持续进入上下文
goal_state_runtime_lines() 将 active objective 转成 Runtime Context 行:
Goal (active):
<objective>
Summary: ...即使较早对话被 compaction,Goal 仍由 Session metadata 在每个 Turn 重新投影到上下文。
这比只把目标写在某条 user 消息中更稳定。
Goal metadata 仍需边界
objective 有长度上限,WebSocket snapshot 使用更短的展示上限。运行上下文和 UI 不是完整 metadata dump。
4. Sustained Goal 的 LLM wall timeout
runner_wall_llm_timeout_s() 对 sustained-goal Turn 返回 0.0,表示禁用 Runner 外层 asyncio.wait_for 的普通 wall timeout;其他 Turn 返回 None,使用环境变量默认策略。
这不代表:
- Provider 永远不会超时;
- stream idle timeout 被禁用;
- 工具没有自己的 timeout;
- Goal 可以无限运行。
它只调整 Runner 包裹单次 LLM 请求的 wall-clock cap。
5. 为什么需要 Continuation
单个 AgentRunner 仍受 max_iterations 约束。若 active goal 在本次预算内没有完成,直接向用户返回“达到上限”会打断长任务。
turn_continuation.maybe_continue_turn() 在满足条件时:
- 确认 stop reason 为
max_iterations; - 确认 pending queue 可用;
- 确认 Goal 仍 active;
- 确认 continuation 轮数未达到上限;
- 创建内部 continuation metadata;
- 去掉人工生成的终止 assistant 消息;
- 抑制当前用户可见回复;
- 将新的内部 InboundMessage 放入 pending queue。
flowchart TD R["Runner: max_iterations"] --> A{"Goal active?"} A -->|否| FINAL["正常完成上限处理"] A -->|是| Q{"pending queue 可用<br/>且轮数未超限?"} Q -->|否| FINAL Q -->|是| I["生成 internal continuation"] I --> S["当前 slice 保存但不回复"] S --> N["下一 slice 继续同一 Session"] N --> R
6. Continuation 不是新用户消息
内部消息带有:
_internal_continuation- continuation kind;
- 原始可见运行开始时间;
- 跳过或控制 user 持久化的 metadata;
session_key_override。
保存边界会避免把内部 continuation prompt 当作普通用户输入重复写入历史。
7. 最大续跑轮数
当前内部常量:
_MAX_GOAL_CONTINUATION_ROUNDS = 12它限制一次 sustained goal 在达到每个 Runner iteration budget 后可以自动续跑多少个 slice。
达到上限仍需要产生明确的用户可见结果,不能静默无限后台运行。
8. Goal、Cron、Subagent 的区别
| 机制 | 核心形式 | 是否后台 | 是否持久化 |
|---|---|---|---|
| Goal | Session metadata | 否,本质仍是 Turn | 是 |
| Continuation | pending queue 内部消息 | 当前 Session 内续跑 | 间接 |
| Cron | 持久化 schedule/job | 是 | 是 |
| Subagent | 后台 AgentRunner Task | 是 | 任务状态主要在内存,结果回主 Session |
观察任务
- 在隔离 Session 中触发
/goal或long_task; - 查看 Session metadata 的
goal_state; - 在 ContextBuilder 的 runtime lines 中找到 Goal;
- 观察 max iteration 时是否进入 continuation;
- 调用
complete_goal后确认 metadata 和 runtime event 变化; - 不人为降低安全限制来制造长任务。
源码与测试
nanobot/session/goal_state.pynanobot/session/turn_continuation.pynanobot/agent/tools/long_task.pynanobot/agent/loop.py::_state_runtests/agent/tools/test_long_task.pytests/agent/test_runner_goal_continue.pytests/agent/test_loop_goal_wall_timeout.py
自检
- Goal 为什么要放在 Session metadata,而不是只放在历史消息?
- sustained goal 禁用的是什么 timeout,哪些 timeout 仍存在?
- continuation 为什么使用 pending queue?
- 内部 continuation prompt 为什么不应作为普通 user 消息保存?
- Goal 与 Subagent 为什么不能互换理解?
下一步
阅读 Cron 与 Heartbeat。