learn-claude-code s06-s10:Context And Capability 复习笔记

June 27, 2026 By 周健
...

创建日期:2026-06-26
适用范围:s06_subagent 到 s10_system_prompt
目标:复习 Agent harness 在上下文隔离、知识加载、上下文压缩、长期记忆、system prompt 组装上的演进。


0. 先背这五句话

  1. s06:Subagent 用新的 messages[] 单独完成子任务,主 Agent 只拿总结。
  2. s07:Skill Loading 先给模型技能目录,需要时再用 load_skill 加载完整 SKILL.md。
  3. s08:Context Compact 在每次 LLM 调用前清理 messages,避免上下文爆掉。
  4. s09:Memory 把长期有用的信息写到 .memory/,跨压缩、跨会话保存。
  5. s10:System Prompt 不再写死,而是按当前 context 运行时组装。

1. s06-s10 的总主线

s01-s05 解决的是 Agent 能不能行动:

tool_use -> Python handler -> tool_result -> loop

s06-s10 解决的是 Agent 怎么长期、专业、稳定地行动:

s06: 复杂过程太吵 -> 用 subagent 隔离
s07: 专业知识太多 -> 用 skill 按需加载
s08: 历史上下文太长 -> 用 compact 压缩
s09: 压缩会丢重要信息 -> 用 memory 长期保存
s10: system prompt 太乱 -> 用 section 运行时组装

可以把这五节看成同一个问题的不同侧面:

上下文是有限资源。

subagent: 不让子任务过程污染主上下文
skill: 不让无关知识提前进入上下文
compact: 不让历史消息无限增长
memory: 不让重要信息因为压缩而消失
system prompt: 不让系统指令变成一坨不可维护文本

2. s06:Subagent,上下文隔离

文件:s06_subagent/code.py

2.1 s06 解决的问题

复杂任务会产生大量中间过程。比如:

读 20 个文件
跑 10 条命令
分析调用链
最后只需要一句结论

如果这些中间过程全留在主 Agent 的 messages,主上下文会变得很吵。
所以 s06 加了 task 工具,让主 Agent 可以派一个子 Agent 去做支线任务。

2.2 task 是普通 tool

代码位置:s06_subagent/code.py:252

TOOLS.append({
    "name": "task",
    "description": "Launch a subagent to handle a complex subtask. Returns only the final conclusion.",
    "input_schema": {"type": "object", "properties": {"description": {"type": "string"}}, "required": ["description"]},
})
TOOL_HANDLERS["task"] = spawn_subagent

这里和之前工具机制完全一样:

模型看到 task schema
-> 模型返回 tool_use: task
-> Python 查 TOOL_HANDLERS["task"]
-> 执行 spawn_subagent(description)

所以 task 没有魔法,它只是一个 handler 比较特殊的 tool。

2.3 子 Agent 有自己的 system prompt

代码位置:s06_subagent/code.py:58

SUB_SYSTEM = (
    f"You are a coding agent at {WORKDIR}. "
    "Complete the task you were given, then return a concise summary. "
    "Do not delegate further."
)

这说明子 Agent 的目标更窄:

完成交给你的任务
返回简短总结
不要继续委派

2.4 子 Agent 有自己的工具集合

代码位置:s06_subagent/code.py:182

SUB_TOOLS = [
    bash,
    read_file,
    write_file,
    edit_file,
    glob,
]

注意:SUB_TOOLS 没有 task。

目的:

防止主 Agent -> 子 Agent -> 子子 Agent -> 无限递归

2.5 spawn_subagent 是核心

代码位置:s06_subagent/code.py:207

def spawn_subagent(description: str) -> str:
    print("[Subagent spawned]")
    messages = [{"role": "user", "content": description}]

最关键是这一句:

messages = [{"role": "user", "content": description}]

它创建的是子 Agent 自己的局部 messages,不是主 Agent 的 history。

所以:

主 Agent messages: 用户原始任务 + 主流程历史
子 Agent messages: 子任务 description + 子 Agent 内部过程

这就是 context isolation。

2.6 子 Agent 自己跑 loop

