module/file.py:function_namedocs/decisions.md 另行记录Context = 每次 LLM 调用时发送的完整消息列表。管理目标:
两者互相影响:压缩可能清掉已注入的内容,需要重新注入。
[system] System Prompt(角色 + 内置 skills) ← 静态注入,trace 创建时
[user] 第一条用户消息
[assistant] ...
[tool] ... ← 可能包含周期注入的 context
[assistant] ...
[tool] ... ← 可能包含动态注入的 skill
[user] 第 N 条用户消息
[assistant] 待模型生成
Trace 创建时构建一次,后续续跑不重复发送。
内容:角色 prompt + 按 preset/call-site 过滤的内置/项目 skills。
优先级:messages 中的 system message > config.system_prompt > preset.system_prompt > DEFAULT_SYSTEM_PREFIX
实现:agent/core/runner.py:_build_system_prompt
每 CONTEXT_INJECTION_INTERVAL(当前=5)轮迭代,框架自动向模型的 tool_calls 中追加一条 get_current_context 调用(如果模型本轮未主动调用)。
注入内容(_build_context_injection 构建):
注入形态:合成的 tool_call + tool_result 消息对。模型看到的效果等同于"我之前主动调用了 get_current_context"。
[assistant] tool_calls: [..., get_current_context()] ← 框架自动追加
[tool] { 当前时间, GoalTree, Collaborators... } ← 工具正常执行
特点:
实现:agent/core/runner.py(CONTEXT_INJECTION_INTERVAL, 工具执行后的自动注入逻辑)
详细文档:架构设计 § Context Injection Hooks
区别于模型通过 skill() 工具主动加载 skill,指定注入是调用方在 runner.run() 时声明需要哪些 skill,由框架自动注入。
System Prompt 中的内置 skills 是启动时一次性注入的。但某些 skill(如 Librarian 的查询策略/上传策略):
skill 工具统一管理调用方通过 runner.run() 的 inject_skills 参数声明(不放在 RunConfig 中,因为同一个 agent 的不同调用可能需要不同的 skill):
# Librarian ask 场景
async for item in runner.run(
messages=[{"role": "user", "content": "[ASK] 查询内容"}],
config=config,
inject_skills=["ask_strategy"], # 本次调用需要的 skill
skill_recency_threshold=10, # 最近 N 条消息内有就不重复注入
):
...
# Librarian upload 场景(同一个 agent,不同 skill)
async for item in runner.run(
messages=[{"role": "user", "content": "[UPLOAD:BATCH] 数据"}],
config=config,
inject_skills=["upload_strategy"],
skill_recency_threshold=10,
):
...
框架在工具执行阶段自动注入(与 context 周期注入同级处理)。
注入形态:合成的 skill("xxx") tool_call + tool_result 消息对,复用现有 skill 工具。
[user] [ASK] 有没有工具能做人物姿态控制?
[assistant] tool_calls: [skill("ask_strategy")] ← 框架自动生成
[tool] { ask_strategy skill 完整内容 } ← skill 工具正常执行
[assistant] 让我来检索... ← 模型真实输出
在 trace.context 中维护已注入 skill 的记录:
trace.context["injected_skills"] = {
"ask_strategy": {
"message_id": "msg_abc123", # tool_result 消息的 ID
"sequence": 42 # 最新注入的消息序列号
}
}
注入前检测流程:
injected_skills[skill_id].message_idrecency_threshold 条消息内(从当前提交给模型的列表中查,不按编号计算) → 跳过| 方式 | 触发者 | 场景 |
|---|---|---|
| 指定注入(本节) | 调用方,通过 run(inject_skills=...) |
已知任务类型,确保策略在场 |
| 主动加载 | 模型自己调 skill() 工具 |
遇到未预见场景,自主判断需要什么 |
两者都走 skill 工具,消息格式一致。
指定注入的 skill 文件遵循现有 skill 体系(Markdown + frontmatter),按项目组织:
knowhub/agents/skills/ # KnowHub Librarian 的 skills
├── ask_strategy.md # 查询检索策略
└── upload_strategy.md # 上传编排策略
压缩分两级,通过 RunConfig.goal_compression 控制 Level 1 行为:
| 模式 | 值 | Level 1 行为 | Level 2 行为 |
|---|---|---|---|
| 不压缩 | "none" |
跳过 Level 1 | 超限时直接进入 Level 2 |
| 完成后压缩 | "on_complete" |
每个 goal 完成时立刻压缩该 goal 的消息 | 超限时进入 Level 2 |
| 超长时压缩 | "on_overflow" |
超限时遍历所有 completed goal 逐个压缩 | Level 1 后仍超限则进入 Level 2 |
默认值:"on_overflow"
对单个 completed/abandoned goal 的压缩逻辑:
msg.goal_id == goal.id)goal 工具goal(...) 的 assistant 消息及其对应的 tool result(包括 add、focus、under、done 等操作)goal(done=...) 的 tool result 内容替换为 "具体执行过程已清理"压缩后的 history 片段示例:
... (前面的消息)
[assistant] tool_calls: [goal(focus="1.1")]
[tool] goal result: "## 更新\n- 焦点切换到: 1.1\n\n## Current Plan\n..."
[assistant] tool_calls: [goal(done="1.1", summary="前端使用 React...")]
[tool] goal result: "具体执行过程已清理"
... (后面的消息)
on_complete 模式在 goal 工具执行 done 操作后,立刻对该 goal 执行压缩。优点是 history 始终保持精简,缺点是如果后续需要回溯到该 goal 的中间过程,信息已丢失(存储层仍保留原始消息)。
触发点:agent/core/runner.py(工具执行后检测 goal(done=...) 调用)
on_overflow 模式在 _manage_context_usage 检测到 token 超限时,遍历所有 completed goal,逐个执行压缩,直到 token 数降到阈值以下或所有 completed goal 都已压缩。如果仍超限,进入 Level 2。
触发点:agent/core/runner.py:_manage_context_usage
实现:agent/trace/compaction.py:compress_completed_goals
触发条件:Level 1 之后 token 数仍超过阈值(默认 context_window × 0.5)。
通过侧分支队列机制执行,force_side_branch 为列表类型:
knowledge.enable_extraction 控制):进入 reflection 侧分支,LLM 可多轮调用工具提取知识knowledge_eval 侧分支,评估待评估知识compression 侧分支,LLM 生成 summary侧分支队列示例:
["reflection", "compression"]["knowledge_eval", "compression"]["compression"]压缩完成后重建 history 为:system prompt + 第一条 user message + summary(含详细 GoalTree)
实现:agent/core/runner.py:_agent_loop(侧分支状态机), agent/core/runner.py:_rebuild_history_after_compression
主路径无工具调用(任务完成)时,如果 knowledge.enable_completion_extraction 为 True,通过侧分支进入反思,完成后退出循环。
to_prompt() 支持两种模式:
include_summary=False(默认):精简视图,用于日常周期性注入include_summary=True:含所有 completed goals 的 summary,用于 Level 2 压缩时提供上下文messages/branch_type 和 branch_id 标记,查询主路径时自动过滤parent_sequence 树结构实现跳过,无需 compression events 或 skip list压缩会移除消息,包括之前注入的 skill 和 context 内容。不同注入类型的应对策略:
| 注入类型 | 压缩影响 | 恢复策略 |
|---|---|---|
| System Prompt | Level 2 重建时保留 | 无需额外处理 |
| Context 周期注入 | 可能被 Level 1/2 移除 | 固定间隔自动重注入(下一个 interval 即恢复) |
| Skill 动态注入 | 可能被 Level 1/2 移除 | message ID 检测 → 自动重注入 |
Skill 重注入的触发点:每次 runner 准备构建 LLM 调用前,检查 injected_skills 中记录的 message_id 是否仍在当前 history 中。不在则标记为需要重新注入,在下一轮工具执行阶段合成 tool_call。
| 文档 | 内容 |
|---|---|
| 架构设计 | Agent 框架完整架构 |
| Skills 指南 | Skill 文件格式、分类、加载机制 |
| Prompt 规范 | 信息分层、条件注入原则 |
| Trace API | 压缩和反思的 REST API |