Files
Artemis-Eapil/docs/plans/history-module-redesign.md
T

97 KiB
Raw Blame History

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 四区纪律(缓存友好的突变规则)

[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 定形:梗概 + 效果 + 压缩步摘要,底下逐步动作):

[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 运行时归属

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)

"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 模式免去消息重建。