代码位置:s06_subagent/code.py:212

for _ in range(30):
    response = client.messages.create(
        model=MODEL,
        system=SUB_SYSTEM,
        messages=messages,
        tools=SUB_TOOLS,
        max_tokens=8000,
    )

子 Agent 本质上也有一个小版 agent loop:

调用 LLM
-> 如果 tool_use
-> 执行 SUB_HANDLERS
-> tool_result 放回子 messages
-> 继续

for _ in range(30) 是安全上限,避免子 Agent 无限循环。

2.7 只返回总结,不返回过程

代码位置:s06_subagent/code.py:249

return result

主 Agent 最后只拿到一个字符串:

子 Agent 的最终总结

不会拿到子 Agent 内部的 10 次读文件、5 次 bash 的详细过程。

但要注意:

子 Agent 的 messages 不进入主 Agent。
子 Agent 的真实副作用会保留。

例如子 Agent 写了文件,这个文件真的会存在。

2.8 s06 复习口诀

task 是入口
spawn_subagent 是执行
fresh messages 是隔离
SUB_TOOLS 无 task 是防递归
return summary 是减上下文

3. s07:Skill Loading,知识按需加载

文件:s07_skill_loading/code.py

3.1 s07 解决的问题

Agent 需要专业知识,比如:

code review 规范
PDF 处理流程
MCP server 编写方法
agent 架构指南

如果一开始全塞进 SYSTEM,会浪费 token,也会让上下文很吵。

s07 的设计是两层:

第一层:技能目录常驻 system prompt
第二层:完整 SKILL.md 按需 load_skill 加载

3.2 skills/ 目录

代码位置:s07_skill_loading/code.py:47

SKILLS_DIR = WORKDIR / "skills"

项目里的技能文件结构:

skills/
  agent-builder/SKILL.md
  code-review/SKILL.md
  mcp-builder/SKILL.md
  pdf/SKILL.md

每个 SKILL.md 顶部有 frontmatter:

---
name: code-review
description: Perform thorough code reviews...
---

3.3 _parse_frontmatter 解析短信息

代码位置:s07_skill_loading/code.py:53

def _parse_frontmatter(text: str) -> tuple[dict, str]:
    if not text.startswith("---"):
        return {}, text
    parts = text.split("---", 2)
    meta = yaml.safe_load(parts[1]) or {}
    return meta, parts[2].strip()

它从 SKILL.md 顶部提取:

name
description

这一步还没有给 LLM,只是在 Python 内部解析。

3.4 _scan_skills 建立注册表

代码位置:s07_skill_loading/code.py:69

def _scan_skills():
    ...
    raw = manifest.read_text()
    meta, body = _parse_frontmatter(raw)
    name = meta.get("name", d.name)
    desc = meta.get("description", ...)
    SKILL_REGISTRY[name] = {"name": name, "description": desc, "content": raw}

SKILL_REGISTRY 是 Python 内存中的技能数据库。

大概长这样:

{
    "code-review": {
        "name": "code-review",
        "description": "Perform thorough code reviews...",
        "content": "完整 SKILL.md 内容"
    }
}

注意:LLM 不能直接读 Python 变量。必须转成文本,通过 system / messages / tool_result 发给它。

3.5 list_skills 生成轻量目录

代码位置:s07_skill_loading/code.py:86

def list_skills() -> str:
    if not SKILL_REGISTRY:
        return "(no skills found)"
    return "\n".join(f"- **{s['name']}**: {s['description']}" for s in SKILL_REGISTRY.values())

这一步把 Python 字典转成普通文本菜单:

- **agent-builder**: Design and build AI agents...
- **code-review**: Perform thorough code reviews...
- **mcp-builder**: Build MCP servers...
- **pdf**: Process PDF files...

这段文本人类能读,LLM 也能读。

3.6 build_system 把目录发给 LLM

代码位置:s07_skill_loading/code.py:93

def build_system() -> str:
    catalog = list_skills()
    return (
        f"You are a coding agent at {WORKDIR}. "
        f"Skills available:\n{catalog}\n"
        "Use load_skill to get full details when needed."
    )

最终通过:

