humanish 0.107.0 → 0.108.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 (521) hide show
  1. package/ARCHITECTURE.md +22 -17
  2. package/CHANGELOG.md +14 -0
  3. package/CONTEXT.md +51 -44
  4. package/README.md +145 -172
  5. package/SECURITY.md +16 -26
  6. package/TELEMETRY.md +8 -6
  7. package/dist/actors/affordance.d.ts +4 -4
  8. package/dist/actors/affordance.js +12 -12
  9. package/dist/actors/agent-session.d.ts +2 -2
  10. package/dist/actors/agent-session.js +7 -7
  11. package/dist/actors/codex/app-server-actor-trace.d.ts +1 -1
  12. package/dist/actors/codex/app-server-actor-trace.js +1 -1
  13. package/dist/actors/codex/app-server-ui.js +1 -1
  14. package/dist/actors/codex/codex-admission.d.ts +91 -0
  15. package/dist/actors/codex/codex-admission.js +150 -0
  16. package/dist/actors/codex/restricted-executable.d.ts +4 -4
  17. package/dist/actors/codex/restricted-executable.js +8 -8
  18. package/dist/actors/codex/restricted-launch.d.ts +2 -1
  19. package/dist/actors/codex/restricted-launch.js +1 -1
  20. package/dist/actors/codex/restricted-participant.js +8 -6
  21. package/dist/actors/codex/restricted-policy.d.ts +2 -2
  22. package/dist/actors/codex/restricted-policy.js +2 -2
  23. package/dist/actors/codex/restricted-session.d.ts +2 -2
  24. package/dist/actors/codex/restricted-session.js +10 -5
  25. package/dist/actors/computer-use/actor.d.ts +16 -16
  26. package/dist/actors/computer-use/actor.js +2 -2
  27. package/dist/actors/computer-use/admission-limit.d.ts +1 -1
  28. package/dist/actors/computer-use/admission-limit.js +1 -1
  29. package/dist/actors/computer-use/loop/actions.d.ts +3 -3
  30. package/dist/actors/computer-use/loop/actions.js +10 -10
  31. package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
  32. package/dist/actors/computer-use/loop/backstop.js +14 -12
  33. package/dist/actors/computer-use/loop/ending.d.ts +5 -5
  34. package/dist/actors/computer-use/loop/ending.js +7 -7
  35. package/dist/actors/computer-use/loop/observation.d.ts +2 -2
  36. package/dist/actors/computer-use/loop/observation.js +9 -9
  37. package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
  38. package/dist/actors/computer-use/loop/provider-call.js +1 -1
  39. package/dist/actors/computer-use/loop/session.js +3 -3
  40. package/dist/actors/computer-use/loop/spend.d.ts +1 -1
  41. package/dist/actors/computer-use/loop/spend.js +1 -1
  42. package/dist/actors/computer-use/loop/trace.js +9 -8
  43. package/dist/actors/computer-use/loop/types.d.ts +25 -23
  44. package/dist/actors/computer-use/loop/usage.js +2 -2
  45. package/dist/actors/computer-use/openai-context.d.ts +37 -0
  46. package/dist/actors/computer-use/openai-context.js +214 -0
  47. package/dist/actors/computer-use/openai-provider.d.ts +11 -8
  48. package/dist/actors/computer-use/openai-provider.js +45 -34
  49. package/dist/actors/computer-use/openai-requests.d.ts +36 -10
  50. package/dist/actors/computer-use/openai-requests.js +82 -15
  51. package/dist/actors/computer-use/openai-wire.d.ts +19 -7
  52. package/dist/actors/computer-use/openai-wire.js +36 -34
  53. package/dist/actors/contract.d.ts +78 -43
  54. package/dist/actors/contract.js +29 -23
  55. package/dist/actors/local-agent/claude-session.d.ts +1 -1
  56. package/dist/actors/local-agent/claude-session.js +10 -9
  57. package/dist/actors/local-agent/cli.d.ts +8 -6
  58. package/dist/actors/local-agent/cli.js +24 -28
  59. package/dist/actors/local-agent/readiness.d.ts +2 -2
  60. package/dist/actors/local-agent/readiness.js +4 -4
  61. package/dist/actors/reasoning-effort.js +6 -6
  62. package/dist/actors/registry.d.ts +10 -10
  63. package/dist/actors/registry.js +18 -17
  64. package/dist/actors/scripted-browser/actor.d.ts +8 -8
  65. package/dist/actors/scripted-browser/actor.js +16 -16
  66. package/dist/actors/scripted-browser/browser-command.js +1 -1
  67. package/dist/actors/scripted-browser/steps.js +1 -1
  68. package/dist/actors/scripted-browser/types.d.ts +1 -1
  69. package/dist/actors/stop-conditions.d.ts +1 -1
  70. package/dist/analysis/codex-config.d.ts +6 -2
  71. package/dist/analysis/codex-config.js +22 -14
  72. package/dist/analysis/service.js +5 -5
  73. package/dist/cli/commands/analyze.js +19 -16
  74. package/dist/cli/commands/codex.js +9 -9
  75. package/dist/cli/commands/comms.js +54 -33
  76. package/dist/cli/commands/feedback.js +20 -21
  77. package/dist/cli/commands/lab-format.d.ts +5 -4
  78. package/dist/cli/commands/lab-format.js +35 -23
  79. package/dist/cli/commands/lab-route-computer-use.js +6 -7
  80. package/dist/cli/commands/lab-route-preview.js +4 -3
  81. package/dist/cli/commands/lab-route-shared-world.js +3 -3
  82. package/dist/cli/commands/lab-run.js +10 -11
  83. package/dist/cli/commands/lab-scorer.d.ts +2 -2
  84. package/dist/cli/commands/lab-scorer.js +1 -1
  85. package/dist/cli/commands/migrate.d.ts +4 -0
  86. package/dist/cli/commands/migrate.js +65 -0
  87. package/dist/cli/commands/observe.js +147 -108
  88. package/dist/cli/commands/run-command.d.ts +10 -0
  89. package/dist/cli/commands/run-command.js +85 -0
  90. package/dist/cli/commands/runs.js +137 -138
  91. package/dist/cli/commands/setup.d.ts +2 -2
  92. package/dist/cli/commands/setup.js +26 -21
  93. package/dist/cli/commands/study.d.ts +3 -0
  94. package/dist/cli/commands/study.js +182 -0
  95. package/dist/cli/commands/tui.d.ts +4 -4
  96. package/dist/cli/commands/tui.js +21 -21
  97. package/dist/cli/commands/watch.js +60 -75
  98. package/dist/cli/deprecations.d.ts +18 -0
  99. package/dist/cli/deprecations.js +48 -0
  100. package/dist/cli/doctor.d.ts +15 -5
  101. package/dist/cli/doctor.js +103 -48
  102. package/dist/cli/first-run-path.d.ts +12 -10
  103. package/dist/cli/first-run-path.js +30 -28
  104. package/dist/cli/io.d.ts +41 -6
  105. package/dist/cli/io.js +97 -22
  106. package/dist/cli/observer-follow.d.ts +2 -2
  107. package/dist/cli/observer-follow.js +5 -6
  108. package/dist/cli/orientation.d.ts +6 -3
  109. package/dist/cli/orientation.js +43 -39
  110. package/dist/cli/product-sentence.d.ts +6 -0
  111. package/dist/cli/product-sentence.js +6 -0
  112. package/dist/cli/program.d.ts +6 -1
  113. package/dist/cli/program.js +109 -40
  114. package/dist/cli/telemetry.d.ts +15 -15
  115. package/dist/cli/telemetry.js +48 -40
  116. package/dist/cli.js +1 -1
  117. package/dist/comms/capture-surface.d.ts +13 -12
  118. package/dist/comms/capture-surface.js +23 -22
  119. package/dist/comms/catch-host.d.ts +7 -7
  120. package/dist/comms/catch-host.js +14 -14
  121. package/dist/comms/email-catch.d.ts +3 -3
  122. package/dist/comms/email-catch.js +8 -8
  123. package/dist/comms/external-evidence.js +2 -2
  124. package/dist/comms/extract.js +2 -2
  125. package/dist/comms/fake-inbox.d.ts +2 -2
  126. package/dist/comms/fake-inbox.js +2 -2
  127. package/dist/comms/receiving-render.d.ts +1 -1
  128. package/dist/comms/receiving-render.js +2 -2
  129. package/dist/comms/sandbox-catch-script.d.ts +6 -6
  130. package/dist/comms/sandbox-catch-script.js +6 -6
  131. package/dist/comms/sandbox-catch.d.ts +36 -37
  132. package/dist/comms/sandbox-catch.js +34 -35
  133. package/dist/comms/setup.js +42 -8
  134. package/dist/comms/thread-evidence.d.ts +1 -1
  135. package/dist/comms/thread-evidence.js +6 -6
  136. package/dist/comms/types.d.ts +6 -6
  137. package/dist/comms/types.js +3 -3
  138. package/dist/evidence/frame-signature.d.ts +1 -1
  139. package/dist/evidence/frame-signature.js +4 -4
  140. package/dist/evidence/redaction.d.ts +3 -3
  141. package/dist/evidence/redaction.js +4 -4
  142. package/dist/feedback/draft.js +36 -78
  143. package/dist/feedback/export-bundle.js +1 -1
  144. package/dist/feedback/export.d.ts +5 -1
  145. package/dist/feedback/export.js +7 -8
  146. package/dist/feedback/feedback.d.ts +2 -2
  147. package/dist/feedback/feedback.js +21 -3
  148. package/dist/guest/chromium-text.js +2 -2
  149. package/dist/guest/desktop-native.d.ts +1 -1
  150. package/dist/guest/runtime.js +1 -1
  151. package/dist/index.d.ts +12 -10
  152. package/dist/index.js +8 -6
  153. package/dist/keys/key-resolution.d.ts +5 -5
  154. package/dist/keys/key-resolution.js +23 -23
  155. package/dist/lab/adapter-extension.d.ts +7 -7
  156. package/dist/lab/adapter-extension.js +13 -36
  157. package/dist/lab/adapter-scorer-loader.d.ts +7 -7
  158. package/dist/lab/adapter-scorer-loader.js +32 -32
  159. package/dist/lab/composition-rules.js +42 -42
  160. package/dist/lab/config.js +65 -25
  161. package/dist/lab/deprecated-fields.d.ts +10 -0
  162. package/dist/lab/deprecated-fields.js +24 -0
  163. package/dist/lab/device-presets.d.ts +8 -8
  164. package/dist/lab/device-presets.js +11 -11
  165. package/dist/lab/discover.d.ts +13 -9
  166. package/dist/lab/discover.js +117 -57
  167. package/dist/lab/doctor.d.ts +10 -3
  168. package/dist/lab/doctor.js +47 -9
  169. package/dist/lab/init-templates.js +147 -147
  170. package/dist/lab/init.d.ts +1 -1
  171. package/dist/lab/init.js +41 -9
  172. package/dist/lab/keys.d.ts +2 -0
  173. package/dist/lab/keys.js +42 -8
  174. package/dist/lab/lab-deps.d.ts +7 -12
  175. package/dist/lab/parse/actors.js +9 -9
  176. package/dist/lab/parse/comms.js +5 -5
  177. package/dist/lab/parse/execution.js +13 -13
  178. package/dist/lab/parse/study-v3.d.ts +23 -0
  179. package/dist/lab/parse/study-v3.js +259 -0
  180. package/dist/lab/parse/subject-state.d.ts +7 -7
  181. package/dist/lab/parse/subject-state.js +16 -16
  182. package/dist/lab/parse/subject.js +28 -28
  183. package/dist/lab/parse/values.d.ts +1 -1
  184. package/dist/lab/parse/values.js +3 -3
  185. package/dist/lab/persona-resolve.d.ts +4 -4
  186. package/dist/lab/persona-resolve.js +4 -4
  187. package/dist/lab/plan-participants.d.ts +2 -2
  188. package/dist/lab/plan-types.d.ts +4 -4
  189. package/dist/lab/plan.d.ts +1 -7
  190. package/dist/lab/plan.js +10 -41
  191. package/dist/lab/preflight-probes.js +11 -11
  192. package/dist/lab/preflight-result.d.ts +2 -1
  193. package/dist/lab/preflight-result.js +5 -4
  194. package/dist/lab/preflight.d.ts +7 -5
  195. package/dist/lab/preflight.js +5 -5
  196. package/dist/lab/routing.d.ts +31 -25
  197. package/dist/lab/routing.js +60 -29
  198. package/dist/lab/run-lab-events.d.ts +0 -3
  199. package/dist/lab/run-lab-events.js +0 -6
  200. package/dist/lab/run-lab-homes.d.ts +22 -10
  201. package/dist/lab/run-lab-homes.js +5 -1
  202. package/dist/lab/run-lab-options.d.ts +1 -1
  203. package/dist/lab/run-lab-options.js +16 -13
  204. package/dist/lab/summary.d.ts +7 -6
  205. package/dist/lab/summary.js +10 -10
  206. package/dist/lab/tasks.d.ts +15 -15
  207. package/dist/lab/tasks.js +14 -14
  208. package/dist/lab/types.d.ts +101 -95
  209. package/dist/lab/types.js +2 -0
  210. package/dist/lab/validation.d.ts +13 -13
  211. package/dist/lab/validation.js +44 -42
  212. package/dist/lab/warnings.d.ts +10 -0
  213. package/dist/lab/warnings.js +61 -33
  214. package/dist/library-aliases.d.ts +56 -0
  215. package/dist/library-aliases.js +22 -0
  216. package/dist/observer/artifact.d.ts +1 -1
  217. package/dist/observer/artifact.js +6 -6
  218. package/dist/observer/data.d.ts +6 -6
  219. package/dist/observer/data.js +5 -4
  220. package/dist/observer/exposure.d.ts +1 -1
  221. package/dist/observer/exposure.js +11 -11
  222. package/dist/observer/http.js +1 -1
  223. package/dist/observer/library.d.ts +1 -1
  224. package/dist/observer/library.js +4 -2
  225. package/dist/observer/listen.js +5 -4
  226. package/dist/observer/render.d.ts +1 -1
  227. package/dist/observer/render.js +10 -5
  228. package/dist/observer/run-routes.d.ts +3 -3
  229. package/dist/observer/run-routes.js +1 -1
  230. package/dist/observer/serve.js +7 -3
  231. package/dist/observer/tunnel.js +1 -1
  232. package/dist/observer-app.html +4 -4
  233. package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
  234. package/dist/routes/computer-use/bundle-parts.js +15 -15
  235. package/dist/routes/computer-use/bundle.js +3 -3
  236. package/dist/routes/computer-use/e2b-desktop/comms.d.ts +1 -1
  237. package/dist/routes/computer-use/e2b-desktop/comms.js +14 -14
  238. package/dist/routes/computer-use/e2b-desktop/desktop.js +4 -4
  239. package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +4 -3
  240. package/dist/routes/computer-use/e2b-desktop/fidelity.js +11 -10
  241. package/dist/routes/computer-use/e2b-desktop/prepare.js +7 -7
  242. package/dist/routes/computer-use/e2b-desktop/start.js +9 -8
  243. package/dist/routes/computer-use/e2b-desktop/teardown.js +5 -4
  244. package/dist/routes/computer-use/external-comms.d.ts +1 -1
  245. package/dist/routes/computer-use/external-comms.js +1 -1
  246. package/dist/routes/computer-use/fanout-bundle.js +18 -18
  247. package/dist/routes/computer-use/fanout-records.js +10 -11
  248. package/dist/routes/computer-use/live-flush.d.ts +1 -1
  249. package/dist/routes/computer-use/live-flush.js +6 -6
  250. package/dist/routes/computer-use/live-phase.d.ts +4 -2
  251. package/dist/routes/computer-use/live-phase.js +6 -6
  252. package/dist/routes/computer-use/local-vm.js +1 -1
  253. package/dist/routes/computer-use/participant-desktop.d.ts +4 -4
  254. package/dist/routes/computer-use/participant-desktop.js +4 -4
  255. package/dist/routes/computer-use/participant-execution.d.ts +4 -4
  256. package/dist/routes/computer-use/participant-execution.js +9 -9
  257. package/dist/routes/computer-use/participant-model.d.ts +6 -0
  258. package/dist/routes/computer-use/participant-model.js +31 -13
  259. package/dist/routes/computer-use/participant-prompt.d.ts +11 -11
  260. package/dist/routes/computer-use/participant-prompt.js +6 -6
  261. package/dist/routes/computer-use/participant-runs.d.ts +7 -7
  262. package/dist/routes/computer-use/participant-runs.js +13 -13
  263. package/dist/routes/computer-use/plan.js +22 -22
  264. package/dist/routes/computer-use/preflight.js +21 -21
  265. package/dist/routes/computer-use/rerun-selection.js +1 -1
  266. package/dist/routes/computer-use/result.js +11 -11
  267. package/dist/routes/computer-use/route.d.ts +2 -1
  268. package/dist/routes/computer-use/route.js +15 -14
  269. package/dist/routes/computer-use/self-report.d.ts +7 -7
  270. package/dist/routes/computer-use/self-report.js +31 -31
  271. package/dist/routes/computer-use/setup.d.ts +1 -1
  272. package/dist/routes/computer-use/setup.js +17 -18
  273. package/dist/routes/computer-use/single-bundle.d.ts +7 -7
  274. package/dist/routes/computer-use/single-bundle.js +28 -30
  275. package/dist/routes/computer-use/subject-projection.d.ts +1 -1
  276. package/dist/routes/computer-use/subject-projection.js +6 -6
  277. package/dist/routes/computer-use/types.d.ts +40 -42
  278. package/dist/routes/computer-use/types.js +10 -10
  279. package/dist/routes/preview.d.ts +8 -2
  280. package/dist/routes/preview.js +8 -4
  281. package/dist/routes/scripted/bundle.d.ts +2 -2
  282. package/dist/routes/scripted/bundle.js +13 -12
  283. package/dist/routes/scripted/plan.d.ts +1 -1
  284. package/dist/routes/scripted/plan.js +9 -9
  285. package/dist/routes/scripted/result.js +5 -5
  286. package/dist/routes/scripted/route.js +5 -6
  287. package/dist/routes/scripted/scenario.js +2 -2
  288. package/dist/routes/scripted/setup.js +7 -7
  289. package/dist/routes/scripted/subject.js +1 -1
  290. package/dist/routes/scripted/surface-records.js +3 -3
  291. package/dist/routes/scripted/types.d.ts +3 -5
  292. package/dist/routes/scripted/types.js +1 -1
  293. package/dist/routes/shared-world/bundle-records.js +9 -8
  294. package/dist/routes/shared-world/bundle.d.ts +5 -5
  295. package/dist/routes/shared-world/bundle.js +40 -38
  296. package/dist/routes/shared-world/checkpoints.d.ts +1 -1
  297. package/dist/routes/shared-world/checkpoints.js +1 -1
  298. package/dist/routes/shared-world/comms.d.ts +5 -5
  299. package/dist/routes/shared-world/comms.js +14 -14
  300. package/dist/routes/shared-world/external-public.d.ts +4 -3
  301. package/dist/routes/shared-world/external-public.js +16 -12
  302. package/dist/routes/shared-world/handoff.d.ts +7 -6
  303. package/dist/routes/shared-world/handoff.js +65 -57
  304. package/dist/routes/shared-world/lobby-code.d.ts +2 -2
  305. package/dist/routes/shared-world/lobby-code.js +9 -9
  306. package/dist/routes/shared-world/participant-specs.d.ts +7 -6
  307. package/dist/routes/shared-world/participant-specs.js +31 -24
  308. package/dist/routes/shared-world/plan.js +7 -7
  309. package/dist/routes/shared-world/provenance.d.ts +2 -2
  310. package/dist/routes/shared-world/provenance.js +2 -2
  311. package/dist/routes/shared-world/provisioned.js +14 -13
  312. package/dist/routes/shared-world/result.js +20 -21
  313. package/dist/routes/shared-world/route.js +23 -22
  314. package/dist/routes/shared-world/setup.d.ts +3 -3
  315. package/dist/routes/shared-world/setup.js +4 -4
  316. package/dist/routes/shared-world/types.d.ts +30 -28
  317. package/dist/routes/shared-world/types.js +4 -4
  318. package/dist/routes/terminal/adapter.d.ts +3 -3
  319. package/dist/routes/terminal/adapter.js +5 -5
  320. package/dist/routes/terminal/bundle.d.ts +3 -3
  321. package/dist/routes/terminal/bundle.js +22 -21
  322. package/dist/routes/terminal/credentials.d.ts +4 -4
  323. package/dist/routes/terminal/credentials.js +7 -7
  324. package/dist/routes/terminal/dry-run.js +4 -4
  325. package/dist/routes/terminal/encoding.js +2 -2
  326. package/dist/routes/terminal/ledger.d.ts +3 -3
  327. package/dist/routes/terminal/ledger.js +14 -14
  328. package/dist/routes/terminal/live-finish.js +3 -3
  329. package/dist/routes/terminal/live-sandbox.js +12 -13
  330. package/dist/routes/terminal/persona.js +1 -1
  331. package/dist/routes/terminal/plan.js +11 -11
  332. package/dist/routes/terminal/recorder.js +2 -2
  333. package/dist/routes/terminal/result.js +8 -8
  334. package/dist/routes/terminal/route.js +6 -7
  335. package/dist/routes/terminal/sandbox.d.ts +1 -1
  336. package/dist/routes/terminal/sandbox.js +1 -1
  337. package/dist/routes/terminal/session.js +8 -7
  338. package/dist/routes/terminal/token-usage.d.ts +1 -1
  339. package/dist/routes/terminal/token-usage.js +7 -8
  340. package/dist/routes/terminal/trace.d.ts +2 -2
  341. package/dist/routes/terminal/trace.js +2 -2
  342. package/dist/routes/terminal/types.d.ts +26 -28
  343. package/dist/routes/terminal/types.js +3 -3
  344. package/dist/run/bundle-shape.js +3 -3
  345. package/dist/run/bundle.d.ts +46 -46
  346. package/dist/run/bundle.js +2 -1
  347. package/dist/run/cost-summary.js +3 -3
  348. package/dist/run/costs.js +7 -1
  349. package/dist/run/detail.d.ts +6 -6
  350. package/dist/run/detail.js +4 -4
  351. package/dist/run/dry-run-inputs.js +1 -1
  352. package/dist/run/dry-run.js +18 -18
  353. package/dist/run/judge.d.ts +19 -13
  354. package/dist/run/judge.js +18 -8
  355. package/dist/run/paths.d.ts +1 -1
  356. package/dist/run/paths.js +1 -1
  357. package/dist/run/pricing.d.ts +11 -6
  358. package/dist/run/pricing.js +20 -13
  359. package/dist/run/projection.d.ts +14 -14
  360. package/dist/run/projection.js +15 -15
  361. package/dist/run/reclaim.js +2 -2
  362. package/dist/run/results.d.ts +5 -5
  363. package/dist/run/run-index.d.ts +8 -6
  364. package/dist/run/run-index.js +15 -16
  365. package/dist/run/run-not-found.d.ts +5 -0
  366. package/dist/run/run-not-found.js +15 -0
  367. package/dist/run/run.d.ts +5 -4
  368. package/dist/run/run.js +1 -1
  369. package/dist/run/sandbox-receipts.d.ts +2 -2
  370. package/dist/run/sandbox-receipts.js +3 -3
  371. package/dist/run/shared-world-evidence.d.ts +36 -35
  372. package/dist/run/shared-world-evidence.js +4 -4
  373. package/dist/run/shared-world-shape.d.ts +1 -1
  374. package/dist/run/shared-world-shape.js +6 -6
  375. package/dist/run/stats.d.ts +12 -6
  376. package/dist/run/stats.js +116 -58
  377. package/dist/run/status.d.ts +8 -29
  378. package/dist/run/status.js +5 -27
  379. package/dist/run/stored-runs.js +2 -1
  380. package/dist/run/stream-shape.js +1 -1
  381. package/dist/run/streams.d.ts +6 -6
  382. package/dist/run/study-provenance.d.ts +47 -0
  383. package/dist/run/study-provenance.js +63 -0
  384. package/dist/run/study-result.d.ts +16 -0
  385. package/dist/run/study-result.js +9 -0
  386. package/dist/run/text.d.ts +2 -0
  387. package/dist/run/text.js +4 -0
  388. package/dist/run-lab.d.ts +7 -7
  389. package/dist/run-lab.js +1 -3
  390. package/dist/study/convert.d.ts +33 -0
  391. package/dist/study/convert.js +432 -0
  392. package/dist/study/field-paths.d.ts +12 -0
  393. package/dist/study/field-paths.js +126 -0
  394. package/dist/study/files.d.ts +26 -0
  395. package/dist/study/files.js +43 -0
  396. package/dist/study/migrate.d.ts +48 -0
  397. package/dist/study/migrate.js +408 -0
  398. package/dist/study/owned-files.d.ts +38 -0
  399. package/dist/study/owned-files.js +182 -0
  400. package/dist/subject/clone.d.ts +1 -1
  401. package/dist/subject/desktop-cli.d.ts +1 -1
  402. package/dist/subject/desktop-cli.js +2 -2
  403. package/dist/subject/local-tree-archive.js +1 -1
  404. package/dist/subject/local-tree.d.ts +1 -1
  405. package/dist/subject/node-bootstrap.js +1 -1
  406. package/dist/subject/runtime.js +8 -9
  407. package/dist/subject/serve.d.ts +1 -1
  408. package/dist/subject/serve.js +3 -3
  409. package/dist/subject/state.d.ts +1 -1
  410. package/dist/subject/steps.d.ts +5 -5
  411. package/dist/subject/steps.js +4 -4
  412. package/dist/substrates/command-failure.d.ts +3 -3
  413. package/dist/substrates/command-failure.js +3 -3
  414. package/dist/substrates/detached.d.ts +6 -6
  415. package/dist/substrates/detached.js +7 -7
  416. package/dist/substrates/e2b/cdp-probe.d.ts +9 -9
  417. package/dist/substrates/e2b/cdp-probe.js +14 -17
  418. package/dist/substrates/e2b/desktop-browser.d.ts +2 -2
  419. package/dist/substrates/e2b/desktop-browser.js +8 -8
  420. package/dist/substrates/e2b/desktop-cdp.d.ts +9 -9
  421. package/dist/substrates/e2b/desktop-cdp.js +8 -8
  422. package/dist/substrates/e2b/desktop-executor.d.ts +1 -1
  423. package/dist/substrates/e2b/desktop-executor.js +1 -1
  424. package/dist/substrates/e2b/desktop-geometry.d.ts +3 -3
  425. package/dist/substrates/e2b/desktop-geometry.js +7 -7
  426. package/dist/substrates/e2b/desktop-media.d.ts +1 -1
  427. package/dist/substrates/e2b/desktop-media.js +1 -1
  428. package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
  429. package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
  430. package/dist/substrates/e2b/sandbox.d.ts +1 -1
  431. package/dist/substrates/e2b/sandbox.js +1 -1
  432. package/dist/substrates/e2b/sdk.d.ts +11 -11
  433. package/dist/substrates/e2b/sdk.js +6 -6
  434. package/dist/tui/actions.d.ts +1 -1
  435. package/dist/tui/actions.js +13 -13
  436. package/dist/tui/contract.d.ts +16 -16
  437. package/dist/tui/contract.js +10 -10
  438. package/dist/tui/launch.d.ts +4 -4
  439. package/dist/tui/launch.js +18 -19
  440. package/dist/tui/project.js +5 -5
  441. package/dist/tui-app.js +82 -82
  442. package/dist/verify/actor.d.ts +8 -8
  443. package/dist/verify/actor.js +36 -36
  444. package/dist/verify/artifacts.d.ts +2 -2
  445. package/dist/verify/artifacts.js +3 -3
  446. package/dist/verify/context-growth.d.ts +9 -0
  447. package/dist/verify/context-growth.js +62 -0
  448. package/dist/verify/costs.d.ts +6 -6
  449. package/dist/verify/costs.js +8 -8
  450. package/dist/verify/shared-world-concurrent.d.ts +2 -2
  451. package/dist/verify/shared-world-concurrent.js +79 -76
  452. package/dist/verify/shared-world.d.ts +2 -2
  453. package/dist/verify/shared-world.js +16 -16
  454. package/dist/verify/subject.d.ts +4 -4
  455. package/dist/verify/subject.js +18 -18
  456. package/dist/verify/verify.js +92 -43
  457. package/docs/README.md +25 -32
  458. package/docs/architecture/actor-contract.md +56 -87
  459. package/docs/architecture/desktop-sessions.md +7 -7
  460. package/docs/architecture/external-public-shared-world.md +61 -59
  461. package/docs/architecture/github-feedback-loop.md +1 -4
  462. package/docs/architecture/guest-desktop.md +2 -2
  463. package/docs/architecture/observer.md +34 -34
  464. package/docs/architecture/restricted-codex-analysis.md +34 -28
  465. package/docs/architecture/serve.md +34 -37
  466. package/docs/architecture/state-driven-executor.md +55 -58
  467. package/docs/architecture/task-protocol-support.md +5 -5
  468. package/docs/architecture/terminal-product-route.md +67 -107
  469. package/docs/contracts/README.md +24 -0
  470. package/docs/contracts/adapter-admission.md +3 -3
  471. package/docs/contracts/adapter-fixtures.md +2 -5
  472. package/docs/contracts/core.md +1 -3
  473. package/docs/contracts/errors.md +62 -0
  474. package/docs/contracts/feedback.md +8 -9
  475. package/docs/contracts/policy.md +7 -12
  476. package/docs/contracts/run-bundle.md +45 -44
  477. package/docs/contracts/schemas.md +421 -362
  478. package/docs/contracts/study-analysis.md +3 -3
  479. package/docs/contracts/study-costs.md +11 -7
  480. package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
  481. package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
  482. package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
  483. package/docs/principles/actor-fidelity.md +4 -4
  484. package/docs/principles/engineering.md +3 -3
  485. package/docs/principles/invariants-and-defaults.md +38 -38
  486. package/docs/principles/self-driving-harness.md +1 -5
  487. package/docs/principles/three-roles.md +1 -1
  488. package/docs/product/automatic-analysis.md +7 -7
  489. package/examples/participant/README.md +5 -5
  490. package/examples/participant/run.mjs +11 -10
  491. package/examples/scorer/README.md +3 -3
  492. package/examples/scorer/run.mjs +11 -12
  493. package/package.json +16 -17
  494. package/skills/humanish/SKILL.md +60 -58
  495. package/skills/humanish/agents/openai.yaml +1 -1
  496. package/AGENTS.md +0 -99
  497. package/CONTRIBUTING.md +0 -148
  498. package/dist/actors/codex/qualified-versions.d.ts +0 -72
  499. package/dist/actors/codex/qualified-versions.js +0 -132
  500. package/dist/cli/commands/lab.d.ts +0 -3
  501. package/dist/cli/commands/lab.js +0 -199
  502. package/dist/cli/renamed-options.d.ts +0 -11
  503. package/dist/cli/renamed-options.js +0 -18
  504. package/dist/observer/static.d.ts +0 -39
  505. package/dist/observer/static.js +0 -369
  506. package/docs/architecture/comms-inbox.md +0 -122
  507. package/docs/architecture/desktop-recording.md +0 -80
  508. package/docs/architecture/local-browser-runtime.md +0 -178
  509. package/docs/architecture/observer-review.md +0 -144
  510. package/docs/architecture/participant-media.md +0 -90
  511. package/docs/architecture/project-layout.md +0 -62
  512. package/docs/architecture/real-email-receiving.md +0 -136
  513. package/docs/assets/humanish-drawdb-hero.png +0 -0
  514. package/docs/assets/humanish-observer-hero.png +0 -0
  515. package/docs/goals/current.md +0 -207
  516. package/docs/product/lobby-trivia-3player-external-public.md +0 -77
  517. package/docs/product/open-source-install-experience.md +0 -240
  518. package/docs/ramp/README.md +0 -295
  519. package/docs/release/open-source-readiness.md +0 -222
  520. package/docs/release/public-readiness-standard.md +0 -210
  521. package/docs/roadmap/world-class-open-source-v0.md +0 -311
