synergyspec-selfevolving 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +31 -18
  2. package/dist/commands/learn.d.ts +12 -1
  3. package/dist/commands/learn.js +158 -11
  4. package/dist/commands/self-evolution-episode.d.ts +177 -0
  5. package/dist/commands/self-evolution-episode.js +431 -0
  6. package/dist/commands/self-evolution.d.ts +12 -190
  7. package/dist/commands/self-evolution.js +114 -866
  8. package/dist/core/archive.d.ts +0 -1
  9. package/dist/core/archive.js +0 -58
  10. package/dist/core/artifact-graph/instruction-loader.d.ts +2 -4
  11. package/dist/core/artifact-graph/instruction-loader.js +3 -31
  12. package/dist/core/fitness/loss.d.ts +5 -5
  13. package/dist/core/fitness/loss.js +4 -4
  14. package/dist/core/fitness/test-failures.js +10 -2
  15. package/dist/core/project-config.d.ts +19 -0
  16. package/dist/core/project-config.js +96 -0
  17. package/dist/core/self-evolution/candidate-fitness.d.ts +23 -1
  18. package/dist/core/self-evolution/candidate-fitness.js +31 -5
  19. package/dist/core/self-evolution/candidates.d.ts +0 -9
  20. package/dist/core/self-evolution/critic-agent.d.ts +192 -0
  21. package/dist/core/self-evolution/critic-agent.js +568 -0
  22. package/dist/core/self-evolution/edits-contract.d.ts +53 -0
  23. package/dist/core/self-evolution/edits-contract.js +89 -0
  24. package/dist/core/self-evolution/episode-orchestrator.d.ts +234 -0
  25. package/dist/core/self-evolution/episode-orchestrator.js +681 -0
  26. package/dist/core/self-evolution/episode-store.d.ts +266 -0
  27. package/dist/core/self-evolution/episode-store.js +573 -0
  28. package/dist/core/self-evolution/evolution-switches.d.ts +1 -1
  29. package/dist/core/self-evolution/evolution-switches.js +5 -10
  30. package/dist/core/self-evolution/evolving-agent.d.ts +208 -0
  31. package/dist/core/self-evolution/evolving-agent.js +535 -0
  32. package/dist/core/self-evolution/host-harness.d.ts +14 -15
  33. package/dist/core/self-evolution/host-harness.js +48 -23
  34. package/dist/core/self-evolution/index.d.ts +11 -6
  35. package/dist/core/self-evolution/index.js +20 -6
  36. package/dist/core/self-evolution/line-diff.d.ts +60 -0
  37. package/dist/core/self-evolution/line-diff.js +130 -0
  38. package/dist/core/self-evolution/policy/fs-safe.d.ts +19 -0
  39. package/dist/core/self-evolution/policy/fs-safe.js +89 -0
  40. package/dist/core/self-evolution/policy/index.d.ts +13 -0
  41. package/dist/core/self-evolution/policy/index.js +13 -0
  42. package/dist/core/self-evolution/policy/policy-store.d.ts +217 -0
  43. package/dist/core/self-evolution/policy/policy-store.js +774 -0
  44. package/dist/core/self-evolution/policy/prediction-reconcile.d.ts +54 -0
  45. package/dist/core/self-evolution/policy/prediction-reconcile.js +191 -0
  46. package/dist/core/self-evolution/policy/reject-buffer.d.ts +55 -0
  47. package/dist/core/self-evolution/policy/reject-buffer.js +170 -0
  48. package/dist/core/self-evolution/promote.d.ts +1 -1
  49. package/dist/core/self-evolution/promote.js +6 -33
  50. package/dist/core/self-evolution/promotion.js +1 -2
  51. package/dist/core/self-evolution/reward-agent.d.ts +379 -0
  52. package/dist/core/self-evolution/reward-agent.js +940 -0
  53. package/dist/core/self-evolution/reward-aggregator.d.ts +59 -0
  54. package/dist/core/self-evolution/reward-aggregator.js +262 -0
  55. package/dist/core/self-evolution/scope-gate.d.ts +66 -0
  56. package/dist/core/self-evolution/scope-gate.js +107 -0
  57. package/dist/core/self-evolution/success-channel.js +2 -2
  58. package/dist/core/self-evolution/tamper-check.d.ts +24 -0
  59. package/dist/core/self-evolution/tamper-check.js +236 -0
  60. package/dist/core/self-evolution/tool-evolution.js +2 -13
  61. package/dist/core/self-evolution/verdict.d.ts +8 -5
  62. package/dist/core/self-evolution/verdict.js +4 -7
  63. package/dist/core/templates/workflows/gen-tests.js +1 -1
  64. package/dist/core/templates/workflows/learn.d.ts +3 -2
  65. package/dist/core/templates/workflows/learn.js +21 -18
  66. package/dist/core/templates/workflows/self-evolving.d.ts +6 -4
  67. package/dist/core/templates/workflows/self-evolving.js +62 -172
  68. package/dist/core/trajectory/scrub.d.ts +27 -0
  69. package/dist/core/trajectory/scrub.js +79 -0
  70. package/dist/core/trajectory/skeleton.d.ts +27 -1
  71. package/dist/core/trajectory/skeleton.js +152 -8
  72. package/dist/dashboard/data.d.ts +25 -51
  73. package/dist/dashboard/data.js +68 -180
  74. package/dist/dashboard/react-client.js +458 -503
  75. package/dist/dashboard/react-styles.js +3 -3
  76. package/dist/dashboard/server.js +23 -17
  77. package/dist/ui/ascii-patterns.d.ts +7 -15
  78. package/dist/ui/ascii-patterns.js +123 -54
  79. package/dist/ui/welcome-screen.d.ts +0 -14
  80. package/dist/ui/welcome-screen.js +16 -35
  81. package/package.json +1 -1
  82. package/dist/core/self-evolution/ga-selection.d.ts +0 -94
  83. package/dist/core/self-evolution/ga-selection.js +0 -153
  84. package/dist/core/self-evolution/proposer-agent.d.ts +0 -182
  85. package/dist/core/self-evolution/proposer-agent.js +0 -326
  86. package/dist/core/self-evolution/replay-runner.d.ts +0 -100
  87. package/dist/core/self-evolution/replay-runner.js +0 -170
  88. package/dist/core/self-evolution/replay.d.ts +0 -45
  89. package/dist/core/self-evolution/replay.js +0 -56
  90. package/dist/core/self-evolution/template-variants.d.ts +0 -62
  91. package/dist/core/self-evolution/template-variants.js +0 -171
  92. package/dist/core/self-evolution/trajectory.d.ts +0 -65
  93. package/dist/core/self-evolution/trajectory.js +0 -185
@@ -14,8 +14,9 @@ export const dashboardClientScript = String.raw `
14
14
  changes: '/api/changes',
15
15
  cliHistory: '/api/cli-history?limit=200',
16
16
  agentInterface: '/api/agent-interface',
17
- evolveRuns: '/api/evolve/runs',
18
- evolveArchive: '/api/evolve/archive',
17
+ episodes: '/api/episodes',
18
+ policyLedger: '/api/policy/ledger',
19
+ rejectBuffer: '/api/reject-buffer',
19
20
  architecture: '/api/architecture',
20
21
  history: '/api/history',
21
22
  codeAgent: '/api/code-agent',
@@ -90,18 +91,18 @@ export const dashboardClientScript = String.raw `
90
91
  en: 'Verification finds the gaps. Learn proposes durable rules. With your approval, those rules become the next change\'s pre-flight check.',
91
92
  zh: '校验定位缺口;学习阶段提出可固化的规则;经你审核后,这些规则成为下一次变更的飞行前检查。'
92
93
  },
93
- 'intro.selfevo.col3.title': { en: 'Lab promotes carefully', zh: '实验室谨慎晋升' },
94
+ 'intro.selfevo.col3.title': { en: 'Loop promotes carefully', zh: '闭环谨慎晋升' },
94
95
  'intro.selfevo.col3.body': {
95
- en: 'The Evaluation Lab runs benchmarks, safety gates, and lineage-tracked variants. Only changes that survive its gates are merged into the main workflow.',
96
- zh: '评估实验室跑基准测试、安全门、谱系追踪的变体。只有通过全部关卡的变更才被合并到主工作流。'
96
+ en: 'The Self-Evolution Loop scores each change by graded advantage against a baseline arm, and rolls back before evolving on a bad result. Only changes with a positive advantage are kept.',
97
+ zh: '自演进闭环以分级优势(advantage)对照基线臂为每次改动打分,结果不佳时先回滚再演进。只有优势为正的改动才会被保留。'
97
98
  },
98
99
  'intro.selfevo.diag.gen': { en: 'GENERATION', zh: '代' },
99
100
  'intro.selfevo.diag.change': { en: 'CHANGE', zh: '变更' },
100
101
  'intro.selfevo.diag.memory': { en: 'MEMORY ACCUMULATES', zh: '记忆累积' },
101
102
  'intro.selfevo.diag.leverage': { en: 'LEVERAGE PER CHANGE GROWS', zh: '单次杠杆增长' },
102
- 'intro.selfevo.stat.generations': { en: 'Archive generations', zh: '归档代数' },
103
+ 'intro.selfevo.stat.generations': { en: 'Policy versions', zh: '策略版本' },
103
104
  'intro.selfevo.stat.entries': { en: 'Memory entries', zh: '记忆条目' },
104
- 'intro.selfevo.stat.candidates': { en: 'Promotable candidates', zh: '可晋升候选' },
105
+ 'intro.selfevo.stat.candidates': { en: 'Kept episodes', zh: '已保留回合' },
105
106
 
106
107
  'intro.kpi.eyebrow': { en: 'Live signals', zh: '实时信号' },
107
108
  'intro.kpi.title': { en: 'Where this workspace is, right now', zh: '此刻这个工作区在哪一步' },
@@ -190,9 +191,9 @@ export const dashboardClientScript = String.raw `
190
191
  'intro.boundary.b2.label': { en: 'Reviewed learning', zh: '经审学习' },
191
192
  'intro.boundary.b2.title': { en: 'learn previews, you approve', zh: 'learn 仅预览 · 你来审批' },
192
193
  'intro.boundary.b2.body': { en: 'Memory candidates surface in the agent. Nothing silently rewrites prompts, templates, or skills.', zh: '记忆候选在智能体内浮现。没有任何东西会静默改写 prompt / 模板 / 技能。' },
193
- 'intro.boundary.b3.label': { en: 'Evaluation Lab', zh: '评估实验室' },
194
+ 'intro.boundary.b3.label': { en: 'Self-Evolution Loop', zh: '自演进闭环' },
194
195
  'intro.boundary.b3.title': { en: 'maintainer-only self-evolution', zh: '仅维护者的自演进通道' },
195
- 'intro.boundary.b3.body': { en: 'Benchmarks, safety gates, candidate variants, and archives — kept fully out of the normal loop.', zh: '基准测试 · 安全门 · 候选变体 · 归档——完全独立于日常循环。' },
196
+ 'intro.boundary.b3.body': { en: 'Two-arm forward, graded advantage, and rollback-before-evolution — kept fully out of the normal loop.', zh: '双臂前向 · 分级优势 · 先回滚再演进——完全独立于日常循环。' },
196
197
 
197
198
  'intro.commands.eyebrow': { en: 'Command surface', zh: '命令面' },
198
199
  'intro.commands.title': { en: 'How to act, in any agent', zh: '在任何智能体里如何下指令' },
@@ -221,9 +222,9 @@ export const dashboardClientScript = String.raw `
221
222
  'overview.hero.body': { en: 'Ask the Code Agent to check, explain, or modify code in this repo.', zh: '让 Code Agent 检查、解释或修改这个仓库里的代码。' },
222
223
  'overview.hero.chip.changes': { en: 'explore active changes', zh: '了解活跃变更' },
223
224
  'overview.hero.chip.trace': { en: 'view agent trace', zh: '查看智能体轨迹' },
224
- 'overview.hero.chip.runs': { en: 'study evolve runs', zh: '研究自演进运行' },
225
+ 'overview.hero.chip.runs': { en: 'study episodes', zh: '研究演进回合' },
225
226
  'overview.eyebrow': { en: 'Latest dynamics', zh: '最新动态' },
226
- 'overview.subhead': { en: 'A live control plane for specs, agent traces, CLI history, and self-evolution runs.', zh: 'spec / 智能体轨迹 / CLI 历史 / 自演进运行的实时控制面板。' },
227
+ 'overview.subhead': { en: 'A live control plane for specs, agent traces, CLI history, and self-evolution episodes.', zh: 'spec / 智能体轨迹 / CLI 历史 / 自演进回合的实时控制面板。' },
227
228
  'overview.metric.active': { en: 'Active changes', zh: '进行中变更' },
228
229
  'overview.metric.completed': { en: 'Completed specs', zh: '已完成 spec' },
229
230
  'overview.metric.traceEvents': { en: 'Trace events', zh: '轨迹事件' },