client.messages.create(..., system=SYSTEM, ...)

发给 LLM。

完整链路:

SKILL.md 文件
-> _parse_frontmatter()
-> _scan_skills()
-> SKILL_REGISTRY
-> list_skills()
-> build_system()
-> client.messages.create(system=SYSTEM)
-> LLM

3.7 load_skill 加载完整内容

代码位置:s07_skill_loading/code.py:269

def load_skill(name: str) -> str:
    skill = SKILL_REGISTRY.get(name)
    if not skill:
        return f"Skill not found: {name}"
    return skill["content"]

模型看到目录后,如果判断需要某个技能,可以调用:

load_skill({"name": "code-review"})

Python 返回完整 SKILL.md,作为 tool_result 进入 messages。

3.8 s07 复习口诀

技能文件在 skills/
frontmatter 提取 name/description
SKILL_REGISTRY 存 Python 内存
list_skills 变文本菜单
build_system 发目录
load_skill 发全文

4. s08:Context Compact,上下文压缩

文件:s08_context_compact/code.py

4.1 s08 解决的问题

messages 会持续增长:

user prompt
assistant tool_use
user tool_result
assistant tool_use
user tool_result
...

读文件、跑命令、加载 skill 都会增加上下文。
如果不压缩,最终 API 会报:

prompt_too_long
too many tokens

s08 的原则:

便宜的压缩先跑,贵的压缩后跑。

4.2 三个常量

代码位置:s08_context_compact/code.py:265

CONTEXT_LIMIT = 50000
KEEP_RECENT = 3
PERSIST_THRESHOLD = 30000

含义:

CONTEXT_LIMIT: 超过这个估算大小就触发 LLM 摘要
KEEP_RECENT: 最近 3 个 tool_result 保留完整
PERSIST_THRESHOLD: 单个大输出超过这个长度就落盘

教学版用 len(str(messages)) 估算大小,不是真正 token 数。

4.3 snip_compact:裁中间消息

代码位置:s08_context_compact/code.py:295

def snip_compact(messages, max_messages=50):
    if len(messages) <= max_messages:
        return messages
    keep_head, keep_tail = 3, max_messages - 3
    ...
    return messages[:head_end] + [{"role": "user", "content": f"[snipped {snipped} messages]"}] + messages[tail_start:]

作用:

保留开头几条
保留最近几十条
中间旧消息用占位符替代

为什么不能随便切?

因为 assistant(tool_use) 和 user(tool_result) 是一组,不能留下半组。

4.4 micro_compact:旧工具结果占位

代码位置:s08_context_compact/code.py:322

def micro_compact(messages):
    tool_results = collect_tool_results(messages)
    if len(tool_results) <= KEEP_RECENT:
        return messages
    for _, _, block in tool_results[:-KEEP_RECENT]:
        if len(block.get("content", "")) > 120:
            block["content"] = "[Earlier tool result compacted. Re-run if needed.]"
    return messages

作用:

最近 3 个 tool_result 保留完整
更早的长 tool_result 替换为一句占位符

这是有损压缩。旧内容没了,但模型可以重新读文件或重新运行命令。

4.5 tool_result_budget:大输出落盘

代码位置:s08_context_compact/code.py:332

def persist_large_output(tool_use_id, output):
    if len(output) <= PERSIST_THRESHOLD:
        return output
    TOOL_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
    path = TOOL_RESULTS_DIR / f"{tool_use_id}.txt"
    if not path.exists():
        path.write_text(output)
    return f"<persisted-output>\nFull output: {path}\nPreview:\n{output[:2000]}\n</persisted-output>"

作用:

完整大输出写到磁盘
messages 里只保留路径 + 预览

这比直接丢掉更稳,因为完整内容还在:

.task_outputs/tool-results/<tool_use_id>.txt

4.6 compact_history:LLM 摘要

代码位置:s08_context_compact/code.py:375

def compact_history(messages):
    transcript_path = write_transcript(messages)
    print(f"[transcript saved: {transcript_path}]")
    summary = summarize_history(messages)
    return [{"role": "user", "content": f"[Compacted]\n\n{summary}"}]

