跳转到主要内容
这一课开始,课程会从“pattern”真正走向“operator workflow”。 你仍在使用同一个 QitOS kernel,但 agent 现在必须在 workspace 中稳定运行很多步。这就带来了新的设计问题:
  • 还要继续手工组装 tool surface 吗?
  • system prompt 里应该放多少 workflow discipline?
  • 什么时候 HistoryPolicy 就够了?
  • 什么时候应该升级到 CompactHistory 或显式 memory?
本课对应的示例是 examples/real/claude_code_agent.py

相比第 2 课的变化

设计分支第 2 课第 3 课
Tools手工 registry + 裁剪版 CodingToolSetcoding_tools(...) preset registry
Promptplanner + executor prompts单个 workflow-heavy system prompt
Stateplan 与 cursortodos、mode、target file、test command、可选 doc URL
History默认行为显式 HistoryPolicy(max_messages=16, max_tokens=2800)
Memory不使用默认仍不使用,但此时 memory 开始成为真实选项
Compaction还未引入作为更长运行的升级路径被正式引入

system prompt 现在定义的是 workflow discipline

和第 1 课不同,这一课的 prompt 不再只是 parser 契约,它还编码了操作流程:
Workflow:
- Start by writing a todo list with `todo_write`.
- If you are unsure which tool to use, call `tool_search`.
- Read before you edit.
- Make the smallest correct change.
- Run verification immediately after editing.
- 只有在任务确实需要文档时才使用 `web_fetch`。
在 v0.4 中,输出契约已经不再被硬编码在 example prompt 里,而是由当前 protocol 自动注入:
Thought: <short reasoning>
Action: <tool_name>(arg=value, ...)
or
Final Answer: <what changed + verification proof>
这一课想让你看到:
  • runtime 没变
  • prompt 却可以变得非常 operational

本课默认 parser 依旧故意保持 ReAct

尽管 prompt 复杂了很多,parser 仍然是:
model_parser=ReActTextParser()
这是刻意设计的。你正在学习:仅通过
  • 更合理的 state
  • 更成熟的 tool surface
  • 更清晰的 workflow prompting
就可以显著改变 agent 行为,而不必立刻升级 protocol。

这个 example 现在是 preset-first

底层 transport 仍然可以是 OpenAICompatibleModel,但 v0.4 会先解析:
  • FamilyPreset
  • HarnessPolicy
  • protocol / parser / tool delivery / context defaults
因此,同一个 example 现在可以在 Qwen、Kimi、MiniMax、gpt-oss 与 Gemma 4 之间切换,而不用改 agent 实现本身。 对其中多数 family 来说,harness 依然是 text/JSON-first:
  • 模型返回文本
  • tool schema 通过 prompt 注入,或通过 tool parameters 交付
  • parser 把文本解析成 Decision
MiniMax 则继续保留自己更适合的 tool-call parser。 这种路径对研究仍然很有价值,因为它:
  • 容易跨 provider 比较
  • 容易在 trace 中检查
  • 容易接到本地 endpoint
如果你确实需要 family-specific protocol,QitOS 现在会通过 preset 系统把这类耦合显式记录下来。
1

从 preset tool registry 开始

本课通常会这样初始化:
super().__init__(
    toolset=[
        coding_tools(
            workspace_root=workspace_root,
            shell_timeout=30,
            include_notebook=True,
        )
    ],
    llm=llm,
    model_parser=ReActTextParser(),
)
到了这里,preset 才是正确抽象层。coding_tools(...) 直接给你一个一致的 workspace bundle,不必再手工把 file、shell、task、notebook 工具逐个注册。这一课的经验是:
  • 学 kernel 时手工暴露 tools
  • agent 进入真实工作流后切换到 presets
2

理解 preset 真正带来了什么

coding_tools(...) 是 QitOS 的标准 coding 工具包。实际上它通常意味着 agent 拥有:
  • 文件查看与编辑
  • shell 执行
  • task / todo helpers
  • 可选 notebook 支持
  • 可选 web / 文档工具
当 agent 走到这一步时,你选择的不再是“一个工具”,而是在选择“一个工作环境”。
3

把 state 设计成长时运行友好的形状

这一课的 state 会开始承载 workflow signals:
@dataclass
class ClaudeCodeState(StateSchema):
    scratchpad: list[str] = field(default_factory=list)
    todos: list[dict[str, Any]] = field(default_factory=list)
    target_file: str = TARGET_FILE
    test_command: str = TEST_COMMAND
    doc_url: str = DOC_URL
    mode: str = "work"