@@ -235,22 +236,22 @@ export const dashboardClientScript = String.raw `
235
236
  'overview.delta.streamConnected': { en: 'stream connected', zh: '数据流已连接' },
236
237
  'overview.delta.streamIdle': { en: 'stream idle', zh: '数据流待机' },
237
238
  'overview.delta.recentCli': { en: '{n} recent CLI events', zh: '近期 {n} 条 CLI 事件' },
238
- 'overview.delta.noRuns': { en: 'no runs', zh: '暂无运行' },
239
+ 'overview.delta.noEpisodes': { en: 'no episodes', zh: '暂无回合' },
239
240
  'overview.section.recentChanges': { en: 'Recent changes', zh: '近期变更' },
240
241
  'overview.section.agentTrace': { en: 'Agent trace', zh: '智能体轨迹' },
241
242
  'overview.section.toolDistribution': { en: 'Tool distribution', zh: '工具分布' },
242
243
  'overview.section.cliHistory': { en: 'CLI history', zh: 'CLI 历史' },
243
- 'overview.section.evoRuns': { en: 'Evolution runs', zh: '自演进运行' },
244
+ 'overview.section.evoRuns': { en: 'Self-evolution episodes', zh: '自演进回合' },
244
245
  'overview.total': { en: '{n} total', zh: '共 {n} 项' },
245
246
  'overview.events': { en: '{n} events', zh: '{n} 条事件' },
246
- 'overview.empty.noRuns': { en: 'No evolution runs', zh: '暂无自演进运行' },
247
- 'overview.empty.noRunsHint': { en: 'evolve/runs is empty', zh: 'evolve/runs 为空' },
247
+ 'overview.empty.noRuns': { en: 'No episodes', zh: '暂无回合' },
248
+ 'overview.empty.noRunsHint': { en: 'self-evolution/episodes is empty', zh: 'self-evolution/episodes 为空' },
248
249
 
249
250
  'interface.eyebrow': { en: 'Agent-native interface', zh: '智能体原生界面' },
250
251
  'interface.title': { en: 'User interface for Claude Code, Codex, and OpenCode', zh: '面向 Claude Code · Codex · OpenCode 的用户界面' },
251
252
  'interface.lead': { en: 'The user talks to slash commands in the coding agent. The CLI stays underneath as skill plumbing for status, artifact creation, validation, and archive operations.', zh: '用户通过编码智能体里的斜杠命令交互。CLI 作为底层管线,承担状态查询、文件创建、校验与归档。' },
252
253
  'interface.hero.title': { en: 'One product surface, tool-native command syntax.', zh: '一套产品界面 · 各家工具的原生命令语法。' },
253
- 'interface.hero.body': { en: 'SynergySpec-SelfEvolving should feel like an agent workflow: plan the change, build it, review it, learn from it, and archive it. Advanced self-evolution remains in the Evaluation Lab.', zh: 'SynergySpec-SelfEvolving 应当像智能体工作流:规划变更 · 构建 · 审阅 · 学习 · 归档。进阶的自演进保留在评估实验室。' },
254
+ 'interface.hero.body': { en: 'SynergySpec-SelfEvolving should feel like an agent workflow: plan the change, build it, review it, learn from it, and archive it. Advanced self-evolution remains in the Self-Evolution Loop.', zh: 'SynergySpec-SelfEvolving 应当像智能体工作流:规划变更 · 构建 · 审阅 · 学习 · 归档。进阶的自演进保留在自演进闭环。' },
254
255
  'interface.chip.openChanges': { en: 'open changes', zh: '未结变更' },
255
256
  'interface.chip.readyApply': { en: 'ready to apply', zh: '可实施' },
256
257
  'interface.chip.readyReview': { en: 'ready to review', zh: '可审阅' },
@@ -308,18 +309,19 @@ export const dashboardClientScript = String.raw `
308
309
 
309
310
  'evolve.eyebrow': { en: 'Self-evolution console', zh: '自演进控制台' },
310
311
  'evolve.title': { en: 'SynergySpec-SelfEvolving', zh: 'SynergySpec-SelfEvolving' },
311
- 'evolve.lead': { en: 'Benchmark runs, blockers, candidate fitness, and archive lineage in one console.', zh: '基准测试运行 · 阻塞项 · 候选适应度 · 归档谱系,全在一个控制台。' },
312
- 'evolve.chip.runs': { en: 'runs', zh: '运行次数' },
313
- 'evolve.chip.archive': { en: 'archive', zh: '归档' },
314
- 'evolve.metric.latestVerdict': { en: 'Latest verdict', zh: '最新判定' },
315
- 'evolve.metric.candidates': { en: 'Valid candidates', zh: '有效候选' },
316
- 'evolve.metric.candidates.detailEmpty': { en: 'no promotable evidence', zh: '暂无可晋升证据' },
317
- 'evolve.metric.candidates.detail': { en: '{p} measured fitness', zh: '已测适应度 {p}' },
318
- 'evolve.metric.waitingRuns': { en: 'waiting for runs', zh: '等待运行' },
319
- 'evolve.section.history': { en: 'Evolution run history', zh: '自演进运行历史' },
320
- 'evolve.section.lineage': { en: 'Archive lineage', zh: '归档谱系' },
321
- 'evolve.runs.label': { en: '{n} runs', zh: '{n} 次运行' },
322
- 'evolve.variants.label': { en: '{n} variants', zh: '{n} 个变体' },
312
+ 'evolve.lead': { en: 'Episodes, two-arm forward advantage, policy version ledger, and the reject-buffer in one console.', zh: '回合 · 双臂前向优势 · 策略版本账本 · 否决缓冲,全在一个控制台。' },
313
+ 'evolve.chip.episodes': { en: 'episodes', zh: '回合数' },
314
+ 'evolve.chip.version': { en: 'policy version', zh: '策略版本' },
315
+ 'evolve.metric.latestStage': { en: 'Latest stage', zh: '最新阶段' },
316
+ 'evolve.metric.candidates': { en: 'Kept episodes', zh: '已保留回合' },
317
+ 'evolve.metric.candidates.detailEmpty': { en: 'no kept episodes', zh: '暂无已保留回合' },
318
+ 'evolve.metric.candidates.detail': { en: '{p} mean advantage', zh: '平均优势 {p}' },
319
+ 'evolve.metric.waitingEpisodes': { en: 'waiting for episodes', zh: '等待回合' },
320
+ 'evolve.section.history': { en: 'Evolution episodes', zh: '自演进回合' },
321
+ 'evolve.section.lineage': { en: 'Policy version ledger', zh: '策略版本账本' },
322
+ 'evolve.section.rejectBuffer': { en: 'Reject-buffer', zh: '否决缓冲' },
323
+ 'evolve.episodes.label': { en: '{n} episodes', zh: '{n} 个回合' },
324
+ 'evolve.ledger.label': { en: 'v{n}', zh: '第 {n} 版' },
323
325
 
324
326
  'learn.eyebrow': { en: 'Skill · review & extract', zh: '技能 · 审阅与提炼' },
325
327
  'learn.title': { en: 'Learn — close the loop after verify, before archive', zh: 'Learn — 校验之后、归档之前的闭环' },
@@ -566,16 +568,16 @@ export const dashboardClientScript = String.raw `
566
568
  'common.noChange': { en: 'No change', zh: '无变更' },
567
569
  'common.openDetail': { en: 'Open detail', zh: '查看详情' },
568
570
  'common.openLatest': { en: 'Open latest', zh: '打开最新' },
569
- 'common.noRun': { en: 'No run', zh: '无运行' },
571
+ 'common.noRun': { en: 'No episode', zh: '无回合' },
570
572
  'common.copyCommand': { en: 'Copy command', zh: '复制命令' },
571
573
  'common.copied': { en: 'Copied', zh: '已复制' },
572
574
  'common.copyFailed': { en: 'Copy failed', zh: '复制失败' },
573
575
  'common.nextAction': { en: 'Next action', zh: '下一步操作' },
574
- 'common.nextAction.runLab': { en: 'Run the lab to create benchmark evidence.', zh: '运行实验室以生成基准证据。' },
575
- 'common.nextAction.inspect': { en: 'Inspect the latest benchmark failure.', zh: '检查最新一次基准失败。' },
576
+ 'common.nextAction.runLab': { en: 'Run an episode to create graded evidence.', zh: '运行一个回合以生成评级证据。' },
577
+ 'common.nextAction.inspect': { en: 'Inspect the latest rolled-back episode.', zh: '检查最近被回滚的回合。' },
576
578
  'common.loading': { en: 'Loading dashboard data', zh: '加载控制台数据中' },
577
- 'common.benchmarkDefault': { en: 'default benchmark', zh: '默认基准' },
578
- 'common.benchmark': { en: 'benchmark {id}', zh: '基准 {id}' },
579
+ 'common.targetDefault': { en: 'default target', zh: '默认目标' },
580
+ 'common.target': { en: 'target {id}', zh: '目标 {id}' },
579
581
 
580
582
  'nav.design': { en: 'Design', zh: '设计' },
581
583
 
@@ -713,8 +715,8 @@ export const dashboardClientScript = String.raw `
713
715
  'arch.metric.surfaces.delta': { en: '{n} live', zh: '{n} 个在线' },
714
716
  'arch.metric.traceEvents': { en: 'Trace events', zh: '轨迹事件' },
715
717
  'arch.metric.traceEvents.delta': { en: '{n} decisions', zh: '{n} 项决策' },
716
- 'arch.metric.runs': { en: 'Runs', zh: '运行' },
717
- 'arch.metric.runs.delta': { en: 'no verdict yet', zh: '尚无判定' },
718
+ 'arch.metric.runs': { en: 'Episodes', zh: '回合' },
719
+ 'arch.metric.runs.delta': { en: 'no stage yet', zh: '尚无阶段' },
718
720
 
719
721
  'arch.model.eyebrow': { en: 'The model', zh: '模型' },
720
722
  'arch.model.title': { en: '4+1 mapped onto this workspace', zh: '4+1 映射到本工作区' },
@@ -726,7 +728,7 @@ export const dashboardClientScript = String.raw `
726
728
 
727
729
  'arch.process.tag': { en: 'Process view', zh: '过程视图' },
728
730
  'arch.process.title': { en: 'Runtime loop', zh: '运行时循环' },
729
- 'arch.process.lead': { en: 'What happens at runtime: trace events, recorded decisions, benchmark runs, and the tools the agent actually exercises.', zh: '运行时发生了什么:轨迹事件、记录的决策、基准运行,以及智能体实际调用的工具。' },
731
+ 'arch.process.lead': { en: 'What happens at runtime: trace events, recorded decisions, test runs, and the tools the agent actually exercises.', zh: '运行时发生了什么:轨迹事件、记录的决策、测试运行,以及智能体实际调用的工具。' },
730
732
 
731
733
  'arch.development.tag': { en: 'Development view', zh: '开发视图' },
732
734
  'arch.development.title': { en: 'Module structure', zh: '模块结构' },
@@ -833,72 +835,72 @@ export const dashboardClientScript = String.raw `
833
835
 
834
836
  const evolutionPhases = [
835
837
  {
836
- key: 'observe',
838
+ key: 'forward',
837
839
  icon: '01',
838
- title: 'Observe',
839
- detail: 'Read traces, changes, task outcomes, cost, and runtime signals.',
840
- evolves: 'Inputs and pressure map',
841
- evidence: ['Trace history', 'Run summaries', 'Archive lineage']
840
+ title: 'Two-arm forward',
841
+ detail: 'Run the main agent (vN+1) alongside the critic baseline (vN) on the same task.',
842
+ evolves: 'Paired episode trajectory',
843
+ evidence: ['Main-arm trace', 'Baseline-arm trace', 'Run summaries']
842
844
  },
843
845
  {
844
- key: 'decide',
846
+ key: 'reward',
845
847
  icon: '02',
846
- title: 'Decide',
847
- detail: 'Think through the fitness signal, parent variant, and safest candidate path.',
848
- evolves: 'Selection policy',
849
- evidence: ['Fitness signal', 'Parent variant', 'Cost ceiling']
848
+ title: 'Reward / advantage',
849
+ detail: 'The reward agent scores each arm; advantage = reward(main) - reward(baseline).',
850
+ evolves: 'Graded advantage signal',
851
+ evidence: ['Reward(main)', 'Reward(baseline)', 'Advantage']
850
852
  },
851
853
  {
852
- key: 'act',
854
+ key: 'rollback',
853
855
  icon: '03',
854
- title: 'Act',
855
- detail: 'Snapshot the genome, propose children, and materialize bounded variants.',
856
- evolves: 'Candidate genome',
857
- evidence: ['Genome snapshot', 'Candidate path', 'Backup checkpoint']
856
+ title: 'Rollback first',
857
+ detail: 'On bad advantage, roll back the change and push it to the reject-buffer before any edit.',
858
+ evolves: 'Reject-buffer',
859
+ evidence: ['Advantage sign', 'Revert checkpoint', 'Reject-buffer entry']
858
860
  },
859
861
  {
860
- key: 'verify',
862
+ key: 'evolve',
861
863
  icon: '04',
862
- title: 'Verify',
863
- detail: 'Run safety gates, benchmark checks, checkpoints, and revert paths.',
864
- evolves: 'Trust boundary',
865
- evidence: ['Typecheck/lint', 'Benchmark verdict', 'Round-trip revert']
864
+ title: 'Evolving agent',
865
+ detail: 'On good advantage, the evolving agent makes one bounded edit (<= L) to the policy.',
866
+ evolves: 'Bounded policy edit',
867
+ evidence: ['Typecheck/lint', 'Edit budget L', 'Round-trip revert']
866
868
  },
867
869
  {
868
- key: 'learn',
870
+ key: 'writeback',
869
871
  icon: '05',
870
- title: 'Learn',
871
- detail: 'Archive reports, lineage, memory signals, and review evidence for future runs.',
872
- evolves: 'Institutional memory',
873
- evidence: ['JSON reports', 'Archive entry', 'Memory signal']
872
+ title: 'Write-back',
873
+ detail: 'Commit the kept edit and its verdict to the policy and the version ledger.',
874
+ evolves: 'Policy + ledger',
875
+ evidence: ['Policy diff', 'Version ledger', 'Verdict record']
874
876
  }
875
877
  ];
876
878
 
877
879
  const selfEvolvingConsoleRows = [
878
880
  {
879
- step: 'Observe',
880
- does: 'Read benchmark results, bounded history, archive lineage, and saved artifacts',
881
- evolves: 'Pressure map'
881
+ step: 'Two-arm forward',
882
+ does: 'Run the main agent (candidate vN+1) alongside the critic baseline (vN) on the same task',
883
+ evolves: 'Paired episode trajectory'
882
884
  },
883
885
  {
884
- step: 'Decide',
885
- does: 'Choose the fitness signal, parent variant, and candidate path',
886
- evolves: 'Selection policy'
886
+ step: 'Reward / advantage',
887
+ does: 'The reward agent scores each arm; advantage = reward(main) - reward(baseline)',
888
+ evolves: 'Graded advantage signal'
887
889
  },
888
890
  {
889
- step: 'Act',
890
- does: 'Snapshot the genome, propose children, then materialize with backups',
891
- evolves: 'Candidate genome'
891
+ step: 'Rollback first',
892
+ does: 'On bad advantage, roll back the change and push it to the reject-buffer before any edit',
893
+ evolves: 'Reject-buffer'
892
894
  },
893
895
  {
894
- step: 'Verify',
895
- does: 'Run safety gates, benchmark checks, round-trip revert, and checkpoints',
896
- evolves: 'Trust boundary'
896
+ step: 'Evolving agent',
897
+ does: 'On good advantage, the evolving agent makes one bounded edit (<= L) to the policy',
898
+ evolves: 'Bounded policy edit'
897
899
  },
898
900
  {
899
- step: 'Learn',
900
- does: 'Write JSON reports, lineage, memory signals, and review evidence',
901
- evolves: 'Archive memory'
901
+ step: 'Write-back',
902
+ does: 'Commit the kept edit and its verdict to the policy and the version ledger',
903
+ evolves: 'Policy + ledger'
902
904
  }
903
905
  ];