作用:

完整历史写 transcript
让 LLM 总结当前目标、关键发现、改动文件、剩余工作、用户约束
用一条摘要替换旧 messages

这是最贵的一层,因为需要额外一次 LLM 调用。

4.7 reactive_compact:应急压缩

代码位置:s08_context_compact/code.py:383

def reactive_compact(messages):
    ...
    return [{"role": "user", "content": f"[Reactive compact]\n\n{summary}"}, *messages[tail_start:]]

如果请求 API 时仍然报 prompt_too_long,就应急压缩并重试一次。

4.8 压缩接入 agent_loop

代码位置:s08_context_compact/code.py:454

def agent_loop(messages: list):
    while True:
        messages[:] = tool_result_budget(messages)
        messages[:] = snip_compact(messages)
        messages[:] = micro_compact(messages)

        if estimate_size(messages) > CONTEXT_LIMIT:
            messages[:] = compact_history(messages)

        response = client.messages.create(...)

注意实际执行顺序:

tool_result_budget -> snip_compact -> micro_compact -> compact_history

虽然讲解里叫 L1/L2/L3/L4,但执行时先落盘大输出,避免后续占位时丢掉完整内容。

4.9 compact 工具

代码位置:s08_context_compact/code.py:416

{"name": "compact", "description": "Summarize earlier conversation to free context space."}

模型可以主动调用 compact,触发 compact_history()。

4.10 s08 复习口诀

messages 越跑越长
大输出先落盘
中间旧消息裁掉
旧 tool_result 占位
还太大就 LLM 摘要
API 报错就 reactive compact

5. s09:Memory,长期记忆

文件:s09_memory/code.py

5.1 s09 解决的问题

s08 压缩能让会话继续,但压缩是有损的。
重要信息可能被摘要得太粗,或者新会话直接丢失。

比如用户说:

以后解释代码请用中文,并且多举例子。

这不应该只留在当前 messages,应该长期保存。

s09 的方案:

把重要信息写到 .memory/ 文件系统
用 MEMORY.md 做索引
每轮开始加载相关记忆
每轮结束提取新记忆
记忆多了再合并整理

5.2 记忆目录

代码位置:s09_memory/code.py:42

MEMORY_DIR = WORKDIR / ".memory"
MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"

目录结构:

.memory/
  MEMORY.md
  user-prefers-chinese.md
  project-facts.md
  feedback-style.md

5.3 四类记忆

代码位置:s09_memory/code.py:56

MEMORY_TYPES = ["user", "feedback", "project", "reference"]

含义:

user: 用户是谁、用户偏好
feedback: 用户对 Agent 做事方式的反馈
project: 项目事实
reference: 外部线索或参考位置

5.4 write_memory_file:写单条记忆

代码位置:s09_memory/code.py:72

def write_memory_file(name: str, mem_type: str, description: str, body: str):
    slug = name.lower().replace(" ", "-").replace("/", "-")
    filename = f"{slug}.md"
    filepath = MEMORY_DIR / filename
    filepath.write_text(
        f"---\nname: {name}\ndescription: {description}\ntype: {mem_type}\n---\n\n{body}\n"
    )
    _rebuild_index()
    return filepath

单条记忆文件格式:

---
name: user-prefers-chinese
description: User prefers Chinese explanations
type: user
---

User prefers Chinese explanations with concrete examples.

5.5 _rebuild_index:重建 MEMORY.md

代码位置:s09_memory/code.py:84

def _rebuild_index():
    lines = []
    for f in sorted(MEMORY_DIR.glob("*.md")):
        if f.name == "MEMORY.md":
            continue
        ...
        lines.append(f"- [{name}]({f.name}) — {desc}")
    MEMORY_INDEX.write_text(...)

MEMORY.md 是记忆目录,不是全文:

- [user-prefers-chinese](user-prefers-chinese.md) — User prefers Chinese explanations

5.6 build_system:把索引放进 SYSTEM

代码位置:s09_memory/code.py:337

