53 lines
6.0 KiB
Markdown
53 lines
6.0 KiB
Markdown
# Agent Note: 通过 Web 宿主边界投影 plan mode
|
|
|
|
Status: implemented
|
|
|
|
[English](2026-07-24-web-plan-mode-projection.md) | 中文
|
|
|
|
## 问题
|
|
|
|
plan 服务拥有持久状态和边界时序,但 Web 宿主契约无法发现或选择 plan mode。浏览器可以检查尾部历史页中的 `plan/mode`,但分页后,该页面可能遗漏最近的相关事件;在空日志、未激活状态下,也可能完全没有对应事件。它同样无法体现一项等待下一次模型请求边界生效的选择。因此,只在客户端维护切换状态会与恢复后的会话、退出工具触发的状态转换,以及其他界面作出的选择发生偏差。
|
|
|
|
并非每一种产品组合都会在宿主中挂载 plan mode。协议必须区分不可用的功能与已支持但提交状态为未激活的会话。切换模式也与取消正在执行的请求互不影响:现有服务会按设计在下一个边界应用最近一次选择。
|
|
|
|
## 决策
|
|
|
|
会话 RPC 域公开 `session.planMode({ sessionId })` 和 `session.setPlanMode({ sessionId, active })`。两者返回相同的值类型:`null | { active: boolean, pending?: boolean }`。`null` 表示可选的 `ctx.planMode` 服务不存在;`{ active: false }` 表示该服务可用,但当前未激活。读取或修改状态前,这两个方法都会通过宿主用于历史记录与提示词请求的同一路径恢复冷会话。
|
|
|
|
宿主适配器把选择与折叠工作交给 `ctx.planMode`,不会自行追加事件或重复实现边界逻辑。`active` 是最近一次已提交并记录到日志的值。`pending` 存在时,其值是等待模型请求边界生效的所选目标,且必定不同于 `active`;表示用户可见的待生效转换的是该字段是否存在,而不是其布尔值。重新选择已提交值时,服务内部可能仍留有一项清理意图,但适配器会把这一无净变化状态规范化为 `{ active }`。随后,边界会移除该意图,且不会记录多余的状态事件。协议 schema 会拒绝 `active` 与 `pending` 相等的值。该 RPC 不会取消正在执行的请求,因此生成期间作出的选择不会改变本次请求,只会影响下一次请求。
|
|
|
|
浏览器会话对象在历史记录加载完成后以及重连时查询完整状态。plan 查询失败不会阻断其他功能:历史记录仍可使用,并保留最近一次已知的功能状态。重连使用代际围栏,避免已被取代的打开流程覆盖较新的结果。一个单调递增的 plan 请求围栏同时覆盖查询和选择,因此较早的 unary 响应无法替换较新请求的结果。另一道独立的本地事件版本围栏会阻止当前查询或选择响应覆盖 mux 流中已抢先到达的 `plan/mode` 提交;提前到达的提交会保持为内部状态,直到查询成功并确认该功能存在。除上述情况外,选择成功后,只有宿主确认的响应才会更新快照;业务错误和传输失败都会保留先前状态。
|
|
|
|
已提交的 `plan/mode` 会话事件仍作为实时通知。当宿主已公布该功能时,有效事件会替换 `active` 并清除 `pending`。追加路径和历史替换窗口都会按序号采用最新的有效 plan 事件,因此即使缓冲的触发帧在回放时已与窗口重叠,缺口回补仍会应用恢复出的提交。对象层会忽略格式错误的事件,也不会仅凭一条原始事件推断功能是否可用。这样既以完整状态读取为真源,又保留现有的日志事件流作为提交信号。
|
|
|
|
## 状态与时序
|
|
|
|
| 起始状态 | 选择 | RPC 即时状态 | 下一请求边界 |
|
|
|---|---|---|---|
|
|
| 未激活 | Plan | `{ active: false, pending: true }` | 记录 `plan/mode: true`;快照变为已激活 |
|
|
| 已激活 | Default | `{ active: true, pending: false }` | 记录 `plan/mode: false`;快照变为未激活 |
|
|
| 未激活,Plan 待生效 | Default | `{ active: false }` | 无需记录状态事件 |
|
|
| 功能不存在 | 任一选择 | `null` | 不会引入 plan 行为 |
|
|
|
|
停止生成仍是单独的会话操作。待生效的选择会在取消后保留,并在下一条提示词或 continuation 到达服务边界时应用。
|
|
|
|
## 考虑过的替代方案
|
|
|
|
**仅折叠当前加载的历史页。** 不予采纳,因为按消息边界分页时,页面可能遗漏最近的模式事件;单个页面也无法表达待生效的意图,或区分空日志的未激活状态与功能不存在。
|
|
|
|
**在浏览器中维护乐观布尔值。** 不予采纳,因为工具审批通过的退出、其他客户端、恢复以及追加失败都可能与推测值不一致。浏览器只显示归属服务返回的状态。
|
|
|
|
**增加专用的 plan 控制帧。** 不予采纳,因为已提交状态已有记录到日志的 `plan/mode` 事件。完整状态的单次查询足以覆盖打开与重连场景,无需增加第二套实时事件词汇。
|
|
|
|
**公开通用具名协作模式。** 根据 plan 专用状态决策,不予采纳。ACP 可以保留通用适配器词汇,但产品目前只拥有一个具体的布尔值领域。
|
|
|
|
## 验证
|
|
|
|
- API schema 拒绝无效的请求与状态结构,包括 `active` 与 `pending` 相等的情况;两个 fetch 方向均可分派这两个方法。
|
|
- 宿主运行时测试覆盖功能不存在、真实服务的待生效状态、取消时无净变化状态的规范化、冷会话错误,以及共享 RPC 语义。
|
|
- 客户端对象测试覆盖打开、选择成功、重叠选择请求的响应顺序、业务错误与传输失败、已提交的实时事件、经缺口回补的提交、格式错误及功能不可用时的事件、查询失败时的容错、重连刷新、针对已被取代查询的围栏保护,以及 mux 提交抢先于 unary 响应到达的情况。
|
|
|
|
## 后果
|
|
|
|
Web UI 包(package)无需导入 plan mode 的宿主实现即可发现该功能,也能显示边界处的待生效状态,而不必重复实现 plan 服务。其他客户端也可以使用同一个可选投影。该契约有意不把模式切换与停止操作合并,不发明通用模式标识符,也不要求每一种宿主组合都必须提供 plan 功能。
|