一次 AI Coding Agent Turn 的上下文生命周期:Hook、Tool、Skill、Compact 与 Subagent
一次 agent turn 不是一次模型调用。用户提交一条消息以后,运行时可能先补项目规则和 Skill 目录,再发起模型请求;模型返回工具调用,权限层批准或拒绝,工具结果追加回历史,模型随后继续判断。这个小循环可以重复很多次,直到验证通过、需要用户输入,或者运行时决定停止。
因此,理解 coding agent 的运行时,不能只看 observe -> act -> observe。还要追踪每一类信息何时进入上下文、以什么身份进入、何时失效,以及 compact 和 subagent 怎样改变信息边界。
讨论范围限定为一次 turn 的上下文生命周期。Skill 的跨产品加载差异见 AI Coding Agent 的 Skill 加载机制深度解析,messages 的追加、裁剪、摘要和外化见 上下文管理全景,MCP、A2A 与 AG-UI 的协议分层见 Agent 互操作协议全景。这里不再重做产品图谱,只保留能解释当前 turn 的官方机制。
Turn 的边界
在本文中,turn 从用户输入进入运行时开始,到主 agent 产生一个对用户可见的最终结果或明确停在阻塞状态结束。turn 内部可以包含多次模型采样和工具执行:
1 | |
会话和 turn 不是同一层。会话保存多个 turn 的历史、摘要和外部状态;turn 只处理当前输入引发的一串动作。工具循环也不是 turn:一次 turn 可以没有工具,也可以包含几十次工具调用。
模型看到的“上下文”同样不等于磁盘上的 session state。运行时可以把 TODO、checkpoint、完整 transcript、文件索引和审批记录存到模型窗口之外,只在需要时抽取其中一部分送给模型。逻辑上的模型输入通常由四层组成:
- 稳定指令:system / developer prompt、项目规则、工作区约束和输出风格。
- 能力目录:内置工具、MCP tools、Skill 描述、可用 subagent 描述。
- 会话历史:用户消息、assistant 消息、工具请求、工具结果,以及 compact 后的摘要。
- 本轮增量:新用户消息、hook 追加的上下文、刚产生的工具结果和审批结果。
这四层的来源、寿命和权限不同,不能当成一个大 prompt 处理。
组件结构:上下文从哪里来
组件图回答一个结构问题:一次模型调用之前,哪些组件共同决定模型能看见什么?
flowchart LR
subgraph Sources["Context sources"]
RULES["System / developer<br/>AGENTS.md / CLAUDE.md / rules"]
CATALOG["Tool schemas<br/>MCP tool catalog<br/>Skill descriptions"]
HISTORY["Messages<br/>tool results<br/>compact summary"]
STATE["Files / memory / TODO<br/>checkpoint / repo state"]
end
subgraph Runtime["Agent runtime"]
BUILDER["Context builder"]
HOOKS["Lifecycle hooks"]
POLICY["Permission policy<br/>sandbox"]
TRACE["Trace / transcript"]
end
MODEL["LLM"]
EXEC["Built-in tools<br/>MCP servers<br/>Skill loader<br/>subagents"]
RULES --> BUILDER
CATALOG --> BUILDER
HISTORY --> BUILDER
STATE -->|selected or retrieved| BUILDER
HOOKS -->|additional context| BUILDER
BUILDER --> MODEL
MODEL -->|tool request| POLICY
HOOKS -->|inspect or modify| POLICY
POLICY -->|allow| EXEC
POLICY -->|ask / deny| BUILDER
EXEC -->|result| BUILDER
BUILDER --> TRACE
POLICY --> TRACE
EXEC --> TRACE
Context builder 是这里经常被忽略的组件。它决定稳定指令的顺序、哪些目录只放 metadata、历史保留到哪里、工具结果怎样裁剪,以及 compact 后重新加载什么。模型负责在给定上下文里做选择,运行时负责构造选择空间。
启动上下文:规则、目录和正文
会话启动或恢复时,运行时先建立相对稳定的前缀。项目规则通常属于这一层。Codex 会按目录层级加载适用的 AGENTS.md;Claude Code 会加载相应的 CLAUDE.md 和 rules;OpenCode 的 rules 与 instructions 也属于长期指导,而不是某次工具结果。
Skill 需要拆成目录和正文两个对象:
1 | |
目录解决“有哪些能力、什么时候可能适用”,正文解决“选中以后具体怎么做”。两者同时常驻,会让未使用的工作流占满上下文;只保留正文而没有目录,模型又无法发现能力。
截至 2026-07-17,Claude Code 官方文档明确写明:普通会话里 Skill descriptions 先进入上下文,完整正文在调用时加载;OpenCode 也通过原生 skill tool 按需返回完整 SKILL.md。Claude Code 的 subagent skills 字段是一个例外,被指定的 Skill 正文会在子 agent 启动时预加载。Codex 官方把 Skill 描述为按需使用的可复用工作流,但公开文档没有承诺和 Claude Code 完全相同的内部消息布局。
正文被调用以后也不等于永久有效。Claude Code 当前会把渲染后的 Skill 正文作为一条消息放进会话;相同正文再次调用时只追加“已加载”的短提示,动态参数或动态命令输出改变后才追加新正文。OpenCode 文档只保证 skill tool 把内容返回到 conversation,不应据此推断每次都重新读取磁盘,或推断正文在所有 compact 路径里原样保留。
Tool 和 MCP:schema 先到,结果后到
Tool 的目录和 Tool 的执行结果也要分开。
模型在决定是否调用工具之前,至少要看到工具名、用途和输入 schema。内置工具、插件工具和 MCP tool 最终都会形成这类可调用描述。文件内容、命令输出、数据库记录或远程 API 响应,只有执行以后才成为新的 observation。
MCP 2025-11-25 规格把 server 能力分成 prompts、resources 和 tools:
| MCP primitive | 规格中的控制方 | 进入上下文的典型方式 |
|---|---|---|
| prompts | 用户控制 | 用户选择模板后,由 host 取得消息内容 |
| resources | 应用控制 | host 决定是否选择、检索或附加资源 |
| tools | 模型控制 | 模型提出调用,host 执行并处理结果 |
“模型控制”不等于绕过用户。MCP tools 规格要求 server 校验输入、做访问控制并清理输出,也建议 client 对敏感操作请求确认、在交给模型前校验结果、设置超时并记录审计日志。MCP 规定 tools/list、tools/call 和结果结构;是否把某个 tool 暴露给当前 agent、是否需要审批、结果截断多少、是否写入 transcript,仍由 host runtime 决定。
这也解释了为什么 MCP server 不是安全边界。它是能力与数据的协议边界,permission、sandbox、hook 和 trace 仍在 host 一侧。
运行时视角:一次工具循环
时序图回答时间问题:一条工具结果怎样回到模型,并推动同一个 turn 继续运行?
sequenceDiagram
participant U as User
participant R as Runtime
participant H as Hooks
participant M as Model
participant P as Permission / Sandbox
participant T as Tool / MCP
participant X as Trace
U->>R: submit prompt
R->>H: UserPromptSubmit / context hook
H-->>R: optional additional context
R->>M: stable prefix + history + current input
M-->>R: tool request
R->>H: pre-tool hook
H-->>R: allow / modify / block metadata
R->>P: evaluate tool and arguments
alt allowed
P->>T: execute
T-->>R: structured result / error
R->>H: post-tool hook
H-->>R: additional context / validation result
else ask
P-->>U: approval request
U-->>R: approve or reject
else denied
P-->>R: blocked observation
end
R->>X: append events and decisions
R->>M: append new observation
M-->>R: next tool request or final answer
R->>H: Stop / verification hook
H-->>R: continue or stop
R-->>U: result or blocker
不同产品不保证 hook 与 permission 的内部先后顺序完全一致,图中表示的是控制点,不是跨产品统一 ABI。需要依赖顺序的安全策略,应以具体产品的事件文档和测试结果为准。
Messages append:观察结果怎样变成状态
不开 compact、prune 或显式清理时,常规工具循环主要做追加:
1 | |
第二次模型调用能利用第一次工具结果,是因为运行时把结果放进了后续模型输入。结果可能在 API 层表现为 tool_result、tool message 或 provider 专用 block,应用内部也可能使用自己的 ToolPart、event 或 transcript 结构。文章里把它统一称为“工具结果消息”,只描述逻辑角色,不把某一家 provider 的 wire format 当成通用格式。
追加带来三个后果。
第一,工具输出会持续占用上下文。一次无边界的日志读取可能污染后面很多轮,不能把“读到了”理解成“运行时会自动提炼并丢弃原文”。
第二,错误也是 observation。退出码、stderr、MCP isError、权限拒绝和 sandbox denial 都应回到控制循环,否则模型只知道动作没有成功,却不知道下一步该换参数、换工具还是停止。
第三,工具结果不等于验证证据。npm test 返回成功可以支持“测试通过”,读了一段源码只能支持“看到这段实现”。Trace 需要记录 claim 与 evidence 的对应关系,而不是把所有成功退出码都当成完成。
Hook:给不确定循环插入确定性控制点
Hook 可以做三类事:
- 观察:记录事件、发送通知、采集耗时和结果摘要。
- 决策:阻止停止、拒绝危险工具、要求审批,或在产品允许时改写参数。
- 注入:把额外规则、当前状态或验证结果放入后续模型上下文。
一个 hook 不一定同时拥有三种权力。事件名相似,也不代表输出协议相同。
截至 2026-07-17,Codex 官方 hooks 文档列出 PreToolUse、PermissionRequest、PostToolUse、PreCompact、PostCompact、UserPromptSubmit、SubagentStart、SubagentStop、Stop 和 SessionStart 等关键事件。大部分工具、压缩和停止事件属于 turn scope;SessionStart 与 SubagentStart 属于线程或子 agent 启动边界。Codex 还明确提醒:多个匹配 command hooks 可能并发启动,不能依赖一个 hook 抢先阻止另一个启动。
Claude Code 的事件面更广,handler 可以是 command、HTTP、MCP tool、prompt 或实验性的 agent。它的 additionalContext 会按事件位置进入上下文:session 与 subagent 启动信息放在开头,用户输入 hook 跟随当前 prompt,工具前后的补充信息靠近工具结果。已经写入 transcript 的中途 hook 内容在 resume 时会被重放,而不是为过去的 turn 重新执行。
OpenCode 插件以 JavaScript / TypeScript 模块提供 tool.execute.before、tool.execute.after、permission.asked、permission.replied、session.compacted、message/file/session events 和 custom tools。其可编程性很强,相应地也必须把插件来源和执行权限纳入信任模型。
Hook 越靠近副作用,越需要来源标记、审计和禁用路径。能重写 shell 参数的 hook 已经不是普通提示词扩展,而是执行链的一部分。
Permission 和 sandbox 是两道门
Permission 回答“这个动作是否被策略允许”,sandbox 回答“即使允许,它实际能触碰到哪里”。两者不能互相替代。
1 | |
适合自动放行的动作通常作用域明确、可逆、可重跑并且有 trace,例如工作区内读取非敏感源码。跨项目目录、读取凭据、安装依赖、访问网络和调用 remote MCP 会扩大数据与供应链边界,更适合 ask 或精细 allowlist。删除、重写历史、强推和生产发布需要更强的显式授权。
OpenCode 把 doom_loop 也做成 permission key:相同工具以相同输入重复三次时进入审批。这个设计很有代表性,权限系统不只防越权,也可以防止 agent 在同一个失败动作上耗尽预算。
审批结果必须回到 loop。拒绝不是异常吞掉,而是一条新的约束:当前路径不可用,模型需要换成只读方案、缩小范围或报告 blocker。
Stop 也是一个运行时事件
模型生成一段自然语言不代表任务完成。停止前至少需要回答:
- 目标 claim 是什么?
- 哪个工具结果或人工检查点支持它?
- 失败路径是否已经处理?
- 是否还有待执行的授权动作?
- 当前停止是完成、取消、阻塞,还是预算终止?
Stop hook 可以把“未验证即完成”重新送回 loop。SubagentStop 可以要求子 agent 补证据。两者都不能凭空制造正确性;它们只能检查验证是否发生、结果是否满足规则。
Codex 的公开 OTel 事件包括 conversation、API request、stream event、tool decision 和 tool result 等记录。Claude Code hook 输入暴露 transcript 路径和事件字段。OpenCode 提供 session、permission、tool、message 与 file events。这些入口足以构造控制面 trace,但默认日志是否保留完整上下文、是否脱敏、是否持久化,要分别查看配置。
Compact:摘要不是恢复
上下文接近窗口上限时,运行时通常会压缩较早历史。这个动作至少包含两步:
- compact:把旧消息转换成更短的 continuation summary,或者移除部分旧工具输出;
- rehydration:从磁盘、配置或外部状态重新放回仍然适用的规则、Skill、memory 和任务状态。
“Rehydration”是本文使用的工程术语,不是各家共同定义的 API 名称。它强调一个事实:摘要只能保留模型选中的信息;长期约束如果有稳定来源,应该从来源重新加载,而不是期待摘要逐字记住。
flowchart LR
subgraph Before["Before compact"]
B1["Stable instructions"]
B2["Old messages<br/>tool results<br/>hook context"]
B3["Recent tail"]
end
COMP["Compact<br/>summarize or prune"]
SUM["Continuation summary"]
RELOAD["Rehydrate from sources<br/>rules / memory / skills / state"]
subgraph After["After compact"]
A1["Rebuilt stable context"]
A2["Summary"]
A3["Preserved recent context<br/>if the product keeps it"]
end
B2 --> COMP
COMP --> SUM
B1 --> RELOAD
SUM --> A2
RELOAD --> A1
B3 --> A3
Claude Code 当前给出了最明确的公开存活表。系统提示和 output style 不属于 message history;项目根 CLAUDE.md、非 path-scoped rules 与 auto memory 会重新注入;path-scoped rules 和嵌套 CLAUDE.md 要等匹配文件再次读取;已调用 Skill 会在预算内重新附加,每个最多 5,000 tokens,总预算 25,000 tokens,较旧 Skill 可能被丢弃。这些数字是 Claude Code 当前实现约束,不是 Agent Skills 标准。
OpenCode 当前配置中 compaction.auto 默认开启,compaction.prune 默认关闭;开启 prune 后会移除旧工具输出。其 experimental.session.compacting 插件 hook 可以向 continuation summary 增加状态,也可以替换 compaction prompt。官方文档没有承诺所有旧工具结果或 Skill 正文都会原样恢复。
Codex 支持手动 /compact,并暴露 PreCompact、PostCompact 以及 SessionStart(source = compact) 事件。公开 hook 契约可以用来记录压缩、补充后续上下文或运行检查,但不应从事件名反推内部摘要格式。
可靠的 compact 设计需要一份恢复契约:
| 信息 | 更合适的来源 | compact 后策略 |
|---|---|---|
| 项目硬规则 | 版本化规则文件 | 重新加载 |
| 当前目标与完成条件 | task state / checkpoint | 结构化注入 |
| 已做决策及理由 | 决策账本 | 摘要并保留证据指针 |
| 大型日志和文件内容 | 文件系统 / artifact | 保留位置与校验值,按需重读 |
| 临时探索过程 | message history | 允许摘要或丢弃 |
| 权限授予 | permission state | 按产品契约处理,不能由摘要自行延长 |
把关键状态只留在聊天散文里,compact 迟早会把它变成近似值。
Subagent:上下文隔离,不是消息复制
Subagent 的主要价值之一,是把高噪声工具结果隔离在另一个上下文窗口。父 agent 发送委托消息,子 agent 在自己的规则、工具和权限下工作,最终把结果或摘要返回父上下文。原始 grep 输出、测试日志和中间尝试可以留在子线程。
flowchart LR
subgraph Parent["Parent context"]
P1["Goal / constraints"]
P2["Delegation message"]
P3["Returned summary<br/>evidence pointers"]
end
subgraph Child["Child context"]
C1["Child system prompt"]
C2["Selected rules / skills"]
C3["Tool calls and noisy results"]
C4["Child final result"]
end
P1 --> P2
P2 --> C1
C1 --> C2 --> C3 --> C4
C4 --> P3
截至 2026-07-17,Codex 把 subagent 工作放进独立 agent thread,主线程收集返回结果;subagent 继承父 turn 的 sandbox 与 permission mode,custom agent 可以进一步收窄。Claude Code 的普通 subagent 也从新上下文开始,只接收委托消息、自己的 system prompt、配置允许的项目指令和预加载 Skill;完成后通常只有摘要和少量 metadata 回到主会话。Claude Code 的 fork 是例外,它继承父会话历史,但自己的工具调用仍留在 fork 中。
OpenCode 以 child session 表示 subagent,并允许 primary agent 自动调用或由用户显式 @ 调用。其 task permission 可以限制哪些 subagent 能启动。不同产品对子 agent 传递规则、git 状态、Skill 和权限的细节不同,委托 prompt 不能假设“子 agent 自动知道父会话里的一切”。
Subagent 返回过细,主上下文仍会被污染;返回过粗,父 agent 又无法验证。比较稳的返回结构包括结论、证据位置、执行过的验证、未解决风险和建议下一步。必要时保留 child thread 或 artifact 引用,不把完整 transcript 复制回来。
Subagent 是一种 Agent 边界,不必与工作阶段边界重合。何时继续当前 turn、compact、delegate 或从 checkpoint 冷启动,见《Agent 会话边界设计》。
编排层处在 turn 之外
OMX 这类 meta-runtime 不是另一种 tool message。官方项目把自己定义为 Codex CLI 上的 workflow layer,并把 guidance、plans、logs 和 state 放进 .omx/。它处理的是模式路由、角色、团队、checkpoint、后台任务和恢复,再把当前阶段需要的信息交给宿主 agent。
这类编排状态不应全量塞进每次模型调用。当前目标、阶段、owner、blocker 和验收条件适合进入上下文;完整队列、历史指标、所有 worker 日志适合留在外部状态,需要时再检索。宿主的 permission 与 sandbox 仍然约束最终工具执行。
Trace 要记录上下文变换
只记录“调用了什么工具”不足以解释一次 turn。可调试 trace 至少要覆盖以下事件:
1 | |
context.built 不必保存所有敏感正文,但应保存 manifest:加载了哪些规则、暴露了哪些 Skill 和 MCP server、使用了哪个 compact summary、哪些来源被裁剪。permission.decided 要记录命中的策略与审批结果。tool.completed 要区分协议错误、执行错误、sandbox 拒绝和正常结果。turn.stopped 要有完成、阻塞、取消或失败原因。
Trajectory 只能复述模型和工具走过的路径。Control-plane trace 还能说明某条信息何时进入上下文、哪个 hook 改过参数、哪个权限门拒绝了动作、compact 丢掉了什么,以及父 agent 最终收到了哪份子任务摘要。
一组运行时不变量
一次 turn 能否稳定收敛,可以用几条不变量检查:
- 目录与正文分离。Tool schema、Skill description 负责发现,正文和结果按需进入。
- 每个副作用动作先经过权限和执行边界,再产生工具结果。
- 每个工具结果都作为新的 observation 回到 loop,错误和拒绝也不例外。
- 验证证据与完成 claim 对应,Stop 不能只依赖模型措辞。
- Compact 有明确的恢复来源,摘要不承担永久记忆职责。
- Subagent 有独立上下文契约,父会话只接收足以判断的结果。
- Trace 能还原上下文变换和控制面决策,同时避免把密钥与原始敏感数据写进日志。
模型决定下一步动作,运行时决定模型在什么信息和权力边界内做这个决定。Hook、permission、Skill、MCP、compact 和 subagent 都在修改这条边界,只是发生的位置不同。
系列文章
- AI Coding Agent 的上下文操作系统:组件、生命周期与阅读地图
- AI Coding Agent 的上下文栈:组件边界与装配顺序
- AI Coding Agent 的 Skill 加载机制深度解析
- Messages 数组与 Prompt Cache:六种操纵策略
- Agent 会话边界设计:Session、Context Window 与工作状态转移
- Context Paging:Compact、外部化、恢复与 Memory 生命周期
- 2026 Coding Harness 实现图谱
参考资料
- OpenAI Codex documentation
- Codex Hooks
- Codex Skills
- Codex MCP
- Codex Subagents
- Codex configuration
- Claude Code: Explore the context window
- Claude Code Skills
- Claude Code Hooks
- Claude Code Subagents
- Claude Code Tools reference
- OpenCode Agent Skills
- OpenCode Tools
- OpenCode Permissions
- OpenCode Plugins
- OpenCode Configuration
- OpenCode Agents
- MCP 2025-11-25 Server Overview
- MCP 2025-11-25 Tools
- MCP 2025-11-25 Resources
- oh-my-codex official repository
版本快照:2026-07-17。文中产品行为只采用当日可访问的官方文档;事件名、Skill 生命周期、compact 预算和权限默认值都可能随版本变化。