def build_system() -> str:
    index = read_memory_index()
    memories_section = f"\n\nMemories available:\n{index}" if index else ""
    return (
        f"You are a coding agent at {WORKDIR}."
        f"{memories_section}\n"
        "Relevant memories are injected below. Respect user preferences from memory.\n"
        "When the user says 'remember' or expresses a clear preference, extract it as a memory."
    )

这和 s07 很像:

s07: skill catalog 放进 SYSTEM
s09: memory index 放进 SYSTEM

索引常驻,全文按需。

5.7 select_relevant_memories:选择相关记忆

代码位置:s09_memory/code.py:132

def select_relevant_memories(messages: list, max_items: int = 5) -> list[str]:

它会:

读取所有 memory 文件的 name/description
收集最近用户消息
让 LLM 从 memory catalog 中选相关项
失败时用关键词匹配降级
最多选 5 条

5.8 load_memories:读取相关记忆全文

代码位置:s09_memory/code.py:207

def load_memories(messages: list) -> str:
    selected_files = select_relevant_memories(messages)
    ...
    parts = ["<relevant_memories>"]
    for filename in selected_files:
        content = read_memory_file(filename)
        if content:
            parts.append(content)
    parts.append("</relevant_memories>")

它返回:

<relevant_memories>
...相关记忆全文...
</relevant_memories>

5.9 把记忆注入当前请求

代码位置:s09_memory/code.py:583

memories_content = load_memories(messages)
memory_turn = len(messages) - 1 if messages and isinstance(messages[-1].get("content"), str) else None
system = build_system()

代码位置:s09_memory/code.py:606

request_messages = messages
if memories_content and memory_turn is not None and memory_turn < len(messages):
    request_messages = messages.copy()
    request_messages[memory_turn] = {
        **messages[memory_turn],
        "content": memories_content + "\n\n" + messages[memory_turn]["content"],
    }

注意它不直接改原 messages,而是构造 request_messages 发给 LLM。

LLM 实际看到:

<relevant_memories>
...
</relevant_memories>

用户当前问题

5.10 extract_memories:每轮结束后提取新记忆

代码位置:s09_memory/code.py:222

def extract_memories(messages: list):

它从最近对话中提取:

用户偏好
用户约束
项目事实
用户反馈

并要求 LLM 返回 JSON:

[
  {
    "name": "user-prefers-chinese",
    "type": "user",
    "description": "User prefers Chinese explanations",
    "body": "User prefers Chinese explanations with examples."
  }
]

之后调用 write_memory_file() 写入磁盘。

5.11 pre_compress:压缩前快照

代码位置:s09_memory/code.py:592

pre_compress = [...]

为什么要保存压缩前版本?

因为 s08 可能把细节压没。
提取记忆时,需要看到更完整的原始对话。

注意:

pre_compress 临时占 Python RAM
不是长期保存
不是主 LLM 上下文
函数返回后会释放

5.12 consolidate_memories:整理记忆

代码位置:s09_memory/code.py:287

def consolidate_memories():
    files = list_memory_files()
    if len(files) < CONSOLIDATE_THRESHOLD:
        return

记忆文件达到 10 个后,让 LLM:

合并重复
删除过时
处理矛盾
保留重要偏好

5.13 s09 复习口诀

messages 是短期记忆
.memory 是长期记忆
MEMORY.md 是索引
相关记忆按需注入
每轮结束提取新记忆
多了就 consolidate

6. s10:System Prompt,运行时组装

文件:s10_system_prompt/code.py

6.1 s10 解决的问题

前面章节会不断往 system prompt 里加内容:

身份
工具
工作目录
技能目录
记忆索引
权限说明
上下文规则

如果一直用一个硬编码 SYSTEM 字符串,会越来越难维护。

s10 的方案:

把 system prompt 拆成 section
根据当前真实 context 按需拼接
如果 context 没变,就用缓存

6.2 PROMPT_SECTIONS:定义可用片段

代码位置:s10_system_prompt/code.py:42

PROMPT_SECTIONS = {
    "identity": "You are a coding agent. Act, don't explain.",
    "tools": "Available tools: bash, read_file, write_file.",
    "workspace": f"Working directory: {WORKDIR}",
    "memory": "Relevant memories are injected below when available.",
}

