989 lines
97 KiB
Markdown
989 lines
97 KiB
Markdown
# ARTEMIS 历史模块重构:增量转写 + 分层压缩(统一方案 v3)
|
||
|
||
状态:v3 已实施并通过最终真机验收(2026-09-01,M0-M5 全部落地;最终验收五项全过,见 `docs/plans/history-acceptance-2026-09-01.md`;仅 git 提交待用户决定)
|
||
|
||
范围:Operator 执行历史的表示、累积、压缩与召回;Flash / Pro 双 profile 收敛为同一记忆运行时。
|
||
|
||
取代关系:
|
||
- **取代** `docs/design/pro-context-memory-redesign.md`(2026-08-25)的 §6/§11/§12(分层历史、token 预算、缓存布局)——其"每回合重编译 + 预算选择"的前提被本方案的"增量转写"取代;其 §7(数据模型)、§8(视觉接地中立摘要)、§9(稳定分块)、§10(recall)被**吸收并保留**。
|
||
- **修订** `docs/plans/context-visibility-redesign.md` 的 §6(方案 D 记忆内核)——StepMemoryService 统一运行时保留,但"两 lens 按 profile 分立"修订为"两 lens 按高度分立"(见 §5,决策变更已获用户确认,2026-08-31)。该文档的 Phase 0/1(State 显式化)已实施,Phase 2(PerceptionStore)/Phase 3(ctx 分解)不受本方案影响,Phase 4 由本方案取代。
|
||
|
||
---
|
||
|
||
## 0. 用户提案复述与结论
|
||
|
||
提案三条(2026-08-31):
|
||
|
||
1. **每步与 Flash 一致,仅做增量构建**;后台用 Gemini 3.5 Flash-Lite 做图片压缩(视觉摘要替代截图)。
|
||
2. **里程碑边界压缩**:不再每步压缩,而是在**切换里程碑时**把上一段任务的历史压缩掉;保留现方案的滑动窗口底线(永远保留最近几步原始消息)。
|
||
3. **上限兜底**:长期不切换里程碑时,按最大上下文窗口限制触发压缩。
|
||
4. **(补充约束,2026-08-31/09-01 两轮明确)段级胶囊要特别详细、尽量维持原状、按步压缩,且定形为三段式**:① 梗概与效果(在做什么/做了什么/有什么效果,含留下的笔记)→ ② 压缩步摘要("Steps xx–xx 做什么、Step xx 做什么"区间叙事)→ ③ 逐步动作账本(每步一行:步号+时间+动作)。理由:视频分析模块按步号/时间戳对齐视频时间轴,且大模型对任务整体的时间感知必须精确到步。落点见 §3.3。
|
||
|
||
**结论:方向成立,且与业界最优实践收敛。** Claude Code、Codex、Gemini CLI 全部采用"累积 + 阈值/边界触发压缩",无一采用 ARTEMIS Pro 现在的"每回合全量重编译"(后者的直接代价:跨回合零消息连续性、零提示缓存命中、思考签名丢失)。姊妹文档把"真多轮消息"排在 Phase 5,本提案把它提前为核心架构——这是正确的提前。
|
||
|
||
在采纳的同时做**四点修正/补强**(正文展开):
|
||
|
||
| # | 提案原文 | 修正 | 理由 |
|
||
|---|---|---|---|
|
||
| 1 | "后台每步做图片压缩" | 摘要**每步后台准备**,但替换**延迟到固定深度批量应用**(尾部倒数第 K 步才把图换成摘要) | 就地改写第 i 字节起的缓存前缀全部失效;固定深度擦洗让前缀单调增长(Anthropic computer-use 官方同款权衡,见 §2.5) |
|
||
| 2 | "切换里程碑时压缩之前的任务" | 里程碑切换当**触发器**,压缩块的**身份**按 step_id 范围锚定,里程碑 hash 仅作标签元数据 | 计划是动态索引不是历史键(姊妹文档 §9 实证结论):计划改写/回滚/重命名不得破坏已压缩历史 |
|
||
| 3 | "按最大上下文窗口限制压缩" | 需要真实计量——现在全仓**没有任何 token 计量**(探索报告 §8);用**上一轮 `usage_metadata.prompt_tokens` 作为当前上下文的实测值** + 增量启发式,零额外成本 | 字符估算对图片/多模态误差大;上一轮实测输入即本轮上下文基数 |
|
||
| 4 | (未提及) | 补两层:**紧急全量快照**(阈值兜底也爆时,Codex/Gemini CLI 式结构化快照重启)与 **recall_history 无损回捞**(DataEngine 冷历史,压缩永远可逆) | 无界任务(`[Loop:continuous]`)会耗尽任何有限分层;无损回捞是"敢压缩"的前提 |
|
||
|
||
---
|
||
|
||
## 1. 现状基线(2026-08-31 工作区实证)
|
||
|
||
### 1.1 Flash:append-only + 每轮就地压缩
|
||
|
||
- 消息列表建一次、只追加,**无任何消息驱逐**,唯一边界是 `max_turns=30`(`runner.py:166,177`)。
|
||
- `compress_flash_messages`(`context_compressor.py:43`)每轮 LLM 调用前全量重扫:只保留**最后一张**图(keep-last-1,`:62-72`);历史 UI XML 从 `--- UI Element List ---` 标记截断(`:133-139`);图片"摘要未就绪则保留"(lossless-pending,`:141-146`);摘要以 `--- Historical Visual Transition ---` 前缀块注入 ToolMessage,幂等重建(`:125-128`)。
|
||
- 已知缺口:摘要**迟到回填**会突变任意深度的历史消息(缓存敌意);图已被丢而摘要仍未到时该步**既无图也无摘要**(race,`:144` 只保护仍在场的图)。
|
||
- `VisualStepSummarizer`(`flash/summarizer.py:39`):flash-lite、前图叠加红色动作标记+后图、**无界重试**(`_retry_delays` 末档 3s 永续循环,`:105`)、单次 25s 超时、成功后写 `data_engine.update_step_summary`(`:205`)。
|
||
- token 仅做事后记账(char/4 + 258/图启发式,`runner.py:293-315`),**从不驱动压缩**。
|
||
|
||
### 1.2 Pro:每回合全量重建,零跨回合消息
|
||
|
||
- `_build_prompt` 每回合新建 PromptBuilder,产出恰好 2 条消息(System + Human),回合结束整表丢弃(`operator.py:161-218,705-715`;`prompts.py:139-154`)。**跨回合连续性只剩 `plan_and_history` 渲染串 + `short_term_memory`**;思考签名、原生多轮全部丢失;提示缓存对易变尾部零命中(网关无任何 cache_control/cached_content plumbing,`llm.py` 全文)。
|
||
- 历史可见性 4 策略散布 8 个调用点硬编码 kwargs(`task_tree.py:797-942` + 探索报告 §2.4 全表);`strict_milestone` 是硬悬崖——活跃里程碑外且最近 N 步外的历史**完全消失,无摘要残留**(`:859-876`)。
|
||
- 摘要未就绪的降级:Flash 保图,Pro 回退整步 detailed 渲染(`task_tree.py:920`)——两者都是"多花 token 等摘要",但均不可观测。
|
||
- 两个 live bug:Diagnoser 传不存在的 `window_steps=` 参数 + 不存在的 `history_window_steps` 配置,异常被吞,**Diagnoser 的历史每次都渲染成错误串**(`diagnoser.py:160-174`);`build_plan_and_history` 两个死参数(`chronological_last_step`/`detailed_subgoal_hashes`)4 个调用点仍在传。
|
||
|
||
### 1.3 存储与事件面
|
||
|
||
- `StepRecord` 无 `summary_status`/`version`/`chunk` 概念;`update_step_summary` 是后台盲覆写,无版本无序保证(`engine.py:974-1005`)。扩展逃生口是 `extra_metadata`(已承载 `subgoal_hash`/`width`/`height`/`token_usage`)。
|
||
- 里程碑切换的现成检测点:① 每步已盖章 `extra_metadata["subgoal_hash"]`(`graph.py:128`),相邻步 hash 变化即切换;② `_process_plan_write`(`graph.py:505-615`)是全部计划写入的单一漏斗,已在计算 `milestones_changed`/`new_top_level_completions`。重命名韧性由 `subgoal_hash_chain.json` 别名链保证(`task_tree.py:991`)。
|
||
- 现成接缝:operator 模板里 `unified_history=""` 是**已接线的空渲染槽**(`prompts.py:213`);`PromptComponent` 是干净插件点;`MAX_MESSAGES_IN_HISTORY=25` 是死常量(`constants.py:16`)。
|
||
|
||
---
|
||
|
||
## 2. 业界机制要点(2026-08 调研,取数字用于定标)
|
||
|
||
### 2.1 Claude Code:三层递进
|
||
|
||
① **微压缩**(每次调用前、无模型参与):清掉旧 tool_result,只留最近 3–5 个,占位符替代,原文落盘可回捞;② **服务端 context editing**(`clear_tool_uses`):默认 100K 触发、keep 3,关键参数 `clear_at_least`——**不值得为小清理打破缓存就不清**(批量摊销失效成本的官方形态);③ **全量压缩**:~95% 用量触发,9 节结构化摘要(意图/技术要点/文件/错误与修复/用户消息/待办/当前工作/下一步),压缩后重注入最近读过的 ~5 个文件。另有 memory tool:清理阈值临近时警告模型先把重要结果写进记忆文件。
|
||
|
||
### 2.2 Codex CLI:极简两件套 + 原生压缩
|
||
|
||
阈值 ≈ 有效窗口 − 13K(用户自设值被钳制在窗口 90% 以内)。压缩后**只剩两样**:一条摘要消息(进度/关键决策/约束/剩余工作)+ 最近 ~20K 原始用户消息;旧摘要被折叠进新摘要,**永不堆叠**。压缩后自动重读最近编辑的 ≤5 个文件(50K 预算)。GPT-5.1-Codex-Max 起模型**原生训练了跨窗口压缩**。UI 明示"多次压缩会降低准确性"。
|
||
|
||
### 2.3 Gemini CLI:70/30 + 结构化快照
|
||
|
||
历史达模型窗口 **70%** 触发;**最近 30% 原样保留**(切分点保证不劈开 tool-call/response 对);更旧的压成 `<state_snapshot>` XML(overall_goal / key_knowledge / file_system_state / recent_actions / current_plan),压缩率 5–15%。
|
||
|
||
### 2.4 Manus KV 缓存法则(生产指标)
|
||
|
||
agent 输入:输出 ≈ 100:1,缓存命中是**第一生产指标**(cached $0.30 vs uncached $3.00/MTok)。法则:前缀字节级稳定(系统提示禁时间戳)、**append-only**(永不修改既往动作/观察、序列化确定性)、工具 mask 不删除、**文件系统即外部记忆**(可恢复地压缩:丢网页留 URL,丢文件内容留路径)、todo 复诵对抗 lost-in-the-middle、错误留在上下文里。
|
||
|
||
### 2.5 截图历史(computer-use 官方指引,与本仓直接同构)
|
||
|
||
Anthropic:截图每张 ~1,000–1,800 token,单请求 ≤20 张;**推荐保留最近 3 张,更旧的每 ~25 轮批量修剪**——明确说明逐轮修剪会每轮打破前缀缓存,批量修剪让失效事件之间前缀字节不变;被逐出的截图用一句文本摘要替位。参考实现即 `only_n_most_recent_images` 参数。OpenAI CUA 则靠服务端 `previous_response_id` 携带历史,客户端只上传新帧。
|
||
|
||
### 2.6 Gemini 缓存约束(本仓运行时)
|
||
|
||
隐式缓存 2.5+ 默认开启;**最小可缓存前缀 2,048(2.5 系)/ 4,096(3.x 系)token**,命中报告在 `cachedContentTokenCount`;条目短寿命,相似前缀请求要时间上贴近。显式 `CachedContent` TTL 默认 1h、按 token-hour 计存储费(explorer.py:1709 已有先例)。**任何更早字节的改动使其后全部失效**——这是修正 #1 的硬依据。
|
||
|
||
### 2.7 提炼的六条通用原则
|
||
|
||
1. 累积为常态,压缩靠**阈值/边界稀疏触发**,不逐步进行。
|
||
2. 突变要么贴尾(浅、可预测),要么稀疏成批(深、与语义边界重合)。
|
||
3. 摘要输出**结构化 schema**,不是自由散文;旧摘要折叠进新摘要,不堆叠。
|
||
4. 最近 K 步/最近 30% 永远原样——所有系统都有生窗口下限。
|
||
5. 压缩必须**可逆**:原文外置(磁盘/DataEngine),占位符携带回捞引用。
|
||
6. 计量用真实值,阈值分软/硬两档,硬档换更激进的形态而非同一手段加量。
|
||
|
||
---
|
||
|
||
## 3. 目标架构:单一转写(Transcript)+ 四区纪律 + 三级压缩
|
||
|
||
### 3.1 核心转变
|
||
|
||
Operator 的上下文从"每回合重编译的渲染串"变为**会话级增量转写**——一份 append-only 的消息账本,Flash 与 Pro 共用同一实现,由新服务 `TranscriptLedger`(挂 `ctx.step_memory` 之下或并列)持有。State 只携带轻引用,重物不进图通道(与 visibility 方案 C 同一原则)。
|
||
|
||
`build_plan_and_history` **不删除**,降级为两个角色:
|
||
- **辅助 agent 的编译视图**:Diagnoser / Committee / Outputter / HistoryAnalyzer / Planner / Checker 仍按各自 ContextPolicy 从 DataEngine 编译(Claude Code 同型:主循环累积、子 agent 拿新鲜编译上下文);
|
||
- **转写的冷启动构造器**:进程重启/崩溃恢复时,从 DataEngine 步骤记录重建转写的冻结区。
|
||
|
||
### 3.2 四区纪律(缓存友好的突变规则)
|
||
|
||
```text
|
||
[S 稳定前缀] 系统提示 + 工具 schema + 计划语法
|
||
整会话字节不变;≥4K token 以满足 Gemini 3.x 隐式缓存下限
|
||
[F 冻结历史] 已压缩产物:chunk 摘要块 + 逐步摘要行
|
||
只在"压缩事件"时突变(稀疏、深)
|
||
[A 活跃窗口] 最近 N 步原始消息(AI 推理原文 + 工具结果)
|
||
append-only;倒数第 K 步处有一个"擦洗沿"(浅、恒定深度)
|
||
[T 当前尾部] 本回合观察:当前截图 + UI 列表 + 计划复诵 + 注入指令
|
||
每回合新建
|
||
```
|
||
|
||
**突变只允许三种,全部可预测:**
|
||
|
||
1. **贴尾擦洗(深度恒定,逐回合)**:
|
||
- 深度 1:上一回合的 UI XML 列表、计划复诵副本剥除(现 Flash `prune_history_xml` 行为,代价只是最后一回合的缓存,几乎免费);
|
||
- 深度 K(默认 3):倒数第 K 步的截图 → 替换为后台已备好的视觉转换摘要。摘要通常在几秒内就绪,K 步 ≈ 数十秒,几乎总能赶上;未就绪则该步暂缓(lossless-pending 保图),最多宽限 m 步(默认 3),仍未到则替换为占位符 `[visual summary pending; evidence at DataEngine step N]`——**彻底消灭现 Flash 的"迟到回填突变任意深度"与"图丢摘要空"两个缺陷**。
|
||
- 恒定深度擦洗让缓存前缀**单调增长**(每轮失效的只是最后 K 步),同时 token 稳态恒定——严格优于"每 25 轮批一次"(后者在批间攒 25 轮的图)。
|
||
2. **压缩事件(稀疏、深)**:见 §3.3,F 区重写 + A 区前移。深突变与语义边界重合,此时接受一次全量 re-prefill(Claude/Codex 同款代价)。
|
||
- **思维签名政策(2026-09-01 定):回合内强制、A 区顺带、压缩即弃**——Gemini thought signature 仅在当前回合函数调用周期内是 API 硬要求(原样回传);A 区 AIMessage 字节不动故签名免费有效(擦洗只碰观察/工具消息,从不碰 AIMessage);已完结回合的签名 API 层面可选,chunk 化整回合移除后自然消亡,不建任何跨压缩维护机制(签名绑定消息精确内容,语义上不可能脱离原消息存续)。真机 A/B 无签名相关错误佐证。
|
||
3. **紧急快照(罕见)**:F 区整体替换为会话快照,见 §3.3 L3。
|
||
|
||
### 3.3 三级压缩
|
||
|
||
**L1 — 图片/XML 擦洗**(上文深度 K 机制)。摘要由 `StepMemoryService` 每步后台 dispatch(flash-lite,沿用红标叠加 + 禁判定词契约),**准备是每步的,应用是延迟的**。
|
||
|
||
**L2 — 段压缩(chunk 化)**,触发器取先到者:
|
||
- **里程碑切换**:**唯一事实源是已执行步骤的 `subgoal_hash` 实际变化**(`_record_turn` 处比较相邻步盖章);`_process_plan_write` 的顶层完结事件只入队一个"待确认边界",由下一个盖章步骤确认后才触发——防止被棘轮验证否决回滚的计划写入造成伪切换。切换时把上一段(不含活跃窗口底线内的步)压成一个 **HistoryChunk**;
|
||
- **尺寸阈值**(不切里程碑时的兜底,即提案第 3 条):开放段累计 ≥12 步或段内摘要 ≥2K token;
|
||
- **边界事件**:包/Activity 大切换、目标变更、FA 边界、不可逆副作用(继承姊妹文档 §6.5 全表)。
|
||
|
||
Chunk 身份 = `(start_step_id, end_step_id, source_step_ids)`;`subgoal_hash`(经别名链解析)只是标签。
|
||
|
||
**chunk 是"分段账本",不是融合胶囊(用户硬约束,2026-08-31)**:压缩只缩每步的**宽度**(推理原文/XML/截图 → 摘要/动作行),永不缩步的**数量**——段内步 1:1 保留,不合并、不重排、不丢步。两个刚性理由:① 视频分析模块以步号/时间戳为外键对齐视频时间轴,步粒度一旦融合即断链;② 大模型对任务整体的时间感知必须精确到步("第 23 步做了什么、距今多少步"要可直接回答)。
|
||
|
||
**chunk 内部结构为三段式(用户 2026-09-01 定形:梗概 + 效果 + 压缩步摘要,底下逐步动作)**:
|
||
|
||
```text
|
||
[Chunk 3 | 里程碑「登录并进入设置页」| Steps 18–29 | T+12:40 → T+15:02]
|
||
|
||
① 梗概与效果(LLM 段头)
|
||
本段在做什么:为后续改配置,先完成登录并进入设置页。
|
||
实际做了什么:用账号 A 走了短信验证码登录,途中关闭了一次推广弹窗。
|
||
效果/留下什么:已登录态(账号 A);验证码输入页出现过一次超时重发;
|
||
笔记 notes/login_flow.md 记录了验证码入口路径。
|
||
Entry: 应用主页,未登录 Exit: 设置页已打开
|
||
Verified: … Unresolved: … Failed paths: … Entities: …
|
||
|
||
② 压缩步摘要(LLM,逐区间叙事,必须带步号且全覆盖)
|
||
- Steps 18–20: 进入登录页,切换到验证码登录并填入手机号
|
||
- Step 21: 首次获取验证码后等待期间出现推广弹窗,关闭之
|
||
- Steps 22–26: 输入验证码,一次超时后重发并重新输入
|
||
- Steps 27–29: 登录后从侧边栏进入设置页
|
||
|
||
③ 逐步动作账本(机械拼装,1:1,永不省略)
|
||
- Step 18 (T+12:40): tap(632,1180)[登录入口] → executed
|
||
- Step 19 (T+12:56): tap(210,884)[验证码登录] → executed
|
||
- Step 20 (T+13:04): type("138…") → executed
|
||
…(每步一行,直到 Step 29)
|
||
```
|
||
|
||
三段的分工与生成方式:
|
||
|
||
- **① 段头梗概(LLM,gemini-3.7-flash)**:三问式散文(在做什么 / 做了什么 / 有什么效果——效果必须包含本段**留下的笔记**:段内 notes 写入/更新的文件与要点引用)+ 结构化字段(沿姊妹文档 §7.3:verified_facts / unresolved / failed_paths / important_entities / entry_state / exit_state)。要求**特别详细、尽量维持原状**:宁可长,不许把事实抽象到不可辨认。**笔记关联是机械校验项(用户 2026-09-01 定,"不能丢")**:段内每条 note 写入(save/update/append_note,经 trace 抓取)的目标文件 key 必须出现在段头 effect 中,缺任一即该次尝试判负重生成——与 ② 的覆盖校验同级;chunk 存"指针+要点",笔记全文在 notes 系统(read_note/recall 可回捞),可恢复压缩。
|
||
- **② 压缩步摘要(LLM,同一次调用产出)**:"Steps xx–xx 做了什么,Step xx 做了什么"式的区间叙事,输入是段内逐步视觉摘要 + 动作 + Validator 结果。硬性约束:每行必须带步号引用;**区间并集必须无缝覆盖整段步范围**(机械校验,缺口即重生成);同一区间只并列同质连续动作,异质动作单步成行;中立措辞契约沿用(禁判定词)。
|
||
- **③ 逐步动作账本(机械拼装,不经 LLM,零失真)**:步号 + 相对时间 + 精确动作 + Controller/Validator 结果短语,全部从 DataEngine 确定性渲染。这是视频对齐与步级时间感知的**硬骨架**,任何层级的压缩都不得触碰。三条账本细则:
|
||
- **动作必须带元素语义(用户约束,2026-09-01)**:`tap(632,1180)['确认按钮']` 而非裸坐标。索引点击路径已在记录时存 `target_text/resource_id/class`(`operator.py:893-896`);裸坐标路径(`operator.py:854-861` 返回全空)与 Flash/FA 动作工具需补**记录时语义充实**(见 §6.1);坐标永远保留(视频对齐/重放的外键),语义并列。
|
||
- **FA 恢复子动作入账**:一步内 Failure Analyzer 补做的真实设备动作(`_exec_*`,已在 `interleaved_events`)以缩进子行机械渲染——视频轴上有帧的动作,账本必须有行。
|
||
- **用户注入指令逐字保全**:段内 `injected_instruction` 以独立行原文保留在 ③(`User @ Step 21: "…"`),任何压缩层级 never-evict(Codex 保用户消息 / Claude Code 摘要设 All User Messages 节,同一共识)。
|
||
|
||
每步的完整视觉摘要不再逐条出现在 chunk 里(它们是 ② 的生成输入,原文永远在 DataEngine,recall 可回捞;A 区未压缩前照常在场)。chunk 版本化 append-only。
|
||
|
||
**就绪门控交换(用户 2026-09-01 定,"必须就绪再换";修订 M3 原实现)**:段头 ①② 未就绪时**禁止**交换——原始消息原样留在转写里,lossless-pending 原则从图片层贯彻到段层。边界事件只做两件事:关闭段、dispatch 段头生成;**交换发生在段头就绪之后的下一次渲染/压缩事件**(深突变时机照旧成批,缓存代价不变,只是延后)。理由:段头缺席时 `verified_facts` 缺席,只剩"发生了什么"没有"确立了什么"——模型会怀疑已验证事实并重做,这是实测过的死循环高危形态。降级梯:生成失败(有界重试耗尽)→ 原文继续保留 + fallback 模型重投;软阈值压力下仍未就绪 → 依旧保留原文(正确性优先于 token);**仅硬阈值紧急态**允许 ③-only 交换(带 pending 标记与 recall 指引;checker post-hoc 标注独立于段头,pending 态照常渲染)或直接走 L3。M3 原"冻结 ③ + pending 标记、下次事件收割"的实现按此修正——A/B 中"21 chunk 段头全 pending"在门控下退化为"尚未压缩"的安全形态,而非"历史只剩账本"。
|
||
|
||
**旧 chunk 不堆叠新摘要**:无界任务(`[Loop:continuous]`)下 chunk 数超限时做二级合并(era 合并)——**合并的只是 ① 段头**(结构化字段集合并,非散文再摘要),② 区间叙事与 ③ 逐步账本仍按段保留(预算极紧时 ② 可退化为只留区间标题行)。可见预算靠分级降宽(全三段 → ①合并+②③ → ①合并+③ → 时段段落)+ recall 兜底。
|
||
|
||
**极限层(决策 5 终审定案,用户 2026-09-01):允许丢步,压成"时段段落"**——era 数再超上限时,最老 era 整体压缩为一段话,**必须携带步号范围与起止时间**:`[Era N | Steps a–b | T+hh:mm → T+hh:mm] <一段话概括该时段在做什么/做了什么/留下什么>` + recall 指引行;用户注入指令行仍逐字保留(never-evict 不随丢步豁免)。段落**机械拼装优先**(由该 era 各 chunk ① 的 doing/did/effect 与 verified_facts 浓缩,不做散文再摘要的 LLM 调用;段头缺失的 chunk 回退用里程碑标签)。步级可寻址在此层降级为"时段可寻址":起止时间是视频对齐的粗粒度外键,步级细节走 recall_history 回捞(DataEngine 永远完整)。此前"步骨架任何层级不破"的表述据此修订:**该不变量适用于 L2 chunk、era 合并、L3 快照,唯极限时段段落层除外**。
|
||
|
||
**L3 — 紧急全量快照**:实测 token 越过硬阈值(如连续注入大量工具输出)时,F 区的**段头/知识面**整体替换为一个结构化会话快照(schema 取 Gemini CLI `state_snapshot` 与 Claude Code 9 节的交集,加本域字段):`overall_goal / plan_state / verified_facts / unresolved / failed_paths / important_entities / device_app_state / recent_actions`。快照从 chunk 段头结构化字段合并生成,尽量不做"摘要的摘要"。**逐步索引仍保留**(最小行宽:步号 + 相对时间 + 动作短语),步级时间骨架在最坏情况下也不断链。A 区底线不动。
|
||
|
||
**滑动窗口底线(提案第 2 条保留)**:任何触发器都不得把 A 区压到 `min_active_steps`(默认 5)以下;最近 1–2 个完整回合(AI 推理原文 + 工具结果 + Validator 配对)永远原样——姊妹文档 §4.2"连续推理先于压缩"原则不变,且切分点永不劈开 tool-call/response 对(Gemini CLI 同款约束)。
|
||
|
||
### 3.4 计量与阈值
|
||
|
||
- **实测优先**:每轮把上一次 LLM 调用的 `usage_metadata.prompt_tokens` 记为转写基数,加本轮追加内容的启发式增量(char/4 + 每图按分辨率估算)作为决策输入。无估算器依赖、无额外调用。
|
||
- 预算三档(**临时默认值,M0 实测后定标**):`context_budget_tokens=80K`;软阈值 0.7(56K)→ 触发 L2 压最旧段;硬阈值 0.9(72K)→ L3 快照。工具单次输出上限 16K(Codex 同款),超限走同回合信封(姊妹文档 §13 保留)。
|
||
- 姊妹文档实测提醒仍有效:8.8–12.7K 区间"长度与延迟无关"**不可外推**到 80K;但缓存命中改变了成本函数——M0 必须重测缓存命中下的 TTFT 曲线再定 budget。
|
||
|
||
### 3.5 可逆性与召回
|
||
|
||
- 一切被压缩内容在 DataEngine 原样在案(现状已满足:截图 SHA-256 内容寻址、步骤/trace 全量);占位符与 chunk 块一律携带 step 引用。
|
||
- `recall_history` 工具照姊妹文档 §10 原样落地(FTS + step 范围 + 包/Activity + 屏幕 hash,≤5 结果 ≤2K token,图片按需回捞单步);本地屏幕相似度提示(不发图、不调模型)保留。
|
||
- **Operator 只挂一个历史工具(2026-09-01 定,防过度设计)**:取历史 = chunk ①②③ 常驻索引(免费)→ `recall_history` 确定性回捞(一次查询,事实级)。HistoryAnalyzer **不进 Operator 工具清单、不进提示词指导**——它输出属推理级(事实优先级链最末)、每次调用是全量历史输入 + 关键路径等待,且"开放式跨历史语义问题"的需求目前纯属推测。仓库中它照旧存在(其他流程自用);若真机 trace 日后出现"recall 反复查不到、问题属聚合/因果型"的实证,再评估以升级路径接入。era 溢出标记行指向 recall。
|
||
|
||
---
|
||
|
||
## 4. 与提案逐条对照
|
||
|
||
| 提案 | 方案落点 |
|
||
|---|---|
|
||
| 每步增量构建(Flash 式) | §3.1 Transcript:Pro/Flash 统一 append-only;Pro 获得真多轮 + 思考签名 + 缓存命中 |
|
||
| 后台 flash-lite 图片压缩 | §3.3 L1:每步后台准备(StepMemoryService dispatch),深度 K 延迟应用(修正 #1) |
|
||
| 里程碑切换时压缩前段 | §3.3 L2:里程碑切换是首要触发器;chunk 身份按 step 范围(修正 #2) |
|
||
| 滑动窗口底线 | §3.3:`min_active_steps` + 最近完整回合硬保留 |
|
||
| 长期不切换按窗口上限压 | §3.3 L2 尺寸阈值 + §3.4 实测计量(修正 #3);再兜底 L3 快照(补强 #4) |
|
||
|
||
---
|
||
|
||
## 5. 摘要语义:两个 lens 按"高度"分工(决策修订,已确认 2026-08-31)
|
||
|
||
visibility 文档 §6.1 曾定"两 lens 按 profile 分立"(Flash=证据替身,Pro=整步胶囊),依据是 *Pro 丢弃旧步思考*。**本方案改变了这个前提**:转写保留推理原文直到段压缩,Pro 的步级摘要也变成了"证据替身"。因此修订为:
|
||
|
||
- **步级 = `VisualTransitionLens`(唯一步级 lens,双 profile 共用,输入按可得自适应)**:**替换哪张图就描述哪张图,有什么用什么**(用户 2026-09-01 定)——Flash 前后双图照旧;Pro 仅单决策帧+动作红标(描述该屏内容与动作落点,转换信息由相邻步摘要自然链接),单图时提示词不含 AFTER ACTION 段。**Pro 无动作后抓屏是刻意安全网**:ADB 截图延迟会丢瞬态控件(实测视频播放控件),截图量已压到最低——不得以任何方式"修复"(不补抓、不用步 N+1 前图拼后图)。禁判定词契约不变(successfully/completed/entered/navigated/failed/... 全禁);缺后图永远只表示"无独立后验证据"。
|
||
- **段级 = `StepCapsuleLens` 产出 chunk 的 ①+②**:意图/策略/进度的压缩发生在段压缩时,输入是段内逐步视觉摘要 + 动作 + Validator 结果 + 该段推理原文(压缩前最后一次可读)+ 段内 notes 写入记录。单次调用产出 ① 梗概与效果(三问式散文 + 结构化字段,含留下的笔记)与 ② 压缩步摘要(步区间叙事,步号全覆盖机械校验)。**③ 逐步动作账本机械拼装不经 LLM**(用户硬约束:按步压缩、尽量维持原状、时间感知精确到步)。`summarizer.md` 的语义权威与中立契约(含 `[Loop]` 循环不算异常的判别)迁移到 ①②。
|
||
- **会话级 = 快照 schema**(L3 专用)。
|
||
- 事实优先级链不变:用户指令 > Validator/Controller 客观结果 > XML/OCR 差异 > 有源摘要 > 推理。
|
||
|
||
推论:Pro 图中的 `SummarizerNode` 不再逐步生成文本胶囊,退化为 `dispatch(VisualTransitionLens)` 一行;辅助 agent 编译视图中的步行改为"时间 + 视觉摘要 + 精确动作 + Validator 结果",段外历史提供 chunk digest。**这是对先前已确认决策的修订,用户已于 2026-08-31 拍板采纳(见 §10 决策 1)。**
|
||
|
||
---
|
||
|
||
## 6. 数据模型与图接线
|
||
|
||
### 6.1 DataEngine 增量(最小 schema 变更)
|
||
|
||
- `StepRecord.extra_metadata` 增写:`summary_status`(pending/ready/failed/stale)、`summary_source`(lens 名)、`summary_version`、`summary_model`——`update_step_summary` 从盲覆写升级为带状态与版本的写入(同键并发以版本判序)。
|
||
- 新增 chunk 存储(SQLite 新表,字段即 §3.3 结构 + `session_id`/`version`);`get_agent_friendly_steps` 增配套 `get_history_chunks`。
|
||
- 修 `record_step` 的隐式语义:post==pre 时 `post_image_name=None` 表示"无独立后图证据",摘要器**不得**据此推断"屏幕未变"(姊妹文档 §4.3 原则写进 lens 契约)。
|
||
- **动作语义充实(record-time enrichment,M0)**:动作落 DataEngine 前,凡带坐标而缺 `target_text` 的(裸坐标点击 `operator.py:854-861`、Flash 动作工具、FA `_exec_*`),对当帧感知快照做命中测试(fused_elements → XML → OCR 取覆盖该点的最小可交互元素),best-effort 填 `target_text/target_class/target_resource_id`,并记 `target_label_source: "index"|"hit_test"|"ocr"|"none"` 区分模型指名与事后推断。渲染侧 `format_action_clean`(task_tree.py:39-64)已支持 target_text,零改动受益。Phase 2 PerceptionStore 落地后命中测试即快照一次查询,零额外抓取。
|
||
|
||
### 6.2 运行时归属
|
||
|
||
```text
|
||
ctx.step_memory: StepMemoryService # visibility 方案 D 保留:调度/有界重试/flush/step_id 键控
|
||
├─ lenses: VisualTransitionLens / StepCapsuleLens(chunk) / SnapshotLens
|
||
└─ transcript: TranscriptLedger # 新:四区消息账本 + 擦洗沿 + 压缩事件
|
||
```
|
||
|
||
- 有界重试(替换 Flash 无界循环)+ `flush(timeout)` 收尾 + 显式降级态,均照 visibility §6.1 原文。
|
||
- 转写在内存;崩溃恢复走 §3.1 冷启动构造器。素材以 `PerceptionRef`/`image_name` 引用(Phase 2 落地后免搬 bytes;Phase 2 未落地前先传 bytes,接口留 ref 形态)。
|
||
|
||
### 6.3 图接线
|
||
|
||
- **事件源**:`_record_turn`(`graph.py:108-148`)比较相邻步 `subgoal_hash` → `transcript.on_milestone_boundary()`;`_process_plan_write` 的 `new_top_level_completions` → 同一入口(去重)。
|
||
- **Operator 切换**(flag `memory.transcript.enabled`):`_build_prompt` 改为 `transcript.render_messages(tail=当前观察)`;旧 2-message 路径完整保留为回退。现成接缝 `unified_history` 槽位用于过渡期 A/B 对拍。
|
||
- **Flash 切换**:`compress_flash_messages` 的逐轮全量重扫替换为擦洗沿推进;`max_turns` 可放宽(压缩已兜底上下文)。
|
||
- 辅助 agent 不动图结构,编译视图接 ContextPolicy 策略表(visibility §6.2 原样,8 个调用点逐点对拍)。
|
||
- 顺手修:Diagnoser `window_steps` bug、两个死参数、死常量 `MAX_MESSAGES_IN_HISTORY`。
|
||
|
||
### 6.4 配置(收敛 visibility §6.3 与姊妹文档 §14)
|
||
|
||
```jsonc
|
||
"agent": {
|
||
"memory": {
|
||
"runtime": { "max_concurrency": 2, "retry_limit": 3, "flush_timeout_s": 30 }, // max_concurrency 1→2:硬化轮定标(基线报告 §6)
|
||
"transcript": {
|
||
"enabled": true, // M2-M4 出厂关灰度;M5 起默认开(2026-09-01 用户拍板);false 即逐字节回滚开关
|
||
"context_budget_tokens": 80000, // 2026-09-01 基线定标:维持(稳态 ≤29% 预算)
|
||
"soft_ratio": 0.7, "hard_ratio": 0.9,
|
||
"min_active_steps": 5,
|
||
"image_scrub_depth": 3, "pending_grace_steps": 3,
|
||
"xml_scrub_depth": 1,
|
||
"similarity_hint": true, "similarity_max_distance": 5 // M5 定标 8→5(460 步 dHash 分布)
|
||
},
|
||
"chunking": { "max_steps": 12, "target_source_tokens": 2000, "model": "gemini-3.7-flash", "max_chunks": 8, "max_eras": null }, // max_eras null=随 max_chunks(M5 新独立键)
|
||
"step_lens": { "model": "gemini-3.5-flash-lite" }, // 现 agent.flash.step_summarizer 键保持兼容别名
|
||
"recall": { "enabled": true, "max_results": 5, "max_text_tokens": 2000 },
|
||
"policies": { /* 辅助 agent ContextPolicy 覆盖 */ }
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 分阶段落地
|
||
|
||
前置说明:visibility 文档 Phase 0/1 已实施;其 Phase 2(PerceptionStore)/Phase 3(ctx 分解)独立推进,与本轨道仅在 §6.2 的 ref 传递处交汇(非阻塞依赖)。
|
||
|
||
**M0 — 计量与 schema(无行为变化,可独立合入)** ✅ 代码侧已实施 2026-08-31;✅ 基线录制已完成 2026-09-01(真机 Pixel 10,见 `docs/plans/history-baseline-2026-09-01.md`)
|
||
- `summary_status/source/version` 落 `extra_metadata`;`update_step_summary` 版本化;
|
||
- 动作语义充实(§6.1):裸坐标/Flash/FA 动作记录时命中测试补 `target_text` 等字段(越早上线历史数据越完整);
|
||
- 转写计量管线(上轮 prompt_tokens 采集 + 增量估算)纯记录不决策;采集缓存命中率基线(`cachedContentTokenCount`);
|
||
- 修 Diagnoser bug、死参数、死常量;Flash 摘要重试改有界 + flush 语义(visibility §8 已定为有意变更,单测覆盖);
|
||
- 冻结代表性长任务集,录基线:输入 token、TTFT、全回合延迟、摘要就绪延迟、重复动作率、FA 触发率(姊妹文档 Phase 0 原样)。
|
||
|
||
M0 实施要点(2026-08-31,工作区未提交;与上述计划的差异逐条标注):
|
||
|
||
1. **摘要状态与版本化**:`StorageManager.update_step_summary` 升级为原子读改写
|
||
(单连接内读 `extra_metadata` → 判序 → 写回),签名
|
||
`(step_id, summary, *, source, version, model, status)`,返回 bool(stale 写返回
|
||
False)。判序规则:显式 `version` 低于在库版本即丢弃;`version=None` 自动
|
||
`在库+1`;**状态降级需显式更高版本**——`pending` 不覆盖 `ready/failed`、
|
||
`failed` 不覆盖 `ready`(防后台竞态回退);`summary` 列仅被
|
||
`ready`+非 None 摘要触碰。`DataEngine.update_step_summary` 同签名透传(后台
|
||
写 + 仅 ready 发 SSE,与旧行为逐字节一致)。调用方:Flash
|
||
`VisualStepSummarizer` dispatch 时补写 `pending`、成功写
|
||
`ready/source="visual_transition"/model`、耗尽写 `failed`;Pro `SummarizerNode`
|
||
写 `ready/source="step_capsule"/model`。`stale` 态本阶段只入枚举,无写入方
|
||
(M2 起由重摘/回滚使用)。
|
||
2. **动作语义充实(record-time enrichment)**:新模块
|
||
`artemis/utils/element_hit_test.py`(`find_element_at_point` /
|
||
`hit_test_semantics`:最小覆盖元素胜出,XML 源优先于 OCR 源,best-effort 永不
|
||
抛异常)。三条落点:① Pro 裸坐标分支(`operator.py` `resolve_target_element`
|
||
坐标路径)就地命中测试 `state.indexed_elements`,click/long_press/input_text
|
||
动作 dict 增 `target_label_source`(索引路径盖 `"index"`);②
|
||
`McpActionExecutor._execute_device_action`(Flash 与 FA 共用漏斗)在
|
||
`_translate` 后、设备调用前对归一化 target 命中测试(**必须在 observe 之前
|
||
——observe 会覆写 indexed_elements 为后帧**),语义并入 action trace 的 args
|
||
payload(FA 的 interleaved_events 渲染 `format_tool_call_clean` 零改动受益)并经
|
||
`ToolExecutionResult.metadata["target_semantics"]` 回传;③ FlashRunner 落
|
||
`record_step` 前把该 metadata 合入 `action_dict` 顶层。与计划的差异:
|
||
`click_sequence`/`swipe` 多点动作本阶段不充实(单目标动作先行);"可交互"
|
||
以"已被感知索引"近似(索引元素不带 clickable 位)。
|
||
3. **token 计量管线**:新模块 `artemis/services/token_meter.py`
|
||
(`extract_usage` 统一 LangChain `usage_metadata` 与 Gemini 原生
|
||
`cachedContentTokenCount`/`input_token_details.cache_read` 两种形态;
|
||
`SessionTokenMeter` 按会话累计)。采集点接在 LLM 网关
|
||
`RobustChatModelWrapper.complete()/ainvoke()` 出口(全部生产调用必经;raw
|
||
模型旁路仅测试使用,不采集),每次调用记一条 `llm_usage` trace(type
|
||
`llm_call`):单次 prompt/completion/cached + `context_base_tokens`(=本次实测
|
||
prompt,即下一轮上下文基数)+ 会话累计五项(calls/prompt/completion/cached/
|
||
cache-hit calls)。纯记录,不驱动任何决策;采集失败静默降级永不进入调用路径。
|
||
4. **三缺陷修复**:① Diagnoser `get_recent_subgoal_hashes` 改传
|
||
`base_dir`(对齐 committee_tool 用法),Diagnoser 历史渲染恢复正常;②
|
||
`build_plan_and_history` 删除 `chronological_last_step`/`detailed_subgoal_hashes`
|
||
死参数,operator/failure_analyzer/planner 3 个调用点与 2 处单测同步清理;③
|
||
删死常量 `MAX_MESSAGES_IN_HISTORY`(constants.py,全仓零引用确认)。
|
||
5. **Flash 摘要重试有界化**(有意行为变更):`_run_summary_until_ready` 以
|
||
`1 + retry_limit` 次尝试封顶(`StepSummarizerConfig.retry_limit`,默认 3,
|
||
FlashRunner 接线),耗尽进显式 failed 态(`has_failed()` + DataEngine
|
||
`summary_status="failed"`);**failed 步在压缩器视角仍为 pending**(保图,
|
||
lossless 语义不变——变更只砍掉无限 LLM 重试,不改消息流);`flush(timeout)`
|
||
收尾语义保留。
|
||
6. **测试**:新增 `tests/unit/utils/test_element_hit_test.py`(6)、
|
||
`tests/unit/data_engine/test_step_summary_versioning.py`(7,含并发判序/降级
|
||
拒绝/SSE 仅 ready)、`tests/unit/test_token_meter.py`(7)、
|
||
`tests/unit/mcp/test_action_executor_semantics.py`(4,含缺元素数据优雅降级
|
||
`"none"`)、operator 命中测试正例 + Flash 有界重试失败注入 2 例;存量断言
|
||
同步(operator 精确 dict 断言补 `target_label_source`、两摘要器调用断言补
|
||
kwargs)。验证:tests/unit 全量 **1094 通过 3 跳过 0 失败**;tests/tools
|
||
相关子集(operator/diagnoser/validator)7 通过(既有 5 个 API key 联网失败
|
||
未触发)。
|
||
7. ~~**未完成项**:代表性长任务基线录制~~ **已完成(2026-09-01)**:3 个冻结
|
||
长任务 ×2 样本真机录制完毕,正式基线与 §3.4 定标建议见
|
||
`docs/plans/history-baseline-2026-09-01.md` §3/§6(要点:Operator 首调输入
|
||
P50 12.2K/P95 17.6K;隐式缓存基线 43% 调用/29.8% token;模型调用 P50
|
||
13.8s;摘要就绪 P50 60s/P90 214s——长尾超擦洗窗口,定标建议
|
||
max_concurrency 1→2)。附带发现:视觉 lens 调用走 `get_google_llm` 原始旁
|
||
路,无 llm_usage 计量(与本条"raw 旁路仅测试使用"表述不符,待收口)。
|
||
|
||
**M1 — StepMemoryService + 擦洗沿(先 Flash 落地验证)** ✅ 代码侧已实施 2026-08-31(缓存命中提升待真机实测)
|
||
- 共享运行时成形;Flash 从"逐轮全量重扫"切到"擦洗沿"(深度 K 图替换 + 宽限 + 占位符);
|
||
- 验收:成功路径消息序列与现版语义等价(图→摘要时点后移属有意变更,单独对拍);缓存命中率显著上升(有实测基线可比);race 缺陷回归测试(图丢摘要空、迟到回填深突变均不再发生)。
|
||
|
||
M1 实施要点(2026-08-31,worktree `zealous-gates-4626b1` 未提交,叠加在 M0 之上;与计划的差异逐条标注):
|
||
|
||
1. **StepMemoryService 成形**:新 `artemis/memory/step_memory.py`——调度骨架自
|
||
Flash `VisualStepSummarizer` 上提:零阻塞 `submit`、有界重试(1 + retry_limit,
|
||
沿 M0 语义,耗尽进显式 failed 态并触发 `_on_status` 钩子)、`flush(timeout)`
|
||
收尾(无参时用配置的 `flush_timeout_s`)、`max_concurrency` 信号量封顶并发
|
||
(重试退避 sleep 在信号量之外,不饿死同伴)、状态查询
|
||
(has_job/is_pending/has_failed/get_summary/get_job_payload)。**键控**:有
|
||
DataEngine step_id 时以 `str(step_id)` 为规范键,tool_call_id 落别名映射;两者
|
||
皆无的旧调用方保持序号键(压缩器的 tool_call_id → 序号回退链原样保留)。
|
||
`VisualStepSummarizer` 取"并入服务"路线——改为服务子类,只保留视觉 lens 语义
|
||
(红标叠加、`flash_summarizer.md` 禁判定词契约、M0 版本化
|
||
`summary_status` 写入),对外表面(dispatch 签名/查询方法)零迁移。与计划的
|
||
差异:a) 服务本阶段仍由 FlashRunner 自持,未挂 `ctx.step_memory`(M2 Pro 切换
|
||
时再上提组合根);b) lens 未做成独立协议对象,视觉 lens 即子类 `_attempt`
|
||
(StepCapsuleLens 落地时再抽形式化 lens 接口);c) `max_concurrency=1` 默认值
|
||
使摘要 LLM 调用串行化(旧实现无界并发),属计划 §6.4 既定值。
|
||
2. **擦洗沿压缩器**:`ScrubEdgeCompressor`(`context_compressor.py`)——索引制
|
||
旁路账本(契约:FlashRunner 消息列表 append-only),增量发现新消息,冻结前缀
|
||
永不重扫。纪律:深度 1 剥 XML(保留集 = 最新 `xml_scrub_depth` 条观察,合并块
|
||
保前缀语义与 `prune_history_xml` 开关照旧);深度 K 图片决议——ready→摘要块
|
||
(沿用 `--- Historical Visual Transition ---` 前缀、剥图后追加于消息尾,与旧
|
||
产物同形)、failed→立即 `[visual summary unavailable; evidence at DataEngine
|
||
step N]`(**不吃宽限**,M0 的"failed 仍保图"按计划收口为有意行为变更,单测
|
||
覆盖)、pending→保图宽限至深度 K + pending_grace_steps,越限则
|
||
`[visual summary pending; evidence at DataEngine step N]`;宽限期内摘要到达即
|
||
正常替换;决议后冻结,**迟到摘要永不回填**(哈希不变量测试)。N = Flash 动作
|
||
序号(与 DataEngine 步序一致)。细节差异:a) 无 job 的历史图(初始观察、摘要
|
||
器关闭时的动作图)同样计深并在沿上静默剥除(无占位符)——旧实现是"下一轮即
|
||
剥",新时点后移至深度 K;b) 宽限计量单位是"图深度推进"而非回合数(无新图的
|
||
空转回合不消耗宽限,上下文也未因图增长);c) 旧 `compress_flash_messages` 原
|
||
样保留,作语义对拍参照与回退路径(M2 转写落地后再删)。
|
||
3. **配置**:`agent.memory.runtime`(max_concurrency=1/retry_limit=3/
|
||
flush_timeout_s=30)与 `agent.memory.transcript`(image_scrub_depth=3/
|
||
pending_grace_steps=3/xml_scrub_depth=1)已落 `AgentGlobalConfig.memory`;
|
||
显式的旧键 `flash.step_summarizer.retry_limit` 在 before-validator 里播种
|
||
`memory.runtime.retry_limit`(新键显式设置时新键胜出,旧键本体不动);
|
||
enabled/model/prune_history_xml 三键权威仍在旧块(未迁移)。
|
||
`xml_scrub_depth` 已实现为通用深度(>1 时保留更多层 UI 列表),默认 1 与现
|
||
行为等价;`prune_history_xml=false` 时冻结也不剥 XML(保旧开关语义)。
|
||
4. **FlashRunner 接线**:摘要器以 `memory.runtime` 三值构造;压缩器为 per-run
|
||
实例(擦洗账本是运行态);`flush()` 走配置超时。
|
||
5. **测试**:+20(`tests/unit/memory/test_step_memory_service.py` 7:零阻塞/有界
|
||
重试/别名键控/并发封顶/flush 超时与默认值/重投递清 failed;
|
||
`tests/unit/agents/test_flash_scrub_edge.py` 8:成功路径新旧对拍(时点后移
|
||
在断言中显式标注)、race a/b/c、冻结哈希不变量、XML 深度 1 等价、
|
||
prune 开关、非跟踪消息不触碰;`tests/unit/test_memory_config.py` 5:默认值/
|
||
显式值/旧键播种/新键优先/无显式旧键不播种)。存量适配 1 处:
|
||
`test_summarizer_keys_actions_independently_of_turn_number` 的
|
||
`_step_inputs["tc-1"]` 直取改为 `get_job_payload`/`resolve_key`(step_id 键控
|
||
所致)。验证:tests/unit 全量 **1114 通过 3 跳过 0 失败**(M0 基线 1094+3,
|
||
只增不减);tests/tools 子集(operator/summarizer/validator/diagnoser)7 通过。
|
||
6. **max_turns 未动**(默认 30):擦洗沿使图片上下文稳态有界,更大 max_turns
|
||
已安全,放宽属配置决定,留给使用方/后续里程碑。
|
||
7. **未完成项**:缓存命中率提升的量化(M1 验收第二条)依赖 M0 尚未录制的真机
|
||
基线——计量管线已就绪,待真机长任务前后对比。
|
||
|
||
**M2 — Pro 转写切换(本方案核心,flag 灰度)** ✅ 代码侧已实施 2026-09-01;✅ 真机 A/B 已完成 2026-09-01(红线全过且大幅改善,含一处阻塞 bug 的最小修复,见 `docs/plans/history-baseline-2026-09-01.md`)
|
||
- **前置子任务**:operator.json 模板拆分——今日 SystemMessage 经 `# CURRENT OBSERVATION` 切分后含易变的 plan+history(prompts.py:218-222),必须把静态规则留 S 区、动态内容移出,否则 S 区无从字节稳定;
|
||
- `TranscriptLedger` + Operator 双路径并存;转写路径 A/B 对拍旧路径(同任务同设备,姊妹文档 §18 红线:P50/P95 延迟回归 ≤5%、关键路径零摘要等待);转写内时间一律 `T+mm:ss` 起点偏移(冻结后字节稳定),不用 "ago" 措辞;
|
||
- SummarizerNode 退化为视觉 lens dispatch(§5 决策已确认);辅助 agent 编译视图接 chunk 前先保持现状。
|
||
|
||
M2 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1 之上;与计划的差异逐条标注):
|
||
|
||
1. **模板拆分(前置子任务)实现为运行时拆分**:operator.json 本体一字节未动——
|
||
唯一易变跨度是字面量 `PLAN_HISTORY_TEMPLATE_SECTION`(`## Current Plan &
|
||
Execution History\n{{ plan_and_history }}`),转写路径
|
||
`render_transcript_static_system`(prompts.py)把它替换为静态指针段后渲染,
|
||
得到整会话字节不变的 S 区(可用性装配 `resolve_operator_prompt_tools` 与
|
||
语法开关 `_operator_grammar_flags` 从 TemplatePromptComponent 逐字提取共用,
|
||
旧组件行为等价)。硬约束双保险:模板中该字面量"恰出现一次"的结构断言
|
||
(缺失即抛错,§9 风险落地)+ 拆分前录制的旧路径渲染 SHA-256 golden
|
||
(空计划/哨兵计划两份,`test_operator_transcript.py`)。S 区不再以
|
||
`# CURRENT OBSERVATION\n` 结尾——该头衔移入每回合尾部观察消息。
|
||
2. **TranscriptLedger**(新 `artemis/memory/transcript.py`):S/F/A 三区实体
|
||
+ 每回合 T 尾部由 render 参数传入。A 区 append-only;回合落账两段式
|
||
**stage→commit**(step_id 与 Validator 结果在 execution_check/validator 之后
|
||
才存在,故本回合消息先 stage,下一回合 build 时带 `step_key` +
|
||
`validator_result` 提交)。Validator 结果消息门控
|
||
`state.structured_decisions`(planner 拒绝清空、无终端动作为 None,均不落
|
||
账),渲染为 `Status: <status>` + format_result_clean 错误/修复细节,带
|
||
`T+mm:ss` 偏移(`format_session_offset`,分钟不进位保字节稳定单调)。
|
||
观察消息的摘要键走 `id(message)` 侧表(不进消息序列化载荷)。工具配对不
|
||
变量:擦洗只改内容块、永不删消息,切分点天然不劈 tool-call/response 对
|
||
(单测覆盖)。
|
||
3. **擦洗沿复用=泛化 ScrubEdgeCompressor**(未另造 Pro 压缩器):新增三个
|
||
构造参数——`summary_key_getter`(无 tool_call_id 的 Pro 观察 HumanMessage
|
||
按 step_id 键控查摘要)、`strip_markers`(深度 1 剥除扩展到
|
||
`--- Visible UI Elements ---` 与 `--- Task Plan (recited) ---` 计划复诵副本)、
|
||
`tail_offset=1`(活尾部在账本外,深度算术与 keep 窗口对齐 Flash 语义)。
|
||
默认参数下 Flash 路径字节不变(M1 全部擦洗测试原样通过);宽限/占位符/
|
||
冻结不回填纪律在 Pro 消息形态下重跑 M1 用例形态(`test_transcript_ledger.py`)。
|
||
`get_step_number` 从 VisualStepSummarizer 上提到 StepMemoryService 基类。
|
||
4. **Operator 双路径**(flag `agent.memory.transcript.enabled`,默认 false,
|
||
出厂关):关——旧 `_build_prompt` 路径零改动(golden + 存量 20 测试全绿);
|
||
开——`_build_prompt_transcript` 产出 S+F+A+尾部,尾部 = `# CURRENT
|
||
OBSERVATION [T+mm:ss]` 头 + 计划复诵(新 PlanRecitationPromptComponent,
|
||
复诵 task_plan 原文)+ 既有观察/注入/反馈组件原样复用。转写构建异常自动
|
||
回退旧路径(灰度安全网)。与计划的差异:a) 过渡接缝取 PromptComponent
|
||
机制新增组件而非 `unified_history` 空槽位(改动面更小,任务书允许取小者);
|
||
b) 复诵内容是 task_plan checklist 而非 build_plan_and_history 编译串——
|
||
历史已由 A 区原始消息承载,整串复诵会大量重复(§3.2 T 区语义即"计划复诵")。
|
||
Operator 可见性读集新增 `last_execution_result`(落账需要,documented
|
||
decision)。
|
||
5. **服务归属收口(M1 差异点 a)**:`ctx.step_memory` / `ctx.transcript_ledger`
|
||
成为 ArtemisContext 声明字段(并列而非持有——§6.2 给出的两个选项取后者);
|
||
`artemis.memory.ensure_step_memory(ctx)` 为组合根工厂(Operator 转写路径与
|
||
SummarizerNode 共用一个实例);FlashRunner 构造后发布到 `ctx.step_memory`
|
||
(只发布不消费,兼容存量 mock ctx 测试);`ArtemisContext.__aexit__` 在后台
|
||
任务清理前 flush 服务(异常退出 timeout=0 直接取消)。State 不携带任何消息
|
||
重物(`test_state_contract.py` 全绿)。
|
||
6. **SummarizerNode 切视觉 lens(无条件,非 flag 后)**:不再自调 LLM 生成文
|
||
本胶囊;按 step_id 从 DataEngine 取 pre/post 图 bytes(`get_step_image_path`),
|
||
动作取步记录 `action_taken`(缺则回退 structured_decisions 解析),dispatch
|
||
到 `ctx.step_memory` 视觉 lens(红标叠加、flash_summarizer.md 契约、M0 版本
|
||
化 `source="visual_transition"` 写入均沿用)。图缺失→None bytes 纯文本降级,
|
||
不抛错。与计划的差异:多动作步 dispatch 首动作为主体、其余进
|
||
`action_args["additional_actions"]`(红标叠加只画单动作)。过渡期安全性靠
|
||
task_tree.py "无摘要回退 detailed" 语义(未动)。
|
||
7. **冷启动构造器(最小版)**:转写空(0 回合、无 F 区)而 DataEngine 已有步
|
||
记录时,用 `build_plan_and_history(min_summaries=len(steps),
|
||
last_n_detailed=1)` 全量策略渲染冻结块,头部 `[Restored history]` 并注明
|
||
"step times relative to the original session start"(该块内的 relative_time
|
||
仍是 DataEngine 措辞,冻结一次生成故可接受;A 区从当前步正常追加)。
|
||
8. **测试**(只增不减):+22 净增——`tests/unit/memory/test_transcript_ledger.py`
|
||
12(S 区哈希稳定/A 区 append-only/T+mm:ss 与禁 ago/深度 1 剥复诵与 UI 列表/
|
||
深度 K ready 替换/宽限→占位符→冻结不回填/failed 立即占位/配对不劈/冷启动
|
||
块唯一性/双 stage 自动落账/无动作回合不落 Validator 消息);
|
||
`tests/unit/agents/test_operator_transcript.py` 7(golden 双哈希/模板单一
|
||
切点/S 区静态渲染无历史/两回合四区结构/无动作提交/冷启动 F 区/flag 关
|
||
2-message 不变);test_summarizer.py 重写为 4(dispatch 断言与不再写胶囊/
|
||
图缺失优雅降级/structured_decisions 回退与 additional_actions/无 step_id
|
||
no-op);test_memory_config.py +1(flag 默认关与显式开)。验证:tests/unit
|
||
全量 **1136 通过 3 跳过 0 失败**(M1 基线 1114+3,只增不减);tests/tools
|
||
子集(operator/summarizer/validator/diagnoser)7 通过。
|
||
9. ~~**未完成项(待真机)**~~ **A/B 已完成(2026-09-01,见
|
||
`docs/plans/history-baseline-2026-09-01.md`)**:红线全过且大幅改善——全回
|
||
合延迟 P50 21.4s→5.0s(−77%)、缓存命中调用 43%→88%(token 29.8%→49.6%)、
|
||
输出 token −74%、成功率 6/6 持平、零回退零摘要等待、无签名/格式类错误。
|
||
三点必读:① A/B 中发现并最小修复了一个阻塞 bug——`_build_prompt_transcript`
|
||
冷启动检查在第 2 回合误命中(上回合只 stage 未 commit,turn_count==0 且
|
||
steps 非空),`set_restored_history` 被空账本不变量拒绝→每回合静默回退旧路
|
||
径;修复为冷启动条件加 `not ledger.has_staged_turn`(operator.py,已在
|
||
worktree,未提交,**需本轨道认领合入并补"第 2 回合 steps 非空"回归测试**)。
|
||
② 处理组实际是 M2+M3 叠加(chunking 挂同 flag),纯 M2 归因未拆。③ 实验
|
||
模型为 gemini-3.6-flash(3.7 当日 503)。思考签名跨压缩事件的供应商侧验证
|
||
仍留 M3 前。灰度顺序照 §10 决策 3:建议修复合入后复测一轮再进默认配置。
|
||
|
||
**M3 — L2/L3 压缩** ✅ 代码侧已实施 2026-09-01(真机验收待跑)
|
||
- 里程碑边界 + 尺寸阈值 + 边界事件三触发;chunk 表 + era 合并;L3 快照;
|
||
- 验收:>30 步长任务转写 token 稳态有界;计划改写/回滚后 chunk 身份与可见性不受损(新增回归:重命名走别名链);`[Loop:continuous]` 100+ 步试跑无摘要堆叠。
|
||
|
||
M3 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1+M2
|
||
之上;全部行为在 `agent.memory.transcript.enabled` flag 之后;与计划的差异逐条标注):
|
||
|
||
1. **HistoryChunk 存储**:SQLite 新表 `history_chunks`(chunk_id/session_id/
|
||
start·end_step_id/start·end_step_number/source_step_ids/subgoal_hash 标签/
|
||
version/status/band1 JSON/band2/band3/rendered_text/created_at)。身份 =
|
||
(session_id, 步号范围),行**append-only**:同范围更高 version 追加新行,
|
||
`get_history_chunks` 默认取各范围最新版(`all_versions=True` 给审计全轨)。
|
||
DataEngine 增 `record_history_chunk`(后台写)/`get_history_chunks`。
|
||
**单写者纪律**:全部 DB 写经 HistoryChunkManager 内存镜像(capsule 服务不
|
||
直写 DB),版本号无竞争。
|
||
2. **三段式 chunk(§3.3 逐字对齐)**:
|
||
- **③ 机械账本**(`chunking.build_action_ledger`,不经 LLM):每步一行
|
||
`- Step N (T+mm:ss): <format_action_clean> -> <结果短语>`(时间 = 步
|
||
timestamp − session_start_time,经 `format_session_offset`,禁 "ago");
|
||
结果短语 = format_result_clean 错误/修复细节,success 收敛为 `executed`;
|
||
FA 恢复动作(interleaved_events 中 `_exec_*`)以 ` FA: ` 缩进子行经
|
||
`format_tool_call_clean` 渲染;injected_instruction 逐字独立行
|
||
` User @ Step N: "…"`,**任何层级 never-evict**(含 era ③ 溢出标记与
|
||
L3 最小索引,单测覆盖)。为此 `_record_turn` 把 `state.injected_instruction`
|
||
盖章进步记录 extra_metadata(execution_check 读集补声明,documented
|
||
decision)。差异:多动作步仍一行,首动作 + `(+N more actions)` 后缀。
|
||
- **①+② StepCapsuleLens**(`memory/chunking.py` + 提示词
|
||
`memory/step_capsule.md`,模型 `agent.memory.chunking.model`):单次调用
|
||
产出 JSON(doing/did/effect 三问式——effect 硬性要求含段内 notes 写入的
|
||
目标文件与要点,输入里逐步附 save/update/append_note 的 key+content 摘录、
|
||
task_plan 写入除外——+ verified_facts/unresolved/failed_paths/
|
||
important_entities/entry·exit_state + intervals)。② 区间并集
|
||
`validate_interval_coverage` 机械校验(缺口/重叠/越界即该次尝试判负→
|
||
StepMemoryService 有界重试→耗尽 failed→chunk 维持 pending,③ 独立可用,
|
||
单测覆盖)。中立契约禁词表沿 flash_summarizer.md/summarizer.md 迁入,
|
||
`[Loop]` 循环不算异常的判别一并迁入 lens 提示词。**lens 形式化接口收口
|
||
(M1 差异点 b)**:`step_memory.StepLens`(`render(key, payload)`)+
|
||
StepMemoryService 可选 `lens` 参数与默认 `_attempt` 委托 + `_on_ready`
|
||
钩子;差异:VisualStepSummarizer 保留内联 `_attempt`(零迁移),未强迁。
|
||
- **异步纪律**:capsule 经 `ChunkCapsuleService`(StepMemoryService 子类,
|
||
memory.runtime 三值)后台 dispatch;压缩事件时刻未就绪→冻结 ③ + pending
|
||
标记;就绪结果**只在下一次压缩事件**收割(镜像+DB ready 版本写均在收割
|
||
时;ctx.__aexit__ 增 chunker.flush 兜底收割落库,无 F 区重渲染)——冻结
|
||
块事件间字节稳定有哈希单测。
|
||
3. **触发接线**:里程碑唯一事实源 = `_record_turn` 记步后通知
|
||
`on_step_stamped(step_id, subgoal_hash)`,chunker 内比较相邻盖章;
|
||
`_process_plan_write` 的 `new_top_level_completions` → `queue_boundary_hint`
|
||
(未确认边界),下一盖章步 hash 未变即丢弃(棘轮否决回滚的伪切换单测覆盖;
|
||
注:执行图在 `_record_turn` 前恒 await planner 验证,盖章天然后于回滚落定)。
|
||
尺寸阈值 = 开放段回合数 ≥ `chunking.max_steps` 或段内转写文本 char/4 ≥
|
||
`chunking.target_source_tokens`(差异:源 token 估算用账本内该段消息文本,
|
||
非"段内摘要"——转写擦洗后消息主体即摘要,作机械代理);软阈值 =
|
||
SessionTokenMeter 的 last_prompt_tokens ≥ budget×soft_ratio 时压最旧开放段
|
||
(≤max_steps 回合;测试经 `meter_getter` 注入假计量)。**压缩事件在
|
||
ledger.render 时执行**(operator 单线程路径;graph 事件只置状态):底层
|
||
`_active` 列表保持 append-only(`_active_start` 边界前移,ScrubEdge 索引
|
||
恒有效),边界永在完整回合末(不劈 tool-call/response 对,单测覆盖);
|
||
加严:闭合段须**整段**滑出 `min_active_steps` 保护窗才压缩——底线只延迟
|
||
事件、不切分段(忠于"上一段压成一个 HistoryChunk"),仅尺寸/软阈值可对
|
||
开放段出窗部分分片(每片 ≤ max_steps)。
|
||
4. **L3 紧急快照**:last_prompt_tokens ≥ budget×hard_ratio 时 F 区渲染为单块
|
||
会话快照(overall_goal 取 operator 构造时 initial_goal / plan_state 读
|
||
task_plan 原文 / verified·unresolved·failed_paths·entities 结构化字段机械
|
||
集合并 / device_app_state 取最新有 capsule chunk 的 exit_state)+ **每
|
||
chunk 最小行宽逐步索引**(`build_action_ledger(minimal=True)`:步号+T+时间+
|
||
动作短语,user 行保留)。差异:计划"一次 LLM 润色可选"未实现(纯机械合并,
|
||
避免摘要的摘要);recent_actions 未入快照(A 区底线本就保留最近原始回合)。
|
||
5. **era 合并**:F 区完整 chunk 数超 `chunking.max_chunks`(新键,默认 8)时
|
||
最旧溢出段折叠成 era(① 结构化字段集合并非散文再摘要、② 退化为
|
||
`- Steps a–b: <里程碑标题|did 首句>` 标题行、③ 逐段 1:1 保留);era 数再超
|
||
同一限值时最旧 era 的 ③ 溢出为
|
||
`[Era N | Steps a–b: ledger via recall_history]` 标记行(user 注入行仍逐字
|
||
保留)。**§10 决策 5 按建议实施,待用户终审**(终审后标记行已升级为时段
|
||
段落,见本节末"决策 5 终审收尾")。差异:每次折叠事件生成一个
|
||
era(未做跨事件 era 再合并);era 上限复用 max_chunks 同值。
|
||
6. **异步检查点回果**:`checkpoints.harvest_run` 在 `_record_verdicts` 后
|
||
best-effort 通知 `annotate_from_checkpoint(checkpoint_id, verdicts)`——按
|
||
subgoal hash 经 `get_all_subgoal_aliases` 别名链匹配 chunk 标签(重命名
|
||
韧性单测),命中即追加 band1.annotations + **version bump + DB 即时追加**,
|
||
F 区文本下一压缩事件才重渲染(单测覆盖)。
|
||
7. **配置**:新块 `agent.memory.chunking`{max_steps=12, target_source_tokens=
|
||
2000, model="gemini-3.7-flash", max_chunks=8};transcript 增
|
||
context_budget_tokens=80000 / soft_ratio=0.7 / hard_ratio=0.9 /
|
||
min_active_steps=5。真机基线报告(history-baseline-2026-09-01.md)截至实施
|
||
时不存在,全部维持计划临时默认,**待定标**。
|
||
8. **Flash 侧**:chunker 只在 Pro 转写路径的 TranscriptLedger 上构造
|
||
(operator `_ensure_transcript_ledger`,有 DataEngine 才挂);Flash 尚无
|
||
ledger,M3 对 Flash 零行为变化。chunker 对里程碑事件是可选输入,尺寸/软硬
|
||
阈值纯凭 ledger 回合元数据触发——Flash 未来接 ledger 时自动呈现"只有尺寸
|
||
阈值触发"的任务书形态。
|
||
9. **测试**:+29 净增——`tests/unit/memory/test_history_chunking.py` 25
|
||
(③ 结构 golden 与 FA 子行/注入逐字/最小行宽可寻址、② 覆盖校验与缺口
|
||
重生成与有界重试降级 pending、三段顺序 golden(对照 §3.3)、真切换整段
|
||
成 chunk、伪切换回滚丢弃、尺寸两式、软/硬假计量注入、min_active 底线、
|
||
F/A 边界不劈对、冻结字节稳定与就绪不即时回填、回果 version bump 与下次
|
||
事件重渲染、重命名别名链、era 合并 ③ 保留、era 溢出标记与 never-evict、
|
||
全层级步可寻址不变量、lens 接口);
|
||
`tests/unit/data_engine/test_history_chunk_storage.py` 4(roundtrip/版本
|
||
append-only 最新胜出/范围排序/会话隔离)。验证:tests/unit 全量
|
||
**1165 通过 3 跳过 0 失败**(M2 基线 1136+3,只增不减);tests/tools 子集
|
||
(operator/summarizer/validator/diagnoser)7 通过。
|
||
10. **未完成项(待真机/后续)**:验收三条真机项(>30 步 token 稳态、
|
||
`[Loop:continuous]` 100+ 步无堆叠试跑、capsule 质量盲评);辅助 agent
|
||
编译视图接 chunk digest(§10 决策 4 建议 M3 末——本次任务书未含,留 M4
|
||
与 recall 一起收口);阈值定标依赖真机基线报告。
|
||
|
||
**M4 — recall + 策略表收拢** ✅ 代码侧已实施 2026-09-01(阈值定标待真机)
|
||
- `recall_history` 工具 + 屏幕相似度提示;ContextPolicy 8 调用点逐点对拍收拢;配置收敛与旧键兼容别名。
|
||
|
||
M4 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1+M2+M3
|
||
之上;与计划的差异逐条标注):
|
||
|
||
1. **recall_history 工具**(新 `artemis/tools/history_recall.py`,规格照姊妹文档
|
||
§10):签名 `(query, step_range=None, include_details=False,
|
||
include_images=False, max_results=5)`。第一期实现为 **Python 扫描而非
|
||
SQLite FTS5**(差异:现库无任何 FTS5/索引,且 `get_agent_friendly_steps`
|
||
已一次性给出全部检索面;FTS 留待量级需要时加)——检索面:步摘要/精确动作/
|
||
Validator 结果/Operator 思考/FA trace 与注入指令(步记录)、XML/OCR 文本
|
||
(`storage.get_image`,**扫描封顶最近 150 步**)、notes 文件(`utils/notes`)、
|
||
history_chunks(band2/band3/rendered_text)。包/Activity 检索经 XML blob
|
||
文本命中(差异标注:`record_step` 的 `foreground_app` 形参历史上从未被任何
|
||
调用方写入,包名不单独持久化)。边界全部落地:结果数钳到
|
||
`recall.max_results`;响应 char/4 估算超 `max_text_tokens` 即截断并提示收窄;
|
||
**每条结果带步号**(notes 锚定到最近写入步,无写入记录则"as of Step N");
|
||
include_details 每字段限幅;include_images 仅回捞**单步**真实存在的图
|
||
(data-URL 块,形态镜像 ObservationPromptComponent;文件不存在给文字说明,
|
||
永不多于 1 步)。**step_range 请求总是附带该范围的 `build_action_ledger`
|
||
全宽行**——era ③ 溢出标记行 `[Era N | Steps a–b: ledger via recall_history]`
|
||
由此获得真实入口。空 query 且无 step_range 拒绝执行(回用法提示)。
|
||
2. **注册(仅 Pro,Flash 不动)**:graph.py `operator_specialized_wrappers`
|
||
追加 wrapper;`DEFERRING_TOOLS` 加 recall_history(pre-decision 语义:与
|
||
Turn-Ending 动作同回合即拒绝动作);提示词 `_PRE_DECISION_MEMORY_TOOLS`
|
||
加名 + operator.json 在 TOOL USE PROTOCOL 第 1 类下加
|
||
`[% if "recall_history" in available_tools %]` gated 指导段(六触发:似曾
|
||
相识/旧精确值/两步无进展/第三次同一动作/摘要与观察冲突/用户要求回早前
|
||
状态;明示禁投机召回;标记行=显式入口)。可用性 = `ctx.data_engine` 存在
|
||
且 `memory.recall.enabled`——M2 的 SHA-256 golden 用 `data_engine=None`
|
||
的 ctx 录制,故 **golden 无需重录、原样通过**;受影响的是全集字面断言
|
||
(见要点 6)。manifest:`BACKEND_INDEPENDENT_TOOLS` 加 recall_history
|
||
(分类完整性测试强制)。
|
||
3. **屏幕相似度本地提示**(零模型调用、零历史图入 prompt):新
|
||
`artemis/utils/image_hash.py` 纯 PIL 64 位 dHash(JPEG draft 快解码,
|
||
哈希对重编码稳健故无需像素组件的 q75 对称重编码技巧);`record_step`
|
||
**同步**盖章 `pre/post_image_dhash` 入 extra_metadata(同步是为了避免与
|
||
后台写线程的并发字典突变;draft 解码使成本毫秒级;post==pre 时 post 哈希
|
||
取 pre 哈希——"无独立后图证据"但后屏即前屏)。新
|
||
`HistoricalStateHintPromptComponent` 插在 ScreenshotSimilarity 之后(legacy
|
||
与 transcript 两条组件清单都插):**近 3 步任一命中即整体静默**(该 regime
|
||
属像素级"卡在同屏"组件,两者永不同时触发);更老步扫描封顶 500 步
|
||
(含已 chunk 化段——`get_agent_friendly_steps` 全量),距离≤阈值取最近的
|
||
最小距离步,注入方案原文句式提示。
|
||
4. **ContextPolicy 策略表**(新 `artemis/memory/context_policy.py`):8 个
|
||
生产调用点(operator/operator_cold_start/failure_analyzer/planner/
|
||
outputter/history_analyzer/diagnoser/committee)收拢为每 agent 一条
|
||
`ContextPolicy` 声明,字段 1:1 映射 build_plan_and_history kwargs
|
||
(`uncompressed`=min_summaries=len(steps) 惯用法、`whitelist`=运行时传
|
||
keep_subgoal_hashes 两个便捷位);`build_history_for(agent, ...)` 为唯一
|
||
入口,operator 的 last_n_detailed 构造器旋钮保留为运行时覆盖。**逐点
|
||
golden 对拍**:`test_context_policy.py` 参数化 8 例逐字节断言 + "全表必有
|
||
golden"覆盖断言。`agent.memory.policies` 按 agent 覆盖字段(未知字段告警
|
||
忽略)。差异:第 9 个调用点 `mcp_server/tools/inspect_trace.py` 属离线
|
||
审计渲染而非 agent 编译视图,未入表(保持直调)。
|
||
5. **编译视图接 chunk(§10 决策 4 收口)**:`build_plan_and_history` 增可选
|
||
`chunks` 入参——形状无关 dict(步号范围 + 预渲染文本),chunk 块紧随
|
||
`--- Execution History ---` 按序输出,范围内步行被替换(detailed 窗口与
|
||
最新步永不吞);**chunks 为 None/空时输出逐字节不变**(golden 锁定)。
|
||
视图渲染在策略层:`full`(outputter/history_analyzer)= 持久化
|
||
rendered_text 原样(含 ③ 全宽行);`digest`(diagnoser/committee)=
|
||
rendered_text 在 "③ Step action ledger" 标题处截断 + recall 标记行
|
||
`③ Step action ledger: available via recall_history (Steps a–b)`。chunk
|
||
仅当调用点传 engine + transcript flag 开 + 有持久化行时进入;engine 仅从
|
||
四个辅助 agent 调用点传入,operator/planner/FA/冷启动永不接(冷启动策略
|
||
`chunk_view=None` 显式声明)。**行为增益(实施标注)**:diagnoser/
|
||
committee 的 strict/whitelist 悬崖在长任务中首次由段外 digest 块补偿——
|
||
chunk 块总是全量渲染,不受步级可见性策略过滤。差异:digest 从持久化
|
||
rendered_text 截断而非从 band1/band2 重渲染(零重复渲染逻辑;pending 态
|
||
chunk 自然退化为 pending 说明 + 标记行)。
|
||
6. **配置**:`agent.memory.recall`{enabled=true, max_results=5,
|
||
max_text_tokens=2000, max_image_steps=1}、transcript 增
|
||
`similarity_hint=true` / `similarity_max_distance=8`、`memory.policies={}`。
|
||
真机基线报告(history-baseline-2026-09-01.md)截至实施时仍不存在,相似度
|
||
距离阈值与 recall 预算默认值**维持临时、待定标**。旧键兼容别名本阶段无
|
||
新增需求(recall/similarity 均为全新键)。
|
||
7. **测试**(只增不减):+45 净增——`tests/unit/tools/test_history_recall.py`
|
||
13(关键词命中与步号不变量、step_range 过滤、范围回捞全宽行(era 入口)、
|
||
结果数钳制、token 截断、notes 检索与步锚定、chunk 行检索、图仅单步仅真实
|
||
文件、配置封零、缺图降级、空参拒绝、无引擎降级、可用性三态);
|
||
`tests/unit/utils/test_image_hash.py` 5(稳定性/JPEG 重编码小距离/异屏大
|
||
距离/垃圾输入 best-effort/距离边界);
|
||
`tests/unit/agents/test_historical_state_hint.py` 8(命中注入、近 3 步
|
||
静默、无命中/关开关/步数不足静默、平距取最近、无哈希跳过、record_step
|
||
盖章含 post==pre 镜像);`tests/unit/memory/test_context_policy.py` 16
|
||
(8 点 golden 参数化、全表覆盖、配置覆盖与未知 agent、flag 关字节不变、
|
||
full/digest 渲染、chunk_view=None 不接、最新步不被吞);
|
||
test_memory_config.py +2(recall/similarity/policies 默认与覆盖)。存量
|
||
断言同步 4 处:test_prompt_assembly 全集枚举字面与 removed-tool 参数化加
|
||
recall_history、test_operator_prompts 记忆工具枚举字面、
|
||
test_history_analyzer patch 目标改 build_history_for、action_manifest 分类
|
||
集。验证:tests/unit 全量 **1210 通过 3 跳过 0 失败**(M3 基线 1165+3,
|
||
只增不减);tests/tools 子集(operator/summarizer/validator/diagnoser)
|
||
7 通过。
|
||
8. **未完成项(待真机/后续)**:相似度距离阈值与 recall 预算定标依赖真机
|
||
基线报告;include_images 的图片块(ToolMessage 多模态内容)在 Gemini 工具
|
||
响应管线的端到端表现待真机验证;Flash 注册 recall 留待其 ledger 接 chunk
|
||
之后(方案既定);FTS5 索引留待检索量级需要时引入。
|
||
|
||
**硬化轮 — A/B 实测问题收口** ✅ 已实施 2026-09-01(worktree `zealous-gates-4626b1` 未提交,叠加在 M0-M4 之上;任务来源 = `history-baseline-2026-09-01.md` §5/§6 的移交项)
|
||
|
||
1. **M2 冷启动 bug 认领合入 + 回归测试**:A/B 会话的最小修复(`_build_prompt_transcript`
|
||
冷启动条件加 `not ledger.has_staged_turn`,operator.py,含注释)经核实形态正确
|
||
完整,本轮正式认领。`test_operator_transcript.py` 的两回合用例数据构造改为
|
||
真实形态——第 1 回合无步记录、第 2 回合 steps 非空且上一回合 staged 未
|
||
commit(原用例 `data_engine=None` 使 steps 恒空,正因此没抓到);负向验证
|
||
已做:临时移除修复条件该用例即失败。
|
||
2. **chunk ①② 段头全 pending 根因(已定论)+ 修复**:worktree
|
||
traces/data_engine.db 实证——dispatch 一环正常(21 个 chunk 全部 submit),
|
||
断环在 LLM:**capsule 尝试 41 次全部 "503 Service Unavailable"**,5 个 job
|
||
走到有界重试耗尽进 failed,其余 16 个卡在 `max_concurrency=1` 串行队列没轮
|
||
到尝试就随会话结束;"下一次压缩事件收割 + flush 兜底收割落库"链路本身无缺
|
||
陷,只是全程无就绪结果可收(21 行 DB 记录恒 version=1 pending 与此吻合)。
|
||
根因 = `chunking.model` 默认 gemini-3.7-flash 当日全天 503,而 capsule lens
|
||
走 `get_google_llm` 原始旁路,没有网关的分类重试/降级模型(同日 A/B 主模型
|
||
手工换 3.6、step lens 3.5-flash-lite 均健康,唯 capsule 绑死单一端点)。
|
||
修复:`StepCapsuleLens` 增降级模型链——`HistoryChunkManager` 从 llm 配置
|
||
summarizer 角色的 fallback(继承全局 default.fallback,现值
|
||
gemini-3.6-flash)解析同 provider(google)且异于主模型的降级模型,render
|
||
经 `services.llm.with_fallback` 分类切换(503/超时类才降级,bad request 不
|
||
掩盖进弱模型)。"短会话等不到下一次压缩事件"的假设不成立——flush 收割落库
|
||
M3 已实现,本轮补单测锁定(迟到就绪 → flush 收割 → DB ready v2 落库、冻结
|
||
文本不重渲染)。单测另覆盖:降级成功(主 503 → fallback 出 capsule)、无降
|
||
级时 503 耗尽 failed 且 chunk 维持 pending、降级模型解析三态(google-only、
|
||
不等于主模型)。
|
||
3. **视觉 lens 单图自适应(§5 修订版落地)**:新增
|
||
`flash_summarizer_single.md` 单图变体提示词——只描述决策帧内容与动作落点
|
||
(红标),**无 AFTER ACTION 段**,明示"缺后图仅表示无独立后验证据,严禁描
|
||
述/推测/合成动作后状态(转换叙事由相邻步摘要链接)",禁判定词表原样沿用;
|
||
`_attempt` 按"前后双图 → 原转换模板、其余(Pro 恒缺后图、任一图缺失、纯文
|
||
本)→ 单图变体"选择,单图块标签改为 DECISION FRAME(仅后图存在时亦有中性
|
||
标签)。Pro 无动作后抓屏的刻意安全网未动(不补抓、不用步 N+1 前图拼合)。
|
||
**回显校验兜底**:`degenerate_summary_reason`——输出含 "--- [" 段标记或以
|
||
"---" 开头、<15 字符、>1500 字符即判该次尝试失败,走既有有界重试,耗尽进
|
||
failed;双图模板补一句"输出永不含 --- 分隔符"降低无谓重试。单测覆盖单图/
|
||
双图模板选择与块标签、回显判负后重试恢复、echo/过短/过长三形态耗尽 failed。
|
||
4. **视觉 lens 计量接入(M0 遗留收口)**:`record_llm_usage`/
|
||
`SessionTokenMeter.record` 增 `update_last_prompt` 开关;视觉 lens 与
|
||
capsule lens 的原始旁路调用成功后各记一条 `llm_usage` trace(source
|
||
`lens:visual_transition:<model>` / `lens:step_capsule:<model>`),一律
|
||
`update_last_prompt=False`——lens 小 prompt 不得覆写 chunker 软/硬阈值消费
|
||
的 `last_prompt_tokens` 上下文基数;网关包装模型(RobustChatModelWrapper)
|
||
已在出口自计量,旁路计量以 isinstance 判别避免重复。M0 实施要点第 3 条
|
||
"raw 旁路仅测试使用"的表述至此与现状一致(旁路仍在,但已计量)。
|
||
5. **定标落地**:`memory.runtime.max_concurrency` 默认 1→2(依据基线报告
|
||
§6:串行队列使摘要就绪 P90 长尾超擦洗窗口 K=3+宽限;lens 是独立轻模型调
|
||
用,并发 2 风险低),Field description 已标注依据与出处;
|
||
`similarity_max_distance=8` 维持待定标(基线未产生相似屏配对数据)。
|
||
|
||
6. **就绪门控交换(§3.3 修订落地;任务中途经同伴会话转达用户拍板补入本轮)**:
|
||
HistoryChunkManager 事件流重构为"关段 ≠ 交换"——触发器(里程碑/尺寸/软阈
|
||
值)只**关段**:建 chunk 镜像、落 v1 pending DB 行(recall/辅助视图可见)、
|
||
dispatch 段头生成,原始回合原样留在转写 A 区(`_awaiting` 队列,lossless
|
||
pending 从图片层贯彻到段层);**交换(freeze_turns + 渲染 chunk 块)发生在
|
||
段头就绪后的首次渲染**,且只交换就绪的连续前缀(交换消耗最旧未压回合,老
|
||
段 pending 时后继段一并保留原文)。降级梯照 §3.3:生成失败(有界重试耗尽)
|
||
→ 原文保留 + 重投(lens 每次尝试自带 fallback 模型,见第 2 项;重投节奏限
|
||
触发/软硬压力渲染,不逐渲染,避免重引入无界 LLM 重试);软阈值压力下仍未
|
||
就绪 → 依旧保留原文(正确性优先于 token);**仅硬阈值**强制交换全部已关段
|
||
(pending 一并入 L3 快照,③ 最小索引存活)。checker post-hoc 标注独立于段
|
||
头(awaiting 态即刻 DB version bump),冻结文本仍只在交换事件重渲染(事件
|
||
间字节稳定)。无步记录的关段(chunk 缺失)永不常规交换,仅硬阈值随队清
|
||
出。CHUNK_PENDING_NOTE 增 recall_history 指引。A/B 的"21 chunk 段头全
|
||
pending"在门控下退化为"尚未压缩"的安全形态。单测:里程碑用例改为"关段不
|
||
交换 → 就绪后下次渲染交换"全流程、冻结区事件间字节稳定重写为门控形态、
|
||
软压力不交换、失败重投且原文保留、硬阈值强制入 L3 等新用例;触发器/era/
|
||
L3/寻址性用例改用 auto-resolve capsule 桩保持原有触发语义断言。
|
||
7. **段头笔记关联升级为机械校验(同伴会话转达用户拍板补入)**:
|
||
`parse_capsule` 在 ② 区间覆盖校验之后增加笔记覆盖校验——payload 中段内
|
||
note_writes 的去重 key 必须以子串出现在 band① 任一文本字段(doing/did/
|
||
effect/entry/exit/四个结构化列表),缺任何一个该次尝试判负返回 None 走既
|
||
有有界重试,耗尽维持 pending(配合门控:不交换、原文保留);
|
||
step_capsule.md 明示该硬约束(machine-checked)。单测:双笔记段漏一 key
|
||
判负、补全通过、无笔记段不受影响。
|
||
|
||
验证:tests/unit 全量 **1224 通过 3 跳过 0 失败**(M4 基线 1210+3,只增不
|
||
减,净增 14);tests/tools 子集(operator/summarizer/validator/diagnoser)
|
||
7 通过。灰度前置 ①(§10 决策 3 / 基线报告 §7-3)至此满足;前置 ②(修复后
|
||
复测一轮 A/B)仍待真机,且复测轮应同时验收门控形态(段头就绪率、交换延
|
||
迟、A 区稳态尺寸)。
|
||
|
||
**M5 — 缓存深化(可选)** ✅ 终轮已实施 2026-09-01(worktree `zealous-gates-4626b1` 未提交,叠加在 M0-M4+硬化轮之上)
|
||
- S 区显式 CachedContent(explorer 先例外推);`short_term_memory` 移除(姊妹文档 §3.3,转写落地后其职责已被真多轮取代);供应商 stateful API 评估。
|
||
|
||
M5 实施要点(2026-09-01;与计划的差异逐条标注):
|
||
|
||
1. **默认启用(用户拍板)**:`agent.memory.transcript.enabled` 默认 false→true
|
||
(`config/agent.py` 默认值 + `config/artemis.jsonc` 新增完整 `agent.memory`
|
||
块同步——jsonc 此前根本没有 memory 块;L2/L3 chunking 挂同一 flag 随同默认
|
||
开)。**旧路径与转写构建异常自动回退逻辑全部保留不删——`enabled: false`
|
||
即回滚开关**,置 false 逐字节回旧 2-message 行为(Field description 与
|
||
jsonc 注释均已标注,含 A/B 依据摘要)。测试纪律:凡"flag 关"语义的既有
|
||
测试改为显式传 false(`test_operator.py` 全部 21 处构造加
|
||
`transcript_config=LEGACY_TRANSCRIPT`;`test_memory_config.py` 默认断言改
|
||
true、显式 false 改为"回滚开关"用例),"flag 开"路径成为默认被测面。涟漪
|
||
仅一处:`test_outputter` 的 Mock engine 需补 `get_history_chunks=[]`(辅助
|
||
视图默认开始咨询 chunk 存储,空 chunk 时输出逐字节不变的 M4 语义未变)。
|
||
2. **short_term_memory 全链移除(姊妹文档 §3.3 落地)**:按 visibility 附录 B
|
||
台账纪律先核销全部消费者再删——State 字段(state.py)、operator 提取/写块
|
||
(operator.py 返回 dict 与 `<short_term_memory>` 正则提取)、
|
||
`ShortTermMemoryPromptComponent`(prompts.py 类 + operator.py 两条组件清单
|
||
引用)、visibility manifest operator 读/写集、operator.json 的
|
||
Short-Term Memory 指令段(含示例块,模板不再教模型产出该标签)。
|
||
sdk `_get_graph_state` 经核实已是 `State.initial()` 单源(Phase 0/1 收拢),
|
||
字段删除即覆盖。**两处防御性清洗有意保留**:`task_tree.py` 渲染侧的
|
||
`<short_term_memory>` 标签剥除与 showcase_ui markdown-parser 的标签过滤——
|
||
历史 DB 中旧会话的思考原文仍含该标签,姊妹文档 §3.3 原文要求
|
||
"safely ignoring it in older responses"。
|
||
3. **C 清尾(M0-M4 标注遗留小项逐项处置)**:
|
||
|
||
| # | 遗留项 | 处置 |
|
||
|---|---|---|
|
||
| C1 | 旧 `compress_flash_messages`(M1 标注"M2 落地后再删") | **已删**。语义对拍测试改造:`test_flash_scrub_edge.py` 两处 legacy 参照改为对 ScrubEdge 产物的直接字面断言;`test_flash_step_summarizer.py` 两例转为 ScrubEdge(`image_scrub_depth=1` 复现 legacy 时点,ordinal 回退键控保留覆盖),三例存档删除(pending 回退/迟到回填为 ScrubEdge 明令禁止的旧语义,前缀保留已由 scrub_edge 用例覆盖) |
|
||
| C2 | `record_step` 的 `foreground_app` 形参从未落库(M4 发现) | **已做**。显式形参优先落 `extra_metadata["foreground_app"]`;形参缺席时从 ui_tree 的 package 属性纯字符串推导(最频繁非 systemui 包,`_derive_foreground_app`,零设备调用零同步开销);recall `_step_haystack` 检索面接入——包名查询不再仅依赖封顶 150 步的 XML blob 扫描。测试 +4 |
|
||
| C3 | click_sequence/swipe 多点动作语义充实(M0 差异) | **已做**。`_target_semantics` 扩展:click_sequence 逐点命中测试(`points_semantics` 列表)、swipe 打 start/end 两点(`start/end_semantics`),**首点语义提升为主 `target_*` 标签**;全程 best-effort 永不抛。测试 +2(原"swipe 跳过充实"断言按新行为改写) |
|
||
| C4 | era 上限复用 max_chunks 同值(M3 差异) | **已做**。新键 `chunking.max_eras`(默认 None=随 max_chunks,保 M3 等值行为);chunker `_fold_eras` 的 era 溢出改用独立上限。测试 +2(宽松 era 上限不溢出/紧上限提前溢出、config 默认与覆盖) |
|
||
| C5 | `similarity_max_distance=8` 待定标(M4) | **已定标 8→5**。worktree traces/data_engine.db 有 460 步 dHash 盖章(16 会话):同屏重拍(同 SHA 相邻/wait 步/同 SHA 远距重访)距离集中 ≤4;相邻步分布双峰、谷在 5-6;跨应用会话对(T1 Settings × T2 时钟,保证异屏,n=11,770)≤5 仅 0.14%(阈 8 时 0.24%,假阳翻倍)。取谷值 5:同屏召回不损、异屏假阳减半。定标证据写入 Field description |
|
||
| C6 | M2 golden(模板拆分前 SHA-256) | **已按新现状重录一次**:operator.json 移除 Short-Term Memory 指令段(本轮 B 项唯一有意模板变更)使旧哈希失效;重录 EMPTY/SENTINEL 双哈希并在测试注释注明重录原因与时间。golden 的"任何其他漂移即 flag-off 路径不再逐字节"约束继续有效 |
|
||
|
||
4. **D1 — S 区显式 CachedContent:评估后决定不实现**。依据:A/B 实测隐式缓存
|
||
已达 88% 调用命中/49.6% token 命中(基线 43%/29.8%),S 区字节稳定 + 转写
|
||
append-only 正是隐式缓存的理想形态,边际收益只剩"稳态间隔太长导致隐式条目
|
||
过期"的场景;而显式 CachedContent 要求最低 4,096 token 起步、按 token-hour
|
||
计存储费、需要显式生命周期管理(创建/续期/删除),并把 S 区绑定到单一模型
|
||
版本(fallback 切模型即缓存失效)。当前 Operator 步间隔 P50 8.8s 远短于隐式
|
||
条目寿命,付费显式缓存买不到增量命中。**重启条件**(写给未来):若出现
|
||
①步间隔常态 >5min 的长轮询任务(`[Loop:continuous]` 大 Interval)实测隐式
|
||
命中率显著回落,或 ②S 区膨胀(如工具 schema 大幅增长)使单次 re-prefill
|
||
成本可观,则按 explorer.py:1709 先例实现,config 开关默认关。
|
||
5. **D2 — Gemini stateful/Interactions API 评估备忘**:见新增 §11(只评估不
|
||
实现)。
|
||
6. **归档**:状态行更新为已实施;§10 决策逐条标注(决策 5 维持"已按建议实施、
|
||
待用户终审");两份姊妹文档头部注记补最终状态指针。
|
||
|
||
验证:tests/unit 全量 **1229 通过 3 跳过 0 失败**(硬化轮基线 1224+3,只增
|
||
不减,净增 5:+8 新增 −3 存档删除);tests/tools 子集(operator/summarizer/
|
||
validator/diagnoser)7 通过。
|
||
|
||
**最终残留(2026-09-01 终轮验收后更新)**:~~recall include_images 真机端到
|
||
端;最终验收轮真机三件套(>30 步 token 稳态、`[Loop:continuous]` 100+ 步、
|
||
capsule 盲评——此轮同时验收就绪门控形态与默认开后的复测 A/B)~~ **已全部
|
||
完成并通过**(2026-09-01 最终真机验收轮,五项全过:4 对同模型 A/B 回退 0/
|
||
就绪率 60/60/延迟−60%/缓存 40%→86%;112 步 Loop 无堆叠、token 有界 33% 预
|
||
算、注入逐字保全;48 chunk 盲评禁词违规 1 例且语义无害;recall 图片块 3 次
|
||
Gemini 调用全 success;相似度提示正例+静默负例均验证——详见
|
||
`docs/plans/history-acceptance-2026-09-01.md`)。仍留:git 提交/PR(用户决
|
||
定,全部改动仍在 worktree `zealous-gates-4626b1` 未提交);Flash 接
|
||
TranscriptLedger/chunk(文档既定后续项,Flash 侧 transcript.enabled 无效果、
|
||
零行为变化);两项不阻塞优化候选([Loop] 里程碑内相似度提示静默、
|
||
gemini-3.7-flash 供应商稳定性观察,验收报告 §6-a/e)。
|
||
|
||
**决策 5 终审收尾** ✅ 已实施 2026-09-01(用户终审 2026-09-01 定案,worktree
|
||
`zealous-gates-4626b1` 未提交;叠加在 M0–M5 之上):
|
||
|
||
1. **形态变化**:era 溢出的极限层从 M3 的"纯标记行"
|
||
`[Era N | Steps a–b: ledger via recall_history]` 升级为 §3.3 终审定案的
|
||
**时段段落**——
|
||
`[Era N | Steps a–b | T+hh:mm → T+hh:mm] <一段话概括>` + 缩进 recall 指引行
|
||
`(Step-level ledger via recall_history for steps a–b)` + user 注入行逐字保留
|
||
(never-evict 不随丢步豁免)。起止时间取该 era 首 chunk `start_offset` 与末
|
||
chunk `end_offset`(既有 T+ 会话偏移格式),是视频对齐的粗粒度外键。
|
||
2. **段落机械拼装,零新增 LLM 调用**(`chunking.render_era_period_paragraph`):
|
||
doing 取各 chunk ① 首个非空;did 逐 chunk 取首句串联(band1 缺失/pending 的
|
||
chunk 回退用 milestone_label,无标签再退 `steps a–b`);effect 中笔记引用
|
||
(`notes/<key>` 路径与 `*.md` 文件名正则)合并去重;verified_facts 集合并后
|
||
取前 4 条。全部 chunk 无 band1 时段落退化为"里程碑标签列表 + 步范围"——
|
||
**永不为空**(新增单测覆盖两级回退)。
|
||
3. **代码位置**:`artemis/memory/chunking.py`(`RECALL_MARKER_TEMPLATE` 更名
|
||
`RECALL_GUIDANCE_TEMPLATE` 并改为缩进指引行;`render_era_block` recall_only
|
||
分支重写;新增 `render_era_period_paragraph`/`_first_sentence`);recall 端
|
||
无解析耦合(`recall_history` 的 step_range 入口本就按步号范围回捞,与标记
|
||
行字面无关),仅同步了 `artemis/tools/history_recall.py` 文档字符串与
|
||
`artemis/agents/operator/operator.json` 提示词中对入口行形态的描述。
|
||
4. **不变量修订落地**(§8 同步):"全层级步可寻址"单测改为 L2 chunk/era 合并/
|
||
L3 快照层逐步可寻址 + **极限段落层时段可寻址**(正则断言 `Steps a–b` 与
|
||
`T+..→T+..` 在场)。era 溢出用例改断言新形态(时间范围在场、段落非空、
|
||
doing/笔记引用/verified 进段落、指引行与 user 行仍在)。
|
||
5. **验证**:tests/unit 全量 **1231 通过 3 跳过 0 失败**(M5 终轮基线 1229+3,
|
||
只增不减,净增 2:时段段落逐 chunk 回退 + 全缺失退化两用例)。
|
||
|
||
每阶段独立可回滚;M2 起所有新行为在 flag 后面,关 flag 即回现状(M5 后 flag 出厂开,关 flag 语义不变)。
|
||
|
||
---
|
||
|
||
## 8. 验收红线(继承并加严)
|
||
|
||
> **终轮判定(2026-09-01):六条红线全部 PASS**("速度/质量"两条因样本量为方向性判定,"对拍"引用 M5 单测 golden)——逐条依据见 `docs/plans/history-acceptance-2026-09-01.md` §7。
|
||
|
||
- **不损伤**:压缩产物一律可经 step 引用回捞原文;chunk/快照生成失败时源摘要与 detailed 渲染回退可用(现 `task_tree.py:920` 语义保留为最后防线)。
|
||
- **步骨架不变量**:L2 chunk、era 合并、L3 快照层级后,转写中步 1:1 可寻址——每步的步号与相对时间可直接读出,无合并、无丢步、无重排;视频分析模块按步号/时间戳的对齐在压缩前后逐步一致(新增回归测试)。**唯一例外:极限时段段落层**(决策 5 终审)——允许丢步,但段落必须带步号范围与起止时间(时段可寻址),用户注入行仍逐字保留,步级细节 recall 可回捞。
|
||
- **速度**:关键路径零摘要等待、零新增同步调用;延迟回归 ≤5%(30 对任务、置信区间)。
|
||
- **缓存**:M1/M2 各自报告命中率提升(这是本方案区别于现状的核心收益,必须量化)。
|
||
- **质量**:重复动作率、历史致回滚率不升;跨计划改写的关键事实保留率上升;摘要/Validator 冲突趋零。
|
||
- **对拍**:辅助 agent 编译视图收拢前后逐字节一致;Flash M1 语义等价对拍。
|
||
|
||
---
|
||
|
||
## 9. 风险与回退
|
||
|
||
| 风险 | 缓解 |
|
||
|---|---|
|
||
| 转写路径引入回归(最大风险面) | 双路径并存 + flag;A/B 对拍先行;State/图结构不动 |
|
||
| Gemini 隐式缓存命中不及预期(条目短寿命、前缀下限) | M0 先测真实命中率;不及预期则 M5 显式缓存提前;方案收益不全押缓存(真多轮/签名连续性独立成立) |
|
||
| chunk 质量差导致远期决策劣化 | 结构化字段合并而非散文再摘要;recall 兜底;盲评(姊妹文档 §17 三方对比法沿用) |
|
||
| flash-lite 摘要质量上限 | lens 模型可配;闲时用 3.7 重摘(姊妹文档 §8.3 隔离模式保留) |
|
||
| 无界任务 chunk 无限增长 | era 二级合并 + 预算封顶 + L3 快照 |
|
||
| 转写崩溃丢失 | DataEngine 为事实源,冷启动构造器重建 F 区(A 区丢失退化为冷启动,可接受) |
|
||
| 裸坐标动作账本无语义("click 82,32 看不出在点什么") | M0 记录时命中测试充实 `target_text`(§6.1);`target_label_source` 区分指名与推断 |
|
||
| 异步检查点结果在段 chunk 化之后才回果,① 的 verified/unresolved 过时 | chunk 版本化 + checkpoint harvest 对命中步范围的 chunk 追加 post-hoc 标注并 bump version;F 区文本在下一次压缩事件时才重渲染(不做即时深突变) |
|
||
| 一步内 FA 恢复动作视频轴有帧、账本无行 | ③ 缩进子行机械渲染 `_exec_*`(数据已在 interleaved_events) |
|
||
| 段内用户注入指令被压缩叙事吞掉 | ③ 逐字保全行 + never-evict(§3.3 账本细则) |
|
||
| 回合内工具循环(≤20 次迭代)在两次计量之间撑爆上下文 | 16K 单工具信封 + 回合内累计估算越硬阈值时强制决策(姊妹文档 §13 语义接入 L3 触发检查) |
|
||
| 计划写入被否决回滚造成伪里程碑切换 | 触发事实源锁定为已执行步骤盖章变化;计划事件只入队待确认(§3.3) |
|
||
| chunk 化删除携带思考签名的旧 AIMessage 影响后续轮 | 压缩点永远在完整回合边界(不劈 tool-call/response 对);M2 真机验证项 |
|
||
| **今日 Pro 的 SystemMessage 本身含易变历史**——模板在 `# CURRENT OBSERVATION` 处切分,part 0(含 `## Current Plan & Execution History`)进系统消息(prompts.py:218-222);不拆模板则 S 区无从稳定,缓存收益归零 | M2 前置子任务:operator.json 模板拆分——静态规则/计划语法留 S 区,plan+history 移出系统消息(姊妹文档 §12 同一要求,在此升为 M2 硬依赖) |
|
||
| 转写内时间戳字节不稳定:现 `get_relative_time` 是 "x min ago" 措辞,冻结后即变陈旧谎言,重算则破字节稳定 | ③ 账本一律用会话起点偏移 `T+mm:ss`(冻结后永真且字节稳定);"ago" 措辞仅限辅助 agent 的每次重编译视图 |
|
||
| 极端规模下 ③ 与有限预算的数学冲突:`[Loop:continuous]` 跑到数千步时,即便最小行宽的逐步账本也会耗尽预算 | 开放决策 §10:最老 era 的 ③ 溢出到 recall-only(转写留 `Steps 1–500: ledger via recall_history` 标记行),或预算随任务放宽——需用户定溢出规则 |
|
||
| 模型路由 fallback 切换后 prompt_tokens 分词口径不一致 | 计量当近似值用,软/硬阈值留 10%+ 余量;切换事件记入计量日志 |
|
||
|
||
---
|
||
|
||
## 10. 开放决策(待用户拍板)
|
||
|
||
1. ~~**步级单 lens 修订**(§5)~~ **已拍板(2026-08-31):采纳**。步级仅保留 `VisualTransitionLens`(双 profile 共用),Pro 步级胶囊上移为 chunk 级 `StepCapsuleLens`。原否决备选(双摘要并存、`update_step_summary` 加 lens 维度、成本 ×2)作废。
|
||
2. ~~**默认值定标**~~ **已决(2026-09-01)**:基线报告 §6 定标——`context_budget_tokens=80K`/软硬阈值/K=3/min_active=5/chunk≤12 全部维持(实测远未触及,触发行为验收留 `[Loop:continuous]` 真机轮);`max_concurrency` 1→2(硬化轮);`similarity_max_distance` 8→5(M5 依 460 步 dHash 分布定标,见 §7 M5 要点 3-C5)。
|
||
3. ~~**M2 灰度顺序**~~ **已决(2026-09-01)**:A/B 红线全过且大幅改善后,用户拍板 M5 将 `transcript.enabled` 提为默认开;`enabled:false` 保留为逐字节回滚开关。基线报告 §7-3 的两个前置中 ①(冷启动修复认领+回归测试)已在硬化轮满足,②(修复后复测轮)并入最终验收真机轮。
|
||
4. ~~**辅助 agent 视图升级时点**~~ **已决**:M4 收口(编译视图接 chunk digest,full/digest 双渲染,见 §7 M4 要点 5)。
|
||
5. **极端规模的 ③ 溢出规则**:~~最老 era 的 ③ 溢出到 recall-only 标记行~~ **已终审定案(用户 2026-09-01,修订原实施)**:极限层允许丢步——最老 era 压缩为"时段段落"(一段话概括 + **步号范围与起止时间必须在场** + recall 指引 + user 注入行逐字保留),段落机械拼装自 era 各 chunk ① 字段。见 §3.3 极限层小节;M3 原"纯标记行"实施按此升级(终审收尾轮落地)。
|
||
|
||
---
|
||
|
||
## 11. 供应商 stateful API 评估备忘(M5,只评估不实现)
|
||
|
||
对象:Gemini **Interactions API**(2026 年起 Google 推荐的新接口面,与本仓现用的
|
||
`generateContent` 并行提供)。其与本架构直接相关的三个特性(官方文档核实于
|
||
2026-09-01):
|
||
|
||
- **服务端会话状态**:`store: true`(默认开)+ 后续请求带
|
||
`previous_interaction_id` 即可让服务端续接完整对话历史,客户端无需重发消息;
|
||
付费层交互对象保留 55 天(免费层 1 天),可配置 7/14/28/55 天窗口,可显式删除。
|
||
- **思考签名服务端化**:stateful 模式下 thought signature(Gemini 3 起的加密思考
|
||
连续性凭据)由服务端随会话状态自动管理,客户端完全无需回传/维护——本方案
|
||
§3.2 的"思维签名政策"(回合内强制、A 区顺带、压缩即弃)在该模式下整体消失为
|
||
非问题。
|
||
- **显式缓存不支持**:Interactions API 只有隐式缓存;显式 `CachedContent` 仅
|
||
`generateContent` 提供。若未来重启 D1(显式缓存),则与迁移 Interactions API
|
||
互斥,需二选一。
|
||
|
||
### 11.1 与本架构的相容性评估
|
||
|
||
| 维度 | 现架构(M0-M5 转写) | Interactions API stateful | 评估 |
|
||
|---|---|---|---|
|
||
| 历史表示 | 客户端 TranscriptLedger 四区,字节级自主控制 | 服务端黑盒累积,客户端只发新尾部 | **根本冲突**:擦洗沿(深度 K 图→摘要替换)、L2 chunk 化、L3 快照全部依赖对历史消息的**受控深突变**——服务端状态是 append-only 黑盒,无法就地替换第 i 条消息的图片块,也无法把 12 步折叠成一个 chunk 块。要保留分层压缩就必须在压缩事件时**放弃续接、整段重建新会话**(丢 `previous_interaction_id` 换新 id),届时"免重发历史"的收益只在压缩事件之间存在 |
|
||
| 缓存 | 隐式缓存实测 88% 调用命中(A/B) | 隐式缓存由服务端在续接时自动优化 | 收益近似打平:本架构的 S 区稳定 + append-only 已把隐式缓存吃到接近上限,服务端续接省的是**上行带宽与序列化**,不是缓存未命中 |
|
||
| 思考签名 | A 区字节不动即免费有效,压缩即弃(§3.2) | 服务端全托管 | Interactions 略优,但现政策实测零签名错误(A/B 红线),痛点不存在 |
|
||
| 可逆性/审计 | DataEngine 全量在案,recall 可回捞 | 服务端 55 天留存,之后自动删除 | 现架构必须保留 DataEngine 事实源不动摇;服务端状态只能作缓存层,不可作事实源 |
|
||
| 生态 | LangChain `langchain-google-genai`(generateContent 系)+ 本仓网关(分类重试/fallback 模型链) | 需直连新 API 面;fallback 切模型时服务端会话是否可跨模型续接未验证 | 迁移面 = LLM 网关全重写 + fallback 语义重新设计,改动半径大 |
|
||
| 隐私/合规 | 请求即用即弃(模型侧) | 默认 `store: true`,设备操作史(截图、输入文本)在 Google 侧留存最长 55 天 | 移动 UI 自动化的截图流含敏感界面,默认服务端留存需要显式合规评估;`store: false` 则失去 stateful 收益,自相矛盾 |
|
||
|
||
### 11.2 结论
|
||
|
||
**不迁移,不部分采用(2026-09-01)。** 三条硬理由:① 分层压缩(本方案的核心
|
||
价值:token 稳态有界 + 步骨架不变量)与服务端黑盒状态根本不相容,保它就得在每
|
||
次压缩事件重建会话,收益归零;② 现架构在缓存命中与签名连续性上的实测表现已接
|
||
近 Interactions 能给的上限(88% 命中、零签名错误),痛点侧无增量;③ 截图流默
|
||
认服务端留存 55 天是移动自动化场景新增的合规面。
|
||
|
||
**重估触发条件**(写给未来):① Google 宣布 `generateContent` 弃用时间表(届时
|
||
迁移变为强制,方案是"压缩事件即会话重建"的混合形态,本备忘 §11.1 第一行即设计
|
||
起点);② Interactions API 补齐显式缓存与服务端历史编辑(可控修剪/替换)能力;
|
||
③ 出现"无压缩短会话"型新 profile(如 <15 步的 Flash 任务),可单独试点
|
||
stateful 模式免去消息重建。
|