跳转至

独立源码审查 A · 初审

结论:FAIL,暂不允许将这一版宣布为最终验收通过。

基线:官方 v1.0.0,commit a13d35a742c6ef8462812a28fbe1d8c8b7431c32。另比较本轮刷新后的 main 9fba660cf1caca0ade5bea72269352416e595a19。以下文档主体按固定稳定版本核对,不拿 main 的变化混入 1.0 实现解释。

这是代码语义审查,不是只检查链接、编译或图是否能渲染。审读了 docs/02-design.md、docs/04-models-messages.md、docs/source/05-agent-loop.md、docs/source/06-session.md、docs/source/07-context.md 共 710 行;完整阅读 Agent、Agent Loop、Agent 类型、SDK 工厂、transcript 重放工具;逐个连续函数上下文阅读 AgentSession 的 prompt / canonical projection / event persistence / boundary / settlement / disposal,以及 SessionManager 的 entry 类型 / projection / persistence / branch / fork,实现压缩准备及 token 估算相关函数也完整阅读。

必须修正的事实与关键边界

A-01 · 中 · normalizeContext 的职责被描述得过强

  • 文档位置:docs/04-models-messages.md:63、:82,§4.5 图。
  • 原文将 normalizeContext 称作“模型协议规范化”,图直接从它进入 provider request,容易让自己实现 Agent 的读者误认为此函数完成 provider 格式转换或合法性检查。
  • 实际源码:packages/ai/src/utils/transcript.ts:27–32 只调用 createInitialSystemMessage(context.systemPrompt, context.tools),将结果前置到 context.messages,返回带品牌类型的 {messages}。它没有 schema / role / 工具配对校验,也没有 provider 格式转换。
  • 不支持会话中 system 更新的 provider 在 resolveTranscript / collapseSystemMessages(同文件 :115–127)折叠更新;具体 provider payload 在 API adapter 中生成,例如 api/openai-responses-shared.ts:151 起。
  • 建议:改称“将旧的外置 prompt / tools 字段折入 transcript”,明确它并非校验器;增加 provider adapter → resolveTranscript / 工具声明映射 → 服务商 payload 的图节点。不要声称所有 provider 都能原位接收 system/tool delta。
  • 独立运行验证:research/reviews/reviewer-a-probe.ts 传入未支持的 role,normalizeContext 原样保留对象,说明它没有执行角色校验。

A-02 · 中 · 本次最终路由模型与图片归一化模型混淆

  • 文档位置:docs/source/06-session.md:78,§6.6 步骤 10:“按最终选定模型的限制归一化图片”。
  • 普通物理模型可如此理解;虚拟模型不成立。
  • 实际源码:agent-session.ts:1900 调 _limitsModel(),:612–614 返回 this.routedModel?.model ?? this.model;:1422–1427 的 routedModel 来自历史中最新成功 response 的物理模型。新请求的 resolveModel(...) 在 _installAgentRequestProjection 的 prepareRequest 中(:798 附近)才执行,这发生在图片处理之后。
  • 因此先前成功路由是 A、本次 router 选择 B 时,不能说图片一定按本次 B 的限制处理。没有成功路由历史时则回退当前 virtual selection 元数据。
  • 建议:写清“使用 _limitsModel() 当时可获得的限制;通常为当前物理模型,virtual selection 下为最近成功路由或当前 selection;本次最终 routing 稍后才发生”。可加一条选型时间线。

A-03 · 中 · fork 的复制范围必须按具体 API 区分

  • 文档位置:docs/source/07-context.md:111 泛称“fork 或 clone 会把选定历史复制到新会话文件”。
  • 实际源码:session-manager.ts:1627 的 createBranchedSession(leafId) 复制选定根到 leaf 的路径并重建 labels。相反 SessionManager.forkFrom(...)(:1815–1865)读取 sourceEntries,逐一复制 全部非 header entries,包括非当前路径上的支线。
  • 因此不能用一条“fork=选定路径复制”概括全部公开 API。
  • 建议:列出 branch() / createBranchedSession() / CLI 或 session-level fork / forkFrom() 的对象、复制范围、文件变化;对未审查的 clone API 不笼统推断。
  • 独立运行验证:同一树含 A 和 B 两条支线,forkFrom 后 getEntries() 包含 A,getBranch() 不包含 A;完整 entry ID 列表与源一致。

A-04 · 中 · active leaf 的持久化边界缺失

  • 文档位置:docs/source/07-context.md:46、:111。
  • 当前文字“切换分支点”和“恢复 active branch”没有交代分支导航本身只在内存改变 leaf。
  • 实际源码:branch(...)(session-manager.ts:1579–1584)只设置 this.leafId,没有写入;重开文件的 _buildIndex()(:1103–1123)按物理文件遍历,每遇 entry 便设置 leafId,最后落在最后一条 entry。只有之后 append 创建新子节点才把新路线落实为文件尾。
  • 建议:明确说明 branch 后尚未 append 便退出,重开不保证回到刚刚选择的节点;不能把 UI 当前选择理解为独立持久化的光标。补一小段 root/A → branch(root) → reopen=A 的轨迹。
  • 独立运行验证:branch 到 root 后本进程 leaf 是 root,立刻 reopen 则 leaf 仍是原来的 branchA;验证通过。

