97 KiB
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):
- 每步与 Flash 一致,仅做增量构建;后台用 Gemini 3.5 Flash-Lite 做图片压缩(视觉摘要替代截图)。
- 里程碑边界压缩:不再每步压缩,而是在切换里程碑时把上一段任务的历史压缩掉;保留现方案的滑动窗口底线(永远保留最近几步原始消息)。
- 上限兜底:长期不切换里程碑时,按最大上下文窗口限制触发压缩。
- (补充约束,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 提炼的六条通用原则
- 累积为常态,压缩靠阈值/边界稀疏触发,不逐步进行。
- 突变要么贴尾(浅、可预测),要么稀疏成批(深、与语义边界重合)。
- 摘要输出结构化 schema,不是自由散文;旧摘要折叠进新摘要,不堆叠。
- 最近 K 步/最近 30% 永远原样——所有系统都有生窗口下限。
- 压缩必须可逆:原文外置(磁盘/DataEngine),占位符携带回捞引用。
- 计量用真实值,阈值分软/硬两档,硬档换更激进的形态而非同一手段加量。
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:上一回合的 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 轮的图)。
- 深度 1:上一回合的 UI XML 列表、计划复诵副本剥除(现 Flash
- 压缩事件(稀疏、深):见 §3.3,F 区重写 + A 区前移。深突变与语义边界重合,此时接受一次全量 re-prefill(Claude/Codex 同款代价)。
- 思维签名政策(2026-09-01 定):回合内强制、A 区顺带、压缩即弃——Gemini thought signature 仅在当前回合函数调用周期内是 API 硬要求(原样回传);A 区 AIMessage 字节不动故签名免费有效(擦洗只碰观察/工具消息,从不碰 AIMessage);已完结回合的签名 API 层面可选,chunk 化整回合移除后自然消亡,不建任何跨压缩维护机制(签名绑定消息精确内容,语义上不可能脱离原消息存续)。真机 A/B 无签名相关错误佐证。
- 紧急快照(罕见):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 节,同一共识)。
- 动作必须带元素语义(用户约束,2026-09-01):
每步的完整视觉摘要不再逐条出现在 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_stepsbug、两个死参数、死常量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,工作区未提交;与上述计划的差异逐条标注):
- 摘要状态与版本化:
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,与旧行为逐字节一致)。调用方:FlashVisualStepSummarizerdispatch 时补写pending、成功写ready/source="visual_transition"/model、耗尽写failed;ProSummarizerNode写ready/source="step_capsule"/model。stale态本阶段只入枚举,无写入方 (M2 起由重摘/回滚使用)。 - 动作语义充实(record-time enrichment):新模块
artemis/utils/element_hit_test.py(find_element_at_point/hit_test_semantics:最小覆盖元素胜出,XML 源优先于 OCR 源,best-effort 永不 抛异常)。三条落点:① Pro 裸坐标分支(operator.pyresolve_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 位)。 - token 计量管线:新模块
artemis/services/token_meter.py(extract_usage统一 LangChainusage_metadata与 Gemini 原生cachedContentTokenCount/input_token_details.cache_read两种形态;SessionTokenMeter按会话累计)。采集点接在 LLM 网关RobustChatModelWrapper.complete()/ainvoke()出口(全部生产调用必经;raw 模型旁路仅测试使用,不采集),每次调用记一条llm_usagetrace(typellm_call):单次 prompt/completion/cached +context_base_tokens(=本次实测 prompt,即下一轮上下文基数)+ 会话累计五项(calls/prompt/completion/cached/ cache-hit calls)。纯记录,不驱动任何决策;采集失败静默降级永不进入调用路径。 - 三缺陷修复:① 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,全仓零引用确认)。 - Flash 摘要重试有界化(有意行为变更):
_run_summary_until_ready以1 + retry_limit次尝试封顶(StepSummarizerConfig.retry_limit,默认 3, FlashRunner 接线),耗尽进显式 failed 态(has_failed()+ DataEnginesummary_status="failed");failed 步在压缩器视角仍为 pending(保图, lossless 语义不变——变更只砍掉无限 LLM 重试,不改消息流);flush(timeout)收尾语义保留。 - 测试:新增
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 联网失败 未触发)。 未完成项:代表性长任务基线录制已完成(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 之上;与计划的差异逐条标注):
- StepMemoryService 成形:新
artemis/memory/step_memory.py——调度骨架自 FlashVisualStepSummarizer上提:零阻塞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 既定值。 - 擦洗沿压缩器:
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 转写落地后再删)。 - 配置:
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(保旧开关语义)。 - FlashRunner 接线:摘要器以
memory.runtime三值构造;压缩器为 per-run 实例(擦洗账本是运行态);flush()走配置超时。 - 测试:+20(
tests/unit/memory/test_step_memory_service.py7:零阻塞/有界 重试/别名键控/并发封顶/flush 超时与默认值/重投递清 failed;tests/unit/agents/test_flash_scrub_edge.py8:成功路径新旧对拍(时点后移 在断言中显式标注)、race a/b/c、冻结哈希不变量、XML 深度 1 等价、 prune 开关、非跟踪消息不触碰;tests/unit/test_memory_config.py5:默认值/ 显式值/旧键播种/新键优先/无显式旧键不播种)。存量适配 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 通过。 - max_turns 未动(默认 30):擦洗沿使图片上下文稳态有界,更大 max_turns 已安全,放宽属配置决定,留给使用方/后续里程碑。
- 未完成项:缓存命中率提升的量化(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 之上;与计划的差异逐条标注):
- 模板拆分(前置子任务)实现为运行时拆分: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结尾——该头衔移入每回合尾部观察消息。 - 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 对 (单测覆盖)。
- 每回合 T 尾部由 render 参数传入。A 区 append-only;回合落账两段式
stage→commit(step_id 与 Validator 结果在 execution_check/validator 之后
才存在,故本回合消息先 stage,下一回合 build 时带
- 擦洗沿复用=泛化 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 基类。 - 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)。 - 服务归属收口(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全绿)。 - 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" 语义(未动)。 - 冷启动构造器(最小版):转写空(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 区从当前步正常追加)。 - 测试(只增不减):+22 净增——
tests/unit/memory/test_transcript_ledger.py12(S 区哈希稳定/A 区 append-only/T+mm:ss 与禁 ago/深度 1 剥复诵与 UI 列表/ 深度 K ready 替换/宽限→占位符→冻结不回填/failed 立即占位/配对不劈/冷启动 块唯一性/双 stage 自动落账/无动作回合不落 Validator 消息);tests/unit/agents/test_operator_transcript.py7(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 通过。 未完成项(待真机)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 之后;与计划的差异逐条标注):
- 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),版本号无竞争。 - 三段式 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 区重渲染)——冻结 块事件间字节稳定有哈希单测。
- ③ 机械账本(
- 触发接线:里程碑唯一事实源 =
_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)。 - 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 区底线本就保留最近原始回合)。 - 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 同值。 - 异步检查点回果:
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 区文本下一压缩事件才重渲染(单测覆盖)。 - 配置:新块
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)截至实施 时不存在,全部维持计划临时默认,待定标。 - Flash 侧:chunker 只在 Pro 转写路径的 TranscriptLedger 上构造
(operator
_ensure_transcript_ledger,有 DataEngine 才挂);Flash 尚无 ledger,M3 对 Flash 零行为变化。chunker 对里程碑事件是可选输入,尺寸/软硬 阈值纯凭 ledger 回合元数据触发——Flash 未来接 ledger 时自动呈现"只有尺寸 阈值触发"的任务书形态。 - 测试:+29 净增——
tests/unit/memory/test_history_chunking.py25 (③ 结构 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.py4(roundtrip/版本 append-only 最新胜出/范围排序/会话隔离)。验证:tests/unit 全量 1165 通过 3 跳过 0 失败(M2 基线 1136+3,只增不减);tests/tools 子集 (operator/summarizer/validator/diagnoser)7 通过。 - 未完成项(待真机/后续):验收三条真机项(>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
之上;与计划的差异逐条标注):
- 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 拒绝执行(回用法提示)。 - 注册(仅 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 (分类完整性测试强制)。 - 屏幕相似度本地提示(零模型调用、零历史图入 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全量),距离≤阈值取最近的 最小距离步,注入方案原文句式提示。 - 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 编译视图,未入表(保持直调)。 - 编译视图接 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 说明 + 标记行)。 - 配置:
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 均为全新键)。 - 测试(只增不减):+45 净增——
tests/unit/tools/test_history_recall.py13(关键词命中与步号不变量、step_range 过滤、范围回捞全宽行(era 入口)、 结果数钳制、token 截断、notes 检索与步锚定、chunk 行检索、图仅单步仅真实 文件、配置封零、缺图降级、空参拒绝、无引擎降级、可用性三态);tests/unit/utils/test_image_hash.py5(稳定性/JPEG 重编码小距离/异屏大 距离/垃圾输入 best-effort/距离边界);tests/unit/agents/test_historical_state_hint.py8(命中注入、近 3 步 静默、无命中/关开关/步数不足静默、平距取最近、无哈希跳过、record_step 盖章含 post==pre 镜像);tests/unit/memory/test_context_policy.py16 (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 通过。 - 未完成项(待真机/后续):相似度距离阈值与 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 的移交项)
-
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 恒空,正因此没抓到);负向验证 已做:临时移除修复条件该用例即失败。 -
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、 不等于主模型)。 -
视觉 lens 单图自适应(§5 修订版落地):新增
flash_summarizer_single.md单图变体提示词——只描述决策帧内容与动作落点 (红标),无 AFTER ACTION 段,明示"缺后图仅表示无独立后验证据,严禁描 述/推测/合成动作后状态(转换叙事由相邻步摘要链接)",禁判定词表原样沿用;_attempt按"前后双图 → 原转换模板、其余(Pro 恒缺后图、任一图缺失、纯文 本)→ 单图变体"选择,单图块标签改为 DECISION FRAME(仅后图存在时亦有中性 标签)。Pro 无动作后抓屏的刻意安全网未动(不补抓、不用步 N+1 前图拼合)。 回显校验兜底:degenerate_summary_reason——输出含 "--- [" 段标记或以 "---" 开头、<15 字符、>1500 字符即判该次尝试失败,走既有有界重试,耗尽进 failed;双图模板补一句"输出永不含 --- 分隔符"降低无谓重试。单测覆盖单图/ 双图模板选择与块标签、回显判负后重试恢复、echo/过短/过长三形态耗尽 failed。 -
视觉 lens 计量接入(M0 遗留收口):
record_llm_usage/SessionTokenMeter.record增update_last_prompt开关;视觉 lens 与 capsule lens 的原始旁路调用成功后各记一条llm_usagetrace(sourcelens:visual_transition:<model>/lens:step_capsule:<model>),一律update_last_prompt=False——lens 小 prompt 不得覆写 chunker 软/硬阈值消费 的last_prompt_tokens上下文基数;网关包装模型(RobustChatModelWrapper) 已在出口自计量,旁路计量以 isinstance 判别避免重复。M0 实施要点第 3 条 "raw 旁路仅测试使用"的表述至此与现状一致(旁路仍在,但已计量)。 -
定标落地:
memory.runtime.max_concurrency默认 1→2(依据基线报告 §6:串行队列使摘要就绪 P90 长尾超擦洗窗口 K=3+宽限;lens 是独立轻模型调 用,并发 2 风险低),Field description 已标注依据与出处;similarity_max_distance=8维持待定标(基线未产生相似屏配对数据)。 -
就绪门控交换(§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 桩保持原有触发语义断言。 -
段头笔记关联升级为机械校验(同伴会话转达用户拍板补入):
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;与计划的差异逐条标注):
-
默认启用(用户拍板):
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 语义未变)。 -
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"。 -
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 扫描。测试 +4C3 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 路径不再逐字节"约束继续有效 -
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 开关默认关。 -
D2 — Gemini stateful/Interactions API 评估备忘:见新增 §11(只评估不 实现)。
-
归档:状态行更新为已实施;§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 稳态、已全部 完成并通过(2026-09-01 最终真机验收轮,五项全过:4 对同模型 A/B 回退 0/ 就绪率 60/60/延迟−60%/缓存 40%→86%;112 步 Loop 无堆叠、token 有界 33% 预 算、注入逐字保全;48 chunk 盲评禁词违规 1 例且语义无害;recall 图片块 3 次 Gemini 调用全 success;相似度提示正例+静默负例均验证——详见[Loop:continuous]100+ 步、 capsule 盲评——此轮同时验收就绪门控形态与默认开后的复测 A/B)docs/plans/history-acceptance-2026-09-01.md)。仍留:git 提交/PR(用户决 定,全部改动仍在 worktreezealous-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 之上):
- 形态变化: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 首 chunkstart_offset与末 chunkend_offset(既有 T+ 会话偏移格式),是视频对齐的粗粒度外键。 - 段落机械拼装,零新增 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 时段落退化为"里程碑标签列表 + 步范围"—— 永不为空(新增单测覆盖两级回退)。 - 代码位置:
artemis/memory/chunking.py(RECALL_MARKER_TEMPLATE更名RECALL_GUIDANCE_TEMPLATE并改为缩进指引行;render_era_blockrecall_only 分支重写;新增render_era_period_paragraph/_first_sentence);recall 端 无解析耦合(recall_history的 step_range 入口本就按步号范围回捞,与标记 行字面无关),仅同步了artemis/tools/history_recall.py文档字符串与artemis/agents/operator/operator.json提示词中对入口行形态的描述。 - 不变量修订落地(§8 同步):"全层级步可寻址"单测改为 L2 chunk/era 合并/
L3 快照层逐步可寻址 + 极限段落层时段可寻址(正则断言
Steps a–b与T+..→T+..在场)。era 溢出用例改断言新形态(时间范围在场、段落非空、 doing/笔记引用/verified 进段落、指引行与 user 行仍在)。 - 验证: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. 开放决策(待用户拍板)
步级单 lens 修订(§5)已拍板(2026-08-31):采纳。步级仅保留VisualTransitionLens(双 profile 共用),Pro 步级胶囊上移为 chunk 级StepCapsuleLens。原否决备选(双摘要并存、update_step_summary加 lens 维度、成本 ×2)作废。默认值定标已决(2026-09-01):基线报告 §6 定标——context_budget_tokens=80K/软硬阈值/K=3/min_active=5/chunk≤12 全部维持(实测远未触及,触发行为验收留[Loop:continuous]真机轮);max_concurrency1→2(硬化轮);similarity_max_distance8→5(M5 依 460 步 dHash 分布定标,见 §7 M5 要点 3-C5)。M2 灰度顺序已决(2026-09-01):A/B 红线全过且大幅改善后,用户拍板 M5 将transcript.enabled提为默认开;enabled:false保留为逐字节回滚开关。基线报告 §7-3 的两个前置中 ①(冷启动修复认领+回归测试)已在硬化轮满足,②(修复后复测轮)并入最终验收真机轮。辅助 agent 视图升级时点已决:M4 收口(编译视图接 chunk digest,full/digest 双渲染,见 §7 M4 要点 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 模式免去消息重建。