这套 state 有效,是因为:
  • todos 把工作队列显式化,并能跨多步保留
  • mode 帮助 agent 记住自己处于 planning 还是 executing
  • doc_url 让外部文档 grounding 变成可选输入,而不是默认浏览
  • scratchpad 仍保存压缩后的最近轨迹
4

让 reduce 吸收结构化工具输出

reduce() 会开始从工具结果中吸收结构化 workflow state:
if isinstance(first, dict):
    if first.get("todos"):
        state.todos = list(first.get("todos") or [])
    if first.get("current_mode"):
        state.mode = str(first.get("current_mode"))
    if int(first.get("returncode", 1)) == 0:
        state.final_result = (
            "Verification passed with the canonical coding toolset."
        )
if state.metadata.get("todos"):
    state.todos = list(state.metadata.get("todos") or [])
if state.metadata.get("mode"):
    state.mode = str(state.metadata.get("mode"))
这仍然是在延续前两课的同一原则:工具在做工作,但真正决定 agent 该记住什么的,依然是 reduce()
5

第一次显式引入 history control

本课的 run 通常会传:
history_policy=HistoryPolicy(max_messages=16, max_tokens=2800)
这是课程里第一次让 message-window management 变得重要。HistoryPolicy 回答的是:
  • 最近多少消息会被保留
  • history 最多可以占多少 tokens
  • 何时旧上下文不再原样进入模型
6

理解 history、compaction 与 memory 的边界

在这一课里,示例默认仍然挂载自定义 history=memory=这是一个有意为之的设计:
  • HistoryPolicy 负责消息预算
  • state 负责保存 todos、mode 之类的即时 workflow artifacts
  • 还不需要单独 memory store
但当 run 足够长、简单裁剪已经开始伤害行为时,就应该升级到 CompactHistory
from qitos.kit import CompactConfig, CompactHistory, WindowMemory

super().__init__(
    toolset=[coding_tools(workspace_root=workspace_root)],
    llm=llm,
    model_parser=ReActTextParser(),
    history=CompactHistory(
        llm=llm,
        config=CompactConfig(
            max_tokens=2800,
            keep_last_messages=10,
            keep_last_rounds=4,
        ),
    ),
    memory=WindowMemory(window_size=30),
)
要把这几层读清楚:
  • HistoryPolicy:这次请求带哪些消息
  • CompactHistory:旧交互如何被压缩与保留
  • Memory:什么信息要脱离即时消息流而长期存在
7

理解什么时候值得升级 protocol

即使 agent 更复杂了,本课依然继续使用文本 ReAct。这通常仍是正确选择。只有在这些场景下才值得考虑协议升级:
  • 需要比文本 ReAct 更严格的结构化输出时,升级到 JSON 或 XML
  • agent 正在驱动 live terminal session 时,考虑 Terminus
  • provider 原生输出 structured tool calls 且你确实想保留它时,使用 MiniMaxToolCallParser 等 model-specific parser
不要因为 agent 变复杂了,就自动切换协议。
8

像 operator 一样运行,像 researcher 一样检查

运行:
python examples/real/claude_code_agent.py
查看:
qita board --logdir runs
qita 中重点看:
  • todos 是否在前期建立,并且后续保持一致
  • mode 的切换是否符合预期 workflow
  • prompt 与 parser 是否仍然保持在简单 ReAct 主路径上
  • history trimming 是否开始影响模型行为
  • 这次 run 是否已经值得升级到 CompactHistory

长时运行 agent 的正确心智模型

到这一课为止,你应该开始用“分层”的方式思考:
  • state:下一步一定需要什么
  • history:下一次模型调用可能需要什么
  • compaction:更早的 history 如何被压缩
  • memory:哪些信息应该脱离即时 turn 结构长期存在
这正是 QitOS 在长时运行设计上最重要的区分之一。

完整示例

完整可运行代码位于:

第 4 课会引入什么

第 4 课会继续保留长时运行结构,但任务域会完全切换。 你将看到如何在不发明新 runtime 的前提下,调整:
  • tool composition
  • prompt policy
  • state semantics
  • reduce() 逻辑
从而得到一个真正的领域化 agent。

下一课:代码安全审计 agent

把同一个内核变成具备 ranked findings 的 defensive review agent

相关指南:可观测性

在进入最终课程前,先把 qita board、replay 与 export 再回顾一遍