详细学习目标要求补足的部分

以下不是已经存在的错误断言,但对于用户要求的“足够细致代码分析”是重要缺口;建议在最终版补充,而不是用源码整文件展示数量代替分析深度。

A-05 · 中 · 自定义 StreamFn 的失败合约没有讲透

docs/source/05-agent-loop.md:79 附近应补充底层 stream / awaited run 两套失败边界。packages/agent/src/types.ts:20–34 要求 request/model/runtime failure 编码为 error/aborted 的 event stream,而不是 throw/reject。convertToLlm / transformContext / getApiKey / 队列函数同样声明安全回退合约。agentLoop() 使用 void runAgentLoop(...).then(...)(:48–60),并无 catch 将任意拒绝转成正常结束,因此外部实现违约不能按标准 EventStream 成功消费处理;Agent.runWithLifecycle 才有 handleRunFailure 兜底。异常 listener 本身也不是被无条件吞掉的观察者。补“低层 caller 要承担什么 / Agent 多提供什么”的表格。

A-06 · 中 · Compaction 目前只有概述,缺少算法与图

§7.7 几乎没有解释 compaction.ts 的 token 估算、切点、split turn、失败 checkpoint、历史 usage 失效机制;这些是长期 Agent 不出错的关键。

建议至少补充:

  1. DEFAULT_COMPACTION_SETTINGS:enabled=true / reserveTokens=16384 / keepRecentTokens=20000;严格阈值 contextTokens > contextWindow - reserveTokens(:126–130、:267–270)。
  2. 上次有效 assistant usage + 后续消息估算;后来的 context_edit / compaction 会让旧 usage 不再表示当前投影,estimateProjectedContextTokens 回退重估(:178–265)。chars/4 是启发式,不能声称对所有语言都保守或准确。
  3. 不在 toolResult 处切断;可以切在 assistant tool request 前,保留其结果(:346–518)。当前 prepareCompaction 使用 projected cut-point(:795–928),尊重 edits,而不是按原始行数切片。
  4. split turn:旧历史摘要 + 本轮 prefix 摘要 + 真实 retained tail,图中画出三段。已有 summary 更新与系统 checkpoint 是不同数据。
  5. 摘要生成拒绝 length/error/工具调用;一条不完整 summary 不能当 checkpoint(:586–597、:754–761);standalone summary 通常关闭缓存写入并有独立 routing ID。

A-07 · 低 · 主循环图应显式呈现保护分支

docs/source/05-agent-loop.md:106 的图把所有有 toolCall 的消息送入“校验并执行工具”,文字虽然另有 length 保护,但图本身省略了这个重要分支。建议补 length? → 全批拒绝并产生 error results,并让 error/aborted 分支经过 turn_end → agent_end,而不是悬空的 X 节点。这样图和完整生命周期一致。

已核对通过的核心断言

  • Core listeners 按注册顺序 await,状态先归约,agent_end 不等于 listeners 已结束;SDK public listeners 则不 await。
  • 两层循环对工具续跑、steering、follow-up、finishTurn end / continue 的调度,含 error/aborted 硬退出。
  • length stop 拒绝该 assistant 中全部 tool calls;预检 schema/hook 顺序;并行预检串行、执行并行、结果 transcript 保持源顺序;一个 sequential 工具令全 batch 串行。
  • terminate 必须每个 finalized result 都为 true;afterToolCall 逐字段替换,content 改写时结构化结果一致性规则。
  • SDK factory 的选项类型、显式 model 优先、virtual selection 恢复、默认工具、blockImages 路径。
  • canonical SessionManager projection 在每个 provider 请求前覆盖 memory-only transcript;message_end public listener 早于对应 persistence;boundary 解析 persisted entry IDs;agent_settled 包含 post-run retry/compaction。
  • append-only entry 树、latest compaction+retained范围、context edits 后投影、custom与custom_message区别、JSONL延迟首次flush与外部效果并非恰好一次。
  • 独立核对范围中主体源码与 stable 快照一致。刷新后的 main 对这些核心文件仅 transcript 的 hasToolRedefinitions 注释 / deprecation 变化,不代表这里可悄悄按 main 替换稳定版逻辑。

验证与不确定边界

  • 独立 probe 运行:node research/reviews/reviewer-a-probe.ts,3 个边界验证通过,无真实 provider / 凭据 / 付费调用。
  • 尚未验证各家真实服务 payload/认证/缓存命中,也没有审读所有 1848 个仓库文件;不能将此报告描述为全仓库逐行审计。
  • 本报告仅判定上述五篇章节与对应主干代码;其余章节由其他独立审查员负责。
  • 复验条件:A-01–A-04 修正并能与固定版本代码对应,A-05–A-07 补充后语义和图准确,再作独立复读。当前 FAIL,不是最终 PASS。