@bizagent/dsh 0.0.0-stage → 1.0.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 (181) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/LICENSE +21 -0
  3. package/README.md +61 -2
  4. package/cordis.patch.yml +14 -0
  5. package/docs/README.md +23 -0
  6. package/docs/architecture.md +154 -0
  7. package/docs/concepts.md +192 -0
  8. package/docs/configuration.md +154 -0
  9. package/docs/quickstart.md +103 -0
  10. package/docs/quickstart.zh-CN.md +86 -0
  11. package/docs/tools.md +607 -0
  12. package/docs/user-guide.zh-CN.md +132 -0
  13. package/docs/verification.md +69 -0
  14. package/examples/README.md +64 -0
  15. package/examples/demo-runtime.mjs +71 -0
  16. package/examples/learning-lab.mjs +263 -0
  17. package/examples/scenarios.mjs +45 -0
  18. package/lib/agent-preset.d.ts +20 -0
  19. package/lib/agent-preset.js +46 -0
  20. package/lib/agent-session-address.d.ts +14 -0
  21. package/lib/agent-session-address.js +15 -0
  22. package/lib/agent-session-coordinator.d.ts +24 -0
  23. package/lib/agent-session-coordinator.js +101 -0
  24. package/lib/cli.d.ts +3 -0
  25. package/lib/cli.js +142 -0
  26. package/lib/client-types/agent-session-address.d.ts +13 -0
  27. package/lib/client-types/client/agent-mentions.d.ts +3 -0
  28. package/lib/client-types/client/api.d.ts +180 -0
  29. package/lib/client-types/client/components.d.ts +22 -0
  30. package/lib/client-types/client/controller.d.ts +23 -0
  31. package/lib/client-types/client/frame-participants.d.ts +5 -0
  32. package/lib/client-types/client/home-name.d.ts +9 -0
  33. package/lib/client-types/client/human-inbox.d.ts +8 -0
  34. package/lib/client-types/client/index.d.ts +13 -0
  35. package/lib/client-types/client/locales.d.ts +796 -0
  36. package/lib/client-types/client/markdown.d.ts +12 -0
  37. package/lib/client-types/client/project-artifact-link.d.ts +10 -0
  38. package/lib/client-types/client/project-outcome-review-inbox.d.ts +13 -0
  39. package/lib/client-types/client/projects.d.ts +11 -0
  40. package/lib/client-types/client/sample-guide.d.ts +9 -0
  41. package/lib/client-types/client/session-resource.d.ts +34 -0
  42. package/lib/client-types/client/session-surfaces.d.ts +24 -0
  43. package/lib/client-types/client/styles.d.ts +1 -0
  44. package/lib/client-types/config.d.ts +55 -0
  45. package/lib/client-types/cron.d.ts +8 -0
  46. package/lib/client-types/domain.d.ts +818 -0
  47. package/lib/client-types/frame-reference.d.ts +4 -0
  48. package/lib/client-types/home-directory.d.ts +5 -0
  49. package/lib/client-types/home-space.d.ts +49 -0
  50. package/lib/client-types/home-store.d.ts +125 -0
  51. package/lib/client-types/inbox-stalled-topic.d.ts +15 -0
  52. package/lib/client-types/inbox-wake-policy.d.ts +18 -0
  53. package/lib/client-types/known-format.d.ts +2 -0
  54. package/lib/client-types/learning-scheduler.d.ts +36 -0
  55. package/lib/client-types/learning-session-runner.d.ts +49 -0
  56. package/lib/client-types/learning-trigger.d.ts +17 -0
  57. package/lib/client-types/learning-usage.d.ts +17 -0
  58. package/lib/client-types/ledger.d.ts +190 -0
  59. package/lib/client-types/local-owner.d.ts +7 -0
  60. package/lib/client-types/message-source.d.ts +9 -0
  61. package/lib/client-types/organization-location.d.ts +19 -0
  62. package/lib/client-types/project-ledger.d.ts +182 -0
  63. package/lib/client-types/project-seat-context.d.ts +17 -0
  64. package/lib/client-types/safe-file.d.ts +23 -0
  65. package/lib/client-types/sample-organization.d.ts +9 -0
  66. package/lib/client-types/service.d.ts +596 -0
  67. package/lib/client-types/shared-knowledge.d.ts +116 -0
  68. package/lib/client-types/ui-contract.d.ts +276 -0
  69. package/lib/client-types/untrusted-text.d.ts +15 -0
  70. package/lib/client-types/work-ledger.d.ts +108 -0
  71. package/lib/client-types/work-output-eval.d.ts +15 -0
  72. package/lib/client-types/worklog-ledger.d.ts +88 -0
  73. package/lib/client-types/worklog-snapshot.d.ts +33 -0
  74. package/lib/client.js +5463 -0
  75. package/lib/config.d.ts +56 -0
  76. package/lib/config.js +115 -0
  77. package/lib/cron.d.ts +9 -0
  78. package/lib/cron.js +127 -0
  79. package/lib/domain.d.ts +819 -0
  80. package/lib/domain.js +662 -0
  81. package/lib/frame-admission.d.ts +6 -0
  82. package/lib/frame-admission.js +39 -0
  83. package/lib/frame-reference.d.ts +5 -0
  84. package/lib/frame-reference.js +12 -0
  85. package/lib/home-archive.d.ts +17 -0
  86. package/lib/home-archive.js +177 -0
  87. package/lib/home-directory.d.ts +6 -0
  88. package/lib/home-directory.js +38 -0
  89. package/lib/home-space.d.ts +50 -0
  90. package/lib/home-space.js +120 -0
  91. package/lib/home-store.d.ts +126 -0
  92. package/lib/home-store.js +837 -0
  93. package/lib/host.d.ts +22 -0
  94. package/lib/host.js +15 -0
  95. package/lib/human-inbox-transport.d.ts +23 -0
  96. package/lib/human-inbox-transport.js +105 -0
  97. package/lib/human-notification-runner.d.ts +83 -0
  98. package/lib/human-notification-runner.js +138 -0
  99. package/lib/human-notification-webhook.d.ts +18 -0
  100. package/lib/human-notification-webhook.js +102 -0
  101. package/lib/human-turn-completion.d.ts +61 -0
  102. package/lib/human-turn-completion.js +157 -0
  103. package/lib/inbox-return-notifier.d.ts +46 -0
  104. package/lib/inbox-return-notifier.js +279 -0
  105. package/lib/inbox-stalled-escalation-runner.d.ts +31 -0
  106. package/lib/inbox-stalled-escalation-runner.js +68 -0
  107. package/lib/inbox-stalled-topic.d.ts +16 -0
  108. package/lib/inbox-stalled-topic.js +44 -0
  109. package/lib/inbox-wake-policy.d.ts +19 -0
  110. package/lib/inbox-wake-policy.js +28 -0
  111. package/lib/inbox-wake-runner.d.ts +45 -0
  112. package/lib/inbox-wake-runner.js +481 -0
  113. package/lib/index.d.ts +29 -0
  114. package/lib/index.js +480 -0
  115. package/lib/known-format.d.ts +3 -0
  116. package/lib/known-format.js +8 -0
  117. package/lib/learning-scheduler.d.ts +37 -0
  118. package/lib/learning-scheduler.js +182 -0
  119. package/lib/learning-session-runner.d.ts +50 -0
  120. package/lib/learning-session-runner.js +435 -0
  121. package/lib/learning-trigger.d.ts +18 -0
  122. package/lib/learning-trigger.js +92 -0
  123. package/lib/learning-usage.d.ts +18 -0
  124. package/lib/learning-usage.js +49 -0
  125. package/lib/ledger.d.ts +191 -0
  126. package/lib/ledger.js +752 -0
  127. package/lib/local-human-host.d.ts +11 -0
  128. package/lib/local-human-host.js +191 -0
  129. package/lib/local-human-token.d.ts +9 -0
  130. package/lib/local-human-token.js +32 -0
  131. package/lib/local-owner.d.ts +8 -0
  132. package/lib/local-owner.js +26 -0
  133. package/lib/message-source.d.ts +10 -0
  134. package/lib/message-source.js +2 -0
  135. package/lib/organization-location.d.ts +20 -0
  136. package/lib/organization-location.js +80 -0
  137. package/lib/organization-workspace.d.ts +22 -0
  138. package/lib/organization-workspace.js +32 -0
  139. package/lib/project-ledger.d.ts +183 -0
  140. package/lib/project-ledger.js +189 -0
  141. package/lib/project-review-transport.d.ts +23 -0
  142. package/lib/project-review-transport.js +155 -0
  143. package/lib/project-seat-context.d.ts +18 -0
  144. package/lib/project-seat-context.js +34 -0
  145. package/lib/proposal-review-transport.d.ts +26 -0
  146. package/lib/proposal-review-transport.js +104 -0
  147. package/lib/route-guard.d.ts +31 -0
  148. package/lib/route-guard.js +80 -0
  149. package/lib/safe-file.d.ts +24 -0
  150. package/lib/safe-file.js +93 -0
  151. package/lib/sample-organization.d.ts +10 -0
  152. package/lib/sample-organization.js +46 -0
  153. package/lib/service.d.ts +597 -0
  154. package/lib/service.js +4076 -0
  155. package/lib/session-surface.d.ts +11 -0
  156. package/lib/session-surface.js +109 -0
  157. package/lib/shared-knowledge-review-transport.d.ts +28 -0
  158. package/lib/shared-knowledge-review-transport.js +85 -0
  159. package/lib/shared-knowledge.d.ts +117 -0
  160. package/lib/shared-knowledge.js +240 -0
  161. package/lib/tools.d.ts +7 -0
  162. package/lib/tools.js +955 -0
  163. package/lib/ui-contract.d.ts +277 -0
  164. package/lib/ui-contract.js +12 -0
  165. package/lib/ui-host.d.ts +24 -0
  166. package/lib/ui-host.js +471 -0
  167. package/lib/untrusted-text.d.ts +16 -0
  168. package/lib/untrusted-text.js +50 -0
  169. package/lib/work-ledger.d.ts +109 -0
  170. package/lib/work-ledger.js +107 -0
  171. package/lib/work-output-eval.d.ts +16 -0
  172. package/lib/work-output-eval.js +58 -0
  173. package/lib/work-runner.d.ts +47 -0
  174. package/lib/work-runner.js +352 -0
  175. package/lib/worklog-ledger.d.ts +89 -0
  176. package/lib/worklog-ledger.js +91 -0
  177. package/lib/worklog-snapshot.d.ts +34 -0
  178. package/lib/worklog-snapshot.js +100 -0
  179. package/lib/writer-guard.d.ts +11 -0
  180. package/lib/writer-guard.js +207 -0
  181. package/package.json +172 -4