904
906
 
@@ -1015,8 +1017,8 @@ export const dashboardClientScript = String.raw `
1015
1017
  detail: 'Learn previews memory candidates. It does not silently rewrite prompts, templates, or skills.'
1016
1018
  },
1017
1019
  {
1018
- title: 'Evaluation Lab',
1019
- detail: 'Raw self-evolution is a maintainer workflow with benchmarks, safety gates, variants, and archives.'
1020
+ title: 'Self-Evolution Loop',
1021
+ detail: 'Raw self-evolution is a maintainer workflow: two-arm forward, graded advantage, and rollback-before-evolution.'
1020
1022
  }
1021
1023
  ];
1022
1024
 
@@ -1061,7 +1063,7 @@ export const dashboardClientScript = String.raw `
1061
1063
  try {
1062
1064
  if (console.groupCollapsed) {
1063
1065
  console.groupCollapsed('%cSynergySpec-SelfEvolving%c browser console map', titleStyle, mutedStyle);
1064
- console.log('%cPurpose%c Controlled Evaluation Lab run for SynergySpec-SelfEvolving itself; no automatic promotion.', labelStyle, bodyStyle);
1066
+ console.log('%cPurpose%c Self-evolution loop v2 (in-context RL) over SynergySpec-SelfEvolving itself; no automatic promotion.', labelStyle, bodyStyle);
1065
1067
  console.log('%cNormal loop%c propose -> apply -> verify -> learn -> archive stays the user workflow.', labelStyle, bodyStyle);
1066
1068
  if (console.table) {
1067
1069
  console.table(selfEvolvingConsoleRows);
@@ -1070,18 +1072,18 @@ export const dashboardClientScript = String.raw `
1070
1072
  console.log(String(index + 1) + '. ' + row.step + ' - ' + row.does + ' [' + row.evolves + ']');
1071
1073
  });
1072
1074
  }
1073
- console.log('%cEvolvable surface%c templates, archive memory, runtime memory, task decomposition, alignment verifier, tool guard, DGM harness.', labelStyle, bodyStyle);
1074
- console.log('%cBoundary%c Browser dashboard observes and explains. Promotion still requires benchmark-backed review.', labelStyle, bodyStyle);
1075
+ console.log('%cEvolvable surface%c templates, archive memory, runtime memory, task decomposition, alignment verifier, tool guard.', labelStyle, bodyStyle);
1076
+ console.log('%cBoundary%c Browser dashboard observes and explains. Evolution is gated by a graded advantage with rollback-before-evolution.', labelStyle, bodyStyle);
1075
1077
  if (console.groupEnd) console.groupEnd();
1076
1078
  return;
1077
1079
  }
1078
1080
 
1079
1081
  console.log([
1080
1082
  'SynergySpec-SelfEvolving',
1081
- 'Purpose: Controlled Evaluation Lab run for SynergySpec-SelfEvolving itself; no automatic promotion.',
1082
- 'Loop: Observe -> Decide -> Act -> Verify -> Learn.',
1083
- 'Surface: templates, archive memory, runtime memory, task decomposition, alignment verifier, tool guard, DGM harness.',
1084
- 'Boundary: browser dashboard observes and explains; promotion still requires benchmark-backed review.'
1083
+ 'Purpose: Self-evolution loop v2 (in-context RL) over SynergySpec-SelfEvolving itself; no automatic promotion.',
1084
+ 'Loop: two-arm forward -> reward agent scores (advantage) -> rollback-before-evolution -> evolving agent.',
1085
+ 'Surface: templates, archive memory, runtime memory, task decomposition, alignment verifier, tool guard.',
1086
+ 'Boundary: browser dashboard observes and explains; evolution is gated by a graded advantage with rollback-before-evolution.'
1085
1087
  ].join('\n'));
1086
1088
  } catch {}
1087
1089
  }
@@ -1243,67 +1245,55 @@ export const dashboardClientScript = String.raw `
1243
1245
  return 'muted';
1244
1246
  }
1245
1247
 
1246
- function runScore(run) {
1247
- if (!run) return 0;
1248
- if (run.passRate !== undefined) return normalizeRate(run.passRate);
1249
- const counts = safeObject(run.verdictCounts);
1250
- const entries = Object.entries(counts);
1251
- const total = entries.reduce(function (sum, entry) {
1252
- return sum + numberOr(entry[1], 0);
1253
- }, 0);
1254
- if (!total) return 0;
1255
- const passed = entries.reduce(function (sum, entry) {
1256
- const key = String(entry[0]).toLowerCase();
1257
- return key.includes('pass') || key.includes('success') ? sum + numberOr(entry[1], 0) : sum;
1258
- }, 0);
1259
- return passed / total;
1248
+ function episodeAdvantage(ep) {
1249
+ return ep && typeof ep.advantage === 'number' ? ep.advantage : null;
1260
1250
  }
1261
1251
 
1262
- function hasBenchmarkEvidence(run) {
1263
- if (!run) return false;
1264
- const counts = safeObject(run.verdictCounts);
1265
- return Object.keys(counts).some(function (key) { return numberOr(counts[key], 0) > 0; });
1252
+ function formatAdvantage(a) {
1253
+ return a == null ? 'n/a' : (a >= 0 ? '+' : '') + Number(a).toFixed(2);
1266
1254
  }
1267
1255
 
1268
- function measuredRuns(runs) {
1269
- return safeArray(runs).filter(hasBenchmarkEvidence);
1256
+ function advantageTone(ep) {
1257
+ const a = episodeAdvantage(ep);
1258
+ return a == null ? 'muted' : a > 0 ? 'good' : a < 0 ? 'bad' : 'info';
1270
1259
  }
1271
1260
 
1272
- function promotableRuns(runs) {
1273
- return measuredRuns(runs).filter(function (run) {
1274
- const verdict = runVerdict(run);
1275
- return verdict.tone !== 'bad' && runScore(run) > 0;
1276
- });
1261
+ function hasGradedAdvantage(ep) {
1262
+ return !!ep && ep.advantage != null;
1277
1263
  }
1278
1264
 
1279
- function primaryBlocker(run) {
1280
- if (!run) return 'No run data yet';
1281
- if (run.failureReasonSummary) return run.failureReasonSummary;
1282
- const verdict = runVerdict(run);
1283
- if (verdict.tone === 'bad') return verdict.label;
1284
- if (run.status === 'empty') return 'Run directory exists, but no summary or task evidence was found';
1285
- if (run.status === 'pending') return 'Run has files but no completed summary yet';
1286
- return 'No blocking issue detected';
1265
+ function gradedEpisodes(eps) {
1266
+ return safeArray(eps).filter(hasGradedAdvantage);
1287
1267
  }
1288
1268
 
1289
- function runVerdict(run) {
1290
- if (run && run.status === 'errored') return { label: 'errored', tone: 'bad' };
1291
- if (run && run.status === 'empty') return { label: 'empty', tone: 'muted' };
1292
- const counts = safeObject(run && run.verdictCounts);
1293
- const entries = Object.entries(counts).filter(function (entry) {
1294
- return numberOr(entry[1], 0) > 0;
1269
+ function keptEpisodes(eps) {
1270
+ return safeArray(eps).filter(function (ep) {
1271
+ return ep.stage === 'kept' || ep.stage === 'evolved';
1295
1272
  });
1296
- if (!entries.length) return { label: 'pending', tone: 'muted' };
1297
- const bad = entries.some(function (entry) { return isFailure(entry[0]); });
1298
- const pass = entries.some(function (entry) { return String(entry[0]).toLowerCase().includes('pass'); });
1299
- return {
1300
- label: entries.map(function (entry) { return entry[0] + ' ' + entry[1]; }).join(', '),
1301
- tone: bad ? 'bad' : pass ? 'good' : 'info'
1302
- };
1303
1273
  }
1304
1274
 
1305
- function latestRunTime(run) {
1306
- return (run && (run.finishedAt || run.startedAt)) || '';
1275
+ function episodeBlocker(ep) {
1276
+ if (!ep) return 'No episode yet';
1277
+ if (ep.stage === 'evolution-refused') return ep.baselineSkippedReason || 'Evolution refused';
1278
+ if (ep.stage === 'rolled-back') return 'Rolled back to reject-buffer';
1279
+ if (ep.stage === 'abstained') return 'Reward agent abstained';
1280
+ return 'No blocker';
1281
+ }
1282
+
1283
+ function episodeVerdict(ep) {
1284
+ const stage = ep && ep.stage ? ep.stage : 'pending';
1285
+ const tone = stage === 'kept' || stage === 'evolved'
1286
+ ? 'good'
1287
+ : stage === 'rolled-back' || stage === 'evolution-refused'
1288
+ ? 'bad'
1289
+ : stage === 'abstained' || stage === 'baseline-skipped'
1290
+ ? 'muted'
1291
+ : 'info';
1292
+ return { label: stage, tone: tone };
1293
+ }
1294
+
1295
+ function episodeTime(ep) {
1296
+ return (ep && (ep.updatedAt || ep.createdAt)) || '';
1307
1297
  }
1308
1298
 
1309
1299
  async function loadEndpoint(url, fallback) {
@@ -1408,25 +1398,24 @@ export const dashboardClientScript = String.raw `
1408
1398
  " philosophy: fluid, not phase-gated — revisit any artifact anytime."
1409
1399
  ].join("\n");
1410
1400
  const DIAGRAM_OUTER_LOOP = [
1411
- " pnpm evolve snapshot baseline ─► ARCHIVE (variants + lineage + scores)",
1412
- " │",
1413
- " ▼ runGeneration() × N generations (or until USD budget)",
1401
+ " synergyspec-selfevolving self-evolution episode (in-context RL, one episode)",
1414
1402
  " │",
1415
- " ① SELECT parent(s) best random score_prop │ score_child_prop",
1403
+ " ① 主智能体 MAIN AGENT (policy vN+1) ∥ CRITIC AGENT 基线智能体 (reruns vN)",
1404
+ " │ same change · isolated worktree · 产物即弃",
1405
+ " ▼",
1406
+ " ② 磁盘 DISK episode store (transcripts × both arms)",
1416
1407
  " │",
1417
- " PROPOSE child claude -p emits a json:patch that mutates ONLY the",
1418
- " │ evolvable genome (prompts · templates · schema).",
1419
- " │ usecases.md · gen-tests.ts · run-tests.ts are FROZEN ✗",
1408
+ " 奖励智能体 REWARD AGENT (LLM judge) reward(主臂) · reward(基线臂)",
1409
+ " │ advantage = reward(主臂) reward(基线臂) · 文本梯度",
1410
+ " │ writes diagnosis.json · never edits · 弃权 abstains",
1420
1411
  " │",
1421
- " EVALUATE child per benchmark task:",
1422
- " │ runTask ─► the SynergySpec pipeline (headless)",
1423
- " │ ─► verify ─► verdict",
1424
- " │ oracle = held_out (curated) OR generated (frozen)",
1425
- " │ micro-averaged passRate [eval-cache keyed by hash]",
1412
+ " bad advantage ─► rollbackPolicyVersion + appendRejectBufferEntry FIRST",
1426
1413
  " │",
1427
- " APPEND RunRecord ─► ARCHIVE (score · lineage · cost)",
1414
+ " 演进智能体 EVOLVING AGENT (optimizer.step) ONE bounded edit L",
1415
+ " │ gates ×3: static · observed-GREEN · 范围⊆诊断",
1416
+ " │ usecases.md · gen-tests.ts · run-tests.ts are FROZEN ✗",
1428
1417
  " │",
1429
- " ▼ best variant's mutated prompts / schema = the evolved harness"
1418
+ " ▼ write back vN+2 to 策略 POLICY = design template (主智能体的「权重」)"
1430
1419
  ].join("\n");
1431
1420
  const DIAGRAM_ORACLE = [
1432
1421
  " PER-TASK PIPELINE",
@@ -1506,21 +1495,21 @@ export const dashboardClientScript = String.raw `
1506
1495
  function NextActionBar(props) {
1507
1496
  const t = props.t || function (k, params) {
1508
1497
  if (k === 'common.nextAction') return 'Next action';
1509
- if (k === 'common.nextAction.runLab') return 'Run the lab to create benchmark evidence.';
1510
- if (k === 'common.nextAction.inspect') return 'Inspect the latest benchmark failure.';
1498
+ if (k === 'common.nextAction.runLab') return 'Run an episode to create graded evidence.';
1499
+ if (k === 'common.nextAction.inspect') return 'Inspect the latest rolled-back episode.';
1511
1500
  if (k === 'common.openLatest') return 'Open latest';
1512
- if (k === 'common.noRun') return 'No run';
1501
+ if (k === 'common.noRun') return 'No episode';
1513
1502
  if (k === 'common.copyCommand') return 'Copy command';
1514
1503
  if (k === 'common.copied') return 'Copied';
1515
1504
  if (k === 'common.copyFailed') return 'Copy failed';
1516
- if (k === 'common.benchmark') return 'benchmark ' + (params && params.id || '');
1517
- if (k === 'common.benchmarkDefault') return 'default benchmark';
1505
+ if (k === 'common.target') return 'target ' + (params && params.id || '');
1506
+ if (k === 'common.targetDefault') return 'default target';
1518
1507
  return k;
1519
1508
  };
1520
1509
  const latest = props.latest;
1521
- const blocker = primaryBlocker(latest);
1522
- const command = 'synergyspec-selfevolving self-evolution evolve';
1523
- const commandLabel = latest && latest.benchmarkId ? t('common.benchmark', { id: latest.benchmarkId }) : t('common.benchmarkDefault');
1510
+ const blocker = episodeBlocker(latest);
1511
+ const command = 'synergyspec-selfevolving self-evolution episode';
1512
+ const commandLabel = latest && latest.targetId ? t('common.target', { id: latest.targetId }) : t('common.targetDefault');
1524
1513
  const [copyStatus, setCopyStatus] = useState('idle');
1525
1514
  const copyCommand = function () {
1526
1515
  const finish = function (status) {
@@ -1552,7 +1541,7 @@ export const dashboardClientScript = String.raw `
1552
1541
  type: 'button',
1553
1542
  className: 'action-button primary',
1554
1543
  onClick: function () {
1555
- if (props.onSelect && latest && latest.runId) props.onSelect({ type: 'run', id: latest.runId, data: latest });
1544
+ if (props.onSelect && latest && latest.episodeId) props.onSelect({ type: 'episode', id: latest.episodeId, data: latest });
1556
1545
  },
1557
1546
  disabled: !latest
1558
1547
  }, latest ? t('common.openLatest') : t('common.noRun')),
@@ -1724,7 +1713,7 @@ export const dashboardClientScript = String.raw `
1724
1713
  learn: props.metrics.readyToLearn,
1725
1714
  trace: props.metrics.totalTrace,
1726
1715
  spec: props.metrics.openChanges,
1727
- evolve: props.metrics.totalRuns,
1716
+ evolve: props.metrics.totalEpisodes,
1728
1717
  architecture: props.metrics.totalChanges
1729
1718
  };
1730
1719
  return e('header', { className: 'sidebar' },
@@ -2003,71 +1992,67 @@ export const dashboardClientScript = String.raw `
2003
1992
  );
2004
1993
  }
2005
1994
 
2006
- function RunCard(props) {
2007
- const run = props.run;
2008
- const score = runScore(run);
2009
- const verdict = runVerdict(run);
2010
- const openRun = function () { props.onSelect({ type: 'run', id: run.runId, data: run }); };
1995
+ function EpisodeCard(props) {
1996
+ const ep = props.episode;
1997
+ const verdict = episodeVerdict(ep);
1998
+ const openEpisode = function () { props.onSelect({ type: 'episode', id: ep.episodeId, data: ep }); };
2011
1999
  return e('div', Object.assign({
2012
2000
  className: 'run-card',
2013
- 'aria-label': 'Open evolution run ' + run.runId
2014
- }, interactiveProps(openRun)),
2001
+ 'aria-label': 'Open episode ' + ep.episodeId
2002
+ }, interactiveProps(openEpisode)),
2015
2003
  e('div', { className: 'run-head' },
2016
2004
  e('div', { style: { minWidth: 0 } },
2017
- e('div', { className: 'benchmark', title: run.benchmarkId || run.runId }, run.benchmarkId || 'benchmark'),
2018
- e('div', { className: 'run-id' }, run.runId)
2005
+ e('div', { className: 'episode-name', title: ep.changeName || ep.episodeId }, ep.changeName || 'episode'),
2006
+ e('div', { className: 'run-id' }, ep.episodeId)
2019
2007
  ),
2020
- e(Donut, { rate: score, tone: verdict.tone })
2008
+ e('div', { className: 'lane-score ' + advantageTone(ep) }, formatAdvantage(episodeAdvantage(ep)))
2021
2009
  ),
2022
2010
  e('div', { className: 'run-stats' },
2023
- e('div', null, e('div', { className: 'stat-label' }, 'Tasks'), e('div', { className: 'stat-value' }, formatInt(run.taskCount))),
2024
- e('div', null, e('div', { className: 'stat-label' }, 'Cost'), e('div', { className: 'stat-value' }, formatUsd(run.totalCostUsd))),
2025
- e('div', null, e('div', { className: 'stat-label' }, 'Wall'), e('div', { className: 'stat-value' }, formatDuration(run.totalWallTimeMs)))
2011
+ e('div', null, e('div', { className: 'stat-label' }, 'Target'), e('div', { className: 'stat-value' }, compact(ep.targetId || 'default', 18))),
2012
+ e('div', null, e('div', { className: 'stat-label' }, 'Main'), e('div', { className: 'stat-value' }, ep.policyVersionMain == null ? 'n/a' : 'v' + ep.policyVersionMain)),
2013
+ e('div', null, e('div', { className: 'stat-label' }, 'Baseline'), e('div', { className: 'stat-value' }, ep.policyVersionBaseline == null ? 'n/a' : 'v' + ep.policyVersionBaseline))
2026
2014
  ),
2027
2015
  e('div', { className: 'run-footer' },
2028
2016
  e(Pill, { tone: verdict.tone }, verdict.label),
2029
- e('span', null, formatDate(latestRunTime(run))),
2030
- run.isolationMode ? e('span', null, run.isolationMode) : null
2017
+ e('span', null, formatDate(episodeTime(ep)))
2031
2018
  )
2032
2019
  );
2033
2020
  }
2034
2021
 
2035
- function RunHistoryTable(props) {
2036
- const runs = safeArray(props.runs);
2037
- if (!runs.length) return e(EmptyState, { title: 'No evolution runs', hint: 'evolve/runs has no run summaries' });
2022
+ function EpisodeHistoryTable(props) {
2023
+ const episodes = safeArray(props.episodes);
2024
+ if (!episodes.length) return e(EmptyState, { title: 'No episodes', hint: 'self-evolution/episodes is empty' });
2038
2025
  return e('div', { className: 'run-table-wrap' },
2039
2026
  e('table', { className: 'run-table' },
2040
2027
  e('thead', null,
2041
2028
  e('tr', null,
2042
2029
  e('th', null, 'Time'),
2043
- e('th', null, 'Benchmark'),
2044
- e('th', null, 'Variant'),
2045
- e('th', null, 'Fitness'),
2046
- e('th', null, 'Verdict'),
2030
+ e('th', null, 'Change'),
2031
+ e('th', null, 'Target'),
2032
+ e('th', null, 'Advantage'),
2033
+ e('th', null, 'Stage'),
2047
2034
  e('th', null, 'Blocker'),
2048
- e('th', null, 'Tasks'),
2049
- e('th', null, 'Cost'),
2050
- e('th', null, 'Wall')
2035
+ e('th', null, 'Versions')
2051
2036
  )
2052
2037
  ),
2053
2038
  e('tbody', null,
2054
- runs.map(function (run, index) {
2055
- const verdict = runVerdict(run);
2056
- const openRun = function () { props.onSelect({ type: 'run', id: run.runId, data: run }); };
2039
+ episodes.map(function (ep, index) {
2040
+ const verdict = episodeVerdict(ep);
2041
+ const openEpisode = function () { props.onSelect({ type: 'episode', id: ep.episodeId, data: ep }); };
2042
+ const main = ep.policyVersionMain == null ? '-' : 'v' + ep.policyVersionMain;
2043
+ const baseline = ep.policyVersionBaseline == null ? '-' : 'v' + ep.policyVersionBaseline;
2057
2044
  return e('tr', Object.assign({
2058
- key: run.runId || index,
2045
+ key: ep.episodeId || index,
2059
2046
  className: 'run-table-row ' + verdict.tone,
2060
- 'aria-label': 'Open evolution run ' + (run.runId || index)
2061
- }, interactiveProps(openRun)),
2062
- e('td', { 'data-label': 'Time' }, formatDate(latestRunTime(run))),
2063
- e('td', { 'data-label': 'Benchmark', title: run.benchmarkId || run.runId }, compact(run.benchmarkId || 'benchmark', 44)),
2064
- e('td', { 'data-label': 'Variant', title: run.harnessVariant || run.runId }, compact(run.harnessVariant || run.runId, 34)),
2065
- e('td', { 'data-label': 'Fitness', className: 'mono-cell' }, formatPercent(runScore(run))),
2066
- e('td', { 'data-label': 'Verdict' }, e(Pill, { tone: verdict.tone }, verdict.label)),
2067
- e('td', { 'data-label': 'Blocker', title: run.failureReasonSummary || '' }, compact(run.failureReasonSummary || primaryBlocker(run), 54)),
2068
- e('td', { 'data-label': 'Tasks', className: 'mono-cell' }, formatInt(run.taskCount)),
2069
- e('td', { 'data-label': 'Cost', className: 'mono-cell' }, formatUsd(run.totalCostUsd)),
2070
- e('td', { 'data-label': 'Wall', className: 'mono-cell' }, formatDuration(run.totalWallTimeMs))
2047
+ 'aria-label': 'Open episode ' + (ep.episodeId || index)
2048
+ }, interactiveProps(openEpisode)),
2049
+ e('td', { 'data-label': 'Time' }, formatDate(episodeTime(ep))),
2050
+ e('td', { 'data-label': 'Change', title: ep.changeName || ep.episodeId }, compact(ep.changeName || ep.episodeId || 'episode', 44)),
2051
+ e('td', { 'data-label': 'Target', title: ep.targetId || '' }, compact(ep.targetId || 'default', 28)),
2052
+ e('td', { 'data-label': 'Advantage', className: 'mono-cell ' + advantageTone(ep) }, formatAdvantage(episodeAdvantage(ep))),
2053
+ e('td', { 'data-label': 'Stage' }, e(Pill, { tone: verdict.tone }, verdict.label)),
2054
+ e('td', { 'data-label': 'Blocker', title: episodeBlocker(ep) }, compact(episodeBlocker(ep), 54)),
2055
+ e('td', { 'data-label': 'Versions', className: 'mono-cell' }, main + ' / ' + baseline)
2071
2056
  );
2072
2057
  })
2073
2058
  )
@@ -2076,101 +2061,104 @@ export const dashboardClientScript = String.raw `
2076
2061
  }
2077
2062
 
2078
2063
  function EvolutionStage(props) {
2079
- const runs = safeArray(props.runs);
2080
- const archive = props.archive || {};
2081
- const entries = safeArray(archive.entries);
2064
+ const episodes = safeArray(props.episodes);
2065
+ const lineages = safeArray(props.policyLineages);
2082
2066
  const phaseIndex = props.phaseIndex % evolutionPhases.length;
2083
- const evidenceRuns = measuredRuns(runs);
2084
- const candidates = promotableRuns(runs);
2085
- const latest = runs[0];
2086
- const champion = candidates.slice().sort(function (a, b) { return runScore(b) - runScore(a); })[0];
2087
- const averageScore = evidenceRuns.length
2088
- ? evidenceRuns.reduce(function (sum, run) { return sum + runScore(run); }, 0) / evidenceRuns.length
2089
- : 0;
2090
- const selectedRunId = props.selectedRunId || (latest && latest.runId) || '';
2091
- const selectedRun = runs.find(function (run) { return run.runId === selectedRunId; }) || latest;
2092
- const latestVerdict = runVerdict(latest);
2093
- const blockerRuns = runs.filter(function (run) {
2094
- const verdict = runVerdict(run);
2095
- return verdict.tone === 'bad' || Boolean(run.failureReasonSummary);
2096
- });
2097
- const pendingRuns = runs.filter(function (run) {
2098
- return run.status === 'pending' || runVerdict(run).tone === 'muted';
2099
- });
2100
- const totalTasks = runs.reduce(function (sum, run) { return sum + numberOr(run.taskCount, 0); }, 0);
2101
- const totalCost = runs.reduce(function (sum, run) { return sum + numberOr(run.totalCostUsd, 0); }, 0);
2067
+ const graded = gradedEpisodes(episodes);
2068
+ const kept = keptEpisodes(episodes);
2069
+ const latest = episodes[0];
2070
+ const headVersion = lineages.reduce(function (max, lineage) {
2071
+ return lineage.headVersion == null ? max : Math.max(max, lineage.headVersion);
2072
+ }, null);
2073
+ const stageCount = function (stage) {
2074
+ return episodes.filter(function (ep) { return ep.stage === stage; }).length;
2075
+ };
2076
+ const baselineArm = episodes.filter(function (ep) { return ep.policyVersionBaseline != null; }).length;
2077
+ const baselineSkipped = stageCount('baseline-skipped');
2078
+ const mainArm = episodes.filter(function (ep) { return ep.policyVersionMain != null; }).length;
2079
+ const scored = graded.length;
2080
+ const evolveCount = lineages.reduce(function (sum, lineage) { return sum + numberOr(lineage.evolveCount, 0); }, 0);
2081
+ const rollbackCount = lineages.reduce(function (sum, lineage) { return sum + numberOr(lineage.rollbackCount, 0); }, 0);
2082
+ const refusedCount = lineages.reduce(function (sum, lineage) { return sum + numberOr(lineage.refusedCount, 0); }, 0);
2083
+ const meanAdvantage = graded.length
2084
+ ? graded.reduce(function (sum, ep) { return sum + episodeAdvantage(ep); }, 0) / graded.length
2085
+ : null;
2086
+ const blockedEpisodes = episodes.filter(function (ep) { return episodeVerdict(ep).tone === 'bad'; });
2087
+ const selectedEpisodeId = props.selectedEpisodeId || (latest && latest.episodeId) || '';
2088
+ const selectedEpisode = episodes.find(function (ep) { return ep.episodeId === selectedEpisodeId; }) || latest;
2089
+ const latestVerdict = episodeVerdict(latest);
2102
2090
  const pipelineStages = [
2103
2091
  {
2104
- key: 'baseline',
2092
+ key: 'baseline-arm',
2105
2093
  phase: 0,
2106
- label: 'Baseline',
2107
- value: entries.length ? formatInt(entries.length) + ' archived' : 'missing',
2108
- detail: entries.length ? shortId(entries[0].id) : 'archive has no baseline entry',
2109
- tone: entries.length ? 'good' : 'warn',
2110
- evidence: 'archive.json'
2094
+ label: 'Baseline arm',
2095
+ value: baselineArm ? formatInt(baselineArm) + ' captured' : 'none',
2096
+ detail: baselineSkipped ? formatInt(baselineSkipped) + ' baseline-skipped' : 'fresh baseline-policy rollout',
2097
+ tone: baselineArm ? 'good' : 'warn',
2098
+ evidence: 'baseline-arm-captured'
2111
2099
  },
2112
2100
  {
2113
- key: 'candidate',
2101
+ key: 'main-arm',
2114
2102
  phase: 2,
2115
- label: 'Candidate run',
2116
- value: latest ? shortId(latest.runId) : 'none',
2117
- detail: latest ? compact(latest.benchmarkId || latest.harnessVariant || latest.runId, 72) : 'run the lab to create evidence',
2103
+ label: 'Main arm',
2104
+ value: mainArm ? formatInt(mainArm) + ' captured' : 'none',
2105
+ detail: latest ? compact(latest.changeName || latest.episodeId, 72) : 'run an episode to capture the main arm',
2118
2106
  tone: latest ? latestVerdict.tone : 'muted',
2119
- evidence: latest ? formatDate(latestRunTime(latest)) : 'no timestamp'
2107
+ evidence: 'main-arm-captured'
2120
2108
  },
2121
2109
  {
2122
- key: 'safety',
2110
+ key: 'reward',
2123
2111
  phase: 3,
2124
- label: 'Safety gate',
2125
- value: blockerRuns.length ? formatInt(blockerRuns.length) + ' blocked' : 'clear',
2126
- detail: latest ? primaryBlocker(latest) : 'waiting for result.json',
2127
- tone: blockerRuns.length ? 'bad' : latest ? 'good' : 'muted',
2128
- evidence: 'task result evidence'
2112
+ label: 'Reward + advantage',
2113
+ value: scored ? formatAdvantage(meanAdvantage) + ' mean' : 'unscored',
2114
+ detail: formatInt(scored) + ' graded of ' + formatInt(episodes.length) + ' episodes',
2115
+ tone: scored ? 'info' : 'warn',
2116
+ evidence: 'scored'
2129
2117
  },
2130
2118
  {
2131
- key: 'benchmark',
2119
+ key: 'decision',
2132
2120
  phase: 3,
2133
- label: 'Benchmark',
2134
- value: evidenceRuns.length ? formatPercent(averageScore) + ' avg' : 'unmeasured',
2135
- detail: formatInt(totalTasks) + ' tasks across ' + formatInt(evidenceRuns.length) + ' measured runs',
2136
- tone: evidenceRuns.length ? 'info' : 'warn',
2137
- evidence: 'verdict counts'
2121
+ label: 'Rollback or evolve',
2122
+ value: evolveCount ? formatInt(evolveCount) + ' evolved' : 'none',
2123
+ detail: formatInt(rollbackCount) + ' rolled back / ' + formatInt(refusedCount) + ' refused',
2124
+ tone: blockedEpisodes.length ? 'bad' : evolveCount ? 'good' : 'muted',
2125
+ evidence: 'kept / rolled-back'
2138
2126
  },
2139
2127
  {
2140
- key: 'promotion',
2128
+ key: 'writeback',
2141
2129
  phase: 4,
2142
- label: 'Promotion',
2143
- value: candidates.length ? formatInt(candidates.length) + ' candidate' : 'none',
2144
- detail: champion ? 'best ' + shortId(champion.runId) + ' at ' + formatPercent(runScore(champion)) : 'no promotable run yet',
2145
- tone: candidates.length ? 'good' : 'bad',
2146
- evidence: 'review required'
2130
+ label: 'Write-back ledger',
2131
+ value: headVersion == null ? 'no version' : 'v' + headVersion,
2132
+ detail: kept.length ? formatInt(kept.length) + ' kept episodes' : 'no kept episode yet',
2133
+ tone: headVersion == null ? 'warn' : 'good',
2134
+ evidence: 'policy ledger'
2147
2135
  }
2148
2136
  ];
2149
- const selectRun = function (run) {
2150
- if (!run || !run.runId) return;
2151
- if (props.onRunSelect) props.onRunSelect(run.runId);
2137
+ const selectEpisode = function (ep) {
2138
+ if (!ep || !ep.episodeId) return;
2139
+ if (props.onEpisodeSelect) props.onEpisodeSelect(ep.episodeId);
2152
2140
  };
2153
2141
  const latestHeadline = latest
2154
2142
  ? latestVerdict.tone === 'bad'
2155
- ? 'Latest run failed'
2143
+ ? 'Latest episode ' + latestVerdict.label
2156
2144
  : latestVerdict.tone === 'good'
2157
- ? 'Latest run passed'
2158
- : 'Latest run ' + latestVerdict.label
2159
- : 'No run evidence';
2145
+ ? 'Latest episode ' + latestVerdict.label
2146
+ : 'Latest episode ' + latestVerdict.label
2147
+ : 'No episode evidence';
2160
2148
  return e('section', { className: 'evo-stage', 'aria-label': 'Self-evolution evidence pipeline' },
2161
2149
  e('div', { className: 'evo-map-head' },
2162
2150
  e('div', { className: 'map-copy' },
2163
2151
  e('div', { className: 'eyebrow' }, 'Evidence pipeline'),
2164
2152
  e('h2', null, latestHeadline),
2165
- e('p', null, latest ? primaryBlocker(latest) : 'Run the lab to populate benchmark evidence.')
2153
+ e('p', null, latest ? episodeBlocker(latest) : 'Run an episode to populate two-arm evidence.')
2166
2154
  ),
2167
2155
  e('div', { className: 'map-totals' },
2168
- e('div', { className: 'map-total' }, e('span', null, 'Runs'), e('strong', null, formatInt(runs.length))),
2169
- e('div', { className: 'map-total' }, e('span', null, 'Blocked'), e('strong', null, formatInt(blockerRuns.length))),
2170
- e('div', { className: 'map-total' }, e('span', null, 'Spend'), e('strong', null, formatUsd(totalCost)))
2156
+ e('div', { className: 'map-total' }, e('span', null, 'Episodes'), e('strong', null, formatInt(episodes.length))),
2157
+ e('div', { className: 'map-total' }, e('span', null, 'Blocked'), e('strong', null, formatInt(blockedEpisodes.length))),
2158
+ e('div', { className: 'map-total' }, e('span', null, 'Head'), e('strong', null, headVersion == null ? 'n/a' : 'v' + headVersion))
2171
2159
  )
2172
2160
  ),
2173
- e('div', { className: 'pipeline-rail', role: 'tablist', 'aria-label': 'Run evidence stages' },
2161
+ e('div', { className: 'pipeline-rail', role: 'tablist', 'aria-label': 'Episode evidence stages' },
2174
2162
  pipelineStages.map(function (stage, index) {
2175
2163
  return e('button', {
2176
2164
  key: stage.key,
@@ -2194,39 +2182,39 @@ export const dashboardClientScript = String.raw `
2194
2182
  e('div', { className: 'run-board' },
2195
2183
  e('div', { className: 'board-toolbar' },
2196
2184
  e('div', null,
2197
- e('span', { className: 'node-kicker' }, 'Run board'),
2198
- e('strong', null, runs.length ? 'Actual benchmark runs' : 'No benchmark runs')
2185
+ e('span', { className: 'node-kicker' }, 'Episode board'),
2186
+ e('strong', null, episodes.length ? 'Recorded episodes' : 'No episodes yet')
2199
2187
  ),
2200
- e('div', { className: 'board-filters', 'aria-label': 'Run evidence summary' },
2188
+ e('div', { className: 'board-filters', 'aria-label': 'Episode evidence summary' },
2201
2189
  e(Pill, { tone: latest ? latestVerdict.tone : 'muted' }, latest ? latestVerdict.label : 'no latest'),
2202
- e(Pill, { tone: candidates.length ? 'good' : 'bad' }, formatInt(candidates.length) + ' promotable'),
2203
- e(Pill, { tone: pendingRuns.length ? 'warn' : 'muted' }, formatInt(pendingRuns.length) + ' pending')
2190
+ e(Pill, { tone: kept.length ? 'good' : 'bad' }, formatInt(kept.length) + ' kept'),
2191
+ e(Pill, { tone: graded.length ? 'info' : 'muted' }, formatInt(graded.length) + ' graded')
2204
2192
  )
2205
2193
  ),
2206
- runs.length ? runs.slice(0, 8).map(function (run, index) {
2207
- const verdict = runVerdict(run);
2194
+ episodes.length ? episodes.slice(0, 8).map(function (ep, index) {
2195
+ const verdict = episodeVerdict(ep);
2208
2196
  return e('button', {
2209
- key: run.runId || index,
2197
+ key: ep.episodeId || index,
2210
2198
  type: 'button',
2211
- className: 'run-lane ' + verdict.tone + (selectedRun && selectedRun.runId === run.runId ? ' selected' : ''),
2212
- onClick: function () { selectRun(run); },
2213
- title: run.runId || ''
2199
+ className: 'run-lane ' + verdict.tone + (selectedEpisode && selectedEpisode.episodeId === ep.episodeId ? ' selected' : ''),
2200
+ onClick: function () { selectEpisode(ep); },
2201
+ title: ep.episodeId || ''
2214
2202
  },
2215
- e('span', { className: 'lane-time' }, formatDate(latestRunTime(run))),
2203
+ e('span', { className: 'lane-time' }, formatDate(episodeTime(ep))),
2216
2204
  e('span', { className: 'lane-main' },
2217
- e('strong', null, compact(run.benchmarkId || run.harnessVariant || run.runId, 62)),
2218
- e('small', null, shortId(run.runId || ('run-' + index)) + ' / ' + formatInt(run.taskCount) + ' tasks')
2205
+ e('strong', null, compact(ep.changeName || ep.episodeId, 62)),
2206
+ e('small', null, shortId(ep.episodeId || ('episode-' + index)) + ' / ' + compact(ep.targetId || 'default', 24))
2219
2207
  ),
2220
- e('span', { className: 'lane-score' }, formatPercent(runScore(run))),
2208
+ e('span', { className: 'lane-score ' + advantageTone(ep) }, formatAdvantage(episodeAdvantage(ep))),
2221
2209
  e('span', { className: 'lane-verdict' },
2222
2210
  e(Pill, { tone: verdict.tone }, verdict.label),
2223
- e('small', null, formatUsd(run.totalCostUsd) + ' / ' + formatDuration(run.totalWallTimeMs))
2211
+ e('small', null, (ep.policyVersionMain == null ? '-' : 'v' + ep.policyVersionMain) + ' / ' + (ep.policyVersionBaseline == null ? '-' : 'v' + ep.policyVersionBaseline))
2224
2212
  ),
2225
- e('span', { className: 'lane-blocker' }, primaryBlocker(run))
2213
+ e('span', { className: 'lane-blocker' }, episodeBlocker(ep))
2226
2214
  );
2227
2215
  }) : e('div', { className: 'candidate-empty' },
2228
- e('strong', null, 'Run the lab to populate lanes'),
2229
- e('span', null, 'Benchmark-backed runs will appear here with verdicts, blockers, cost, and wall time.')
2216
+ e('strong', null, 'Run an episode to populate lanes'),
2217
+ e('span', null, 'Two-arm episodes will appear here with stages, advantage, and policy versions.')
2230
2218
  )
2231
2219
  )
2232
2220
  )
@@ -2235,59 +2223,55 @@ export const dashboardClientScript = String.raw `
2235
2223
 
2236
2224
  function EvolutionInspector(props) {
2237
2225
  const phase = evolutionPhases[props.phaseIndex % evolutionPhases.length];
2238
- const runs = safeArray(props.runs);
2239
- const archive = props.archive || {};
2240
- const entries = safeArray(archive.entries);
2241
- const generations = safeArray(archive.generations);
2242
- const latest = runs[0];
2243
- const evidenceRuns = measuredRuns(runs);
2244
- const candidates = promotableRuns(runs);
2245
- const champion = candidates.slice().sort(function (a, b) { return runScore(b) - runScore(a); })[0];
2246
- const selectedRunId = props.selectedRunId || (latest && latest.runId) || '';
2247
- const selectedRun = runs.find(function (run) { return run.runId === selectedRunId; }) || latest;
2248
- const selectedVerdict = runVerdict(selectedRun);
2249
- const blockerRuns = runs.filter(function (run) {
2250
- const verdict = runVerdict(run);
2251
- return verdict.tone === 'bad' || Boolean(run.failureReasonSummary);
2252
- });
2253
- const pendingRuns = runs.filter(function (run) {
2254
- return run.status === 'pending' || runVerdict(run).tone === 'muted';
2255
- });
2256
- const averageScore = evidenceRuns.length
2257
- ? evidenceRuns.reduce(function (sum, run) { return sum + runScore(run); }, 0) / evidenceRuns.length
2258
- : 0;
2226
+ const episodes = safeArray(props.episodes);
2227
+ const lineages = safeArray(props.policyLineages);
2228
+ const rejectBuffer = safeArray(props.rejectBuffer);
2229
+ const latest = episodes[0];
2230
+ const graded = gradedEpisodes(episodes);
2231
+ const kept = keptEpisodes(episodes);
2232
+ const selectedEpisodeId = props.selectedEpisodeId || (latest && latest.episodeId) || '';
2233
+ const selectedEpisode = episodes.find(function (ep) { return ep.episodeId === selectedEpisodeId; }) || latest;
2234
+ const selectedVerdict = episodeVerdict(selectedEpisode);
2235
+ const blockedEpisodes = episodes.filter(function (ep) { return episodeVerdict(ep).tone === 'bad'; });
2236
+ const meanAdvantage = graded.length
2237
+ ? graded.reduce(function (sum, ep) { return sum + episodeAdvantage(ep); }, 0) / graded.length
2238
+ : null;
2239
+ const activeLineage = (selectedEpisode && lineages.find(function (lineage) { return lineage.targetId === selectedEpisode.targetId; })) || lineages[0] || null;
2240
+ const lineageEntries = activeLineage ? safeArray(activeLineage.entries) : [];
2241
+ const headVersion = activeLineage ? activeLineage.headVersion : null;
2242
+ const rejectNewestFirst = rejectBuffer.slice().reverse();
2259
2243
  const stageSignals = [
2260
2244
  ['Focus', phase.title],
2261
- ['Runs', formatInt(runs.length)],
2262
- ['Blocked', formatInt(blockerRuns.length)],
2263
- ['Promotable', formatInt(candidates.length)]
2245
+ ['Episodes', formatInt(episodes.length)],
2246
+ ['Blocked', formatInt(blockedEpisodes.length)],
2247
+ ['Kept', formatInt(kept.length)]
2264
2248
  ];
2265
- const selectRun = function (run) {
2266
- if (run && run.runId && props.onRunSelect) props.onRunSelect(run.runId);
2249
+ const selectEpisode = function (ep) {
2250
+ if (ep && ep.episodeId && props.onEpisodeSelect) props.onEpisodeSelect(ep.episodeId);
2267
2251
  };
2268
- const openRun = function (run) {
2269
- if (run && run.runId && props.onOpenRun) props.onOpenRun(run);
2252
+ const openEpisode = function (ep) {
2253
+ if (ep && ep.episodeId && props.onOpenEpisode) props.onOpenEpisode(ep);
2270
2254
  };
2271
- const openArchive = function (entry) {
2272
- if (entry && entry.id && props.onOpenArchive) props.onOpenArchive(entry);
2255
+ const openReject = function (entry) {
2256
+ if (entry && props.onOpenReject) props.onOpenReject(entry);
2273
2257
  };
2274
2258
  return e('aside', { className: 'evo-inspector' },
2275
2259
  e('div', { className: 'inspector-top' },
2276
- e('div', { className: 'inspector-phase' }, 'Run inspector'),
2277
- e('h2', null, selectedRun ? compact(selectedRun.benchmarkId || selectedRun.harnessVariant || selectedRun.runId, 72) : 'No selected run'),
2278
- e('p', null, selectedRun ? primaryBlocker(selectedRun) : 'Select a benchmark run to inspect verdicts, blockers, artifacts, and lineage.'),
2260
+ e('div', { className: 'inspector-phase' }, 'Episode inspector'),
2261
+ e('h2', null, selectedEpisode ? compact(selectedEpisode.changeName || selectedEpisode.episodeId, 72) : 'No selected episode'),
2262
+ e('p', null, selectedEpisode ? episodeBlocker(selectedEpisode) : 'Select an episode to inspect its stage, advantage, versions, and policy ledger.'),
2279
2263
  e('div', { className: 'inspector-actions' },
2280
2264
  e('button', {
2281
2265
  type: 'button',
2282
2266
  className: 'step-button',
2283
- disabled: !blockerRuns.length,
2284
- onClick: function () { selectRun(blockerRuns[0]); }
2285
- }, blockerRuns.length ? 'Select blocker' : 'No blocker'),
2267
+ disabled: !blockedEpisodes.length,
2268
+ onClick: function () { selectEpisode(blockedEpisodes[0]); }
2269
+ }, blockedEpisodes.length ? 'Select blocker' : 'No blocker'),
2286
2270
  e('button', {
2287
2271
  type: 'button',
2288
2272
  className: 'step-button primary',
2289
- disabled: !selectedRun,
2290
- onClick: function () { openRun(selectedRun); }
2273
+ disabled: !selectedEpisode,
2274
+ onClick: function () { openEpisode(selectedEpisode); }
2291
2275
  }, 'Open detail')
2292
2276
  )
2293
2277
  ),
@@ -2299,51 +2283,55 @@ export const dashboardClientScript = String.raw `
2299
2283
  );
2300
2284
  })
2301
2285
  ),
2302
- selectedRun ? e('div', { className: 'selected-run-sheet' },
2286
+ selectedEpisode ? e('div', { className: 'selected-run-sheet' },
2303
2287
  e('div', { className: 'sheet-row' },
2304
- e('span', null, 'Run'),
2305
- e('strong', null, shortId(selectedRun.runId))
2288
+ e('span', null, 'Episode'),
2289
+ e('strong', null, shortId(selectedEpisode.episodeId))
2306
2290
  ),
2307
2291
  e('div', { className: 'sheet-row' },
2308
- e('span', null, 'Verdict'),
2292
+ e('span', null, 'Stage'),
2309
2293
  e(Pill, { tone: selectedVerdict.tone }, selectedVerdict.label)
2310
2294
  ),
2311
2295
  e('div', { className: 'sheet-row' },
2312
- e('span', null, 'Fitness'),
2313
- e('strong', null, formatPercent(runScore(selectedRun)))
2296
+ e('span', null, 'Advantage'),
2297
+ e('strong', { className: advantageTone(selectedEpisode) }, formatAdvantage(episodeAdvantage(selectedEpisode)))
2314
2298
  ),
2315
2299
  e('div', { className: 'sheet-row' },
2316
- e('span', null, 'Tasks'),
2317
- e('strong', null, formatInt(selectedRun.taskCount))
2300
+ e('span', null, 'Versions'),
2301
+ e('strong', null, (selectedEpisode.policyVersionMain == null ? '-' : 'v' + selectedEpisode.policyVersionMain) + ' / ' + (selectedEpisode.policyVersionBaseline == null ? '-' : 'v' + selectedEpisode.policyVersionBaseline))
2318
2302
  ),
2319
2303
  e('div', { className: 'sheet-row' },
2320
- e('span', null, 'Cost / wall'),
2321
- e('strong', null, formatUsd(selectedRun.totalCostUsd) + ' / ' + formatDuration(selectedRun.totalWallTimeMs))
2304
+ e('span', null, 'Change'),
2305
+ e('strong', null, compact(selectedEpisode.changeName || 'n/a', 40))
2306
+ ),
2307
+ e('div', { className: 'sheet-row' },
2308
+ e('span', null, 'Target'),
2309
+ e('strong', null, compact(selectedEpisode.targetId || 'default', 40))
2322
2310
  ),
2323
2311
  e('div', { className: 'sheet-row wide' },
2324
2312
  e('span', null, 'Artifact path'),
2325
- e('code', null, 'evolve/runs/' + selectedRun.runId)
2313
+ e('code', null, 'self-evolution/episodes/' + selectedEpisode.episodeId)
2326
2314
  )
2327
2315
  ) : null,
2328
2316
  e('div', { className: 'evidence-queue' },
2329
2317
  e('div', { className: 'queue-head' },
2330
2318
  e('span', null, 'Evidence queue'),
2331
- e(Pill, { tone: blockerRuns.length ? 'bad' : 'muted' }, formatInt(blockerRuns.length) + ' blockers')
2319
+ e(Pill, { tone: blockedEpisodes.length ? 'bad' : 'muted' }, formatInt(blockedEpisodes.length) + ' blockers')
2332
2320
  ),
2333
- (blockerRuns.length ? blockerRuns : pendingRuns.slice(0, 3)).slice(0, 5).map(function (run, index) {
2334
- const verdict = runVerdict(run);
2321
+ (blockedEpisodes.length ? blockedEpisodes : episodes.slice(0, 3)).slice(0, 5).map(function (ep, index) {
2322
+ const verdict = episodeVerdict(ep);
2335
2323
  return e('button', {
2336
- key: run.runId || index,
2324
+ key: ep.episodeId || index,
2337
2325
  type: 'button',
2338
- className: 'queue-run ' + verdict.tone + (selectedRun && selectedRun.runId === run.runId ? ' selected' : ''),
2339
- onClick: function () { selectRun(run); }
2326
+ className: 'queue-run ' + verdict.tone + (selectedEpisode && selectedEpisode.episodeId === ep.episodeId ? ' selected' : ''),
2327
+ onClick: function () { selectEpisode(ep); }
2340
2328
  },
2341
- e('span', null, compact(run.benchmarkId || run.harnessVariant || run.runId, 46)),
2342
- e('strong', null, primaryBlocker(run)),
2343
- e('small', null, shortId(run.runId) + ' / ' + formatDate(latestRunTime(run)))
2329
+ e('span', null, compact(ep.changeName || ep.episodeId, 46)),
2330
+ e('strong', null, episodeBlocker(ep)),
2331
+ e('small', null, shortId(ep.episodeId) + ' / ' + formatDate(episodeTime(ep)))
2344
2332
  );
2345
2333
  }),
2346
- !blockerRuns.length && !pendingRuns.length ? e('div', { className: 'queue-empty' }, 'No blocker queue. Inspect latest measured runs in the board.') : null
2334
+ !blockedEpisodes.length && !episodes.length ? e('div', { className: 'queue-empty' }, 'No episode queue. Run an episode to populate evidence.') : null
2347
2335
  ),
2348
2336
  e('div', { className: 'signal-grid' },
2349
2337
  e('div', { className: 'signal' },
@@ -2357,55 +2345,49 @@ export const dashboardClientScript = String.raw `
2357
2345
  e('div', { className: 'signal-detail' }, phase.detail)
2358
2346
  ),
2359
2347
  e('div', { className: 'signal' },
2360
- e('div', { className: 'signal-label' }, 'Fitness snapshot'),
2361
- e('div', { className: 'signal-value' }, runs.length ? formatPercent(averageScore) + ' average pass rate' : 'no run signal'),
2362
- e('div', { className: 'signal-detail' }, formatInt(runs.length) + ' runs, ' + formatInt(entries.length) + ' archived variants')
2348
+ e('div', { className: 'signal-label' }, 'Advantage snapshot'),
2349
+ e('div', { className: 'signal-value' }, graded.length ? formatAdvantage(meanAdvantage) + ' mean advantage' : 'no graded signal'),
2350
+ e('div', { className: 'signal-detail' }, formatInt(graded.length) + ' graded of ' + formatInt(episodes.length) + ' episodes')
2363
2351
  ),
2364
2352
  e('div', { className: 'signal' },
2365
- e('div', { className: 'signal-label' }, 'Lineage'),
2366
- e('div', { className: 'signal-value' }, formatInt(generations.length || newestGeneration(entries) + 1) + ' generations'),
2367
- e('div', { className: 'signal-detail' }, entries.length ? 'latest archive: ' + shortId(entries[0].id) : 'archive has no entries')
2353
+ e('div', { className: 'signal-label' }, 'Policy lineage'),
2354
+ e('div', { className: 'signal-value' }, headVersion == null ? 'no version' : 'v' + headVersion),
2355
+ e('div', { className: 'signal-detail' }, lineageEntries.length ? formatInt(lineageEntries.length) + ' ledger entries' : 'ledger has no entries')
2368
2356
  )
2369
2357
  ),
2370
2358
  e('div', { className: 'archive-queue' },
2371
2359
  e('div', { className: 'queue-head' },
2372
- e('span', null, 'Archive lineage'),
2373
- e(Pill, { tone: entries.length ? 'good' : 'muted' }, formatInt(entries.length) + ' variants')
2360
+ e('span', null, 'Reject-buffer'),
2361
+ e(Pill, { tone: rejectBuffer.length ? 'bad' : 'muted' }, formatInt(rejectBuffer.length) + ' entries')
2374
2362
  ),
2375
- entries.length ? entries.slice(0, 4).map(function (entry) {
2363
+ rejectBuffer.length ? rejectNewestFirst.slice(0, 4).map(function (entry, index) {
2364
+ const summary = safeObject(entry.editSummary);
2376
2365
  return e('button', {
2377
- key: entry.id,
2366
+ key: (entry.episodeId || 'reject') + '-' + index,
2378
2367
  type: 'button',
2379
2368
  className: 'archive-queue-row',
2380
- onClick: function () { openArchive(entry); }
2369
+ onClick: function () { openReject(entry); }
2381
2370
  },
2382
- e('span', null, 'generation ' + formatInt(entry.generation)),
2383
- e('strong', null, shortId(entry.id)),
2384
- e('small', null, formatDate(entry.createdAt))
2371
+ e('span', null, entry.reason || 'reject'),
2372
+ e('strong', { className: advantageTone(entry) }, formatAdvantage(entry.advantage == null ? null : entry.advantage)),
2373
+ e('small', null, shortId(entry.episodeId) + ' / ' + compact(summary.rationaleExcerpt || '', 40))
2385
2374
  );
2386
- }) : e('div', { className: 'queue-empty' }, 'No archived variants yet')
2375
+ }) : e('div', { className: 'queue-empty' }, 'No reject-buffer entries yet')
2387
2376
  )
2388
2377
  );
2389
2378
  }
2390
2379
 
2391
- function newestGeneration(entries) {
2392
- return safeArray(entries).reduce(function (max, entry) {
2393
- return Math.max(max, numberOr(entry.generation, 0));
2394
- }, 0);
2395
- }
2396
-
2397
- function Lineage(props) {
2398
- const entries = safeArray(props.archive && props.archive.entries).slice().sort(function (a, b) {
2399
- return numberOr(b.generation, 0) - numberOr(a.generation, 0) || String(b.createdAt || '').localeCompare(String(a.createdAt || ''));
2400
- }).slice(0, 12);
2401
- if (!entries.length) return e(EmptyState, { title: 'No archived lineage', hint: 'evolve/archive/archive.json has no entries' });
2380
+ function PolicyLedger(props) {
2381
+ const lineage = props.lineage || {};
2382
+ const entries = safeArray(lineage.entries).slice().reverse().slice(0, 12);
2383
+ if (!entries.length) return e(EmptyState, { title: 'No policy ledger', hint: 'self-evolution/policy/ledger.ndjson has no entries' });
2402
2384
  return e('div', { className: 'lineage' },
2403
- entries.map(function (entry) {
2404
- return e('div', { key: entry.id, className: 'lineage-node' },
2405
- e('div', { className: 'gen' }, 'generation ' + formatInt(entry.generation)),
2406
- e('strong', { title: entry.id }, shortId(entry.id)),
2407
- e('span', null, formatDate(entry.createdAt)),
2408
- e('span', null, formatInt(entry.childCount || 0) + ' children')
2385
+ entries.map(function (entry, index) {
2386
+ return e('div', { key: (entry.version != null ? entry.version : index) + '-' + index, className: 'lineage-node' },
2387
+ e('div', { className: 'gen' }, 'v' + formatInt(entry.version)),
2388
+ e('strong', { title: entry.action || '' }, entry.action || 'n/a'),
2389
+ e('span', null, formatDate(entry.at)),
2390
+ e('span', null, compact(entry.reason || '', 40))
2409
2391
  );
2410
2392
  })
2411
2393
  );
@@ -3156,10 +3138,12 @@ export const dashboardClientScript = String.raw `
3156
3138
  function IntroductionView(props) {
3157
3139
  const t = props.t || function (k) { return k; };
3158
3140
  const changes = safeArray(props.changes);
3159
- const runs = safeArray(props.runs);
3160
- const archive = props.archive || { entries: [], generations: [] };
3161
- const archiveEntries = safeArray(archive.entries);
3162
- const archiveGenerations = safeArray(archive.generations);
3141
+ const episodes = safeArray(props.episodes);
3142
+ const lineages = safeArray(props.policyLineages);
3143
+ const ledgerEntries = lineages.reduce(function (sum, lineage) { return sum + safeArray(lineage.entries).length; }, 0);
3144
+ const headVersion = lineages.reduce(function (max, lineage) {
3145
+ return lineage.headVersion == null ? max : Math.max(max, lineage.headVersion);
3146
+ }, 0);
3163
3147
  const openChanges = changes.filter(function (change) { return change.status !== 'completed'; });
3164
3148
  const readyToApply = changes.filter(function (change) {
3165
3149
  return change.hasProposal && change.hasTasks && change.totalTasks > 0 && change.completedTasks < change.totalTasks;
@@ -3171,8 +3155,8 @@ export const dashboardClientScript = String.raw `
3171
3155
  return change.hasTasks && change.totalTasks > 0 && change.completedTasks >= change.totalTasks && (change.hasTestReport || change.hasTestPlan);
3172
3156
  });
3173
3157
  const completed = changes.filter(function (change) { return change.status === 'completed'; });
3174
- const latestRun = runs[0];
3175
- const latestVerdict = latestRun ? runVerdict(latestRun) : { label: 'n/a', tone: 'muted' };
3158
+ const latestEpisode = episodes[0];
3159
+ const latestVerdict = latestEpisode ? episodeVerdict(latestEpisode) : { label: 'n/a', tone: 'muted' };
3176
3160
  const nextChange = archiveReady[0] || readyToReview[0] || readyToApply[0] || openChanges[0] || changes[0];
3177
3161
  const nextLabel = archiveReady.length
3178
3162
  ? t('intro.cta.label.review')
@@ -3230,8 +3214,8 @@ export const dashboardClientScript = String.raw `
3230
3214
  { num: '03', titleKey: 'intro.selfevo.col3.title', bodyKey: 'intro.selfevo.col3.body', tone: 'warn' }
3231
3215
  ];
3232
3216
 
3233
- const archiveGenCount = archiveGenerations.length || Math.max(2, Math.min(8, Math.ceil(completed.length / 3) || 4));
3234
- const archiveEntryCount = archiveEntries.length || completed.length || changes.length || 0;
3217
+ const archiveGenCount = headVersion || Math.max(2, Math.min(8, Math.ceil(completed.length / 3) || 4));
3218
+ const archiveEntryCount = ledgerEntries || completed.length || changes.length || 0;
3235
3219
 
3236
3220
  return e('div', { className: 'ss2', 'aria-label': 'Introduction' },
3237
3221
  // ---------------- Hero (now self-evolving-forward)
@@ -3286,7 +3270,7 @@ export const dashboardClientScript = String.raw `
3286
3270
  ),
3287
3271
  e('div', { className: 'ss2-selfevo-stat' },
3288
3272
  e('span', { className: 'label' }, t('intro.selfevo.stat.candidates')),
3289
- e('span', { className: 'value' }, formatInt(promotableRuns(runs).length))
3273
+ e('span', { className: 'value' }, formatInt(keptEpisodes(episodes).length))
3290
3274
  )
3291
3275
  ),
3292
3276
  e('div', { className: 'ss2-selfevo-cols' },
@@ -3335,7 +3319,7 @@ export const dashboardClientScript = String.raw `
3335
3319
  e('div', { className: 'ss2-kpi' },
3336
3320
  e('span', { className: 'label' }, t('intro.kpi.latestVerdict')),
3337
3321
  e('span', { className: 'value mono' }, latestVerdict.label),
3338
- e('span', { className: 'delta' }, latestRun ? shortId(latestRun.runId) + ' · ' + formatDate(latestRunTime(latestRun)) : t('intro.kpi.latestVerdict.delta.empty'))
3322
+ e('span', { className: 'delta' }, latestEpisode ? shortId(latestEpisode.episodeId) + ' · ' + formatDate(episodeTime(latestEpisode)) : t('intro.kpi.latestVerdict.delta.empty'))
3339
3323
  )
3340
3324
  )
3341
3325
  ),
@@ -3659,8 +3643,8 @@ export const dashboardClientScript = String.raw `
3659
3643
  ),
3660
3644
  e(Panel, { title: 'Evaluation boundary' },
3661
3645
  e('div', { className: 'boundary-note warn' },
3662
- e('strong', null, 'Keep self-evolution in the lab'),
3663
- e('span', null, 'Benchmarks, variants, fitness, and no-benchmark evidence are maintainer tools, not the first-run user interface.')
3646
+ e('strong', null, 'Keep self-evolution in the loop'),
3647
+ e('span', null, 'Two-arm forward, graded advantage, and rollback-before-evolution are maintainer tools, not the first-run user interface.')
3664
3648
  )
3665
3649
  ),
3666
3650
  e(Panel, { title: 'Implementation rule' },
@@ -4020,11 +4004,11 @@ export const dashboardClientScript = String.raw `
4020
4004
  const changes = safeArray(props.changes);
4021
4005
  const cli = safeArray(props.cli);
4022
4006
  const history = safeArray(props.history);
4023
- const runs = safeArray(props.runs);
4007
+ const episodes = safeArray(props.episodes);
4024
4008
  const completed = changes.filter(function (change) { return change.status === 'completed'; }).length;
4025
4009
  const inProgress = changes.filter(function (change) { return change.status === 'in-progress'; }).length;
4026
4010
  const failures = cli.filter(function (event) { return isFailure(event.outcome); }).length;
4027
- const latestRun = runs[0];
4011
+ const latestEpisode = episodes[0];
4028
4012
  return e(React.Fragment, null,
4029
4013
  e(HeroBand, {
4030
4014
  agent: true,
@@ -4033,7 +4017,7 @@ export const dashboardClientScript = String.raw `
4033
4017
  chips: [
4034
4018
  e(Chip, { key: 'changes', label: t('overview.hero.chip.changes'), value: formatInt(changes.length) }),
4035
4019
  e(Chip, { key: 'trace', label: t('overview.hero.chip.trace'), value: formatInt(history.length) }),
4036
- e(Chip, { key: 'runs', label: t('overview.hero.chip.runs'), value: formatInt(runs.length) })
4020
+ e(Chip, { key: 'runs', label: t('overview.hero.chip.runs'), value: formatInt(episodes.length) })
4037
4021
  ]
4038
4022
  }),
4039
4023
  e(PageHead, {
@@ -4053,7 +4037,7 @@ export const dashboardClientScript = String.raw `
4053
4037
  e(Metric, { label: t('overview.metric.completed'), value: formatInt(completed), delta: changes.length ? t('overview.delta.complete', { p: formatPercent(completed / changes.length) }) : t('overview.delta.noChanges') }),
4054
4038
  e(Metric, { label: t('overview.metric.traceEvents'), value: formatInt(history.length), delta: props.live ? t('overview.delta.streamConnected') : t('overview.delta.streamIdle'), className: 'accent' }),
4055
4039
  e(Metric, { label: t('overview.metric.cliFailures'), value: formatInt(failures), delta: t('overview.delta.recentCli', { n: cli.length }) }),
4056
- e(Metric, { label: t('overview.metric.latestEvolve'), value: latestRun ? formatPercent(runScore(latestRun)) : 'n/a', delta: latestRun ? formatDate(latestRunTime(latestRun)) : t('overview.delta.noRuns'), className: 'mono' })
4040
+ e(Metric, { label: t('overview.metric.latestEvolve'), value: latestEpisode ? formatAdvantage(episodeAdvantage(latestEpisode)) : 'n/a', delta: latestEpisode ? formatDate(episodeTime(latestEpisode)) : t('overview.delta.noEpisodes'), className: 'mono' })
4057
4041
  ),
4058
4042
  e('div', { className: 'layout-2' },
4059
4043
  e(Panel, { title: t('overview.section.recentChanges'), meta: t('overview.total', { n: changes.length }), tight: true },
@@ -4071,8 +4055,8 @@ export const dashboardClientScript = String.raw `
4071
4055
  e(CliList, { events: cli, limit: 7, onSelect: props.onSelect })
4072
4056
  ),
4073
4057
  e(Panel, { title: t('overview.section.evoRuns') },
4074
- runs.length ? e('div', { className: 'runs-grid' }, runs.slice(0, 2).map(function (run) {
4075
- return e(RunCard, { key: run.runId, run: run, onSelect: props.onSelect });
4058
+ episodes.length ? e('div', { className: 'runs-grid' }, episodes.slice(0, 2).map(function (ep) {
4059
+ return e(EpisodeCard, { key: ep.episodeId, episode: ep, onSelect: props.onSelect });
4076
4060
  })) : e(EmptyState, { title: t('overview.empty.noRuns'), hint: t('overview.empty.noRunsHint') })
4077
4061
  )
4078
4062
  )
@@ -4193,29 +4177,33 @@ export const dashboardClientScript = String.raw `
4193
4177
 
4194
4178
  function SelfEvolveView(props) {
4195
4179
  const t = props.t || function (k) { return k; };
4196
- const runs = safeArray(props.runs);
4197
- const archive = props.archive || { entries: [], generations: [] };
4198
- const entries = safeArray(archive.entries);
4199
- const latest = runs[0];
4200
- const evidenceRuns = measuredRuns(runs);
4201
- const candidates = promotableRuns(runs);
4202
- const averageScore = evidenceRuns.length
4203
- ? evidenceRuns.reduce(function (sum, run) { return sum + runScore(run); }, 0) / evidenceRuns.length
4204
- : 0;
4180
+ const episodes = safeArray(props.episodes);
4181
+ const lineages = safeArray(props.policyLineages);
4182
+ const reject = safeArray(props.rejectBuffer);
4183
+ const latest = episodes[0];
4184
+ const candidates = keptEpisodes(episodes);
4185
+ const graded = gradedEpisodes(episodes);
4186
+ const meanAdvantage = graded.length
4187
+ ? graded.reduce(function (sum, ep) { return sum + episodeAdvantage(ep); }, 0) / graded.length
4188
+ : null;
4189
+ const headVersion = lineages.reduce(function (max, lineage) {
4190
+ return lineage.headVersion == null ? max : Math.max(max, lineage.headVersion);
4191
+ }, null);
4192
+ const activeLineage = (latest && lineages.find(function (lineage) { return lineage.targetId === latest.targetId; })) || lineages[0] || { entries: [], headVersion: null };
4205
4193
  const [selectedPhaseIndex, setSelectedPhaseIndex] = useState(0);
4206
- const [selectedRunId, setSelectedRunId] = useState(latest && latest.runId ? latest.runId : '');
4194
+ const [selectedEpisodeId, setSelectedEpisodeId] = useState(latest && latest.episodeId ? latest.episodeId : '');
4207
4195
  useEffect(function () {
4208
- if (!latest || !latest.runId) return;
4209
- if (!selectedRunId || !runs.some(function (run) { return run.runId === selectedRunId; })) {
4210
- setSelectedRunId(latest.runId);
4196
+ if (!latest || !latest.episodeId) return;
4197
+ if (!selectedEpisodeId || !episodes.some(function (ep) { return ep.episodeId === selectedEpisodeId; })) {
4198
+ setSelectedEpisodeId(latest.episodeId);
4211
4199
  }
4212
- }, [latest && latest.runId, selectedRunId, runs.length]);
4213
- const latestVerdict = latest ? runVerdict(latest) : { label: 'n/a', tone: 'muted' };
4214
- const openRun = function (run) {
4215
- if (run && run.runId) props.onSelect({ type: 'run', id: run.runId, data: run });
4200
+ }, [latest && latest.episodeId, selectedEpisodeId, episodes.length]);
4201
+ const latestVerdict = latest ? episodeVerdict(latest) : { label: 'n/a', tone: 'muted' };
4202
+ const openEpisode = function (ep) {
4203
+ if (ep && ep.episodeId) props.onSelect({ type: 'episode', id: ep.episodeId, data: ep });
4216
4204
  };
4217
- const openArchive = function (entry) {
4218
- if (entry && entry.id) props.onSelect({ type: 'archive', id: entry.id, data: entry });
4205
+ const openReject = function (entry) {
4206
+ if (entry) props.onSelect({ type: 'reject', id: entry.episodeId, data: entry });
4219
4207
  };
4220
4208
  return e(React.Fragment, null,
4221
4209
  e(PageHead, {
@@ -4223,45 +4211,46 @@ export const dashboardClientScript = String.raw `
4223
4211
  title: t('evolve.title'),
4224
4212
  subhead: t('evolve.lead'),
4225
4213
  actions: [
4226
- e(Chip, { key: 'runs', label: t('evolve.chip.runs'), value: formatInt(runs.length) }),
4227
- e(Chip, { key: 'archive', label: t('evolve.chip.archive'), value: formatInt(entries.length) })
4214
+ e(Chip, { key: 'episodes', label: t('evolve.chip.episodes'), value: formatInt(episodes.length) }),
4215
+ e(Chip, { key: 'version', label: t('evolve.chip.version'), value: headVersion == null ? 'n/a' : 'v' + headVersion })
4228
4216
  ]
4229
4217
  }),
4230
4218
  e('section', { className: 'evolve-command-center', 'aria-label': 'Self-evolution command center' },
4231
4219
  e('div', { className: 'evolve-status-strip', 'aria-label': 'Self-evolution operational summary' },
4232
- e(StatusItem, { label: t('evolve.metric.latestVerdict'), value: latestVerdict.label, detail: latest ? shortId(latest.runId) : t('evolve.metric.waitingRuns'), tone: latestVerdict.tone }),
4233
- e(StatusItem, { label: t('evolve.metric.candidates'), value: formatInt(candidates.length), detail: evidenceRuns.length ? t('evolve.metric.candidates.detail', { p: formatPercent(averageScore) }) : t('evolve.metric.candidates.detailEmpty'), tone: candidates.length ? 'info' : 'bad' })
4220
+ e(StatusItem, { label: t('evolve.metric.latestStage'), value: latestVerdict.label, detail: latest ? shortId(latest.episodeId) : t('evolve.metric.waitingEpisodes'), tone: latestVerdict.tone }),
4221
+ e(StatusItem, { label: t('evolve.metric.candidates'), value: formatInt(candidates.length), detail: graded.length ? t('evolve.metric.candidates.detail', { p: formatAdvantage(meanAdvantage) }) : t('evolve.metric.candidates.detailEmpty'), tone: candidates.length ? 'info' : 'bad' })
4234
4222
  ),
4235
4223
  e(NextActionBar, { latest: latest, onSelect: props.onSelect, t: t })
4236
4224
  ),
4237
4225
  e('div', { className: 'evolution-studio' },
4238
4226
  e(EvolutionStage, {
4239
- runs: runs,
4240
- archive: archive,
4227
+ episodes: episodes,
4228
+ policyLineages: lineages,
4241
4229
  phaseIndex: selectedPhaseIndex,
4242
4230
  onPhaseSelect: setSelectedPhaseIndex,
4243
- selectedRunId: selectedRunId,
4244
- onRunSelect: setSelectedRunId,
4245
- onOpenRun: openRun,
4246
- onOpenArchive: openArchive
4231
+ selectedEpisodeId: selectedEpisodeId,
4232
+ onEpisodeSelect: setSelectedEpisodeId,
4233
+ onOpenEpisode: openEpisode,
4234
+ onOpenReject: openReject
4247
4235
  }),
4248
4236
  e(EvolutionInspector, {
4249
- runs: runs,
4250
- archive: archive,
4237
+ episodes: episodes,
4238
+ policyLineages: lineages,
4239
+ rejectBuffer: reject,
4251
4240
  phaseIndex: selectedPhaseIndex,
4252
4241
  onPhaseSelect: setSelectedPhaseIndex,
4253
- selectedRunId: selectedRunId,
4254
- onRunSelect: setSelectedRunId,
4255
- onOpenRun: openRun,
4256
- onOpenArchive: openArchive
4242
+ selectedEpisodeId: selectedEpisodeId,
4243
+ onEpisodeSelect: setSelectedEpisodeId,
4244
+ onOpenEpisode: openEpisode,
4245
+ onOpenReject: openReject
4257
4246
  })
4258
4247
  ),
4259
- e(Panel, { title: t('evolve.section.history'), meta: t('evolve.runs.label', { n: runs.length }), tight: true },
4260
- e(RunHistoryTable, { runs: runs, onSelect: props.onSelect })
4248
+ e(Panel, { title: t('evolve.section.history'), meta: t('evolve.episodes.label', { n: episodes.length }), tight: true },
4249
+ e(EpisodeHistoryTable, { episodes: episodes, onSelect: props.onSelect })
4261
4250
  ),
4262
4251
  e('div', { style: { height: '16px' } }),
4263
- e(Panel, { title: t('evolve.section.lineage'), meta: t('evolve.variants.label', { n: entries.length }) },
4264
- e(Lineage, { archive: archive })
4252
+ e(Panel, { title: t('evolve.section.lineage'), meta: t('evolve.ledger.label', { n: headVersion == null ? 0 : headVersion }) },
4253
+ e(PolicyLedger, { lineage: activeLineage })
4265
4254
  )
4266
4255
  );
4267
4256
  }
@@ -4277,9 +4266,9 @@ export const dashboardClientScript = String.raw `
4277
4266
  useEffect(function () {
4278
4267
  let cancelled = false;
4279
4268
  setDetail(null);
4280
- if (!selected || (selected.type !== 'run' && selected.type !== 'change')) return undefined;
4281
- const url = selected.type === 'run'
4282
- ? '/api/evolve/runs/' + encodeURIComponent(selected.id)
4269
+ if (!selected || (selected.type !== 'episode' && selected.type !== 'change')) return undefined;
4270
+ const url = selected.type === 'episode'
4271
+ ? '/api/episodes/' + encodeURIComponent(selected.id)
4283
4272
  : '/api/changes/' + encodeURIComponent(selected.id);
4284
4273
  setLoading(true);
4285
4274
  loadEndpoint(url, selected.data || {}).then(function (result) {
@@ -4330,16 +4319,16 @@ export const dashboardClientScript = String.raw `
4330
4319
 
4331
4320
  if (!selected) return null;
4332
4321
  const data = detail || selected.data || {};
4333
- const title = selected.type === 'run'
4334
- ? 'Evolution run'
4322
+ const title = selected.type === 'episode'
4323
+ ? 'Episode'
4335
4324
  : selected.type === 'change'
4336
4325
  ? 'Change detail'
4337
4326
  : selected.type === 'cli'
4338
4327
  ? 'CLI event'
4339
- : selected.type === 'archive'
4340
- ? 'Archive variant'
4328
+ : selected.type === 'reject' || selected.type === 'ledger'
4329
+ ? 'Reject-buffer entry'
4341
4330
  : 'Trace event';
4342
- const subtitle = selected.id || data.runId || data.id || data.eventId || 'detail';
4331
+ const subtitle = selected.id || data.episodeId || data.id || data.eventId || 'detail';
4343
4332
  const titleId = 'detail-drawer-title';
4344
4333
  return e(React.Fragment, null,
4345
4334
  e('div', { className: 'modal-backdrop', onClick: onClose }),
@@ -4354,7 +4343,6 @@ export const dashboardClientScript = String.raw `
4354
4343
  e('div', { className: 'slide-body' },
4355
4344
  loading ? e('div', { className: 'empty-hint' }, 'loading detail') : null,
4356
4345
  e(DetailSummary, { type: selected.type, data: data }),
4357
- selected.type === 'run' ? e(RunDiagnostics, { data: data }) : null,
4358
4346
  e('div', { className: 'section-title' }, 'Payload'),
4359
4347
  e('pre', {
4360
4348
  style: {
@@ -4374,58 +4362,20 @@ export const dashboardClientScript = String.raw `
4374
4362
  );
4375
4363
  }
4376
4364
 
4377
- function LogBlock(props) {
4378
- if (!props.text) return null;
4379
- return e(React.Fragment, null,
4380
- e('div', { className: 'section-title' }, props.title),
4381
- e('pre', {
4382
- style: {
4383
- margin: '0 0 12px',
4384
- padding: '12px',
4385
- border: '1px solid var(--line)',
4386
- borderRadius: '8px',
4387
- background: 'var(--surface-2)',
4388
- color: 'var(--ink-2)',
4389
- font: '11px/1.5 var(--mono)',
4390
- maxHeight: '320px',
4391
- overflow: 'auto',
4392
- whiteSpace: 'pre-wrap'
4393
- }
4394
- }, props.text)
4395
- );
4396
- }
4397
-
4398
- function RunDiagnostics(props) {
4399
- const data = props.data || {};
4400
- const fileListing = safeArray(data.fileListing);
4401
- const hasAny = data.wrapperStderrTail || data.wrapperStdoutTail || fileListing.length;
4402
- if (!hasAny) return null;
4403
- return e('div', { style: { marginTop: '12px' } },
4404
- e(LogBlock, { title: 'wrapper.stderr.log (tail)', text: data.wrapperStderrTail }),
4405
- e(LogBlock, { title: 'wrapper.stdout.log (tail)', text: data.wrapperStdoutTail }),
4406
- fileListing.length ? e(React.Fragment, null,
4407
- e('div', { className: 'section-title' }, 'Run directory'),
4408
- e('div', { style: { marginBottom: '12px', font: '11px/1.5 var(--mono)', color: 'var(--ink-2)' } },
4409
- fileListing.join(' '))
4410
- ) : null
4411
- );
4412
- }
4413
-
4414
4365
  function DetailSummary(props) {
4415
4366
  const data = props.data || {};
4416
4367
  let rows;
4417
- if (props.type === 'run') {
4368
+ if (props.type === 'episode') {
4418
4369
  rows = [
4419
- ['run', data.runId],
4420
- ['status', data.status || (data.verdictCounts ? 'completed' : 'unknown')],
4421
- ['benchmark', data.benchmarkId],
4422
- ['variant', data.harnessVariant],
4423
- ['pass rate', data.passRate !== undefined ? formatPercent(data.passRate) : formatPercent(runScore(data))],
4424
- ['tasks', data.taskCount || safeArray(data.tasks).length],
4425
- ['cost', formatUsd(data.totalCostUsd)],
4426
- ['wall time', formatDuration(data.totalWallTimeMs)],
4427
- ['started', formatDate(data.startedAt)],
4428
- ['finished', formatDate(data.finishedAt)]
4370
+ ['episode', data.episodeId],
4371
+ ['stage', data.stage],
4372
+ ['change', data.changeName],
4373
+ ['target', data.targetId],
4374
+ ['advantage', data.advantage != null ? formatAdvantage(data.advantage) : 'abstained'],
4375
+ ['main version', data.policyVersionMain == null ? 'n/a' : 'v' + data.policyVersionMain],
4376
+ ['baseline version', data.policyVersionBaseline == null ? 'n/a' : 'v' + data.policyVersionBaseline],
4377
+ ['created', formatDate(data.createdAt)],
4378
+ ['updated', formatDate(data.updatedAt)]
4429
4379
  ];
4430
4380
  } else if (props.type === 'change') {
4431
4381
  rows = [
@@ -4446,14 +4396,15 @@ export const dashboardClientScript = String.raw `
4446
4396
  ['started', formatDate(data.startedAt)],
4447
4397
  ['finished', formatDate(data.finishedAt)]
4448
4398
  ];
4449
- } else if (props.type === 'archive') {
4399
+ } else if (props.type === 'reject' || props.type === 'ledger') {
4400
+ const summary = safeObject(data.editSummary);
4450
4401
  rows = [
4451
- ['variant', data.id],
4452
- ['generation', formatInt(data.generation)],
4453
- ['parent', data.parentId],
4454
- ['children', formatInt(data.childCount || 0)],
4455
- ['created', formatDate(data.createdAt)],
4456
- ['fitness', data.fitness !== undefined ? formatPercent(data.fitness) : data.score !== undefined ? formatPercent(data.score) : 'n/a']
4402
+ ['episode', data.episodeId],
4403
+ ['from', data.fromVersion == null ? 'n/a' : 'v' + data.fromVersion],
4404
+ ['to', data.toVersion == null ? 'n/a' : 'v' + data.toVersion],
4405
+ ['advantage', data.advantage != null ? formatAdvantage(data.advantage) : 'n/a'],
4406
+ ['reason', data.reason],
4407
+ ['rationale', summary.rationaleExcerpt]
4457
4408
  ];
4458
4409
  } else {
4459
4410
  rows = [
@@ -4746,8 +4697,9 @@ export const dashboardClientScript = String.raw `
4746
4697
  const [changes, setChanges] = useState(initial && Array.isArray(initial.changes) ? initial.changes : []);
4747
4698
  const [cli, setCli] = useState(initial && Array.isArray(initial.cliHistory) ? initial.cliHistory : []);
4748
4699
  const [agentInterface, setAgentInterface] = useState(initial && initial.agentInterface ? initial.agentInterface : {});
4749
- const [runs, setRuns] = useState(initial && Array.isArray(initial.runs) ? initial.runs : []);
4750
- const [archive, setArchive] = useState(initial && initial.archive ? initial.archive : { entries: [], generations: [] });
4700
+ const [episodes, setEpisodes] = useState(initial && Array.isArray(initial.episodes) ? initial.episodes : []);
4701
+ const [policyLineages, setPolicyLineages] = useState(initial && Array.isArray(initial.policyLineages) ? initial.policyLineages : []);
4702
+ const [rejectBuffer, setRejectBuffer] = useState(initial && Array.isArray(initial.rejectBuffer) ? initial.rejectBuffer : []);
4751
4703
  const [architecture, setArchitecture] = useState(initial && initial.architecture ? initial.architecture : {});
4752
4704
  const [history, setHistory] = useState(initial && Array.isArray(initial.history) ? initial.history : []);
4753
4705
  const [streamEvents, setStreamEvents] = useState([]);
@@ -4762,8 +4714,9 @@ export const dashboardClientScript = String.raw `
4762
4714
  loadEndpoint(API.changes, []),
4763
4715
  loadEndpoint(API.cliHistory, []),
4764
4716
  loadEndpoint(API.agentInterface, {}),
4765
- loadEndpoint(API.evolveRuns, []),
4766
- loadEndpoint(API.evolveArchive, { entries: [], generations: [] }),
4717
+ loadEndpoint(API.episodes, []),
4718
+ loadEndpoint(API.policyLedger, []),
4719
+ loadEndpoint(API.rejectBuffer, []),
4767
4720
  loadEndpoint(API.history, []),
4768
4721
  loadEndpoint(API.architecture, {})
4769
4722
  ]);
@@ -4771,10 +4724,11 @@ export const dashboardClientScript = String.raw `
4771
4724
  setChanges(safeArray(results[1].value));
4772
4725
  setCli(safeArray(results[2].value));
4773
4726
  setAgentInterface(safeObject(results[3].value));
4774
- setRuns(safeArray(results[4].value));
4775
- setArchive(safeObject(results[5].value));
4776
- setHistory(safeArray(results[6].value));
4777
- setArchitecture(safeObject(results[7].value));
4727
+ setEpisodes(safeArray(results[4].value));
4728
+ setPolicyLineages(safeArray(results[5].value));
4729
+ setRejectBuffer(safeArray(results[6].value));
4730
+ setHistory(safeArray(results[7].value));
4731
+ setArchitecture(safeObject(results[8].value));
4778
4732
  const errors = results.filter(function (result) { return !result.ok; }).map(function (result) { return result.error; });
4779
4733
  setError(errors.join(' | '));
4780
4734
  setLoading(false);
@@ -4822,10 +4776,10 @@ export const dashboardClientScript = String.raw `
4822
4776
  openChanges: openChanges,
4823
4777
  readyToLearn: readyToLearn,
4824
4778
  totalTrace: history.length,
4825
- totalRuns: runs.length,
4779
+ totalEpisodes: episodes.length,
4826
4780
  totalAgentSignals: numberOr(agentSummary.totalSignals, 0)
4827
4781
  };
4828
- }, [changes, history, runs, agentInterface]);
4782
+ }, [changes, history, episodes, agentInterface]);
4829
4783
 
4830
4784
  const tickerItems = streamEvents.length ? streamEvents : sortByTimeDesc(history).slice(0, 10);
4831
4785
  const setView = useCallback(function (nextView) {
@@ -4840,8 +4794,9 @@ export const dashboardClientScript = String.raw `
4840
4794
  changes: changes,
4841
4795
  cli: cli,
4842
4796
  agentInterface: agentInterface,
4843
- runs: runs,
4844
- archive: archive,
4797
+ episodes: episodes,
4798
+ policyLineages: policyLineages,
4799
+ rejectBuffer: rejectBuffer,
4845
4800
  history: history,
4846
4801
  architecture: architecture,
4847
4802
  streamEvents: streamEvents,