aps-agent/poc/pi-fallback/P1-SMOKE.md

137 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# P1-SMOKE — P1 只读兜底 · 真实端到端冒烟全记录(Agent-G)
> 冒烟员:Agent-G · 日期:2026-09-02 · 协调板:`GOAL-P1.md`
> 定位:P1 此前全部测试均为 fake runner 注入;本次是**第一次全真实链路**(真实 server / 真实对话入口 / 真实 Pi headless / 真实 Kimi K2.6)。
> 硬约束遵守:未 git commit/push;未修改任何产品代码文件;Python 一律 `.venv\Scripts\python.exe`;
> server 单命令内启动→验证→杀树;全程临时 APS_HOME 隔离,`server/data/` 零污染(git status 与 mtime 双重核实);
> LLM key 只进请求头/子进程 env,未打印未落盘。
## 0. TL;DR
**严格判定:原样产品代码下真实链路 NO-GO**——冒烟抓到一个 fake runner 永远摸不到的产品 bug:
`fallback_lane.py` 守卫模板注释行有一对**未转义的 `{ }`**,`write_guard_extension` 的 `str.format` 必抛
`KeyError: ' block'`,真实 runner 路径 100% 失败(被正确归并为 harness_error 显式失败,用户侧回退话术正常)。
**修复预判:一行修复后链路全通。** 诊断轮(server 进程内做内存级单行修正,产品文件零改动)同一口令全绿:
真实 Pi 经新链路读真实快照、产出真实分析回到对话,run 目录/凭证/审计链全部齐全,34.9s 完成。
另发现一个环境级坑:本机 PATH 里 `node` 优先解析为宿主注入的 `node.CMD` 垫片,真实 runner 起不来;
用既有配置项 `APS_FALLBACK_NODE` 指向真实 `node.exe` 即可绕过(建议产品侧优先解析 .exe 或文档明示)。
## 1. 冒烟设置
| 项 | 值 |
|---|---|
| 启动方式 | `.venv\Scripts\python.exe -m uvicorn server.main:app --host 127.0.0.1 --port <port>`(诊断轮经 `shim_server.py`,见 §4) |
| 隔离 | `APS_HOME/APS_WORLD_PATH/APS_DB_PATH/APS_FEATURES_PATH` 全部指向 `poc/pi-fallback/smoke/site{,2}/` |
| 开关 | 临时 features.json = `{"version":1,"features":{"fallback":true}}`(APS_FEATURES_PATH 指向) |
| 认证 | 开发态桌面模式:`x-aps-client: desktop` + `APS_LICENSE_PROVIDER=mock` + 授权码 `APS-DAY-DEMO`(`/api/auth/license/activate` → `aps_desktop_session` cookie)。Web/JMS 认证需要外部 JMS,不可用 |
| 模型 | `APS_FALLBACK_MODEL=aps-fallback/kimi-k2.6`(P0 已知 .env 的 kimi-k2-0711-preview 该 key 404;GET /models 实测清单 = kimi-k2.7-code-highspeed / kimi-k3 / kimi-k2.7-code / kimi-k2.6) |
| 口令 | 「琢磨个冷门的角度:把每张待交付订单的交货日期换算成一周里的第几天,交货压力集中在一周的开头还是末尾?再数数物料种类,凭数据给个备料松紧的判断,别改任何东西,只给结论。」——逐条避让 intent.py 全部快路正则(无 分析+数据 / 看看+订单 / 交期\|看板 / 排产祈使 / 星期几 等),只读分析类、零写意图 |
| 脚本 | `poc/pi-fallback/smoke/smoke_e2e.py`(基线轮)、`smoke_e2e_diag.py` + `shim_server.py`(诊断轮) |
**意图落点实测**(两轮一致,SSE intent 事件):`assistant.reply / confidence 0.9 / source LLM`——
主线模型 kimi-k2-0711-preview 404 → 二级分类按设计降级为 assistant.reply,随后 workflow.py:2875 分支进 `propose_reply`。
## 2. 七条判据逐条结论
| # | 判据 | 结论 | 证据 |
|---|------|------|------|
| 1 | server 启动健康 | **PASS** | `/api/health` 200,`interfaceVersion="1.0"`,storage.writable=true,home 指向临时 APS_HOME(两轮一致,见 results.json/results2.json health 段) |
| 2 | /api/features 开关语义 | **PASS** | `fallback.enabled=true`、`defaultOff=["fallback"]`、`source=file`(临时配置生效;默认关标注存在) |
| 3 | 未覆盖口令 → 真实 Pi 产出回复 | **FAIL(原样代码)/ PASS(单行修复后)** | 基线轮:回复=原话术+「智能兜底本次未完成:编排器内部错误」,`[智能兜底·草稿]` 标记未出现(bug A)。诊断轮:回复含 `[智能兜底 · 草稿] run fb-20260902-075334-401b3c` + 真实分析正文 775 字(摘录见 §3),其中「2026-08-08 星期六」经独立核对属实(Saturday),订单号 102285668、物料 22 种均与快照一致 |
| 4 | run 目录 + 计划/日志/报告 + calls.jsonl 凭证 | **FAIL→PASS(同上分轮)** | 诊断轮 run 目录:`{events.jsonl, orchestrator.log, result.json, calls.jsonl, guard-<runId>.ts, inbox/{snapshot.md,orders.csv}, work/, outbox/report.md}`;calls.jsonl 4 行 = 2×issued+2×completed 配对,工具=fs_read;`citation_check: valid=true, cited=[], missing=[], issued=2` |
| 5 | 审计链 agent.fallback.propose | **PASS**(两轮都落链) | 审计写在**聊天身份所属的租户世界** `data/tenants/<tenant>/projects/personal-<uid>/world.json` 的 auditEvents:基线轮 result=FAILED(stopReason=harness_error),诊断轮 result=SUCCESS(stopReason=stop, steps=2, elapsedSec=34.89, reportPath 齐全),power=P1、actor=desktop-425e60c0、哈希链连续 |
| 6 | 开关关闭时行为不变 | **PASS** | 复跑 `tests/golden/test_fallback_lane.py` 13 例全绿(1.12s):`test_flag_off_preserves_original_behavior` 断言回复==原话术、零审计、零目录;`test_fallback_default_off_when_config_missing`/`test_fallback_non_bool_stays_off` 佐证默认关语义。代码路径:`fallback_feature_enabled()=False → propose_reply 返回 None → 原路径逐字节不变`。未起第二实例,以既有确定性测试佐证 |
| 7 | 收尾无残留 + 临时目录清单 | **PASS** | taskkill /T /F 后:8391/8392 端口释放(curl 超时)、无 uvicorn/smoke python 进程、全部 node.exe 命令行逐一核查无 pi-coding-agent/cli.js 残留;`git status server/data` 干净、world.json mtime 保持 Aug 4;临时目录清单见 §5 |
## 3. 真实回复摘录(诊断轮 run fb-20260902-075334-401b3c)
> [智能兜底 · 草稿] run fb-20260902-075334-401b3c
>
> 当前待排订单仅 **1 张**(`102285668`),交货日期为 **2026-08-08,星期六**。……交货压力出现在**周末尾部**。但样本量只有 1 张,远不足以判断统计趋势,只能如实记录这一个点落在周末。
>
> **综合**:整体备料环境在**总量上很松**,但在**齐套精度上要求很高**——22 根绳子吊 1 个篮子,每根都不能断。
非话术证据:数字来自真实快照(orders.csv 一行 = 102285668 / 28200003654300 / 1 / 2026-08-08 / RELEASED;snapshot.md = 物料 22 种);pi 真实发起 2 次 read 工具调用(events.jsonl:tool_execution_start×2,无 guard 拦截记录)。
## 4. 失败项与根因(本次冒烟的核心产出)
### Bug A(产品 bug,必修,一行):守卫模板注释行花括号未转义
- **现象**:真实 runner 路径必败——`result.json: stopReason=harness_error, errorMessage="KeyError: ' block'"`,0.001s 即败,pi 从未启动。
- **根因**:`server/agent_core/fallback_lane.py` `_GUARD_TS_TEMPLATE` 第 2 行注释
`// pi.on("tool_call") 返回 { block: true, reason } 即可在工具执行前拦截(P0 已实测)。`
里的 `{ block: true, reason }` 是单花括号字面量,`write_guard_extension` 里 `.format(RUN_ROOT_POSIX=...)` 把它当字段名解析 → KeyError。
- **为何测试没拦住**:守卫文件只在**真实 runner** 内生成(fake runner 无 pi 进程可加载),13 例黄金测试全走注入 runner——正是本轮冒烟要补的缺口。
- **修法**:注释行 `{`/`}` 双写(`{{ block: true, reason }}`)。诊断轮用内存 shim 做等价修正后,生成的 guard TS 含正确 `return { block: true, reason };`,链路全绿——**可预判该行修复即解锁**。
- **按硬约束未自行修改产品文件**;shim 只存在于 `poc/pi-fallback/smoke/shim_server.py`(内存 patch + 自检断言,模板若变化 shim 会拒绝运行)。
### 坑 B(环境/健壮性,建议项):node 被解析为 .CMD 垫片时真实 runner 起不来
- **现象**:修好 Bug A 后 pi 仍 0.41s 秒败(`error:no_stop_reason`),stderr 为 GBK 的 `'""' 不是内部或外部命令`。
- **根因**:本机 PATH 中 `node` 优先命中宿主环境的 `node.CMD` 垫片;`shutil.which("node")` 返回它,Popen 以含换行/空格的参数起 .cmd 时 cmd 引号语义爆炸。
- **绕过**:通过 `APS_FALLBACK_NODE=<node.exe-absolute-path>` 显式注入真实 Node 可执行文件(配置项本就存在,诊断轮已验证)。
- **建议**:`build_pi_runner` 在 Windows 下优先探测 `node.exe`,或文档明示该配置项为现场必查项。P0 未踩到是因为当时 shell PATH 顺序不同。
### 冒烟环境处理(非产品问题,如实记录)
- 桌面 mock 身份的聊天读写**租户世界**(`data/tenants/<uuid>/projects/personal-<uid>/world.json`),首访为空世界 → 首轮快照为空(订单 0 张)。诊断轮按 deviceId 的 sha256 确定性推出租户世界路径并预播种演示数据副本(全部在临时 APS_HOME 内)。
- 主线 LLM(kimi-k2-0711-preview)404 使意图二级分类稳定降级 assistant.reply——本次兜底触发依赖该合法降级路径;**「主线模型正常时未登记口令经 LLM 分类是否落 assistant.reply」未覆盖**,列为遗留(见 §6)。
## 5. 现场与产物清单(全部保留供查验)
| 路径 | 内容 |
|------|------|
| `poc/pi-fallback/smoke/smoke_e2e.py` | 基线轮脚本(原样产品代码) |
| `poc/pi-fallback/smoke/smoke_e2e_diag.py` + `shim_server.py` | 诊断轮脚本 + 内存单行修正 shim |
| `poc/pi-fallback/smoke/results.json` / `results2.json` | 两轮结构化结果(health/features/intent/resultJson/审计摘录) |
| `poc/pi-fallback/smoke/chat1.sse` / `chat2.sse`、`reply1.txt` / `reply2.txt` | 真实 SSE 原始流与回复全文 |
| `poc/pi-fallback/smoke/server.log` / `server2.log` | server 日志 |
| `poc/pi-fallback/smoke/site/` | 基线轮临时现场(APS_HOME:run fb-20260902-074954-990a0c、租户世界、features.json、master.db) |
| `poc/pi-fallback/smoke/site2/` | 诊断轮临时现场(run fb-20260902-075334-401b3c,成功run全产物) |
## 6. 遗留问题
1. **Bug A 未修**(硬约束:不自行修产品代码)——修复后建议补一条「真实 runner 模板 format 冒烟」级别的轻量防线(哪怕只对 `_GUARD_TS_TEMPLATE.format` 做单元断言),否则同类模板注释回归无网可拦。
2. **坑 B 待产品决策**:node 解析健壮性(优先 .exe / 文档化 APS_FALLBACK_NODE)。
3. **主线 LLM 正常时的意图落点未验证**:本次触发依赖 404 降级;待主线模型可用后补一轮「未登记口令 → LLM 分类 → assistant.reply」的触发确认。
4. **超时预算**:诊断轮 34.9s < 90s 熔断,余量健康;但更复杂的分析口令可能逼近预算,90s 同步上限的真实体验未压测。
5. 凭证语义已知张力(P1-IMPL-NOTES 遗留 #4)本轮如实复现:真实 pi 报告未引用任何 callId → valid;若引用必 forged。P2 前需明确真实路径凭证语义。
---
## 7. 修复后复证(Agent-H · 2026-09-02 · 修复后原样代码真实冒烟)
**结论:修复后复证 GO——七条判据全部 PASS,无任何进程内存补丁,产品文件原样跑通。**
### 修复内容(改动最小化,未 commit)
| 项 | 改动 | 文件 |
|---|---|---|
| Bug A | `_GUARD_TS_TEMPLATE` 注释行 `{ block: true, reason }` → `{{ block: true, reason }}`(双写转义,format 后生成 TS 仍含字面量单花括号)。已全文核查:该文件仅此一处 format 模板,其余代码花括号本就已双写,无同类残留 | `server/agent_core/fallback_lane.py`(1 行) |
| 坑 B | 新增 `_resolve_node()`:显式 `APS_FALLBACK_NODE` 最高优先级原样命中;默认 `"node"` 且 Windows 时 `shutil.which("node.exe")` 优先于 `shutil.which("node")`,避开 node.CMD 垫片;其余回退原逻辑。本机实测 `where node.exe` 可命中真实 Node 可执行文件 | `server/agent_core/fallback_lane.py`(+14 行) |
| 防回归 | 新增 `test_write_guard_extension_real_template_format`:真实调用 `write_guard_extension`,断言不抛异常、守卫文件落盘、内容含字面量 `{ block: true`、占位符替换干净——fake runner 盲区从此有网 | `tests/golden/test_fallback_lane.py`(+1 例,14 例) |
影响面(GitNexus MCP 不可用,rg 盘点):`write_guard_extension`/`build_pi_runner` 调用方均仅 fallback_lane.py 自身(poc/ 下同名函数属 PoC 独立副本,未动),爆炸半径 **LOW**,无 HIGH/CRITICAL。
### 复证执行(全部真实,数字写实)
1. **黄金测试**:`.venv\Scripts\python.exe -m pytest tests/golden/test_fallback_lane.py tests/golden/test_feature_flags.py -q` → **20 passed**(14 兜底 + 6 开关,1.41s)。
2. **ruff**:`fallback_lane.py` + `test_fallback_lane.py` → `All checks passed!`(零新增告警)。
3. **真实冒烟复跑**:`.venv\Scripts\python.exe poc/pi-fallback/smoke/smoke_e2e.py`(无 shim、无内存补丁、未设 `APS_FALLBACK_NODE`——orchestrator.log 实证 `node_bin='node'`,坑 B 修复生效自动命中 node.exe)。run = **fb-20260902-075907-3af703**,13.0s 完成(< 90s 熔断)。注:复跑按脚本设计重建了 `site/` 并刷新 `results.json`/`chat1.sse`/`reply1.txt`/`server.log`(基线轮旧文件被覆盖,诊断轮 site2/results2 保留)。
### 七条判据复证结果
| # | 判据 | 结论 | 证据 |
|---|------|------|------|
| 1 | server 启动健康 | **PASS** | `/api/health` 200,`interfaceVersion="1.0"`,storage.writable=true,dataDir 指向临时 APS_HOME(results.json health 段) |
| 2 | /api/features 开关语义 | **PASS** | `fallback.enabled=true`、`defaultOff=["fallback"]`、`source=file` |
| 3 | 未覆盖口令 → 真实 Pi 产出回复 | **PASS** | 回复 499 字,含 `[智能兜底 · 草稿] run fb-20260902-075907-3af703` + 真实分析正文(reply1.txt)。本轮租户世界首访为空(同基线轮),Pi 如实报告「待排订单 0 张、物料 0 种」并列出未竟事项——**这正是真实产出而非话术的力证**(空快照不编造数字) |
| 4 | run 目录 + 计划/日志/报告 + calls.jsonl 凭证 | **PASS** | run 目录全产物:`{events.jsonl, orchestrator.log, result.json, calls.jsonl, guard-<runId>.ts, inbox/{snapshot.md,orders.csv}, work/, outbox/report.md}`;守卫 TS 含正确 `return { block: true, reason };`(Bug A 修复直接证据);calls.jsonl 4 行 = 2×issued+2×completed(fs_read);`citation_check: valid=true, cited=[], missing=[], issued=2`;零 guard 拦截 |
| 5 | 审计链 agent.fallback.propose | **PASS** | 租户世界 `data/tenants/demo0000000000000000000000000001/projects/personal-298897889579184597/world.json`:1 条 `agent.fallback.propose` result=SUCCESS(power=P1、actor=desktop-425e60c0、stopReason=stop、steps=2、elapsedSec=13.0、reportPath 齐全)+ 3 条 tool.run,哈希链连续(逐对核验 prevHash==hash) |
| 6 | 开关关闭时行为不变 | **PASS** | 上述 20 passed 中 `test_flag_off_preserves_original_behavior` 断言回复==原话术逐字节、零审计、零目录;默认关三态测试同绿 |
| 7 | 收尾无残留 + 临时目录清单 | **PASS** | 脚本 finally taskkill /T /F 后:8391 端口释放(curl 超时);ps 无 uvicorn/pi 进程;全机 node.exe 命令行逐一核验均为用户既有 dev 环境(Kimi runtime/WebStorm/vite/electron),零 `pi-coding-agent/cli.js` 残留;`git status server/data` 干净、world.json mtime 保持 2026-08-04 00:36 |
**P1 最终判定:GO。** Bug A 一行修复即解锁的预判成立;坑 B 产品化处置(Windows 优先 node.exe)真实生效;遗留项不变(主线 LLM 正常时的意图落点仍待验证,见 §6.3)。