这就是把一整块 system prompt 拆成多个主题。

6.3 assemble_system_prompt:按 context 拼接

代码位置:s10_system_prompt/code.py:50

def assemble_system_prompt(context: dict) -> str:
    sections = []
    sections.append(PROMPT_SECTIONS["identity"])
    sections.append(PROMPT_SECTIONS["tools"])
    sections.append(PROMPT_SECTIONS["workspace"])

    memories = context.get("memories", "")
    if memories:
        sections.append(f"Relevant memories:\n{memories}")

    return "\n\n".join(sections)

始终加载:

identity
tools
workspace

条件加载:

memory

如果 .memory/MEMORY.md 不存在或为空,memory section 不进入最终 prompt。

6.4 update_context:读取真实状态

代码位置:s10_system_prompt/code.py:156

def update_context(context: dict, messages: list) -> dict:
    memories = ""
    if MEMORY_INDEX.exists():
        content = MEMORY_INDEX.read_text().strip()
        if content:
            memories = content
    return {
        "enabled_tools": list(TOOL_HANDLERS.keys()),
        "workspace": str(WORKDIR),
        "memories": memories,
    }

这里的 context 来自真实状态:

实际注册了哪些工具
当前工作目录是什么
MEMORY.md 是否真的存在且有内容

不是根据用户消息里有没有关键词猜测。

6.5 get_system_prompt:缓存

代码位置:s10_system_prompt/code.py:71

def get_system_prompt(context: dict) -> str:
    key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
    if key == _last_context_key and _last_prompt:
        return _last_prompt
    _last_context_key = key
    _last_prompt = assemble_system_prompt(context)
    return _last_prompt

作用:

context 没变 -> 直接返回上次 prompt
context 变了 -> 重新组装 prompt

为什么用 json.dumps?

dict/list 不能直接 hash
Python hash 不稳定
json.dumps(sort_keys=True) 能生成稳定字符串 key

注意:这是本地字符串缓存,不是 API prompt cache。

6.6 agent_loop 使用动态 system

代码位置:s10_system_prompt/code.py:172

def agent_loop(messages: list, context: dict):
    system = get_system_prompt(context)
    while True:
        response = client.messages.create(
            model=MODEL,
            system=system,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000
        )

以前是:

system=SYSTEM

现在是:

system=get_system_prompt(context)

工具执行后重新计算:

代码位置:s10_system_prompt/code.py:195

context = update_context(context, messages)
system = get_system_prompt(context)

6.7 s10 复习口诀

PROMPT_SECTIONS 定义材料
update_context 读取真实状态
assemble_system_prompt 按需拼装
get_system_prompt 做缓存
client.messages.create 发给 LLM

7. s06-s10 横向对比

