ContextBuilder 与模型上下文

对应源码

主题源码
system prompt 与 messagesnanobot/agent/context.py::ContextBuilder.build_system_prompt、ContextBuilder.build_messages
Bootstrap、Memory 与 Skillsnanobot/agent/context.py、nanobot/agent/memory.py、nanobot/agent/skills.py
Prompt 模板nanobot/templates/agent/、nanobot/utils/prompt_templates.py
Session 回放历史nanobot/session/manager.py::Session.get_history
BUILD 阶段调用位置nanobot/agent/loop.py::AgentLoop._state_build、AgentLoop._build_initial_messages

学习目标

读完后,你应当能够:

  • 解释 system prompt 的主要组成;
  • 区分 Session history、Memory、Skill 和 runtime metadata;
  • 说明当前用户消息如何与运行上下文合并;
  • 找到模型本轮真正收到的初始 messages。

前置知识

1. ContextBuilder 负责“选择和组织”

定义:

  • nanobot/agent/context.py::ContextBuilder

它不负责调用 Provider,而是把分散的信息组织成模型输入:

flowchart TD
    ID["身份、平台和 workspace"] --> SYS["System Prompt"]
    BOOT["AGENTS.md / SOUL.md / USER.md"] --> SYS
    TC["Tool Contract 模板"] --> SYS
    MEM["MEMORY.md"] --> SYS
    SK["Always Skills + Skills Summary"] --> SYS
    RH["Recent Memory History"] --> SYS
    SUM["Archived Session Summary"] --> SYS

    SYS --> MSGS["messages"]
    HIST["Session history"] --> MSGS
    CUR["当前用户消息"] --> MSGS
    RT["Runtime Context"] --> CUR

2. System Prompt 的组成

build_system_prompt() 按顺序组合:

  1. identity;
  2. workspace bootstrap 文件;
  3. Tool Contract;
  4. 用户已定制的长期 Memory;
  5. always-on Skills;
  6. 其他可用 Skills 的摘要;
  7. Dream cursor 之后的 recent history;
  8. archived session summary。

各部分使用分隔线连接。

Identity

包含:

  • workspace 绝对路径;
  • 操作系统和 Python 版本;
  • 平台策略;
  • 当前 Channel。

来源:

  • nanobot/templates/agent/identity.md
  • nanobot/templates/agent/platform_policy.md

Bootstrap 文件

固定名称:

  • AGENTS.md
  • SOUL.md
  • USER.md

它们来自当前 workspace,不是 nanobot 源码目录中的同名模板。onboard 初始化模板后,用户可以在 workspace 定制。

Tool Contract

来源:

  • nanobot/templates/agent/tool_contract.md

它描述如何使用工具,而具体 tool schemas 由 ToolRegistry 单独传给 Provider。

Memory

memory/MEMORY.md 只有在内容不再等同于原始模板时才加入长期 Memory 段。

Skills

Skills 分为:

  • always-on:完整内容进入上下文;
  • 其他可用 Skills:先提供摘要,由 Agent 按需读取。

这体现 progressive disclosure,避免把所有 Skill 全文塞进每个请求。

3. Recent History 与 Session History 不同

ContextBuilder 中的 recent history 来自 MemoryStore,有:

  • cursor;
  • session scope;
  • unified session 行为;
  • 最多 50 条;
  • 最多约 8000 tokens。

Session history 则由 Session.get_history() 取得,来自当前 Session 的持久化消息,并受本轮 replay budget 约束。

Session history:当前对话的可回放消息
Recent Memory History:MemoryStore 中尚未被 Dream 处理的近期记录
Archived summary:较早 Session 内容的摘要
MEMORY.md:长期整理后的记忆

不要把它们统称为“历史记录”后忽略来源。

4. 当前用户消息附带 Runtime Context

build_messages() 会构造一段明确标记的 metadata:

