Skip to content

P0:消除 Agora 启动阻塞与 read_file 截断假完成 #42

Description

@zhuqingyv

P0:消除 Agora 启动阻塞与 read_file 截断假完成

项目 内容
产品 my-agent(MA)
严重级别 P0 / release blocker
当前候选 MA 0.3.0(PR #40 8e110a5)+ Agora 0.2.0(Agora PR #22 cb4cb6d
关联 #39#41、PR #40、zimoos/agora#22
范围 一个父 Issue,包含启动性能与文件读取可信度两个必须同时完成的工作流

Problem

当前最终候选存在两个直接破坏首次体验和用户信任的问题:

  1. 启动阻塞:执行 ma 后,TUI 必须等待 Agora 二进制启动、MCP 握手和 Provider ready,用户会面对约 5~10 秒空白。
  2. 文件读取假完成read_file 虽然先返回完整文件,但 MA 在写入模型历史前统一截为 4000 字符。模型尝试分页时,字符串形式的 offset 又被文件服务静默忽略,导致重复读取第一页,最后仍可能声称“已完整查看”。

这不是两个孤立的小 Bug:前者让用户误判产品无法启动,后者让 Agent 在代码工作中给出没有证据的结论。两者都必须在正式交付前解决,不能用增加 spinner、扩大字符上限或提示词约束冒充完成。

Evidence

A. 启动性能

同一台 Apple Silicon Mac、同一候选包的串行实测:

场景 耗时
最终 ma 到首个可见 TUI frame 5347ms、6619ms
当前配置冷 bootstrap 最高 8453ms
仅 Agora、无普通 MCP 4328ms、4630ms
远程 Provider + 5 个普通 MCP 135ms、145ms
MA 空壳基线 10ms
packaged Agora MCP initialize 4278ms、4541ms
同代码源码 runtime initialize 242ms、433ms

Agora 初始化的分步结果:tools/list 1~2ms、resources/list 1ms、doctor 10~11ms、runtime_capabilities 1ms、models_list 9ms。慢点集中在 packaged runtime 启动,不在 35B 模型加载,也不在能力查询。

当前代码路径:

  • src/cli/index.tsxrender(<App />) 前执行 await bootstrap(...)
  • src/agent.ts#createAgent 在返回 Agent 前执行 await providerRuntime.ready?.()
  • src/provider/agora.ts#start 同步等待 MCP initialize 和完整能力握手。
  • Agora scripts/build_native_release.py 同时传入 --onefile --standalone;产物包含 NUITKA_ONEFILE_* 标记并按进程解包到临时目录。

Nuitka 官方文档明确说明:onefile 会在目标机器运行前自解包,默认使用每次唯一且随后删除的临时目录;--mode=onefile 已自动包含 standalone。npm 本身已经完成安装解包,因此这里继续使用 onefile 只增加重复启动成本:

B. read_file 截断与重复读取

真实 Agora 会话的脱敏证据:

  • src/agent/compact.ts 对普通工具结果设置 TOOL_RESULT_MAX_CHARS = 4000,保留 75% 头部和 25% 尾部。
  • 一个 198 行 JS 文件进入模型历史时丢失 1380 字符;一个 212 行 JS 文件丢失 2324 字符。
  • 模型后续分别发出 offset: "100"offset: "95"
  • servers/fs-mcp.ts 只接受 typeof offset === "number";字符串会被静默降级成 offset = 1
  • 两个文件的第二次工具结果分别与第一次 SHA-256 完全相同,证明没有翻页,只是重新读取第一页。
  • 最终回答仍声称“已经完整查看”,属于确定性假完成。

这里还有两层必须区分的截断:

  • TUI 工具结果预览约 400 字符、progress 预览约 90 字符,属于展示折叠。
  • 写入模型历史的 4000 字符上限属于真实信息丢失,模型确实看不到中间内容。

fs-mcp 已经返回 offset/limit/totalLines/start/end/complete/nextOffset/hash structured content,但该证据目前没有进入模型可见的分页说明,也没有进入 Agent 的读取覆盖账本。

Architecture Direction

1. 启动源真相:TUI 生命周期与 Provider 生命周期解耦

启动必须拆成两个阶段:

同步读取配置/会话
        ↓
立即渲染完整 TUI shell(可输入、显示启动状态)
        ↓
后台并行连接普通 MCP + 启动 Agora MCP
        ↓
能力握手完成后原地 hydrate Agent/Memory/Model 状态

固定状态机:

shell_visible → connecting → ready
                         ↘ degraded / failed(TUI 不退出)

要求:

  • runChat() 不得在首屏前等待完整 bootstrap()
  • 启动期间输入框允许编辑;用户提交的第一条消息最多排队一条,ready 后自动发送。失败时保留草稿。
  • Provider/MCP 失败不得 process.exit(1);TUI 提供重试、doctor 详情和切换远程 Provider。
  • 普通 MCP 使用 Promise.allSettled 并行连接,同时保持配置顺序和独立失败状态。
  • Agora 仍必须执行真实 initialize、工具清单、协议、manifest、签名和 capability 验证;缓存不得跳过信任握手。
  • 模型权重仍只在第一次 chat 时加载,启动阶段不得预加载 35B 模型。

2. Agora 发布源真相:npm 安装目录就是可复用的 standalone runtime

将 Agora 平台包从 Nuitka onefile 改为 Nuitka standalone 编译目录

  • 用户入口仍只有 agora Mach-O launcher。
  • 平台包内部可包含多个编译后的 Mach-O、.so/.dylib 和明确 allowlist 的运行数据,但不得包含任何 .py.pyc.js、source map、源码路径、私钥或 debug symbols。
  • launcher 直接 execv 已安装的 standalone runtime,不做每进程临时解包。
  • manifest 覆盖 standalone runtime tree 的全部可加载文件;验证每个 hash,并拒绝未列出的可执行/源码型文件。
  • 对 runtime tree 内所有 Mach-O 递归执行 strip、Developer ID Hardened Runtime 签名和 release 审计。
  • 保留现有 host protocol、native ABI、capability、notarization、Gatekeeper 和 npm integrity 机制。
  • 产物变化必须发布新 semver;禁止复用已有版本制造同版本不同 hash。

--onefile-tempdir-spec 持久缓存只能作为实验对照,不能作为最终方案:它仍让首次启动承担解包成本,并引入缓存失效、权限和篡改边界。

3. 文件读取源真相:由 Read Receipt 证明覆盖范围

不得通过无限扩大 TOOL_RESULT_MAX_CHARS 解决。建立 provider 无关的 FileReadLedger,唯一可信输入是 read_file 的 structured content:

canonical_path
file_hash
covered_ranges[]
total_lines
complete
next_offset / next_cursor

每个模型可见页面必须包含受控正文和机器可读页脚,例如:

[read_file page] lines 1-80/198 · complete=false · next_offset=81 · hash=ab12…

规则:

  • 默认读取改为受字符预算约束的分页,单页正文目标不超过 3200 字符,为页脚和下一轮推理留空间。
  • 保留 offset/limit 兼容;正整数字符串必须规范化为 number,其他非法值必须返回 typed error,禁止静默回到第一页。
  • 对超长单行增加 next_cursor(包含行/列位置或等价 opaque cursor),保证 minified 文件也不会静默丢中段。
  • ToolExecutionResult.structuredContent 必须进入 FileReadLedger;当前只进入 UI event/返回对象但在 Agent 主循环被丢弃的路径必须接通。
  • read_file 的模型历史使用“已受限页面 + receipt”,不再经过会切掉中段的通用 head/tail compaction;其他工具继续保留通用压缩策略。
  • 相同 path + hash + range/cursor 在同一任务重复时,不再把同一正文二次写入上下文;返回 duplicate_page 和正确下一位置。
  • 文件 hash 变化时废弃旧覆盖范围,明确提示文件已变化。
  • exec cat/sed/head/tail 的文本不计入“完整读取”证据;完整代码审阅必须来自可验证 read receipt。

4. 完成源真相:没有覆盖证据就不能声称完整读取

复用 CompletionObligationAudit

  • 当用户要求“完整/全部审阅文件或项目”,增加 file_read_coverage obligation。
  • 当候选最终回答主动声称“完整看过/fully read/已阅读全部代码”,若本任务存在未完成页面,也触发同一 gate。
  • gate 返回未覆盖文件和下一位置,让 Agent 继续分页;达到有界补救次数仍不完整时,必须诚实报告“部分读取”,不能输出成功口径。
  • TUI 明确区分 预览折叠页面未读完Provider 输出截断,不能都显示成同一个“截断”。

Reuse / Mature Solution Plan

优先复用现有实现:

  • MA:BootstrapResult、Ink App/store、provider:attempt/retry events、MCP client、structuredContent 管道、CompletionObligationAudit、PTY/visual helper、Context Usage。
  • 文件工具:现有 offset/limit/totalLines/complete/nextOffset/hash 及分页重建测试。
  • Agora:C++ launcher、manifest/hash verifier、native core、tarball source audit、strip/sign/notary/Gatekeeper/release evidence 流程。
  • 打包方式:使用 Nuitka 官方 standalone 模式,不引入自定义解包器或常驻 daemon。

Implementation Checklist

Workstream A — Agora 与 MA 启动

Agora 仓库

  • scripts/build_native_release.py 从 onefile 改为 standalone-only 构建。
  • 将 standalone runtime tree 安装到平台 npm 包的稳定 libexec/runtime 路径。
  • 更新 C++ launcher,直接启动已安装 runtime。
  • manifest 枚举并校验全部 runtime 文件;拒绝缺失、篡改和未允许的源码/可执行文件。
  • 递归 strip/sign 所有 Mach-O;扩展 tarball、debug symbol、源码路径和私钥扫描。
  • release runner 增加 packaged doctor 与 MCP initialize 冷/热启动性能门。
  • 增加从 candidate 到正式签名包完全相同布局的测试,禁止 release 阶段重新回退 onefile。
  • 为新产物提升 semver,并输出最终 manifest SHA/npm integrity evidence。

MA 仓库

  • 新增 Startup Coordinator/状态机,配置与 session 就绪后立即 render shell。
  • bootstrap 拆成 shell bootstrap 与 async runtime hydration;保留 CLI、E2E 和 benchmark 可复用入口。
  • 首屏路径移除 await providerRuntime.ready?.();Agent chat 在首次提交边界等待 ready。
  • 并行连接普通 MCP,按配置顺序投影结果。
  • 启动中支持编辑和单消息排队;ready 后自动发送,失败后保留草稿。
  • Agora/MCP 启动失败进入 degraded 状态,不退出 TUI;支持重试和 Provider 切换。
  • StatusBar/Activity 显示 连接中/ready/失败,不向 transcript 插入启动噪声。
  • 新 Agora 版本发布后更新 exact dependency、lockfile integrity、runtime manifest SHA 和 portable 包。

Workstream B — 文件分页与真实性

  • read_file offset/limit 增加统一正整数规范化;非法参数 typed error,删除静默默认。
  • 增加字符预算分页和超长单行 cursor;兼容现有 line offset/limit。
  • 模型可见结果包含 page receipt;TUI 使用 receipt 摘要而不是代码前 90 字符。
  • ToolExecutionResult.structuredContent 接入新的 FileReadLedger
  • 相同 hash/range 重复页去重并返回下一页指引。
  • 文件变化时重置覆盖账本,防止拼接不同版本。
  • read_file 页面绕过通用中段截断;generic tool compaction 保持不变。
  • 扩展 completion obligation,阻止无覆盖证据的“完整读取”声明。
  • 明确 execute_command 读取文本不构成完整读取凭证,并补回归测试。
  • session resume/context compact 后保留最小读取账本,不能把已读正文无限常驻上下文。

Validation

1. 启动性能测试

  • 使用真实 npm candidate、真实 MCP stdio、同一参考 Mac,串行执行,禁止并发测量干扰。
  • 至少执行 5 次 clean-install/cold run 和 30 次 warm run。
  • PTY 分别记录:process spawn、首个完整 TUI frame、输入可编辑、Agora initialize、provider ready。
  • 同时跑 remote Provider + 5 MCP 对照组。
  • 保存原始 JSON timing artifact,不只粘贴平均值。

2. Agora 包验证

  • strings 不得出现 NUITKA_ONEFILE_*
  • npm tarball 清单不得包含 .py/.pyc/.js/.map、源码路径、私钥或 debug symbols。
  • 修改任意 runtime 文件后 launcher 必须拒绝启动。
  • 删除任意 manifest 文件、加入未允许可执行文件、协议/ABI 不匹配都必须失败。
  • candidate 与正式 release runner 使用同一 layout 和同一审计逻辑。

3. 文件读取单元/契约测试

  • 198/212 行普通源码可通过连续 receipt 无缝重建,覆盖范围无重叠、无缺口、hash 一致。
  • offset: 100offset: "100" 行为一致;"abc"、0、负数、NaN 明确失败。
  • 单行超过 20K 字符的文件通过 cursor 完整重建。
  • 同页重复调用不重复写入模型上下文,并返回下一页。
  • 文件中途修改后旧 ledger 作废。
  • Context compact/session resume 后 ledger 语义保持,正文不无限膨胀。

4. 真实 Agent E2E

  • 使用真实 packaged Agora + 本地白名单模型执行“完整审阅多文件项目”。
  • Trace 必须证明:所有目标文件 page coverage 完成;没有相同 hash/range 重复;最终回答前无未读页。
  • 再用一个远程 OpenAI-compatible Provider 执行同一 fixture,证明不是 Agora 特判。
  • 增加反例:故意让模型给出字符串 offset、重复页和提前“完整看过”,MA 必须规范化、去重或阻止完成。

5. 全量回归

  • MA build、全量 tests、MA_RUN_PTY_TESTS=1、visual、真实 provider/tool calling、Context Usage。
  • Agora 全量 tests、native release pipeline、packaged MCP、签名/篡改审计。
  • skip 不算 PTY/packaged 验收通过。

Acceptance Criteria

启动

  • 参考环境上首个完整 TUI frame p95 ≤ 500ms,输入可编辑 p95 ≤ 1s;首屏不能只是 TUI 外的一行 spinner。
  • packaged Agora MCP initialize:cold p95 ≤ 2s、warm p95 ≤ 1s,并且相较当前 4.28~4.54s 至少降低 70%。
  • 用户在 runtime 尚未 ready 时可以输入并安全排队第一条消息;失败时草稿不丢。
  • Agora/MCP 启动失败不会退出 TUI,用户可查看错误、重试或切换 Provider。
  • remote Provider + 5 MCP 启动无性能和功能回归。
  • 发布平台包不含 onefile 自解包路径,也不暴露 Python/JavaScript 源码。

文件读取

  • 普通源码、超过 4000 字符的源码和超长单行文件均可增量、经济、无缺口地完整读取。
  • 模型每页都看到范围、总量、complete、下一位置和文件 hash;TUI 清楚区分预览折叠与页面未完成。
  • 数字字符串不会再被静默重置到第一页;非法分页参数必须显式失败。
  • 相同文件版本与范围不会重复污染上下文,Agent 会前进到正确下一页。
  • 未完成覆盖时,Agent 不能声称完整读取;有界补救失败后必须报告部分读取。
  • 分页机制不通过无限扩大上下文实现,Context Usage/compact 语义保持不变。
  • Agora 和远程 Provider 使用同一读取契约与完成证据门。

Redlines

  • 正式 runtime strings 中无 NUITKA_ONEFILE_*,构建脚本主路径无 --onefile
  • runChat 首屏路径不再 await 完整 runtime bootstrap;createAgent 的 ready 等待不再阻塞 render。
  • read_file 非法 offset/limit 不得回退默认值。
  • 有 structured page receipt 的 read_file 不得再由通用 head/tail compaction 静默切掉中段。
  • 没有 ledger coverage 的文字自述不得被 completion audit 视为完整读取证据。

Acceptance Mapping

Acceptance criterion Code or system path Validation/proof Old path status Notes
TUI ≤500ms 可见 src/cli/index.tsx, Startup Coordinator, Ink store 5 cold + 30 warm PTY timing JSON pre-render full bootstrap removed 完整 shell,不是外部 spinner
输入排队且失败不丢 startup state/store, App hydration PTY ready/failure/retry cases process-exit startup removed 最多一条待发送消息
Agora initialize 达标 Agora standalone runtime、launcher packaged MCP cold/warm p95 onefile removed 不加载模型权重
无源码且可验证 Agora build/release/manifest/signing tarball audit、codesign、篡改测试 source ban unchanged; manifest expanded 多二进制允许,语言源码禁止
MCP 并行且有序 src/index.ts, connection projection slow/failed/success mixed contract test sequential connect removed 单个 MCP 失败不阻塞首屏
Read Page 契约 servers/fs-mcp.ts, MCP types page reconstruction + long-line tests full-file default bypassed bounded chars + receipt
参数不静默降级 fs argument normalization number/string/invalid matrix silent fallback removed 错误必须可自愈
读取覆盖账本 ToolExecutor, agent.ts, new FileReadLedger range merge/hash change/resume tests structuredContent discard removed 正文可 compact,证据保留
重复页不污染上下文 FileReadLedger/tool policy identical hash/range regression repeated-success path removed 返回 next position
禁止假完整 completion-obligations.ts, candidate final audit explicit request + spontaneous claim tests text-only success removed 失败时报告部分读取
Provider 一致性 Agora + OpenAI-compatible request loop 同 fixture 双 Provider real E2E unchanged by design 不是 Agora 特判
Context Usage 无回归 agent.getContextUsage(), StatusBar 宽/窄 PTY + compact/resume unchanged by design 分页不清空 context

Parent Scope Status

Non-goals

  • 不在启动时预加载本地大模型。
  • 不引入常驻 daemon、账号登录、机器绑定或联网授权。
  • 不把 Agora 改回向用户分发 Python/JavaScript 源码;standalone 包仍必须是编译产物。
  • 不取消通用工具结果压缩,也不把整个大文件一次性塞入上下文。
  • 不修改 Agora Memory v2、MemoryPatch、内化和热拔插语义。
  • 不用 onefile 持久缓存、splash screen 或单纯调大 timeout 作为关闭证据。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions