docs: remove completed design and planning documents
This commit is contained in:
1 parent
b8ecda9dd1
commit
99343dfe55
7 files changed
-3745
No files matched your search
@@ -1,370 +0,0 @@
|
||||
# Checker 全流程验证重构(计划驱动检查点 + 乐观异步 + 出口结算)
|
||||
|
||||
> 交接文档:本计划自包含,执行者不需要任何前置对话上下文。
|
||||
> 日期:2026-08-30(v2.1 实施版)。文中行号以当日工作区为准,**执行前一律先按内容搜索确认,行号可能漂移**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 工作区纪律(先读)
|
||||
|
||||
- 本工作区可能有**多个并发 Claude 会话**在改代码,工作树里有大量未提交改动(部分不是你产生的)。
|
||||
- **严禁 `git stash`**(会卷走别人的半成品)。
|
||||
- 需要对比基线用 `git show HEAD:<path>` 或临时 worktree,不要动工作区里已有的未提交修改。
|
||||
- 不要 commit/push,除非用户明确要求。
|
||||
- 每个 Work Package 完成后跑对应的单测(见 §8),基础设施类测试可用 `ARTEMIS_FAKE_LLM=1` 环境变量走假 LLM。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与结论(已定,不要重新设计)
|
||||
|
||||
### 1.1 旧中途 Checker 的病根(仍然成立,全部要摘除)
|
||||
|
||||
1. **Checker 失败的 turn 不写入 DataEngine 历史**——`graph.py` `execution_check_node` 里 `if checker_success:` 才 `record_step`。
|
||||
2. **notes 目录全量快照回滚**——Operator 每轮前全量快照,checker 失败整体恢复。
|
||||
3. **Prompt 整体切换**——存在 checker feedback 时换 `troubleshooter_template` + verification chat 多轮辩论。
|
||||
4. **僵尸 checker**——`ctx.checker_task` 单槽位覆盖不 cancel,副作用在任务体内。
|
||||
|
||||
**关键认知:病根是"回滚过去、切换模板、副作用失控",不是异步验证本身。**
|
||||
|
||||
### 1.2 目标架构与能力边界
|
||||
|
||||
系统承担**测试任务**,短暂状态只能在发生时刻附近取证,所以中途检查点必须存在,以**乐观异步**方式运行。架构分四层:
|
||||
|
||||
- **配置层**:`中途检查` 与 `结尾终审` 两个独立开关(§6)。用户关闭的检查一律尊重;**未执行的断言报告为 unchecked,绝不计为通过**。
|
||||
- **计划层**:Planner 把检查项声明进 task_plan.md(`- verify:` / `- assert:`,带时机,§4)。计划文件是唯一权威载体(锚定子目标、随重写存活、受守护覆盖);但**相关 prompt 注入全部条件化**(§4.4)——检查全关时 Planner 不生成 check 行、语法说明不进任何 prompt,零上下文污染。
|
||||
- **能力层**:Checker 享有与 Operator 等价的观察能力(历史、截图、当前屏幕),但工具权限独立——只读检索 + 受约束探测,零副作用,只出裁决(§5.4)。
|
||||
- **控制层**:Graph 识别检查点、spawn 任务、收割裁决、执行副作用、出口结算(§5.1-5.3)。
|
||||
|
||||
**能力边界(必须如实声明,写进 planner prompt 与最终报告语义)**:
|
||||
- 本期的中途检查是**事后核验**:它能在事后发现流程违规并留下证据,**不能阻止**违规发生。"必须确认 A 之后才允许执行 B"这类**强制前置关卡本期不支持**——Planner 遇到此类用户要求时不得静默接受:断言照常生成(保留用户原语义),但计划与报告中标注"顺序核验为事后判定"。
|
||||
- 异步检查只能核验**已被采集的证据**。从未被记录的瞬时提示无法找回——瞬态断言的证据来源是执行历史中的前后截图;Planner 应优先把预期表述为可持久探测的状态。
|
||||
|
||||
**三条铁律**:
|
||||
1. Operator 的历史与上下文 **append-only**——不换 prompt、不回滚 notes、不丢 step、不开平行对话。
|
||||
2. 验证影响未来(打回状态、注入 finding),**不修改过去**。
|
||||
3. **放行、裁决、任务收尾三者分离**:fail-open 只影响放行决策,裁决原值(inconclusive)永远入账;assert 失败不修复但必须产生机器可读的测试失败结果并传导到 SDK 收尾(§5.5)。
|
||||
|
||||
**裁决 append-only**:每次检查有独立 `attempt_id`,所有裁决(含被 supersede 的)永久入账;断言首次失败不被覆盖;报告呈现每条断言 passed / failed / inconclusive / unchecked 与完整裁决序列。
|
||||
|
||||
### 1.3 横切原则:Prompt 与工具按配置装配(No Dead Instructions)
|
||||
|
||||
本次全部改动遵守同一条装配纪律,**任何 agent 的 prompt 段与工具注册都是"配置 × 当前场景"的函数**:
|
||||
|
||||
1. 配置关闭的能力,不得在任何 prompt 中被提及,其工具不得注册——不存在"指令还在、机制已关"的死指令;
|
||||
2. 反向同样成立:prompt 中出现的每条规则,必须对应一个当前激活的机制(如"删除会被恢复"只在守护生效时可说);
|
||||
3. 装配点集中在代码里的组件列表/工具注册表(PromptComponent 列表、工具 dict 构造函数),**不在 prompt 模板内部堆条件文本**;
|
||||
4. 场景也参与装配:同一 agent 的不同入口(如 checker 的中途/终审)按各自的证据纪律装配不同的工具与指南段。
|
||||
|
||||
落点:Planner(§4.1/§4.2 条件语法段与生成指令)、Operator(§4.4 说明段、§3.1 feedback 组件按内容渲染)、Checker(§5.4 两入口装配表)、SDK 返回结构(§5.5 test summary 仅在存在 check 项时出现)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 代码地图(改动涉及的锚点)
|
||||
|
||||
Graph 拓扑(`artemis/graph/graph.py` `get_graph`,~622):
|
||||
|
||||
```
|
||||
START → planner → convergence
|
||||
perception → operator → execution_check ─(execute_decisions)→ validator → summarizer → convergence
|
||||
└(review_subgoals)→ convergence
|
||||
convergence ─(continue)→ perception
|
||||
└(end)→ END
|
||||
```
|
||||
|
||||
注意时序:Operator 在自己的 turn 内通过 note 工具写计划(含完成标记);该 turn 的设备动作在**其后的 validator 节点**才执行;`execution_check_node` 的 `record_step` 记录的是本 turn 的观察(pre 截图)与决策。因此"子目标完成"的证据 = 截至本 turn step 的历史(上一 turn 的 post 截图 + 本 turn 的 pre 截图)。
|
||||
|
||||
| 锚点 | 位置(按内容搜索确认) | 处置 |
|
||||
|---|---|---|
|
||||
| `execution_check_node` | `graph.py` ~88-289 | 重构(§3.2、§5.1、§5.2) |
|
||||
| checker 中途触发 ①(chat_path 分支) | ~100-132 | 删除 |
|
||||
| checker 单槽消费段 | ~134-153 | 删除 |
|
||||
| planner validation 消费段 + 拒绝提前 return | ~155-211,**return 在 ~190** | 保留机制;拒绝分支补 record_step(§3.2) |
|
||||
| record_step(`if checker_success:` 门控) | ~213-250 | 无条件化(§3.2) |
|
||||
| notes 快照 commit/rollback 段 | ~252-280 | 删除 |
|
||||
| checker 中途触发 ②(`new_completions`) | `_process_plan_write` 末段 ~499-506 | 改为排队 pending checkpoint(§5.1) |
|
||||
| planner validation 漂移检测 | `_process_plan_write` `milestones_changed`(~452) | 保留;旁加确定性 check 行守护(§4.3) |
|
||||
| `convergence_gate` | ~625 | 扩为三值路由(§5.3) |
|
||||
| notes 快照创建 / troubleshooter 分支 / verification chat 机制 | `operator.py` | 删除(§3) |
|
||||
| `CheckerFeedbackPromptComponent` | `operator/prompts.py` ~231 | 改写为读 state 的 append 注入组件 |
|
||||
| checker 本体 | `agents/checker/checker.py` `run_async_check`(~80)、`CheckerResult`(~68) | 重写(§5.4) |
|
||||
| context 字段 | `context.py`:`checker_task`、`disable_checker=True`(~108)、`checker_max_chat_rounds`(~110)、`disable_planner_validation=True`(~111,**注意默认关**)、`task_plan_snapshot` | §3、§6 |
|
||||
| state 字段 | `graph/state.py`:`checker_success`、`initial_goal` | 保留;新增字段见 §5.2/§5.5 |
|
||||
| plan 语法 | `utils/plan_grammar.py`:`CHECKBOX_LINE_RE`(~49)、`parse_plan`(~149)、`PlanSnapshot`(~100)、`subgoal_hash`(~56)、`milestones_changed`(~167)、`new_top_level_completions`(~172)、`PLAN_GRAMMAR_SPEC`(~199) | 扩展(§4.1) |
|
||||
| DataEngine | `engine.py`:`record_step`(~491,存 pre/post 截图)、`get_step_number`(~922)、`get_agent_friendly_steps`(~1045)、`end_session(status)`(~300);`storage.py` `get_steps`(~651) | 只加只读检索辅助 + 收尾状态核对(§5.5) |
|
||||
| SDK 收尾 | `sdk/agent.py`:图完成后无条件 success 日志 + `end_session("completed")`(~718-721)、task.status 收敛(~1035-1040)、trace 命名 `_PASS/_FAIL`(~1061) | 接入 run outcome(§5.5) |
|
||||
| verification 工具函数 | `utils/verification.py` | 删除 |
|
||||
| 快照工具函数 | `utils/file.py` `create_snapshot`/`restore_snapshot` | 删前 grep 确认无其他调用方 |
|
||||
| SDK 配置透传 | `sdk/types/agent.py`、`sdk/builders/agent_config_builder.py`、`sdk/agent.py` | §6 |
|
||||
|
||||
---
|
||||
|
||||
## 3. WP1 — 摘除旧干扰源(先做)
|
||||
|
||||
### 3.1 Prompt 不再切换
|
||||
- `operator.py` `_build_prompt`:删 `has_feedback` 分支,恒用 `main_template`。
|
||||
- `CheckerFeedbackPromptComponent` 改写为 `CheckFeedbackPromptComponent`:读 state 的 `check_feedback`(§5.2 写入),无内容不渲染,加入恒定组件列表。
|
||||
- 删 `_has_checker_feedback`、`_get_verification_chat_rounds`、`_get_reply_to_checker_tool` 及工具注册逻辑;`operator.json` `troubleshooter_template` 相关键清理(grep 确认无他处引用)。
|
||||
|
||||
### 3.2 每个 operator turn 都必须 record_step(含 planner 拒绝的 turn)
|
||||
- 仅移除 `if checker_success:` 门控**不够**:planner 拒绝分支在 ~190 提前 `return`,同样跳过记录。
|
||||
- 做法:把现有 record_step 代码抽成节点内局部函数 `_record_turn(extra_metadata: dict) -> str | None`:
|
||||
- 正常路径原位调用(extra 不变);
|
||||
- planner 拒绝分支在 `return` 前调用,extra 加 `{"planner_rejected": True}`(该 turn 的 terminal actions 被拦截未执行,记录如实反映决策与拦截)。
|
||||
- 保留"planner 拒绝的 turn 不清零 subagent 循环计数"语义(`checker_success` 今后仅由 planner 拒绝路径置 False,维持现有 `if checker_success: update["subagent_calls"] = []` 写法)。
|
||||
|
||||
### 3.3 删除 notes 快照/回滚
|
||||
- `operator.py` `create_snapshot` 段、`ctx.task_plan_snapshot` 赋值:删。
|
||||
- `execution_check_node` "Handle optimistic execution rollback/commit" 整段(~252-280):删。
|
||||
- `context.py` `task_plan_snapshot`:删。`utils/file.py` 快照函数 grep 无调用方后删。
|
||||
- planner validation 拒绝时的 task_plan.md 单文件回滚(`ctx.task_plan_content_before`)**保留**。
|
||||
|
||||
### 3.4 删除单槽位与 chat 机制
|
||||
- `execution_check_node`:删 chat_path 触发段与 `await ctx.checker_task` 消费段。
|
||||
- `context.py`:删 `checker_task`、`checker_max_chat_rounds`。`planner_task` 保留。
|
||||
- `_process_plan_write` `new_completions` 触发段:暂时整段删除(§5.1 重建)。
|
||||
- `utils/verification.py`、`checker_status.json` 写入、相关 import:删。
|
||||
|
||||
WP1 完成后系统处于"无任何语义验证"的干净中间态,可独立运行与测试。
|
||||
|
||||
---
|
||||
|
||||
## 4. WP2 — 计划语法、Planner 与检查标准保护
|
||||
|
||||
### 4.1 plan grammar 扩展(`utils/plan_grammar.py`)
|
||||
- 新语法:顶层子目标下缩进附属行,关键字带可选时机后缀:
|
||||
```
|
||||
- [ ] 在时钟应用创建 7:30 AM 闹钟
|
||||
- verify: 闹钟列表应出现 7:30 AM 且开关为开
|
||||
- assert: 创建后应弹出"已设定闹钟"提示
|
||||
- [ ] …
|
||||
(任务级,可置于计划末尾游离段)
|
||||
- assert@end: 全程不应出现应用崩溃对话框
|
||||
```
|
||||
- 解析为:
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class CheckItem:
|
||||
kind: Literal["verify", "assert"]
|
||||
when: Literal["on_complete", "at_end"] # 无后缀=on_complete;@end=at_end
|
||||
text: str
|
||||
parent_key: str | None # 挂靠子目标的 key;任务级为 None
|
||||
```
|
||||
`when` 语义:`on_complete` 在锚定子目标完成时刻用**当时证据**判定;`at_end` 只在出口用**最终状态**判定。**禁止**在出口用当前状态重判 `on_complete` 项,也禁止在中途提前执行 `at_end` 项。
|
||||
- 新增 `CHECK_LINE_RE`;先确认 `parse_plan` 对非 checkbox 缩进行的容忍度(`CHECKBOX_LINE_RE` 只匹配 checkbox 行),确保 check 行**不影响** `milestones_changed`/`subgoal_hash`/`new_top_level_completions` 现有行为。
|
||||
- `PlanSnapshot` 新增 `check_items_of(item)`、`task_level_check_items`、`all_check_items`;新增模块函数 `check_items_changed(before, after)`(比较 check 行多重集合)。
|
||||
- **语法说明条件化**:`PLAN_GRAMMAR_SPEC` 从模块常量拆为"基础语法 + check 扩展"两段,新增 `render_plan_grammar_spec(include_checks: bool) -> str`。check 扩展段内容:两关键字语义(verify=完成度,失败触发修复;assert=测试断言,失败如实记录不修复)、`@end` 后缀、§1.2 能力边界声明(事后核验、不支持强制前置关卡、瞬态断言的证据约束)。三个现有注入点改用该函数:`planner.py` ~105/~178、`operator/prompts.py` ~206;`include_checks = not(两开关全关)`。全关时任何 prompt 里都不出现 check 语法。
|
||||
|
||||
### 4.2 Planner 生成检查项(`planner.json`)
|
||||
- **生成指令是 planner prompt 的条件段**:检查项相关的全部指令(下述各条 + §4.1 的 check 语法扩展)只在 `include_checks` 为真时注入。两开关全关 → Planner 的 prompt 与产出和无此功能时完全一致,不生成任何 check 行。
|
||||
- 用户目标里的显式预期 → `- assert:` 行,逐字保留用户语义;属于全程/最终状态的预期用 `@end`。
|
||||
- `- verify:` 行**只为确需验收的关键节点**生成——判断标准:不可逆操作完成点、对最终目标有直接贡献的里程碑。**不是**每个子目标一条;克制是明确要求(成本与 check 项数量成正比,见 §6)。
|
||||
- 不可验证的子目标不硬造断言;瞬态预期优先改写为可持久探测的状态断言。
|
||||
- 用户提出"必须先确认 A 才能做 B"类强序要求时:断言照常生成,但按 §1.2 标注"事后核验",不得静默承诺前置拦截。
|
||||
|
||||
### 4.3 检查标准保护(确定性守护,不依赖 LLM)
|
||||
- **前提事实**:planner validation 默认关闭(`disable_planner_validation=True`),不能作为 check 行不可删的唯一保障。
|
||||
- **确定性守护**(`_process_plan_write` 内,Operator 来源的写入):写入后若 `check_items_changed(before, after)` 检出 check 行被删除或改写,Graph **把缺失的 check 行合并回写**进新计划(锚定其 `parent_key`;父子目标整个被删时,该行转为任务级 `@end` 项)。合并回写是纯文本操作、同步完成,不打断 Operator;Operator 新增 check 行允许保留。Planner 节点自身的计划写入不受此守护(Planner 有权修订检查标准)。
|
||||
- planner validation **开启时**:check 行差异同样纳入其审计范围(prompt 增加"检查标准被弱化应拒绝"要点)。机制本体(CAS 回滚、ratchet baseline)不动。
|
||||
|
||||
### 4.4 Operator 可选说明段(`operator/prompts.py`)
|
||||
- 新增 `CheckItemsExplainerPromptComponent`,加入 `main_template` 恒定组件列表,**仅当当前 task_plan.md 解析出 check 行时渲染**(以计划实际内容为准,不看开关——续跑的旧计划带 check 行时同样需要解释),否则输出空串。
|
||||
- 内容(一段,短):
|
||||
- `- verify:` 是该子目标的验收标准,执行时可以参考它确认做到位,但**判定由独立 Checker 执行**,不要自行宣布通过或替 checker 记录结论;
|
||||
- `- assert:` 是测试断言,**不需要为它做额外动作**,更不得为满足断言去构造/伪造状态——断言失败是合法的测试结果;
|
||||
- check 行**不可删除或改写**(删除会被系统自动恢复);重写计划时原样保留,新增允许;
|
||||
- 正常按语法打完成标记即可,检查在后台异步进行,不阻塞执行。
|
||||
- 这与 §4.1 注入 operator 的语法扩展分工:语法扩展讲"这些行是合法语法",本组件讲"你该怎么对待它们";前者受开关控制,后者受计划内容控制。
|
||||
|
||||
### 5.1 触发:排队与 spawn 分离(证据锚点修正)
|
||||
- **`_process_plan_write` 只排队**:`new_top_level_completions` 检出新完成项后,对每个带 `on_complete` check 项的完成项,追加 `ctx.pending_checkpoints: list[PendingCheckpoint]`(字段:`checkpoint_id=subgoal_hash`、check 项、完成时计划全文、触发时间戳)。**不在此处 spawn**——此刻本 turn 的 step 尚未记录,`get_step_number` 读到的是上一轮,锚点会错位。
|
||||
- **`execution_check_node` 在 record_step 之后 spawn**:对每个 pending checkpoint 生成 `attempt_id`(`{checkpoint_id}#{递增序号}`),构造证据锚:
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class EvidenceAnchor:
|
||||
anchor_step_id: str # 刚记录的本 turn step(含 pre 截图;上一 turn 的 post 截图已在库)
|
||||
trigger_ts: float
|
||||
plan_text: str # 完成时刻的计划全文
|
||||
```
|
||||
然后 `ctx.checkpoint_tasks[checkpoint_id] = (attempt_id, asyncio.create_task(run_checkpoint_check(...)))`。
|
||||
- **supersede 不丢裁决**:spawn 前若同 `checkpoint_id` 已有旧条目——旧任务 `done()` → **先收割入账**(§5.2)再替换;仍在运行 → `cancel()` 并 `await`(吞 `CancelledError`,完成资源清理),账本记一条 `superseded` 记录(attempt_id、时间),再 spawn 新 attempt。
|
||||
- 每个新完成项都触发(修复旧"只验 `new_completions[-1]`"bug)。中途开关关闭时不排队不 spawn(相关项出口按 §5.3 处理)。`user_stop_requested` 后不再 spawn 任何新检查。
|
||||
- **并发上限**:`max_concurrent_checkpoints`(默认 3),超出的 pending 留在队列,下轮 execution_check 再 spawn。
|
||||
|
||||
### 5.2 收割(`execution_check_node` 内,非阻塞)
|
||||
- 每轮遍历 `ctx.checkpoint_tasks`,**只收割 `task.done()` 的**(绝不 await 未完成的),收割后从 dict 移除:
|
||||
1. **入账**:所有裁决 append-only 写入 DataEngine 账本(session 元数据或专用 jsonl:attempt_id、checkpoint_id、item_text、kind、when、status、evidence、anchor_step_id、时间)。这是"首次失败永久保留"的唯一载体,任何后续结果只能追加。
|
||||
2. **状态副作用的适用性校验**:仅当该 attempt 仍是此 checkpoint 的**当前 attempt**、且锚定子目标在当前计划中文本未变时,才允许改执行状态;过期/失配的裁决只入账不作用。
|
||||
3. `verify` FAIL(适用时)→ 子目标 `[x]`→`[/]`(`subgoal_hash` 匹配,直接写文件不走 wrapped note tool)+ finding 追加进 state `check_feedback`;**每 checkpoint 修复配额 `checkpoint_max_repairs`(默认 2)**:超配额不再打回,裁决保持 failed,留待出口如实呈现。`user_stop_requested` 后不再打回。
|
||||
4. `assert` FAIL → 只入账,不打回不注入(可配 `assert_failure_policy: "continue" | "halt"`,halt=置停止标记走 §5.3 收尾)。
|
||||
5. cancelled 记 `superseded`;异常/超时:verify 记 `inconclusive` + warning(**裁决不改写为 passed**,仅放行决策按通过处理),assert 记 `inconclusive`。
|
||||
- **单次检查超时**:`checkpoint_timeout`(默认 180s,spawn 时用 `asyncio.wait_for` 包裹)→ 超时按异常处理。
|
||||
- 副作用只发生在收割点与出口结算,任务体内零副作用。
|
||||
|
||||
### 5.3 出口:结算与终审分离
|
||||
- `convergence_gate` 扩为 `Literal["continue", "exit_settlement", "end"]`:
|
||||
- `ctx.data_engine` 为 None → `"end"`;
|
||||
- 任务到达终态(`all_top_level_done`,或 continuous+user_stop,或 halt 标记)→ 只要**存在任何 check 项/未决任务/账本记录,或结尾终审开启**,一律 `"exit_settlement"`;两者皆无才 `"end"`。
|
||||
- **空计划(`not has_top_level`)且结尾终审开启 → 仍走 `"exit_settlement"`**:没有计划项不等于无可验之物,终审对象是用户原始目标。
|
||||
- 新增节点 `exit_settlement_node`,两阶段,**阶段一无条件执行,阶段二受结尾开关控制**:
|
||||
1. **结算(barrier)**:收齐 `checkpoint_tasks` 全部未决任务并按 §5.2 入账——这是全流程唯一有意的阻塞等待,带**加总超时** `settlement_timeout`(默认 120s):超时 cancel 剩余任务、对应项记 `unchecked(timeout)`。已启动的检查必须在退出前有账,不论结尾开关。
|
||||
2. **终审(`disable_final_check=False` 时)**:运行 `run_final_check`,对照 **`state.initial_goal`(用户原始目标,不是计划)** + 全部 check 项:
|
||||
- `at_end` 项:用最终状态判定;
|
||||
- `on_complete` 项:**引用账本裁决**(当前 attempt 的 PASS/FAIL 直接采信,FAIL 过的 assert 保持 FAIL);从未检查过的(中途关闭/配额限制),基于锚点附近的**历史证据**补判,证据不足记 `unchecked`——禁止用当前屏幕重判过程断言;
|
||||
- 终审自身超时/异常:fail-open 放行,账记 `inconclusive`。
|
||||
3. 路由:终审通过或关闭 → `END`;verify 类未达成(assert 失败**不算**)→ 打回 unmet 项 + `check_feedback` 注入 → `perception`;`final_check_attempts` 达上限(默认 3)或 user_stop/halt → `END`。
|
||||
- 收尾状态见 §5.5;continuous+user_stop 时 FAIL 不回循环。
|
||||
|
||||
### 5.4 Checker 本体(`agents/checker/checker.py` 重写)
|
||||
- 两个入口共用旧 `run_async_check` 的工具循环骨架(多轮循环、迭代上限 `checker_max_iterations`、结构化兜底,可整体沿用):
|
||||
- `run_checkpoint_check(check_items, anchor: EvidenceAnchor, goal, ...)`
|
||||
- `run_final_check(goal, plan_text, ledger, ...)`
|
||||
- **输入**:用户原始目标、计划、待验项、证据锚、`get_agent_friendly_steps()` 摘要历史。
|
||||
- **工具表按入口 × 配置装配(§1.3;工具表是唯一权限来源,全部只读)**:
|
||||
|
||||
| 工具 | `run_checkpoint_check` | `run_final_check` | 配置开关 |
|
||||
|---|---|---|---|
|
||||
| `get_step_detail(step_no)` / `get_step_screenshot(step_no, which: pre\|post)`(`storage.get_steps` + step 目录,新增只读辅助) | ✓ | ✓ | 恒定 |
|
||||
| `read_note`(只读) | ✓ | ✓ | 恒定 |
|
||||
| `probe_device(kind, params)`(固定探测类型 + 程序生成 argv,见下) | ✓ | ✓ | `disable_device_probes`(默认 False);关闭则**不注册且 prompt 零提及** |
|
||||
| 实时屏幕读取(`UnifiedMobileController.get_screen_data()` + OCR,旧 checker 有现成代码) | **✗ 不装配** | ✓ | 恒定(仅 final 入口) |
|
||||
|
||||
中途检查点**不装配实时屏幕**是刻意的证据纪律:检查点的证据是锚点历史与持久状态探测,当前屏幕早已随 Operator 前进,装配它只会诱导用错证据——纪律靠工具表落实,不靠 prompt 劝告。
|
||||
**任何入口都不注册**:设备 action、write_note、子代理。
|
||||
- **`checker.json` 拆为组件段,按待验内容与入口装配**(不在单一模板里堆条件文本):
|
||||
|
||||
| prompt 段 | 装配条件 |
|
||||
|---|---|
|
||||
| 基础裁决规则(逐字引用、证据具体化、inconclusive 语义) | 恒定 |
|
||||
| verify 语义段(完成度判定、fail-open 放行规则、suggestion 要求) | 待验项含 verify |
|
||||
| assert 语义段(测试断言、失败即结果、证据缺失记 inconclusive 不记 failed) | 待验项含 assert |
|
||||
| 锚点取证指南(瞬态/顺序断言必须查锚点附近历史) | `run_checkpoint_check` 入口 |
|
||||
| 终审指南(对照原始目标、引用账本、at_end 用最终状态、禁止重判过程断言) | `run_final_check` 入口 |
|
||||
| probe 使用指南(持久状态优先探测、探测类型速查) | `probe_device` 已注册 |
|
||||
- **`probe_device` 设计(不做前缀白名单)**:`dumpsys` 等含状态修改子命令(如 `dumpsys battery set level 100` 可改电量模拟),且前缀判断挡不住参数拼接。改为枚举探测表,argv 列表由代码构造、不经 shell 字符串拼接,参数正则校验(拒绝空白符与 shell 元字符):
|
||||
```python
|
||||
PROBES: dict[str, ProbeSpec] = {
|
||||
"alarms": argv=["dumpsys", "alarm"], # 无参数
|
||||
"battery": argv=["dumpsys", "battery"], # 无参数(禁 set/unplug 等子命令)
|
||||
"foreground": argv=["dumpsys", "activity", "activities"],
|
||||
"notifications":argv=["dumpsys", "notification"],
|
||||
"setting": argv=["settings", "get", <ns>, <key>], # ns∈{system,secure,global};key: ^[A-Za-z0-9._-]+$
|
||||
"content": argv=["content", "query", "--uri", <uri>], # uri: ^content://[A-Za-z0-9./_-]+$
|
||||
"packages": argv=["pm", "list", "packages"],
|
||||
"prop": argv=["getprop", <key>], # key: ^[A-Za-z0-9._-]+$
|
||||
}
|
||||
```
|
||||
未列举的探测类型一律拒绝;扩表需改代码,不接受运行时自由命令。底层复用 `run_adb_command` 的执行通道但以 argv 形式传递。
|
||||
- **输出**(structured output):
|
||||
```python
|
||||
class CheckVerdict(BaseModel):
|
||||
item_text: str # check 行原文,逐字
|
||||
kind: Literal["verify", "assert"]
|
||||
status: Literal["passed", "failed", "inconclusive"]
|
||||
evidence: str # 具体观察,禁止空泛
|
||||
suggestion: str # 仅 verify failed 需要
|
||||
|
||||
class CheckReport(BaseModel):
|
||||
verdicts: list[CheckVerdict]
|
||||
unmet_subgoals: list[str] # 仅 final check:逐字引用计划项
|
||||
```
|
||||
放行决策(passed 布尔)由 **node 侧**从 verdicts 计算(verify 全 passed/inconclusive → 放行),不由模型直接输出——保证"放行≠裁决改值"。
|
||||
- **各段共同要点**:failed 必须附具体证据(探测输出或截图元素),空泛否决 node 侧降级 inconclusive;证据从未被采集时输出 inconclusive 并说明"证据缺失"而非 failed;引用计划项逐字。
|
||||
|
||||
### 5.5 任务收尾与 SDK 传导(放行 / 测试结果 / 收尾状态三轴分离)
|
||||
- Graph 在 END 前汇总 **run outcome**(写入 state 新字段 `run_outcome` 与 DataEngine session 元数据):
|
||||
```python
|
||||
class RunOutcome(BaseModel):
|
||||
task_status: Literal["completed", "partial", "blocked"] # 目标达成轴
|
||||
tests: TestSummary # passed/failed/inconclusive/unchecked 计数 + failed 明细
|
||||
```
|
||||
- verify 未达成且预算耗尽 → `blocked`(或 `partial`);
|
||||
- **assert 有任何 FAIL → `tests.failed > 0`,即使 `task_status="completed"`**——任务做完与测试通过是两回事。
|
||||
- **SDK 链路必改**(`sdk/agent.py`):
|
||||
- 图完成处(~718-721):不再无条件打 success 日志 / `end_session("completed")`——读 `run_outcome`:`blocked` → `end_session("failed")`(确认 `data_engine.end_session` 支持的状态集,必要时扩);`completed` 但 `tests.failed>0` → `end_session("completed")` 且 session 元数据带 test summary,日志明确打出"任务完成、N 条断言失败";
|
||||
- task.status 收敛处(~1035-1040)与 trace 命名处(~1061 `_PASS/_FAIL`):断言失败的运行 trace 命名用 `_TESTFAIL`(或并入 `_FAIL`,二选一但必须与 `_PASS` 区分);
|
||||
- `mobile_run_task`/`mobile_manage_task` 返回结构透出 test summary,调用方无需翻报告正文即可拿到机器可读结果。
|
||||
- Outputter 报告呈现完整裁决序列与三态统计;BLOCKED/partial 收尾时最后一次 finding 入元数据。
|
||||
|
||||
---
|
||||
|
||||
## 6. WP5 — 配置与 SDK 透传
|
||||
|
||||
- `ExecutionSetup`(`context.py`):
|
||||
- `disable_midway_checks: bool = False`、`disable_final_check: bool = False`(默认开。成本与计划中 check 项数量成正比——§4.2 已要求 Planner 只在关键节点生成 verify,这是成本上界的来源,**不存在"零成本"**);
|
||||
- `disable_checker` 保留为兼容别名:True 时两者同关;
|
||||
- `final_check_max_attempts: int = 3`、`checkpoint_max_repairs: int = 2`、`max_concurrent_checkpoints: int = 3`、`checkpoint_timeout: float = 180.0`、`settlement_timeout: float = 120.0`、`assert_failure_policy: Literal["continue","halt"] = "continue"`、`disable_device_probes: bool = False`;
|
||||
- `disable_planner_validation`、`checker_max_iterations` 语义不变。
|
||||
- SDK 透传链:`sdk/types/agent.py`、`sdk/builders/agent_config_builder.py`(`with_disable_checker` 保留 + 新开关映射)、`sdk/agent.py`。
|
||||
- 四组合行为:
|
||||
| 中途 | 结尾终审 | 行为 |
|
||||
|---|---|---|
|
||||
| 关 | 关 | 不 spawn、不终审;**已启动的任务仍出口结算**(本组合下无任务);全部 check 项报 unchecked |
|
||||
| 关 | 开 | 出口结算(空)+ 终审:`at_end` 判最终状态,`on_complete` 凭历史证据补判,不足记 unchecked |
|
||||
| 开 | 关 | 检查点照跑;出口**只结算不终审**(阶段一无条件),账本与 test summary 照常产出 |
|
||||
| 开 | 开 | 检查点 + 结算 + 终审 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 明确不做(Out of scope,勿顺手扩大)
|
||||
|
||||
- **强制前置关卡**("确认 A 后才允许执行 B"的中途阻塞等待)——本期只提供事后核验;出口 barrier 保证的是**退出前完成裁决结算**,不保证执行顺序约束。此边界必须如实声明(§1.2),不得静默接受此类需求。
|
||||
- "动作前"检查时机(前置状态是 Operator 感知职责)。
|
||||
- 逐动作依赖分析。
|
||||
- 多验证器投票、版本化 VerificationKey、shadow 遥测。
|
||||
- Validator 拆分、统一 FailureEvent/RecoveryCoordinator、`_check_infinite_loop` 降级。
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试
|
||||
|
||||
现有测试(先跑通再动手,改完逐个修):
|
||||
- `tests/unit/graph/test_graph.py` —— execution_check_node / convergence_gate 断言大改。
|
||||
- `tests/unit/agents/test_plan_mutation_guardrails.py` —— planner validation 主体不动;补 check 行确定性守护用例(§4.3)。
|
||||
- `tests/unit/utils/test_plan_grammar.py` —— verify/assert/@end 解析、不影响 milestone hash。
|
||||
- 全仓 grep `verification_chat`、`checker_status`、`task_plan_snapshot`、`create_snapshot`、`troubleshooter`、`ctx.checker_task`、`checker_max_chat_rounds`:零残留。
|
||||
|
||||
新增(建议 `tests/unit/graph/test_checkpoints.py` + `test_exit_settlement.py`):
|
||||
1. 排队/spawn 分离:`_process_plan_write` 只排队;execution_check 在 record_step 后 spawn,anchor_step_id 等于本 turn 刚记录的 step。
|
||||
2. 多个同轮完成项全部排队;无 check 项不排队;中途开关关不排队;user_stop 后不 spawn。
|
||||
3. supersede:旧任务 done 未收割 → 先入账再替换(**失败裁决不丢**);运行中 → cancel 且 await 完成,账本有 superseded 记录。
|
||||
4. 收割只取 done;适用性校验:过期 attempt / 子目标文本已变 → 只入账不打回。
|
||||
5. verify FAIL 打回 + 注入;超 `checkpoint_max_repairs` 后不再打回、裁决保持 failed。
|
||||
6. assert FAIL 只入账;`halt` 策略触发收尾;账本 append-only(先 FAIL 后 PASS 两条都在)。
|
||||
7. 超时:单任务超 `checkpoint_timeout` → inconclusive;结算超 `settlement_timeout` → 剩余 cancel + unchecked(timeout)。
|
||||
8. 出口路由:结算与终审解耦——终审关、有未决任务 → 仍进 exit_settlement 且只走阶段一;空计划 + 终审开 → 进终审;全关全无 → 直接 end。
|
||||
9. `when` 语义:`at_end` 不在中途执行;`on_complete` 在终审引用账本、无账本凭历史补判、证据不足 unchecked(构造"当前状态已满足但历史证据缺失"的用例,断言**不**判 passed)。
|
||||
10. record_step 无条件:正常 turn 与 planner 拒绝 turn 都有记录,后者带 `planner_rejected` 标记。
|
||||
11. check 行守护:Operator 写入删除 check 行 → 合并回写;父目标被删 → 转任务级 `@end`;Planner 写入可修订;planner validation 关闭时守护独立生效。
|
||||
12. `probe_device`:枚举表内放行、表外拒绝、参数带空白/元字符拒绝、`battery set` 类子命令无法表达。
|
||||
13. fail-open 三轴分离:verify 异常 → 放行但账记 inconclusive(**不是 passed**)。
|
||||
14. run outcome / SDK:assert 失败 → `tests.failed>0`、trace 名非 `_PASS`、`end_session` 元数据含 summary;verify 预算耗尽 → `blocked` + `end_session("failed")`。
|
||||
15. continuous+user_stop+FAIL → 不回循环。
|
||||
16. 四开关组合矩阵(§6 表逐行)。
|
||||
17. prompt 条件化:两开关全关 → `render_plan_grammar_spec` 输出不含 check 语法、planner prompt 无生成指令;`CheckItemsExplainerPromptComponent` 仅在计划含 check 行时渲染(含"开关关但旧计划带 check 行仍渲染"的用例)。
|
||||
18. checker 装配:`run_checkpoint_check` 的工具表**无实时屏幕**、`run_final_check` 有;`disable_device_probes=True` → `probe_device` 未注册且 prompt 无探测指南;待验项全 verify → prompt 无 assert 段(反之亦然);checkpoint 入口无终审指南段。
|
||||
|
||||
运行:`python -m pytest tests/unit/graph tests/unit/agents tests/unit/utils -x`(需要假 LLM 设 `ARTEMIS_FAKE_LLM=1`)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收标准
|
||||
|
||||
1. Operator 的 prompt 模板、notes、DataEngine 历史在任何验证结果下不被回滚/切换/丢弃;**每个 operator turn(含 planner 拒绝的)都有 step 记录**;中途路径上不存在对未完成验证任务的等待。
|
||||
2. verification chat、`checker_status.json`、notes 全量快照、单槽 `ctx.checker_task` 从代码库消失。
|
||||
3. 检查项由 Planner 声明(verify/assert × on_complete/at_end),Operator 删除/弱化 check 行会被**确定性守护**恢复,不依赖 planner validation 开关。
|
||||
3a. **§1.3 装配纪律全局成立**:检查全关时 planner/operator prompt 与无此功能时一致(零污染);计划含 check 行时 Operator 收到行为说明段;checker 的 prompt 段与工具表按入口 × 配置装配(checkpoint 入口无实时屏幕、probe 关闭则工具不注册且 prompt 零提及、无 assert 项则无 assert 段);不存在指向已关闭机制的死指令。
|
||||
4. 检查点在 record_step 之后 spawn,证据锚指向正确的 step;裁决全部 append-only 入账,supersede 不丢已完成裁决;检查有单次超时、并发上限、修复配额。
|
||||
5. 出口结算无条件执行(含加总超时与 unchecked 标记),终审受结尾开关控制;空计划不绕过已开启的终审;终审对照用户原始目标;`at_end`/`on_complete` 判定时机不错位。
|
||||
6. 放行、裁决、收尾三轴分离:fail-open 不改写裁决值;assert 失败产生机器可读 test summary 并传导至 `sdk/agent.py` 收尾(日志、end_session、trace 命名、返回结构)。
|
||||
7. `probe_device` 为枚举探测 + 程序生成 argv,无自由命令通道。
|
||||
8. 计划/报告如实声明"事后核验"边界,强序需求不被静默接受。
|
||||
9. planner validation(入口卡点)除新增 check 行审计外与现状一致。
|
||||
10. §8 测试全绿。
|
||||
|
||||
---
|
||||
|
||||
## 10. 建议执行顺序
|
||||
|
||||
WP1(3.2 → 3.3 → 3.1 → 3.4,摘干扰源,得到干净中间态)→ WP2(§4 语法 + Planner + 守护,独立可测)→ §5.4 checker 本体与 probe 表(可单测)→ §5.1 触发 → §5.2 收割 → §5.3 出口 → §5.5 收尾传导 → WP5 配置透传 → 全量测试。
|
||||
|
||||
每步完成后系统都应可运行:WP1 后=无验证;WP2 后=计划里有 check 项但无人消费(守护已生效,无害);§5.4 后=checker 可单测;接线后=全功能。
|
||||
@@ -1,598 +0,0 @@
|
||||
# ARTEMIS 上下文与记忆重构:显式状态、按 agent 可见域、统一记忆内核
|
||||
|
||||
状态:提案 v2(2026-08-31,经"不损伤"审计细化,待终审)
|
||||
|
||||
> **2026-08-31 更新**:Phase 0/1 已实施;Phase 4(§6 方案 D 记忆内核)由
|
||||
> `docs/plans/history-module-redesign.md`(统一方案 v3)取代并修订——其中
|
||||
> "两 lens 按 profile 分立"修订为"步级视觉 lens + 段级胶囊 lens 按高度分立"
|
||||
> (该修订已于 2026-08-31 获用户拍板采纳),
|
||||
> StepMemoryService 运行时与 ContextPolicy 策略表保留。Phase 2/3 不受影响。
|
||||
>
|
||||
> **最终状态(2026-09-01)**:统一方案 v3 已全部实施(M0-M5,worktree
|
||||
> `zealous-gates-4626b1`,未提交)——StepMemoryService/ContextPolicy 均已按
|
||||
> 该方案落地;附录 B 中"保留、移除权属姊妹文档 §3.3"的 `short_term_memory`
|
||||
> 台账行已在 M5 核销移除(全部消费者先核销后删,台账纪律照本文档执行)。
|
||||
> 各里程碑实施标注见 history-module-redesign.md §7。
|
||||
|
||||
范围:State / ArtemisContext 的类型化与生命周期分域;感知数据通道;Flash 压缩机制上提为通用记忆内核。
|
||||
|
||||
底线与目标:**不损伤为最低要求**——每一处删除/搬迁都在附录 B 台账中核销其全部消费者,
|
||||
任何信息流不得静默中断;**优雅处理所有信息为目标**——每份数据有唯一属主、显式通道、
|
||||
声明的可见域,重复实现收敛为单一入口。
|
||||
|
||||
姊妹文档:`docs/design/pro-context-memory-redesign.md`(2026-08-25)深度覆盖了 Pro 侧
|
||||
"压缩什么、怎么压"(视觉摘要、历史分块、召回工具、token 预算)。本提案覆盖它没有处理的
|
||||
另一半——**数据放在哪、谁能看见什么、通道是否显式**——并在 §6 把两份文档的交叠处收敛为
|
||||
同一个记忆运行时。
|
||||
|
||||
---
|
||||
|
||||
## 1. 调查结论:现状与证据
|
||||
|
||||
### 1.1 实际存在四个上下文平面,只有一个有主人
|
||||
|
||||
| 平面 | 载体 | 生命周期 | 现状 |
|
||||
|---|---|---|---|
|
||||
| P1 图通道 | `State`(pydantic + `extra="allow"` + reducer) | 跨节点、跨回合 | 声明字段与实际使用严重脱节(见 1.2) |
|
||||
| P2 就地突变 | 同一个 State 实例被节点/工具直接赋值 | **节点内**(经 `InjectedState` 同实例) | 无声明的隐式契约;经核验其全部消费都是节点内的(见 5.3) |
|
||||
| P3 ctx 动态属性 | `ArtemisContext`(`extra="allow"`) | 进程/运行期 | `latest_perception_data` 等动态挂载,无 schema |
|
||||
| P4 持久存储 | DataEngine(步骤/截图/摘要)+ notes 文件 | 持久 | **唯一健康的平面**:单一写入路径,多消费者 |
|
||||
|
||||
Pro 的真实架构其实很好:Operator 每回合从 P4 **重新编译**全量 prompt
|
||||
(`operator.py:624` → `build_plan_and_history`),不靠累积消息。问题不在架构方向,
|
||||
而在 P1–P3 三个平面无 schema、无属主、互相兜圈子。
|
||||
|
||||
### 1.2 `extra="allow"` 已经吞掉的真 bug(本次调查实证)
|
||||
|
||||
**B1 幽灵键 `ui_tree`:4 个读取点,0 个写入点。**
|
||||
`operator.py:995`、`validator/tool_declarations.py:477`、`mcp/action_executor.py:401`、
|
||||
`tools/mobile/exec_tools.py:548` 均 `getattr(state, "ui_tree", None)`,但全仓无人写入
|
||||
(真实数据在 `latest_ui_hierarchy`)。后果:`utils/coordinates.py:185-215` 的
|
||||
"从层级树找主滚动容器"分支**永远走不到**,smart swipe 静默降级。
|
||||
|
||||
**B2 异步 Planner 驳回反馈被静默丢弃。**
|
||||
`graph.py:195` 把驳回理由写进 `validator_messages` 通道,但该通道**没有任何节点读取**
|
||||
(`operator.py:388` 和 `tool_wrapper.py:175` 只是当场拆 `Command.update` 信封,从不读
|
||||
State 通道)。后果:计划被回滚了,Operator 下一回合看到回滚后的计划,却永远看不到
|
||||
驳回理由。同时 `validator_messages` 经 `add_messages` 单调累积整轮所有 ToolMessage,
|
||||
无人消费、无人裁剪。
|
||||
|
||||
**B3 `ctx.latest_perception_data` 孤儿写。**
|
||||
`perception.py:187` 每轮把整张截图 b64 挂上 ctx,注释声称"给 Checker 零延迟复用";
|
||||
但工作区的 checker 重写已改为自己抓屏(`checker.py:620-652`)+ 从 DataEngine 取历史
|
||||
证据。全仓**零读取点**,纯内存驻留。
|
||||
|
||||
**B4 `indexed_elements` 只写实例不回通道。**
|
||||
`operator.py:649-650` 就地写两个字段,但 `operator.py:766` 的 update 只回传
|
||||
`indexed_points`,漏了 `indexed_elements`。审计确认跨节点消费实际不存在(validator 侧
|
||||
自行重推导,`action_executor.py:173-174`),但两键不对称是持续的误导源。
|
||||
|
||||
**B5 更多幽灵/半死键与坏路径。** 全表见附录 B。要点:
|
||||
约 9 个只初始化/只读不写的死键;`graph_runner.py:48` 的
|
||||
`getattr(self.ctx, "artemis_context", None)` 恒 None → 构造出丢失 data_engine 的空
|
||||
ArtemisContext;且 `reactive_runner.py` 的 `State(messages=[])` 缺必填字段构造即抛
|
||||
——engine 双 runner 的真机路径今日均必炸;`_active_driver` 未声明为 `PrivateAttr`。
|
||||
|
||||
(v2.1 更正:初版审计将 `operator_agent.py:63` 的 `task_goal` 与
|
||||
`failure_analyzer_agent.py:48-50` 的 `failure_context` 列为图 State 幽灵键,实为
|
||||
engine/BaseAgent 栈自己的 state 协议——`ExecutionContextState.task_goal` 存在
|
||||
(core/state.py:48),`.get()` 有 `isinstance(state, dict)` 防护。两处均无需修改。)
|
||||
|
||||
**B6 感知数据同屏最多 4 份拷贝、抓屏三处重复实现。**
|
||||
同一帧 b64 同时在 `ctx.latest_perception_data`(死)、`state.operator_raw_data`
|
||||
(走图通道且随 `astream` values 流每超步复制,`sdk/agent.py:694-696`)、operator 局部、
|
||||
prompt data-URL。抓屏管线在 perception_node(settle+OCR+融合)、validator 动作后轮询
|
||||
(`validator.py:117-140`)、checker 终检(`checker.py:620-652`)三处各写一遍,
|
||||
planner 首帧还有第四处自拍(`planner.py:211-236`,注意 planner 在图中先于 perception
|
||||
运行,这是常规路径不是边角)。
|
||||
|
||||
### 1.3 Flash 与 Pro 的压缩机制现状
|
||||
|
||||
| | Flash | Pro |
|
||||
|---|---|---|
|
||||
| 上下文模型 | 累积消息 + 每轮幂等原地压缩(`compress_flash_messages`) | 每回合从 DataEngine 重编译(`build_plan_and_history`) |
|
||||
| 步骤摘要 | `VisualStepSummarizer`:前后截图 + 动作红标叠加,客观中立措辞契约,异步无阻塞,无界重试 | `SummarizerNode`:**纯文本**(不加载 pre/post 图),3 次退避重试,无 flush 收尾语义 |
|
||||
| 摘要落点 | `data_engine.update_step_summary`(同一个 API!) | `data_engine.update_step_summary` |
|
||||
| 未就绪降级 | 摘要 pending → 保留原图(lossless pending) | 无 summary → detailed 渲染(同构原则的另一形态,`task_tree.py:917-921`) |
|
||||
| 历史可见性策略 | 无(30 轮上限内全量) | 三种互斥策略 + 6 个调用点各自硬编码 kwargs |
|
||||
|
||||
关键事实:**两条路径的摘要已经写进同一个 DataEngine 字段**。但两者的摘要语义结构性
|
||||
不同(见 6.1),统一的只能是运行时,不是语义。
|
||||
|
||||
---
|
||||
|
||||
## 2. 设计原则
|
||||
|
||||
1. **上下文是编译出来的,不是累积出来的。** Pro 已经如此;把它升格为正式模型,
|
||||
Flash 的消息列表只是同一份记忆的另一种渲染。
|
||||
2. **一份情景记忆,多种视图。** DataEngine 是唯一事实源;每个 agent 看到的是按其
|
||||
角色声明的"最高价值密度视图"。agent 的智能上限由输入质量决定。
|
||||
3. **可见域靠声明与类型约束。** `extra="forbid"` 拦截幽灵**写**与非法构造;幽灵**读**
|
||||
(`getattr` 带默认值不会因 forbid 报错)由节点 READS/WRITES manifest 的测试期
|
||||
代理拦截。两者缺一不可。
|
||||
4. **重物不过通道。** 截图 b64 不进 LangGraph 通道;通道里只放引用,重物放进有属主、
|
||||
有生命周期的 store。
|
||||
5. **就地突变不消灭,而是正名。** P2 平面的全部用途经审计都是"节点内工具通信"——
|
||||
给它一个声明的、节点局部的载体(TurnWorkspace),而不是假装它不存在。
|
||||
6. **后台增强不可成为依赖。**(继承姊妹文档 §4.6)摘要未就绪必有无损降级。
|
||||
7. **不损伤:删除必先核销。** 每个字段/属性/通道的处置都对照附录 B 台账,列明全部
|
||||
消费者与同步改动点;无消费者证据不删。
|
||||
|
||||
---
|
||||
|
||||
## 3. 方案 A:State 显式化与按 agent 可见域
|
||||
|
||||
### 3.1 State 收敛为四组显式字段,`extra="forbid"`
|
||||
|
||||
```python
|
||||
class State(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
|
||||
|
||||
# ── 控制面(跨回合、路由与信号)──────────────────────
|
||||
initial_goal: str
|
||||
injected_instruction: str | None # take_last
|
||||
user_stop_requested: bool # sticky_or
|
||||
checker_success: bool | None # take_last
|
||||
operator_feedback: list[str] | None # take_last;见 3.3
|
||||
exit_settlement_route: str | None # take_last
|
||||
run_outcome: dict | None # take_last
|
||||
|
||||
# ── 回合产物(上一回合的决策与结果)───────────────────
|
||||
current_step_id: str | None
|
||||
structured_decisions: str | None
|
||||
operator_raw_thinking: str | None
|
||||
operator_native_thinking: str | None
|
||||
short_term_memory: str | None # 活跃字段;移除权属于姊妹文档 §3.3
|
||||
last_execution_result: dict | None
|
||||
subagent_calls: list[str]
|
||||
operator_tool_limit_exceeded: bool | None
|
||||
|
||||
# ── 感知引用(重物不进通道,见方案 C)─────────────────
|
||||
perception: PerceptionRef | None # take_last
|
||||
|
||||
# ── 节点局部工作区(声明但非通道语义,见 5.3)─────────
|
||||
workspace: TurnWorkspace | None = None # 不进 update、不跨节点
|
||||
```
|
||||
|
||||
**处置总表见附录 B。** 与 v1 的差别:`short_term_memory` 保留(它是活跃读写对,
|
||||
operator.py:743-765 写 → prompts.py:331 读;删除属姊妹文档 Phase 1 的事,本方案
|
||||
不越权);`indexed_*` 不再声称"并入冻结快照"(见 5.3);`messages` 删除需同步改
|
||||
三处消费代码(`sdk/agent.py:1182-1187` 兜底输出、`sdk/agent.py:1321-1325` stderr
|
||||
打印、`engine/graph_runner.py:57` 入参——三处今日均为空转/坏路径,删除无信息损失,
|
||||
但必须同 PR 内改掉)。
|
||||
|
||||
`asanitize_update`(`state.py:152-159` 空壳直通)删除;其在 14 处工具里的调用
|
||||
随信封迁移(3.3)一并消失。
|
||||
|
||||
### 3.2 可见域:节点声明读/写集,测试期强制
|
||||
|
||||
每个节点类声明清单,运行期零开销,测试期强制:
|
||||
|
||||
```python
|
||||
class OperatorNode:
|
||||
READS = frozenset({"initial_goal", "perception", "operator_feedback",
|
||||
"short_term_memory", "subagent_calls", "current_step_id",
|
||||
"operator_tool_limit_exceeded", "injected_instruction",
|
||||
"structured_decisions"})
|
||||
WRITES = frozenset({"structured_decisions", "operator_raw_thinking",
|
||||
"operator_native_thinking", "short_term_memory",
|
||||
"current_step_id", "subagent_calls",
|
||||
"operator_tool_limit_exceeded"})
|
||||
```
|
||||
|
||||
- 新增 `artemis/graph/visibility.py`:`StateView(state, reads)` 代理,未声明字段访问
|
||||
即抛 `VisibilityError`;`check_update(update, writes)` 校验返回键。
|
||||
- 仅在测试与 `ARTEMIS_STRICT_STATE=1` 下启用;生产路径直接传 State。
|
||||
- 附录 B 的实证读写清单即初始 manifest;上线先按现状放宽,再逐步收紧。
|
||||
- **幽灵读的防线在这里**,不在 `extra="forbid"`(forbid 管不住 `getattr(x, k, None)`)。
|
||||
|
||||
### 3.3 工具信封与反馈通道拆分(修 B2)
|
||||
|
||||
`validator_messages` 承担两个不相干职责,拆开:
|
||||
|
||||
**1)工具返回信封 → 显式返回类型。**
|
||||
审计确认全部 14 个工具写入点(`exec_tools.py` 5 处、`scratchpad.py` 6 处、
|
||||
`launch_app.py:191`、`ocr.py:185`、`video_tool.py:161`、`object_detector.py:210,227`)
|
||||
的 `Command.update` **只含**这一个键(以 `exec_tools.py:215-221` 为样本核验),
|
||||
迁移是纯机械的:工具直接返回 `ToolOutcome(content, status)`,
|
||||
`tool_wrapper.get_tool_result_content`(`tool_wrapper.py:175-176`)与 operator 循环
|
||||
(`operator.py:388-391`)的拆信封逻辑同步简化。`graph.py:526` 的计划驳回信封同理
|
||||
(它经 `_process_plan_write` 返回给 operator 循环当场消费,属节点内信封)。
|
||||
通道及 `add_messages` 无界累积随之消失。
|
||||
|
||||
**2)跨回合反馈 → 统一 `operator_feedback` 通道。**
|
||||
`check_feedback` 更名并扩容为 `operator_feedback`:来源包括 checker harvest
|
||||
(`graph.py:198,230`)、exit settlement 弹回(`graph.py:367`)、以及**新接入的**
|
||||
异步 Planner 驳回(`graph.py:195` 改写到这里,条目带 `[planner]` 前缀,与现有
|
||||
`[final check]`/`[verify failed]` 前缀惯例一致)。渲染组件更名
|
||||
`FeedbackPromptComponent`,提示语从"来自独立 Checker"改为按条目前缀中性表述。
|
||||
现有读者同步改名:operator prompt(`prompts.py:243`)与 diagnoser
|
||||
(`diagnoser.py:178`)——diagnoser 因此也能看到 planner 驳回上下文,属增益。
|
||||
|
||||
---
|
||||
|
||||
## 4. 方案 B:ArtemisContext 分解为组合根
|
||||
|
||||
ctx 保留为唯一入口(避免全仓签名大改),字段按属主聚类成显式子对象,
|
||||
`extra="forbid"`:
|
||||
|
||||
```python
|
||||
class ArtemisContext(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
|
||||
|
||||
# 基础设施(启动后只读)
|
||||
device: DeviceContext
|
||||
llm_config: LLMConfig | None
|
||||
model_router: Any | None
|
||||
agent_config: Any
|
||||
adb_client / ui_adb_client / actuator / action_session / mcp_*
|
||||
|
||||
# 运行服务(有生命周期、有属主)
|
||||
execution_setup: ExecutionSetup | None
|
||||
data_engine: DataEngine | None
|
||||
perception: PerceptionStore # 方案 C,取代 latest_perception_data
|
||||
step_memory: StepMemoryService | None # 方案 D,统一摘要运行时
|
||||
background: BackgroundTaskGroup # 吸收 background_tasks/jobs + 宽限期
|
||||
|
||||
# 编排状态(从散字段聚成对象)
|
||||
checkpoints: CheckpointLedger # 吸收 pending_checkpoints/checkpoint_tasks/
|
||||
# attempt_seq/repairs/final_check_attempts/assert_halt
|
||||
plan_governor: PlanGovernor # 吸收 task_plan_content_before/
|
||||
# last_validated_plan/pending_validated_plan/planner_task
|
||||
|
||||
run_outcome: dict | None = None # 显式化(今日为 sdk/agent.py:721 动态挂载)
|
||||
package_cache: dict[str, str | None]
|
||||
_active_driver: Any | None = PrivateAttr(default=None)
|
||||
```
|
||||
|
||||
`CheckpointLedger` 与 `PlanGovernor` 不是新逻辑,只是把 `graph/checkpoints.py` 和
|
||||
`graph/graph.py::_process_plan_write` 已在操作的字段簇搬进属主类型。
|
||||
`ExecutionSetup` 审计后可直接上 `forbid`(唯一运行期赋值 `app_lock_status` 为已声明
|
||||
字段,`sdk/agent.py:997`)。`graph_runner.py:48` 的 `artemis_context` 恒 None 坏路径
|
||||
在 Phase 0 修复(显式传入或显式报错,禁止静默构造空 ctx)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 方案 C:感知走显式通道(PerceptionStore + TurnWorkspace)
|
||||
|
||||
### 5.1 数据模型
|
||||
|
||||
```python
|
||||
class PerceptionSnapshot(BaseModel):
|
||||
"""一帧感知的产物。frozen:每次捕获生成新快照,绝不就地改。"""
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
step_id: str | None # 关联的 DataEngine step(轮询帧可为 None)
|
||||
image_name: str # sha256,DataEngine 图片主键
|
||||
screenshot_path: str | None
|
||||
screenshot_b64: str # 重物:只存在于 store,不进图通道
|
||||
width: int
|
||||
height: int
|
||||
captured_at: float
|
||||
kind: Literal["decision", "post_action", "probe"]
|
||||
# 完整管线产物(raw 帧为 None)
|
||||
raw_xml: list[dict] | None
|
||||
fused_elements: list[dict] | None # OCR+XML 融合
|
||||
ocr_results: list | None
|
||||
minimal_list: str | None # 派生物在此算一次,消费者不再各自重算
|
||||
|
||||
class PerceptionRef(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
step_id: str | None
|
||||
image_name: str
|
||||
width: int
|
||||
height: int
|
||||
|
||||
class PerceptionStore:
|
||||
"""ctx.perception。环形缓存最近 N=4 帧;更早的按 image_name/step_id 从
|
||||
DataEngine 回捞重建(b64 懒加载)。"""
|
||||
async def capture_full(self, *, settle: bool, kind: str) -> PerceptionSnapshot: ...
|
||||
async def capture_raw(self, *, kind: str) -> PerceptionSnapshot: ...
|
||||
def put(self, snap: PerceptionSnapshot) -> PerceptionRef: ...
|
||||
def latest(self, *, full_only: bool = False) -> PerceptionSnapshot | None: ...
|
||||
def get(self, ref: PerceptionRef | str) -> PerceptionSnapshot | None: ...
|
||||
```
|
||||
|
||||
**抓屏统一收口**:现有三处重复管线 + planner 自拍全部改走 store——
|
||||
|
||||
| 现调用点 | 改为 | 备注 |
|
||||
|---|---|---|
|
||||
| `perception_node`(settle+OCR+融合,`perception.py:116-168`) | `capture_full(settle=..., kind="decision")` | settle 启发式与 OCR/融合逻辑原样搬入 store,行为不变 |
|
||||
| validator 动作后轮询(`validator.py:117-140`) | 轮询保持轻量私有;**最终帧** `capture_raw(kind="post_action")` 入 store | 轮询中间帧不入 store,避免噪音;最终帧成为可引用证据(今日被就地写进 `state.latest_screenshot` 后即失名) |
|
||||
| checker 终检自拍(`checker.py:620-652`) | `capture_full(kind="probe")` | 其 OCR+裁剪逻辑与 perception 重复,删除 |
|
||||
| planner 首帧自拍(`planner.py:211-236`) | `capture_full(kind="decision")` | planner 先于 perception 运行是常规路径,必须显式支持 |
|
||||
|
||||
由此**每一帧都自动进入环形缓存与 DataEngine**,异步消费者(Checker 检查点)持
|
||||
`PerceptionRef`/`image_name` 而非活对象——缓存被挤掉也能从 DataEngine 重建,
|
||||
天然解决"异步任务引用过期感知"的悬挂问题。
|
||||
|
||||
### 5.2 通道改造
|
||||
|
||||
- `perception_node`:`capture_full` → `put()` → update 只含
|
||||
`{"perception": ref, "injected_instruction": ..., "user_stop_requested": ...}`。
|
||||
删除 `ctx.latest_perception_data`(B3)与 `operator_raw_data` 过通道(B6:
|
||||
b64 不再随 `astream` values 每超步复制)。
|
||||
- 消费者一律 `ctx.perception.get(state.perception)` 或 `latest()`:
|
||||
- **Operator**:快照直取 `minimal_list`/`fused_elements`,不再自算;
|
||||
- **Validator 内工具**(object_detection 兜底 `object_detection_tool.py:93` 今日读
|
||||
validator 就地写的 `latest_screenshot`):改读 `latest()`——validator 每次动作后
|
||||
`capture_raw` 入 store,时序语义与今日一致;
|
||||
- **Diagnoser/Committee/Explorer/FA**:同一入口,删除各自的文件重读与 getattr 链;
|
||||
- **Outputter**:今日读最终 State 的 `operator_raw_data`(最后决策帧)。改为
|
||||
`latest()`(最后 post_action 帧,比决策帧更新,属增益);若需保持逐字节兼容,
|
||||
改读 `get(final_state.perception)` 即决策帧——落地时二选一并记录;
|
||||
- 幽灵键 `ui_tree` 的 4 个读取点改读 `snapshot.fused_elements`(B1 终态修复;
|
||||
Phase 0 先直改 `latest_ui_hierarchy` 止血)。
|
||||
|
||||
### 5.3 TurnWorkspace:给 P2 平面正名(修 B4 的正确姿势)
|
||||
|
||||
审计结论:`indexed_points`/`indexed_elements` 是**回合内可变工作集**——
|
||||
explorer 增量 append 候选(`explorer_tool.py:311-315`),validator/FA 在动作后
|
||||
整体重推导(`action_executor.py:173-174`);且全部消费都发生在**产生它的节点内部**
|
||||
(operator 在返回前就把索引翻译成坐标;validator 侧自给自足)。跨节点传递从未
|
||||
被需要——B4 之所以没被发现,正因为如此。
|
||||
|
||||
因此它们**不属于冻结快照,也不属于图通道**,属于节点局部工作区:
|
||||
|
||||
```python
|
||||
class TurnWorkspace(BaseModel):
|
||||
"""节点局部的可变工作集。经 InjectedState 对同节点工具可见;
|
||||
不进 update、不跨节点、每节点从快照重播种。"""
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
indexed_points: list[list[int]] = []
|
||||
indexed_elements: list[dict] = []
|
||||
|
||||
@classmethod
|
||||
def seed_from(cls, snap: PerceptionSnapshot) -> "TurnWorkspace": ...
|
||||
def reseed(self, snap: PerceptionSnapshot) -> None: ... # validator/FA 重观察
|
||||
def extend_candidates(self, cands: list[dict]) -> None: ... # explorer 增量
|
||||
```
|
||||
|
||||
- `state.workspace` 为声明字段但**永不出现在任何节点的 update 里**(visibility
|
||||
manifest 强制),每个需要它的节点入口自行 `seed_from(store.get(state.perception))`。
|
||||
- 隐式契约("恰好同一实例")就此变成显式契约("声明的节点局部容器 + 三个具名操作")。
|
||||
- `state.indexed_points`/`indexed_elements`/就地写 `latest_screenshot`/`operator_raw_thinking`
|
||||
等全部 P2 突变点随之收编(thinking 就地写是给工具内 `record_step` 读的,同为
|
||||
节点内通信,归入 workspace 或显式传参,落地时按触点少者取舍)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 方案 D:记忆内核统一(Flash 压缩上提)
|
||||
|
||||
### 6.1 一个运行时,两份摘要契约
|
||||
|
||||
两个摘要器的差异是**结构性的**,不能合并语义:
|
||||
|
||||
- Flash 的摘要是**证据替身**——消息流保留了模型全部推理原文,压缩器只摘图,
|
||||
摘要因此必须是纯客观的视觉转换描述,掺入意图即是污染;
|
||||
- Pro 的摘要是**整步胶囊**——`build_plan_and_history` 丢弃旧步骤的全部思考,
|
||||
一行摘要+精确动作+Validator 结果就是该步的全部遗留,因此必须压缩意图/策略/
|
||||
进度(这正是姊妹文档 §8.2 坚持 `summarizer.md` 为语义唯一权威的原因)。
|
||||
|
||||
因此统一的是**机制层**,分离的是**语义层**。新建
|
||||
`artemis/memory/step_memory.py`:
|
||||
|
||||
- **共享运行时 `StepMemoryService`**(合并两边的调度骨架):
|
||||
- 键:`step_id`(Flash 的 `tool_call_id` 保留为别名映射;`dispatch` 已有
|
||||
`data_engine_step_id` 参数,接线即可);
|
||||
- 调度:零阻塞 dispatch + **有界**重试 + 显式降级态(替换 Flash 的无界重试与
|
||||
Pro 的 3 次退避;此为有意的行为变更,见 §8)+ `flush(timeout)` 收尾语义
|
||||
(Pro 目前 run 结束时未完成摘要行为未定义,直接补上);
|
||||
- 落点:`data_engine.update_step_summary`;状态机(pending/ready/failed)与
|
||||
版本化按姊妹文档 §7.1;lossless-pending 原则由消费侧共同遵守;
|
||||
- 素材来源:pre/post 图直接以 `PerceptionRef` 引用(方案 C 的红利:不再在
|
||||
dispatch 参数里搬运 bytes,`summarizer.py:92-100` 的 `_step_inputs` 图字节
|
||||
缓存与手动置 None 回收逻辑消失)。
|
||||
- **两份摘要契约(lens),各 profile 只跑自己的**:
|
||||
- `VisualTransitionLens`(Flash):输入 pre/post 截图(动作红标叠加)+ 动作 +
|
||||
Controller 结果;产出客观视觉转换,`flash_summarizer.md` 禁判定词契约不变;
|
||||
- `StepCapsuleLens`(Pro):输入按姊妹文档 §8.1(决策图 + 可选后图 + 动作 +
|
||||
Validator 结果 + FA 记录 + 当步思考 + 少量近期摘要);产出意图/策略/进度
|
||||
胶囊,`summarizer.md` 为语义权威,视觉仅作接地、仍禁自行判定成败
|
||||
(姊妹文档 §4.5 事实优先级)。
|
||||
- 单 profile 单 lens,`update_step_summary` 单字段无冲突;未来若 Pro 需同时保留
|
||||
视觉转换记录,按姊妹文档 §7.1 版本化元数据加 lens 标记,不在本期范围。
|
||||
- Pro 图中 `SummarizerNode` 退化为 `dispatch(StepCapsuleLens)` 一行调用;
|
||||
Flash `runner.py` 同理换成 `dispatch(VisualTransitionLens)`。
|
||||
|
||||
### 6.2 两个渲染器,一张策略表
|
||||
|
||||
渲染器保持各自形态(合理的方言差异,不强行同构):
|
||||
|
||||
- `compress_flash_messages`:消息列表原地压缩,摘要后端换成共享运行时;
|
||||
- `build_plan_and_history`:编译式渲染,无改动即消费到视觉接地摘要。
|
||||
|
||||
把 6 个调用点散落的硬编码 kwargs 收拢为**每 agent 一条声明**,新建
|
||||
`artemis/memory/context_policy.py`:
|
||||
|
||||
```python
|
||||
CONTEXT_POLICIES: dict[str, ContextPolicy] = {
|
||||
"operator": ContextPolicy(strategy="strict_milestone", last_n_detailed=1, recent_window=3, chronological_last=True),
|
||||
"planner": ContextPolicy(strategy="strict_milestone", last_n_detailed=2, recent_window=5, chronological_last=True),
|
||||
"failure_analyzer": ContextPolicy(strategy="strict_milestone", last_n_detailed=1, recent_window=3, for_failure_analyzer=True),
|
||||
"checker": ContextPolicy(strategy="milestone_whitelist"),
|
||||
"diagnoser": ContextPolicy(strategy="milestone_whitelist", window_from_config="history_window_steps"),
|
||||
"committee": ContextPolicy(strategy="milestone_whitelist", last_n_detailed=3),
|
||||
"outputter": ContextPolicy(strategy="full", last_n_detailed=0),
|
||||
"summarizer": ContextPolicy(strategy="sliding_window", last_n_detailed=1, min_summaries=10),
|
||||
}
|
||||
```
|
||||
|
||||
这张表与 3.2 的 READS/WRITES manifest 互为镜像:一个声明"看 State 的哪些字段",
|
||||
一个声明"看历史的哪个投影"。合起来就是"按 agent 划分可见域"的完整答案,
|
||||
也是今后调优各 agent 输入质量的唯一改动点。收拢时**逐调用点对拍**:策略表渲染
|
||||
输出与现 kwargs 输出必须一致(对拍测试进 CI)。
|
||||
|
||||
### 6.3 与姊妹文档的配置收敛
|
||||
|
||||
姊妹文档 §14 主张 Pro 单独 `agent.pro.memory`、不复用 Flash `step_summarizer`。
|
||||
本方案在语义层同意(lens 分离),仅在**运行时层**共享:
|
||||
|
||||
```jsonc
|
||||
"agent": {
|
||||
"memory": { // 共享运行时:重试/flush/并发 + 策略表覆盖
|
||||
"runtime": { "max_concurrency": 1, "retry_limit": 3, "flush_timeout_s": 30 },
|
||||
"policies": { "operator": { "recent_window": 3 } }
|
||||
},
|
||||
"flash": { "step_summarizer": { /* VisualTransitionLens:模型、开关(现有键保持兼容) */ } },
|
||||
"pro": { "memory": { /* StepCapsuleLens 模型/输入项 + token 预算等,per 姊妹文档 §14 */ } }
|
||||
}
|
||||
```
|
||||
|
||||
姊妹文档的 L3 历史分块、`recall_history`、token 预算不变,全部建立在同一个
|
||||
`StepMemoryService` 产物之上。
|
||||
|
||||
---
|
||||
|
||||
## 7. 分阶段落地(已按依赖重排,消除 v1 的顺序隐患)
|
||||
|
||||
**Phase 0 — 修 bug(不动架构,可独立合入,零行为风险)** ✅ 已实施 2026-08-31
|
||||
1. B1 止血:4 处 `ui_tree` → `latest_ui_hierarchy`(终态在 Phase 2 改快照)。✅
|
||||
2. B2:`graph.py` 驳回反馈改走现有 `check_feedback` 通道(条目带 `[planner]`
|
||||
前缀,弃写无人读的 validator_messages),`CheckFeedbackPromptComponent`
|
||||
提示语按前缀中性化(标题保持 "Verification Findings" 不变,兼容既有断言)。✅
|
||||
3. B3:删 `ctx.latest_perception_data` 写入与过期注释。✅
|
||||
4. B4 止血:operator update 补回 `indexed_elements`(消除两键不对称;终态在
|
||||
Phase 2 收编进 workspace)。✅
|
||||
5. B5:`_active_driver` → `PrivateAttr`;新增 `State.initial()` 工厂统一初始状态
|
||||
构造(sdk `_get_graph_state` 与 engine 双 runner 三处收拢单源);
|
||||
`ExecutionContext.artemis_context` 显式声明;`graph_runner`/`reactive_runner`
|
||||
真机路径缺 ctx 时显式 RuntimeError(替代静默构造空 ctx 后期炸)。
|
||||
(task_goal / failure_context 两项经复核为误报,见 §1.2 v2.1 更正。)✅
|
||||
|
||||
验证:目标子集 99/99 通过;全量 `tests/unit + tests/tools` 1072 通过、
|
||||
5 失败均为真实 LLM 联网测试的 API key 环境问题(与改动无关)。
|
||||
|
||||
**Phase 1 — State 显式化(仅动经台账核销的死键与信封)** ✅ 已实施 2026-08-31
|
||||
|
||||
实施要点(与下述计划的差异):信封迁移最终形态为**工具直接返回 ToolMessage**
|
||||
(承载 content/status/tool_call_id,未传 state 时仍返回纯字符串),未引入独立的
|
||||
ToolOutcome 类型——ToolMessage 已是 LangChain 原生形态,语义完全覆盖;
|
||||
`_process_plan_write` 的拒绝路径返回 status="success" 的 ToolMessage 以逐字节保持
|
||||
旧信封行为(SystemMessage 无 status 属性 → 旧路径视为 success)。visibility
|
||||
manifest 已接线至全部 7 个节点入口(`strict_state`,默认关闭零开销),
|
||||
契约测试 tests/unit/graph/test_state_contract.py 11 项覆盖往返/forbid/代理/清单。
|
||||
验证:tests/unit 全量 1067 通过 0 失败;tests/tools 仅余 5 个既有 API key
|
||||
环境失败(与 Phase 0 前完全一致)。测试改写含 test_operator.py 12 处
|
||||
asanitize 断言 → 返回值断言、15 个工具测试文件 Command→ToolMessage。
|
||||
- 删真死键:`messages`(同步改 `sdk/agent.py:1182-1187`、`:1321-1325`、
|
||||
`graph_runner.py:57`、`_get_graph_state`)、`remaining_steps`、`focused_app_info`、
|
||||
`device_date`、`subgoal_plan`、`operator_tactical_plan`、`complete_subgoals_by_ids`
|
||||
(同步改 `operator.py:767` 与 `_get_graph_state`)、`current_agent`(scratchpad
|
||||
两处读改固定 `"operator"` 或显式传参)、`operator_replan_reason`(committee 去引用)。
|
||||
- **不删**:`short_term_memory`(活跃;移除并入姊妹文档 Phase 1 落地时执行)、
|
||||
感知相关键(归宿在 Phase 2)。
|
||||
- 信封迁移:14 个工具写入点 → `ToolOutcome` 直返;`operator.py:388-391` 与
|
||||
`tool_wrapper.py:175-176` 拆信封逻辑简化;删 `validator_messages` 通道与
|
||||
`asanitize_update`。
|
||||
- `check_feedback` → `operator_feedback` 更名(operator prompt + diagnoser 两读者
|
||||
同步)。
|
||||
- `extra="forbid"` 上 State + `State(**values)` 往返测试(`sdk/agent.py:696` 路径);
|
||||
visibility manifest + StateView 测试强制(先按附录 B 现状放宽,再收紧)。
|
||||
|
||||
**Phase 2 — PerceptionStore + TurnWorkspace**
|
||||
- store/快照/引用/环形缓存;四个抓屏点收口(§5.1 表);消费者逐个切换;
|
||||
- 删 `operator_raw_data`、`latest_ui_hierarchy`、`latest_screenshot`、
|
||||
`indexed_points`、`indexed_elements`(State 通道侧);新增 `perception` ref 与
|
||||
`workspace`;
|
||||
- outputter 帧选择决策(post_action vs 决策帧)落地并记录。
|
||||
|
||||
**Phase 3 — ctx 分解**
|
||||
`CheckpointLedger` / `PlanGovernor` / `BackgroundTaskGroup`;`run_outcome` 显式化;
|
||||
`extra="forbid"` 上 ArtemisContext / DeviceContext / ExecutionSetup / AppLaunchResult。
|
||||
|
||||
**Phase 4 — 记忆内核**
|
||||
`StepMemoryService` 统一运行时、双 lens 分立语义(Pro 侧 `StepCapsuleLens` 即
|
||||
姊妹文档 Phase 2 的实现载体);策略表收拢(逐点对拍);配置收敛。之后按姊妹文档
|
||||
继续 L3 分块与 recall。
|
||||
|
||||
每阶段独立可回滚。Phase 0/1 以台账+现有单测护航;Phase 2 起加对拍测试。
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收
|
||||
|
||||
**不损伤(最低要求)**
|
||||
- 附录 B 台账全表核销:每个字段/属性的每个消费者都有对应改动与测试;CI 中
|
||||
visibility manifest 测试保证幽灵读为零。
|
||||
- `State(**values)` 往返(`astream` values 流)在 `forbid` 下全绿。
|
||||
- Flash:成功路径下压缩器输出与现版**逐字节对拍一致**;重试上界与 flush 属有意
|
||||
变更,单独测试覆盖(失败注入下有界退出 + 降级态可见)。
|
||||
- Pro:`build_plan_and_history` 策略表收拢后与现 6 处调用点输出逐点对拍一致。
|
||||
- 性能红线沿用姊妹文档 §18(P50/P95 输入与延迟回归 ≤3–5%,关键路径零摘要等待)。
|
||||
|
||||
**优雅处理所有信息(目标)**
|
||||
- 全部 `extra="forbid"`;抓屏管线唯一实现;图通道序列化不再含任何 b64
|
||||
(对照:当前 `operator_raw_data` 每超步随 values 流复制)。
|
||||
- Planner 驳回后,Operator 与 Diagnoser 的下一 prompt 可见驳回理由(新增回归测试)。
|
||||
- validator 的 post-action 帧从"就地写路径后失名"变为可引用证据(store + DataEngine)。
|
||||
- Pro 长任务(>30 步)历史出现视觉接地摘要;每个 agent 的输入面由 manifest +
|
||||
ContextPolicy 两张表完整声明。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:agent → State 读/写实证清单(2026-08-31 工作区)
|
||||
|
||||
(visibility manifest 初始值;来源为本次全仓扫描。)
|
||||
|
||||
| 节点 | 读 | 写 |
|
||||
|---|---|---|
|
||||
| perception | structured_decisions | latest_screenshot, latest_ui_hierarchy, operator_raw_data, injected_instruction, user_stop_requested |
|
||||
| planner | latest_screenshot, initial_goal | (无通道写;就地突变 latest_screenshot) |
|
||||
| operator | operator_raw_data, latest_ui_hierarchy, indexed_elements, subagent_calls, current_step_id, initial_goal, check_feedback, short_term_memory, operator_tool_limit_exceeded, injected_instruction | structured_decisions, operator_raw_thinking, operator_native_thinking, short_term_memory, indexed_points, complete_subgoals_by_ids, current_step_id, subagent_calls, operator_tool_limit_exceeded |
|
||||
| execution_check | operator_raw_data, structured_decisions, operator_*_thinking, user_stop_requested, initial_goal | checker_success, validator_messages, structured_decisions, current_step_id, check_feedback, subagent_calls |
|
||||
| validator | structured_decisions, latest_screenshot, current_step_id, operator_raw_data, operator_*_thinking, indexed_points, indexed_elements | last_execution_result(+就地突变 latest_screenshot, indexed_*) |
|
||||
| summarizer | current_step_id, structured_decisions, operator_*_thinking, last_execution_result | (无;异步写 DataEngine) |
|
||||
| exit_settlement | user_stop_requested, initial_goal | exit_settlement_route, check_feedback, run_outcome |
|
||||
| checker | (不读 State) | (不写 State;ctx.assert_halt + ledger) |
|
||||
| diagnoser | initial_goal, check_feedback, latest_screenshot, latest_ui_hierarchy | (无) |
|
||||
| outputter | initial_goal, operator_raw_data | (无) |
|
||||
| explorer 工具 | operator_raw_data, latest_screenshot, latest_ui_hierarchy, indexed_* | (就地 append indexed_*) |
|
||||
| failure_analyzer | latest_ui_hierarchy, initial_goal | (就地重推导 indexed_*) |
|
||||
| committee 工具 | latest_screenshot, initial_goal | (无) |
|
||||
| video/object_detector/log/history_analyzer | (不读或仅透传;object_detection 兜底读 latest_screenshot) | validator_messages(信封用法) |
|
||||
|
||||
## 附录 B:信息处置台账("不损伤"核销表)
|
||||
|
||||
### B.1 State 字段
|
||||
|
||||
| 字段 | 消费者(证据) | 处置 | 阶段 | 同步改动点 |
|
||||
|---|---|---|---|---|
|
||||
| initial_goal | 多节点读 | 保留 | — | — |
|
||||
| injected_instruction | perception 写 / operator prompt 读 | 保留 | — | — |
|
||||
| user_stop_requested | perception 写 / gate、checkpoints、plan 守卫读 | 保留 | — | — |
|
||||
| checker_success | execution_check 写 / 两处 gate 读 | 保留 | — | — |
|
||||
| check_feedback | graph.py:198,230,367 写 / prompts.py:243、diagnoser.py:178 读 | 更名 operator_feedback,扩容纳 planner 驳回 | P0(接入)+P1(更名) | 写 3 处、读 2 处、组件提示语 |
|
||||
| run_outcome | graph.py:367 写 / sdk/agent.py:720 读 | 保留;ctx 侧动态挂载显式化 | P3 | sdk/agent.py:721 |
|
||||
| exit_settlement_route | exit_settlement 写 / gate 读 | 保留 | — | — |
|
||||
| current_step_id / structured_decisions / operator_*_thinking / last_execution_result / subagent_calls / operator_tool_limit_exceeded | 活跃读写对(附录 A) | 保留 | — | — |
|
||||
| short_term_memory | operator.py:743-765 写 / prompts.py:331 读 | ~~**保留**;移除权属姊妹文档 §3.3~~ **已移除(M5,2026-09-01)**:全链核销——State 字段/operator 提取写块/prompt 组件/manifest 读写集/operator.json 指令段;渲染侧旧标签剥除保留 | — | — |
|
||||
| messages | 写:无(Pro);读:sdk/agent.py:1182(空转)、:1321(空转);graph_runner.py:57 写(坏路径) | 删除 | P1 | 读 2 处、写 1 处、_get_graph_state |
|
||||
| validator_messages | 14 工具信封写 + graph.py:195 跨回合写;通道 0 读 | 信封→ToolOutcome;:195→operator_feedback;删通道 | P0(:195)+P1(信封) | 14 工具 + tool_wrapper:175 + operator:388 + graph:526,595 |
|
||||
| remaining_steps | 仅 init(sdk:1201);上限实际由 recursion_limit 承载(sdk:674) | 删除 | P1 | _get_graph_state |
|
||||
| focused_app_info / device_date | 仅 init(sdk:1197-1198) | 删除 | P1 | _get_graph_state |
|
||||
| subgoal_plan / operator_tactical_plan | 仅 init / 零引用 | 删除 | P1 | _get_graph_state |
|
||||
| complete_subgoals_by_ids | operator:767 写 []、init、0 读 | 删除 | P1 | operator:767、_get_graph_state |
|
||||
| current_agent | scratchpad:166,260 等读(恒回退 "operator"),0 写 | 删除 | P1 | scratchpad 固定值或显式传参 |
|
||||
| operator_replan_reason | committee_tool:203 读(恒 None),0 写 | 删除 | P1 | committee 去引用 |
|
||||
| latest_ui_hierarchy | perception 写 / operator:614、diagnoser:193、FA:419、explorer:1595 读 | → snapshot.fused_elements | P2 | 4 读者切 store |
|
||||
| latest_screenshot | perception/planner/validator/flash 写 / validator:98、diagnoser:186、committee:138、explorer:268、object_detection:93 读 | → snapshot + capture 收口 | P2 | §5.1 表全部触点 |
|
||||
| operator_raw_data | perception 写 / operator:610、validator:618、explorer:290、outputter:139 读 | → PerceptionRef + store | P2 | 4 读者切 store;outputter 帧选择决策 |
|
||||
| indexed_points / indexed_elements | 节点内写读(operator/validator/FA/explorer/action_executor,附录 A) | → TurnWorkspace(节点局部) | P2 | §5.3 全部触点 |
|
||||
| (幽灵)ui_tree | 4 读 0 写 | P0 止血→latest_ui_hierarchy ✅;P2 终态 | P0/P2 | operator:995、tool_declarations:477、action_executor:401、exec_tools:548 |
|
||||
| (更正)failure_context | engine/BaseAgent 栈自有协议,`.get()` 有 isinstance 防护 | 误报,不动 | — | — |
|
||||
| (更正)task_goal | `ExecutionContextState.task_goal` 存在(core/state.py:48) | 误报,不动 | — | — |
|
||||
| (幽灵)metadata | sdk/types/task.py:223 对 finalize 快照 dict.get(恒 None) | 无害;随 finalize 清理顺手删 | P1 | 1 处 |
|
||||
|
||||
### B.2 ArtemisContext 属性
|
||||
|
||||
| 属性 | 消费者 | 处置 | 阶段 |
|
||||
|---|---|---|---|
|
||||
| latest_perception_data | 0 读(HEAD 版 checker 曾读,工作区已删) | 删除写入 + 注释 | P0 |
|
||||
| run_outcome(动态) | sdk:1107 读 → trace 后缀 | 显式字段 | P3 |
|
||||
| _active_driver | drivers/factory:75-78、tools/base:104、mock:55、4 处测试 | → PrivateAttr | P0 |
|
||||
| pending_checkpoints / checkpoint_tasks / checkpoint_attempt_seq / checkpoint_repairs / final_check_attempts / assert_halt | graph/checkpoints.py 族 | → CheckpointLedger | P3 |
|
||||
| task_plan_content_before / last_validated_plan / pending_validated_plan / planner_task | graph.py:_process_plan_write / execution_check | → PlanGovernor | P3 |
|
||||
| background_tasks / background_jobs / grace_period | ctx.__aexit__、summarizer:66、diagnoser:234 | → BackgroundTaskGroup | P3 |
|
||||
| 其余声明字段 | 基础设施 | 保留(分组) | P3 |
|
||||
| (engine 侧)ExecutionContext.artemis_context | graph_runner:48、reactive_runner:57 读,0 写 | 显式接线或显式报错 | P0 |
|
||||
@@ -1,141 +0,0 @@
|
||||
# 历史模块统一方案 v3:最终真机验收报告(2026-09-01)
|
||||
|
||||
状态:已完成(真机 Pixel 10 / 59100DLCR0033X,worktree `zealous-gates-4626b1` 未提交代码,M0-M5+硬化轮全量、默认开形态)
|
||||
|
||||
对应任务:`docs/plans/history-module-redesign.md` §7 M5 要点"最终残留"中的真机三件套 + 默认开复测 A/B + recall include_images 端到端 + 相似度提示抽查(五项)。
|
||||
|
||||
---
|
||||
|
||||
## TLDR
|
||||
|
||||
在 Pixel 10 真机上以出厂默认配置(`agent.memory.transcript.enabled=true`)完成五项最终验收:**全部通过(样本量有限处为方向性结论)**。要点:
|
||||
|
||||
1. **默认开复测 A/B(4 对任务,同日同模型 gemini-3.6-flash)**:转写路径回退 **0 次**(11 个 flag-on 会话全程在岗);全回合延迟 P50 8.5s→3.4s(−60%)、缓存命中调用 40%→86%、输出 token −31%、成功率 4/4 vs 4/4 持平——上一轮 A/B 的收益方向在冷启动修复+就绪门控+并发 2 的最终形态下完整复现。
|
||||
2. **段头就绪门控病理消失**:上一轮"21/21 chunk 段头全 pending"→ 本轮全部 flag-on 会话 **60/60 个 chunk 范围段头就绪**(就绪率 100%),关段→就绪落库(即交换时刻)中位 8–12s ≈ 1–2 回合。
|
||||
3. **[Loop:continuous] 112 步长跑**:无摘要堆叠(6 era + 8 chunk 分层正常)、token 稳态有界(上下文基数峰值 26.3K = 预算 33%)、注入指令逐字保全(chunk ③ 与最终渲染提示中均在场)、release_loop 一次生效。
|
||||
4. **capsule 盲评(48 个就绪段头全量机检 + 5 个人工精查)**:禁判定词违规 1/48("entered in the search bar",描述搜索框文字输入,语义无害);无编造;笔记关联与 ② 区间覆盖全部忠实。
|
||||
5. **recall include_images 端到端打通**:data-URL 图片块(128KB)经 ToolMessage 进入 3 次连续 Gemini 调用全部 success,Operator 消费后写出正确笔记。
|
||||
6. **相似度提示**:真实重访场景正确注入(指向 Step 2/4);同屏等待循环 60+ 步 0 次注入(近 3 步静默规则有效)。
|
||||
|
||||
**两条结论建议见 §8:flag 维持默认开;具备提交条件。**
|
||||
|
||||
必读偏差:主实验模型为 **gemini-3.6-flash**(3.7-flash 当日午间再次出现 503 风暴,见 §6-a;与 2026-09-01 基线报告同模型故纵向可比);`chunking.model` 保持 3.7-flash 且当轮 49 个 capsule 全部由 3.7 直接服务成功。`config/artemis.jsonc` 实验后已逐字节还原。
|
||||
|
||||
---
|
||||
|
||||
## 1. 方法与环境
|
||||
|
||||
- **代码**:worktree `C:\Users\wfq55\Documents\ChatGPT\artemis\.claude\worktrees\zealous-gates-4626b1`(M0-M5+硬化轮已实施未提交,含 A/B 轮的 operator.py 冷启动修复)。主检出 `.venv` Python、worktree 为 cwd,`python -m artemis.interfaces.cli.main run … --standalone -s 59100DLCR0033X -n <name> --session-id <sid> --without-video-recording-tools`。
|
||||
- **设备**:Pixel 10(59100DLCR0033X),Android 16,中文界面。弹窗由 agent 自行处理。
|
||||
- **模型**:default=google:gemini-3.6-flash(fallback 同 3.6);chunking=gemini-3.7-flash(capsule lens 带 3.6 降级链);step lens=gemini-3.5-flash-lite。当日 10:40 探针三模型全部健康,11:36–11:53 出现 3.7 的 503 窗口(§6-a)。
|
||||
- **flag**:A 轮/Loop/recall/sim 用出厂默认(enabled=true 即 worktree 配置原文);B 轮临时置 false,跑完立即还原;实验结束后 `config/artemis.jsonc` 与实验前备份 diff 为空(含 default.model 还原 3.7)。
|
||||
- **数据源**:worktree `traces/data_engine.db`(steps / traces:llm_call·llm_usage·log·tool / history_chunks),分析脚本在会话 scratchpad(analyze.py、swap_lag.py、dump_chunks.py、flagon/flagoff/loop_metrics.txt、chunks_dump.txt)。
|
||||
- **统计口径**:P50/P90/P95 为跨会话池化分位数;"首调输入"= 每个 operator 回合的第一次 LLM 调用 input_tokens。
|
||||
|
||||
任务集(每任务 A/B 各一遍,同 goal 文本):
|
||||
|
||||
| 任务 | 内容概述 | 步数(on / off) |
|
||||
|---|---|---|
|
||||
| V1 settings-sweep | 设置 7 面板巡检,逐节写笔记+汇总 | 24 / 26 |
|
||||
| V2 clock-alarms | 3 闹钟创建→核对→关 1→删 3→确认清空,逐阶段写笔记 | 55 / 38 |
|
||||
| V3 multi-app-time | 时区+世界时钟 3 城添加读取移除+日历+笔记 | 19 / 20 |
|
||||
| V4 mega-audit | 4 阶段综合巡检(设置 4 面板+世界时钟 2 城+闹钟 2 只+汇总),逐项写笔记 | 40 / 45 |
|
||||
|
||||
>30 步样本:flag-on V2(55)/V4(40),flag-off V2(38)/V4(45);V1/V3 在转写路径下被高效完成未达 30 步(与上一轮 T3 同型的任务设计偏差,长历史压力由 V2/V4 与 112 步 Loop 承载)。8 次 A/B 运行全部自然完成(rc=0,status=completed)。
|
||||
|
||||
## 2. 验收项 1:默认开复测 A/B
|
||||
|
||||
**flag-on(A 轮,4 会话)原始统计**:
|
||||
|
||||
| 会话 | 任务 | 步数 | 墙钟 s | 缓存命中调用 | cached/input | 首调输入 max | 段头就绪 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| aaaa2001 | V1 | 24 | 551 | 80.6% | 50.5% | 21.3K | 3/3 |
|
||||
| aaaa2002 | V2 | 55 | 504 | 86.8% | 53.2% | 29.6K | 8/8 |
|
||||
| aaaa2003 | V3 | 19 | 210 | 82.8% | 54.0% | 22.6K | 3/3 |
|
||||
| aaaa2004 | V4 | 40 | 383 | 90.2% | 48.1% | 22.5K | 4/4 |
|
||||
|
||||
**flag-off(B 轮,4 会话)**:bbbb3001-4,步数 26/38/20/45,墙钟 921/501/292/583s,缓存命中调用 26.4–45.3%,cached/input 16.8–37.3%。
|
||||
|
||||
**池化对照(重点指标逐条)**:
|
||||
|
||||
| 指标 | flag-off | flag-on | Δ |
|
||||
|---|---|---|---|
|
||||
| 回退旧路径次数 | —(旧路径本体) | **0**(且 11 个 flag-on 会话含 Loop/recall/sim 均 0;消息数跨回合正常累积) | ✅ 冷启动修复后转写全程在岗 |
|
||||
| 段头就绪率 | — | **60/60 chunk 范围就绪(100%,全部 flag-on 会话)** | ✅ 上一轮 21/21 pending 病理消失 |
|
||||
| 关段→就绪落库(=交换时刻,门控语义) | — | 中位 8–12s,max 65s(≈1–2 回合;ready 版本行在收割/交换时写入) | ✅ |
|
||||
| 全回合延迟 P50/P90 | 8.5 / 21.0s | **3.4 / 8.0s** | −60% / −62% |
|
||||
| 模型调用 P50/P90 | 4.1 / 11.6s | **3.0 / 6.4s** | −27% / −45% |
|
||||
| 步间隔 P50/P90 | 12.7 / 23.5s | **7.5 / 12.3s** | −41% / −48% |
|
||||
| Operator 缓存命中调用 | 93/234(40%) | **158/184(86%)** | ×2.2(不低于上一轮 88% 的量级) |
|
||||
| 首调输入 P50/P95/max | 12.5K / 14.2K / 15.3K | 17.4K / 24.8K / 28.5K | +39%(≤预算 36%,符合转写保历史预期) |
|
||||
| 输出 token 合计 | 236.0K(129 步) | **163.8K(138 步)** | −31%(按步 −35%) |
|
||||
| 摘要退化回显率 | 0/125 | **0/134** | ✅ 单图变体修复后≈0(两侧同零:lens 修复不在 flag 后) |
|
||||
| 摘要 failed | 0 | 0 | ✅ |
|
||||
| 任务成功率 | 4/4 | 4/4 | ✅ 不劣化(笔记/删除收尾均在案) |
|
||||
|
||||
**A 区稳态**:flag-on 首调输入曲线有界(各会话 max 21–30K,远低于 80K 预算),chunk 交换处可见 >1.5K 的下降沿(详见 §3 Loop 曲线;短任务亦有 3.7K 级下降)。
|
||||
|
||||
辅助证据:换模型前用出厂 3.7-flash 完成的首轮 flag-on 三会话(aaaa1001-3,24/38/18 步)同样 0 回退、8/8 chunk 就绪、全部 completed——默认配置原样形态亦被验证过一遍(数据未入池化,避免混模型)。
|
||||
|
||||
## 3. 验收项 2:[Loop:continuous] 112 步长跑
|
||||
|
||||
会话 cccc2001:goal 明确要求 [Loop:continuous] 持续监控(电池+关于手机轮询,逐轮写 loop_monitor.md)。
|
||||
|
||||
- **规模与稳态**:112 步 / 879s(步间隔 P50 7.4s / P90 9.6s,全程无劣化趋势);回合 P50 3.6s / P95 6.3s。
|
||||
- **token 稳态有界**:首调输入 11.3K(起)→ 26.3K(第 110 步附近,= 预算 33%),5 处 >1.5K 交换下降沿;净斜率 ≈130 tok/步,主导项为 ③ 账本逐行累积(外推软阈值 56K 需 ~340 步,之后依设计由 era ③ 溢出与 L3 接管——本轮未达该规模,未验证)。
|
||||
- **无摘要堆叠**:最终渲染 F 区 = **6 个 era 块(各"merged from 1 chunks",① 结构化字段集合并 + ② 退化标题行 + ③ 逐步账本完整保留)+ 8 个全宽 chunk 块**,无"摘要的摘要"。15/15 chunk 段头就绪。
|
||||
- **步骨架不变量**:最终提示中 ③ 账本行连续覆盖 Steps 1–98(era+chunk 两层均逐步可寻址),Steps 99–112 以原始消息在 A 区(99–105 已关段就绪、按门控待下次渲染交换;106–112 活跃窗口)——任何层级无丢步、无合并。
|
||||
- **[Loop] 轮询记为计划性进度**:capsule 段头以"monitoring cycles / round 12、round 13"客观记录轮次,无异常标记(中立契约的 [Loop] 判别生效)。
|
||||
- **注入指令 never-evict**:第 50 步注入的格式变更指令被 agent 当轮执行(后续笔记行含 [HH:MM] 时间戳),并以 `User @ Step 50: "…"` **逐字**保全于 chunk 50-56 的 ③(pending 与 ready 两个版本行均在),且在最终渲染提示中仍逐字在场。
|
||||
- **release_loop**:第 112 步注入 release 信号后一回合内停止,session completed。
|
||||
|
||||
## 4. 验收项 3:capsule 盲评
|
||||
|
||||
样本:6 个会话的 **48 个就绪 chunk**(≥10 要求的 4.8 倍)全量自动机检 + 5 个跨场景人工精查(设置巡检段、闹钟创建段、世界时钟段、Loop 注入段、recall 首段)。
|
||||
|
||||
- **禁判定词**:全量扫描 band①② 命中 **1/48**——aaaa2004 chunk 13-19 exit_state 中 "'Singapore' entered in the search bar"。属描述搜索框内已输入文字的可观察状态,非导航/成功判定;按字面契约计 1 次违规,语义无害。其余 47 个 chunk 零命中(successfully/completed/failed/navigated to 等全部缺席)。
|
||||
- **③ 账本对照**:人工精查的 5 段中 doing/did/effect 与 ③ 逐行动作、步级视觉摘要完全对得上;verified_facts 逐条可溯源到步摘要中的屏上文字(如 IMEI 356766283081582、toast "闹钟设置为 19 小时34 分钟后"逐字进账);无编造字段。
|
||||
- **笔记要点摘录**:含笔记写入的段,effect 均点名目标文件并给出要点(如 "Appended two records to note 'settings_info_log': '电池电量: 100% (已充满)' and '设备名称: Pixel 10'")——机械校验(缺 key 判负重生成)在真机形态下工作。
|
||||
- **② 区间叙事**:抽查段全部无缝覆盖(区间并集=段范围,生成时机械校验兜底)、同质动作并组正确(如 16–19 = 添加+选时+确认一组)、异质动作单步成行;叙事与实际步骤一致。
|
||||
- 值得记录的正面细节:闹钟段 unresolved 正确写"Confirming the 21:30 alarm dialog"(段末选择器仍开着)——段头对"段内未完结事"的刻画准确。
|
||||
|
||||
## 5. 验收项 4/5:recall include_images 与相似度提示
|
||||
|
||||
**recall include_images(会话 dddd3001,82 步)**:阶段 1 浏览 3 个设置页后到时钟页等待;注入"回忆关于手机页内容,用 recall_history include_images=True 回捞截图对照确认"指令。结果:
|
||||
|
||||
- Operator 共调 recall_history 2 次(`include_images=true`,一次带 `step_range=[3,4]`),工具返回 `[text, caption("--- pre-action screenshot of Step 4 ---"), image_url(data:image/jpeg;base64, 128KB)]` 三块结构;
|
||||
- 含图 ToolMessage 进入后续 **3 次连续 Gemini 调用全部 success**(input 20.7K/22.0K/22.1K,无格式/签名错误);
|
||||
- Operator 消费后写出 recall_check.md:设备名称 Pixel 10、Android 版本、注明"来自 Step 4 截图"——**端到端 PASS**。
|
||||
- 15/15 chunk 就绪、0 回退(该会话历史大部分已 chunk 化,recall 正是从冷历史回捞)。
|
||||
|
||||
**相似度提示(会话 dddd3002,11 步 + 交叉验证)**:
|
||||
|
||||
- 正例:电池→显示→声音→再回电池场景,Historical state hint 注入 10 次调用,指向 **Step 2(首次电池页)与 Step 4**,句式与方案原文一致,附 recall_history 指引;
|
||||
- 静默负例:dddd3001 在时钟页等待轮询 60+ 步(同屏连续),hint **0 次**注入——近 3 步同屏静默规则有效,与像素级"卡同屏"组件未同时触发;
|
||||
- 阈值 5 的行为观察:Loop 会话中 hint 在 106/112 次调用出现(轮询周期 7 步 > 静默窗口 3 步,每次重访都命中更早轮次)。成本一行文本无害,但可作为后续优化候选:活跃 [Loop] 里程碑内提示语义近于噪声(见 §6-e)。
|
||||
|
||||
## 6. 异常与发现清单
|
||||
|
||||
- **a.(环境)gemini-3.7-flash 午间 503 风暴**:11:36 起 planner/operator 连续 503 重试,首跑 acc-on-v4(aaaa1004)在 operator 180s 硬超时后 session failed(0 步入库,17 分钟耗在重试)。处置:按上一轮先例全轮切 gemini-3.6-flash 重跑(同日探针 3.6 三连 1.6–1.9s 健康),报告数据均为 3.6 同模型 A/B;`chunking.model` 维持 3.7——当轮 **49 个 capsule 全部由 3.7 直连成功**,硬化轮新加的 capsule 降级链真机上未被触发(其行为由单测覆盖,供应商窗口期的实弹验证留待自然发生)。3.7 作为出厂默认的可用性风险属供应商侧,与本方案无关,但建议留意。
|
||||
- **b.(环境)设备 USB 掉线**:B 轮首次启动时设备从 adb 消失(4 个任务各 15s 内 ConnectError 退出,bbbb2001-4 弃用);adb 重启后短暂 unauthorized、约 1 分钟内自动恢复授权,换 sid 重跑成功。对数据无污染。
|
||||
- **c.(行为观察,非缺陷)"等待用户指令"被建模为 [Loop:continuous]**:acc-recall 的 goal 说"完成后在页面等待用户进一步指令",planner 将其建模为常驻轮询循环;执行完注入指令后 agent 不自行退出(每 5–10s wait 轮询 + 逐轮更新 task_plan),需 release_loop 显式停止——与 plan grammar"[Loop] 只能被 release 信号终止"的设计一致,但此类措辞的任务会持续烧步数(本例 60+ 步等待轮询)。给使用侧的提示而非代码问题。
|
||||
- **d.(口径)交换延迟的测量语义**:history_chunks 的 ready 版本行在"段头就绪后的首次渲染收割"时写库,因此 DB 上 close→ready 的 8–12s 中位数即"关段→交换"的端到端延迟(capsule LLM 生成时刻另见 lens:step_capsule llm_usage 计 49 条);"ready 行→首个含该 chunk 块的 llm_call"恒为 0s 与该语义吻合。
|
||||
- **e.(优化候选,不阻塞)** [Loop] 任务中相似度提示近乎每回合触发(106/112);语义价值低。可选优化:活跃里程碑为 [Loop] 时静默或降频。
|
||||
- **f.(样本量)** A/B 4 对 + Loop 1 + 场景 2,>30 步样本 4 个;未做置信区间,全部结论按方向性措辞。§8 红线"30 对任务、置信区间"的完整统计功课依旧未做——但两轮独立 A/B(上一轮 6 对 + 本轮 4 对)方向完全一致。
|
||||
- **g.(工作区)** 实验新增 traces/(15 次运行)为 gitignored;`config/artemis.jsonc` 已还原(与实验前备份逐字节一致);无任何 artemis/ 源码改动。
|
||||
|
||||
## 7. §8 验收红线逐条判定
|
||||
|
||||
| 红线(方案 §8) | 判定 | 依据 |
|
||||
|---|---|---|
|
||||
| 不损伤(压缩可逆、回捞可用、失败回退可用) | **PASS** | recall 从已 chunk 化冷历史回捞文本+图片成功消费(§5);60/60 段头就绪故 pending 降级梯本轮未被迫使用;摘要 failed 0 |
|
||||
| 步骨架不变量(任何压缩层级步 1:1 可寻址) | **PASS** | 112 步长跑最终提示:era/chunk ③ 连续覆盖 1–98、99–112 原文在场;era 合并后 ③ 逐段完整;注入指令逐字保全(§3) |
|
||||
| 速度(关键路径零摘要等待;延迟回归 ≤5%) | **PASS(方向性)** | dispatch 零阻塞;全回合 P50 −60%、P90 −62%——大幅改善而非回归;样本 4 对 <30 对 |
|
||||
| 缓存(命中率提升必须量化) | **PASS** | 同日同模型 A/B:命中调用 40%→86%,op cached/input 17–37%→48–54% |
|
||||
| 质量(重复动作率/回滚率不升、冲突趋零) | **PASS(方向性)** | 成功率 8/8 持平;退化摘要 0/259(两轮合计);盲评无摘要-事实冲突;重复动作率未单独统计(Loop 任务的重复为计划性,段头正确标注轮次) |
|
||||
| 对拍(辅助视图收拢、Flash M1 语义等价逐字节) | **PASS(引用)** | M4/M5 golden 单测锁定(tests/unit 1229+3 全绿,2026-09-01 M5 轮验证);本轮未重跑单测、无代码改动 |
|
||||
|
||||
## 8. 结论与建议
|
||||
|
||||
1. **flag 维持默认开(`agent.memory.transcript.enabled=true`)**。依据:本轮为冷启动修复+就绪门控+max_concurrency=2 的最终形态下、与基线轮同模型的独立复测——延迟减半、缓存命中翻倍、输出 token −31%、成功率持平、0 回退、0 退化摘要、段头就绪率 100%;上一轮遗留的三项病理(每回合静默回退、21/21 pending、单图回显)全部实测消失。`enabled:false` 回滚开关本轮实际使用(B 轮)且行为正确(0 chunk、旧 2-message 路径),回滚通道可信。
|
||||
2. **具备提交条件**。M0-M5+硬化轮的全部真机验收项完成且无阻塞缺陷;worktree(含 operator.py 冷启动修复、1229+3 单测)建议按仓库纪律走提交/PR(用户决定时机)。不阻塞提交的遗留优化两项:§6-e([Loop] 内相似度提示静默)、§6-c("等待指令"类措辞的使用侧文档提示);另建议对 3.7-flash 的供应商稳定性保持观察(§6-a)。
|
||||
@@ -1,129 +0,0 @@
|
||||
# 历史模块真机基线与 M2 A/B 实测报告(2026-09-01)
|
||||
|
||||
状态:已完成(真机 Pixel 10 / 59100DLCR0033X,worktree `zealous-gates-4626b1` 未提交代码)
|
||||
|
||||
对应任务:`docs/plans/history-module-redesign.md` §7 的 M0 未完成项(基线录制)与 M2 待真机项(A/B 对拍)。
|
||||
|
||||
---
|
||||
|
||||
## TLDR
|
||||
|
||||
在 Pixel 10 真机上,用冻结的 3 个代表性长任务(各 2 对样本,共 6 基线 + 6 flag-on 运行,全部 Pro profile)完成了 M0 基线录制与 M2 转写路径 A/B。**结论(方向性,样本量 6 对):§8 全部红线通过,且转写路径远超"不回归"标准——全回合延迟 P50 从 21.4s 降到 5.0s(−77%),模型调用 P50 13.8s→4.3s,缓存命中调用占比 43%→88%(token 口径 29.8%→49.6%),总输出 token −74%,任务成功率两轮均 6/6,flag-on 轮零回退、零摘要等待、零摘要 failed、无思考签名/消息格式类错误。**建议按 §10 决策 3 推进灰度,但先复测一轮确认(见"重要注意事项")。
|
||||
|
||||
三个必须显著标注的事项:
|
||||
|
||||
1. **发现并最小修复了一个 M2 阻塞 bug**(operator.py 冷启动误判,导致转写路径从第 2 回合起永久静默回退旧路径)。首轮 flag-on 数据因此作废重跑。修复未提交,在 worktree 工作区中,需 M2/M3 会话认领合入。
|
||||
2. **A/B 的处理组不是纯 M2**:worktree 中已有 M3 代码(`HistoryChunkManager` 挂同一 flag),flag-on 轮实际是"M2 转写 + M3 L2 chunking"叠加(6 会话共产生 21 个 chunk)。收益归因到 M2/M3 的拆分需要后续单独对拍。
|
||||
3. **实验模型为 `gemini-3.6-flash`**(配置默认的 3.7-flash 当日对本 key 持续 503),两轮同模型可比;`config/artemis.jsonc` 已还原原文。
|
||||
|
||||
---
|
||||
|
||||
## 1. 方法与环境
|
||||
|
||||
- **代码**:worktree `C:\Users\wfq55\Documents\ChatGPT\artemis\.claude\worktrees\zealous-gates-4626b1`(分支 `claude/zealous-gates-4626b1`,M0+M1+M2 已实施未提交;另含 M3 chunking 代码)。以主检出 `.venv` 的 Python、worktree 为 cwd 运行 `python -m artemis.interfaces.cli.main run … --standalone`(绕开外部 Daemon,确保跑的是 worktree 代码;`import artemis` 解析与 ROOT_DIR/traces 均已验证指向 worktree)。
|
||||
- **设备**:Pixel 10(59100DLCR0033X),Android 16,中文界面。`--without-video-recording-tools`,checker/committee/planner_validation 均按仓库配置关闭;两轮环境完全一致。
|
||||
- **模型**:default=google:gemini-3.6-flash(原因见 TLDR #3;fallback 同 3.6);lens=gemini-3.5-flash-lite(可用性已验证)。
|
||||
- **flag**:基线轮 `agent.memory.transcript.enabled` 缺省(=false);A/B 轮在 worktree `config/artemis.jsonc` 临时置 true,实验后已还原。
|
||||
- **数据源**(worktree `traces/data_engine.db`):
|
||||
- `llm_call` trace(DataEngineCallbackHandler):单次模型调用 duration、消息数、token_usage(含 `input_token_details.cache_read`)、父 agent span 归属;
|
||||
- `agent` span trace:operator 回合(=全回合延迟)、planner/validator/summarizer;
|
||||
- `llm_usage` trace(M0 token_meter):网关出口计量 + 会话累计;
|
||||
- `steps` 表:步时间戳(步间隔)、`extra_metadata.summary_status/…`(M0 版本化字段,全程在案);
|
||||
- 摘要就绪延迟 = 视觉 lens 调用(summarizer span 派生)的结束时刻 − dispatch span 时刻(配对按 FIFO 顺序,近似值)。
|
||||
- **统计口径**:P50/P90/P95 为跨会话池化分位数;分析脚本与原始输出保存在会话 scratchpad(`analyze.py`、`base-metrics.txt`、`flagon2-metrics.txt`)。
|
||||
- **看门狗**:单任务 45 分钟 timeout,任务间 10s 间隔;全部任务均未触发 timeout。
|
||||
|
||||
## 2. 冻结任务集(Pro profile,多里程碑)
|
||||
|
||||
| 任务 | 目标概述 | 无副作用性 |
|
||||
|---|---|---|
|
||||
| T1 settings-sweep | 设置 7 个面板逐项采集(关于本机/电池/显示/声音/网络/存储/日期时间)并逐节追加到笔记,最后汇总 | 纯读 + 内部笔记 |
|
||||
| T2 clock-alarms | 时钟创建 3 个闹钟(6:15/7:45/21:30)→ 逐个核对 → 关闭其一 → 逐个删除 → 确认清空 | 自建自删 |
|
||||
| T3 multi-app-time | 设置读时区 → 时钟世界钟加 London/Tokyo 读时间后移除 → 日历读今日日期 → 笔记比对汇总 | 自加自删 + 纯读 |
|
||||
|
||||
实测步数:T1 26–45 步、T2 28–45 步均达 >30 步目标区间;T3 14–27 步偏短(多应用切换任务被 agent 高效完成,未达 30 步——记为任务设计偏差,不影响 T1/T2 承载的长历史压力)。12 次运行全部自然完成(rc=0、状态 completed),无人工干预;设备侧弹窗由 agent 自行处理。
|
||||
|
||||
## 3. 基线轮原始统计(flag 关,6 会话)
|
||||
|
||||
| 会话 | 任务 | 步数 | 墙钟 s | Operator 首调输入 tok P50/P95 | 缓存命中调用 | cached/input | 模型调用 s P50/P90 | 回合 s P50/P90 | 步间隔 s P50/P90 | 摘要就绪 s P50/P90 | 摘要 failed |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| db83934e | T1 | 34 | 1415 | 11305/12204 | 23/59 | 26.6% | 22.0/32.5 | 29.2/60.2 | 33.2/63.2 | 189.2/275.3 | 0 |
|
||||
| 1f229af1 | T2 | 40 | 1560 | 12107/17034 | 13/52 | 16.0% | 26.9/41.3 | 30.1/55.9 | 33.5/52.5 | 107.7/215.5 | 0 |
|
||||
| e31a052d | T3 | 14 | 729 | 11646/12805 | 14/29 | 32.6% | 8.0/25.1 | 17.4/56.1 | 21.4/59.8 | 2.3/11.4 | 0 |
|
||||
| f02e473f | T1 | 45 | 1375 | 15879/18558 | 72/109 | 45.3% | 6.9/23.0 | 15.3/48.5 | 18.2/52.2 | 25.5/106.3 | 0 |
|
||||
| 765305ba | T2 | 39 | 1098 | 12701/14272 | 6/46 | 8.3% | 15.2/32.2 | 15.6/33.9 | 19.0/37.8 | 127.4/198.7 | 1 |
|
||||
| 87b3d0e6 | T3 | 16 | 634 | 12808/13918 | 10/28 | 22.7% | 9.6/29.1 | 17.4/69.6 | 22.8/91.5 | 29.3/93.0 | 0 |
|
||||
|
||||
**基线聚合(这是 §3.4/§10 定标的正式基线)**:
|
||||
|
||||
- Operator 首调输入 token:**P50 = 12,190,P95 = 17,589**(legacy 2-message 全量重编译路径)
|
||||
- Operator 缓存:命中调用 **138/323(43%)**,cached/input = **29.8%**(Gemini 隐式缓存对稳定提示头已有基础命中)
|
||||
- 模型调用延迟:**P50 = 13.8s,P90 = 31.2s**;全回合:**P50 = 21.4s,P90 = 58.0s**;步间隔:**P50 = 24.8s,P90 = 62.2s**
|
||||
- 摘要就绪延迟(dispatch→ready):**P50 = 60.0s,P90 = 213.6s**(长尾主因:`max_concurrency=1` 串行队列 + lens 25s 超时重试;见 §5-d)
|
||||
- 摘要 failed 率:**1/188(0.5%)**;任务成功率 6/6
|
||||
- 会话级(6 会话合计):LLM 调用 349 次,prompt 4.61M tok,completion 263.6K tok
|
||||
|
||||
## 4. A/B 轮(flag 开 = M2 转写 + M3 chunking)与红线对照
|
||||
|
||||
有效 A/B 轮(flagon2,operator.py 修复后)原始统计:
|
||||
|
||||
| 会话 | 任务 | 步数 | 墙钟 s | 首调输入 tok P50/P95 | 缓存命中调用 | cached/input | 模型调用 s P50/P90 | 回合 s P50/P90 | 步间隔 s P50/P90 | 摘要就绪 s P50/P90 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 5867d1f2 | T1 | 26 | 345 | 16158/19551 | 28/30 | 50.4% | 4.9/8.0 | 5.3/8.7 | 8.9/11.4 | 44.9/74.2 |
|
||||
| bebd7329 | T2 | 28 | 480 | 15588/20225 | 26/30 | 48.0% | 4.2/40.4 | 4.4/33.6 | 8.4/37.7 | 25.3/65.5 |
|
||||
| 8f9cf01b | T3 | 14 | 344 | 16174/20191 | 22/27 | 51.7% | 3.3/8.6 | 6.0/14.6 | 8.2/18.0 | 2.9/5.8 |
|
||||
| fed69c93 | T1 | 39 | 608 | 15218/17104 | 44/50 | 51.7% | 5.3/21.0 | 6.7/22.8 | 9.7/26.7 | 17.3/64.0 |
|
||||
| 4b426449 | T2 | 40 | 635 | 17154/21287 | 38/43 | 46.4% | 4.0/32.6 | 4.5/24.0 | 8.3/27.0 | 35.2/61.2 |
|
||||
| 59a9613f | T3 | 18 | 508 | 15789/19905 | 26/29 | 50.2% | 3.2/7.8 | 4.0/13.2 | 8.1/26.7 | 82.0/330.3 |
|
||||
|
||||
**红线对照(方案 §8 / 姊妹文档 §18)**:
|
||||
|
||||
| 红线 | 基线 | flag 开 | 判定 |
|
||||
|---|---|---|---|
|
||||
| 模型调用延迟回归 ≤5% | P50 13.8s / P90 31.2s | **P50 4.3s(−69%)/ P90 22.8s(−27%)** | ✅ 大幅改善而非回归 |
|
||||
| 全回合延迟回归 ≤5% | P50 21.4s / P90 58.0s | **P50 5.0s(−77%)/ P90 21.4s(−63%)** | ✅ 同上 |
|
||||
| 缓存命中显著上升 | 43% 调用 / 29.8% token | **88% 调用 / 49.6% token** | ✅(这是本方案核心收益,已量化) |
|
||||
| 关键路径零摘要等待 | —(旧路径无此概念) | 回合 P50 5.0s,无任何等待摘要的阻塞形态;dispatch span 恒 <0.1s | ✅ |
|
||||
| 无思考签名/消息格式错误 | — | 12 会话日志与 log trace 扫描(signature/format/malformed/invalid message 关键词)零命中;503 重试 0–3 次/会话属供应商限流 | ✅ |
|
||||
| 转写异常回退次数为零或有明确原因 | — | **修复后 6 会话 0 次**(修复前每回合都回退,原因明确且已修,见 §5-a) | ✅ |
|
||||
| 任务成功率不劣化 | 6/6 | 6/6(笔记调用 14–20 次/任务、删除等收尾动作在案,完成质量抽查可信) | ✅ |
|
||||
|
||||
**附加观察(非红线但值得记录)**:
|
||||
|
||||
- 首调输入 token P50 +32%(12.2K→16.1K):转写保留全部历史消息,符合预期;绝对值仍远低于 80K 预算。
|
||||
- 总输出 token **−74%**(263.6K→68.7K)、LLM 总调用 −34%(349→231):真多轮下模型不再每回合重新推理全局,重复思考被消除——这是延迟改善的主要来源之一(另一来源是缓存命中提高了 TTFT)。
|
||||
- 墙钟:T1 1415/1375s → 345/608s;步间隔 P50 24.8s→8.8s。
|
||||
- M3 chunking 实际触发:6 会话共 21 个 HistoryChunk(里程碑切换 + 尺寸边界,步区间 1:1 无丢步,最大 7 步/块);③ 机械账本全部就位,**①② LLM 段头全部停留 pending**(后台生成未落地,属 M3 未完成面,移交 M3 会话)。
|
||||
|
||||
## 5. 异常与发现清单
|
||||
|
||||
**a.(阻塞级,已最小修复——需 M2/M3 会话认领)M2 冷启动误判 bug**:`operator.py::_build_prompt_transcript` 的冷启动检查(`ledger.turn_count == 0 and steps`)在第 2 回合必然误命中——上一回合只 stage 未 commit,`turn_count` 仍为 0 而 steps 已非空,`set_restored_history` 被账本的空账本不变量拒绝抛异常,触发灰度安全网**每回合静默回退旧路径**。实测表现:首轮 flag-on 6 会话全部 operator 首调恒为 2 条消息、每会话 ~30 条 "Transcript prompt path failed … Restored history can only seed an empty ledger" 错误日志。**首轮 flag-on 数据作废**(它实测的是"旧路径+每回合异常开销")。最小修复:冷启动条件增加 `not ledger.has_staged_turn`(operator.py,含注释共 +8 行);修复后 68 个相关单测全绿,验证运行零回退、消息数跨回合正常累积(2→7→11→…)。**测试缺口**:`test_operator_transcript.py` 的"两回合四区结构"用例未覆盖"第 2 回合时 steps 非空"的真实形态,建议补一例。
|
||||
|
||||
**b.(质量级,两轮同等存在)Pro 步记录后图恒缺 → 视觉 lens 单图退化**:Pro 的 `record_step` 中 `post_image_name` 恒为 None(步 N 的后图即步 N+1 的前图,记录时不存在独立后图),M2 SummarizerNode 从 DataEngine 取图 dispatch 时 post 恒为 None,视觉 lens 只收到前图。flash-lite 在单图输入下高频退化——直接回显分隔符原文(`--- [2] AFTER ACTION SCREEN ---`)当作摘要。实测退化率:基线 52/181(29%)、flag 开 55/159(35%)。flag 开时这些坏摘要会在擦洗沿替换截图进入 Operator 上下文,是当前转写质量的最大缺口。建议(M3 前修):lens dispatch 延迟到下一步感知就绪后,用步 N+1 的前图充当步 N 的后图;或 lens 契约对单图输入改为"描述前屏与动作意图"并在 prompt 中显式声明无后图。
|
||||
|
||||
**c.(计量缺口)Flash/Pro 视觉 lens 调用不经 LLM 网关**:`VisualStepSummarizer` 用 `get_google_llm` 原始模型(`self._llm.ainvoke`),绕过 `RobustChatModelWrapper` → **无 llm_usage 计量条目**(本轮 995 条 llm_usage 全部为 google:gemini-3.6-flash,无 flash-lite 来源)。M0 实施要点第 3 条"raw 模型旁路仅测试使用"的表述与现状不符。lens 成本目前只能从回调 llm_call trace 旁证。建议 lens 切网关或单独接计量。
|
||||
|
||||
**d.(长尾)摘要就绪延迟远超擦洗窗口**:基线 P50 60s / P90 214s;flag 开 P50 23.6s / P90 72.9s(转写降低了主调用排队干扰后明显改善)。长尾成因:`memory.runtime.max_concurrency=1` 串行队列 + 单次 25s 超时 + 有界重试。flag 开后步间隔 P50 仅 8.8s,K=3 的擦洗沿深度 ≈26s:P50 恰好赶上,P90 会吃满宽限(3 步 ≈26s)并落占位符。对定标的含义见 §6。
|
||||
|
||||
**e.(环境)gemini-3.7-flash 当日持续 503**(实验前探测 5/5 失败),整个实验固定用 3.6-flash;`config/artemis.jsonc` 已按备份还原为 3.7 原文。绝对延迟数字换模型后不可直接外推,A/B 相对结论不受影响。
|
||||
|
||||
**f.(样本)** T3 实测 14–27 步未达 30 步设计目标;每任务 2 对的样本量下所有结论按方向性措辞,未做置信区间。§8 "30 对任务、置信区间"的正式验收仍待更大样本。
|
||||
|
||||
**g.(工作区)** 实验在 worktree 留下的痕迹:`.env`(从主检出复制,gitignored)、`traces/`(12+3 次运行的 db 与 trace 目录,gitignored)、operator.py 最小修复(见 a)。`config/artemis.jsonc` 已还原。基线含 1 例摘要 failed(765305ba,M0 有界重试耗尽进显式 failed 态,符合设计)。
|
||||
|
||||
## 6. §3.4 临时默认值定标建议(基于本轮实测,供 M3/M4 采用)
|
||||
|
||||
| 参数 | 临时值 | 实测证据 | 建议 |
|
||||
|---|---|---|---|
|
||||
| `context_budget_tokens` | 80K | 40+ 步长任务转写路径首调输入 P95 ≈ 20.4K、最大 23.4K(含活跃窗口内截图);稳态远低于预算 | **维持 80K**。当前形态下 ~100 步内不会触及软阈值;若未来放开图片保留深度或加大 XML 保留再复标 |
|
||||
| 软/硬阈值 | 0.7 / 0.9 | 本轮从未接近(≤29%),未获得触发数据 | **维持 0.7/0.9**(无证据要求调整;触发行为的验收留给 M3 的 `[Loop:continuous]` 100+ 步试跑) |
|
||||
| `image_scrub_depth` | 3 | flag 开步间隔 P50 8.8s → K=3 ≈ 26s 深度;摘要就绪 P50 23.6s、P90 72.9s → P50 恰好赶上、P90 吃满宽限落占位符 | **维持 K=3**,但配套二选一:`memory.runtime.max_concurrency` 1→2(压就绪长尾,lens 是独立 flash-lite 调用,并发 2 风险低),或 `pending_grace_steps` 3→5。优先前者(占位符是信息损失,宽限只是延迟损失) |
|
||||
| `min_active_steps` | 5 | 本轮 L2 触发时活跃窗口底线未被逼近(chunk 最大 7 步、上下文远未满) | **维持 5**(无反证数据) |
|
||||
| chunk 触发 ≥12 步 / ≥2K token | 12 / 2000 | 21 个 chunk 实际由里程碑切换主导(1–7 步/块),尺寸兜底未成为主触发器 | **维持**。里程碑触发工作正常;尺寸兜底的检验需要单里程碑长段任务(`[Loop:continuous]`),留 M3 验收项 |
|
||||
|
||||
## 7. 结论与建议
|
||||
|
||||
1. **M0 基线已录制**(§3 表格即正式基线),M2 A/B 待真机项已完成,红线全过(方向性)。
|
||||
2. **转写路径收益远超预期**:延迟 −63%~−77%、缓存命中翻倍、输出 token −74%、调用数 −34%,且成功率持平。"真多轮 + 缓存命中"的架构判断被实测强烈支持。
|
||||
3. **灰度建议**:flag 具备进入默认开的候选资格,但建议满足两个前置后再翻默认:① §5-a 修复被 M2/M3 会话正式认领合入(含补测试);② 修复后再跑一轮 A/B 确认(本轮 A/B 的处理组叠加了 M3 chunking 与 operator.py 修复,归因不纯)。
|
||||
4. **移交 M3 会话**:chunk ①② 段头 pending 未落地、lens 单图退化修复(§5-b)、lens 计量接入(§5-c)、`max_concurrency` 调参(§6)。
|
||||
@@ -1,988 +0,0 @@
|
||||
# ARTEMIS 历史模块重构:增量转写 + 分层压缩(统一方案 v3)
|
||||
|
||||
状态:v3 已实施并通过最终真机验收(2026-09-01,M0-M5 全部落地;最终验收五项全过,见 `docs/plans/history-acceptance-2026-09-01.md`;仅 git 提交待用户决定)
|
||||
|
||||
范围:Operator 执行历史的表示、累积、压缩与召回;Flash / Pro 双 profile 收敛为同一记忆运行时。
|
||||
|
||||
取代关系:
|
||||
- **取代** `docs/design/pro-context-memory-redesign.md`(2026-08-25)的 §6/§11/§12(分层历史、token 预算、缓存布局)——其"每回合重编译 + 预算选择"的前提被本方案的"增量转写"取代;其 §7(数据模型)、§8(视觉接地中立摘要)、§9(稳定分块)、§10(recall)被**吸收并保留**。
|
||||
- **修订** `docs/plans/context-visibility-redesign.md` 的 §6(方案 D 记忆内核)——StepMemoryService 统一运行时保留,但"两 lens 按 profile 分立"修订为"两 lens 按高度分立"(见 §5,决策变更已获用户确认,2026-08-31)。该文档的 Phase 0/1(State 显式化)已实施,Phase 2(PerceptionStore)/Phase 3(ctx 分解)不受本方案影响,Phase 4 由本方案取代。
|
||||
|
||||
---
|
||||
|
||||
## 0. 用户提案复述与结论
|
||||
|
||||
提案三条(2026-08-31):
|
||||
|
||||
1. **每步与 Flash 一致,仅做增量构建**;后台用 Gemini 3.5 Flash-Lite 做图片压缩(视觉摘要替代截图)。
|
||||
2. **里程碑边界压缩**:不再每步压缩,而是在**切换里程碑时**把上一段任务的历史压缩掉;保留现方案的滑动窗口底线(永远保留最近几步原始消息)。
|
||||
3. **上限兜底**:长期不切换里程碑时,按最大上下文窗口限制触发压缩。
|
||||
4. **(补充约束,2026-08-31/09-01 两轮明确)段级胶囊要特别详细、尽量维持原状、按步压缩,且定形为三段式**:① 梗概与效果(在做什么/做了什么/有什么效果,含留下的笔记)→ ② 压缩步摘要("Steps xx–xx 做什么、Step xx 做什么"区间叙事)→ ③ 逐步动作账本(每步一行:步号+时间+动作)。理由:视频分析模块按步号/时间戳对齐视频时间轴,且大模型对任务整体的时间感知必须精确到步。落点见 §3.3。
|
||||
|
||||
**结论:方向成立,且与业界最优实践收敛。** Claude Code、Codex、Gemini CLI 全部采用"累积 + 阈值/边界触发压缩",无一采用 ARTEMIS Pro 现在的"每回合全量重编译"(后者的直接代价:跨回合零消息连续性、零提示缓存命中、思考签名丢失)。姊妹文档把"真多轮消息"排在 Phase 5,本提案把它提前为核心架构——这是正确的提前。
|
||||
|
||||
在采纳的同时做**四点修正/补强**(正文展开):
|
||||
|
||||
| # | 提案原文 | 修正 | 理由 |
|
||||
|---|---|---|---|
|
||||
| 1 | "后台每步做图片压缩" | 摘要**每步后台准备**,但替换**延迟到固定深度批量应用**(尾部倒数第 K 步才把图换成摘要) | 就地改写第 i 字节起的缓存前缀全部失效;固定深度擦洗让前缀单调增长(Anthropic computer-use 官方同款权衡,见 §2.5) |
|
||||
| 2 | "切换里程碑时压缩之前的任务" | 里程碑切换当**触发器**,压缩块的**身份**按 step_id 范围锚定,里程碑 hash 仅作标签元数据 | 计划是动态索引不是历史键(姊妹文档 §9 实证结论):计划改写/回滚/重命名不得破坏已压缩历史 |
|
||||
| 3 | "按最大上下文窗口限制压缩" | 需要真实计量——现在全仓**没有任何 token 计量**(探索报告 §8);用**上一轮 `usage_metadata.prompt_tokens` 作为当前上下文的实测值** + 增量启发式,零额外成本 | 字符估算对图片/多模态误差大;上一轮实测输入即本轮上下文基数 |
|
||||
| 4 | (未提及) | 补两层:**紧急全量快照**(阈值兜底也爆时,Codex/Gemini CLI 式结构化快照重启)与 **recall_history 无损回捞**(DataEngine 冷历史,压缩永远可逆) | 无界任务(`[Loop:continuous]`)会耗尽任何有限分层;无损回捞是"敢压缩"的前提 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 现状基线(2026-08-31 工作区实证)
|
||||
|
||||
### 1.1 Flash:append-only + 每轮就地压缩
|
||||
|
||||
- 消息列表建一次、只追加,**无任何消息驱逐**,唯一边界是 `max_turns=30`(`runner.py:166,177`)。
|
||||
- `compress_flash_messages`(`context_compressor.py:43`)每轮 LLM 调用前全量重扫:只保留**最后一张**图(keep-last-1,`:62-72`);历史 UI XML 从 `--- UI Element List ---` 标记截断(`:133-139`);图片"摘要未就绪则保留"(lossless-pending,`:141-146`);摘要以 `--- Historical Visual Transition ---` 前缀块注入 ToolMessage,幂等重建(`:125-128`)。
|
||||
- 已知缺口:摘要**迟到回填**会突变任意深度的历史消息(缓存敌意);图已被丢而摘要仍未到时该步**既无图也无摘要**(race,`:144` 只保护仍在场的图)。
|
||||
- `VisualStepSummarizer`(`flash/summarizer.py:39`):flash-lite、前图叠加红色动作标记+后图、**无界重试**(`_retry_delays` 末档 3s 永续循环,`:105`)、单次 25s 超时、成功后写 `data_engine.update_step_summary`(`:205`)。
|
||||
- token 仅做事后记账(char/4 + 258/图启发式,`runner.py:293-315`),**从不驱动压缩**。
|
||||
|
||||
### 1.2 Pro:每回合全量重建,零跨回合消息
|
||||
|
||||
- `_build_prompt` 每回合新建 PromptBuilder,产出恰好 2 条消息(System + Human),回合结束整表丢弃(`operator.py:161-218,705-715`;`prompts.py:139-154`)。**跨回合连续性只剩 `plan_and_history` 渲染串 + `short_term_memory`**;思考签名、原生多轮全部丢失;提示缓存对易变尾部零命中(网关无任何 cache_control/cached_content plumbing,`llm.py` 全文)。
|
||||
- 历史可见性 4 策略散布 8 个调用点硬编码 kwargs(`task_tree.py:797-942` + 探索报告 §2.4 全表);`strict_milestone` 是硬悬崖——活跃里程碑外且最近 N 步外的历史**完全消失,无摘要残留**(`:859-876`)。
|
||||
- 摘要未就绪的降级:Flash 保图,Pro 回退整步 detailed 渲染(`task_tree.py:920`)——两者都是"多花 token 等摘要",但均不可观测。
|
||||
- 两个 live bug:Diagnoser 传不存在的 `window_steps=` 参数 + 不存在的 `history_window_steps` 配置,异常被吞,**Diagnoser 的历史每次都渲染成错误串**(`diagnoser.py:160-174`);`build_plan_and_history` 两个死参数(`chronological_last_step`/`detailed_subgoal_hashes`)4 个调用点仍在传。
|
||||
|
||||
### 1.3 存储与事件面
|
||||
|
||||
- `StepRecord` 无 `summary_status`/`version`/`chunk` 概念;`update_step_summary` 是后台盲覆写,无版本无序保证(`engine.py:974-1005`)。扩展逃生口是 `extra_metadata`(已承载 `subgoal_hash`/`width`/`height`/`token_usage`)。
|
||||
- 里程碑切换的现成检测点:① 每步已盖章 `extra_metadata["subgoal_hash"]`(`graph.py:128`),相邻步 hash 变化即切换;② `_process_plan_write`(`graph.py:505-615`)是全部计划写入的单一漏斗,已在计算 `milestones_changed`/`new_top_level_completions`。重命名韧性由 `subgoal_hash_chain.json` 别名链保证(`task_tree.py:991`)。
|
||||
- 现成接缝:operator 模板里 `unified_history=""` 是**已接线的空渲染槽**(`prompts.py:213`);`PromptComponent` 是干净插件点;`MAX_MESSAGES_IN_HISTORY=25` 是死常量(`constants.py:16`)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 业界机制要点(2026-08 调研,取数字用于定标)
|
||||
|
||||
### 2.1 Claude Code:三层递进
|
||||
|
||||
① **微压缩**(每次调用前、无模型参与):清掉旧 tool_result,只留最近 3–5 个,占位符替代,原文落盘可回捞;② **服务端 context editing**(`clear_tool_uses`):默认 100K 触发、keep 3,关键参数 `clear_at_least`——**不值得为小清理打破缓存就不清**(批量摊销失效成本的官方形态);③ **全量压缩**:~95% 用量触发,9 节结构化摘要(意图/技术要点/文件/错误与修复/用户消息/待办/当前工作/下一步),压缩后重注入最近读过的 ~5 个文件。另有 memory tool:清理阈值临近时警告模型先把重要结果写进记忆文件。
|
||||
|
||||
### 2.2 Codex CLI:极简两件套 + 原生压缩
|
||||
|
||||
阈值 ≈ 有效窗口 − 13K(用户自设值被钳制在窗口 90% 以内)。压缩后**只剩两样**:一条摘要消息(进度/关键决策/约束/剩余工作)+ 最近 ~20K 原始用户消息;旧摘要被折叠进新摘要,**永不堆叠**。压缩后自动重读最近编辑的 ≤5 个文件(50K 预算)。GPT-5.1-Codex-Max 起模型**原生训练了跨窗口压缩**。UI 明示"多次压缩会降低准确性"。
|
||||
|
||||
### 2.3 Gemini CLI:70/30 + 结构化快照
|
||||
|
||||
历史达模型窗口 **70%** 触发;**最近 30% 原样保留**(切分点保证不劈开 tool-call/response 对);更旧的压成 `<state_snapshot>` XML(overall_goal / key_knowledge / file_system_state / recent_actions / current_plan),压缩率 5–15%。
|
||||
|
||||
### 2.4 Manus KV 缓存法则(生产指标)
|
||||
|
||||
agent 输入:输出 ≈ 100:1,缓存命中是**第一生产指标**(cached $0.30 vs uncached $3.00/MTok)。法则:前缀字节级稳定(系统提示禁时间戳)、**append-only**(永不修改既往动作/观察、序列化确定性)、工具 mask 不删除、**文件系统即外部记忆**(可恢复地压缩:丢网页留 URL,丢文件内容留路径)、todo 复诵对抗 lost-in-the-middle、错误留在上下文里。
|
||||
|
||||
### 2.5 截图历史(computer-use 官方指引,与本仓直接同构)
|
||||
|
||||
Anthropic:截图每张 ~1,000–1,800 token,单请求 ≤20 张;**推荐保留最近 3 张,更旧的每 ~25 轮批量修剪**——明确说明逐轮修剪会每轮打破前缀缓存,批量修剪让失效事件之间前缀字节不变;被逐出的截图用一句文本摘要替位。参考实现即 `only_n_most_recent_images` 参数。OpenAI CUA 则靠服务端 `previous_response_id` 携带历史,客户端只上传新帧。
|
||||
|
||||
### 2.6 Gemini 缓存约束(本仓运行时)
|
||||
|
||||
隐式缓存 2.5+ 默认开启;**最小可缓存前缀 2,048(2.5 系)/ 4,096(3.x 系)token**,命中报告在 `cachedContentTokenCount`;条目短寿命,相似前缀请求要时间上贴近。显式 `CachedContent` TTL 默认 1h、按 token-hour 计存储费(explorer.py:1709 已有先例)。**任何更早字节的改动使其后全部失效**——这是修正 #1 的硬依据。
|
||||
|
||||
### 2.7 提炼的六条通用原则
|
||||
|
||||
1. 累积为常态,压缩靠**阈值/边界稀疏触发**,不逐步进行。
|
||||
2. 突变要么贴尾(浅、可预测),要么稀疏成批(深、与语义边界重合)。
|
||||
3. 摘要输出**结构化 schema**,不是自由散文;旧摘要折叠进新摘要,不堆叠。
|
||||
4. 最近 K 步/最近 30% 永远原样——所有系统都有生窗口下限。
|
||||
5. 压缩必须**可逆**:原文外置(磁盘/DataEngine),占位符携带回捞引用。
|
||||
6. 计量用真实值,阈值分软/硬两档,硬档换更激进的形态而非同一手段加量。
|
||||
|
||||
---
|
||||
|
||||
## 3. 目标架构:单一转写(Transcript)+ 四区纪律 + 三级压缩
|
||||
|
||||
### 3.1 核心转变
|
||||
|
||||
Operator 的上下文从"每回合重编译的渲染串"变为**会话级增量转写**——一份 append-only 的消息账本,Flash 与 Pro 共用同一实现,由新服务 `TranscriptLedger`(挂 `ctx.step_memory` 之下或并列)持有。State 只携带轻引用,重物不进图通道(与 visibility 方案 C 同一原则)。
|
||||
|
||||
`build_plan_and_history` **不删除**,降级为两个角色:
|
||||
- **辅助 agent 的编译视图**:Diagnoser / Committee / Outputter / HistoryAnalyzer / Planner / Checker 仍按各自 ContextPolicy 从 DataEngine 编译(Claude Code 同型:主循环累积、子 agent 拿新鲜编译上下文);
|
||||
- **转写的冷启动构造器**:进程重启/崩溃恢复时,从 DataEngine 步骤记录重建转写的冻结区。
|
||||
|
||||
### 3.2 四区纪律(缓存友好的突变规则)
|
||||
|
||||
```text
|
||||
[S 稳定前缀] 系统提示 + 工具 schema + 计划语法
|
||||
整会话字节不变;≥4K token 以满足 Gemini 3.x 隐式缓存下限
|
||||
[F 冻结历史] 已压缩产物:chunk 摘要块 + 逐步摘要行
|
||||
只在"压缩事件"时突变(稀疏、深)
|
||||
[A 活跃窗口] 最近 N 步原始消息(AI 推理原文 + 工具结果)
|
||||
append-only;倒数第 K 步处有一个"擦洗沿"(浅、恒定深度)
|
||||
[T 当前尾部] 本回合观察:当前截图 + UI 列表 + 计划复诵 + 注入指令
|
||||
每回合新建
|
||||
```
|
||||
|
||||
**突变只允许三种,全部可预测:**
|
||||
|
||||
1. **贴尾擦洗(深度恒定,逐回合)**:
|
||||
- 深度 1:上一回合的 UI XML 列表、计划复诵副本剥除(现 Flash `prune_history_xml` 行为,代价只是最后一回合的缓存,几乎免费);
|
||||
- 深度 K(默认 3):倒数第 K 步的截图 → 替换为后台已备好的视觉转换摘要。摘要通常在几秒内就绪,K 步 ≈ 数十秒,几乎总能赶上;未就绪则该步暂缓(lossless-pending 保图),最多宽限 m 步(默认 3),仍未到则替换为占位符 `[visual summary pending; evidence at DataEngine step N]`——**彻底消灭现 Flash 的"迟到回填突变任意深度"与"图丢摘要空"两个缺陷**。
|
||||
- 恒定深度擦洗让缓存前缀**单调增长**(每轮失效的只是最后 K 步),同时 token 稳态恒定——严格优于"每 25 轮批一次"(后者在批间攒 25 轮的图)。
|
||||
2. **压缩事件(稀疏、深)**:见 §3.3,F 区重写 + A 区前移。深突变与语义边界重合,此时接受一次全量 re-prefill(Claude/Codex 同款代价)。
|
||||
- **思维签名政策(2026-09-01 定):回合内强制、A 区顺带、压缩即弃**——Gemini thought signature 仅在当前回合函数调用周期内是 API 硬要求(原样回传);A 区 AIMessage 字节不动故签名免费有效(擦洗只碰观察/工具消息,从不碰 AIMessage);已完结回合的签名 API 层面可选,chunk 化整回合移除后自然消亡,不建任何跨压缩维护机制(签名绑定消息精确内容,语义上不可能脱离原消息存续)。真机 A/B 无签名相关错误佐证。
|
||||
3. **紧急快照(罕见)**:F 区整体替换为会话快照,见 §3.3 L3。
|
||||
|
||||
### 3.3 三级压缩
|
||||
|
||||
**L1 — 图片/XML 擦洗**(上文深度 K 机制)。摘要由 `StepMemoryService` 每步后台 dispatch(flash-lite,沿用红标叠加 + 禁判定词契约),**准备是每步的,应用是延迟的**。
|
||||
|
||||
**L2 — 段压缩(chunk 化)**,触发器取先到者:
|
||||
- **里程碑切换**:**唯一事实源是已执行步骤的 `subgoal_hash` 实际变化**(`_record_turn` 处比较相邻步盖章);`_process_plan_write` 的顶层完结事件只入队一个"待确认边界",由下一个盖章步骤确认后才触发——防止被棘轮验证否决回滚的计划写入造成伪切换。切换时把上一段(不含活跃窗口底线内的步)压成一个 **HistoryChunk**;
|
||||
- **尺寸阈值**(不切里程碑时的兜底,即提案第 3 条):开放段累计 ≥12 步或段内摘要 ≥2K token;
|
||||
- **边界事件**:包/Activity 大切换、目标变更、FA 边界、不可逆副作用(继承姊妹文档 §6.5 全表)。
|
||||
|
||||
Chunk 身份 = `(start_step_id, end_step_id, source_step_ids)`;`subgoal_hash`(经别名链解析)只是标签。
|
||||
|
||||
**chunk 是"分段账本",不是融合胶囊(用户硬约束,2026-08-31)**:压缩只缩每步的**宽度**(推理原文/XML/截图 → 摘要/动作行),永不缩步的**数量**——段内步 1:1 保留,不合并、不重排、不丢步。两个刚性理由:① 视频分析模块以步号/时间戳为外键对齐视频时间轴,步粒度一旦融合即断链;② 大模型对任务整体的时间感知必须精确到步("第 23 步做了什么、距今多少步"要可直接回答)。
|
||||
|
||||
**chunk 内部结构为三段式(用户 2026-09-01 定形:梗概 + 效果 + 压缩步摘要,底下逐步动作)**:
|
||||
|
||||
```text
|
||||
[Chunk 3 | 里程碑「登录并进入设置页」| Steps 18–29 | T+12:40 → T+15:02]
|
||||
|
||||
① 梗概与效果(LLM 段头)
|
||||
本段在做什么:为后续改配置,先完成登录并进入设置页。
|
||||
实际做了什么:用账号 A 走了短信验证码登录,途中关闭了一次推广弹窗。
|
||||
效果/留下什么:已登录态(账号 A);验证码输入页出现过一次超时重发;
|
||||
笔记 notes/login_flow.md 记录了验证码入口路径。
|
||||
Entry: 应用主页,未登录 Exit: 设置页已打开
|
||||
Verified: … Unresolved: … Failed paths: … Entities: …
|
||||
|
||||
② 压缩步摘要(LLM,逐区间叙事,必须带步号且全覆盖)
|
||||
- Steps 18–20: 进入登录页,切换到验证码登录并填入手机号
|
||||
- Step 21: 首次获取验证码后等待期间出现推广弹窗,关闭之
|
||||
- Steps 22–26: 输入验证码,一次超时后重发并重新输入
|
||||
- Steps 27–29: 登录后从侧边栏进入设置页
|
||||
|
||||
③ 逐步动作账本(机械拼装,1:1,永不省略)
|
||||
- Step 18 (T+12:40): tap(632,1180)[登录入口] → executed
|
||||
- Step 19 (T+12:56): tap(210,884)[验证码登录] → executed
|
||||
- Step 20 (T+13:04): type("138…") → executed
|
||||
…(每步一行,直到 Step 29)
|
||||
```
|
||||
|
||||
三段的分工与生成方式:
|
||||
|
||||
- **① 段头梗概(LLM,gemini-3.7-flash)**:三问式散文(在做什么 / 做了什么 / 有什么效果——效果必须包含本段**留下的笔记**:段内 notes 写入/更新的文件与要点引用)+ 结构化字段(沿姊妹文档 §7.3:verified_facts / unresolved / failed_paths / important_entities / entry_state / exit_state)。要求**特别详细、尽量维持原状**:宁可长,不许把事实抽象到不可辨认。**笔记关联是机械校验项(用户 2026-09-01 定,"不能丢")**:段内每条 note 写入(save/update/append_note,经 trace 抓取)的目标文件 key 必须出现在段头 effect 中,缺任一即该次尝试判负重生成——与 ② 的覆盖校验同级;chunk 存"指针+要点",笔记全文在 notes 系统(read_note/recall 可回捞),可恢复压缩。
|
||||
- **② 压缩步摘要(LLM,同一次调用产出)**:"Steps xx–xx 做了什么,Step xx 做了什么"式的区间叙事,输入是段内逐步视觉摘要 + 动作 + Validator 结果。硬性约束:每行必须带步号引用;**区间并集必须无缝覆盖整段步范围**(机械校验,缺口即重生成);同一区间只并列同质连续动作,异质动作单步成行;中立措辞契约沿用(禁判定词)。
|
||||
- **③ 逐步动作账本(机械拼装,不经 LLM,零失真)**:步号 + 相对时间 + 精确动作 + Controller/Validator 结果短语,全部从 DataEngine 确定性渲染。这是视频对齐与步级时间感知的**硬骨架**,任何层级的压缩都不得触碰。三条账本细则:
|
||||
- **动作必须带元素语义(用户约束,2026-09-01)**:`tap(632,1180)['确认按钮']` 而非裸坐标。索引点击路径已在记录时存 `target_text/resource_id/class`(`operator.py:893-896`);裸坐标路径(`operator.py:854-861` 返回全空)与 Flash/FA 动作工具需补**记录时语义充实**(见 §6.1);坐标永远保留(视频对齐/重放的外键),语义并列。
|
||||
- **FA 恢复子动作入账**:一步内 Failure Analyzer 补做的真实设备动作(`_exec_*`,已在 `interleaved_events`)以缩进子行机械渲染——视频轴上有帧的动作,账本必须有行。
|
||||
- **用户注入指令逐字保全**:段内 `injected_instruction` 以独立行原文保留在 ③(`User @ Step 21: "…"`),任何压缩层级 never-evict(Codex 保用户消息 / Claude Code 摘要设 All User Messages 节,同一共识)。
|
||||
|
||||
每步的完整视觉摘要不再逐条出现在 chunk 里(它们是 ② 的生成输入,原文永远在 DataEngine,recall 可回捞;A 区未压缩前照常在场)。chunk 版本化 append-only。
|
||||
|
||||
**就绪门控交换(用户 2026-09-01 定,"必须就绪再换";修订 M3 原实现)**:段头 ①② 未就绪时**禁止**交换——原始消息原样留在转写里,lossless-pending 原则从图片层贯彻到段层。边界事件只做两件事:关闭段、dispatch 段头生成;**交换发生在段头就绪之后的下一次渲染/压缩事件**(深突变时机照旧成批,缓存代价不变,只是延后)。理由:段头缺席时 `verified_facts` 缺席,只剩"发生了什么"没有"确立了什么"——模型会怀疑已验证事实并重做,这是实测过的死循环高危形态。降级梯:生成失败(有界重试耗尽)→ 原文继续保留 + fallback 模型重投;软阈值压力下仍未就绪 → 依旧保留原文(正确性优先于 token);**仅硬阈值紧急态**允许 ③-only 交换(带 pending 标记与 recall 指引;checker post-hoc 标注独立于段头,pending 态照常渲染)或直接走 L3。M3 原"冻结 ③ + pending 标记、下次事件收割"的实现按此修正——A/B 中"21 chunk 段头全 pending"在门控下退化为"尚未压缩"的安全形态,而非"历史只剩账本"。
|
||||
|
||||
**旧 chunk 不堆叠新摘要**:无界任务(`[Loop:continuous]`)下 chunk 数超限时做二级合并(era 合并)——**合并的只是 ① 段头**(结构化字段集合并,非散文再摘要),② 区间叙事与 ③ 逐步账本仍按段保留(预算极紧时 ② 可退化为只留区间标题行)。可见预算靠分级降宽(全三段 → ①合并+②③ → ①合并+③ → 时段段落)+ recall 兜底。
|
||||
|
||||
**极限层(决策 5 终审定案,用户 2026-09-01):允许丢步,压成"时段段落"**——era 数再超上限时,最老 era 整体压缩为一段话,**必须携带步号范围与起止时间**:`[Era N | Steps a–b | T+hh:mm → T+hh:mm] <一段话概括该时段在做什么/做了什么/留下什么>` + recall 指引行;用户注入指令行仍逐字保留(never-evict 不随丢步豁免)。段落**机械拼装优先**(由该 era 各 chunk ① 的 doing/did/effect 与 verified_facts 浓缩,不做散文再摘要的 LLM 调用;段头缺失的 chunk 回退用里程碑标签)。步级可寻址在此层降级为"时段可寻址":起止时间是视频对齐的粗粒度外键,步级细节走 recall_history 回捞(DataEngine 永远完整)。此前"步骨架任何层级不破"的表述据此修订:**该不变量适用于 L2 chunk、era 合并、L3 快照,唯极限时段段落层除外**。
|
||||
|
||||
**L3 — 紧急全量快照**:实测 token 越过硬阈值(如连续注入大量工具输出)时,F 区的**段头/知识面**整体替换为一个结构化会话快照(schema 取 Gemini CLI `state_snapshot` 与 Claude Code 9 节的交集,加本域字段):`overall_goal / plan_state / verified_facts / unresolved / failed_paths / important_entities / device_app_state / recent_actions`。快照从 chunk 段头结构化字段合并生成,尽量不做"摘要的摘要"。**逐步索引仍保留**(最小行宽:步号 + 相对时间 + 动作短语),步级时间骨架在最坏情况下也不断链。A 区底线不动。
|
||||
|
||||
**滑动窗口底线(提案第 2 条保留)**:任何触发器都不得把 A 区压到 `min_active_steps`(默认 5)以下;最近 1–2 个完整回合(AI 推理原文 + 工具结果 + Validator 配对)永远原样——姊妹文档 §4.2"连续推理先于压缩"原则不变,且切分点永不劈开 tool-call/response 对(Gemini CLI 同款约束)。
|
||||
|
||||
### 3.4 计量与阈值
|
||||
|
||||
- **实测优先**:每轮把上一次 LLM 调用的 `usage_metadata.prompt_tokens` 记为转写基数,加本轮追加内容的启发式增量(char/4 + 每图按分辨率估算)作为决策输入。无估算器依赖、无额外调用。
|
||||
- 预算三档(**临时默认值,M0 实测后定标**):`context_budget_tokens=80K`;软阈值 0.7(56K)→ 触发 L2 压最旧段;硬阈值 0.9(72K)→ L3 快照。工具单次输出上限 16K(Codex 同款),超限走同回合信封(姊妹文档 §13 保留)。
|
||||
- 姊妹文档实测提醒仍有效:8.8–12.7K 区间"长度与延迟无关"**不可外推**到 80K;但缓存命中改变了成本函数——M0 必须重测缓存命中下的 TTFT 曲线再定 budget。
|
||||
|
||||
### 3.5 可逆性与召回
|
||||
|
||||
- 一切被压缩内容在 DataEngine 原样在案(现状已满足:截图 SHA-256 内容寻址、步骤/trace 全量);占位符与 chunk 块一律携带 step 引用。
|
||||
- `recall_history` 工具照姊妹文档 §10 原样落地(FTS + step 范围 + 包/Activity + 屏幕 hash,≤5 结果 ≤2K token,图片按需回捞单步);本地屏幕相似度提示(不发图、不调模型)保留。
|
||||
- **Operator 只挂一个历史工具(2026-09-01 定,防过度设计)**:取历史 = chunk ①②③ 常驻索引(免费)→ `recall_history` 确定性回捞(一次查询,事实级)。HistoryAnalyzer **不进 Operator 工具清单、不进提示词指导**——它输出属推理级(事实优先级链最末)、每次调用是全量历史输入 + 关键路径等待,且"开放式跨历史语义问题"的需求目前纯属推测。仓库中它照旧存在(其他流程自用);若真机 trace 日后出现"recall 反复查不到、问题属聚合/因果型"的实证,再评估以升级路径接入。era 溢出标记行指向 recall。
|
||||
|
||||
---
|
||||
|
||||
## 4. 与提案逐条对照
|
||||
|
||||
| 提案 | 方案落点 |
|
||||
|---|---|
|
||||
| 每步增量构建(Flash 式) | §3.1 Transcript:Pro/Flash 统一 append-only;Pro 获得真多轮 + 思考签名 + 缓存命中 |
|
||||
| 后台 flash-lite 图片压缩 | §3.3 L1:每步后台准备(StepMemoryService dispatch),深度 K 延迟应用(修正 #1) |
|
||||
| 里程碑切换时压缩前段 | §3.3 L2:里程碑切换是首要触发器;chunk 身份按 step 范围(修正 #2) |
|
||||
| 滑动窗口底线 | §3.3:`min_active_steps` + 最近完整回合硬保留 |
|
||||
| 长期不切换按窗口上限压 | §3.3 L2 尺寸阈值 + §3.4 实测计量(修正 #3);再兜底 L3 快照(补强 #4) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 摘要语义:两个 lens 按"高度"分工(决策修订,已确认 2026-08-31)
|
||||
|
||||
visibility 文档 §6.1 曾定"两 lens 按 profile 分立"(Flash=证据替身,Pro=整步胶囊),依据是 *Pro 丢弃旧步思考*。**本方案改变了这个前提**:转写保留推理原文直到段压缩,Pro 的步级摘要也变成了"证据替身"。因此修订为:
|
||||
|
||||
- **步级 = `VisualTransitionLens`(唯一步级 lens,双 profile 共用,输入按可得自适应)**:**替换哪张图就描述哪张图,有什么用什么**(用户 2026-09-01 定)——Flash 前后双图照旧;Pro 仅单决策帧+动作红标(描述该屏内容与动作落点,转换信息由相邻步摘要自然链接),单图时提示词不含 AFTER ACTION 段。**Pro 无动作后抓屏是刻意安全网**:ADB 截图延迟会丢瞬态控件(实测视频播放控件),截图量已压到最低——不得以任何方式"修复"(不补抓、不用步 N+1 前图拼后图)。禁判定词契约不变(successfully/completed/entered/navigated/failed/... 全禁);缺后图永远只表示"无独立后验证据"。
|
||||
- **段级 = `StepCapsuleLens` 产出 chunk 的 ①+②**:意图/策略/进度的压缩发生在段压缩时,输入是段内逐步视觉摘要 + 动作 + Validator 结果 + 该段推理原文(压缩前最后一次可读)+ 段内 notes 写入记录。单次调用产出 ① 梗概与效果(三问式散文 + 结构化字段,含留下的笔记)与 ② 压缩步摘要(步区间叙事,步号全覆盖机械校验)。**③ 逐步动作账本机械拼装不经 LLM**(用户硬约束:按步压缩、尽量维持原状、时间感知精确到步)。`summarizer.md` 的语义权威与中立契约(含 `[Loop]` 循环不算异常的判别)迁移到 ①②。
|
||||
- **会话级 = 快照 schema**(L3 专用)。
|
||||
- 事实优先级链不变:用户指令 > Validator/Controller 客观结果 > XML/OCR 差异 > 有源摘要 > 推理。
|
||||
|
||||
推论:Pro 图中的 `SummarizerNode` 不再逐步生成文本胶囊,退化为 `dispatch(VisualTransitionLens)` 一行;辅助 agent 编译视图中的步行改为"时间 + 视觉摘要 + 精确动作 + Validator 结果",段外历史提供 chunk digest。**这是对先前已确认决策的修订,用户已于 2026-08-31 拍板采纳(见 §10 决策 1)。**
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据模型与图接线
|
||||
|
||||
### 6.1 DataEngine 增量(最小 schema 变更)
|
||||
|
||||
- `StepRecord.extra_metadata` 增写:`summary_status`(pending/ready/failed/stale)、`summary_source`(lens 名)、`summary_version`、`summary_model`——`update_step_summary` 从盲覆写升级为带状态与版本的写入(同键并发以版本判序)。
|
||||
- 新增 chunk 存储(SQLite 新表,字段即 §3.3 结构 + `session_id`/`version`);`get_agent_friendly_steps` 增配套 `get_history_chunks`。
|
||||
- 修 `record_step` 的隐式语义:post==pre 时 `post_image_name=None` 表示"无独立后图证据",摘要器**不得**据此推断"屏幕未变"(姊妹文档 §4.3 原则写进 lens 契约)。
|
||||
- **动作语义充实(record-time enrichment,M0)**:动作落 DataEngine 前,凡带坐标而缺 `target_text` 的(裸坐标点击 `operator.py:854-861`、Flash 动作工具、FA `_exec_*`),对当帧感知快照做命中测试(fused_elements → XML → OCR 取覆盖该点的最小可交互元素),best-effort 填 `target_text/target_class/target_resource_id`,并记 `target_label_source: "index"|"hit_test"|"ocr"|"none"` 区分模型指名与事后推断。渲染侧 `format_action_clean`(task_tree.py:39-64)已支持 target_text,零改动受益。Phase 2 PerceptionStore 落地后命中测试即快照一次查询,零额外抓取。
|
||||
|
||||
### 6.2 运行时归属
|
||||
|
||||
```text
|
||||
ctx.step_memory: StepMemoryService # visibility 方案 D 保留:调度/有界重试/flush/step_id 键控
|
||||
├─ lenses: VisualTransitionLens / StepCapsuleLens(chunk) / SnapshotLens
|
||||
└─ transcript: TranscriptLedger # 新:四区消息账本 + 擦洗沿 + 压缩事件
|
||||
```
|
||||
|
||||
- 有界重试(替换 Flash 无界循环)+ `flush(timeout)` 收尾 + 显式降级态,均照 visibility §6.1 原文。
|
||||
- 转写在内存;崩溃恢复走 §3.1 冷启动构造器。素材以 `PerceptionRef`/`image_name` 引用(Phase 2 落地后免搬 bytes;Phase 2 未落地前先传 bytes,接口留 ref 形态)。
|
||||
|
||||
### 6.3 图接线
|
||||
|
||||
- **事件源**:`_record_turn`(`graph.py:108-148`)比较相邻步 `subgoal_hash` → `transcript.on_milestone_boundary()`;`_process_plan_write` 的 `new_top_level_completions` → 同一入口(去重)。
|
||||
- **Operator 切换**(flag `memory.transcript.enabled`):`_build_prompt` 改为 `transcript.render_messages(tail=当前观察)`;旧 2-message 路径完整保留为回退。现成接缝 `unified_history` 槽位用于过渡期 A/B 对拍。
|
||||
- **Flash 切换**:`compress_flash_messages` 的逐轮全量重扫替换为擦洗沿推进;`max_turns` 可放宽(压缩已兜底上下文)。
|
||||
- 辅助 agent 不动图结构,编译视图接 ContextPolicy 策略表(visibility §6.2 原样,8 个调用点逐点对拍)。
|
||||
- 顺手修:Diagnoser `window_steps` bug、两个死参数、死常量 `MAX_MESSAGES_IN_HISTORY`。
|
||||
|
||||
### 6.4 配置(收敛 visibility §6.3 与姊妹文档 §14)
|
||||
|
||||
```jsonc
|
||||
"agent": {
|
||||
"memory": {
|
||||
"runtime": { "max_concurrency": 2, "retry_limit": 3, "flush_timeout_s": 30 }, // max_concurrency 1→2:硬化轮定标(基线报告 §6)
|
||||
"transcript": {
|
||||
"enabled": true, // M2-M4 出厂关灰度;M5 起默认开(2026-09-01 用户拍板);false 即逐字节回滚开关
|
||||
"context_budget_tokens": 80000, // 2026-09-01 基线定标:维持(稳态 ≤29% 预算)
|
||||
"soft_ratio": 0.7, "hard_ratio": 0.9,
|
||||
"min_active_steps": 5,
|
||||
"image_scrub_depth": 3, "pending_grace_steps": 3,
|
||||
"xml_scrub_depth": 1,
|
||||
"similarity_hint": true, "similarity_max_distance": 5 // M5 定标 8→5(460 步 dHash 分布)
|
||||
},
|
||||
"chunking": { "max_steps": 12, "target_source_tokens": 2000, "model": "gemini-3.7-flash", "max_chunks": 8, "max_eras": null }, // max_eras null=随 max_chunks(M5 新独立键)
|
||||
"step_lens": { "model": "gemini-3.5-flash-lite" }, // 现 agent.flash.step_summarizer 键保持兼容别名
|
||||
"recall": { "enabled": true, "max_results": 5, "max_text_tokens": 2000 },
|
||||
"policies": { /* 辅助 agent ContextPolicy 覆盖 */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 分阶段落地
|
||||
|
||||
前置说明:visibility 文档 Phase 0/1 已实施;其 Phase 2(PerceptionStore)/Phase 3(ctx 分解)独立推进,与本轨道仅在 §6.2 的 ref 传递处交汇(非阻塞依赖)。
|
||||
|
||||
**M0 — 计量与 schema(无行为变化,可独立合入)** ✅ 代码侧已实施 2026-08-31;✅ 基线录制已完成 2026-09-01(真机 Pixel 10,见 `docs/plans/history-baseline-2026-09-01.md`)
|
||||
- `summary_status/source/version` 落 `extra_metadata`;`update_step_summary` 版本化;
|
||||
- 动作语义充实(§6.1):裸坐标/Flash/FA 动作记录时命中测试补 `target_text` 等字段(越早上线历史数据越完整);
|
||||
- 转写计量管线(上轮 prompt_tokens 采集 + 增量估算)纯记录不决策;采集缓存命中率基线(`cachedContentTokenCount`);
|
||||
- 修 Diagnoser bug、死参数、死常量;Flash 摘要重试改有界 + flush 语义(visibility §8 已定为有意变更,单测覆盖);
|
||||
- 冻结代表性长任务集,录基线:输入 token、TTFT、全回合延迟、摘要就绪延迟、重复动作率、FA 触发率(姊妹文档 Phase 0 原样)。
|
||||
|
||||
M0 实施要点(2026-08-31,工作区未提交;与上述计划的差异逐条标注):
|
||||
|
||||
1. **摘要状态与版本化**:`StorageManager.update_step_summary` 升级为原子读改写
|
||||
(单连接内读 `extra_metadata` → 判序 → 写回),签名
|
||||
`(step_id, summary, *, source, version, model, status)`,返回 bool(stale 写返回
|
||||
False)。判序规则:显式 `version` 低于在库版本即丢弃;`version=None` 自动
|
||||
`在库+1`;**状态降级需显式更高版本**——`pending` 不覆盖 `ready/failed`、
|
||||
`failed` 不覆盖 `ready`(防后台竞态回退);`summary` 列仅被
|
||||
`ready`+非 None 摘要触碰。`DataEngine.update_step_summary` 同签名透传(后台
|
||||
写 + 仅 ready 发 SSE,与旧行为逐字节一致)。调用方:Flash
|
||||
`VisualStepSummarizer` dispatch 时补写 `pending`、成功写
|
||||
`ready/source="visual_transition"/model`、耗尽写 `failed`;Pro `SummarizerNode`
|
||||
写 `ready/source="step_capsule"/model`。`stale` 态本阶段只入枚举,无写入方
|
||||
(M2 起由重摘/回滚使用)。
|
||||
2. **动作语义充实(record-time enrichment)**:新模块
|
||||
`artemis/utils/element_hit_test.py`(`find_element_at_point` /
|
||||
`hit_test_semantics`:最小覆盖元素胜出,XML 源优先于 OCR 源,best-effort 永不
|
||||
抛异常)。三条落点:① Pro 裸坐标分支(`operator.py` `resolve_target_element`
|
||||
坐标路径)就地命中测试 `state.indexed_elements`,click/long_press/input_text
|
||||
动作 dict 增 `target_label_source`(索引路径盖 `"index"`);②
|
||||
`McpActionExecutor._execute_device_action`(Flash 与 FA 共用漏斗)在
|
||||
`_translate` 后、设备调用前对归一化 target 命中测试(**必须在 observe 之前
|
||||
——observe 会覆写 indexed_elements 为后帧**),语义并入 action trace 的 args
|
||||
payload(FA 的 interleaved_events 渲染 `format_tool_call_clean` 零改动受益)并经
|
||||
`ToolExecutionResult.metadata["target_semantics"]` 回传;③ FlashRunner 落
|
||||
`record_step` 前把该 metadata 合入 `action_dict` 顶层。与计划的差异:
|
||||
`click_sequence`/`swipe` 多点动作本阶段不充实(单目标动作先行);"可交互"
|
||||
以"已被感知索引"近似(索引元素不带 clickable 位)。
|
||||
3. **token 计量管线**:新模块 `artemis/services/token_meter.py`
|
||||
(`extract_usage` 统一 LangChain `usage_metadata` 与 Gemini 原生
|
||||
`cachedContentTokenCount`/`input_token_details.cache_read` 两种形态;
|
||||
`SessionTokenMeter` 按会话累计)。采集点接在 LLM 网关
|
||||
`RobustChatModelWrapper.complete()/ainvoke()` 出口(全部生产调用必经;raw
|
||||
模型旁路仅测试使用,不采集),每次调用记一条 `llm_usage` trace(type
|
||||
`llm_call`):单次 prompt/completion/cached + `context_base_tokens`(=本次实测
|
||||
prompt,即下一轮上下文基数)+ 会话累计五项(calls/prompt/completion/cached/
|
||||
cache-hit calls)。纯记录,不驱动任何决策;采集失败静默降级永不进入调用路径。
|
||||
4. **三缺陷修复**:① Diagnoser `get_recent_subgoal_hashes` 改传
|
||||
`base_dir`(对齐 committee_tool 用法),Diagnoser 历史渲染恢复正常;②
|
||||
`build_plan_and_history` 删除 `chronological_last_step`/`detailed_subgoal_hashes`
|
||||
死参数,operator/failure_analyzer/planner 3 个调用点与 2 处单测同步清理;③
|
||||
删死常量 `MAX_MESSAGES_IN_HISTORY`(constants.py,全仓零引用确认)。
|
||||
5. **Flash 摘要重试有界化**(有意行为变更):`_run_summary_until_ready` 以
|
||||
`1 + retry_limit` 次尝试封顶(`StepSummarizerConfig.retry_limit`,默认 3,
|
||||
FlashRunner 接线),耗尽进显式 failed 态(`has_failed()` + DataEngine
|
||||
`summary_status="failed"`);**failed 步在压缩器视角仍为 pending**(保图,
|
||||
lossless 语义不变——变更只砍掉无限 LLM 重试,不改消息流);`flush(timeout)`
|
||||
收尾语义保留。
|
||||
6. **测试**:新增 `tests/unit/utils/test_element_hit_test.py`(6)、
|
||||
`tests/unit/data_engine/test_step_summary_versioning.py`(7,含并发判序/降级
|
||||
拒绝/SSE 仅 ready)、`tests/unit/test_token_meter.py`(7)、
|
||||
`tests/unit/mcp/test_action_executor_semantics.py`(4,含缺元素数据优雅降级
|
||||
`"none"`)、operator 命中测试正例 + Flash 有界重试失败注入 2 例;存量断言
|
||||
同步(operator 精确 dict 断言补 `target_label_source`、两摘要器调用断言补
|
||||
kwargs)。验证:tests/unit 全量 **1094 通过 3 跳过 0 失败**;tests/tools
|
||||
相关子集(operator/diagnoser/validator)7 通过(既有 5 个 API key 联网失败
|
||||
未触发)。
|
||||
7. ~~**未完成项**:代表性长任务基线录制~~ **已完成(2026-09-01)**:3 个冻结
|
||||
长任务 ×2 样本真机录制完毕,正式基线与 §3.4 定标建议见
|
||||
`docs/plans/history-baseline-2026-09-01.md` §3/§6(要点:Operator 首调输入
|
||||
P50 12.2K/P95 17.6K;隐式缓存基线 43% 调用/29.8% token;模型调用 P50
|
||||
13.8s;摘要就绪 P50 60s/P90 214s——长尾超擦洗窗口,定标建议
|
||||
max_concurrency 1→2)。附带发现:视觉 lens 调用走 `get_google_llm` 原始旁
|
||||
路,无 llm_usage 计量(与本条"raw 旁路仅测试使用"表述不符,待收口)。
|
||||
|
||||
**M1 — StepMemoryService + 擦洗沿(先 Flash 落地验证)** ✅ 代码侧已实施 2026-08-31(缓存命中提升待真机实测)
|
||||
- 共享运行时成形;Flash 从"逐轮全量重扫"切到"擦洗沿"(深度 K 图替换 + 宽限 + 占位符);
|
||||
- 验收:成功路径消息序列与现版语义等价(图→摘要时点后移属有意变更,单独对拍);缓存命中率显著上升(有实测基线可比);race 缺陷回归测试(图丢摘要空、迟到回填深突变均不再发生)。
|
||||
|
||||
M1 实施要点(2026-08-31,worktree `zealous-gates-4626b1` 未提交,叠加在 M0 之上;与计划的差异逐条标注):
|
||||
|
||||
1. **StepMemoryService 成形**:新 `artemis/memory/step_memory.py`——调度骨架自
|
||||
Flash `VisualStepSummarizer` 上提:零阻塞 `submit`、有界重试(1 + retry_limit,
|
||||
沿 M0 语义,耗尽进显式 failed 态并触发 `_on_status` 钩子)、`flush(timeout)`
|
||||
收尾(无参时用配置的 `flush_timeout_s`)、`max_concurrency` 信号量封顶并发
|
||||
(重试退避 sleep 在信号量之外,不饿死同伴)、状态查询
|
||||
(has_job/is_pending/has_failed/get_summary/get_job_payload)。**键控**:有
|
||||
DataEngine step_id 时以 `str(step_id)` 为规范键,tool_call_id 落别名映射;两者
|
||||
皆无的旧调用方保持序号键(压缩器的 tool_call_id → 序号回退链原样保留)。
|
||||
`VisualStepSummarizer` 取"并入服务"路线——改为服务子类,只保留视觉 lens 语义
|
||||
(红标叠加、`flash_summarizer.md` 禁判定词契约、M0 版本化
|
||||
`summary_status` 写入),对外表面(dispatch 签名/查询方法)零迁移。与计划的
|
||||
差异:a) 服务本阶段仍由 FlashRunner 自持,未挂 `ctx.step_memory`(M2 Pro 切换
|
||||
时再上提组合根);b) lens 未做成独立协议对象,视觉 lens 即子类 `_attempt`
|
||||
(StepCapsuleLens 落地时再抽形式化 lens 接口);c) `max_concurrency=1` 默认值
|
||||
使摘要 LLM 调用串行化(旧实现无界并发),属计划 §6.4 既定值。
|
||||
2. **擦洗沿压缩器**:`ScrubEdgeCompressor`(`context_compressor.py`)——索引制
|
||||
旁路账本(契约:FlashRunner 消息列表 append-only),增量发现新消息,冻结前缀
|
||||
永不重扫。纪律:深度 1 剥 XML(保留集 = 最新 `xml_scrub_depth` 条观察,合并块
|
||||
保前缀语义与 `prune_history_xml` 开关照旧);深度 K 图片决议——ready→摘要块
|
||||
(沿用 `--- Historical Visual Transition ---` 前缀、剥图后追加于消息尾,与旧
|
||||
产物同形)、failed→立即 `[visual summary unavailable; evidence at DataEngine
|
||||
step N]`(**不吃宽限**,M0 的"failed 仍保图"按计划收口为有意行为变更,单测
|
||||
覆盖)、pending→保图宽限至深度 K + pending_grace_steps,越限则
|
||||
`[visual summary pending; evidence at DataEngine step N]`;宽限期内摘要到达即
|
||||
正常替换;决议后冻结,**迟到摘要永不回填**(哈希不变量测试)。N = Flash 动作
|
||||
序号(与 DataEngine 步序一致)。细节差异:a) 无 job 的历史图(初始观察、摘要
|
||||
器关闭时的动作图)同样计深并在沿上静默剥除(无占位符)——旧实现是"下一轮即
|
||||
剥",新时点后移至深度 K;b) 宽限计量单位是"图深度推进"而非回合数(无新图的
|
||||
空转回合不消耗宽限,上下文也未因图增长);c) 旧 `compress_flash_messages` 原
|
||||
样保留,作语义对拍参照与回退路径(M2 转写落地后再删)。
|
||||
3. **配置**:`agent.memory.runtime`(max_concurrency=1/retry_limit=3/
|
||||
flush_timeout_s=30)与 `agent.memory.transcript`(image_scrub_depth=3/
|
||||
pending_grace_steps=3/xml_scrub_depth=1)已落 `AgentGlobalConfig.memory`;
|
||||
显式的旧键 `flash.step_summarizer.retry_limit` 在 before-validator 里播种
|
||||
`memory.runtime.retry_limit`(新键显式设置时新键胜出,旧键本体不动);
|
||||
enabled/model/prune_history_xml 三键权威仍在旧块(未迁移)。
|
||||
`xml_scrub_depth` 已实现为通用深度(>1 时保留更多层 UI 列表),默认 1 与现
|
||||
行为等价;`prune_history_xml=false` 时冻结也不剥 XML(保旧开关语义)。
|
||||
4. **FlashRunner 接线**:摘要器以 `memory.runtime` 三值构造;压缩器为 per-run
|
||||
实例(擦洗账本是运行态);`flush()` 走配置超时。
|
||||
5. **测试**:+20(`tests/unit/memory/test_step_memory_service.py` 7:零阻塞/有界
|
||||
重试/别名键控/并发封顶/flush 超时与默认值/重投递清 failed;
|
||||
`tests/unit/agents/test_flash_scrub_edge.py` 8:成功路径新旧对拍(时点后移
|
||||
在断言中显式标注)、race a/b/c、冻结哈希不变量、XML 深度 1 等价、
|
||||
prune 开关、非跟踪消息不触碰;`tests/unit/test_memory_config.py` 5:默认值/
|
||||
显式值/旧键播种/新键优先/无显式旧键不播种)。存量适配 1 处:
|
||||
`test_summarizer_keys_actions_independently_of_turn_number` 的
|
||||
`_step_inputs["tc-1"]` 直取改为 `get_job_payload`/`resolve_key`(step_id 键控
|
||||
所致)。验证:tests/unit 全量 **1114 通过 3 跳过 0 失败**(M0 基线 1094+3,
|
||||
只增不减);tests/tools 子集(operator/summarizer/validator/diagnoser)7 通过。
|
||||
6. **max_turns 未动**(默认 30):擦洗沿使图片上下文稳态有界,更大 max_turns
|
||||
已安全,放宽属配置决定,留给使用方/后续里程碑。
|
||||
7. **未完成项**:缓存命中率提升的量化(M1 验收第二条)依赖 M0 尚未录制的真机
|
||||
基线——计量管线已就绪,待真机长任务前后对比。
|
||||
|
||||
**M2 — Pro 转写切换(本方案核心,flag 灰度)** ✅ 代码侧已实施 2026-09-01;✅ 真机 A/B 已完成 2026-09-01(红线全过且大幅改善,含一处阻塞 bug 的最小修复,见 `docs/plans/history-baseline-2026-09-01.md`)
|
||||
- **前置子任务**:operator.json 模板拆分——今日 SystemMessage 经 `# CURRENT OBSERVATION` 切分后含易变的 plan+history(prompts.py:218-222),必须把静态规则留 S 区、动态内容移出,否则 S 区无从字节稳定;
|
||||
- `TranscriptLedger` + Operator 双路径并存;转写路径 A/B 对拍旧路径(同任务同设备,姊妹文档 §18 红线:P50/P95 延迟回归 ≤5%、关键路径零摘要等待);转写内时间一律 `T+mm:ss` 起点偏移(冻结后字节稳定),不用 "ago" 措辞;
|
||||
- SummarizerNode 退化为视觉 lens dispatch(§5 决策已确认);辅助 agent 编译视图接 chunk 前先保持现状。
|
||||
|
||||
M2 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1 之上;与计划的差异逐条标注):
|
||||
|
||||
1. **模板拆分(前置子任务)实现为运行时拆分**:operator.json 本体一字节未动——
|
||||
唯一易变跨度是字面量 `PLAN_HISTORY_TEMPLATE_SECTION`(`## Current Plan &
|
||||
Execution History\n{{ plan_and_history }}`),转写路径
|
||||
`render_transcript_static_system`(prompts.py)把它替换为静态指针段后渲染,
|
||||
得到整会话字节不变的 S 区(可用性装配 `resolve_operator_prompt_tools` 与
|
||||
语法开关 `_operator_grammar_flags` 从 TemplatePromptComponent 逐字提取共用,
|
||||
旧组件行为等价)。硬约束双保险:模板中该字面量"恰出现一次"的结构断言
|
||||
(缺失即抛错,§9 风险落地)+ 拆分前录制的旧路径渲染 SHA-256 golden
|
||||
(空计划/哨兵计划两份,`test_operator_transcript.py`)。S 区不再以
|
||||
`# CURRENT OBSERVATION\n` 结尾——该头衔移入每回合尾部观察消息。
|
||||
2. **TranscriptLedger**(新 `artemis/memory/transcript.py`):S/F/A 三区实体
|
||||
+ 每回合 T 尾部由 render 参数传入。A 区 append-only;回合落账两段式
|
||||
**stage→commit**(step_id 与 Validator 结果在 execution_check/validator 之后
|
||||
才存在,故本回合消息先 stage,下一回合 build 时带 `step_key` +
|
||||
`validator_result` 提交)。Validator 结果消息门控
|
||||
`state.structured_decisions`(planner 拒绝清空、无终端动作为 None,均不落
|
||||
账),渲染为 `Status: <status>` + format_result_clean 错误/修复细节,带
|
||||
`T+mm:ss` 偏移(`format_session_offset`,分钟不进位保字节稳定单调)。
|
||||
观察消息的摘要键走 `id(message)` 侧表(不进消息序列化载荷)。工具配对不
|
||||
变量:擦洗只改内容块、永不删消息,切分点天然不劈 tool-call/response 对
|
||||
(单测覆盖)。
|
||||
3. **擦洗沿复用=泛化 ScrubEdgeCompressor**(未另造 Pro 压缩器):新增三个
|
||||
构造参数——`summary_key_getter`(无 tool_call_id 的 Pro 观察 HumanMessage
|
||||
按 step_id 键控查摘要)、`strip_markers`(深度 1 剥除扩展到
|
||||
`--- Visible UI Elements ---` 与 `--- Task Plan (recited) ---` 计划复诵副本)、
|
||||
`tail_offset=1`(活尾部在账本外,深度算术与 keep 窗口对齐 Flash 语义)。
|
||||
默认参数下 Flash 路径字节不变(M1 全部擦洗测试原样通过);宽限/占位符/
|
||||
冻结不回填纪律在 Pro 消息形态下重跑 M1 用例形态(`test_transcript_ledger.py`)。
|
||||
`get_step_number` 从 VisualStepSummarizer 上提到 StepMemoryService 基类。
|
||||
4. **Operator 双路径**(flag `agent.memory.transcript.enabled`,默认 false,
|
||||
出厂关):关——旧 `_build_prompt` 路径零改动(golden + 存量 20 测试全绿);
|
||||
开——`_build_prompt_transcript` 产出 S+F+A+尾部,尾部 = `# CURRENT
|
||||
OBSERVATION [T+mm:ss]` 头 + 计划复诵(新 PlanRecitationPromptComponent,
|
||||
复诵 task_plan 原文)+ 既有观察/注入/反馈组件原样复用。转写构建异常自动
|
||||
回退旧路径(灰度安全网)。与计划的差异:a) 过渡接缝取 PromptComponent
|
||||
机制新增组件而非 `unified_history` 空槽位(改动面更小,任务书允许取小者);
|
||||
b) 复诵内容是 task_plan checklist 而非 build_plan_and_history 编译串——
|
||||
历史已由 A 区原始消息承载,整串复诵会大量重复(§3.2 T 区语义即"计划复诵")。
|
||||
Operator 可见性读集新增 `last_execution_result`(落账需要,documented
|
||||
decision)。
|
||||
5. **服务归属收口(M1 差异点 a)**:`ctx.step_memory` / `ctx.transcript_ledger`
|
||||
成为 ArtemisContext 声明字段(并列而非持有——§6.2 给出的两个选项取后者);
|
||||
`artemis.memory.ensure_step_memory(ctx)` 为组合根工厂(Operator 转写路径与
|
||||
SummarizerNode 共用一个实例);FlashRunner 构造后发布到 `ctx.step_memory`
|
||||
(只发布不消费,兼容存量 mock ctx 测试);`ArtemisContext.__aexit__` 在后台
|
||||
任务清理前 flush 服务(异常退出 timeout=0 直接取消)。State 不携带任何消息
|
||||
重物(`test_state_contract.py` 全绿)。
|
||||
6. **SummarizerNode 切视觉 lens(无条件,非 flag 后)**:不再自调 LLM 生成文
|
||||
本胶囊;按 step_id 从 DataEngine 取 pre/post 图 bytes(`get_step_image_path`),
|
||||
动作取步记录 `action_taken`(缺则回退 structured_decisions 解析),dispatch
|
||||
到 `ctx.step_memory` 视觉 lens(红标叠加、flash_summarizer.md 契约、M0 版本
|
||||
化 `source="visual_transition"` 写入均沿用)。图缺失→None bytes 纯文本降级,
|
||||
不抛错。与计划的差异:多动作步 dispatch 首动作为主体、其余进
|
||||
`action_args["additional_actions"]`(红标叠加只画单动作)。过渡期安全性靠
|
||||
task_tree.py "无摘要回退 detailed" 语义(未动)。
|
||||
7. **冷启动构造器(最小版)**:转写空(0 回合、无 F 区)而 DataEngine 已有步
|
||||
记录时,用 `build_plan_and_history(min_summaries=len(steps),
|
||||
last_n_detailed=1)` 全量策略渲染冻结块,头部 `[Restored history]` 并注明
|
||||
"step times relative to the original session start"(该块内的 relative_time
|
||||
仍是 DataEngine 措辞,冻结一次生成故可接受;A 区从当前步正常追加)。
|
||||
8. **测试**(只增不减):+22 净增——`tests/unit/memory/test_transcript_ledger.py`
|
||||
12(S 区哈希稳定/A 区 append-only/T+mm:ss 与禁 ago/深度 1 剥复诵与 UI 列表/
|
||||
深度 K ready 替换/宽限→占位符→冻结不回填/failed 立即占位/配对不劈/冷启动
|
||||
块唯一性/双 stage 自动落账/无动作回合不落 Validator 消息);
|
||||
`tests/unit/agents/test_operator_transcript.py` 7(golden 双哈希/模板单一
|
||||
切点/S 区静态渲染无历史/两回合四区结构/无动作提交/冷启动 F 区/flag 关
|
||||
2-message 不变);test_summarizer.py 重写为 4(dispatch 断言与不再写胶囊/
|
||||
图缺失优雅降级/structured_decisions 回退与 additional_actions/无 step_id
|
||||
no-op);test_memory_config.py +1(flag 默认关与显式开)。验证:tests/unit
|
||||
全量 **1136 通过 3 跳过 0 失败**(M1 基线 1114+3,只增不减);tests/tools
|
||||
子集(operator/summarizer/validator/diagnoser)7 通过。
|
||||
9. ~~**未完成项(待真机)**~~ **A/B 已完成(2026-09-01,见
|
||||
`docs/plans/history-baseline-2026-09-01.md`)**:红线全过且大幅改善——全回
|
||||
合延迟 P50 21.4s→5.0s(−77%)、缓存命中调用 43%→88%(token 29.8%→49.6%)、
|
||||
输出 token −74%、成功率 6/6 持平、零回退零摘要等待、无签名/格式类错误。
|
||||
三点必读:① A/B 中发现并最小修复了一个阻塞 bug——`_build_prompt_transcript`
|
||||
冷启动检查在第 2 回合误命中(上回合只 stage 未 commit,turn_count==0 且
|
||||
steps 非空),`set_restored_history` 被空账本不变量拒绝→每回合静默回退旧路
|
||||
径;修复为冷启动条件加 `not ledger.has_staged_turn`(operator.py,已在
|
||||
worktree,未提交,**需本轨道认领合入并补"第 2 回合 steps 非空"回归测试**)。
|
||||
② 处理组实际是 M2+M3 叠加(chunking 挂同 flag),纯 M2 归因未拆。③ 实验
|
||||
模型为 gemini-3.6-flash(3.7 当日 503)。思考签名跨压缩事件的供应商侧验证
|
||||
仍留 M3 前。灰度顺序照 §10 决策 3:建议修复合入后复测一轮再进默认配置。
|
||||
|
||||
**M3 — L2/L3 压缩** ✅ 代码侧已实施 2026-09-01(真机验收待跑)
|
||||
- 里程碑边界 + 尺寸阈值 + 边界事件三触发;chunk 表 + era 合并;L3 快照;
|
||||
- 验收:>30 步长任务转写 token 稳态有界;计划改写/回滚后 chunk 身份与可见性不受损(新增回归:重命名走别名链);`[Loop:continuous]` 100+ 步试跑无摘要堆叠。
|
||||
|
||||
M3 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1+M2
|
||||
之上;全部行为在 `agent.memory.transcript.enabled` flag 之后;与计划的差异逐条标注):
|
||||
|
||||
1. **HistoryChunk 存储**:SQLite 新表 `history_chunks`(chunk_id/session_id/
|
||||
start·end_step_id/start·end_step_number/source_step_ids/subgoal_hash 标签/
|
||||
version/status/band1 JSON/band2/band3/rendered_text/created_at)。身份 =
|
||||
(session_id, 步号范围),行**append-only**:同范围更高 version 追加新行,
|
||||
`get_history_chunks` 默认取各范围最新版(`all_versions=True` 给审计全轨)。
|
||||
DataEngine 增 `record_history_chunk`(后台写)/`get_history_chunks`。
|
||||
**单写者纪律**:全部 DB 写经 HistoryChunkManager 内存镜像(capsule 服务不
|
||||
直写 DB),版本号无竞争。
|
||||
2. **三段式 chunk(§3.3 逐字对齐)**:
|
||||
- **③ 机械账本**(`chunking.build_action_ledger`,不经 LLM):每步一行
|
||||
`- Step N (T+mm:ss): <format_action_clean> -> <结果短语>`(时间 = 步
|
||||
timestamp − session_start_time,经 `format_session_offset`,禁 "ago");
|
||||
结果短语 = format_result_clean 错误/修复细节,success 收敛为 `executed`;
|
||||
FA 恢复动作(interleaved_events 中 `_exec_*`)以 ` FA: ` 缩进子行经
|
||||
`format_tool_call_clean` 渲染;injected_instruction 逐字独立行
|
||||
` User @ Step N: "…"`,**任何层级 never-evict**(含 era ③ 溢出标记与
|
||||
L3 最小索引,单测覆盖)。为此 `_record_turn` 把 `state.injected_instruction`
|
||||
盖章进步记录 extra_metadata(execution_check 读集补声明,documented
|
||||
decision)。差异:多动作步仍一行,首动作 + `(+N more actions)` 后缀。
|
||||
- **①+② StepCapsuleLens**(`memory/chunking.py` + 提示词
|
||||
`memory/step_capsule.md`,模型 `agent.memory.chunking.model`):单次调用
|
||||
产出 JSON(doing/did/effect 三问式——effect 硬性要求含段内 notes 写入的
|
||||
目标文件与要点,输入里逐步附 save/update/append_note 的 key+content 摘录、
|
||||
task_plan 写入除外——+ verified_facts/unresolved/failed_paths/
|
||||
important_entities/entry·exit_state + intervals)。② 区间并集
|
||||
`validate_interval_coverage` 机械校验(缺口/重叠/越界即该次尝试判负→
|
||||
StepMemoryService 有界重试→耗尽 failed→chunk 维持 pending,③ 独立可用,
|
||||
单测覆盖)。中立契约禁词表沿 flash_summarizer.md/summarizer.md 迁入,
|
||||
`[Loop]` 循环不算异常的判别一并迁入 lens 提示词。**lens 形式化接口收口
|
||||
(M1 差异点 b)**:`step_memory.StepLens`(`render(key, payload)`)+
|
||||
StepMemoryService 可选 `lens` 参数与默认 `_attempt` 委托 + `_on_ready`
|
||||
钩子;差异:VisualStepSummarizer 保留内联 `_attempt`(零迁移),未强迁。
|
||||
- **异步纪律**:capsule 经 `ChunkCapsuleService`(StepMemoryService 子类,
|
||||
memory.runtime 三值)后台 dispatch;压缩事件时刻未就绪→冻结 ③ + pending
|
||||
标记;就绪结果**只在下一次压缩事件**收割(镜像+DB ready 版本写均在收割
|
||||
时;ctx.__aexit__ 增 chunker.flush 兜底收割落库,无 F 区重渲染)——冻结
|
||||
块事件间字节稳定有哈希单测。
|
||||
3. **触发接线**:里程碑唯一事实源 = `_record_turn` 记步后通知
|
||||
`on_step_stamped(step_id, subgoal_hash)`,chunker 内比较相邻盖章;
|
||||
`_process_plan_write` 的 `new_top_level_completions` → `queue_boundary_hint`
|
||||
(未确认边界),下一盖章步 hash 未变即丢弃(棘轮否决回滚的伪切换单测覆盖;
|
||||
注:执行图在 `_record_turn` 前恒 await planner 验证,盖章天然后于回滚落定)。
|
||||
尺寸阈值 = 开放段回合数 ≥ `chunking.max_steps` 或段内转写文本 char/4 ≥
|
||||
`chunking.target_source_tokens`(差异:源 token 估算用账本内该段消息文本,
|
||||
非"段内摘要"——转写擦洗后消息主体即摘要,作机械代理);软阈值 =
|
||||
SessionTokenMeter 的 last_prompt_tokens ≥ budget×soft_ratio 时压最旧开放段
|
||||
(≤max_steps 回合;测试经 `meter_getter` 注入假计量)。**压缩事件在
|
||||
ledger.render 时执行**(operator 单线程路径;graph 事件只置状态):底层
|
||||
`_active` 列表保持 append-only(`_active_start` 边界前移,ScrubEdge 索引
|
||||
恒有效),边界永在完整回合末(不劈 tool-call/response 对,单测覆盖);
|
||||
加严:闭合段须**整段**滑出 `min_active_steps` 保护窗才压缩——底线只延迟
|
||||
事件、不切分段(忠于"上一段压成一个 HistoryChunk"),仅尺寸/软阈值可对
|
||||
开放段出窗部分分片(每片 ≤ max_steps)。
|
||||
4. **L3 紧急快照**:last_prompt_tokens ≥ budget×hard_ratio 时 F 区渲染为单块
|
||||
会话快照(overall_goal 取 operator 构造时 initial_goal / plan_state 读
|
||||
task_plan 原文 / verified·unresolved·failed_paths·entities 结构化字段机械
|
||||
集合并 / device_app_state 取最新有 capsule chunk 的 exit_state)+ **每
|
||||
chunk 最小行宽逐步索引**(`build_action_ledger(minimal=True)`:步号+T+时间+
|
||||
动作短语,user 行保留)。差异:计划"一次 LLM 润色可选"未实现(纯机械合并,
|
||||
避免摘要的摘要);recent_actions 未入快照(A 区底线本就保留最近原始回合)。
|
||||
5. **era 合并**:F 区完整 chunk 数超 `chunking.max_chunks`(新键,默认 8)时
|
||||
最旧溢出段折叠成 era(① 结构化字段集合并非散文再摘要、② 退化为
|
||||
`- Steps a–b: <里程碑标题|did 首句>` 标题行、③ 逐段 1:1 保留);era 数再超
|
||||
同一限值时最旧 era 的 ③ 溢出为
|
||||
`[Era N | Steps a–b: ledger via recall_history]` 标记行(user 注入行仍逐字
|
||||
保留)。**§10 决策 5 按建议实施,待用户终审**(终审后标记行已升级为时段
|
||||
段落,见本节末"决策 5 终审收尾")。差异:每次折叠事件生成一个
|
||||
era(未做跨事件 era 再合并);era 上限复用 max_chunks 同值。
|
||||
6. **异步检查点回果**:`checkpoints.harvest_run` 在 `_record_verdicts` 后
|
||||
best-effort 通知 `annotate_from_checkpoint(checkpoint_id, verdicts)`——按
|
||||
subgoal hash 经 `get_all_subgoal_aliases` 别名链匹配 chunk 标签(重命名
|
||||
韧性单测),命中即追加 band1.annotations + **version bump + DB 即时追加**,
|
||||
F 区文本下一压缩事件才重渲染(单测覆盖)。
|
||||
7. **配置**:新块 `agent.memory.chunking`{max_steps=12, target_source_tokens=
|
||||
2000, model="gemini-3.7-flash", max_chunks=8};transcript 增
|
||||
context_budget_tokens=80000 / soft_ratio=0.7 / hard_ratio=0.9 /
|
||||
min_active_steps=5。真机基线报告(history-baseline-2026-09-01.md)截至实施
|
||||
时不存在,全部维持计划临时默认,**待定标**。
|
||||
8. **Flash 侧**:chunker 只在 Pro 转写路径的 TranscriptLedger 上构造
|
||||
(operator `_ensure_transcript_ledger`,有 DataEngine 才挂);Flash 尚无
|
||||
ledger,M3 对 Flash 零行为变化。chunker 对里程碑事件是可选输入,尺寸/软硬
|
||||
阈值纯凭 ledger 回合元数据触发——Flash 未来接 ledger 时自动呈现"只有尺寸
|
||||
阈值触发"的任务书形态。
|
||||
9. **测试**:+29 净增——`tests/unit/memory/test_history_chunking.py` 25
|
||||
(③ 结构 golden 与 FA 子行/注入逐字/最小行宽可寻址、② 覆盖校验与缺口
|
||||
重生成与有界重试降级 pending、三段顺序 golden(对照 §3.3)、真切换整段
|
||||
成 chunk、伪切换回滚丢弃、尺寸两式、软/硬假计量注入、min_active 底线、
|
||||
F/A 边界不劈对、冻结字节稳定与就绪不即时回填、回果 version bump 与下次
|
||||
事件重渲染、重命名别名链、era 合并 ③ 保留、era 溢出标记与 never-evict、
|
||||
全层级步可寻址不变量、lens 接口);
|
||||
`tests/unit/data_engine/test_history_chunk_storage.py` 4(roundtrip/版本
|
||||
append-only 最新胜出/范围排序/会话隔离)。验证:tests/unit 全量
|
||||
**1165 通过 3 跳过 0 失败**(M2 基线 1136+3,只增不减);tests/tools 子集
|
||||
(operator/summarizer/validator/diagnoser)7 通过。
|
||||
10. **未完成项(待真机/后续)**:验收三条真机项(>30 步 token 稳态、
|
||||
`[Loop:continuous]` 100+ 步无堆叠试跑、capsule 质量盲评);辅助 agent
|
||||
编译视图接 chunk digest(§10 决策 4 建议 M3 末——本次任务书未含,留 M4
|
||||
与 recall 一起收口);阈值定标依赖真机基线报告。
|
||||
|
||||
**M4 — recall + 策略表收拢** ✅ 代码侧已实施 2026-09-01(阈值定标待真机)
|
||||
- `recall_history` 工具 + 屏幕相似度提示;ContextPolicy 8 调用点逐点对拍收拢;配置收敛与旧键兼容别名。
|
||||
|
||||
M4 实施要点(2026-09-01,worktree `zealous-gates-4626b1` 未提交,叠加在 M0+M1+M2+M3
|
||||
之上;与计划的差异逐条标注):
|
||||
|
||||
1. **recall_history 工具**(新 `artemis/tools/history_recall.py`,规格照姊妹文档
|
||||
§10):签名 `(query, step_range=None, include_details=False,
|
||||
include_images=False, max_results=5)`。第一期实现为 **Python 扫描而非
|
||||
SQLite FTS5**(差异:现库无任何 FTS5/索引,且 `get_agent_friendly_steps`
|
||||
已一次性给出全部检索面;FTS 留待量级需要时加)——检索面:步摘要/精确动作/
|
||||
Validator 结果/Operator 思考/FA trace 与注入指令(步记录)、XML/OCR 文本
|
||||
(`storage.get_image`,**扫描封顶最近 150 步**)、notes 文件(`utils/notes`)、
|
||||
history_chunks(band2/band3/rendered_text)。包/Activity 检索经 XML blob
|
||||
文本命中(差异标注:`record_step` 的 `foreground_app` 形参历史上从未被任何
|
||||
调用方写入,包名不单独持久化)。边界全部落地:结果数钳到
|
||||
`recall.max_results`;响应 char/4 估算超 `max_text_tokens` 即截断并提示收窄;
|
||||
**每条结果带步号**(notes 锚定到最近写入步,无写入记录则"as of Step N");
|
||||
include_details 每字段限幅;include_images 仅回捞**单步**真实存在的图
|
||||
(data-URL 块,形态镜像 ObservationPromptComponent;文件不存在给文字说明,
|
||||
永不多于 1 步)。**step_range 请求总是附带该范围的 `build_action_ledger`
|
||||
全宽行**——era ③ 溢出标记行 `[Era N | Steps a–b: ledger via recall_history]`
|
||||
由此获得真实入口。空 query 且无 step_range 拒绝执行(回用法提示)。
|
||||
2. **注册(仅 Pro,Flash 不动)**:graph.py `operator_specialized_wrappers`
|
||||
追加 wrapper;`DEFERRING_TOOLS` 加 recall_history(pre-decision 语义:与
|
||||
Turn-Ending 动作同回合即拒绝动作);提示词 `_PRE_DECISION_MEMORY_TOOLS`
|
||||
加名 + operator.json 在 TOOL USE PROTOCOL 第 1 类下加
|
||||
`[% if "recall_history" in available_tools %]` gated 指导段(六触发:似曾
|
||||
相识/旧精确值/两步无进展/第三次同一动作/摘要与观察冲突/用户要求回早前
|
||||
状态;明示禁投机召回;标记行=显式入口)。可用性 = `ctx.data_engine` 存在
|
||||
且 `memory.recall.enabled`——M2 的 SHA-256 golden 用 `data_engine=None`
|
||||
的 ctx 录制,故 **golden 无需重录、原样通过**;受影响的是全集字面断言
|
||||
(见要点 6)。manifest:`BACKEND_INDEPENDENT_TOOLS` 加 recall_history
|
||||
(分类完整性测试强制)。
|
||||
3. **屏幕相似度本地提示**(零模型调用、零历史图入 prompt):新
|
||||
`artemis/utils/image_hash.py` 纯 PIL 64 位 dHash(JPEG draft 快解码,
|
||||
哈希对重编码稳健故无需像素组件的 q75 对称重编码技巧);`record_step`
|
||||
**同步**盖章 `pre/post_image_dhash` 入 extra_metadata(同步是为了避免与
|
||||
后台写线程的并发字典突变;draft 解码使成本毫秒级;post==pre 时 post 哈希
|
||||
取 pre 哈希——"无独立后图证据"但后屏即前屏)。新
|
||||
`HistoricalStateHintPromptComponent` 插在 ScreenshotSimilarity 之后(legacy
|
||||
与 transcript 两条组件清单都插):**近 3 步任一命中即整体静默**(该 regime
|
||||
属像素级"卡在同屏"组件,两者永不同时触发);更老步扫描封顶 500 步
|
||||
(含已 chunk 化段——`get_agent_friendly_steps` 全量),距离≤阈值取最近的
|
||||
最小距离步,注入方案原文句式提示。
|
||||
4. **ContextPolicy 策略表**(新 `artemis/memory/context_policy.py`):8 个
|
||||
生产调用点(operator/operator_cold_start/failure_analyzer/planner/
|
||||
outputter/history_analyzer/diagnoser/committee)收拢为每 agent 一条
|
||||
`ContextPolicy` 声明,字段 1:1 映射 build_plan_and_history kwargs
|
||||
(`uncompressed`=min_summaries=len(steps) 惯用法、`whitelist`=运行时传
|
||||
keep_subgoal_hashes 两个便捷位);`build_history_for(agent, ...)` 为唯一
|
||||
入口,operator 的 last_n_detailed 构造器旋钮保留为运行时覆盖。**逐点
|
||||
golden 对拍**:`test_context_policy.py` 参数化 8 例逐字节断言 + "全表必有
|
||||
golden"覆盖断言。`agent.memory.policies` 按 agent 覆盖字段(未知字段告警
|
||||
忽略)。差异:第 9 个调用点 `mcp_server/tools/inspect_trace.py` 属离线
|
||||
审计渲染而非 agent 编译视图,未入表(保持直调)。
|
||||
5. **编译视图接 chunk(§10 决策 4 收口)**:`build_plan_and_history` 增可选
|
||||
`chunks` 入参——形状无关 dict(步号范围 + 预渲染文本),chunk 块紧随
|
||||
`--- Execution History ---` 按序输出,范围内步行被替换(detailed 窗口与
|
||||
最新步永不吞);**chunks 为 None/空时输出逐字节不变**(golden 锁定)。
|
||||
视图渲染在策略层:`full`(outputter/history_analyzer)= 持久化
|
||||
rendered_text 原样(含 ③ 全宽行);`digest`(diagnoser/committee)=
|
||||
rendered_text 在 "③ Step action ledger" 标题处截断 + recall 标记行
|
||||
`③ Step action ledger: available via recall_history (Steps a–b)`。chunk
|
||||
仅当调用点传 engine + transcript flag 开 + 有持久化行时进入;engine 仅从
|
||||
四个辅助 agent 调用点传入,operator/planner/FA/冷启动永不接(冷启动策略
|
||||
`chunk_view=None` 显式声明)。**行为增益(实施标注)**:diagnoser/
|
||||
committee 的 strict/whitelist 悬崖在长任务中首次由段外 digest 块补偿——
|
||||
chunk 块总是全量渲染,不受步级可见性策略过滤。差异:digest 从持久化
|
||||
rendered_text 截断而非从 band1/band2 重渲染(零重复渲染逻辑;pending 态
|
||||
chunk 自然退化为 pending 说明 + 标记行)。
|
||||
6. **配置**:`agent.memory.recall`{enabled=true, max_results=5,
|
||||
max_text_tokens=2000, max_image_steps=1}、transcript 增
|
||||
`similarity_hint=true` / `similarity_max_distance=8`、`memory.policies={}`。
|
||||
真机基线报告(history-baseline-2026-09-01.md)截至实施时仍不存在,相似度
|
||||
距离阈值与 recall 预算默认值**维持临时、待定标**。旧键兼容别名本阶段无
|
||||
新增需求(recall/similarity 均为全新键)。
|
||||
7. **测试**(只增不减):+45 净增——`tests/unit/tools/test_history_recall.py`
|
||||
13(关键词命中与步号不变量、step_range 过滤、范围回捞全宽行(era 入口)、
|
||||
结果数钳制、token 截断、notes 检索与步锚定、chunk 行检索、图仅单步仅真实
|
||||
文件、配置封零、缺图降级、空参拒绝、无引擎降级、可用性三态);
|
||||
`tests/unit/utils/test_image_hash.py` 5(稳定性/JPEG 重编码小距离/异屏大
|
||||
距离/垃圾输入 best-effort/距离边界);
|
||||
`tests/unit/agents/test_historical_state_hint.py` 8(命中注入、近 3 步
|
||||
静默、无命中/关开关/步数不足静默、平距取最近、无哈希跳过、record_step
|
||||
盖章含 post==pre 镜像);`tests/unit/memory/test_context_policy.py` 16
|
||||
(8 点 golden 参数化、全表覆盖、配置覆盖与未知 agent、flag 关字节不变、
|
||||
full/digest 渲染、chunk_view=None 不接、最新步不被吞);
|
||||
test_memory_config.py +2(recall/similarity/policies 默认与覆盖)。存量
|
||||
断言同步 4 处:test_prompt_assembly 全集枚举字面与 removed-tool 参数化加
|
||||
recall_history、test_operator_prompts 记忆工具枚举字面、
|
||||
test_history_analyzer patch 目标改 build_history_for、action_manifest 分类
|
||||
集。验证:tests/unit 全量 **1210 通过 3 跳过 0 失败**(M3 基线 1165+3,
|
||||
只增不减);tests/tools 子集(operator/summarizer/validator/diagnoser)
|
||||
7 通过。
|
||||
8. **未完成项(待真机/后续)**:相似度距离阈值与 recall 预算定标依赖真机
|
||||
基线报告;include_images 的图片块(ToolMessage 多模态内容)在 Gemini 工具
|
||||
响应管线的端到端表现待真机验证;Flash 注册 recall 留待其 ledger 接 chunk
|
||||
之后(方案既定);FTS5 索引留待检索量级需要时引入。
|
||||
|
||||
**硬化轮 — A/B 实测问题收口** ✅ 已实施 2026-09-01(worktree `zealous-gates-4626b1` 未提交,叠加在 M0-M4 之上;任务来源 = `history-baseline-2026-09-01.md` §5/§6 的移交项)
|
||||
|
||||
1. **M2 冷启动 bug 认领合入 + 回归测试**:A/B 会话的最小修复(`_build_prompt_transcript`
|
||||
冷启动条件加 `not ledger.has_staged_turn`,operator.py,含注释)经核实形态正确
|
||||
完整,本轮正式认领。`test_operator_transcript.py` 的两回合用例数据构造改为
|
||||
真实形态——第 1 回合无步记录、第 2 回合 steps 非空且上一回合 staged 未
|
||||
commit(原用例 `data_engine=None` 使 steps 恒空,正因此没抓到);负向验证
|
||||
已做:临时移除修复条件该用例即失败。
|
||||
2. **chunk ①② 段头全 pending 根因(已定论)+ 修复**:worktree
|
||||
traces/data_engine.db 实证——dispatch 一环正常(21 个 chunk 全部 submit),
|
||||
断环在 LLM:**capsule 尝试 41 次全部 "503 Service Unavailable"**,5 个 job
|
||||
走到有界重试耗尽进 failed,其余 16 个卡在 `max_concurrency=1` 串行队列没轮
|
||||
到尝试就随会话结束;"下一次压缩事件收割 + flush 兜底收割落库"链路本身无缺
|
||||
陷,只是全程无就绪结果可收(21 行 DB 记录恒 version=1 pending 与此吻合)。
|
||||
根因 = `chunking.model` 默认 gemini-3.7-flash 当日全天 503,而 capsule lens
|
||||
走 `get_google_llm` 原始旁路,没有网关的分类重试/降级模型(同日 A/B 主模型
|
||||
手工换 3.6、step lens 3.5-flash-lite 均健康,唯 capsule 绑死单一端点)。
|
||||
修复:`StepCapsuleLens` 增降级模型链——`HistoryChunkManager` 从 llm 配置
|
||||
summarizer 角色的 fallback(继承全局 default.fallback,现值
|
||||
gemini-3.6-flash)解析同 provider(google)且异于主模型的降级模型,render
|
||||
经 `services.llm.with_fallback` 分类切换(503/超时类才降级,bad request 不
|
||||
掩盖进弱模型)。"短会话等不到下一次压缩事件"的假设不成立——flush 收割落库
|
||||
M3 已实现,本轮补单测锁定(迟到就绪 → flush 收割 → DB ready v2 落库、冻结
|
||||
文本不重渲染)。单测另覆盖:降级成功(主 503 → fallback 出 capsule)、无降
|
||||
级时 503 耗尽 failed 且 chunk 维持 pending、降级模型解析三态(google-only、
|
||||
不等于主模型)。
|
||||
3. **视觉 lens 单图自适应(§5 修订版落地)**:新增
|
||||
`flash_summarizer_single.md` 单图变体提示词——只描述决策帧内容与动作落点
|
||||
(红标),**无 AFTER ACTION 段**,明示"缺后图仅表示无独立后验证据,严禁描
|
||||
述/推测/合成动作后状态(转换叙事由相邻步摘要链接)",禁判定词表原样沿用;
|
||||
`_attempt` 按"前后双图 → 原转换模板、其余(Pro 恒缺后图、任一图缺失、纯文
|
||||
本)→ 单图变体"选择,单图块标签改为 DECISION FRAME(仅后图存在时亦有中性
|
||||
标签)。Pro 无动作后抓屏的刻意安全网未动(不补抓、不用步 N+1 前图拼合)。
|
||||
**回显校验兜底**:`degenerate_summary_reason`——输出含 "--- [" 段标记或以
|
||||
"---" 开头、<15 字符、>1500 字符即判该次尝试失败,走既有有界重试,耗尽进
|
||||
failed;双图模板补一句"输出永不含 --- 分隔符"降低无谓重试。单测覆盖单图/
|
||||
双图模板选择与块标签、回显判负后重试恢复、echo/过短/过长三形态耗尽 failed。
|
||||
4. **视觉 lens 计量接入(M0 遗留收口)**:`record_llm_usage`/
|
||||
`SessionTokenMeter.record` 增 `update_last_prompt` 开关;视觉 lens 与
|
||||
capsule lens 的原始旁路调用成功后各记一条 `llm_usage` trace(source
|
||||
`lens:visual_transition:<model>` / `lens:step_capsule:<model>`),一律
|
||||
`update_last_prompt=False`——lens 小 prompt 不得覆写 chunker 软/硬阈值消费
|
||||
的 `last_prompt_tokens` 上下文基数;网关包装模型(RobustChatModelWrapper)
|
||||
已在出口自计量,旁路计量以 isinstance 判别避免重复。M0 实施要点第 3 条
|
||||
"raw 旁路仅测试使用"的表述至此与现状一致(旁路仍在,但已计量)。
|
||||
5. **定标落地**:`memory.runtime.max_concurrency` 默认 1→2(依据基线报告
|
||||
§6:串行队列使摘要就绪 P90 长尾超擦洗窗口 K=3+宽限;lens 是独立轻模型调
|
||||
用,并发 2 风险低),Field description 已标注依据与出处;
|
||||
`similarity_max_distance=8` 维持待定标(基线未产生相似屏配对数据)。
|
||||
|
||||
6. **就绪门控交换(§3.3 修订落地;任务中途经同伴会话转达用户拍板补入本轮)**:
|
||||
HistoryChunkManager 事件流重构为"关段 ≠ 交换"——触发器(里程碑/尺寸/软阈
|
||||
值)只**关段**:建 chunk 镜像、落 v1 pending DB 行(recall/辅助视图可见)、
|
||||
dispatch 段头生成,原始回合原样留在转写 A 区(`_awaiting` 队列,lossless
|
||||
pending 从图片层贯彻到段层);**交换(freeze_turns + 渲染 chunk 块)发生在
|
||||
段头就绪后的首次渲染**,且只交换就绪的连续前缀(交换消耗最旧未压回合,老
|
||||
段 pending 时后继段一并保留原文)。降级梯照 §3.3:生成失败(有界重试耗尽)
|
||||
→ 原文保留 + 重投(lens 每次尝试自带 fallback 模型,见第 2 项;重投节奏限
|
||||
触发/软硬压力渲染,不逐渲染,避免重引入无界 LLM 重试);软阈值压力下仍未
|
||||
就绪 → 依旧保留原文(正确性优先于 token);**仅硬阈值**强制交换全部已关段
|
||||
(pending 一并入 L3 快照,③ 最小索引存活)。checker post-hoc 标注独立于段
|
||||
头(awaiting 态即刻 DB version bump),冻结文本仍只在交换事件重渲染(事件
|
||||
间字节稳定)。无步记录的关段(chunk 缺失)永不常规交换,仅硬阈值随队清
|
||||
出。CHUNK_PENDING_NOTE 增 recall_history 指引。A/B 的"21 chunk 段头全
|
||||
pending"在门控下退化为"尚未压缩"的安全形态。单测:里程碑用例改为"关段不
|
||||
交换 → 就绪后下次渲染交换"全流程、冻结区事件间字节稳定重写为门控形态、
|
||||
软压力不交换、失败重投且原文保留、硬阈值强制入 L3 等新用例;触发器/era/
|
||||
L3/寻址性用例改用 auto-resolve capsule 桩保持原有触发语义断言。
|
||||
7. **段头笔记关联升级为机械校验(同伴会话转达用户拍板补入)**:
|
||||
`parse_capsule` 在 ② 区间覆盖校验之后增加笔记覆盖校验——payload 中段内
|
||||
note_writes 的去重 key 必须以子串出现在 band① 任一文本字段(doing/did/
|
||||
effect/entry/exit/四个结构化列表),缺任何一个该次尝试判负返回 None 走既
|
||||
有有界重试,耗尽维持 pending(配合门控:不交换、原文保留);
|
||||
step_capsule.md 明示该硬约束(machine-checked)。单测:双笔记段漏一 key
|
||||
判负、补全通过、无笔记段不受影响。
|
||||
|
||||
验证:tests/unit 全量 **1224 通过 3 跳过 0 失败**(M4 基线 1210+3,只增不
|
||||
减,净增 14);tests/tools 子集(operator/summarizer/validator/diagnoser)
|
||||
7 通过。灰度前置 ①(§10 决策 3 / 基线报告 §7-3)至此满足;前置 ②(修复后
|
||||
复测一轮 A/B)仍待真机,且复测轮应同时验收门控形态(段头就绪率、交换延
|
||||
迟、A 区稳态尺寸)。
|
||||
|
||||
**M5 — 缓存深化(可选)** ✅ 终轮已实施 2026-09-01(worktree `zealous-gates-4626b1` 未提交,叠加在 M0-M4+硬化轮之上)
|
||||
- S 区显式 CachedContent(explorer 先例外推);`short_term_memory` 移除(姊妹文档 §3.3,转写落地后其职责已被真多轮取代);供应商 stateful API 评估。
|
||||
|
||||
M5 实施要点(2026-09-01;与计划的差异逐条标注):
|
||||
|
||||
1. **默认启用(用户拍板)**:`agent.memory.transcript.enabled` 默认 false→true
|
||||
(`config/agent.py` 默认值 + `config/artemis.jsonc` 新增完整 `agent.memory`
|
||||
块同步——jsonc 此前根本没有 memory 块;L2/L3 chunking 挂同一 flag 随同默认
|
||||
开)。**旧路径与转写构建异常自动回退逻辑全部保留不删——`enabled: false`
|
||||
即回滚开关**,置 false 逐字节回旧 2-message 行为(Field description 与
|
||||
jsonc 注释均已标注,含 A/B 依据摘要)。测试纪律:凡"flag 关"语义的既有
|
||||
测试改为显式传 false(`test_operator.py` 全部 21 处构造加
|
||||
`transcript_config=LEGACY_TRANSCRIPT`;`test_memory_config.py` 默认断言改
|
||||
true、显式 false 改为"回滚开关"用例),"flag 开"路径成为默认被测面。涟漪
|
||||
仅一处:`test_outputter` 的 Mock engine 需补 `get_history_chunks=[]`(辅助
|
||||
视图默认开始咨询 chunk 存储,空 chunk 时输出逐字节不变的 M4 语义未变)。
|
||||
2. **short_term_memory 全链移除(姊妹文档 §3.3 落地)**:按 visibility 附录 B
|
||||
台账纪律先核销全部消费者再删——State 字段(state.py)、operator 提取/写块
|
||||
(operator.py 返回 dict 与 `<short_term_memory>` 正则提取)、
|
||||
`ShortTermMemoryPromptComponent`(prompts.py 类 + operator.py 两条组件清单
|
||||
引用)、visibility manifest operator 读/写集、operator.json 的
|
||||
Short-Term Memory 指令段(含示例块,模板不再教模型产出该标签)。
|
||||
sdk `_get_graph_state` 经核实已是 `State.initial()` 单源(Phase 0/1 收拢),
|
||||
字段删除即覆盖。**两处防御性清洗有意保留**:`task_tree.py` 渲染侧的
|
||||
`<short_term_memory>` 标签剥除与 showcase_ui markdown-parser 的标签过滤——
|
||||
历史 DB 中旧会话的思考原文仍含该标签,姊妹文档 §3.3 原文要求
|
||||
"safely ignoring it in older responses"。
|
||||
3. **C 清尾(M0-M4 标注遗留小项逐项处置)**:
|
||||
|
||||
| # | 遗留项 | 处置 |
|
||||
|---|---|---|
|
||||
| C1 | 旧 `compress_flash_messages`(M1 标注"M2 落地后再删") | **已删**。语义对拍测试改造:`test_flash_scrub_edge.py` 两处 legacy 参照改为对 ScrubEdge 产物的直接字面断言;`test_flash_step_summarizer.py` 两例转为 ScrubEdge(`image_scrub_depth=1` 复现 legacy 时点,ordinal 回退键控保留覆盖),三例存档删除(pending 回退/迟到回填为 ScrubEdge 明令禁止的旧语义,前缀保留已由 scrub_edge 用例覆盖) |
|
||||
| C2 | `record_step` 的 `foreground_app` 形参从未落库(M4 发现) | **已做**。显式形参优先落 `extra_metadata["foreground_app"]`;形参缺席时从 ui_tree 的 package 属性纯字符串推导(最频繁非 systemui 包,`_derive_foreground_app`,零设备调用零同步开销);recall `_step_haystack` 检索面接入——包名查询不再仅依赖封顶 150 步的 XML blob 扫描。测试 +4 |
|
||||
| C3 | click_sequence/swipe 多点动作语义充实(M0 差异) | **已做**。`_target_semantics` 扩展:click_sequence 逐点命中测试(`points_semantics` 列表)、swipe 打 start/end 两点(`start/end_semantics`),**首点语义提升为主 `target_*` 标签**;全程 best-effort 永不抛。测试 +2(原"swipe 跳过充实"断言按新行为改写) |
|
||||
| C4 | era 上限复用 max_chunks 同值(M3 差异) | **已做**。新键 `chunking.max_eras`(默认 None=随 max_chunks,保 M3 等值行为);chunker `_fold_eras` 的 era 溢出改用独立上限。测试 +2(宽松 era 上限不溢出/紧上限提前溢出、config 默认与覆盖) |
|
||||
| C5 | `similarity_max_distance=8` 待定标(M4) | **已定标 8→5**。worktree traces/data_engine.db 有 460 步 dHash 盖章(16 会话):同屏重拍(同 SHA 相邻/wait 步/同 SHA 远距重访)距离集中 ≤4;相邻步分布双峰、谷在 5-6;跨应用会话对(T1 Settings × T2 时钟,保证异屏,n=11,770)≤5 仅 0.14%(阈 8 时 0.24%,假阳翻倍)。取谷值 5:同屏召回不损、异屏假阳减半。定标证据写入 Field description |
|
||||
| C6 | M2 golden(模板拆分前 SHA-256) | **已按新现状重录一次**:operator.json 移除 Short-Term Memory 指令段(本轮 B 项唯一有意模板变更)使旧哈希失效;重录 EMPTY/SENTINEL 双哈希并在测试注释注明重录原因与时间。golden 的"任何其他漂移即 flag-off 路径不再逐字节"约束继续有效 |
|
||||
|
||||
4. **D1 — S 区显式 CachedContent:评估后决定不实现**。依据:A/B 实测隐式缓存
|
||||
已达 88% 调用命中/49.6% token 命中(基线 43%/29.8%),S 区字节稳定 + 转写
|
||||
append-only 正是隐式缓存的理想形态,边际收益只剩"稳态间隔太长导致隐式条目
|
||||
过期"的场景;而显式 CachedContent 要求最低 4,096 token 起步、按 token-hour
|
||||
计存储费、需要显式生命周期管理(创建/续期/删除),并把 S 区绑定到单一模型
|
||||
版本(fallback 切模型即缓存失效)。当前 Operator 步间隔 P50 8.8s 远短于隐式
|
||||
条目寿命,付费显式缓存买不到增量命中。**重启条件**(写给未来):若出现
|
||||
①步间隔常态 >5min 的长轮询任务(`[Loop:continuous]` 大 Interval)实测隐式
|
||||
命中率显著回落,或 ②S 区膨胀(如工具 schema 大幅增长)使单次 re-prefill
|
||||
成本可观,则按 explorer.py:1709 先例实现,config 开关默认关。
|
||||
5. **D2 — Gemini stateful/Interactions API 评估备忘**:见新增 §11(只评估不
|
||||
实现)。
|
||||
6. **归档**:状态行更新为已实施;§10 决策逐条标注(决策 5 维持"已按建议实施、
|
||||
待用户终审");两份姊妹文档头部注记补最终状态指针。
|
||||
|
||||
验证:tests/unit 全量 **1229 通过 3 跳过 0 失败**(硬化轮基线 1224+3,只增
|
||||
不减,净增 5:+8 新增 −3 存档删除);tests/tools 子集(operator/summarizer/
|
||||
validator/diagnoser)7 通过。
|
||||
|
||||
**最终残留(2026-09-01 终轮验收后更新)**:~~recall include_images 真机端到
|
||||
端;最终验收轮真机三件套(>30 步 token 稳态、`[Loop:continuous]` 100+ 步、
|
||||
capsule 盲评——此轮同时验收就绪门控形态与默认开后的复测 A/B)~~ **已全部
|
||||
完成并通过**(2026-09-01 最终真机验收轮,五项全过:4 对同模型 A/B 回退 0/
|
||||
就绪率 60/60/延迟−60%/缓存 40%→86%;112 步 Loop 无堆叠、token 有界 33% 预
|
||||
算、注入逐字保全;48 chunk 盲评禁词违规 1 例且语义无害;recall 图片块 3 次
|
||||
Gemini 调用全 success;相似度提示正例+静默负例均验证——详见
|
||||
`docs/plans/history-acceptance-2026-09-01.md`)。仍留:git 提交/PR(用户决
|
||||
定,全部改动仍在 worktree `zealous-gates-4626b1` 未提交);Flash 接
|
||||
TranscriptLedger/chunk(文档既定后续项,Flash 侧 transcript.enabled 无效果、
|
||||
零行为变化);两项不阻塞优化候选([Loop] 里程碑内相似度提示静默、
|
||||
gemini-3.7-flash 供应商稳定性观察,验收报告 §6-a/e)。
|
||||
|
||||
**决策 5 终审收尾** ✅ 已实施 2026-09-01(用户终审 2026-09-01 定案,worktree
|
||||
`zealous-gates-4626b1` 未提交;叠加在 M0–M5 之上):
|
||||
|
||||
1. **形态变化**:era 溢出的极限层从 M3 的"纯标记行"
|
||||
`[Era N | Steps a–b: ledger via recall_history]` 升级为 §3.3 终审定案的
|
||||
**时段段落**——
|
||||
`[Era N | Steps a–b | T+hh:mm → T+hh:mm] <一段话概括>` + 缩进 recall 指引行
|
||||
`(Step-level ledger via recall_history for steps a–b)` + user 注入行逐字保留
|
||||
(never-evict 不随丢步豁免)。起止时间取该 era 首 chunk `start_offset` 与末
|
||||
chunk `end_offset`(既有 T+ 会话偏移格式),是视频对齐的粗粒度外键。
|
||||
2. **段落机械拼装,零新增 LLM 调用**(`chunking.render_era_period_paragraph`):
|
||||
doing 取各 chunk ① 首个非空;did 逐 chunk 取首句串联(band1 缺失/pending 的
|
||||
chunk 回退用 milestone_label,无标签再退 `steps a–b`);effect 中笔记引用
|
||||
(`notes/<key>` 路径与 `*.md` 文件名正则)合并去重;verified_facts 集合并后
|
||||
取前 4 条。全部 chunk 无 band1 时段落退化为"里程碑标签列表 + 步范围"——
|
||||
**永不为空**(新增单测覆盖两级回退)。
|
||||
3. **代码位置**:`artemis/memory/chunking.py`(`RECALL_MARKER_TEMPLATE` 更名
|
||||
`RECALL_GUIDANCE_TEMPLATE` 并改为缩进指引行;`render_era_block` recall_only
|
||||
分支重写;新增 `render_era_period_paragraph`/`_first_sentence`);recall 端
|
||||
无解析耦合(`recall_history` 的 step_range 入口本就按步号范围回捞,与标记
|
||||
行字面无关),仅同步了 `artemis/tools/history_recall.py` 文档字符串与
|
||||
`artemis/agents/operator/operator.json` 提示词中对入口行形态的描述。
|
||||
4. **不变量修订落地**(§8 同步):"全层级步可寻址"单测改为 L2 chunk/era 合并/
|
||||
L3 快照层逐步可寻址 + **极限段落层时段可寻址**(正则断言 `Steps a–b` 与
|
||||
`T+..→T+..` 在场)。era 溢出用例改断言新形态(时间范围在场、段落非空、
|
||||
doing/笔记引用/verified 进段落、指引行与 user 行仍在)。
|
||||
5. **验证**:tests/unit 全量 **1231 通过 3 跳过 0 失败**(M5 终轮基线 1229+3,
|
||||
只增不减,净增 2:时段段落逐 chunk 回退 + 全缺失退化两用例)。
|
||||
|
||||
每阶段独立可回滚;M2 起所有新行为在 flag 后面,关 flag 即回现状(M5 后 flag 出厂开,关 flag 语义不变)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收红线(继承并加严)
|
||||
|
||||
> **终轮判定(2026-09-01):六条红线全部 PASS**("速度/质量"两条因样本量为方向性判定,"对拍"引用 M5 单测 golden)——逐条依据见 `docs/plans/history-acceptance-2026-09-01.md` §7。
|
||||
|
||||
- **不损伤**:压缩产物一律可经 step 引用回捞原文;chunk/快照生成失败时源摘要与 detailed 渲染回退可用(现 `task_tree.py:920` 语义保留为最后防线)。
|
||||
- **步骨架不变量**:L2 chunk、era 合并、L3 快照层级后,转写中步 1:1 可寻址——每步的步号与相对时间可直接读出,无合并、无丢步、无重排;视频分析模块按步号/时间戳的对齐在压缩前后逐步一致(新增回归测试)。**唯一例外:极限时段段落层**(决策 5 终审)——允许丢步,但段落必须带步号范围与起止时间(时段可寻址),用户注入行仍逐字保留,步级细节 recall 可回捞。
|
||||
- **速度**:关键路径零摘要等待、零新增同步调用;延迟回归 ≤5%(30 对任务、置信区间)。
|
||||
- **缓存**:M1/M2 各自报告命中率提升(这是本方案区别于现状的核心收益,必须量化)。
|
||||
- **质量**:重复动作率、历史致回滚率不升;跨计划改写的关键事实保留率上升;摘要/Validator 冲突趋零。
|
||||
- **对拍**:辅助 agent 编译视图收拢前后逐字节一致;Flash M1 语义等价对拍。
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与回退
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| 转写路径引入回归(最大风险面) | 双路径并存 + flag;A/B 对拍先行;State/图结构不动 |
|
||||
| Gemini 隐式缓存命中不及预期(条目短寿命、前缀下限) | M0 先测真实命中率;不及预期则 M5 显式缓存提前;方案收益不全押缓存(真多轮/签名连续性独立成立) |
|
||||
| chunk 质量差导致远期决策劣化 | 结构化字段合并而非散文再摘要;recall 兜底;盲评(姊妹文档 §17 三方对比法沿用) |
|
||||
| flash-lite 摘要质量上限 | lens 模型可配;闲时用 3.7 重摘(姊妹文档 §8.3 隔离模式保留) |
|
||||
| 无界任务 chunk 无限增长 | era 二级合并 + 预算封顶 + L3 快照 |
|
||||
| 转写崩溃丢失 | DataEngine 为事实源,冷启动构造器重建 F 区(A 区丢失退化为冷启动,可接受) |
|
||||
| 裸坐标动作账本无语义("click 82,32 看不出在点什么") | M0 记录时命中测试充实 `target_text`(§6.1);`target_label_source` 区分指名与推断 |
|
||||
| 异步检查点结果在段 chunk 化之后才回果,① 的 verified/unresolved 过时 | chunk 版本化 + checkpoint harvest 对命中步范围的 chunk 追加 post-hoc 标注并 bump version;F 区文本在下一次压缩事件时才重渲染(不做即时深突变) |
|
||||
| 一步内 FA 恢复动作视频轴有帧、账本无行 | ③ 缩进子行机械渲染 `_exec_*`(数据已在 interleaved_events) |
|
||||
| 段内用户注入指令被压缩叙事吞掉 | ③ 逐字保全行 + never-evict(§3.3 账本细则) |
|
||||
| 回合内工具循环(≤20 次迭代)在两次计量之间撑爆上下文 | 16K 单工具信封 + 回合内累计估算越硬阈值时强制决策(姊妹文档 §13 语义接入 L3 触发检查) |
|
||||
| 计划写入被否决回滚造成伪里程碑切换 | 触发事实源锁定为已执行步骤盖章变化;计划事件只入队待确认(§3.3) |
|
||||
| chunk 化删除携带思考签名的旧 AIMessage 影响后续轮 | 压缩点永远在完整回合边界(不劈 tool-call/response 对);M2 真机验证项 |
|
||||
| **今日 Pro 的 SystemMessage 本身含易变历史**——模板在 `# CURRENT OBSERVATION` 处切分,part 0(含 `## Current Plan & Execution History`)进系统消息(prompts.py:218-222);不拆模板则 S 区无从稳定,缓存收益归零 | M2 前置子任务:operator.json 模板拆分——静态规则/计划语法留 S 区,plan+history 移出系统消息(姊妹文档 §12 同一要求,在此升为 M2 硬依赖) |
|
||||
| 转写内时间戳字节不稳定:现 `get_relative_time` 是 "x min ago" 措辞,冻结后即变陈旧谎言,重算则破字节稳定 | ③ 账本一律用会话起点偏移 `T+mm:ss`(冻结后永真且字节稳定);"ago" 措辞仅限辅助 agent 的每次重编译视图 |
|
||||
| 极端规模下 ③ 与有限预算的数学冲突:`[Loop:continuous]` 跑到数千步时,即便最小行宽的逐步账本也会耗尽预算 | 开放决策 §10:最老 era 的 ③ 溢出到 recall-only(转写留 `Steps 1–500: ledger via recall_history` 标记行),或预算随任务放宽——需用户定溢出规则 |
|
||||
| 模型路由 fallback 切换后 prompt_tokens 分词口径不一致 | 计量当近似值用,软/硬阈值留 10%+ 余量;切换事件记入计量日志 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 开放决策(待用户拍板)
|
||||
|
||||
1. ~~**步级单 lens 修订**(§5)~~ **已拍板(2026-08-31):采纳**。步级仅保留 `VisualTransitionLens`(双 profile 共用),Pro 步级胶囊上移为 chunk 级 `StepCapsuleLens`。原否决备选(双摘要并存、`update_step_summary` 加 lens 维度、成本 ×2)作废。
|
||||
2. ~~**默认值定标**~~ **已决(2026-09-01)**:基线报告 §6 定标——`context_budget_tokens=80K`/软硬阈值/K=3/min_active=5/chunk≤12 全部维持(实测远未触及,触发行为验收留 `[Loop:continuous]` 真机轮);`max_concurrency` 1→2(硬化轮);`similarity_max_distance` 8→5(M5 依 460 步 dHash 分布定标,见 §7 M5 要点 3-C5)。
|
||||
3. ~~**M2 灰度顺序**~~ **已决(2026-09-01)**:A/B 红线全过且大幅改善后,用户拍板 M5 将 `transcript.enabled` 提为默认开;`enabled:false` 保留为逐字节回滚开关。基线报告 §7-3 的两个前置中 ①(冷启动修复认领+回归测试)已在硬化轮满足,②(修复后复测轮)并入最终验收真机轮。
|
||||
4. ~~**辅助 agent 视图升级时点**~~ **已决**:M4 收口(编译视图接 chunk digest,full/digest 双渲染,见 §7 M4 要点 5)。
|
||||
5. **极端规模的 ③ 溢出规则**:~~最老 era 的 ③ 溢出到 recall-only 标记行~~ **已终审定案(用户 2026-09-01,修订原实施)**:极限层允许丢步——最老 era 压缩为"时段段落"(一段话概括 + **步号范围与起止时间必须在场** + recall 指引 + user 注入行逐字保留),段落机械拼装自 era 各 chunk ① 字段。见 §3.3 极限层小节;M3 原"纯标记行"实施按此升级(终审收尾轮落地)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 供应商 stateful API 评估备忘(M5,只评估不实现)
|
||||
|
||||
对象:Gemini **Interactions API**(2026 年起 Google 推荐的新接口面,与本仓现用的
|
||||
`generateContent` 并行提供)。其与本架构直接相关的三个特性(官方文档核实于
|
||||
2026-09-01):
|
||||
|
||||
- **服务端会话状态**:`store: true`(默认开)+ 后续请求带
|
||||
`previous_interaction_id` 即可让服务端续接完整对话历史,客户端无需重发消息;
|
||||
付费层交互对象保留 55 天(免费层 1 天),可配置 7/14/28/55 天窗口,可显式删除。
|
||||
- **思考签名服务端化**:stateful 模式下 thought signature(Gemini 3 起的加密思考
|
||||
连续性凭据)由服务端随会话状态自动管理,客户端完全无需回传/维护——本方案
|
||||
§3.2 的"思维签名政策"(回合内强制、A 区顺带、压缩即弃)在该模式下整体消失为
|
||||
非问题。
|
||||
- **显式缓存不支持**:Interactions API 只有隐式缓存;显式 `CachedContent` 仅
|
||||
`generateContent` 提供。若未来重启 D1(显式缓存),则与迁移 Interactions API
|
||||
互斥,需二选一。
|
||||
|
||||
### 11.1 与本架构的相容性评估
|
||||
|
||||
| 维度 | 现架构(M0-M5 转写) | Interactions API stateful | 评估 |
|
||||
|---|---|---|---|
|
||||
| 历史表示 | 客户端 TranscriptLedger 四区,字节级自主控制 | 服务端黑盒累积,客户端只发新尾部 | **根本冲突**:擦洗沿(深度 K 图→摘要替换)、L2 chunk 化、L3 快照全部依赖对历史消息的**受控深突变**——服务端状态是 append-only 黑盒,无法就地替换第 i 条消息的图片块,也无法把 12 步折叠成一个 chunk 块。要保留分层压缩就必须在压缩事件时**放弃续接、整段重建新会话**(丢 `previous_interaction_id` 换新 id),届时"免重发历史"的收益只在压缩事件之间存在 |
|
||||
| 缓存 | 隐式缓存实测 88% 调用命中(A/B) | 隐式缓存由服务端在续接时自动优化 | 收益近似打平:本架构的 S 区稳定 + append-only 已把隐式缓存吃到接近上限,服务端续接省的是**上行带宽与序列化**,不是缓存未命中 |
|
||||
| 思考签名 | A 区字节不动即免费有效,压缩即弃(§3.2) | 服务端全托管 | Interactions 略优,但现政策实测零签名错误(A/B 红线),痛点不存在 |
|
||||
| 可逆性/审计 | DataEngine 全量在案,recall 可回捞 | 服务端 55 天留存,之后自动删除 | 现架构必须保留 DataEngine 事实源不动摇;服务端状态只能作缓存层,不可作事实源 |
|
||||
| 生态 | LangChain `langchain-google-genai`(generateContent 系)+ 本仓网关(分类重试/fallback 模型链) | 需直连新 API 面;fallback 切模型时服务端会话是否可跨模型续接未验证 | 迁移面 = LLM 网关全重写 + fallback 语义重新设计,改动半径大 |
|
||||
| 隐私/合规 | 请求即用即弃(模型侧) | 默认 `store: true`,设备操作史(截图、输入文本)在 Google 侧留存最长 55 天 | 移动 UI 自动化的截图流含敏感界面,默认服务端留存需要显式合规评估;`store: false` 则失去 stateful 收益,自相矛盾 |
|
||||
|
||||
### 11.2 结论
|
||||
|
||||
**不迁移,不部分采用(2026-09-01)。** 三条硬理由:① 分层压缩(本方案的核心
|
||||
价值:token 稳态有界 + 步骨架不变量)与服务端黑盒状态根本不相容,保它就得在每
|
||||
次压缩事件重建会话,收益归零;② 现架构在缓存命中与签名连续性上的实测表现已接
|
||||
近 Interactions 能给的上限(88% 命中、零签名错误),痛点侧无增量;③ 截图流默
|
||||
认服务端留存 55 天是移动自动化场景新增的合规面。
|
||||
|
||||
**重估触发条件**(写给未来):① Google 宣布 `generateContent` 弃用时间表(届时
|
||||
迁移变为强制,方案是"压缩事件即会话重建"的混合形态,本备忘 §11.1 第一行即设计
|
||||
起点);② Interactions API 补齐显式缓存与服务端历史编辑(可控修剪/替换)能力;
|
||||
③ 出现"无压缩短会话"型新 profile(如 <15 步的 Flash 任务),可单独试点
|
||||
stateful 模式免去消息重建。
|
||||
Reference in new issue
Block a user