章节 解决的问题 核心数据结构 关键函数 最终作用
s06 子任务过程污染主上下文 子 Agent 的局部 messages[] spawn_subagent() 主 Agent 只收总结
s07 专业知识太多不能全塞 prompt SKILL_REGISTRY list_skills(), load_skill() 技能目录常驻,全文按需
s08 messages 越来越长 messages + transcript + tool output files snip_compact(), micro_compact(), compact_history() 长会话不爆上下文
s09 compact 会丢重要信息 .memory/*.md, MEMORY.md extract_memories(), load_memories() 长期记住偏好和事实
s10 system prompt 越来越乱 PROMPT_SECTIONS, context assemble_system_prompt(), get_system_prompt() prompt 动态组装

8. 三组容易混的概念

8.1 skill vs memory

skill:
- 人提前写好的专业知识
- 存在 skills/
- 例子:code-review, pdf, mcp-builder
- 用 load_skill 按需加载

memory:
- Agent 从对话中积累的长期信息
- 存在 .memory/
- 例子:用户偏好、项目事实、反馈
- 每轮结束 extract_memories

8.2 compact vs memory

compact:
- 解决当前 messages 太长
- 目标是减少上下文体积
- 可以丢细节

memory:
- 解决重要信息长期保存
- 目标是跨压缩、跨会话记住
- 不应该随便丢

一句话:

compact 负责忘掉不重要的。
memory 负责记住重要的。

8.3 SYSTEM vs messages vs tool_result

SYSTEM:
- 给模型的高优先级长期指令
- s07 放 skill catalog
- s09 放 memory index
- s10 动态组装

messages:
- 当前对话历史
- 会增长,会被 compact

tool_result:
- 工具执行结果
- 作为 user message 内容返回给模型

9. 五节合起来的执行想象

假设用户说:

请 review 这个项目,并记住以后解释要用中文。

可能发生:

1. s10 update_context()
   根据工具、workspace、memory 组装 system prompt

2. s07 system prompt 里有 skill catalog
   模型看到 code-review 技能

3. 模型调用 load_skill("code-review")
   完整 review 指南进入 tool_result

4. 任务很大时,模型调用 task
   s06 spawn_subagent 用独立 messages 做子任务

5. 读文件和工具输出很多
   s08 在下一轮 LLM 前压缩 messages

6. 用户说“以后用中文”
   s09 在 turn 结束后 extract_memories
   写入 .memory/user-prefers-chinese.md

7. 下次对话开始
   s10/s09 把 MEMORY.md 索引和相关记忆带回上下文

这就是 s06-s10 组合后的完整能力。


10. 口述版总结

如果别人问你:s06-s10 在讲什么?

可以这样回答:

这五节是在给 Agent 加上下文和知识管理能力。s06 用 subagent 把复杂子任务隔离出去,只把最终总结返回主 Agent。s07 用两级 skill loading,让模型先看到技能目录,需要时再加载完整技能文档。s08 在每次 LLM 调用前压缩 messages,避免上下文过长。s09 把用户偏好和项目事实写入 .memory/,形成跨压缩、跨会话的长期记忆。s10 把 system prompt 拆成 section,根据当前 context 运行时组装,避免硬编码一大段不可维护的 prompt。

更短版:

s06-s10 的主题是:保护上下文、按需加载知识、长期保存重要信息、动态组装系统提示。


11. 自测题

题 1

主 Agent 调用 task 后,子 Agent 内部读了 10 个文件、跑了 5 次 bash。主 Agent 的 messages 会保存这 15 次详细过程吗?

答案:不会。主 Agent 只收到 spawn_subagent() 返回的最终总结。但子 Agent 的真实副作用,比如写文件,会保留。

题 2

SKILL_REGISTRY 模型能直接看到吗?

答案:不能。它是 Python 内存里的字典。必须通过 list_skills() 转成普通文本,再通过 system=SYSTEM 发给模型。

题 3

为什么 s07 不把所有 SKILL.md 全塞进 SYSTEM?

答案:因为大多数任务用不到所有技能,全文会浪费 token 并制造噪音。s07 只放目录,全文按需 load_skill。

题 4

s08 为什么要把大 tool_result 写到磁盘?

答案:大输出会撑爆上下文。写磁盘可以保留完整内容,同时在 messages 里只放路径和预览。

题 5

compact_history() 和 extract_memories() 的目标一样吗?

答案:不一样。compact_history() 是为了缩短当前上下文;extract_memories() 是为了长期保存重要偏好、约束、项目事实。

题 6

为什么 s09 要用 pre_compress 提取记忆?

答案:因为压缩后的 messages 可能已经丢了细节。pre_compress 是压缩前快照,能让记忆提取看到更完整的信息。

题 7

.memory/MEMORY.md 不存在时,s10 会把 memory section 加入 system prompt 吗?

答案:不会。update_context() 得到 memories = "",assemble_system_prompt() 中 if memories 判断失败。

题 8

s10 的缓存和 API prompt cache 是一回事吗?

答案:不是。教学版 get_system_prompt() 只是避免本地重复拼接字符串;API prompt cache 是服务端缓存 prompt token 的机制。


12. 代码定位索引

s06

  • s06_subagent/code.py:58 - SUB_SYSTEM
  • s06_subagent/code.py:182 - SUB_TOOLS
  • s06_subagent/code.py:196 - SUB_HANDLERS
  • s06_subagent/code.py:201 - extract_text()
  • s06_subagent/code.py:207 - spawn_subagent()
  • s06_subagent/code.py:210 - 子 Agent fresh messages
  • s06_subagent/code.py:212 - 子 Agent 30 轮上限
  • s06_subagent/code.py:249 - 只返回总结
  • s06_subagent/code.py:252 - task tool schema
  • s06_subagent/code.py:257 - task handler 映射

s07

  • s07_skill_loading/code.py:47 - SKILLS_DIR
  • s07_skill_loading/code.py:53 - _parse_frontmatter()
  • s07_skill_loading/code.py:67 - SKILL_REGISTRY
  • s07_skill_loading/code.py:69 - _scan_skills()
  • s07_skill_loading/code.py:86 - list_skills()
  • s07_skill_loading/code.py:93 - build_system()
  • s07_skill_loading/code.py:102 - SYSTEM = build_system()
  • s07_skill_loading/code.py:269 - load_skill()
  • s07_skill_loading/code.py:297 - load_skill schema
  • s07_skill_loading/code.py:304 - load_skill handler 映射

s08

  • s08_context_compact/code.py:265 - context constants
  • s08_context_compact/code.py:295 - snip_compact()
  • s08_context_compact/code.py:313 - collect_tool_results()
  • s08_context_compact/code.py:322 - micro_compact()
  • s08_context_compact/code.py:332 - persist_large_output()
  • s08_context_compact/code.py:339 - tool_result_budget()
  • s08_context_compact/code.py:357 - write_transcript()
  • s08_context_compact/code.py:364 - summarize_history()
  • s08_context_compact/code.py:375 - compact_history()
  • s08_context_compact/code.py:383 - reactive_compact()
  • s08_context_compact/code.py:416 - compact tool schema
  • s08_context_compact/code.py:454 - agent_loop() 压缩接入点
  • s08_context_compact/code.py:488 - 模型主动 compact

s09

  • s09_memory/code.py:42 - .memory/ 路径
  • s09_memory/code.py:56 - memory types
  • s09_memory/code.py:72 - write_memory_file()
  • s09_memory/code.py:84 - _rebuild_index()
  • s09_memory/code.py:98 - read_memory_index()
  • s09_memory/code.py:114 - list_memory_files()
  • s09_memory/code.py:132 - select_relevant_memories()
  • s09_memory/code.py:207 - load_memories()
  • s09_memory/code.py:222 - extract_memories()
  • s09_memory/code.py:287 - consolidate_memories()
  • s09_memory/code.py:337 - memory-aware build_system()
  • s09_memory/code.py:583 - memory loading in agent_loop()
  • s09_memory/code.py:592 - pre_compress
  • s09_memory/code.py:606 - memory injection into request
  • s09_memory/code.py:627 - extract memory after final response

s10

  • s10_system_prompt/code.py:42 - PROMPT_SECTIONS
  • s10_system_prompt/code.py:50 - assemble_system_prompt()
  • s10_system_prompt/code.py:67 - prompt cache variables
  • s10_system_prompt/code.py:71 - get_system_prompt()
  • s10_system_prompt/code.py:156 - update_context()
  • s10_system_prompt/code.py:172 - dynamic prompt in agent_loop()
  • s10_system_prompt/code.py:195 - tool round 后重新计算 context/prompt
  • s10_system_prompt/code.py:204 - initial context = update_context({}, [])

13. 推荐复习顺序

第一次复习:

1. 读第 1 节总主线
2. 读第 7 节横向对比
3. 读 s06、s07
4. 读 s08、s09
5. 读 s10
6. 做第 11 节自测题

第二次复习:

1. 只看第 12 节代码索引
2. 跳到源码对应行
3. 口述每个函数在整个 Agent harness 中解决什么问题

第三次复习:

自己画出这条线:

复杂任务 -> task -> subagent -> summary
专业知识 -> skill catalog -> load_skill
上下文太长 -> compact pipeline
重要信息 -> extract_memories -> .memory
系统提示 -> context -> prompt sections -> SYSTEM

能画出来,s06-s10 的骨架就掌握了。

← 返回首页