[Runtime Context — metadata only, not instructions]
Current Time: ...
Channel: ...
Chat ID: ...
Sender ID: ...
...
[/Runtime Context]

这段内容被追加到当前 user 消息后面,而不是创建第二条连续 user 消息。原因包括:

  • 某些 Provider 不接受连续同 role 消息;
  • 让用户原始内容保持稳定前缀,有利于 prompt cache;
  • 明确说明 metadata 不是用户指令。

标记不是安全边界

“metadata only” 是对模型的语义提示,不是权限隔离。真正的权限仍必须由 Tool、workspace、Channel 和网络安全代码执行。

5. 连续同 role 消息会被合并

如果历史最后一条消息与当前消息 role 相同,build_messages() 会合并内容,避免发送连续同 role 消息。

文本与多模态 block 使用不同的合并方式。

这个行为可能影响调试观察:

  • 当前输入不一定是 messages 列表中新追加的一项;
  • 它可能被合并进最后一条历史消息。

6. 媒体如何进入上下文

_build_user_content() 对媒体进行处理:

  • 图片可以进入多模态 content block;
  • 文档在 RESTORE 阶段可能先提取文本;
  • 非图片附件可能以路径引用形式出现;
  • Channel 配置 extractDocumentText 影响文档提取。

理解媒体时,需要同时看:

  • AgentLoop._prepare_message_media
  • utils/document.py
  • ContextBuilder._build_user_content
  • Channel/WebUI 上传路径

7. 三个需要分别观察的列表

调试时不要只打印一个 messages:

对象位置含义
Session.messagesSession磁盘会话状态
ctx.initial_messagesAgentLoop BUILD 后初次交给 Runner 的消息
messages_for_modelRunner 每次 iteration治理、裁剪后真正交给 Provider 的消息

Runner 还会对第三份列表执行:

  • orphan tool result 清理;
  • 缺失 tool result 回填;
  • microcompact;
  • tool result budget;
  • history snip。

8. 上下文顺序为什么重要

通用设计解读:

  • identity 和规则越靠前,越像全局约束;
  • 当前用户内容需要保持清晰;
  • 不可信工具结果不能冒充 system instruction;
  • 过多历史会挤占工具 schema 和输出预算;
  • prompt 模板变化会像 Python 代码变化一样影响行为。

项目 .agent/gotchas.md 明确提醒:

  • Prompt templates 会直接改变 Agent 行为;
  • Tool 描述、Skills 和 Session replay 都会塑造模型行为;
  • 写入 Memory 或 Session 的污染可能在未来反复出现。

观察任务

在 AgentLoop._state_build() 执行后查看:

ctx.history
ctx.initial_messages[0]["content"]
ctx.initial_messages[-1]["content"]

回答:

  1. system prompt 中有哪些段落?
  2. 当前 workspace 的 AGENTS/SOUL/USER 是否进入?
  3. runtime metadata 在什么位置?
  4. 当前消息是否与上一条同 role 消息合并?
  5. Session 中有多少消息,实际 replay 了多少?

不要把完整 system prompt 贴进公开日志

其中可能包含本机路径、用户信息、Memory、Skill 内容或运行上下文。学习记录只摘录结构和必要字段。

源码与测试

  • nanobot/agent/context.py
  • nanobot/templates/agent/
  • nanobot/utils/prompt_templates.py
  • nanobot/agent/skills.py
  • nanobot/agent/memory.py
  • tests/agent/test_context_builder.py
  • tests/agent/test_context_prompt_cache.py
  • tests/agent/test_context_aware.py
  • tests/agent/test_document_extraction_toggle.py

自检

  1. Skills 和 Tools 为什么不是同一种上下文信息?
  2. Session history 与 recent memory history 有什么来源差异?
  3. Runtime Context 为什么附在 user 消息后面?
  4. “metadata only” 为什么不能代替权限检查?
  5. 为什么模型实际收到的 messages 还可能不同于 initial_messages?

下一步

阅读 AgentRunner 的模型—工具循环。