@@ -0,0 +1,132 @@
1
+ # 用户指南
2
+
3
+ [文档导航](README.md) · 尚未安装?先看[快速开始](quickstart.zh-CN.md)。
4
+
5
+ 只用个人记忆,可从[存下一条规则](quickstart.zh-CN.md#第一次成功存下一条规则)开始。
6
+ 想试团队协作,直接做下面的虚构订单练习;已有组织和未建组织的环境都可用。
7
+
8
+ ## 入口与状态速查
9
+
10
+ | 你要做什么 | 从哪里进入 | 看什么 |
11
+ | --- | --- | --- |
12
+ | 确认当前 Agent 属于谁 | 会话顶部的 Home 按钮 | Home 名称,以及日常/主/项目/工作会话类型 |
13
+ | 查规则是否保存 | Home 按钮 → 学习 | 资产正文、证据来源、active/candidate 状态 |
14
+ | 查派工和交付 | 会话的「工作与经验」页签 | 工作状态、结果正文、产物、查看执行会话 |
15
+ | 从 Worker 回到负责人 | 工作与经验 → 返回派工会话 | 原来的派工会话 |
16
+ | 找项目并人工验收 | 工作与经验 → 项目;或 BizAgent → 项目 → 我的审核待办 | 当前提交、证据、验收或返工理由 |
17
+ | 回信给 Agent | 会话顶部或 BizAgent 中的「我的收件箱」 | 回复内容、发送回复、标记已处理 |
18
+ | 查这一轮产生了什么 | 回答下方的「本轮留下…项记录」 | 该轮成功保存的经验、工作和项目记录 |
19
+
20
+ 空白新会话需要先发送消息,才会出现完整的会话入口。学习页点击「对话」会重开该 Home 的长期主会话;
21
+ 新项目有自己的项目主会话,正式 Work 在另一段执行会话中完成。
22
+
23
+ 三种状态分别回答不同问题:
24
+
25
+ - **运行中 / 空闲 / 未加载**:DSH Agent 当前是否正在执行;未加载本身不表示失败。
26
+ - **待接单 / 已接单 / 已完成**:正式 Work 的进度;完成后展开结果检查交付内容。
27
+ - **推进中 / 待验收 / 已验收**:Project 的交付与审核进度;页面验收后为 `accepted`,项目详情也可能显示「已通过」。
28
+
29
+ 回执中的「记录时」描述历史,当前状态以工作卡片或项目详情为准。「工作与经验」只列当前会话派出或承接的工作;
30
+ 项目列表也受当前 Home 的参与权限影响。找不到时返回负责人/项目会话,或查看自己的审核待办。
31
+
32
+ ## 完成一次协作练习
33
+
34
+ ### 1. 准备两个练习 Home
35
+
36
+ 在 **BizAgent 组织 → 学习 → 创建 Home** 分别填写:
37
+
38
+ | 字段 | 负责人 | 分析师 |
39
+ | --- | --- | --- |
40
+ | Home 类型 | Personal | Personal |
41
+ | 唯一标识 | `practice-lead` | `practice-analyst` |
42
+ | 显示名称 | 练习负责人 | 练习分析师 |
43
+ | 初始 Identity | 负责上手练习项目的派工、复核和提交,只处理练习数据。 | 承接上手练习的正式 Work,独立核对订单,交付报告和计算日志。 |
44
+
45
+ 将生成 `personal:practice-lead` 和 `personal:practice-analyst`。编号已存在时换用新编号,并同步替换后面的提示语。
46
+ 已有组织时练习 Home 创建在当前组织空间;`Personal` 表示成员类型,和外部的「我的个人 Home」分开。
47
+ 当前本机用户拥有新建的两个 Home,满足自动派工的同 owner 条件。
48
+
49
+ **检查**:学习页能分别选择两个 Home。选择负责人,点「对话」,确认会话顶部显示「练习负责人」。
50
+
51
+ ### 2. 教负责人一条规则
52
+
53
+ 在负责人的主会话发送:
54
+
55
+ > 记住这条仅用于上手练习的规则:订单 GMV 只计 paid,排除 cancelled 和 refunded。
56
+ > 请实际保存到当前 Home,告诉我资产编号和状态。本轮只保存规则。
57
+
58
+ **检查**:从 Home 按钮进入学习页,展开资产,核对正文、来源和 `active` 状态。
59
+ 如果是候选,先核对依据再请 Agent 发布,不要把候选当成已生效规则。
60
+
61
+ ### 3. 创建项目,再进入项目对话
62
+
63
+ 回到负责人的主会话发送:
64
+
65
+ > 创建 BizAgent 项目「上手练习:订单 GMV 核对」。目标是让练习分析师独立核对四笔虚构订单。
66
+ > 完成标准:正式 Work 在分析师自己的会话接单并完成,交付逐笔报告和计算日志,负责人复核后提交给我人工验收。
67
+ > 本轮只创建项目,订单稍后在项目对话提供。
68
+
69
+ **检查**:在「工作与经验」的项目栏点这个项目,再点「打开项目对话」。
70
+ 会话顶部应显示「项目主会话」及项目名称,和刚才的 Agent 主会话分开。
71
+
72
+ ### 4. 在项目对话派工
73
+
74
+ 发送:
75
+
76
+ > 先读取当前 Home 保存的上手练习 GMV 规则。订单是 A:paid 100 元,B:paid 200 元,
77
+ > C:cancelled 50 元,D:refunded 30 元。请关联当前项目,向 personal:practice-analyst 派正式 Work。
78
+ > 请分析师在独立会话接单并核对,将逐笔报告和计算日志保存到 artifacts/ 并随结果交付。
79
+ > 结果返回后请复核产物,提交给我人工验收。
80
+
81
+ **检查**:「工作与经验」出现负责人 → 分析师的工作卡片。用「查看执行会话」核对分析师的 Home 和接单记录,
82
+ 再用「返回派工会话」回到项目。结果会自动回传;交付前不要重复派同一个任务。
83
+
84
+ ### 5. 看结果,然后亲自验收
85
+
86
+ 正确结果为 **300 元**:A、B 计入,C、D 排除。展开 Work 的结果检查逐笔说明和产物。
87
+
88
+ 负责人提交后,从项目卡片进入详情:查看当前交付与「下载提交版本」,填写「验收或返工理由」,
89
+ 点击「验收通过」;不满足标准时点击「退回返工」。也可从「我的审核待办」查找待验收结果。
90
+
91
+ **成功标志**:Work 为「已完成」,Project 为「已验收」,结果和已下载的提交版本一致。
92
+ 若退回返工,负责人需修订并重新提交,你审核的是新版本。
93
+
94
+ ### 6. 可选:验证收件箱回信
95
+
96
+ 在项目对话发送:
97
+
98
+ > 读取当前 Home 的 owner,向对应的用户地址(user: 后接实际 owner)发一封普通 Inbox 信件,标题为「上手练习:核对完成」,
99
+ > 简短告知结果与项目状态,然后结束本轮。
100
+
101
+ 打开「我的收件箱」,回复“收到,请确认这条回复,不再派工或修改记忆”,并标记原信已处理。
102
+
103
+ **检查**:回到原项目会话,它自动接收到你的回复并确认。发信/已处理记录与 Work、Project 的状态各自保存。
104
+
105
+ ## 该用哪种协作方式
106
+
107
+ | 你的目的 | 用什么 | 行为边界 |
108
+ | --- | --- | --- |
109
+ | 当前对话参考另一个 Home 的知识 | `@` 选择共享 Home / Frame | 在同一会话加入只读上下文,检查输入区 Frame 状态 |
110
+ | 给另一个 Agent 留信或提问 | Inbox | 对方在自己的会话读取、回复;送达和已处理各有记录 |
111
+ | 让对方负责一份明确交付 | 正式 Work | 约定目标和验收标准,在独立工作会话接单、完成 |
112
+ | 围绕长期目标组织交付并由人确认 | Project | 关联 Work,汇总证据,提交后由人验收 |
113
+ | 把经验保存到另一个 Home | Proposal | 目标方主人接受后才进入目标 Home |
114
+
115
+ `@` 候选提供 Business、Role、Capability 共享 Home,不列出其他 Personal 成员 Home。
116
+ 输入 `@`,从带 Home 地址的候选中选中再发送;在输入区查看加载状态,失败时先处理加载提示。
117
+ 给分析师派工时直接使用它的地址即可,无需先 `@` 它。
118
+ Frame 条目上的「主导」「协作」描述上下文角色,独立执行要看正式 Work 及执行会话。
119
+
120
+ ## 内置示例团队
121
+
122
+ 首次组织设置中的「创建示例团队」会提供 Sample Shop、两个成员、共享角色/能力、`orders.csv` 和四步向导。
123
+ 已有组织时继续用上面的独立练习即可。
124
+
125
+ 内置数据排除 `cancelled`、`refunded`,保留 `paid` 和 `shipped`:
126
+ **120.50 + 45.25 + 200.00 + 34.75 = 400.50 元**。
127
+ 它和上面的四笔订单使用不同数据、不同口径。
128
+
129
+ 想继续体验知识分享,可让分析师把规则连同依据提议给 `business:sample-shop`,再到该 Home 的学习页审批。
130
+
131
+ 进阶时再读:[核心概念](concepts.md)、[配置与备份](configuration.md)、[兼容与验证](verification.md)。
132
+ 遇到入口、权限或模型问题,先看[排查表](quickstart.zh-CN.md#遇到问题时)。
@@ -0,0 +1,69 @@
1
+ # Verification and compatibility
2
+
3
+ [Documentation](README.md) · [中文上手](quickstart.zh-CN.md)
4
+
5
+ This page separates development baselines, manual acceptance, and reproducible checks.
6
+
7
+ ## Version baselines
8
+
9
+ | Baseline | Version | Evidence |
10
+ | --- | --- | --- |
11
+ | Package | `@bizagent/dsh` 1.0.0 | Package manifest and [release notes](../CHANGELOG.md). |
12
+ | Installation / development / CI runtime | DSH `0.2.1-rc.2` | Installation guide, dev dependencies, and runtime CI use the same version. DSH peers declare `^0.2.1-rc.2`. |
13
+ | Manual browser acceptance, 2026-10-20 | DSH `0.2.1-rc.2` | The 1.0.0 implementation used in DSH Web with DeepSeek-V4.1-Flash / High. |
14
+
15
+ Keep an existing working installation unless an upgrade is needed; inspect DSH activation errors
16
+ when using a different version. Other DSH versions need their own compatibility checks.
17
+
18
+ Within BizAgent 1.x, semantic versioning covers public entry points (`@bizagent/dsh` and
19
+ `@bizagent/dsh/host`), Agent tool names, and persisted file formats. Internal `lib/` modules are
20
+ not a public API; DSH runtime compatibility still needs its own verification.
21
+
22
+ ## What was checked
23
+
24
+ The 1.0.0 implementation passed **179 automated tests** and a real-model browser exercise with
25
+ **11 checks**:
26
+
27
+ | Area | Observed behavior |
28
+ | --- | --- |
29
+ | Selected `@Agent` | A logged Frame snapshot reached the model before tool execution. |
30
+ | Save learning | The rule appeared with its body, source, and status. |
31
+ | Recall in another session | The separate Project Main read the saved rule. |
32
+ | Independent formal Work | Another Home's worker accepted and completed the Work. |
33
+ | Result navigation | The result expanded; worker and requester sessions could be opened. |
34
+ | Human Project acceptance | A browser review changed the Project to `accepted`. |
35
+ | Artifact download | Downloaded bytes and SHA-256 matched the submitted snapshot. |
36
+ | Human Inbox reply | Replying in My Inbox resumed the original Project session. |
37
+ | Home creation dialog | Dialog semantics and initial focus were checked after the fix. |
38
+ | Markdown result | The Work result rendered an actual table. |
39
+ | Worker workspace | The worker appeared under its Home workspace without a new execution turn. |
40
+
41
+ The fictional four-order case is reproducible through the [user guide](user-guide.zh-CN.md).
42
+ Its expected GMV is **300**. The built-in Sample Shop's different dataset gives **400.50**.
43
+ The manual exercise also checked existing data, configuration, and process preservation.
44
+ Private transcripts, runtime identifiers, and credentials are intentionally not part of public examples.
45
+
46
+ ## Repeat the checks
47
+
48
+ From the repository root, check documentation:
49
+
50
+ ```sh
51
+ node scripts/check-docs.mjs
52
+ git diff --check
53
+ ```
54
+
55
+ From `packages/dsh`, check implementation and isolated runtime integration:
56
+
57
+ ```sh
58
+ npm ci
59
+ npm test
60
+ npm run smoke:local-human
61
+ npm run smoke:session-ui
62
+ ```
63
+
64
+ The smoke scripts start real isolated DSH profiles but use a deterministic local model. They
65
+ test integration, not how a real model chooses tools. The Learning Lab checks service behavior
66
+ without starting DSH. The [user guide](user-guide.zh-CN.md) supplies the real-model manual route.
67
+
68
+ The browser run covered one complete delivery flow and the listed UI checks. Long-term stability,
69
+ every failure/recovery path, other model routes, and multi-user hosting were not established by it.
@@ -0,0 +1,64 @@
1
+ # Learning Lab
2
+
3
+ Three scripted cases that run BizAgent's real learning and collaboration services without a model
4
+ or a DSH process. Use them to see what BizAgent records, step by step, in a few seconds.
5
+
6
+ | Case | From → to | What is learned |
7
+ | --- | --- | --- |
8
+ | `growth` | Personal analyst → Role `growth-strategy` | A retention comparison method with stated limits |
9
+ | `engineering` | Personal developer → Capability `config-review` | A method for verifying backups before configuration changes |
10
+ | `support` | Personal support agent → Business `demo-store` | Product knowledge tied to a product version |
11
+
12
+ The cases and their wording are fictional teaching material, not real business policies.
13
+
14
+ ## Run
15
+
16
+ ```sh
17
+ cd packages/dsh
18
+ npm ci
19
+ npm run examples # all three cases
20
+ npm run examples -- growth # one case
21
+ node examples/learning-lab.mjs # after a build, or from an unpacked tarball
22
+ ```
23
+
24
+ Each case uses a fresh temporary directory, removes it afterwards, and prints eleven checks:
25
+
26
+ ```text
27
+ [growth] Growth analysis: comparable retention cohorts
28
+ 1. Candidate saved; default search excludes it; source evidence is readable.
29
+ 2. Stale publication rejected; the reviewed revision is now active.
30
+ 3. Agents cannot approve; a host-authenticated human accepts; replay creates no duplicate.
31
+ 4. A read-only Agent Frame reuses the target index without changing the author Home.
32
+ 5. Future context contains only the index; feedback requests review; owner retires the asset.
33
+ 6. Home files reload correctly; retirement does not alter the author's copy.
34
+ 7. Review progress is explicit; later Session changes remain pending after a ledger reopen.
35
+ 8. Inbox delivery, reply, and handling survive a ledger reopen without changing Work or Memory.
36
+ 9. Formal Work binds one worker Session and one returned result beyond ordinary Inbox replies.
37
+ 10. Project submission records linked Work and a human acceptance across ledger reopen.
38
+ 11. Session Worklog links formal Work, verifies source events, and tracks a Workspace file.
39
+ ...
40
+ Learning lab passed: 3 scenarios. Temporary data removed.
41
+ ```
42
+
43
+ ## What is real here
44
+
45
+ | Part | In the lab | Covers |
46
+ | --- | --- | --- |
47
+ | Home files | Real `HomeStore` on a temporary directory | Candidates, revisions, index, retirement, reload |
48
+ | BizAgent service | Real | Ownership, revision conflicts, proposals, feedback, Inbox, Work, Projects, Worklogs |
49
+ | Ledgers | Real schemas and logic over in-memory tables | Records within one process; not persistence across processes |
50
+ | DSH sessions | Small synthetic event logs | Evidence references and bounded reads |
51
+ | Model and data | None; fixed fixtures | Not whether a model calls the tools well |
52
+
53
+ To see the same loops with a real model, follow the sample team in the
54
+ [quick start](../docs/quickstart.md#use-bizagent).
55
+
56
+ ## Read the code
57
+
58
+ - [learning-lab.mjs](learning-lab.mjs): the eleven steps in order, each with assertions.
59
+ - [scenarios.mjs](scenarios.mjs): inputs and reviewed text for the three cases.
60
+ - [demo-runtime.mjs](demo-runtime.mjs): the in-memory stand-in for DSH storage and sessions.
61
+
62
+ The lab imports internal modules from `lib/` to show the current implementation; they are not a
63
+ stable API. Agents use the [tools](../docs/tools.md); hosts use the package entry points listed in
64
+ the [package README](../README.md#package-entry-points).
@@ -0,0 +1,71 @@
1
+ // A minimal in-memory DSH storage/session adapter for the learning lab.
2
+ // The production plugin gets these facilities from DSH; do not use this adapter for real data.
3
+ import { LearningLedger } from '../lib/ledger.js'
4
+ import { WorkLedger } from '../lib/work-ledger.js'
5
+ import { ProjectLedger } from '../lib/project-ledger.js'
6
+ import { WorklogLedger } from '../lib/worklog-ledger.js'
7
+
8
+ export async function createDemoRuntime() {
9
+ const tables = new Map()
10
+ const sessions = new Map()
11
+ const facility = {
12
+ async open() {
13
+ return {
14
+ table(name) {
15
+ if (!tables.has(name)) {
16
+ const records = new Map()
17
+ tables.set(name, {
18
+ get: key => structuredClone(records.get(key)),
19
+ entries: () => structuredClone(records).entries(),
20
+ async put(key, value) { records.set(key, structuredClone(value)) },
21
+ })
22
+ }
23
+ return tables.get(name)
24
+ },
25
+ async close() {},
26
+ }
27
+ },
28
+ }
29
+ const ledger = await LearningLedger.open(facility)
30
+ const workLedger = await WorkLedger.open(facility)
31
+ const projectLedger = await ProjectLedger.open(facility)
32
+ const worklogLedger = await WorklogLedger.open(facility)
33
+ return {
34
+ ledger,
35
+ workLedger,
36
+ projectLedger,
37
+ worklogLedger,
38
+ reopenLedger: () => LearningLedger.open(facility),
39
+ reopenWorkLedger: () => WorkLedger.open(facility),
40
+ reopenProjectLedger: () => ProjectLedger.open(facility),
41
+ reopenWorklogLedger: () => WorklogLedger.open(facility),
42
+ sessionQuery: {
43
+ async listSessions() {
44
+ return [...sessions.values()].map(snapshot => ({ header: structuredClone(snapshot.session), live: false, persisted: true }))
45
+ },
46
+ async readSession(id) {
47
+ const session = sessions.get(id)
48
+ if (!session) throw new Error(`Unknown demo session: ${id}`)
49
+ return structuredClone(session)
50
+ },
51
+ },
52
+ appendSessionEvent(id, event) {
53
+ const session = sessions.get(id)
54
+ if (!session) throw new Error(`Unknown demo session: ${id}`)
55
+ session.events.push(structuredClone(event))
56
+ },
57
+ agent(id, cwd, message) {
58
+ const events = [
59
+ { seq: 0, time: 1, type: 'turn/start', data: { turn: 1 } },
60
+ { seq: 1, time: 2, type: 'user/message', data: { content: [{ type: 'text', text: message }] } },
61
+ ]
62
+ sessions.set(id, { session: { id, cwd }, events })
63
+ return { id, session: liveSession({ id, cwd }, events) }
64
+ },
65
+ }
66
+ }
67
+
68
+ /** The part of a live DSH Session the plugin reads: its header and a snapshot of its event log. */
69
+ export function liveSession(header, events) {
70
+ return { header, events, snapshotEvents: () => events }
71
+ }
@@ -0,0 +1,263 @@
1
+ import assert from 'node:assert/strict'
2
+ import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { HomeStore } from '../lib/home-store.js'
6
+ import { BizAgentService } from '../lib/service.js'
7
+ import { createDemoRuntime } from './demo-runtime.mjs'
8
+ import { scenarios } from './scenarios.mjs'
9
+
10
+ const requested = process.argv[2] ?? 'all'
11
+ const selected = scenarios.filter(scenario => requested === 'all' || scenario.id === requested)
12
+ if (selected.length === 0 || process.argv.length > 3) {
13
+ process.stderr.write('Usage: node examples/learning-lab.mjs [all|growth|engineering|support]\n')
14
+ process.exitCode = 1
15
+ } else {
16
+ for (const scenario of selected) await runScenario(scenario)
17
+ process.stdout.write(`Learning lab passed: ${selected.length} scenarios. Temporary data removed.\n`)
18
+ }
19
+
20
+ async function runScenario(scenario) {
21
+ const root = await mkdtemp(join(await realpath(tmpdir()), 'bizagent-learning-lab-'))
22
+ const runtime = await createDemoRuntime()
23
+ try {
24
+ const store = new HomeStore(root)
25
+ await store.initialize()
26
+ await store.createHome({ address: scenario.author })
27
+ await store.createHome({ address: scenario.owner })
28
+ const ownerCwd = join(root, 'owner-work')
29
+ const service = new BizAgentService(store, runtime.ledger, {
30
+ homeRoot: root,
31
+ defaultHome: scenario.author,
32
+ autoCreateDefaultHome: false,
33
+ workspaceHomes: [{ root: ownerCwd, address: scenario.owner }],
34
+ budget: { identityMaxBytes: 1024, indexMaxBytes: 4096 },
35
+ learningCheckpointEnabled: false,
36
+ }, runtime.sessionQuery, undefined, undefined, runtime.workLedger, runtime.projectLedger, runtime.worklogLedger)
37
+ await service.initialize()
38
+ const author = runtime.agent(`${scenario.id}-author`, root, scenario.observation)
39
+ const initialRevision = store.getHome(scenario.author).revision
40
+
41
+ process.stdout.write(`\n[${scenario.id}] ${scenario.name}\n`)
42
+ const draft = await service.remember(author, `${scenario.id}-draft`, {
43
+ kind: scenario.kind, status: 'candidate',
44
+ description: scenario.description, body: scenario.draft, tags: [scenario.id],
45
+ })
46
+ assert.equal((await service.searchMemory(author, {})).count, 0)
47
+ assert.equal((await service.searchMemory(author, { status: 'candidate' })).count, 1)
48
+ assert.equal(store.getHome(scenario.author).revision, initialRevision)
49
+ const evidence = await service.readEvidence(author, { assetId: draft.asset.id })
50
+ assert.equal(evidence.events[0].text, scenario.observation)
51
+ process.stdout.write(' 1. Candidate saved; default search excludes it; source evidence is readable.\n')
52
+
53
+ const reviewed = await service.updateMemory(author, {
54
+ assetId: draft.asset.id, action: 'revise', expectedRevision: draft.asset.revision,
55
+ body: scenario.reviewed,
56
+ })
57
+ assert.equal(reviewed.asset.status, 'candidate')
58
+ await assert.rejects(() => service.updateMemory(author, {
59
+ assetId: draft.asset.id, action: 'publish', expectedRevision: draft.asset.revision,
60
+ }), /revision conflict/)
61
+ const read = await service.readMemory(author, `${scenario.id}-read`, draft.asset.id)
62
+ const published = await service.updateMemory(author, {
63
+ assetId: read.asset.id, action: 'publish', expectedRevision: read.asset.revision,
64
+ })
65
+ assert.equal(published.asset.status, 'active')
66
+ assert.equal(published.home.revision, initialRevision + 1)
67
+ process.stdout.write(' 2. Stale publication rejected; the reviewed revision is now active.\n')
68
+
69
+ const proposed = await service.proposeMemory(author, `${scenario.id}-proposal`, {
70
+ toAddress: scenario.owner, proposedKind: scenario.kind,
71
+ description: scenario.description, body: scenario.reviewed, tags: [scenario.id],
72
+ })
73
+ assert.equal(store.listAssets(scenario.owner).length, 0)
74
+ await assert.rejects(() => service.acceptProposal(author, {
75
+ proposalId: proposed.proposal.id,
76
+ }), /host-authenticated human review/)
77
+ const owner = runtime.agent(`${scenario.id}-owner`, ownerCwd, 'Review the scope and evidence of the demo proposal.')
78
+ await assert.rejects(() => service.acceptProposal(owner, {
79
+ proposalId: proposed.proposal.id,
80
+ }), /host-authenticated human review/)
81
+ const humanDecision = { ownerAddress: scenario.owner, proposalId: proposed.proposal.id,
82
+ action: 'accept', reviewerId: 'demo-reviewer', authorityRef: 'demo-host:scenario-owner',
83
+ decision: 'Reviewed against the fictional scenario and its stated scope.' }
84
+ const accepted = await service.decideProposalAsHuman(humanDecision)
85
+ const replay = await service.decideProposalAsHuman(humanDecision)
86
+ assert.equal(replay.idempotentReplay, true)
87
+ assert.equal(store.listAssets(scenario.owner).length, 1)
88
+ process.stdout.write(' 3. Agents cannot approve; a host-authenticated human accepts; replay creates no duplicate.\n')
89
+
90
+ const frame = await service.openFrame(author, { address: scenario.owner, mode: 'contribute' })
91
+ assert.equal(frame.state.primaryHome, scenario.author)
92
+ assert.ok(frame.context.includes(scenario.description))
93
+ const frameHits = await service.searchFrame(author, { query: scenario.description })
94
+ assert.deepEqual(frameHits.assets.map(item => item.id), [accepted.asset.id])
95
+ assert.equal((await service.readFrame(author, `${scenario.id}-frame-read`, {
96
+ assetId: accepted.asset.id,
97
+ })).asset.id, accepted.asset.id)
98
+ const reopenedFrameLedger = await runtime.reopenLedger()
99
+ assert.equal(reopenedFrameLedger.getFrameState(author.id).focusAddress, scenario.owner)
100
+ await reopenedFrameLedger.close()
101
+ await service.closeFrame(author)
102
+ assert.equal((await service.frameStatus(author)).state.frames.length, 0)
103
+ process.stdout.write(' 4. A read-only Agent Frame reuses the target index without changing the author Home.\n')
104
+
105
+ const future = runtime.agent(`${scenario.id}-future`, ownerCwd, 'Use the reviewed demo asset in a subsequent task.')
106
+ const snapshot = service.contextForSession(future.id, ownerCwd)
107
+ assert.ok(snapshot.includes(scenario.description))
108
+ assert.ok(!snapshot.includes(scenario.reviewed))
109
+ await service.readMemory(future, `${scenario.id}-future-read`, accepted.asset.id)
110
+ const applied = await service.feedback(future, `${scenario.id}-applied`, {
111
+ assetId: accepted.asset.id, signal: 'applied',
112
+ })
113
+ assert.equal(applied.asset.fitness, 0)
114
+ const confirmed = await service.feedback(future, `${scenario.id}-confirmed`, {
115
+ assetId: accepted.asset.id, signal: 'confirmed', outcome: scenario.confirmation,
116
+ })
117
+ assert.equal(confirmed.asset.fitness, 0.2)
118
+ await service.feedback(future, `${scenario.id}-contradicted`, {
119
+ assetId: accepted.asset.id, signal: 'contradicted', outcome: scenario.contradiction,
120
+ })
121
+ const failed = await service.feedback(future, `${scenario.id}-failed`, {
122
+ assetId: accepted.asset.id, signal: 'failed', outcome: scenario.failure,
123
+ })
124
+ assert.equal(failed.reviewRequired, true)
125
+ assert.equal(failed.asset.status, 'active')
126
+ await service.updateMemory(future, {
127
+ assetId: accepted.asset.id, action: 'retire', expectedRevision: failed.asset.revision,
128
+ })
129
+ assert.equal((await service.searchMemory(future, {})).count, 0)
130
+ process.stdout.write(' 5. Future context contains only the index; feedback requests review; owner retires the asset.\n')
131
+
132
+ // Reload actual Markdown files; the demo ledger itself is intentionally ephemeral.
133
+ const reloaded = new HomeStore(root)
134
+ await reloaded.initialize()
135
+ assert.equal(reloaded.readAsset(scenario.owner, accepted.asset.id).status, 'retired')
136
+ assert.equal(reloaded.readAsset(scenario.author, published.asset.id).status, 'active')
137
+ assert.deepEqual((await reloaded.doctor()).issues, [])
138
+ process.stdout.write(' 6. Home files reload correctly; retirement does not alter the author\'s copy.\n')
139
+
140
+ const learningAgent = runtime.agent(`${scenario.id}-learning`, root, 'Review earlier work for useful experience.')
141
+ const materials = await service.scanLearningMaterials(learningAgent)
142
+ assert.deepEqual(materials.materials.map(item => item.sessionId), [author.id])
143
+ const reviewedMaterial = materials.materials[0]
144
+ await service.readEpisode(learningAgent, { sessionId: author.id })
145
+ await service.reviewLearningMaterial(learningAgent, {
146
+ materialId: reviewedMaterial.id,
147
+ summary: 'The earlier observation was checked; its reviewed asset already captures the reusable part.',
148
+ })
149
+ const resumedLedger = await runtime.reopenLedger()
150
+ assert.equal(resumedLedger.getMaterialReview(reviewedMaterial.id).materialId, reviewedMaterial.id)
151
+ await resumedLedger.close()
152
+ assert.equal((await service.scanLearningMaterials(learningAgent)).materials.length, 0)
153
+ author.session.events.push({
154
+ seq: 2, time: 3, type: 'user/message',
155
+ data: { content: [{ type: 'text', text: 'A later result changes the interpretation of the earlier work.' }] },
156
+ })
157
+ const changed = await service.scanLearningMaterials(learningAgent)
158
+ assert.equal(changed.materials.length, 1)
159
+ assert.notEqual(changed.materials[0].fingerprint, reviewedMaterial.fingerprint)
160
+ process.stdout.write(' 7. Review progress is explicit; later Session changes remain pending after a ledger reopen.\n')
161
+
162
+ const message = await service.sendInboxMessage(author, `${scenario.id}-inbox-send`, {
163
+ to: [scenario.owner], subject: 'Review the later result',
164
+ body: 'A later observation may change how we use the earlier asset.',
165
+ idempotencyKey: `${scenario.id}-later-result`,
166
+ })
167
+ assert.equal(message.deliveries[0].status, 'delivered')
168
+ const inboxLedger = await runtime.reopenLedger()
169
+ assert.equal(inboxLedger.getInboxMessage(message.message.id).returnToSessionId, author.id)
170
+ assert.equal(inboxLedger.getInboxDelivery(message.message.id, scenario.owner).status, 'delivered')
171
+ await inboxLedger.close()
172
+ assert.equal((await service.listInbox(future, { pendingOnly: true })).messages.length, 1)
173
+ assert.equal((await service.readInboxMessage(future, message.message.id)).message.body,
174
+ 'A later observation may change how we use the earlier asset.')
175
+ await service.handleInboxMessage(future, message.message.id)
176
+ const reply = await service.sendInboxMessage(future, `${scenario.id}-inbox-reply`, {
177
+ to: [scenario.author], subject: 'Re: Review the later result',
178
+ body: 'I will review the new evidence before changing the method.', replyTo: message.message.id,
179
+ })
180
+ assert.equal(reply.message.rootId, message.message.id)
181
+ assert.equal((await service.listInbox(future, { pendingOnly: true })).messages.length, 0)
182
+ assert.equal((await service.listInbox(author)).messages[0].id, reply.message.id)
183
+ process.stdout.write(' 8. Inbox delivery, reply, and handling survive a ledger reopen without changing Work or Memory.\n')
184
+
185
+ const project = await service.createProject(author, {
186
+ title: `${scenario.name} evidence review`,
187
+ goal: 'Decide how the later observation affects the earlier result.',
188
+ doneWhen: 'The formal review is returned and the final conclusion is recorded.',
189
+ idempotencyKey: `${scenario.id}-review-project`,
190
+ })
191
+ const assignment = await service.dispatchWork(author, {
192
+ toAddress: scenario.owner,
193
+ goal: `Review the later ${scenario.id} result`,
194
+ acceptanceCriteria: 'Return a concrete conclusion tied to the later observation.',
195
+ idempotencyKey: `${scenario.id}-formal-review`,
196
+ projectId: project.project.id,
197
+ })
198
+ assert.equal(assignment.work.status, 'open')
199
+ assert.equal(assignment.notification.message.kind, 'work-request')
200
+ const workReopened = await runtime.reopenWorkLedger()
201
+ assert.equal(workReopened.get(assignment.work.id).requestMessageId, assignment.notification.message.id)
202
+ await workReopened.close()
203
+ const acceptedWork = await service.acceptWork(future, assignment.work.id)
204
+ assert.equal(acceptedWork.work.workerSessionId, future.id)
205
+ const result = await service.completeWork(future, {
206
+ workId: assignment.work.id,
207
+ subject: `Reviewed ${scenario.id} result`,
208
+ body: 'The later observation needs a fresh evidence check before reuse.',
209
+ })
210
+ assert.equal(result.work.status, 'completed')
211
+ assert.equal(result.notification.message.kind, 'work-result')
212
+ assert.equal((await service.readWork(author, assignment.work.id)).resultDelivery.status, 'delivered')
213
+ assert.equal((await service.completeWork(future, {
214
+ workId: assignment.work.id, subject: 'Another result', body: 'Should not replace the first.',
215
+ })).alreadyCompleted, true)
216
+ process.stdout.write(' 9. Formal Work binds one worker Session and one returned result beyond ordinary Inbox replies.\n')
217
+
218
+ const submittedProject = await service.submitProject(author, {
219
+ projectId: project.project.id,
220
+ summary: 'Evidence review complete',
221
+ body: 'The later observation calls for a fresh evidence check before reuse.',
222
+ doneWhenAssessment: 'The formal review was delivered and the conclusion is recorded.',
223
+ })
224
+ // Agents can only request rework; acceptance comes from an authenticated person through the host.
225
+ await assert.rejects(() => service.reviewProject(author, {
226
+ projectId: project.project.id, claimId: submittedProject.claim.id,
227
+ decision: 'accept', reason: 'An agent cannot accept its own Project.',
228
+ }), /host-authenticated human review/)
229
+ const reviewedProject = await service.reviewProjectAsHuman('demo-reviewer', {
230
+ projectId: project.project.id, claimId: submittedProject.claim.id,
231
+ expectedRevision: submittedProject.project.revision,
232
+ decision: 'accept', reason: 'The stated completion standard is met in this fictional scenario.',
233
+ }, principal => principal === 'demo-reviewer')
234
+ assert.equal(reviewedProject.project.status, 'accepted')
235
+ const projectReopened = await runtime.reopenProjectLedger()
236
+ assert.equal(projectReopened.get(project.project.id).acceptedClaimId, submittedProject.claim.id)
237
+ await projectReopened.close()
238
+ process.stdout.write(' 10. Project submission records linked Work and a human acceptance across ledger reopen.\n')
239
+
240
+ await mkdir(ownerCwd, { recursive: true })
241
+ await writeFile(join(ownerCwd, 'review.txt'), 'The later observation needs a fresh evidence check before reuse.\n')
242
+ const worklog = (await service.recordWorklog(future, {
243
+ description: 'Evidence review completed',
244
+ body: 'The formal review found that the later observation needs a fresh evidence check.',
245
+ status: 'completed', idempotencyKey: `${scenario.id}-worklog-v1`,
246
+ files: ['review.txt'], workId: assignment.work.id, projectId: project.project.id,
247
+ })).worklog
248
+ assert.equal((await service.searchWorklogs(future, { query: 'review.txt' })).worklogs[0].id, worklog.id)
249
+ const worklogRead = await service.readWorklog(future, { sessionId: future.id })
250
+ assert.equal(worklogRead.source.status, 'verified')
251
+ assert.equal(worklogRead.files[0].status, 'current')
252
+ const worklogReopened = await runtime.reopenWorklogLedger()
253
+ assert.equal(worklogReopened.listVersions(future.id)[0].workId, assignment.work.id)
254
+ await worklogReopened.close()
255
+ process.stdout.write(' 11. Session Worklog links formal Work, verifies source events, and tracks a Workspace file.\n')
256
+ } finally {
257
+ await runtime.ledger.close()
258
+ await runtime.workLedger.close()
259
+ await runtime.projectLedger.close()
260
+ await runtime.worklogLedger.close()
261
+ await rm(root, { recursive: true, force: true })
262
+ }
263
+ }
@@ -0,0 +1,45 @@
1
+ // Fictional teaching fixtures. These statements are not real business policies.
2
+ export const scenarios = [
3
+ {
4
+ id: 'growth',
5
+ name: 'Growth analysis: comparable retention cohorts',
6
+ author: 'personal:analyst',
7
+ owner: 'role:growth-strategy',
8
+ kind: 'method',
9
+ description: 'Compare retention within matching acquisition cohorts.',
10
+ observation: 'The demo campaign changed the acquisition mix; the overall retention comparison was misleading.',
11
+ draft: 'Compare retention by acquisition cohort before explaining the overall change.',
12
+ reviewed: 'For this demo campaign, compare cohorts with the same acquisition channel and observation window. Report the cohort sizes and the overall result separately; do not infer causality from this comparison alone.',
13
+ confirmation: 'The second demo analysis separated a mix shift from within-cohort changes.',
14
+ contradiction: 'A later demo dataset lacks acquisition-channel labels.',
15
+ failure: 'Applying the procedure to incomplete demo cohorts produced an unsupported conclusion.',
16
+ },
17
+ {
18
+ id: 'engineering',
19
+ name: 'Engineering: reviewable configuration changes',
20
+ author: 'personal:developer',
21
+ owner: 'capability:config-review',
22
+ kind: 'method',
23
+ description: 'Verify a restorable backup before changing configuration.',
24
+ observation: 'A demo configuration edit could not be rolled back because the backup had not been checked.',
25
+ draft: 'Create and check a backup before changing a configuration file.',
26
+ reviewed: 'For the demo file-based configuration, save a versioned backup, restore it into an isolated directory, compare its content, and only then edit the original. This procedure does not cover live database migrations.',
27
+ confirmation: 'The next demo file change was restored successfully from its verified backup.',
28
+ contradiction: 'A later demo configuration includes generated files absent from the backup.',
29
+ failure: 'The incomplete demo backup could not recreate the generated configuration.',
30
+ },
31
+ {
32
+ id: 'support',
33
+ name: 'Support: a scoped product knowledge entry',
34
+ author: 'personal:support-agent',
35
+ owner: 'business:demo-store',
36
+ kind: 'knowledge',
37
+ description: 'Check order state before explaining the demo cancellation flow.',
38
+ observation: 'In the fictional demo store, an unshipped order and a shipped order use different cancellation flows.',
39
+ draft: 'Check whether the demo order has shipped before explaining cancellation.',
40
+ reviewed: 'Demo policy v1: for an unshipped order, explain the in-app cancellation flow; for a shipped order, hand off to a human support reviewer. This fixture describes a fictional product, not a general refund policy.',
41
+ confirmation: 'The next demo conversation used the order state to select the documented flow.',
42
+ contradiction: 'Demo policy v2 changes the flow for partially shipped orders.',
43
+ failure: 'The old demo entry gives an incorrect answer for a partially shipped order.',
44
+ },
45
+ ]