# Agent Note: 移除纯日志事件的合成轮次

Status: implemented

[English](2026-07-28-remove-synthetic-log-only-turns.md) | 中文

## 问题

会话存储曾暴露 `appendOutOfBand()`，让插件可以在没有 agent（智能体）轮次运行时发布延迟到达的纯日志事件。该方法会用 `turn/start` 和 `turn/end` 包住事件，再将其刷写。这保留了「每个持久事件都必须位于轮次内」的旧规则，却让同一个标识符既表示模型循环执行，又表示仅持久化更新。

引入该规则时，持久化恢复曾将最后一个 `turn/end` 视为唯一的已提交边界。如今，持久化扫描器会保留每个合法且连续的事件，崩溃修复也只处理确实处于开放状态的轮次。空闲上下文早已采用同一机制，在轮次之间追加 `user/message`。因此，为标题更新保留合成轮次会夸大轮次计数、为从未运行模型的工作产生执行结果，还会让延迟到达的元数据写入占用下一个轮次编号。

通用 seam 还重复了领域策略。它的标记映射说明哪些插件事件符合条件，而标题功能本就拥有取消、活跃性和陈旧结果处理规则。改用另一个通用或标题专属追加包装层，仍会为两个字面量事件类型保留同一层类型间接性。

## 决策

`SessionStore.appendOutOfBand()`、`OutOfBandSessionEventMap` 和 `OutOfBandSessionEventType` 均不再存在。拥有纯日志事件的插件通过 `Session` 追加该事件；当操作承诺持久性时，插件会显式等待 `ctx.sessions.flush(session)`。系统不会仅为获得该检查点而打开轮次。

核心会话不变量继续强制核心所属的执行关系：轮次与步骤编号、steering、助手、工具、待办和请求头事件的封闭，以及同一步骤内的工具调用／结果配对。核心允许可合并扩展事件位于轮次之间，因为只有声明它们的插件知道这些事件受执行作用域约束，还是可以独立存在。插件的不变量配套组件仍负责其自身的事件关系。

标题服务会在完成既有的服务状态、修订、取消和实时会话检查后，直接追加 `session/title`。随附模型辅助函数会在发起调用前追加其字面量 `session/title-llm-request` 记录。持久化通过尽快处理的 `session/event` 路径观察两者，并在常规检查点与生命周期 teardown 时排空；二者都不会仅因为位于轮次之间就强制 flush。因此，回退标题、辅助请求记录或已接受的提供方标题可以出现在 `turn/end` 之后、下一个 `turn/start` 之前。

会话 fork 可以结束于开放轮次之外的任意稳定事件位置，而不限于 `turn/end`。这样，默认 fork 会保留独立标题和上下文记录，同时仍拒绝在活跃执行过程中截断前缀。

历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md)确立了当前语义：一个轮次表示一次模型循环执行。

## 曾考虑的替代方案

**保留合成的零步骤轮次。** 这可以让日志在形式上保持统一，并复用 `turn/end` 作为刷写点，但会报告从未发生的执行、扰动轮次编号，还会迫使每个轮次消费方过滤仅持久化记录。持久性已有独立的 `session/flush` 边界。

**保留不使用合成轮次的通用核心持久追加辅助函数。** 执行 `append()` 再执行 `flush()` 的方法本身很小，但其准入标记和并发承诺仍会将插件策略集中到会话存储中。事件所有方已经拥有直接、类型化的字面量追加点；真正需要持久性屏障的调用方可以在该边界等待既有的 `session/flush` 操作。

**将标题存储为可变会话元数据。** 这可以避免轮次间事件，却会在仅追加日志之外建立第二套变更、回放、持久化和 fork 协议。标题仍采用可回放、后写覆盖的事件。

**要求每个插件事件向核心声明可独立存在的资格。** 这可以保留中心准入列表，但会让缺少声明意味着一种核心无法验证的执行关系。可合并扩展联合类型已经将语义所有权赋予声明事件的插件；该插件的不变量配套组件才是正确的强制位置。

## 验证

核心不变量测试会接受轮次之间的未知插件事件，同时继续拒绝位于该处的内置执行事件。钩子、压缩（compaction）、plan-mode、Code Mode 分发和审批的不变量配套组件会回放既有日志，并在没有开放轮次时，于提交前拒绝相同的执行作用域事件。会话标题服务测试会在并发刷新、会话脱离拒绝和最新修订接受场景下，固定一个直接追加的回退事件。JSONL 和 SQLite 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题；fork 测试会保留独立纯日志尾部，同时拒绝位于开放轮次内的边界。一个无密钥、经完整组装的 ACP（Agent Client Protocol）快照会将模型生成的标题延迟到 `turn/end` 之后，并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。

## 后果

轮次计数和结果重新只描述模型循环执行。独立事件会占用会话 seq，像其他追加一样启动尽快持久化，并且仅当操作承诺持久性时，才要求事件所有方请求显式持久性屏障。通用插件错误不再因核心默认的封闭规则而失败，因此每个需要执行关系的插件都必须自行声明并测试该关系。标题功能保留修订排序和生命周期持久化，同时减少了核心状态，不再重复类型 seam，并消除了轮次编号冲突。