@@ -1,12 +1,9 @@
1
1
  # Contract Schema Index
2
2
 
3
- Date: 2026-06-02 (current-state note updated 2026-07-14)
4
-
5
- Status: reference map for the major contracts shipped through source version
6
- `0.107.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
7
- schema constants, parsers, and validators in `src/` are authoritative. Rows
8
- marked "reserved" name layering intent only; no code emits or validates them
9
- yet. Do not emit a reserved schema.
3
+ This page maps the shipped contracts; it does not list every command or result envelope. The
4
+ exported types, schema constants, parsers and validators in `src/` are the source of truth. A row
5
+ marked "reserved" names layering intent only: no code emits or validates it yet, so do not emit a
6
+ reserved schema.
10
7
 
11
8
  ## Purpose
12
9
 
@@ -31,7 +28,7 @@ workflow without leaking private upstream truth into core.
31
28
  | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
32
29
  | Run bundle | `humanish.run-bundle.v1` | `synthetic-run-bundle` |
33
30
  | Adapter | `humanish.adapter.v1` | `synthetic-cli-adapter` |
34
- | Lab | `humanish.lab.v2` | `first-run` |
31
+ | Study file | `humanish.study.v3` | `first-run` |
35
32
  | Persona | `humanish.persona.v1` | `synthetic-maintainer` |
36
33
  | Scenario | `humanish.scenario.v1` | `first-run-smoke` |
37
34
  | Actor trace | `humanish.actor-trace.v1` | `synthetic-actor-trace` |
@@ -55,32 +52,72 @@ workflow without leaking private upstream truth into core.
55
52
  | Adapter score | `humanish.adapter-score.v1` (`RunBundle.adapterScore`; namespaced; route-specific acceptance semantics) | see Product-Adapter Extension Seam below |
56
53
  | Adapter artifact | `humanish.adapter-artifact.v1` (`RunBundle.adapterArtifacts[]`; namespaced; local relative proof references) | see Product-Adapter Extension Seam below |
57
54
  | Shared-world evidence | `humanish.shared-world.v1` (additive `RunBundle.sharedWorld` + `RunBundle.attributionClass`; `topologyMode: sequential \| concurrent`) | see Shared-World Evidence below |
58
- | Comms thread | `humanish.comms-thread.v1` (off-app email/SMS the app sent, captured; a `kind: log` run-dir artifact of DIGESTS only — from/to/subject/link digests + an OTP count, never raw) | see `comms` under Lab Manifest |
55
+ | Comms thread | `humanish.comms-thread.v1` (off-app email/SMS the app sent, captured; a `kind: log` run-dir artifact of digests only: from/to/subject/link digests + one OTP count, never raw) | see `comms` under Study File |
59
56
  | Serve result | `humanish.serve-result.v1` (`src/observer/serve.ts` is authoritative) | none (command result envelope; see Serve Result below) |
60
57
  | Serve control plane | reserved (`/_humanish/api/*` answers `501` `HUMANISH_SERVE_CONTROL_PLANE_DISABLED` in v1) | none |
61
58
 
62
- ## Lab Manifest
63
-
64
- Schema: `humanish.lab.v2` (types in `src/lab/types.ts`, parsed by `src/lab/config.ts`). There
65
- is deliberately no v1 compatibility: v1 (`kind`, top-level `sims`) had zero real users and was
66
- deleted when labs became config.
67
-
68
- A lab is a composition over code primitives, not a hardcoded kind:
59
+ ## Study File
60
+
61
+ Schema: `humanish.study.v3`. `src/lab/parse/study-v3.ts` rewrites a v3 document into the
62
+ normalized config in `src/lab/types.ts`, which `src/lab/config.ts` parses. A `humanish.lab.v2`
63
+ file still parses in 0.108 with one warning that names `humanish migrate`; 0.109 removes it.
64
+
65
+ A study declares its route and composes code primitives; it is not a hardcoded kind. The
66
+ top-level keys:
67
+
68
+ | Key | Meaning |
69
+ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `schema`, `id`, `title`, `description` | the document's identity |
71
+ | `route` | required: `preview`, `computer-use`, `shared-world`, `terminal` or `scripted` |
72
+ | `mode` | `dry-run` (the default) or `live` |
73
+ | `actor` | who drives it, one object: `type`, `model`, `localAgent`, `persona`, `mission`, `tasks`, `maxOutputTokens`, `reasoningEffort`, `stopWhen`, `dwell` |
74
+ | `participants` | a count, a homogeneous `{ count, instruction }`, or a list of entries (below) |
75
+ | `surfaces` | scripted only: `[desktop]` or `[desktop, mobile]` |
76
+ | `caps` | the route's budget (below) |
77
+ | `scenario` | scripted only: a committed scenario id or path |
78
+ | `subject`, `execution`, `policies`, `review`, `defaults`, `comms` | as below |
79
+
80
+ The parser reads only the declared route's keys. A key that route never reads is an error that
81
+ names the route, such as "route: computer-use does not read scenario. Remove it.", and an
82
+ unknown key is an error that lists the known ones; both are `HUMANISH_STUDY_INVALID`.
83
+
84
+ `participants` takes the forms its route accepts:
85
+
86
+ | Route | Forms | Default |
87
+ | -------------- | ------------------------------------------------------------------------------ | -------------- |
88
+ | `preview` | a count | 4 |
89
+ | `computer-use` | a count, a homogeneous object, or a list (a `local-app` subject takes no list) | 1 |
90
+ | `shared-world` | a list of at least two entries | none: required |
91
+ | `scripted` | none; `surfaces` instead | |
92
+ | `terminal` | none; one agent | |
93
+
94
+ `caps` keeps each route's meaning, and a route refuses the keys it does not read:
95
+ `computer-use` and `shared-world` read `maxUsd` and `maxTotalUsd`; `terminal` reads `maxUsd`,
96
+ `maxJobs` and `maxMinutes`; `preview` and `scripted` take no `caps`.
97
+
98
+ A v2 file maps as follows: `actors[0]` is `actor`; `lanes` is the `participants` list; a
99
+ `roster` group is an entry with `count`; `count` is the count; `count` with `laneFocus` is the
100
+ homogeneous object; `execution.caps` (computer-use, shared-world) or `scenario.caps`
101
+ (terminal) is `caps`; `scenario.mode` is `mode`; `scenario.ref` is `scenario`; and
102
+ `subject.topology: shared-world` is `route: shared-world`. A key the v2 inert-field table
103
+ marks inert for the route is not in v3.
104
+
105
+ The rest of this section names keys by their v3 spelling:
69
106
 
70
107
  - `subject`: what the run acts on: `this-repo`, `clone` (owner/repo slugs,
71
108
  optional in-sandbox `serve` + env var names + `state`), `local-tree` (the
72
109
  operator's own working tree, packed on the host and provisioned in-sandbox
73
110
  in place of a clone; see below), `app-url`
74
111
  (loopback unless `policies.allowPublicTargets` declares an owned deployment),
75
- `local-app` (an already-running LOCAL dev server driven IN-PROCESS via a
76
- custom `CuaExecutor`, NO clone and NO E2B desktop, always loopback),
112
+ `local-app` (an already-running local dev server driven in-process via a
113
+ custom `ComputerUseExecutor`, no clone and no E2B desktop, always loopback),
77
114
  `terminal-product` (a CLI/product a real autonomous terminal agent studies
78
- from PUBLIC surfaces only; see below), or `desktop-cli` (a computer-use
115
+ from public surfaces only; see below), or `desktop-cli` (a computer-use
79
116
  participant using a CLI in a terminal on a hosted desktop). A
80
117
  `local-app` subject pairs a computer-use actor with `execution.target: local`
81
118
  (or absent) and is library-assisted: the caller supplies
82
- `RunLabOptions.inProcess.executor` + `createProvider`; with neither the engine fails
83
- closed (`HUMANISH_CUA_LAB_LOCAL_APP_NO_EXECUTOR`), never a desktop attempt. See
119
+ `RunStudyOptions.inProcess.executor` + `createProvider`; with neither the engine fails
120
+ closed (`HUMANISH_COMPUTER_USE_LOCAL_APP_NO_EXECUTOR`), never a desktop attempt. See
84
121
  [`docs/architecture/state-driven-executor.md`](../architecture/state-driven-executor.md);
85
122
  - `subject.localTree` (`local-tree` subjects, computer-use route): pack/upload
86
123
  knobs for the packed working tree: `exclude[]` (extra archive excludes on
@@ -93,8 +130,8 @@ A lab is a composition over code primitives, not a hardcoded kind:
93
130
  `maxArchiveBytes` (upload size cap override; default 256 MiB). Routing requires `execution.target: e2b-desktop` and a
94
131
  computer-use actor; `subject.serve`/`env`/`state` apply exactly as they do
95
132
  on the clone route (identical install/build/start/state semantics). The
96
- packed root is the lab resolution cwd; there is no path field, by design
97
- (an absolute path in a lab manifest would be a machine-specific,
133
+ packed root is the study resolution cwd; there is no path field, by design
134
+ (an absolute path in a study file would be a machine-specific,
98
135
  unshareable, leak-prone artifact). Enumeration is git-aware when the root
99
136
  is a git work tree (`git ls-files --cached --others --exclude-standard`,
100
137
  honoring `.gitignore`) or a denylist-only recursive walk otherwise; an
@@ -104,17 +141,17 @@ A lab is a composition over code primitives, not a hardcoded kind:
104
141
  `src/subject/local-tree-archive.ts`)
105
142
  applies in both modes and is not overridable. The denylist matches names,
106
143
  not contents; a secret in a file it does not name packs like any other
107
- file, so review the pack summary line and use `localTree.exclude`. The lab packs
108
- ONCE per run and uploads the identical archive to every fan-out participant. The
144
+ file, so review the pack summary line and use `localTree.exclude`. The study packs
145
+ once per run and uploads the identical archive to every fan-out participant. The
109
146
  in-sandbox commit refresh clone subjects use is skipped: `.git` is never
110
147
  uploaded, so identity comes from the host-side archive digest instead. See
111
- [`docs/goals/local-tree-subject/goal.md`](https://github.com/danielgwilson/humanish/blob/main/docs/goals/local-tree-subject/goal.md);
148
+ [`docs/history/goals/local-tree-subject/goal.md`](https://github.com/danielgwilson/humanish/blob/main/docs/history/goals/local-tree-subject/goal.md);
112
149
  - `subject.product` (terminal-product subjects): the product the agent studies.
113
- `product.name` is a public-safe token (committed fixtures use a NEUTRAL mock
150
+ `product.name` is a public-safe token (committed fixtures use a neutral mock
114
151
  name); `product.publicSurfaces[]` is the list of http(s) URLs (docs, llms.txt,
115
- skill manifest) that are the ONLY world the agent sees. The lab does not
116
- clone/provision the product, so its provenance is recorded UNPINNED
117
- (invariant 5). `serve`/`clone`/`state`/`repos`/`appUrl` are rejected on a
152
+ skill manifest) that are the only world the agent sees. The study does not
153
+ clone/provision the product, so its provenance is recorded
154
+ unpinned. `serve`/`clone`/`state`/`repos`/`appUrl` are rejected on a
118
155
  terminal-product subject (a field that cannot act on the route is a parse
119
156
  error, not silently dropped). See
120
157
  [`docs/architecture/terminal-product-route.md`](../architecture/terminal-product-route.md);
@@ -122,83 +159,80 @@ A lab is a composition over code primitives, not a hardcoded kind:
122
159
  subject's state story. `state.seed[]` declares ordered, bounded
123
160
  seed/migration/fixture steps (`{ name, command, when: before-build |
124
161
  before-start | after-ready, timeoutMs }`) executed in-sandbox around the
125
- serve sequence; `state.external[]` declares env var NAMES (each must also
126
- appear in `subject.env`) pointing at state the lab does not control,
127
- recorded as UNPINNED in provenance. Commands persist in evidence as
162
+ serve sequence; `state.external[]` declares env var names (each must also
163
+ appear in `subject.env`) pointing at state the study does not control,
164
+ recorded as unpinned in provenance. Commands persist in evidence as
128
165
  sha256-16 digests only, never as text;
129
- - `actors`: who drives it. On computer-use (including shared-world),
130
- scripted-browser, and terminal-product routes, `actors[0].type` is a real
131
- dispatch key resolved against the actor registry. On the synthetic route it
132
- remains a descriptive label (e.g. `synthetic-persona`). The
166
+ - `actor`: who drives it. On the computer-use, shared-world, scripted and
167
+ terminal routes, `actor.type` is a real dispatch key resolved against the
168
+ actor registry. On the preview route it remains a descriptive label (e.g.
169
+ `synthetic-persona`). The
133
170
  `codex-exec` descriptor's direct `runSession` member is a fail-closed
134
- compatibility entry, not the live runner; the terminal-product lab route
171
+ compatibility entry, not the live runner; the terminal route
135
172
  owns the live sandbox, auth, cap, evidence, and cleanup lifecycle.
136
- `actors[0].count` carries route-specific meanings: synthetic route participant
137
- count (simCount); scripted-browser route surface roster (1 = desktop,
138
- 2 = desktop + mobile, default 1); computer-use **E2B** route the HOMOGENEOUS
139
- fan-out participant count (N identical participants, each its own E2B desktop: separate
140
- worlds, cap 16). The in-process/local-app computer-use route stays single
141
- participant (no E2B to fan out);
142
- - `actors[0].lanes[]` (computer-use E2B route): a DIFFERENTIATED fan-out roster,
143
- each `{ id?, actorType?, surface?, caseGroup?, persona?, device?,
173
+ A `participants` count is the preview route's participant count (simCount),
174
+ and on the computer-use **E2B** route the homogeneous fan-out count (N
175
+ identical participants, each its own E2B desktop: separate worlds, cap 16).
176
+ The scripted route's `surfaces` is its surface list (`[desktop]`, the default,
177
+ or `[desktop, mobile]`). The in-process/local-app computer-use route stays
178
+ single participant (no E2B to fan out);
179
+ - a `participants` list (computer-use E2B route): a differentiated fan-out,
180
+ each entry `{ id?, actorType?, surface?, caseGroup?, persona?, device?,
144
181
  instruction?, target?, entry?, host?, reasoningEffort?, stopWhen?, dwell? }` becoming one independent E2B desktop (or, on the
145
182
  shared-world routes, one participant against the shared plane). `actorType`,
146
183
  `surface`, and `caseGroup` are adapter-owned public-safe labels for grouping
147
184
  simulated users; they are not core enums, and `actorType` is deliberately
148
- separate from the execution dispatch key `actors[0].type`. `lanes` is XOR with
149
- `count` (declare a roster OR a homogeneous count) and XOR with
150
- `actors[0].laneFocus` (a roster's per-participant `instruction` is the steer);
151
- `lanes[].device` is XOR with a raw `execution.desktop.resolution`. Participant ids
185
+ separate from the execution dispatch key `actor.type`. A list sets the count,
186
+ so `--count` does not apply to it, and each entry's `instruction` is the steer;
187
+ an entry's `device` is XOR with a raw `execution.desktop.resolution`. Participant ids
152
188
  default `lane-01`..`lane-NN` (`role-01`..`role-NN` for shared-world participants), must be unique, and name per-participant evidence paths
153
- (`actors/<streamId>.json`, `screenshots/<laneId>/`). Cap 16 participants. On other
154
- routes `lanes` is inert (warned), except the scripted-browser route, which rejects it. `subject.clone.fanout` is REJECTED on
155
- the cua, shared-world and scripted-browser routes (declare fan-out via
156
- `count`/`lanes`). No current route reads `clone.fanout`;
157
- - `actors[0].lanes[].target` (app-url × computer-use E2B route only): an
189
+ (`actors/<streamId>.json`, `screenshots/<laneId>/`). Cap 16 participants. The other
190
+ routes refuse a list (table above). `subject.clone.fanout` is rejected on the
191
+ computer-use, shared-world and scripted routes (declare fan-out with
192
+ `participants`). No current route reads `clone.fanout`;
193
+ - `participants[].target` (app-url × computer-use E2B route only): an
158
194
  absolute browser URL that participant opens instead of `subject.appUrl`. This is the
159
- setup-produced-target handoff for crawler/swarm labs: an adapter may start any
195
+ setup-produced-target handoff for crawler/swarm studies: an adapter may start any
160
196
  topology it needs, then declare exactly which target each actor should drive.
161
- If any participant declares `target`, every participant in that roster must declare one.
197
+ If any participant declares `target`, every entry in the list must declare one.
162
198
  Public/non-loopback targets still require `policies.allowPublicTargets: true`.
163
199
  `target` is mutually exclusive with `entry`: `target` is an absolute app-url
164
200
  browser target; `entry` is a shared-world participant's same-origin path;
165
- - `actors[0].roster[]` (computer-use E2B route): compact authoring sugar for
166
- repeated participant groups, each `{ id, count, actorType?, surface?, caseGroup?,
167
- persona?, device?, instruction?, target?, entry?, host?, reasoningEffort?, stopWhen?,
168
- dwell? }`. The parser expands it into
169
- deterministic `lanes[]` before the engine runs (`viewer-01`, `viewer-02`,
170
- ...), so the runtime and run bundle keep one normalized participant shape. `roster`
171
- is XOR with explicit `lanes`, homogeneous `count`, and `laneFocus`;
201
+ - a `participants` entry with `count: n` (computer-use E2B route): a group of
202
+ repeated participants. It needs an `id` and the parser expands it to
203
+ `<id>-01` … `<id>-NN`, even when n is 1, before the engine runs (`viewer-01`,
204
+ `viewer-02`, ...), so the runtime and run bundle keep one normalized participant
205
+ shape. Ids are checked for collisions after expansion;
172
206
  - `execution.concurrency` (computer-use E2B routes, including shared-world): a
173
- CAP on participants in flight at once. When omitted, every participant runs
207
+ cap on participants in flight at once. When omitted, every participant runs
174
208
  simultaneously: independent participants resolve it from the final participant count,
175
209
  after any `--count` override, and the parser fills `concurrency = participantCount`
176
- for multi-participant shared-world labs. Total sessions and spend are identical either way; only wall-clock
210
+ for multi-participant shared-world studies. Total sessions and spend are identical either way; only wall-clock
177
211
  and simultaneity differ. Declaring a value below the participant count runs participants in
178
212
  waves and emits a warning saying so, because a green waved run is otherwise
179
- indistinguishable from the all-live run the author meant. Shared-world labs
213
+ indistinguishable from the all-live run the author meant. Shared-world studies
180
214
  need at least 2 (the host and a follower are live together); `concurrency: 1`
181
215
  there is refused at parse, since the sequential turn-taking route was removed
182
216
  in 0.106.0. The env override
183
- `HUMANISH_CUA_MAX_CONCURRENCY` may only LOWER the effective bound, never
184
- raise concurrent paid desktops (invariant 3), and a lowering is recorded on
217
+ `HUMANISH_CUA_MAX_CONCURRENCY` may only lower the effective bound, never
218
+ raise concurrent paid desktops, and a lowering is recorded on
185
219
  the plan (`envLoweredConcurrencyFrom`). Inert (warned) on other routes.
186
- `execution.timeoutMs` is the PER-PARTICIPANT session budget on this route (semantics
220
+ `execution.timeoutMs` is the per-participant session budget on this route (semantics
187
221
  change: it was the single-session budget pre-fan-out); there is no run-level
188
222
  wall clock. `policies.allowPublicTargets` cannot combine with N>1 against one
189
223
  implicit public `subject.appUrl` (ambiguous shared-world-ish topology); it may
190
- combine with N>1 only when the roster declares explicit `lanes[].target` for
191
- every participant, OR when `subject.topology: shared-world` is ALSO declared. That
192
- routes N>1 against one public target to the EXTERNAL-PUBLIC shared-world plane
224
+ combine with N>1 only when every `participants` entry declares `target`, or when
225
+ the study declares `route: shared-world`. That
226
+ routes N>1 against one public target to the external-public shared-world plane
193
227
  (see Shared-World Evidence below), not separate worlds;
194
228
  - `subject.publicTarget: { owner, authorized: true }` (external-public shared-world
195
- route ONLY): the operator's REQUIRED ownership attestation when a real PUBLIC
196
- deployment (`source: app-url` + `topology: shared-world` + `allowPublicTargets` +
229
+ route only): the operator's required ownership attestation when a real public
230
+ deployment (`source: app-url` + `route: shared-world` + `allowPublicTargets` +
197
231
  `concurrency > 1`) is used directly as the shared plane. The harness neither
198
232
  provisions nor exposes the target, so it cannot attest synthetic data; the operator
199
- MUST attest they own/operate it. Author-trust (unverifiable); rejected on non-app-url
200
- subjects and inert (warned) on other app-url routes. `actors[0].lanes[].host: true` marks the single designated host participant there;
201
- - `subject.appUrl`: a loopback http(s) URL a computer-use actor drives, OR (on the
233
+ must attest they own/operate it. Author-trust (unverifiable); rejected on non-app-url
234
+ subjects and inert (warned) on other app-url routes. `participants[].host: true` marks the single designated host participant there;
235
+ - `subject.appUrl`: a loopback http(s) URL a computer-use actor drives, or (on the
202
236
  external-public shared-world route) the non-loopback public deployment used as the
203
237
  shared plane (with `publicTarget` + `allowPublicTargets`);
204
238
  - `execution`: where it runs (`local`, `e2b-desktop`, or `e2b-terminal`), plus
@@ -210,18 +244,18 @@ dwell? }`. The parser expands it into
210
244
  computer-use and scripted-browser routes refuse it when a library caller skips
211
245
  the parser;
212
246
  - `execution.desktop.template` (e2b-desktop computer-use and clone scripted-browser routes): a custom E2B
213
- desktop TEMPLATE (image) NAME or ID the run launches on, for a subject that
247
+ desktop template (image) name or ID the run launches on, for a subject that
214
248
  needs runtimes the stock `desktop` image lacks (e.g. node/bun/a local Postgres
215
249
  baked into an adopter-maintained image). Any non-empty string is a valid
216
250
  name/id (no allowlist); a blank/whitespace value is rejected. Threaded to the
217
- SDK's `Sandbox.create(template, opts)` on EVERY desktop-creating route (the
218
- single-participant and fan-out cua participants, and the shared-world subject AND every actor
251
+ SDK's `Sandbox.create(template, opts)` on every desktop-creating route (the
252
+ single-participant and fan-out cua participants, and the shared-world subject and every actor
219
253
  sandbox); when absent the call
220
254
  stays the byte-stable `Sandbox.create(opts)` default (the stock template). The
221
255
  template actually used is recorded in the run bundle as `desktopTemplate`
222
256
  (public-safe, since a template name is not a secret). Inert (warned) on every route
223
257
  that creates no desktop, incl. the in-process `local-app` cua route, so it is
224
- never silently ignored (invariant 6). Custom images need the
258
+ never silently ignored. Custom images need the
225
259
  Desktop SDK's `xdotool` input support, `mktemp`, and the `C.UTF-8` locale:
226
260
  every typed text is written to a private 0600 file and typed with
227
261
  `LC_ALL=C.UTF-8 xdotool type --file` in one attempt. Without that locale a
@@ -238,79 +272,76 @@ dwell? }`. The parser expands it into
238
272
  when known. Inert (warned) where this route-specific browser launcher is not
239
273
  used;
240
274
  - `execution.terminal` + `execution.runtimeAuth` (terminal-product route):
241
- `terminal.transport` is `exec-stream`: captured NON-interactive exec output
275
+ `terminal.transport` is `exec-stream`: captured non-interactive exec output
242
276
  (stdin disabled); `pty` is rejected because labeling captured exec output as
243
- an interactive PTY would overstate the mechanism (invariant 6; a true duplex
244
- PTY transport does not ship). `terminal.stdin` defaults to `disabled`
277
+ an interactive PTY would overstate the mechanism (a duplex PTY
278
+ transport does not ship). `terminal.stdin` defaults to `disabled`
245
279
  (`sent`/assisted input is rejected until the interventions ledger + a
246
280
  non-comparable marker exist). `runtimeAuth` (`openai-env`, the default, or `openai-egress`)
247
- declares the agent's runtime-auth channel, recorded as NAMES ONLY. On a live run, the engine
281
+ declares the agent's runtime-auth channel, recorded as names only. On a live run, the engine
248
282
  resolves the registered terminal descriptor and requires
249
283
  `keyPlacement: in-sandbox-command-scoped` before creating a sandbox. With `openai-env`
250
284
  the key is passed only to the agent command, never to `Sandbox.create` or
251
285
  metadata; with `openai-egress` it stays in the host-side E2B header transform
252
286
  set at sandbox creation, and the agent gets a placeholder. A dry-run neither
253
287
  reads nor injects it;
254
- - `scenario`: `mode: dry-run` (contract evidence, no spend) or `live`.
255
- `scenario.ref` is CONSUMED (and REQUIRED) on the scripted-browser route: it
256
- resolves a committed scenario (`humanish/scenarios/<ref>.yaml` or a repo
257
- path) whose `browser.steps` ARE what the actor executes, digest-pinned into
258
- bundle provenance; on other routes `ref`/`inline` stay forward-declared
259
- warnings. On the scripted route `live` gates real browser ACTUATION against
260
- the declared app. Provider spend stays $0 by mechanism (no model runs);
261
- - `scenario.caps` (terminal-product route): `{ maxUsd, maxJobs, maxMinutes }`,
288
+ - `mode`: `dry-run` (a synthetic bundle, no spend; the default) or `live`. On the
289
+ scripted route `live` gates real browser actuation against the declared app.
290
+ Provider spend stays $0 there by mechanism (no model runs);
291
+ - `scenario` (scripted route, required): a committed scenario id or path
292
+ (`humanish/scenarios/<id>.yaml` or a repo path) whose `browser.steps` are what
293
+ the actor executes, digest-pinned into bundle provenance. Every other route
294
+ refuses it;
295
+ - `caps` on the terminal route: `{ maxUsd, maxJobs, maxMinutes }`,
262
296
  all non-negative numbers (0 = no-spend, the default). This budget bounds the
263
297
  in-sandbox live key by mechanism: the live key is never exercised without a
264
298
  fail-closed cap in force. `maxMinutes` is the
265
299
  wall-clock kill; `maxUsd`/`maxJobs` are enforced fail-closed against the cost
266
- ledger after the session (a run whose KNOWN spend exceeds the cap fails closed,
267
- `HUMANISH_TERMINAL_LAB_CAPS_EXCEEDED`). Core records Codex provider spend as
300
+ ledger after the session (a run whose known spend exceeds the cap fails closed,
301
+ `HUMANISH_TERMINAL_CAPS_EXCEEDED`). Core records Codex provider spend as
268
302
  unpriced tokens, so without a cost probe no line can trip a positive `maxUsd`:
269
303
  a live run refuses `maxUsd > 0` before creating a sandbox
270
- (`HUMANISH_TERMINAL_LAB_UNPRICED_CAP`) unless the caller passes a `costProbe`
304
+ (`HUMANISH_TERMINAL_UNPRICED_CAP`) unless the caller passes a `costProbe`
271
305
  hook. The sandbox's server-side timeout is the steps before the
272
306
  codex command (Node bootstrap, runtime version check, and product setup when
273
307
  `subject.product.install` is declared) plus `maxMinutes` plus a 5-minute
274
308
  teardown buffer; a live run refuses a `maxMinutes` that would take it past
275
- E2B's one-hour limit (`HUMANISH_TERMINAL_LAB_CAPS_INVALID`). The no-spend proof is derived from that real ledger, never asserted (see
276
- Terminal Cost Ledger And No-Spend Proof).
277
- Inert (warned) on every other route, except that a positive
278
- `scenario.caps.maxUsd` or `scenario.caps.maxTotalUsd` on a computer-use lab
279
- is a parse error naming the matching `execution.caps` field, which is what
280
- that route enforces;
309
+ E2B's one-hour limit (`HUMANISH_TERMINAL_CAPS_INVALID`). The no-spend proof is derived from that real ledger, never asserted (see
310
+ Terminal Cost Ledger And No-Spend Proof). `maxTotalUsd` is refused on the
311
+ terminal route;
281
312
  - `policies`: `redactRepos`, `redactScreenshots`, `allowPublicTargets`,
282
313
  `mediaPermission` (`prompt` | `granted`), and the
283
314
  terminal-product credential-boundary booleans `allowPrivateRepoAccess`,
284
315
  `allowProviderCredentials`, `allowPaymentCredentials`, `allowGitHubMutation`
285
- (all DEFAULT FALSE, deny-by-default; only the runtime LLM key enters, and
316
+ (all default false, deny-by-default; only the runtime LLM key enters, and
286
317
  only command-scoped). The scripted-browser route is loopback-only and rejects
287
318
  `redactScreenshots: true` (blur unimplemented there) and
288
319
  `allowPublicTargets: true` fail-closed rather than ignoring them.
289
- - `comms` (#297; hosted on the clone/local-tree computer-use route and the
320
+ - `comms` (hosted on the clone/local-tree computer-use route and the
290
321
  shared-world getHost plane, or connected to an external catch on
291
322
  app-url/operator-provided subjects): off-app
292
- email the app itself sends, made a persona-driven testable surface. Lab
323
+ email the app itself sends, made a persona-driven testable surface. Study
293
324
  configuration rejects `comms.sms` and unknown channel names; message-bus SMS
294
325
  types do not imply a supported SMS execution route. SMTP capture is supported
295
326
  on provisioned routes with separate worlds and rejected for shared-world studies.
296
327
  `comms.email` = `{ kind: fake, injectEnv?, port?, smtp?, recipients?, linkOrigin?, external? }`.
297
- `injectEnv` is the ADOPTER-NAMED env var the app reads for its email-API base
328
+ `injectEnv` is the adopter-named env var the app reads for its email-API base
298
329
  URL (e.g. `RESEND_API_URL`); the harness sets it to an in-sandbox catch (so it
299
- is NOT declared in `subject.env`) that captures the app's sends without touching
330
+ is not declared in `subject.env`) that captures the app's sends without touching
300
331
  the internet. Verify the app actually reads that variable, because a run whose
301
332
  catch captured zero sends warns at teardown for exactly that. `kind` must be
302
333
  `fake`; a real inbox names a saved `comms.email.connection` instead; `port` ≤ 65534
303
334
  (the catch reserves `port+1` for the read-only inbox listener the shared-world
304
- route getHost-exposes). `recipients[]` = `{ lane, address? }`: OMIT the list
335
+ route getHost-exposes). `recipients[]` = `{ lane, address? }`: omit the list
305
336
  and the parser fills one deterministic address per participant
306
- (`<laneId>@example.test`) so every participant can do email (#351). When declared, a
307
- `lane` must be one of the lab's real participant ids (roster ids, or the generated
308
- `lane-01..lane-NN` under `count`). An unknown participant id is a hard parse error
337
+ (`<laneId>@example.test`) so every participant can do email. When declared, a
338
+ `lane` must be one of the study's real participant ids (list or group ids, or the
339
+ generated `lane-01..lane-NN` under a count). An unknown participant id is a hard parse error
309
340
  listing them, zero addressed participants is a hard error, partial coverage warns
310
341
  with the uncovered participants. Each addressed participant's actor prompt is extended with
311
342
  the full handoff: its address ("enter exactly that"), the inbox URL, and the
312
343
  wait steering ("waiting for an email is normal, not a blocker").
313
- `external` (#328) switches the funnel to an ADOPTER-HOSTED catch, which is what
344
+ `external` switches the funnel to an adopter-hosted catch, which is what
314
345
  makes comms work on planes humanish does not provision (app-url /
315
346
  operator-provisioned): `{ catchBaseUrl, inboxBaseUrl?, authTokenEnv? }`. The
316
347
  operator runs the catch and points their own app's email-API base URL at it.
@@ -323,19 +354,19 @@ dwell? }`. The parser expands it into
323
354
  `humanish-comms-catch` marker, so a proxy answering 200 for everything cannot
324
355
  pass for a catch), the teardown drain over `GET /deliveries`, and the same
325
356
  digest-only evidence. `authTokenEnv` names an env var holding a bearer token
326
- for the drain read. The NAME is recorded as evidence; the value never
357
+ for the drain read. The name is recorded as evidence; the value never
327
358
  persists. A live run refuses a token shorter than 16 characters, or not
328
359
  well-formed Unicode, before it probes the catch
329
- (`HUMANISH_CUA_LAB_COMMS_TOKEN_INVALID`,
330
- `HUMANISH_CONCURRENT_SHARED_WORLD_LAB_COMMS_TOKEN_INVALID`), as
360
+ (`HUMANISH_COMPUTER_USE_COMMS_TOKEN_INVALID`,
361
+ `HUMANISH_SHARED_WORLD_COMMS_TOKEN_INVALID`), as
331
362
  `humanish comms catch --token` does, and scrubs the token and its encoded
332
363
  forms from every comms warning. Declaring `external` on a harness-provisioned subject warns: two
333
364
  catches would exist and the app would point at humanish's own. `linkOrigin`
334
365
  is an optional operator-declared origin the app bakes into links when it
335
366
  differs from the serve origin; the harness rewrites captured links through it
336
367
  so a clicked link resolves to a reachable host. Captured mail is drained into a
337
- digest-only `humanish.comms-thread.v1` artifact (from/to/subject/link DIGESTS +
338
- an OTP COUNT; no raw address/link/code persists). The READABLE proof a
368
+ digest-only `humanish.comms-thread.v1` artifact (from/to/subject/link digests +
369
+ an OTP count; no raw address/link/code persists). The readable proof a
339
370
  persona saw the email is its screenshots of the inbox page. Requires `python3`
340
371
  in the subject sandbox (the stock E2B desktop template has it).
341
372
 
@@ -361,7 +392,7 @@ Adding an existing name with different settings refuses to overwrite it.
361
392
  profiles and local key presence/source, not provider authentication or delivery.
362
393
  `comms providers --json` returns `humanish.comms-providers.v1` with explicit
363
394
  setup/receiving availability. The key store accepts `humanish keys set agentmail`.
364
- Saving a connection neither modifies a lab nor creates a provider resource;
395
+ Saving a connection neither modifies a study nor creates a provider resource;
365
396
  a supported study explicitly selects `comms.email: { connection: agentmail }`.
366
397
  Optional `allowedOrigins` lists exact additional HTTP(S) origins; `linkOrigin`
367
398
  participates in the existing provisioned-subject origin rewrite. Connection
@@ -379,15 +410,15 @@ acquisition and cleanup status. It contains no raw addresses, bodies, provider I
379
410
  or content digests. `run.json` embeds the final projection as `commsReceiving` and
380
411
  carries `publication.restrictions: [real-communications]`. Verification keeps the
381
412
  run local-only regardless of screenshot redaction. See the
382
- [receiving contract](../architecture/real-email-receiving.md).
383
-
384
- Lab backends report results in their own schemas (`humanish.run-result.v1`,
385
- `humanish.cua-lab-result.v2`, `humanish.scripted-lab-result.v1`,
386
- `humanish.terminal-lab-result.v1`,
387
- `humanish.concurrent-shared-world-lab-result.v1`); the evidence record stays
388
- `humanish.run-bundle.v1` in every case. The computer-use result bumped to v2 for
389
- fan-out: it carries `plan` (the pre-flight participant table: concurrency, waves,
390
- per-participant session budget, worst-case sandbox-minutes), `lanes[]` (ALWAYS present,
413
+ [receiving contract](https://humanish.dev/docs/email-receiving).
414
+
415
+ A study's result has one schema on every route, `humanish.study-result.v1`
416
+ (`src/run/study-result.ts`), with `route`, `studyId` and, until 0.109, a deprecated `labId`
417
+ holding the same value. Route-specific fields stay on their route's result. `humanish run`
418
+ with no study, `observe`, and a refusal made before a route is picked keep
419
+ `humanish.run-result.v1`. The evidence record stays `humanish.run-bundle.v1` in every case.
420
+ The computer-use result carries `plan` (the pre-flight participant table: concurrency, waves,
421
+ per-participant session budget, worst-case sandbox-minutes), `lanes[]` (always present,
391
422
  length 1 at N=1; per-participant status/session/sandbox/subject), and `laneSummary`
392
423
  (passed/skipped/harnessErrors/hollow counts). The top-level `session`/`sandbox`
393
424
  mirror the first participant and `subject.commit` is unanimity-gated across participants
@@ -396,14 +427,14 @@ the run bundle is byte-stable with the pre-fan-out output; only the result
396
427
  projection changed. A fan-out run records a `cua-lab.fanout.plan` bundle event
397
428
  (and a `cua-lab.fanout.fail-fast` event when a harness error skips queued participants);
398
429
  `ok = observer.ok ∧ every participant ok ∧ no failed adapter or declared-scorer verdict`,
399
- where a dry-run participant is ok as contract evidence and a live participant must pass
430
+ where a dry-run participant is ok as synthetic and a live participant must pass
400
431
  `participantPassed` (`src/run/judge.ts`): not skipped, status `passed`, no harness
401
432
  error, engaged, and no self-reported blocker.
402
433
 
403
434
  Explicit failed-participant reruns are supported on the CUA fan-out route via
404
- `humanish lab run <lab> --rerun-failed-from <run-id> [--participants <ids>]`, where a
405
- participant id is a declared `actors[0].lanes[].id`, or `lane-01`, `lane-02`, … by position.
406
- The source run must be a live CUA fan-out bundle. humanish creates a NEW run for
435
+ `humanish run <study> --rerun-failed-from <run-id> [--participants <ids>]`, where a
436
+ participant id is a declared `participants` entry id, or `lane-01`, `lane-02`, … by position.
437
+ The source run must be a live CUA fan-out bundle. humanish creates a new run for
407
438
  the selected failed/blocked/timed-out/hollow participants (or explicit participant ids), leaves
408
439
  the source verdict unchanged, and records lineage as `run.rerun` plus a
409
440
  `cua-lab.fanout.rerun` event: source run id, selected participant ids, and previous participant
@@ -414,26 +445,28 @@ That field is the judge's status (`judgedStatus` in `src/run/judge.ts`), so a go
414
445
  session that reported a blocker counts as blocked. A source bundle without `judgedStatus` falls back
415
446
  to the stream's trace status, its actor status and completion reason, and a zero-action check.
416
447
 
417
- Manifests are human-authored `.yaml` source under `humanish/labs/*.yaml` for
418
- committed public-safe labs, or ignored `.humanish/labs/*.yaml` /
419
- `.humanish/local/labs/*.yaml` for private local dogfood. Fields the engine does
420
- not yet consume are accepted but reported as warnings (`humanish lab inspect`
421
- shows them), so a manifest never silently claims behavior that did not run.
448
+ Study files are human-authored `.yaml` source under `humanish/studies/*.yaml` for
449
+ committed public-safe studies, or ignored `.humanish/studies/*.yaml` /
450
+ `.humanish/local/studies/*.yaml` for private local dogfood. A v3 key the declared route
451
+ does not read is an error, so a study file never silently claims behavior that did not run.
452
+ A v2 file keeps its inert-field warnings until 0.109 (`humanish study show` shows them).
422
453
 
423
- Committed fixture (`humanish/labs/first-run.yaml`):
454
+ Committed fixture (`humanish/studies/first-run.yaml`):
424
455
 
425
456
  ```yaml
426
- schema: humanish.lab.v2
457
+ schema: humanish.study.v3
427
458
  id: first-run
428
459
  title: First-run synthetic Observer
429
- description: Public-safe starter lab that generates a synthetic run bundle and Observer without provider spend.
460
+ description: >-
461
+ Writes a preview run: a synthetic run bundle and Observer for four participants. Needs no browser,
462
+ model or keys, and costs nothing. Run it with humanish run first-run.
463
+ route: preview
464
+ mode: dry-run
430
465
  subject:
431
466
  source: this-repo
432
- actors:
433
- - type: synthetic-persona
434
- count: 4
435
- scenario:
436
- mode: dry-run
467
+ actor:
468
+ type: synthetic-persona
469
+ participants: 4
437
470
  defaults:
438
471
  open: true
439
472
  ```
@@ -462,38 +495,40 @@ Core-owned fields:
462
495
  - `artifacts`
463
496
  - `review`
464
497
  - `feedbackCandidates`
465
- - `lab` (optional, additive): which manifest produced the run,
466
- `{ id, path?, origin? }`, where `origin` is `committed` (humanish/labs),
498
+ - `study` (optional, additive): which study file produced the run,
499
+ `{ id, path?, origin? }`, where `origin` is `committed` (`humanish/studies/`, or
500
+ `humanish/labs/` for a v2 file),
467
501
  `ignored` (a local overlay), or `explicit` (a path the operator passed).
468
- Absent on bundles written before this contract and on library callers who
469
- hand a `LabConfig` directly. Such a run has no lab, and nothing guesses
470
- one. Readers wanting attribution for an older bundle may fall back to
471
- `inferLegacyLabId`, which reads only the historical
472
- `persona.source = "lab:<id>"` convention and nothing else.
502
+ `lab` holds the same value until 0.109 removes it, so a 0.107 reader keeps working.
503
+ Both are absent on bundles written before this contract and on library callers who
504
+ hand a `StudyConfig` directly; such a run has no study. Readers go through
505
+ `studyProvenanceOf` (`src/run/study-provenance.ts`): a valid `study`, else a valid `lab`,
506
+ else `inferLegacyStudyId`, which reads the `persona.source` or `scenario.source`
507
+ convention `study:<id>`, or `lab:<id>` from runs written before 0.108, and nothing else.
473
508
  - `subject` (optional, additive): structured subject provenance,
474
509
  `{ source: clone | app-url | local-tree, repo?, commit?, archiveSha256?,
475
510
  dirty?, envNames?, state }` where `state` is `{ provenance: seeded |
476
511
  unpinned | declared-not-run | undeclared | external-public, seed?: [{ name, when,
477
512
  commandDigest, ok?, exitCode?, timedOut?, durationMs? }], externalEnvNames?
478
- }`. Emitted by the computer-use and shared-world backends and the clone
513
+ }`. Emitted by the computer-use and shared-world routes and the clone
479
514
  scripted-browser route; absent on pre-existing and other bundles. `repo`/`commit` are clone-route fields; `archiveSha256`
480
515
  (64-hex sha256, the local-tree provenance pin) and `dirty` (host git
481
516
  porcelain status at pack time) are local-tree-route fields, additive under
482
517
  `humanish.run-bundle.v1`: a dirty working tree cannot be commit-pinned, so
483
518
  the archive content digest stands in for it. `commandDigest` is the
484
519
  sha256-16 of the exact seed command; command text and env values never
485
- appear. `humanish verify` fails closed when a LIVE `local-tree` bundle carries
520
+ appear. `humanish verify` fails closed when a live `local-tree` bundle carries
486
521
  no well-formed `archiveSha256`, in addition to the existing `subject state
487
522
  provenance` check.
488
- - `desktopTemplate` (optional, additive): the custom E2B desktop TEMPLATE (image)
523
+ - `desktopTemplate` (optional, additive): the custom E2B desktop template (image)
489
524
  the run's sandbox(es) launched on, from `execution.desktop.template`, so the
490
- evidence shows WHICH image ran. Present only when a template was configured;
525
+ evidence shows which image ran. Present only when a template was configured;
491
526
  absent == the stock `desktop` template, so every pre-existing bundle is
492
527
  byte-stable. Public-safe (a template name is not a secret).
493
528
  - `attributionClass` (optional, additive): `isolated | shared-world`. Absent ==
494
529
  `isolated`, so every existing bundle is byte-stable. It is the
495
- interaction-attribution axis (#164), independent of the persona-sampling
496
- evidence classes. Set to `shared-world` by the shared-world backend, paired
530
+ interaction-attribution axis, independent of the persona-sampling
531
+ evidence classes. Set to `shared-world` by the shared-world route, paired
497
532
  with `sharedWorld`.
498
533
  - `sharedWorld` (optional, additive): the shared-world evidence block
499
534
  (`humanish.shared-world.v1`); see [Shared-World Evidence](#shared-world-evidence)
@@ -567,43 +602,43 @@ feedbackCandidates: []
567
602
 
568
603
  ## Shared-World Evidence
569
604
 
570
- The shared-world topology (#164) is the DECLARED override of the `per-lane-worlds`
571
- default: N distinct participants drive ONE provisioned, mutable service plane (one
572
- app + one seeded DB) so their actions interact through shared state. The ONE
605
+ The shared-world topology is the declared override of the `per-lane-worlds`
606
+ default: N distinct participants drive one provisioned, mutable service plane (one
607
+ app + one seeded DB) so their actions interact through shared state. The one
573
608
  subject plane is provisioned via `subject.source: clone` (a fresh `git clone`) or
574
609
  `subject.source: local-tree` (the operator's own working tree, packed on the host
575
610
  and provisioned in-sandbox in place of a clone - see `subject.localTree` above);
576
611
  both sources are accepted on the shared-world route.
577
612
 
578
- Current runs write only the CONCURRENT shape below. The SEQUENTIAL shape came from the
613
+ Current runs write only the concurrent shape below. The sequential shape came from the
579
614
  sequential shared-world route, which 0.106.0 removed. Only bundles written before
580
615
  0.106.0 carry it, and `humanish verify` still checks them
581
- (`tests/run/legacy-sequential-shared-world.test.ts`). The SEQUENTIAL rules below apply
616
+ (`tests/run/legacy-sequential-shared-world.test.ts`). The sequential rules below apply
582
617
  to those bundles.
583
618
 
584
- A shared-world bundle adds TWO additive, optional fields to `humanish.run-bundle.v1`
619
+ A shared-world bundle adds two additive, optional fields to `humanish.run-bundle.v1`
585
620
  (absent on every other bundle, so they stay byte-stable):
586
621
 
587
622
  - `attributionClass: isolated | shared-world`: a separate attribution axis
588
- ("how well did the run attribute INTERACTION?"), distinct from the persona-sampling
623
+ ("how well did the run attribute interaction?"), distinct from the persona-sampling
589
624
  evidence classes ("how representative is the actor?"). Absent == `isolated`.
590
- - `sharedWorld` (`humanish.shared-world.v1`): TWO variants discriminated by
625
+ - `sharedWorld` (`humanish.shared-world.v1`): two variants discriminated by
591
626
  `topologyMode: "sequential" | "concurrent"` (`sharedWorldEvidenceFindings` in `src/verify/shared-world.ts` branches on
592
- it FIRST; unknown/missing or a mismatched shape fails closed). Common fields:
627
+ it first; unknown/missing or a mismatched shape fails closed). Common fields:
593
628
  - `topology: shared-world`
594
629
  - `topologyMode: sequential | concurrent`
595
- - `roleCount`: the DECLARED number of participants.
596
- - `plane: { commit?, seedDigest, envNames, hostDigest?, exposure?, publicOriginDigest?, declaredOriginDigest? }`: the ONE
630
+ - `roleCount`: the declared number of participants.
631
+ - `plane: { commit?, seedDigest, envNames, hostDigest?, exposure?, publicOriginDigest?, declaredOriginDigest? }`: the one
597
632
  shared-plane provenance. `seedDigest` is the sha256-16 of the ordered seed-step
598
- command digests (the seed RECIPE identity, not the runtime state); `envNames` are
599
- NAMES only. `hostDigest`/`exposure` are CONCURRENT provisioned-getHost-only, and
600
- `publicOriginDigest` is CONCURRENT external-public-only (below).
633
+ command digests (the seed recipe identity, not the runtime state); `envNames` are
634
+ names only. `hostDigest`/`exposure` are concurrent provisioned-getHost-only, and
635
+ `publicOriginDigest` is concurrent external-public-only (below).
601
636
  - `planeClass?: provisioned-getHost | external-public` and `lobbyConvergenceDigest?` are
602
- CONCURRENT-only (below); both absent on the sequential shape.
637
+ concurrent-only (below); both absent on the sequential shape.
603
638
  - `attributionLimits: [...]`: the verify-enforced attribution ceiling (the set
604
639
  differs per `topologyMode`, below).
605
640
 
606
- SEQUENTIAL shape (`topologyMode: sequential`, #164 PR1; only in bundles written before
641
+ Sequential shape (`topologyMode: sequential`; only in bundles written before
607
642
  0.106.0):
608
643
  - `sequence: [roleId, …]`: the role ids that actually took a turn, in declared order.
609
644
  - `skippedTail` (optional, live sequential only): `{ afterRoleId, roles,
@@ -620,121 +655,121 @@ cause, maxTotalUsd?, estimatedTotalUsd? }`. Each ordered `roles` entry names
620
655
  timeline that starts `cp-baseline`, alternates checkpoint → turn → checkpoint,
621
656
  and ends on a checkpoint:
622
657
  - checkpoint = `{ kind: checkpoint, name, digest, deltaFromPrev }`; `digest` is
623
- sha256-16(scrub+redact(probe stdout)); the record is DIGEST-ONLY (no
658
+ sha256-16(scrub+redact(probe stdout)); the record is digest-only (no
624
659
  value-shaped field). `deltaFromPrev` is true when the observed state changed
625
660
  across the intervening turn.
626
661
  - turn = `{ kind: turn, roleId, simId, streamId, commit?, seedDigest }` references
627
662
  a real `RunBundle["simulations"][number]` and `RunBundle["streams"][number]`; carries the
628
663
  plane provenance it observed
629
664
  (identical across turns by construction; this is the single-plane proof).
630
- - Sequential `attributionLimits` MUST contain `sequential-only`, `no-concurrent-races`,
665
+ - Sequential `attributionLimits` must contain `sequential-only`, `no-concurrent-races`,
631
666
  and `delta-attributed-to-turn-not-action`.
632
667
 
633
- CONCURRENT shape (`topologyMode: concurrent`, #164 phase 2: N personas drive ONE
634
- getHost-exposed plane AT ONCE; NO `timeline`/`sequence`):
635
- - `plane.hostDigest`: sha256-16 of the harness-minted `getHost` ORIGIN every actor
636
- drove (a first-class provisioned-subject target; invariant 2). A DIGEST, not the raw
668
+ Concurrent shape (`topologyMode: concurrent`: N personas drive one
669
+ getHost-exposed plane at once; no `timeline`/`sequence`):
670
+ - `plane.hostDigest`: sha256-16 of the harness-minted `getHost` origin every actor
671
+ drove (a first-class provisioned-subject target). A digest, not the raw
637
672
  URL: a getHost URL embeds the live sandbox id and matches the publish-safety e2b-URL
638
673
  redaction, so it never lands raw in a published bundle (the raw tokenless URL is
639
- surfaced only on the ephemeral lab result). The orchestrator confirms the URL is
640
- TOKENLESS (no authKey; invariant 1) before digesting.
641
- - `plane.exposure: synthetic`: the REQUIRED author attestation that the subject behind
674
+ surfaced only on the ephemeral study result). The orchestrator confirms the URL is
675
+ tokenless (no authKey) before digesting.
676
+ - `plane.exposure: synthetic`: the required author attestation that the subject behind
642
677
  the internet-reachable getHost URL is synthetic seeded data (author-trust + a
643
- provenance gate, NOT a no-real-data guarantee).
678
+ provenance gate, not a no-real-data guarantee).
644
679
  - `laneWindows: [{ roleId, simId, streamId, startedAt, endedAt, verdict, routeHostDigest,
645
- commit?, seedDigest }]`: one harness-clocked window per actor; OVERLAPPING windows
680
+ commit?, seedDigest }]`: one harness-clocked window per actor; overlapping windows
646
681
  prove ≥2 personas were active simultaneously. `routeHostDigest` == `plane.hostDigest`
647
682
  (every actor drove exactly the harness-minted host).
648
683
  - `stateSeries: [{ timestamp, digest }]`: cadence digests of the shared world under
649
- load (baseline + periodic + final). DIGEST-ONLY: the allowed-keys tripwire permits
650
- ONLY `timestamp` + `digest` (no per-delta→actor field, because causation under concurrency
684
+ load (baseline + periodic + final). Digest-only: the allowed-keys tripwire permits
685
+ only `timestamp` + `digest` (no per-delta→actor field, because causation under concurrency
651
686
  is structurally inexpressible).
652
687
  - `outcomes: [{ roleId, simId, streamId, status, completionReason?, ok }]`: per-persona
653
- OUTCOME (the "M of N succeeded" headline).
654
- - Concurrent `attributionLimits` MUST contain `concurrent`,
688
+ outcome (the "M of N succeeded" headline).
689
+ - Concurrent `attributionLimits` must contain `concurrent`,
655
690
  `best-effort-causal-attribution`, `non-deterministic-shared-state`,
656
691
  `window-and-snapshot-granularity`, `contention-observed-not-proven-safe`,
657
- `state-change-not-isolated-to-actors`, and MUST NOT contain `sequential-only` or
692
+ `state-change-not-isolated-to-actors`, and must not contain `sequential-only` or
658
693
  `no-concurrent-races` (a sequential guarantee on a concurrent run is an overclaim).
659
694
 
660
695
  The `shared-world evidence` check in `humanish verify` is fail-closed (live runs only;
661
- dry-run contract bundles are skipped). It dispatches on `topologyMode` FIRST.
662
- SEQUENTIAL: the timeline must be well-formed (start `cp-baseline`, strictly alternate,
696
+ dry-run contract bundles are skipped). It dispatches on `topologyMode` first.
697
+ Sequential: the timeline must be well-formed (start `cp-baseline`, strictly alternate,
663
698
  end on a checkpoint, turn order == sequence, sequence length == roleCount == turn
664
699
  count, no `laneWindows`); every turn's simId/streamId resolves; every checkpoint digest
665
- is sha256-16 with NO value-shaped field; all turns share ONE plane provenance; the
666
- mandatory limits are present; and a PASSED run shows ≥1 checkpoint `deltaFromPrev` (the
667
- delta-on-pass gate). CONCURRENT: no `timeline`; laneWindows and outcomes each
668
- cover exactly roleCount and stateSeries is present; the required limits are present AND the forbidden ones absent;
669
- `plane.hostDigest` present and every `routeHostDigest` equals it (invariant 2);
670
- `plane.exposure == synthetic` AND `subject.state.provenance == seeded` (the
700
+ is sha256-16 with no value-shaped field; all turns share one plane provenance; the
701
+ mandatory limits are present; and a passed run shows ≥1 checkpoint `deltaFromPrev` (the
702
+ delta-on-pass gate). Concurrent: no `timeline`; laneWindows and outcomes each
703
+ cover exactly roleCount and stateSeries is present; the required limits are present and the forbidden ones absent;
704
+ `plane.hostDigest` present and every `routeHostDigest` equals it;
705
+ `plane.exposure == synthetic` and `subject.state.provenance == seeded` (the
671
706
  synthetic-subject gate); stateSeries snapshots are digest-only (allowed-keys tripwire);
672
- all laneWindows share ONE plane provenance; and the CONCURRENCY-ON-PASS gate: a PASSED
673
- run MUST show ≥2 overlapping laneWindows AND a stateSeries delta whose timestamp is
707
+ all laneWindows share one plane provenance; and the concurrency-on-pass gate: a passed
708
+ run must show ≥2 overlapping laneWindows and a stateSeries delta whose timestamp is
674
709
  AT/AFTER an overlap interval start (otherwise it was not actually concurrent, or the
675
710
  world never changed under load). The per-participant no-engagement guard applies to both.
676
- Checkpoints / stateSeries persist digest-only by DEFAULT until the #108 PII/PHI
677
- detector lands.
678
-
679
- WHAT THE BUNDLE CAN / CANNOT CLAIM. SEQUENTIAL: each role's own behavior at full
680
- fidelity; the OBSERVED system outcome as an ordered DIGEST sequence; and the
681
- SEQUENCED-INTERACTION proof (role B entered a world already containing role A's mutation;
682
- the checkpoint after A strictly precedes B's turn). It CANNOT claim action-granular
683
- causation, concurrency/races (sequential-only), or exact-state determinism. CONCURRENT:
684
- each persona's own behavior at full fidelity; per-persona OUTCOME against the contended
685
- world ("M of N"); PROVEN CONCURRENCY (overlapping windows); and system-state evolution
686
- under load (the stateSeries) with best-effort temporal correlation. It CANNOT claim
711
+ Checkpoints / stateSeries persist digest-only by default until the
712
+ [PII/PHI detector](https://github.com/danielgwilson/humanish/issues/108) lands.
713
+
714
+ What the bundle can / cannot claim. Sequential: each role's own behavior at full
715
+ fidelity; the observed system outcome as an ordered digest sequence; and the
716
+ sequenced-interaction proof (role B entered a world already containing role A's mutation;
717
+ the checkpoint after A strictly precedes B's turn). It cannot claim action-granular
718
+ causation, concurrency/races (sequential-only), or exact-state determinism. Concurrent:
719
+ each persona's own behavior at full fidelity; per-persona outcome against the contended
720
+ world ("M of N"); proven concurrency (overlapping windows); and system-state evolution
721
+ under load (the stateSeries) with best-effort temporal correlation. It cannot claim
687
722
  strict causal attribution of a delta to an actor (concurrent ⇒ ambiguous), determinism
688
- of exact state, per-action granularity, or concurrency-SAFETY (races are OBSERVED, never
689
- PROVEN absent). HONESTY: the deterministic $0 gate proves the plumbing + the
723
+ of exact state, per-action granularity, or concurrency-safety (races are observed, never
724
+ proven absent). Limits: the deterministic $0 gate proves the plumbing + the
690
725
  attribution contract. A kept 2026-06-17 live receipt separately proves one
691
726
  bounded three-persona trial against a synthetic plane. Neither the deterministic
692
727
  gate nor that receipt proves scale, repeatability, or adopter-harness replacement.
693
728
 
694
- ### Concurrent plane classes: provisioned-getHost vs external-public (#164 phase 2, 0.20.0)
729
+ ### Concurrent plane classes: provisioned and external-public
695
730
 
696
- The CONCURRENT shape carries a PLANE-class discriminator, `sharedWorld.planeClass:
731
+ The concurrent shape carries a plane-class discriminator, `sharedWorld.planeClass:
697
732
  "provisioned-getHost" | "external-public"`. Absent == `provisioned-getHost` (every existing
698
- concurrent bundle byte-stable). It gates EVERY getHost-specific verify assertion, so a getHost
733
+ concurrent bundle byte-stable). It gates every getHost-specific verify assertion, so a getHost
699
734
  claim can never leak onto the external-public class (or vice versa).
700
735
 
701
736
  - `provisioned-getHost`: the historical plane described above: a `clone`/`local-tree` subject the
702
- harness serves + getHost-exposes in-sandbox. The harness MINTED the host, so it asserts the
737
+ harness serves + getHost-exposes in-sandbox. The harness minted the host, so it asserts the
703
738
  synthetic-seeded attestation (`plane.exposure: synthetic` + `subject.state.provenance == seeded`),
704
739
  the harness-minted host identity (`plane.hostDigest`, every `routeHostDigest == it`), and an
705
740
  authoritative in-sandbox checkpoint `stateSeries` with a delta-on-pass.
706
741
 
707
- - `external-public`: a real, operator-OWNED public deployment used DIRECTLY as the shared plane
708
- (`subject.source: app-url` + `topology: shared-world` + `policies.allowPublicTargets: true` +
709
- `execution.concurrency > 1`). NO getHost, NO clone, NO subject sandbox, NO seed. The subject
742
+ - `external-public`: a real, operator-owned public deployment used directly as the shared plane
743
+ (`subject.source: app-url` + `route: shared-world` + `policies.allowPublicTargets: true` +
744
+ `execution.concurrency > 1`). No getHost, no clone, no subject sandbox, no seed. The subject
710
745
  carries the ownership attestation `subject.publicTarget: { owner, authorized: true }` (the
711
- analog of `exposure: synthetic`: you cannot attest synthetic on a real site, but you MUST attest
746
+ analog of `exposure: synthetic`: you cannot attest synthetic on a real site, but you must attest
712
747
  you own/operate it; author-trust, unverifiable by the harness). Evidence deltas, all
713
748
  asserted-absent (never silently dropped):
714
- - `plane.publicOriginDigest`: sha256-16 of the one OBSERVED origin the participants converged on. Every
715
- `laneWindow.routeHostDigest` (computed from that participant's CDP-OBSERVED final-URL origin) equals it.
749
+ - `plane.publicOriginDigest`: sha256-16 of the one observed origin the participants converged on. Every
750
+ `laneWindow.routeHostDigest` (computed from that participant's CDP-observed final-URL origin) equals it.
716
751
  `plane.declaredOriginDigest` records the operator-declared origin as evidence only; a redirect
717
- can make the two differ. This proves inter-participant CONVERGENCE on one origin, NOT harness control of the plane
718
- (WEAKER than getHost's `hostDigest`, and disclosed).
719
- - `plane.exposure` is ABSENT (verify fails closed if `synthetic` appears, since that
752
+ can make the two differ. This proves inter-participant convergence on one origin, not harness control of the plane
753
+ (weaker than getHost's `hostDigest`, and disclosed).
754
+ - `plane.exposure` is absent (verify fails closed if `synthetic` appears, since that
720
755
  attestation would be false on a real site);
721
- `plane.hostDigest` is ABSENT (the harness minted no host).
756
+ `plane.hostDigest` is absent (the harness minted no host).
722
757
  - `subject.state.provenance == "external-public"` (never `seeded`, `unpinned` or `undeclared`).
723
- - `stateSeries` is OMITTED (Option A): no in-sandbox filesystem to authoritatively digest, so there
724
- is NO authoritative shared-state proof. Concurrency-on-pass is RELAXED to temporal co-occupancy
725
- ONLY (≥2 overlapping `laneWindows`); there is no state-delta requirement.
726
- - `lobbyConvergenceDigest` (optional, strong, cheap): sha256-16 of the shared `/lobby/CODE` PATH
727
- all participants' CDP URLs converged on; present only when every participant converged on ONE code. Digest-only
758
+ - `stateSeries` is omitted (Option A): no in-sandbox filesystem to authoritatively digest, so there
759
+ is no authoritative shared-state proof. Concurrency-on-pass is relaxed to temporal co-occupancy
760
+ only (≥2 overlapping `laneWindows`); there is no state-delta requirement.
761
+ - `lobbyConvergenceDigest` (optional, strong, cheap): sha256-16 of the shared `/lobby/CODE` path
762
+ all participants' CDP URLs converged on; present only when every participant converged on one code. Digest-only
728
763
  (the raw 6-char code and full URLs are runtime-only and never land).
729
- - `attributionLimits` MUST contain the concurrent family PLUS `external-public-plane`,
764
+ - `attributionLimits` must contain the concurrent family plus `external-public-plane`,
730
765
  `operator-attested-target-not-harness-controlled`, `no-synthetic-attestation`,
731
- `no-authoritative-shared-state-proof`, `concurrency-by-temporal-co-occupancy-only`; and MUST NOT
766
+ `no-authoritative-shared-state-proof`, `concurrency-by-temporal-co-occupancy-only`; and must not
732
767
  contain `sequential-only`, `no-concurrent-races`, or any `seeded`/`synthetic` limit.
733
768
 
734
- The getHost synthetic gate is deliberately NOT reachable from the app-url branch, and why: that gate
735
- exists because getHost is internet-reachable AND harness-owned (real data behind a harness-exposed
769
+ The getHost synthetic gate is deliberately not reachable from the app-url branch, and why: that gate
770
+ exists because getHost is internet-reachable and harness-owned (real data behind a harness-exposed
736
771
  URL is the hazard). A public site the harness neither provisioned nor exposed has neither property,
737
- so the gate's hazard does not exist there. Attribution stays `shared-world` (N participants, ONE plane); the
772
+ so the gate's hazard does not exist there. Attribution stays `shared-world` (N participants, one plane); the
738
773
  plane-control claim degrades from "harness-controlled" to "operator-attested, observed-only".
739
774
 
740
775
  ## Adapter
@@ -829,7 +864,7 @@ Legacy profiles retain medium defaults. `none_declared`/`none` access needs are
829
864
  omitted. `keyboard_first` allows pointer fallback; `keyboard_only` does not.
830
865
  These are prompt instructions, not evidence that a participant complied.
831
866
 
832
- `humanish lab inspect` adds `personas: [{ id, resolved, brief? }]`. A brief contains
867
+ `humanish study show` adds `personas: [{ id, resolved, brief? }]`. A brief contains
833
868
  `compilerVersion`, persona-section `text`, its pre-redaction `digest`, `redacted`,
834
869
  and an optional source-file `sourceDigest`. It excludes task criteria and runtime
835
870
  grants. This is authored context; whether a selected actor consumes it depends on
@@ -858,7 +893,7 @@ Actors execute or simulate the trial. Actor evidence is the provider-neutral
858
893
  computer-use cycles, scripted browser steps, and in-sandbox terminal-agent exec
859
894
  output all map onto one `ActorTrace`. Registered actors live in
860
895
  `src/actors/registry.ts` (`codex-app-server`, `openai-computer-use`,
861
- `local-agent`, `scripted-browser`, `codex-exec`). A lab that names
896
+ `local-agent`, `scripted-browser`, `codex-exec`). A study that names
862
897
  `pi-agent-core` or `claude-agent-sdk` fails to parse. There is no
863
898
  `humanish.actor.v1`; that name never shipped.
864
899
 
@@ -870,7 +905,7 @@ Core-owned fields:
870
905
  `terminal-exec`)
871
906
  - `lane` (`code` | `computer-use` | `scripted-browser` | `terminal`)
872
907
  - `persona` (`id`, `traitsApplied`, `promptDigest`)
873
- - `capabilities.keyPlacement` (`external` | `in-sandbox-command-scoped`): WHERE
908
+ - `capabilities.keyPlacement` (`external` | `in-sandbox-command-scoped`): where
874
909
  the actor's runtime key lives. It is registry metadata the engine enforces. The
875
910
  terminal agent declares `in-sandbox-command-scoped` (the agent-under-test runs
876
911
  inside the sandbox); every other actor is `external` (absent === external).
@@ -881,7 +916,7 @@ Core-owned fields:
881
916
  meaning the subject failed the script while the harness executed faithfully; and
882
917
  `budget_reached`: a computer-use session ended by a limit. The loop
883
918
  (`src/actors/computer-use/loop/ending.ts`) emits it when the time budget runs
884
- out after at least one material action, when `execution.caps.maxUsd` or the
919
+ out after at least one material action, when `caps.maxUsd` or the
885
920
  study's shared spend threshold is crossed, when the adapter reports a local
886
921
  admission limit, or when the provider hits its output or context token limit.
887
922
  Every `budget_reached` session has status `incomplete`
@@ -893,24 +928,24 @@ Core-owned fields:
893
928
  may carry `cacheWriteInput` (tokens billed at the provider's cache-write rate,
894
929
  OpenAI 5.6+) and `turns[]` (per provider-request usage, the recorded fact
895
930
  long-context tier pricing needs). Both are additive and absent on
896
- producers that do not report them (#334). Items may
931
+ producers that do not report them. Items may
897
932
  carry `at` (ISO-8601 recording stamp from the loop's clock) and, on
898
- click-like `ui_action` items, structured `coord` (`x`/`y`). Both are additive
899
- (#441): absent on older bundles and non-stamping producers, and absence means
933
+ click-like `ui_action` items, structured `coord` (`x`/`y`). Both are additive:
934
+ absent on older bundles and non-stamping producers, and absence means
900
935
  "timing/position unrecorded", never zero
901
- - optional `modelSettings` (`humanish.model-settings.v1`, #497): HOW the model was
936
+ - optional `modelSettings` (`humanish.model-settings.v1`): how the model was
902
937
  asked to run, alongside `ids.model` which says which model it was.
903
- `reasoningEffort` records the value the request ACTUALLY carried,
904
- including the provider default when a lab declared nothing. The resolved
938
+ `reasoningEffort` records the value the request actually carried,
939
+ including the provider default when a study declared nothing. The resolved
905
940
  value is what produced the trace, and recording "unset" would misdescribe
906
941
  the run. Absent when a provider declares no settings and on every
907
942
  pre-existing bundle; tolerated by verify. It exists because effort was
908
- unreachable from a lab, which made every run take the default silently. Effort
909
- is part of WHO the participant was rather than of how the instrument was tuned
943
+ unreachable from a study, which made every run take the default silently. Effort
944
+ is part of who the participant was rather than of how the instrument was tuned
910
945
  (docs/principles/actor-fidelity.md), so a trace without it is a result missing
911
946
  half its sample description
912
947
  - optional `modelSettings.maxOutputTokens`: the declared positive integer
913
- `actors[0].maxOutputTokens`, passed to every first-party OpenAI CUA response
948
+ `actor.maxOutputTokens`, passed to every first-party OpenAI CUA response
914
949
  request, including reasoning output. Absent when undeclared. The first request
915
950
  of a session and closing reports use `min(maxOutputTokens, 1024)`, or 1024 when
916
951
  undeclared; the first request also carries the opening screenshot as an
@@ -923,17 +958,37 @@ Core-owned fields:
923
958
  “provider reply cut off by the output limit; asking again” notice. A second
924
959
  cut-off reply to the same request ends the session with
925
960
  `stopCause: provider_output_limit`.
926
- - optional `affordanceUse` (`humanish.affordance-use.v1`): which KIND of route this
961
+ - optional `conversation`: how the first-party OpenAI computer-use provider
962
+ carried the conversation between requests. `mode` is `threaded` (the server
963
+ kept it through `previous_response_id`) or `explicit_context` (the server kept
964
+ none). `explicitReason` says why: `configured` (`zeroDataRetention`) or
965
+ `zdr_rejection` (the organization rejected server-side state; `switchedAt` and
966
+ `switchedAtRequest` say when). In `explicit_context` each request carries the
967
+ conversation from the client, with `store: false` and the reasoning returned
968
+ in encrypted form. It carries the opening message, every exchange of a reply
969
+ with the outputs that answered it, and the latest reply. Past an estimated
970
+ 64,000 input tokens the opening screenshot is dropped first, then the oldest
971
+ exchanges become lines of a note, sent as an assistant message, that keeps
972
+ their reasoning summaries, messages and actions as text. The two newest
973
+ exchanges are always carried whole, even when they alone pass the budget. `summarizedTurns` counts the earlier exchanges the latest
974
+ `explicit_context` request no longer carried whole: lines of the note, or
975
+ only counted once the note reached its cap.
976
+ `requests[]` has one entry per participant request: its `mode` and, in
977
+ `explicit_context`, `carriedExchanges`, `carriedScreenshots` and
978
+ `estimatedInputTokens`. Billed input per request is in `tokenUsage.turns`. A
979
+ participant that ran in `explicit_context` gets a run warning. Absent on other
980
+ providers and on earlier bundles
981
+ - optional `affordanceUse` (`humanish.affordance-use.v1`): which kind of route this
927
982
  actor took (see Affordance Use below)
928
983
  - optional `estimatedCost` (`humanish.actor-estimated-cost.v1`): a token-derived
929
- cost ESTIMATE for this participant (see Run Cost Summary And Estimated Actor Cost).
930
- It is deliberately a DIFFERENT field from `tokenUsage.costUsd`: a bare
931
- `costUsd` is RESERVED for a real provider-returned charge, while
932
- `estimatedCost.estimatedCostUsd` is a rate-table multiply, named honestly as
933
- an estimate so a reader can never confuse the two (invariant 6). Absent on
984
+ cost estimate for this participant (see Run Cost Summary And Estimated Actor Cost).
985
+ It is deliberately a different field from `tokenUsage.costUsd`: a bare
986
+ `costUsd` is reserved for a real provider-returned charge, while
987
+ `estimatedCost.estimatedCostUsd` is a rate-table multiply, named as an
988
+ estimate so a reader can never confuse the two. Absent on
934
989
  the Codex app-server and scripted routes and on every pre-existing bundle; the
935
990
  terminal route records a `null` estimate. A `null`
936
- `estimatedCostUsd` is DECLARED ABSENT (unknown rate / no usage), never 0.
991
+ `estimatedCostUsd` is declared absent (unknown rate / no usage), never 0.
937
992
 
938
993
  Unexpected actor-loop diagnostics live inside `items[]` as
939
994
  `kind: notice`, `status: error` rows. They are public-safe evidence, not crash
@@ -976,12 +1031,12 @@ items: []
976
1031
 
977
1032
  Reserved: `humanish.substrate.v1` is named here for layering intent but has
978
1033
  never shipped; no code emits or validates it. Substrate truth today lives
979
- inside run bundles (per-stream transport and status) and lab execution config
1034
+ inside run bundles (per-stream transport and status) and study execution config
980
1035
  (`execution.target: local | e2b-desktop | e2b-terminal`). Do not emit this schema.
981
1036
 
982
1037
  ## Serve Result And Reserved Control-Plane Namespace
983
1038
 
984
- `humanish serve` reports `humanish.serve-result.v1`. The exported `ServeResult`
1039
+ `humanish observe --all` reports `humanish.serve-result.v1`. The exported `ServeResult`
985
1040
  type and `SERVE_SCHEMA` constant in `src/observer/serve.ts` are authoritative:
986
1041
  mode (`loopback | exposed | share-safe-open`), the loopback host/port,
987
1042
  `publicUrl`, the `tunnel` provider/url, an `oauth` echo (`provider`,
@@ -1022,10 +1077,10 @@ discipline** that distinguishes three states and never conflates them:
1022
1077
  - `usd: null` is **not measured**: no spend signal exists for the category on
1023
1078
  this run. `null` is written explicitly (never `undefined`-omitted, never guessed
1024
1079
  to `0`). A line with `null` says "this category exists but we did not measure
1025
- it"; the no-spend proof reports it as unmeasured and does NOT claim it is zero.
1080
+ it"; the no-spend proof reports it as unmeasured and does not claim it is zero.
1026
1081
  - an **absent** line is **not applicable** (n/a) to the participant or run.
1027
1082
 
1028
- `knownTotalUsd` sums ONLY the non-null lines (a `null` line contributes nothing
1083
+ `knownTotalUsd` sums only the non-null lines (a `null` line contributes nothing
1029
1084
  and is never coerced to `0`); `fullyMeasured` is true only when no line is null.
1030
1085
  Core meters only the `provider` line, populated from the actor trace's
1031
1086
  `tokenUsage.costUsd` when present (else `null`); `product`/`media`/`payment`
@@ -1034,7 +1089,7 @@ cost-probe seam. The terminal route records the model as `codex` and does not pi
1034
1089
  model, so a measured token count stays `usd: null` with `source: unpriced-token-usage`, and
1035
1090
  `knownTotalUsd: 0` with `fullyMeasured: false` means no line of this ledger was priced. E2B
1036
1091
  time for the terminal sandbox is not a line of this ledger, whose lines are checked against
1037
- `scenario.caps.maxUsd`. The run bundle's `cost` summary prices it as a `desktop-minutes`
1092
+ `caps.maxUsd`. The run bundle's `cost` summary prices it as a `desktop-minutes`
1038
1093
  line (see Run Cost Summary And Estimated Actor Cost below).
1039
1094
 
1040
1095
  ```yaml
@@ -1049,10 +1104,10 @@ knownTotalUsd: 0
1049
1104
  fullyMeasured: false
1050
1105
  ```
1051
1106
 
1052
- The no-spend proof (`humanish.terminal-no-spend-proof.v1`) is DERIVED from the
1107
+ The no-spend proof (`humanish.terminal-no-spend-proof.v1`) is derived from the
1053
1108
  ledger. It vouches only for what it measured: `knownZeroLines` (proven zero),
1054
1109
  `knownNonZeroLines` (break `satisfied`), and `unmeasuredLines` (the `null` lines
1055
- it explicitly CANNOT vouch for). `satisfied` is true only when every KNOWN line
1110
+ it explicitly cannot vouch for). `satisfied` is true only when every known line
1056
1111
  is within `maxUsd` (for a no-spend run, `maxUsd: 0` ⇒ every known line is `0`).
1057
1112
  Unmeasured lines never break it, so a ledger whose lines are all `null` is
1058
1113
  `satisfied` with nothing proven. The `statement` says which case applies: it
@@ -1072,9 +1127,9 @@ knownTotalUsd: 0
1072
1127
  statement: "No-spend proof not established for maxUsd=0: no spend line was measured. …"
1073
1128
  ```
1074
1129
 
1075
- **Full caps enforcement (fail-closed, not advisory).** `scenario.caps.maxUsd`
1076
- is enforced against the ledger: if the observed KNOWN spend exceeds `maxUsd`, the
1077
- run fails closed (`HUMANISH_TERMINAL_LAB_CAPS_EXCEEDED`); `maxJobs` likewise when
1130
+ **Full caps enforcement (fail-closed, not advisory).** `caps.maxUsd`
1131
+ is enforced against the ledger: if the observed known spend exceeds `maxUsd`, the
1132
+ run fails closed (`HUMANISH_TERMINAL_CAPS_EXCEEDED`); `maxJobs` likewise when
1078
1133
  a known job count is present; `maxMinutes` is the wall-clock kill (unchanged).
1079
1134
  Unknowns (`null`) never trip a cap (we cannot claim a violation we did not
1080
1135
  measure) and never grant a green pass (they surface as unmeasured). `verifyRun`
@@ -1084,10 +1139,10 @@ proof claims zero on a `null` line, or when known spend exceeds the declared cap
1084
1139
  ## Run Status (identity + liveness index)
1085
1140
 
1086
1141
  `humanish.run-status.v1` is `status.json`, written inside each run directory by
1087
- every backend at run start, refreshed on a fixed cadence while the run is
1142
+ every route at run start, refreshed on a fixed cadence while the run is
1088
1143
  alive, and finalized when it ends: `{ schema, runId, state: running |
1089
- finished | interrupted, mode, lab?, pid, startedAt, updatedAt, completedAt?,
1090
- signal?, outcome? }`.
1144
+ finished | interrupted, mode, study?, lab?, pid, startedAt, updatedAt, completedAt?,
1145
+ signal?, outcome? }`. `lab` repeats `study` until 0.109.
1091
1146
  `outcome` carries the bundle's `verdict`, `participants` and `estimatedCostUsd`
1092
1147
  when the run finishes, then the result's `ok` and `execution: { succeeded,
1093
1148
  failures: [{ kind, message }], warnings? }` once the Observer has rendered. The
@@ -1116,26 +1171,26 @@ analysis. It then kills the E2B sandboxes the run's `sandbox-receipts.ndjson`
1116
1171
  names, writes `reclaim-receipt.json` as `humanish reclaim` does, and exits
1117
1172
  128+n. The bundle is not finished: run.json keeps its last live flush, and no
1118
1173
  `outcome` is written. A `running` record that stopped refreshing still reads as
1119
- interrupted once stale. A library caller of `runLab` gets no signal handling. A
1174
+ interrupted once stale. A library caller of `runStudy` gets no signal handling. A
1120
1175
  humanish older than this field rejects the state and falls back to the bundle's
1121
1176
  own liveness.
1122
1177
 
1123
- It answers two questions the filesystem could not answer before: **which lab**
1178
+ It answers two questions the filesystem could not answer before: **which study**
1124
1179
  a run belongs to, and **whether it is still alive**, including for runs an
1125
- agent launched (`lab run --json`) or that were detached, which previously wrote
1180
+ agent launched (`run --json`) or that were detached, which previously wrote
1126
1181
  nothing at all until they completed.
1127
1182
 
1128
- It is a DERIVED INDEX, not evidence. `run.json` remains the evidence-of-record;
1183
+ It is a derived index, not evidence. `run.json` remains the evidence-of-record;
1129
1184
  `verify` never gates on `status.json`, nothing in it is a claim about what a
1130
1185
  participant did, and when the two disagree the bundle wins and the record is
1131
1186
  rebuildable from it. A `running` record whose `updatedAt` is older than three
1132
- touch intervals is INTERRUPTED, not alive. A dropped connection or a killed
1187
+ touch intervals is interrupted, not alive. A dropped connection or a killed
1133
1188
  terminal leaves exactly that shape, so the record reads as interrupted
1134
1189
  (`classifyRunStatus` is the one shared definition). Fields are
1135
1190
  public-safe by construction: no hostname and no user paths, because a run
1136
1191
  directory may be shared.
1137
1192
 
1138
- ## Run Index, Run Detail, And The Terminal Surface (#455)
1193
+ ## Run Index, Run Detail, And The Terminal Surface
1139
1194
 
1140
1195
  Three derived projections that exist so a surface can list, classify and watch
1141
1196
  runs without opening evidence for all of them. None is authoritative: `run.json`
@@ -1145,17 +1200,17 @@ them is a claim about what a participant did.
1145
1200
  `humanish.run-index.v1` is the listing projection. One entry per run, read
1146
1201
  cheapest-source-first: the `status.json` record, else the bundle, else the run
1147
1202
  directory alone. `{ runId, derivedFrom: status | bundle | directory, liveness,
1148
- mode?, pid?, lab?, startedAt?, updatedAt?, completedAt?, verdict?,
1203
+ mode?, pid?, study?, startedAt?, updatedAt?, completedAt?, verdict?,
1149
1204
  participants?, estimatedCostUsd?, durationMs? }`. The point is cost: walking
1150
1205
  every run tree and parsing every bundle measured 167ms on a 25-run project,
1151
1206
  against 16ms cold and 2.8ms warm here, which is what makes a surface that
1152
1207
  refreshes on a cadence affordable. `derivedFrom` is reported so a surprising row
1153
1208
  can be traced to the file it came from. A run with receipts and no outcome is
1154
1209
  `interrupted` (the shape a dropped connection leaves), and so is an
1155
- IN-PROGRESS bundle reached without a status record: there is no freshness to
1210
+ in-progress bundle reached without a status record: there is no freshness to
1156
1211
  judge, and nothing in it says the run finished.
1157
1212
 
1158
- `humanish.run-detail.v1` is the watching projection, for ONE run. Who is in it
1213
+ `humanish.run-detail.v1` is the watching projection, for one run. Who is in it
1159
1214
  and what they are thinking: `{ runId, participants: [{ id, label, personaId?,
1160
1215
  traits, status?, completionReason?, thought?, turns?, actions?, thoughts?,
1161
1216
  estimatedCostUsd? }], observerPath?, automaticAnalysis? }`. It reads the actor trace from
@@ -1163,7 +1218,7 @@ estimatedCostUsd? }], observerPath?, automaticAnalysis? }`. It reads the actor t
1163
1218
  finished, preferring the live one, so a single screen renders a run the whole
1164
1219
  way through. A reasoning item still being written is skipped rather than quoted
1165
1220
  half-finished, and the text is carried verbatim: a surface may wrap it, and
1166
- nothing paraphrases it. Unlike the index this DOES open the bundle, which is affordable
1221
+ nothing paraphrases it. Unlike the index this does open the bundle, which is affordable
1167
1222
  only because it is asked for the one run being watched.
1168
1223
 
1169
1224
  `humanish.tui-result.v1` is what `humanish tui` emits when it refuses:
@@ -1172,23 +1227,23 @@ only because it is asked for the one run being watched.
1172
1227
  `HUMANISH_TUI_BUNDLE_MISSING`. `HUMANISH_TUI_AGENT_SESSION` is the one a TTY
1173
1228
  check could not catch: `codex exec` allocates a PTY for the commands it runs, so
1174
1229
  both streams are terminals and the surface used to open. A study watched an
1175
- agent move through the labs list and start a run it did not mean to start
1176
- (humanish/labs/handed-a-human-surface.yaml). The refusal names the environment variable
1230
+ agent move through the studies list and start a run it did not mean to start
1231
+ (humanish/studies/handed-a-human-surface.yaml). The refusal names the environment variable
1177
1232
  that identified the runner, so the reader can check the claim, and `--force` is
1178
1233
  the escape for a person who really is at that keyboard. Every other
1179
1234
  command is built for an agent to drive; this one takes the screen and waits for
1180
1235
  a person, so a non-interactive stdin or stdout fails closed naming the commands
1181
- that DO answer the question rather than rendering escape codes into a pipe.
1236
+ that do answer the question rather than rendering escape codes into a pipe.
1182
1237
 
1183
1238
  ## Run Cost Summary And Estimated Actor Cost
1184
1239
 
1185
- The computer-use (CUA) route surfaces an ADVISORY, additive cost ESTIMATE. It is
1240
+ The computer-use (CUA) route surfaces an advisory, additive cost estimate. It is
1186
1241
  never authoritative: every dollar figure is a rate-table multiply, labeled
1187
- "estimated (rates as of `<date>`)", and is NEVER presented as a provider charge
1188
- (invariant 6). Three new `.v1` schema tags ship, all additive and optional so
1242
+ "estimated (rates as of `<date>`)", and is never presented as a provider charge.
1243
+ Three new `.v1` schema tags ship, all additive and optional so
1189
1244
  `humanish.run-bundle.v1` stays v1 and every pre-existing bundle is byte-stable:
1190
1245
 
1191
- - `humanish.pricing.v1`: the OPERATOR-EDITABLE rate table in `src/run/pricing.ts`:
1246
+ - `humanish.pricing.v1`: the operator-editable rate table in `src/run/pricing.ts`:
1192
1247
  dated per-model input/output USD-per-token rates and E2B CPU/GiB-second
1193
1248
  rates, each with a public pricing-page `source` and an `asOf`
1194
1249
  date. A prominent banner says these are estimates to update when providers
@@ -1198,9 +1253,9 @@ never authoritative: every dollar figure is a rate-table multiply, labeled
1198
1253
  request; priced exactly only from the trace's per-request `turns` ledger;
1199
1254
  totals alone never re-tier, which errs toward under-estimating). Some
1200
1255
  entries are `placeholder: true` stand-ins (the legacy desktop helper's
1201
- 8-vCPU/8-GiB planning assumption); an operator MUST confirm them before trusting
1256
+ 8-vCPU/8-GiB planning assumption); an operator must confirm them before trusting
1202
1257
  the magnitude; the flag propagates into every estimate so a stand-in is never
1203
- mistaken for a live rate. An UNKNOWN model/desktop rate is DECLARED ABSENT
1258
+ mistaken for a live rate. An unknown model/desktop rate is declared absent
1204
1259
  (`estimatedCostUsd: null` + a `reason`), never guessed.
1205
1260
  - `humanish.actor-estimated-cost.v1` (`ActorTrace.estimatedCost`): one participant's
1206
1261
  token-derived model cost, with `estimatedCostUsd` (or `null` + `reason`
@@ -1209,7 +1264,7 @@ never authoritative: every dollar figure is a rate-table multiply, labeled
1209
1264
  arrived as turn totals, such as Codex `turn.completed`, priced at the base tier because no single
1210
1265
  request's size is known; the run summary's `model-tokens` line carries the same `basis`.
1211
1266
  - `humanish.run-cost-summary.v1` (`RunBundle.cost`): the sum of every participant's
1212
- `model-tokens` lines PLUS `desktop-minutes` lines. New independent CUA runs
1267
+ `model-tokens` lines plus `desktop-minutes` lines. New independent CUA runs
1213
1268
  price each owned desktop separately; concurrent shared-world runs add one line per participant
1214
1269
  and a `laneId: subject` desktop line for a provisioned plane; older single aggregate
1215
1270
  lines remain readable.
@@ -1222,23 +1277,23 @@ never authoritative: every dollar figure is a rate-table multiply, labeled
1222
1277
  Live terminal-product runs emit the same summary: one `desktop-minutes` line for
1223
1278
  the E2B shell sandbox (its acquired-to-cleanup span and `e2b.getInfo` size) and
1224
1279
  one `model-tokens` line for the Codex participant, priced from the model the route passed
1225
- to Codex (`gpt-5.6-sol` unless the lab declares one) with `basis:
1280
+ to Codex (`gpt-5.6-sol` unless the study declares one) with `basis:
1226
1281
  aggregated_turns_base_rate`, or `null` with `no_token_usage`. Older terminal bundles record
1227
1282
  `no_rate_for_model` (model `codex`). The terminal trace records the same `estimatedCost`. The sandbox line is not part of the
1228
- terminal cost ledger, whose lines are checked against `scenario.caps.maxUsd`.
1283
+ terminal cost ledger, whose lines are checked against `caps.maxUsd`.
1229
1284
 
1230
- The summary follows the SAME null discipline as the terminal cost ledger above.
1231
- `estimatedTotalUsd` sums ONLY the non-null `breakdown` lines and is `null` iff
1232
- EVERY line is null (never coerced to `0`); a present-but-unpriceable line stays
1285
+ The summary follows the same null discipline as the terminal cost ledger above.
1286
+ `estimatedTotalUsd` sums only the non-null `breakdown` lines and is `null` iff
1287
+ every line is null (never coerced to `0`); a present-but-unpriceable line stays
1233
1288
  in `breakdown` with `estimatedCostUsd: null` + a `reason` (it records that we
1234
1289
  tried and could not price it) and contributes nothing. `fullyEstimated` is
1235
1290
  `false` when any applicable line is null (the total is then a lower bound). A spend-free
1236
1291
  live run has no lines and records `estimatedTotalUsd: 0` with `ratesAsOf: null`; verify
1237
1292
  asks for a rates date only when a line is priced.
1238
1293
  `placeholder` is true when any contributing rate is a stand-in; `ratesAsOf` is
1239
- the MIN (oldest) `asOf` across contributing rates. An aggregate is only as fresh
1240
- as its stalest input, so MAX would overclaim freshness (each `breakdown` line
1241
- keeps its own true `asOf`). `desktopMinutes` is a HOST-SIDE
1294
+ the min (oldest) `asOf` across contributing rates. An aggregate is only as fresh
1295
+ as its stalest input, so max would overclaim freshness (each `breakdown` line
1296
+ keeps its own true `asOf`). `desktopMinutes` is a host-side
1242
1297
  acquired-handle→cleanup span, excluding allocation/startup before handle acquisition.
1243
1298
  It approximates E2B's server-side billed lifetime. Plan fees, credits, and negotiated
1244
1299
  prices are excluded; unknown remaining lifetime makes `fullyEstimated` false.
@@ -1272,17 +1327,17 @@ desktopMinutes: 1
1272
1327
  note: "Estimated 11.60167 USD total…"
1273
1328
  ```
1274
1329
 
1275
- `verifyRun` asserts LABELING/provenance, never MAGNITUDE. Absence PASSES
1330
+ `verifyRun` asserts labeling and provenance, never magnitude. Absence passes
1276
1331
  (fail-open on display): a bundle with no cost, a null estimate that carries a
1277
- `reason` and a null `ratesAsOf`, or a participant without `estimatedCost` verifies fine. A CLAIMED number FAILS closed when it lacks its
1332
+ `reason` and a null `ratesAsOf`, or a participant without `estimatedCost` verifies fine. A claimed number fails closed when it lacks its
1278
1333
  `ratesAsOf` date or `source`, or when `estimatedTotalUsd` does not equal the
1279
1334
  rounded sum of its non-null lines (a null line coerced to 0 is a mechanism
1280
1335
  mismatch). A correctly-labeled huge estimate still passes. Cost is neither a
1281
1336
  secret nor a share-blocker: its magnitude never affects `shareSafety`. A labeling
1282
1337
  failure fails verify, which blocks sharing like any other failed check.
1283
1338
 
1284
- **Computer-use spend thresholds.** `execution.caps.maxUsd` applies independently
1285
- to each participant. `execution.caps.maxTotalUsd` shares an estimated model-spend ledger
1339
+ **Computer-use spend thresholds.** `caps.maxUsd` applies independently
1340
+ to each participant. `caps.maxTotalUsd` shares an estimated model-spend ledger
1286
1341
  across the study's participants. Either, both, or neither may be declared. Without a
1287
1342
  shared threshold, N participants each have their own `maxUsd`; N × `maxUsd` is the sum
1288
1343
  of those thresholds, not a guaranteed total spending ceiling.
@@ -1326,10 +1381,10 @@ threshold is not proof of task completion.
1326
1381
  An absent threshold is uncapped. A zero threshold can still permit a paid model
1327
1382
  request before reported usage trips it; `maxUsd: 0` is not a no-provider-call
1328
1383
  mode. Use the keyless `humanish run first-run` preview or an explicit
1329
- `humanish lab run <lab> --dry-run` for a path without provider calls. A declared
1384
+ `humanish run <study> --dry-run` for a path without provider calls. A declared
1330
1385
  threshold on a model `src/run/pricing.ts` cannot price is refused at preflight
1331
- (`HUMANISH_CUA_LAB_UNPRICED_CAP`, or `HUMANISH_CONCURRENT_SHARED_WORLD_LAB_UNPRICED_CAP` on a
1332
- shared-world lab) before sandbox allocation. This rate-availability
1386
+ (`HUMANISH_COMPUTER_USE_UNPRICED_CAP`, or `HUMANISH_SHARED_WORLD_UNPRICED_CAP` on a
1387
+ shared-world study) before sandbox allocation. This rate-availability
1333
1388
  check is separate from the post-response spend check.
1334
1389
 
1335
1390
  Library callers can make a capped session stricter by composing it from the loop and the default
@@ -1370,19 +1425,19 @@ original interruption cause first, with any missing usage still recorded as
1370
1425
  unknown.
1371
1426
 
1372
1427
  These computer-use rules do not replace the terminal route's separate
1373
- `scenario.caps` cost-ledger and product-spend rules described above.
1428
+ `caps` cost-ledger and product-spend rules described above.
1374
1429
 
1375
1430
  ## Affordance Use
1376
1431
 
1377
- `humanish.affordance-use.v1` (additive `ActorTrace.affordanceUse`) records WHICH
1378
- KIND of route an actor took, per dispatched action, as counts by class:
1432
+ `humanish.affordance-use.v1` (additive `ActorTrace.affordanceUse`) records which
1433
+ kind of route an actor took, per dispatched action, as counts by class:
1379
1434
 
1380
1435
  | Class | What it covers |
1381
1436
  | ------------------ | ------------------------------------------------------------------------------------------------------------- |
1382
1437
  | `pointer` | click, double-click, drag, scroll: interaction with what is rendered |
1383
1438
  | `keyboard` | typed text and key presses into the page |
1384
1439
  | `speech` | spoken input from a `speak` action |
1385
- | `url-navigation` | typing a URL (the `nav` subset); a HUMAN affordance, see below |
1440
+ | `url-navigation` | typing a URL (the `nav` subset); a human affordance, see below |
1386
1441
  | `script-execution` | a `javascript:` or `data:` URL; not a human affordance |
1387
1442
  | `devtools` | developer tooling opened by keyboard chord |
1388
1443
  | `browser-internal` | `chrome://`, `about:`, `view-source:`, `devtools:`, `file:`, `edge:`, `brave:` (the browser, not the product) |
@@ -1393,45 +1448,49 @@ rate; `shortcutTotal` rolls up `script-execution` + `devtools` +
1393
1448
  `browser-internal`, and a value of `0` is a meaningful result rather than an
1394
1449
  absence.
1395
1450
 
1396
- Direct URL navigation is deliberately its OWN class and is grouped with the
1451
+ Direct URL navigation is deliberately its own class and is grouped with the
1397
1452
  naturalistic classes, not with script execution: `load(url)` appears in 99.4% of
1398
1453
  2,337 real human web demonstrations, so address-bar use is ordinary human
1399
1454
  behavior and classifying it as a shortcut would make an ordinary human participant look
1400
1455
  unfaithful. See [`docs/principles/actor-fidelity.md`](../principles/actor-fidelity.md)
1401
1456
  for the evidence and the scoping of what a fidelity claim can mean.
1402
1457
 
1403
- The record carries a CLASS and at most a scheme-shaped signal (`javascript:`,
1458
+ The record carries a class and at most a scheme-shaped signal (`javascript:`,
1404
1459
  `https:`, a devtools chord), never the typed text, which can be a password, a
1405
1460
  session token, or an identifying URL path. Classification runs at dispatch
1406
1461
  because the text exists only there: the trace's own action label deliberately
1407
1462
  renders `type [N chars]`.
1408
1463
 
1409
- The harness states NO verdict about a class. Whether an affordance invalidates a
1464
+ The harness states no verdict about a class. Whether an affordance invalidates a
1410
1465
  study depends on the population that study declares, which is product semantics
1411
1466
  and belongs to the adopter's scorer. That scorer already receives the full trace,
1412
1467
  so `affordanceUse` needs no extra wiring to reach it. Present on computer-use participants
1413
1468
  that dispatched at least one action; absent elsewhere and on every pre-existing
1414
1469
  bundle, and its absence is tolerated by verify.
1415
1470
 
1416
- ## Library options (`RunLabOptions`)
1417
-
1418
- `runLab(config, options)` checks its options against the lab's route before anything runs. A
1419
- refusal comes back in the route's own result envelope, with no run directory, receipt or sandbox.
1420
-
1421
- | Option | What it does | Routes |
1422
- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1423
- | `env` | Keys and subject env for the run. Defaults to `process.env` | all |
1424
- | `scorer` | An `AdapterScorerModule` (`score`, `deriveFeedback`, and `deriveArtifacts` on browser routes) over the finished evidence. A scorer written for one context passes through `browserScorer` or `terminalScorer` | computer use, shared world, terminal |
1425
- | `createProvider(ctx)` | The participant's brain. `ctx` is `ProviderContext`: `config`, `participant { id, index, count }` and `executor` | computer use: E2B, local VM or in process |
1426
- | `inProcess.executor({ config, appUrl })` | Drives an `app-url` or `local-app` subject in process, one participant. Requires `createProvider` | computer use |
1427
- | `prepareDesktop(desktop, target)` | Runs on an E2B sandbox before provisioning. `target` is `{ kind: "subject" }` or `{ kind: "participant", participant }` | computer use on E2B (each participant); shared world (provisioned: the subject, then each participant; external-public: each participant); scripted clone (the subject) |
1428
- | `onEvent(event)` | Observes `LabEvent`s, below | all |
1429
- | `onStream(event)` | Awaited. `ready` after a participant's live stream starts; its `url` carries an auth key and must never be persisted. `ended` after the participant's sandbox is gone. A rejected `ready` becomes a run warning; an `ended` rejection is swallowed | computer use and shared world on E2B |
1430
- | `analysisSignal` | Cancels post-run analysis only | routes with post-run analysis |
1431
- | `onObserverReady(observer)` | Awaited before any participant desktop exists | live computer use; live shared world, where the provisioned subject sandbox is already billed |
1432
- | `rerun.participantIds` | Participants to rerun from `rerun.sourceRunId` | computer use |
1433
-
1434
- `LabEvent` has four types:
1471
+ ## Library options
1472
+
1473
+ `runStudy(config, options)` checks its `RunStudyOptions` against the study's route before anything
1474
+ runs. A refusal comes back in the route's own result envelope, with no run directory, receipt or
1475
+ sandbox. The 0.107 names (`runLab`, `RunLabOptions`, `parseLabConfig`, `LAB_CONFIG_SCHEMA`,
1476
+ `LabConfig`, the `Lab*` result and event types, the nine `Cua*` loop types, `CuaAdmissionLimitError`
1477
+ and `BrowserLabScoringContext`) are deprecated aliases with the same value or class until 0.109
1478
+ (`src/library-aliases.ts`).
1479
+
1480
+ | Option | What it does | Routes |
1481
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1482
+ | `env` | Keys and subject env for the run. Defaults to `process.env` | all |
1483
+ | `scorer` | An `AdapterScorerModule` (`score`, `deriveFeedback`, and `deriveArtifacts` on browser routes) over the finished evidence. A scorer written for one context passes through `browserScorer` or `terminalScorer` | computer use, shared world, terminal |
1484
+ | `createProvider(ctx)` | The participant's brain. `ctx` is `ProviderContext`: `config`, `participant { id, index, count }` and `executor` | computer use: E2B, local VM or in process |
1485
+ | `inProcess.executor({ config, appUrl })` | Drives an `app-url` or `local-app` subject in process, one participant. Requires `createProvider` | computer use |
1486
+ | `prepareDesktop(desktop, target)` | Runs on an E2B sandbox before provisioning. `target` is `{ kind: "subject" }` or `{ kind: "participant", participant }` | computer use on E2B (each participant); shared world (provisioned: the subject, then each participant; external-public: each participant); scripted clone (the subject) |
1487
+ | `onEvent(event)` | Observes `StudyEvent`s, below | all |
1488
+ | `onStream(event)` | Awaited. `ready` after a participant's live stream starts; its `url` carries an auth key and must never be persisted. `ended` after the participant's sandbox is gone. A rejected `ready` becomes a run warning; an `ended` rejection is swallowed. Each event carries `participantId`, `recordId` (the id of this participant's entry in `run.json` `simulations[]`, such as `sim-001`, which `streams[].simId` also holds) and `streamId`; `ready` adds `sandboxId` and `url`. `simId` is the older name for `recordId`: its first read prints a `DeprecationWarning` with code `HUMANISH_STREAM_EVENT_FIELD_DEPRECATED`, and the next minor removes it. | computer use and shared world on E2B |
1489
+ | `analysisSignal` | Cancels post-run analysis only | routes with post-run analysis |
1490
+ | `onObserverReady(observer)` | Awaited before any participant desktop exists | live computer use; live shared world, where the provisioned subject sandbox is already billed |
1491
+ | `rerun.participantIds` | Participants to rerun from `rerun.sourceRunId` | computer use |
1492
+
1493
+ `StudyEvent` has four types:
1435
1494
 
1436
1495
  - `plan`: the participants (`id`, `persona`, `device`, `instructionDigest`) before any sandbox
1437
1496
  or provider call. Computer use only; the other routes gain it later.
@@ -1446,16 +1505,16 @@ throw or a rejected promise becomes a redacted run warning.
1446
1505
 
1447
1506
  Refusals:
1448
1507
 
1449
- - `HUMANISH_LAB_OPTION_UNSUPPORTED`: the route cannot honor the option, for example `scorer` on
1450
- a preview or scripted lab, `createProvider` off computer use, or `prepareDesktop` where no E2B
1508
+ - `HUMANISH_STUDY_OPTION_UNSUPPORTED`: the route cannot honor the option, for example `scorer` on
1509
+ a preview or scripted study, `createProvider` off computer use, or `prepareDesktop` where no E2B
1451
1510
  desktop exists. The message names the option and the route. The same code refuses a field
1452
- `RunLabOptions` no longer has (below); that message names the field and where its job went.
1511
+ `RunStudyOptions` no longer has (below); that message names the field and where its job went.
1453
1512
 
1454
1513
  `rerun.laneIds`, the older name of `rerun.participantIds`, was removed in 0.107.0. A caller that
1455
- still passes it is refused with `HUMANISH_LAB_OPTION_UNSUPPORTED`; use `rerun.participantIds`.
1514
+ still passes it is refused with `HUMANISH_STUDY_OPTION_UNSUPPORTED`; use `rerun.participantIds`.
1456
1515
 
1457
1516
  The route hook bags (`cuaHooks`, `scriptedHooks`, `terminalHooks`, `sharedWorldHooks`,
1458
- `automaticAnalysis`) were removed from `RunLabOptions`, with the hook bag types and the four route
1517
+ `automaticAnalysis`) were removed from `RunStudyOptions`, with the hook bag types and the four route
1459
1518
  runners that took them (`runCuaActorLab`, `runScriptedBrowserLab`, `runTerminalProductLab`,
1460
1519
  `runConcurrentSharedWorld`). `lab` and `scorerProvenance` are set only by the humanish CLI. A
1461
1520
  JavaScript caller that passes any of these fields is refused before anything runs. Where each
@@ -1481,25 +1540,25 @@ route-specific verdict rules in the next section.
1481
1540
  0.107.0 also removed these exports from `humanish`. A removed function no longer warns; an import
1482
1541
  of it fails.
1483
1542
 
1484
- | Removed export | Use |
1485
- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1486
- | `runDryRun`, `RunOptions`, `RunResult` | `runLab(config, options)` on a `this-repo` lab; its result is `LabResult<"preview">` |
1487
- | `runCuaActorSession` | `runComputerUseLoop` with `createOpenAiResponsesProvider({ singleDispatch: true })`, `redaction: defaultRedactionHooks` and `now: Date.now`, which `runCuaActorSession` defaulted (the composition above). A caller that passed `desktop` and `executorOptions` writes its own `CuaExecutor`: the E2B desktop executor is not exported |
1488
- | `CuaActorLabResult`, `ScriptedBrowserLabResult`, `TerminalProductLabResult`, `ConcurrentSharedWorldLabResult` | `LabResult<"computer-use">`, `LabResult<"scripted">`, `LabResult<"terminal">`, `LabResult<"shared-world">` |
1489
- | `SubjectPhaseEvent` | the `subject-phase` `LabEvent` through `onEvent`. Its `type` is `"subject-phase"`; the phase that was `type` is `name`, and `target` is `{ kind: "subject" }` or `{ kind: "participant", participant }`. `message`, `at`, `ok` and `durationMs` are unchanged |
1490
- | `LabOutcome.backend`, `LabBackend` | `outcome.route` |
1491
- | `selectLabBackend`, the `routesTo*` predicates, `actorResolvesToTerminal` | `routeOf(config)` |
1492
- | `resolveLabDryRun` | `RunLabOptions.dryRun`; without it, `scenario.mode` decides |
1493
- | `cuaLaneCount` | the `plan` `LabEvent`, which lists every participant |
1494
- | `resolveSeatUrl` | `parseLabConfig`, which refuses a participant entry that is not same-origin loopback; build the URL with `new URL(entry, serveUrl)` |
1495
- | `cuaLaneValidationReason`, `sharedWorldValidationReason`, `concurrentSharedWorldValidationReason`, `externalPublicSharedWorldValidationReason` | `parseLabConfig`, which returns the same reason |
1496
- | `MAX_CUA_LANES` | none; `parseLabConfig` refuses a roster of more than 16 participants |
1543
+ | Removed export | Use |
1544
+ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1545
+ | `runDryRun`, `RunOptions`, `RunResult` | `runStudy(config, options)` on a `this-repo` study; its result is `StudyResult<"preview">` |
1546
+ | `runCuaActorSession` | `runComputerUseLoop` with `createOpenAiResponsesProvider({ singleDispatch: true })`, `redaction: defaultRedactionHooks` and `now: Date.now`, which `runCuaActorSession` defaulted (the composition above). A caller that passed `desktop` and `executorOptions` writes its own `ComputerUseExecutor`: the E2B desktop executor is not exported |
1547
+ | `CuaActorLabResult`, `ScriptedBrowserLabResult`, `TerminalProductLabResult`, `ConcurrentSharedWorldLabResult` | `StudyResult<"computer-use">`, `StudyResult<"scripted">`, `StudyResult<"terminal">`, `StudyResult<"shared-world">` |
1548
+ | `SubjectPhaseEvent` | the `subject-phase` `StudyEvent` through `onEvent`. Its `type` is `"subject-phase"`; the phase that was `type` is `name`, and `target` is `{ kind: "subject" }` or `{ kind: "participant", participant }`. `message`, `at`, `ok` and `durationMs` are unchanged |
1549
+ | `StudyOutcome.backend`, `LabBackend` | `outcome.route` |
1550
+ | `selectLabBackend`, the `routesTo*` predicates, `actorResolvesToTerminal` | `routeOf(config)` |
1551
+ | `resolveLabDryRun` | `RunStudyOptions.dryRun`; without it, `scenario.mode` decides |
1552
+ | `cuaLaneCount` | the `plan` `StudyEvent`, which lists every participant |
1553
+ | `resolveSeatUrl` | `parseStudy`, which refuses a participant entry that is not same-origin loopback; build the URL with `new URL(entry, serveUrl)` |
1554
+ | `cuaLaneValidationReason`, `sharedWorldValidationReason`, `concurrentSharedWorldValidationReason`, `externalPublicSharedWorldValidationReason` | `parseStudy`, which returns the same reason |
1555
+ | `MAX_CUA_LANES` | none; `parseStudy` refuses a roster of more than 16 participants |
1497
1556
 
1498
1557
  ## Product-Adapter Extension Seam
1499
1558
 
1500
1559
  The terminal-product and browser/computer-use routes let an adopter attach
1501
- product-specific scoring + feedback as a THIN in-repo extension WITHOUT forking
1502
- core. The seam is `RunLabOptions.scorer`, an `AdapterScorerModule`, for terminal-product,
1560
+ product-specific scoring + feedback as a thin in-repo extension without forking
1561
+ core. The seam is `RunStudyOptions.scorer`, an `AdapterScorerModule`, for terminal-product,
1503
1562
  computer-use and shared-world runs; the CLI loads the same module with `--scorer`. This is never a built-in product scorer (the adopter's scorecard
1504
1563
  lives in the adopter's repo).
1505
1564
 
@@ -1516,8 +1575,8 @@ status, score, summary, data? }`. Core never reads `data`, where the adopter's
1516
1575
  feedback-candidate shape; product-specific concepts (public CLI/product command
1517
1576
  observed, hosted product success-or-blocker, feedback id/draft, media/job/asset
1518
1577
  ids, explicit no-media/no-provider-spend proof, defection/friction risk) are
1519
- recorded ONLY under `adapter: { namespace, data }`, never as core enums. Core
1520
- validates the SHAPE (a non-empty `namespace` + a `data` record); the keys inside
1578
+ recorded only under `adapter: { namespace, data }`, never as core enums. Core
1579
+ validates the shape (a non-empty `namespace` + a `data` record); the keys inside
1521
1580
  `data` are the adapter's.
1522
1581
  - **Adapter artifacts** (`humanish.adapter-artifact.v1`,
1523
1582
  `RunBundle.adapterArtifacts[]`). A namespaced list of local relative artifact
@@ -1563,7 +1622,7 @@ Acceptance semantics are route-specific:
1563
1622
 
1564
1623
  - Terminal-product runs keep the mission-based `review` verdict unchanged for a
1565
1624
  library `scorer`; the adapter score is additive. A config-declared scorer
1566
- (`--scorer` or the lab's `review.scorer.ref`) that returns `status: fail`,
1625
+ (`--scorer` or the study's `review.scorer.ref`) that returns `status: fail`,
1567
1626
  throws, or returns a malformed score turns a pass-like verdict into `fail` and
1568
1627
  fails the run result.
1569
1628
  - Browser/computer-use runs treat `adapterScore.status: fail` as product-red:
@@ -1574,18 +1633,18 @@ Acceptance semantics are route-specific:
1574
1633
  evidence.
1575
1634
 
1576
1635
  The `e2b-terminal` substrate is added to `RunFeedbackCandidate.substrate` so a
1577
- terminal-agent candidate names its substrate honestly; browser candidates use
1578
- the existing `e2b-desktop` substrate. The routes invoke hooks over FULLY-ASSEMBLED,
1636
+ terminal-agent candidate names the substrate it ran on; browser candidates use
1637
+ the existing `e2b-desktop` substrate. The routes invoke hooks over fully-assembled,
1579
1638
  redacted evidence (`TerminalProductScoringContext` or
1580
- `BrowserLabScoringContext`: `bundle`, runtime-only `runDir`, run identifiers,
1639
+ `BrowserScoringContext`: `bundle`, runtime-only `runDir`, run identifiers,
1581
1640
  actor and route metadata; all exported public types), scrub+redact returned
1582
- payloads, and DROP any malformed score, candidate, or artifact reference with a
1641
+ payloads, and drop any malformed score, candidate, or artifact reference with a
1583
1642
  warning so a bad extension never poisons a verifiable bundle. On the terminal
1584
- route the context also carries `transcript`: the FULL normalized session
1643
+ route the context also carries `transcript`: the full normalized session
1585
1644
  transcript, source-scrubbed then shape-redacted and byte-identical to the
1586
1645
  persisted `terminal-transcript.txt` (the trace's `transcriptTail` is a ~2KB
1587
1646
  projection of it), so an adopter rubric can find command-tier evidence anywhere
1588
- in the session rather than only in the tail window (#341). Default behavior
1647
+ in the session, including outside the tail window. Default behavior
1589
1648
  (no hook) is unchanged. `verifyRun` re-checks the surviving shapes fail-closed,
1590
1649
  including existence for referenced adapter artifacts.
1591
1650
 
@@ -1671,10 +1730,10 @@ bundlePath: .humanish/runs/synthetic-run-bundle-2026-06-02t10-00-00-000z-proof/r
1671
1730
  checks:
1672
1731
  - name: run.json exists
1673
1732
  ok: true
1674
- message: run.json present
1733
+ message: run.json is present
1675
1734
  - name: redaction passed
1676
1735
  ok: true
1677
- message: redaction status must be passed
1736
+ message: redaction passed
1678
1737
  shareSafety:
1679
1738
  status: share_ready
1680
1739
  reasons: []