Skip to content

feat: 为 Agent / Deep Research 持久化不可变运行清单并新增 Run Info 视图 - #262

Open
nanhanq1 wants to merge 12 commits into
helsome:mainfrom
nanhanq1:feat/run-manifest-21
Open

nanhanq1 wants to merge 12 commits into
helsome:mainfrom
nanhanq1:feat/run-manifest-21

Conversation

@nanhanq1

@nanhanq1 nanhanq1 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

改动说明

为每次 Agent / Deep Research run 持久化一份 immutable、machine-readable 的 manifest,记录当时实际生效的运行配置,使任何历史报告都能回答「当时到底是用什么配置跑出来的」。

按包拆分:

  • core:新增 RunManifest / RunManifestContext / RunManifestContextPatch / RunManifestDiff 等类型,覆盖 provider/model identity、实际生效的 model params、prompt version/hash 与来源、agent/workflow strategy、enabled tools(含 capability)、search/retrieval provider 与 routing、feature flags/runtime mode、app/build/git 版本、budget(含 effective/clamped)与 eval 上下文(与 [Eval] Add Gold Case dataset and real end-to-end evaluation harness #15/[Runtime] Add run budgets and runaway-agent safeguards #17 对接);Run 与 ResearchRunSummary 增加可选 manifest 字段;新增 AGENT_WORKFLOW_ID。
  • shared:captureRunManifest 在 run 创建时快照配置;redactManifestSecrets 递归替换 secret 形状的键(apiKey/secret/token/credential 等);diffRunManifests 按 model / prompt / tools / config / versions 分组产出结构化差异;exportRunManifest 输出缩进、已脱敏 JSON;manifestToLangfuseMetadata 由清单派生 Langfuse trace metadata(与 [Eval] Integrate Langfuse tracing and evaluation for Agent / Deep Research runs #14 对齐)。
  • electron:主进程装配 manifest 上下文(runtime mode、provider/model/params、prompt hash、agent 工作流身份、enabled tools、search routing、feature flags、app/build/git 版本),新增 getRunManifest / exportRunManifest / compareRunManifests / getResearchManifest / compareResearchManifests;main / preload / renderer client 全链路暴露。diff 在主进程计算(渲染进程不引入 @finagent/shared)。
  • ui:新增 Run Info 视图(人可读展示 + 脱敏 JSON 复制导出 + 两次运行结构化 diff),Deep Research 运行历史新增「运行信息」入口与勾选两次运行的对比入口。

不可变性:manifest 只在 run 创建时写入一次,execute 阶段从不改写。因此应用重启或全局配置变更后,历史 run 仍展示其诞生时的配置,而非当前设置。secret 在采集阶段就不会进入 manifest(另有一层递归脱敏兜底)。

把「已声明但未填充」的字段补齐

首版里 budget / strategy / evaluation 三个字段在 schema 中存在,但实际没有采集路径。已补齐,使 issue 要求的每一项都真正落到 manifest:

要求 实现
relevant budget/config(与 #17 对接) 预算在 run 落盘之前解析(defaults → per-run overrides → system ceiling),把 defaults/ceiling/overrides/effective/clamped 一并写入清单,记录 run 实际遵守的上限而不只是请求值。非法预算在写入前 fail loud,不再留下半创建的 run。Deep Research 同样记录其 research 预算。
eval dataset / case id / dataset version(与 #15 对接) 评测运行把 { datasetId, caseId, datasetVersion } 写进清单。
agent/workflow strategy version Copilot agent 路径记录工作流身份(AGENT_WORKFLOW_ID + 随包版本)。Deep Research 用 StrategyId 覆盖 id,version 记录「策略解析出的能力计划」的 SHA-256(内容寻址):同一策略稳定、计划一变即变,两次行为不同的运行绝不会共用同一个策略版本。
与 #14 Langfuse metadata 保持同一 run identity manifestToLangfuseMetadata(manifest, extras) 成为唯一映射点:清单 runId 即 Langfuse 的 folioRunId(也是 trace id),trace 同时带上 model/provider/strategy/prompt version/gold case,trace 与本地清单可互相定位。回读确认值优先,requestedModel/requestedProvider 单独保留,不会被读成「实际跑的模型」。

同时把「清单上下文」从调用点收回到 kernel:RunManager 新增 getRunManifestContext,宿主注入一次,所有运行入口(Copilot run、评测直接调用 kernel 的 run)都落一份真实清单,而不是空 fallback。单次调用只补充自己负责的字段(评测补 gold case)。

关联 Issue

Closes #21

验收:两条真实运行项(本 PR 已提供真实证据)

issue #21 里 fixture 无法满足的两条验收项,已用 apps/electron/e2e/run-manifest-acceptance.ts 跑通。脚本只用真实组件、不含 fixture:真实 AgentKernel + ResearchService(生产研究路径)、真实 DeepSeek HTTP 流式调用、经仓库自带的 capability fetcher 注入点(createFullRegistry)走公网取数(能力体——校验/脱敏/摘要/溯源——原样执行)。完整报告见 docs/run-manifest-test-report.md。

1) 两次真实 Deep Research run + 刻意改动配置 → diff 精确反映

两次运行刻意改动 model(deepseek-flash → deepseek-v4-pro)与 budget(modelCalls 3 → 6),其余维度保持不变:

changed = true
groups  = { model: true, prompt: false, tools: false, config: true, versions: false }
paths   = ["budget.defaults.modelCalls", "budget.effective.modelCalls", "model"]
model   : "deepseek-flash" -> "deepseek-v4-pro"
budget  : 3 -> 6

prompt / tools / versions 保持 false,说明 diff 是有选择性的,不是「有差异就全标红」。两次运行都真实完成取数(market.quote、market.kline、company.profile、research.news),状态 partial(无真实等价数据源的 12 个能力显式 unavailable)。provider 回显的 apiModel 与请求模型一致,证明清单记录的就是实际跑的模型。

2) 重启后读回历史 Run Info → 仍报告诞生时的配置

第三个子进程是全新进程,其当前全局模型为 deepseek-v4-pro,却读回历史 run A(诞生于 deepseek-flash):

项 值
读取进程 全新进程(模拟应用重启)
该进程当前全局模型 deepseek-v4-pro
历史清单模型 deepseek-flash
历史 run id research-e95ce1be-6eb9-479c-96dd-668d359c631e
是否被覆盖 preserved: true(未覆盖)

证据产物

docs/acceptance/run-manifest/:verification.json、两次运行的 manifest、JSON export、manifest-diff.json、重启读回载荷、provider 用量,以及 Run Info 的 HTML/文本视图。两个 HTML 由 apps/electron/e2e/render-run-info.ts 用真正发布的 ManifestView / DiffView 组件渲染(颜色取自渲染进程自身的 index.css 设计令牌),即应用实际展示的视图。

测试报告(正式审核前必填)

环境

  • Bun:1.4.2
  • OS:Microsoft Windows 11 家庭中文版 10.0.26200(AMD64)
  • 基线:upstream/main @ ba5dcdf
  • 提交:402bd92(验收产物记录的就是这个 revision)

实际执行命令与结果

# 本 PR 直接触及的 4 个测试文件(focused)
bun test --isolate packages/shared/src/research/service.test.ts \
    packages/shared/src/kernel/run-manager.test.ts \
    packages/shared/src/evaluation/langfuse/metadata.test.ts \
    packages/shared/src/evaluation/experiment-service.test.ts
→ 84 passed / 0 failed(325 assertions,4 files)

# 覆盖改动面的更大范围
bun test --isolate packages/shared/src/research packages/shared/src/kernel \
    packages/shared/src/evaluation packages/ui/src/components/kernel
→ 419 passed / 1 failed(1964 assertions,36 files)
   唯一失败项 langfuse.test.ts「does not throw when Langfuse is down」为**本机既有失败**:
   已用 git stash 在干净基线复现同一失败,且该用例在 CI 中通过。与本 PR 无关。

# 两次真实 Deep Research 运行的现场验收(真实模型 + 真实公网取数)
bun apps/electron/e2e/run-manifest-acceptance.ts
→ PASS,全部断言通过,≈1m18s

bun node_modules/typescript/bin/tsc --noEmit   # 根 tsconfig,覆盖 core/shared/ui/electron
→ exit 0,0 error

与改动对应的验证(核心行为)

  • packages/shared/src/kernel/run-manifest.test.ts(13 项):captureRunManifest 快照与不修改入参、默认 tools、secret 不入 manifest;redactManifestSecrets 递归脱敏且保持结构;diffRunManifests 忽略身份锚点、按工具名比对、分组标记;exportRunManifest 输出脱敏 JSON;hashManifestInput 稳定。
  • packages/shared/src/kernel/run-manager.test.ts 新增 8 项:manifest 采集并持久化;同一 session 内两次 run 的 manifest 互不覆盖(不可变性);无上下文时默认 tools: [] 与 runtimeMode: 'pi';预算解析结果(含 ceiling clamp 与 clamped)写入清单;未配置预算时不写 budget;宿主基础上下文与单次调用 override 合并;基础上下文抛错时降级到 fallback 而不阻塞 run。
  • packages/shared/src/evaluation/langfuse/metadata.test.ts(4 项):manifestToLangfuseMetadata 使 folioRunId === manifest.runId 并携带 model/provider/strategy/promptVersion;评测清单派生出 run_kind:evaluation 与 gold_case: / dataset: tag;回读值优先且 requested 值不会被误读为实际模型。
  • packages/shared/src/research/service.test.ts 新增 2 项:Deep Research 清单记录 research strategy id(优先于宿主默认 agent 工作流)与解析后的预算(含 clamped);策略版本内容寻址——不同策略的计划产出不同版本,避免清单 diff 把行为不同的策略误判为相同。
  • packages/shared/src/evaluation/experiment-service.test.ts 新增 1 项:评测运行把 gold case / dataset version 传给 kernel 的清单上下文。
  • packages/ui/src/components/kernel/RunInfoPanel.test.tsx(3 项):manifest 人可读渲染 + 脱敏 JSON 导出内容;结构化 diff 的 before → after 与分组标记;无差异时渲染为「一致」。

已知失败 / Baseline(如有)

  • 本 PR 相关的 focused tests、typecheck 与现场验收 全绿。

  • 首轮 CI 的 Focused tests 曾失败一次:kernelHost 单测用 mock.module('@finagent/shared', ...) 提供了部分导出,本 PR 让 kernelHost 新增了 diffRunManifests / exportRunManifest 导入后,mock 需要同步补齐,否则 bun 的 ESM 链接会整文件报 Export named ... not found。已修复并复跑通过;本次新增的 manifestToLangfuseMetadata 导入也已同步补进该 mock。

  • langfuse.test.ts 的 1 项失败为本机既有失败:已 stash 本 PR 改动在干净基线上复现同一失败;CI 中该文件通过。属本地环境差异,非代码问题。

  • apps/electron/e2e/ 下 2 个既有脚本的类型错误(quote-provenance-live-acceptance.ts:192 的 union payload、research-recovery-kernel.ts:139 的 ReadableStream 异步迭代)在 main 上即存在,且仓库根 tsconfig.json 只 include packages/*/src、apps/*/src,e2e 目录不参与 CI typecheck。本 PR 新增的两个 e2e 脚本在同等设置下类型干净。

  • 修复一个既有 flake:CI 的 Focused tests 曾在 durable research recovery > fails safely and preserves a version checkpoint 报 Run did not settle。该用例用迭代数(400 × 5ms)作轮询预算,在饱和的 CI runner 上会在健康运行 settle 之前耗尽迭代数。本仓库在 service.test.ts 里已为同类问题做过「迭代预算 → 墙钟预算」的修复并附注释,此处把同一修复应用到 recovery.test.ts(2s → 5s 墙钟)。同一 commit 的 advisory 全量跑与本地复跑均通过,指向时序 flake 而非确定性失败。

  • 环境基线说明(与本 PR 无关):Windows 上 bun 的符号链接创建受限(无开发者模式),bun install 会静默不建工作区链接,导致 signal-exit、zod(被解析成 4.4.3,而 lockfile 是 3.25.76)等包解析到错误版本。本机用 directory junction 复刻 hoisted node_modules 后即恢复;这是环境问题,不是代码问题,origin/main 上同样存在。

  • 已提供实际测试命令与 pass/fail 结果

  • 已说明测试环境

  • 如果存在已知 baseline / 环境失败,已提供 main 对照或说明

  • 核心改动已有对应 focused test / smoke / integration 验证

UI 截图(仅可见 UI 变化时必填)

本 PR 含可见 UI 变化(Deep Research 运行历史新增「运行信息」入口、Run Info 弹窗、勾选两次运行的对比视图)。

贡献者环境无法启动浏览器 / Electron(Mojo IPC channel 创建与管道子进程创建均 access denied / EPERM),无法提供真实窗口截图。作为替代,已用 renderToStaticMarkup 把真正发布的 ManifestView / DiffView 组件渲染为静态 HTML,颜色取自应用自身的 index.css 设计令牌——它证明了视图的内容与布局,但不等于真实窗口的像素渲染:

说明:如需真实窗口截图,可由能在本机启动应用的一方补拍。

Scope / 后续

本 PR 交付 issue #21 的完整实现,两条真实运行验收项均已跑通并提供可复现证据(见上)。

需要诚实说明的范围限制:

  1. 取数来源替换:Longbridge CLI 未安装在本机,验收经仓库自带的 fetcher 注入点改用 Yahoo Finance 公开 chart API(quote / kline / profile)与 NYT Business RSS(news)。跑的是生产能力的实现(校验/脱敏/摘要/溯源原样执行),只有原始 HTTP 源不同;verification.json 里以 substitutedFor 显式记录了该替换。其余 12 个能力无公开等价源,用 notWired() 显式抛错,因此两次运行均为 partial。
  2. 未覆盖层:本次现场验收走的是 kernel / research-service 持久化路径,不包含 Electron IPC 层与桌面 Pi adapter——那部分的清单装配由 kernelHost 单测覆盖。这不是打包测试。
  3. UI 证据为静态 HTML 而非真实窗口截图(原因见上)。

新增 RunManifest / RunManifestContext / RunManifestDiff 等类型,覆盖 provider/model、model params、prompt hash、strategy、enabled tools、search/retrieval、feature flags、app/build/git 版本与 eval 上下文;并在 Run 与 ResearchRunSummary 上挂载可选的 manifest 字段。
captureRunManifest 在 run 创建时快照运行配置,写入后不再被当前全局设置覆盖;redactManifestSecrets 递归替换 secret 形状的键;diffRunManifests 按 model/prompt/tools/config/versions 分组产出结构化差异;exportRunManifest 输出脱敏 JSON。RunManager 在 prepareRun 写入 manifest,Deep Research 服务同样在启动时记录,AgentKernel 传入 runtimeMode。附 run-manifest 单测与 RunManager 不可变持久化单测。
kernelHost 在 startRun 前装配 manifest 上下文(runtime mode、provider/model/params、prompt hash、enabled tools、search routing、feature flags、app/build/git 版本),并新增 getRunManifest / exportRunManifest / compareRunManifests / getResearchManifest / compareResearchManifests。main 注册 runs:getManifest|exportManifest|compareManifests 与 research:getManifest|compareManifests,preload 与 renderer client 同步暴露(diff 在主进程计算,渲染进程不引入 @finagent/shared)。
RunInfoPanel 以人可读形式展示 manifest(运行时模式、模型/参数、prompt hash、策略、工具、检索、版本、feature flags),提供脱敏 JSON 复制导出,并渲染结构化 diff;ResearchPanel 运行历史新增“运行信息”入口与勾选两次运行的对比入口。client 暴露 kernel/research 的 getManifest/compareManifests,diff 由主进程计算。附 RunInfoPanel 渲染单测。
kernelHost 新增从 @finagent/shared 导入 diffRunManifests/exportRunManifest 后,该单测的部分 mock 需要同步提供这两个导出,否则 bun 的 ESM 链接会报 "Export named ... not found" 而整文件失败。
- RunManifestBudget 增加 effective/clamped:记录 run 实际遵守的预算上限,
  以及被系统天花板下调的键,避免事后重算当时的解析规则(与 helsome#17 对接)。
- 新增 RunManifestContextPatch:每次调用可只补充自己负责的字段(如评测的
  gold case),其余由宿主提供的基础上下文填充。
- 新增 AGENT_WORKFLOW_ID:Copilot agent 路径的工作流标识,与 Deep Research
  的 StrategyId 一起落到 RunManifestStrategy.id。
- RunManager 新增 getRunManifestContext:由宿主提供基础上下文,使所有运行
  入口(含评测直接调用 kernel 的路径)都落一份真实清单,而不是空 fallback。
  单次调用的 manifestContext 作为 patch 叠加其上。
- 预算在 run 落盘前解析(defaults → overrides → ceiling),并把
  defaults/ceiling/overrides/effective/clamped 一并写入清单(与 helsome#17 对接);
  非法预算在写入前 fail loud,不再留下半创建的 run。
- 评测运行把 gold case / dataset version 写进清单(与 helsome#15 对接)。
- Deep Research 清单记录 research strategy id(优先于宿主的默认 agent 工作流)
  与解析后的 research 预算。
- 新增 manifestToLangfuseMetadata:由清单派生 Langfuse trace metadata,
  使 trace 与本地清单共用同一 folioRunId 与同一份 model/provider/strategy/
  prompt 版本记录(与 helsome#14 对齐);回读确认值优先,requested 值单独保留。
- AgentKernel 注入 getRunManifestContext,Copilot run 与评测 run 共用同一份
  上下文装配,不再需要调用点各自传 manifestContext。
- 清单记录 agent 工作流身份(AGENT_WORKFLOW_ID + 随包版本)。
- agent / Deep Research 的 Langfuse trace metadata 改由 manifestToLangfuseMetadata
  派生,trace id 与清单 runId 同源,trace 也带上 model/provider/strategy/
  prompt version,便于与历史清单互相定位。
研究策略没有自带的 version 字段,此前的清单会退而继承宿主的应用版本
(与 appVersion/buildVersion 重复)。改为记录「策略解析出的能力计划」
的 SHA-256:同一策略稳定、计划一变即变,因此两次行为不同的运行绝不会
共用同一个策略版本,清单 diff 也就不会把不同策略误判为相同。
新增一个独立的现场验收脚本,只用真实组件、不含 fixture:

- 真实 AgentKernel + ResearchService(生产研究路径);
- 真实 DeepSeek HTTP 流式调用,配置的模型就是实际请求的模型;
- 通过仓库自带的 capability fetcher 注入点(createFullRegistry)走公网
  取数,能力体(校验、脱敏、摘要、溯源)原样执行;
- 另起一个全新进程做「重启后读回」,其当前全局配置与历史清单故意不同,
  证明历史运行仍报告它诞生时的配置。

另附一个 SSR 渲染脚本,用真正发布的 ManifestView/DiffView 组件与设计
令牌产出 Run Info 的 HTML/文本证据(本机无法拉起 Electron 截图)。
按仓库既有验收约定(docs/acceptance/<主题>/ + docs/<主题>-test-report.md)
提交现场验收的证据与报告。

验收覆盖 issue helsome#21 中 fixture 无法满足的两项:
1. 两次真实生产 Deep Research 运行,刻意改动 model 与 budget,diff 精确反映;
2. 全新进程(当前全局模型与历史不同)重启后读回历史 Run Info,配置未被覆盖。

取数经仓库自带的 fetcher 注入点改用公开源(Yahoo Finance chart API + NYT
Business RSS)替代未安装的 Longbridge CLI,报告与 verification.json 均显式记录
该替换,不声称使用过 Longbridge。
durable research recovery 的 settled() 用固定 400 × 5ms 迭代数作轮询
预算;在饱和的 CI runner 上,每轮都要经磁盘读 run 记录,会在健康运行
settle 之前耗尽迭代数,表现为 'Run did not settle'。service.test.ts 里
waitForTerminal 已为同类问题改用墙钟预算(5s)并附注释,此处对齐同一
做法。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Reproducibility] Persist immutable run manifests for Agent / Deep Research

1 participant