@jr2/orchestrator 0.4.1 → 0.6.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 (361) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +478 -0
  10. package/docs/concepts/architecture.md +243 -0
  11. package/docs/concepts/charts.md +146 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +257 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +658 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +298 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +306 -0
  20. package/docs/concepts/the-menu.md +381 -0
  21. package/docs/concepts/workspaces.md +756 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +383 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +993 -0
  27. package/docs/get-started/install.md +130 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +308 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +224 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/cut-a-worktree.md +560 -0
  34. package/docs/guides/fix-a-failed-converge.md +361 -0
  35. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  36. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  37. package/docs/guides/let-an-agent-write-a-chart.md +230 -0
  38. package/docs/guides/look-inside-a-sandbox.md +177 -0
  39. package/docs/guides/mirror-the-kit-images.md +176 -0
  40. package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
  41. package/docs/guides/open-the-console.md +91 -0
  42. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  43. package/docs/guides/push-and-sign-a-branch.md +532 -0
  44. package/docs/guides/run-a-chart-by-name.md +270 -0
  45. package/docs/guides/run-a-command-or-call-a-url.md +587 -0
  46. package/docs/guides/run-one-worker-per-item.md +322 -0
  47. package/docs/guides/set-an-agents-access-and-dials.md +420 -0
  48. package/docs/guides/size-a-workspace.md +217 -0
  49. package/docs/guides/start-a-run-from-a-run.md +237 -0
  50. package/docs/guides/start-a-run.md +207 -0
  51. package/docs/guides/upgrade-the-kit-version.md +240 -0
  52. package/docs/guides/use-a-hosted-provider.md +214 -0
  53. package/docs/guides/use-a-local-model.md +313 -0
  54. package/docs/guides/watch-a-run.md +349 -0
  55. package/docs/guides/write-a-workflow.md +318 -0
  56. package/docs/index.md +44 -0
  57. package/docs/llms.txt +237 -0
  58. package/docs/reference/authoring-surface/agent.md +151 -0
  59. package/docs/reference/authoring-surface/answer.md +156 -0
  60. package/docs/reference/authoring-surface/arg.md +82 -0
  61. package/docs/reference/authoring-surface/basic.md +62 -0
  62. package/docs/reference/authoring-surface/bearer.md +53 -0
  63. package/docs/reference/authoring-surface/branchname.md +15 -0
  64. package/docs/reference/authoring-surface/chartschema.md +23 -0
  65. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  66. package/docs/reference/authoring-surface/command.md +206 -0
  67. package/docs/reference/authoring-surface/customize.md +64 -0
  68. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  69. package/docs/reference/authoring-surface/defineevent.md +114 -0
  70. package/docs/reference/authoring-surface/dispatch.md +126 -0
  71. package/docs/reference/authoring-surface/fail.md +96 -0
  72. package/docs/reference/authoring-surface/file.md +142 -0
  73. package/docs/reference/authoring-surface/frompick.md +277 -0
  74. package/docs/reference/authoring-surface/http.md +259 -0
  75. package/docs/reference/authoring-surface/isfailure.md +58 -0
  76. package/docs/reference/authoring-surface/isready.md +14 -0
  77. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  78. package/docs/reference/authoring-surface/open.md +105 -0
  79. package/docs/reference/authoring-surface/openbody.md +86 -0
  80. package/docs/reference/authoring-surface/paramsof.md +143 -0
  81. package/docs/reference/authoring-surface/pool.md +186 -0
  82. package/docs/reference/authoring-surface/push.md +130 -0
  83. package/docs/reference/authoring-surface/readyof.md +21 -0
  84. package/docs/reference/authoring-surface/secret.md +139 -0
  85. package/docs/reference/authoring-surface/sentby.md +83 -0
  86. package/docs/reference/authoring-surface/source.md +51 -0
  87. package/docs/reference/authoring-surface/text.md +62 -0
  88. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  89. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  90. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  91. package/docs/reference/authoring-surface/type-agentslots.md +15 -0
  92. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  93. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  94. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  95. package/docs/reference/authoring-surface/type-arg.md +19 -0
  96. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  97. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  98. package/docs/reference/authoring-surface/type-binding.md +61 -0
  99. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  100. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  101. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  102. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  103. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  104. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  105. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  106. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  107. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  108. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  109. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  110. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  111. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  112. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  113. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  114. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  115. package/docs/reference/authoring-surface/type-childat.md +12 -0
  116. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  117. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  118. package/docs/reference/authoring-surface/type-commandinput.md +20 -0
  119. package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
  120. package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
  121. package/docs/reference/authoring-surface/type-customize.md +111 -0
  122. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  123. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  124. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  125. package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
  126. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  127. package/docs/reference/authoring-surface/type-duration.md +15 -0
  128. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  129. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  130. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  131. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  132. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  133. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  134. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  135. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  136. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  137. package/docs/reference/authoring-surface/type-failure.md +19 -0
  138. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  139. package/docs/reference/authoring-surface/type-filebody.md +37 -0
  140. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  141. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  142. package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
  143. package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
  144. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  145. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  146. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  147. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
  149. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  150. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  151. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  152. package/docs/reference/authoring-surface/type-lends.md +38 -0
  153. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  154. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  155. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  156. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  157. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  158. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
  159. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  160. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  161. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  162. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  163. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  164. package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
  165. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  166. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  167. package/docs/reference/authoring-surface/type-openof.md +12 -0
  168. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  169. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  170. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
  171. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
  172. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  173. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  174. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  175. package/docs/reference/authoring-surface/type-pushinput.md +34 -0
  176. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  177. package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
  178. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  179. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  180. package/docs/reference/authoring-surface/type-reached.md +12 -0
  181. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  182. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  183. package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
  184. package/docs/reference/authoring-surface/type-secret.md +25 -0
  185. package/docs/reference/authoring-surface/type-sender.md +19 -0
  186. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  187. package/docs/reference/authoring-surface/type-size.md +85 -0
  188. package/docs/reference/authoring-surface/type-slothandles.md +24 -0
  189. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  190. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  191. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  192. package/docs/reference/authoring-surface/type-text.md +19 -0
  193. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  194. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  195. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  196. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  197. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  198. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  199. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  200. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  201. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  202. package/docs/reference/authoring-surface/type-workspaced.md +115 -0
  203. package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
  204. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  205. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  206. package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
  207. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  208. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  209. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  210. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  211. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  212. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  213. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  214. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  215. package/docs/reference/authoring-surface/workspace.md +287 -0
  216. package/docs/reference/authoring-surface/worktree.md +71 -0
  217. package/docs/reference/authoring-surface.md +217 -0
  218. package/docs/reference/cli.md +433 -0
  219. package/docs/reference/config.md +765 -0
  220. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
  221. package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
  222. package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
  223. package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
  224. package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
  225. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  226. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  227. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  228. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  229. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  230. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  231. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
  232. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  233. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
  234. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
  235. package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
  236. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  237. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
  238. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  239. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
  240. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  241. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
  242. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
  243. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  244. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  245. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
  246. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
  247. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  248. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  249. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
  250. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  251. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
  252. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  253. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  254. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  255. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  256. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  257. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  258. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  259. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  260. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  261. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  262. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  263. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  264. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  265. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  266. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  267. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  268. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
  269. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
  270. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
  271. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  272. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  273. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  274. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  275. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
  276. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
  277. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  278. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  279. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
  280. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
  281. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  282. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  283. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
  284. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
  285. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
  286. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  287. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
  288. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  289. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  290. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  291. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  292. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  293. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  294. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  295. package/docs/reference/decisions.md +86 -0
  296. package/docs/reference/glossary.md +687 -0
  297. package/docs/reference/http-api.md +1386 -0
  298. package/docs/reference/machines.md +189 -0
  299. package/docs/reference/mechanism-events.md +72 -0
  300. package/docs/why.md +207 -0
  301. package/package.json +5 -3
  302. package/src/actor.ts +694 -106
  303. package/src/agent.ts +114 -38
  304. package/src/ambient.ts +3 -4
  305. package/src/assembly-checks.ts +189 -7
  306. package/src/cause.ts +234 -0
  307. package/src/chart.ts +345 -46
  308. package/src/charts.ts +43 -8
  309. package/src/config.ts +440 -178
  310. package/src/custodian.lua +17 -5
  311. package/src/custodian.ts +325 -99
  312. package/src/customize.ts +199 -49
  313. package/src/dispatch-effect.ts +945 -0
  314. package/src/door-compat.ts +217 -0
  315. package/src/durability.ts +11 -4
  316. package/src/effect-checks.ts +50 -3
  317. package/src/effect.ts +641 -128
  318. package/src/effector-wire.ts +165 -13
  319. package/src/fail.ts +222 -0
  320. package/src/harness-client.ts +587 -50
  321. package/src/harness-env.ts +33 -0
  322. package/src/held-secrets.ts +125 -16
  323. package/src/holds.ts +3 -0
  324. package/src/http-effect.ts +556 -58
  325. package/src/http.ts +480 -30
  326. package/src/index.ts +74 -82
  327. package/src/instance.ts +68 -8
  328. package/src/internal.ts +85 -0
  329. package/src/kube-client.ts +6 -3
  330. package/src/machine-doc.ts +24 -7
  331. package/src/mechanism-routes.ts +168 -0
  332. package/src/model-keys.generated.ts +53 -0
  333. package/src/model-keys.ts +42 -58
  334. package/src/names.ts +19 -6
  335. package/src/notices.ts +483 -41
  336. package/src/open-body.ts +242 -40
  337. package/src/open-effect.ts +81 -8
  338. package/src/open.ts +90 -6
  339. package/src/parts.ts +213 -30
  340. package/src/party.ts +101 -2
  341. package/src/pick.ts +497 -11
  342. package/src/pool.ts +282 -50
  343. package/src/registration.ts +133 -12
  344. package/src/repo-identity.ts +7 -3
  345. package/src/repos.ts +22 -3
  346. package/src/run-host.ts +665 -45
  347. package/src/run-output.ts +38 -0
  348. package/src/sandbox-effect.ts +457 -71
  349. package/src/sandbox-kube.ts +66 -17
  350. package/src/secret-placement.ts +59 -17
  351. package/src/server.ts +3 -0
  352. package/src/setup.ts +325 -62
  353. package/src/size.ts +121 -9
  354. package/src/snapshot-store.ts +54 -10
  355. package/src/stub-harness.ts +44 -16
  356. package/src/timers.ts +14 -1
  357. package/src/tokens.ts +107 -32
  358. package/src/vocabulary.ts +88 -14
  359. package/src/wire.ts +100 -26
  360. package/src/workspace.ts +771 -238
  361. package/src/worktree.ts +249 -0
package/src/chart.ts CHANGED
@@ -19,10 +19,12 @@
19
19
  // (open-body.ts), so that `jr2Setup`'s own refusals and load warnings are lines of the same report.
20
20
 
21
21
  import { createHash } from "node:crypto";
22
+ import { posix } from "node:path";
22
23
  import { z } from "zod";
23
24
  import { defineEvent, type EventAudience, type EventDef } from "@jr2/agent-protocol";
24
25
  import type { AgentDefinition, ThinkingLevel, WorkspaceAccess } from "./agent.ts";
25
- import { inputKeysOf, isEffect } from "./effect.ts";
26
+ import { inputKeysOf, inputShapeOf, isEffect, type InputShape } from "./effect.ts";
27
+ import { FAILURE_GUARD } from "./fail.ts";
26
28
  import { compileChart, durationMs, type LentParts, type OpenBodyDeclaration } from "./open-body.ts";
27
29
  import { SENT_BY } from "./party.ts";
28
30
  import { loadWarningsOf } from "./vocabulary.ts";
@@ -38,7 +40,8 @@ export type ChartJson = string | number | boolean | null | ChartJson[] | { [key:
38
40
  */
39
41
  export type ChartValue = ChartJson;
40
42
 
41
- /** A guard: a lent guard by name, with or without params, `jr2.sentBy`, or `not`/`and`/`or`. */
43
+ /** A guard: a lent guard by name, with or without params, `jr2.sentBy`, `jr2.failure`, or
44
+ * `not`/`and`/`or`. */
42
45
  export type ChartGuard =
43
46
  | string
44
47
  | { type: string; params?: ChartValue }
@@ -71,7 +74,7 @@ export type ChartTransition =
71
74
  /** One transition or a list, in xstate's order. */
72
75
  export type ChartTransitions = ChartTransition | ChartTransition[];
73
76
 
74
- /** An invoke: a lent Agent, Effect or Machine, a minted Agent, or the kit's `gate`. */
77
+ /** An invoke: a lent Agent, Effect or Machine, a minted Agent, or the kit's `gate` or `fail`. */
75
78
  export type ChartInvoke = {
76
79
  src: string;
77
80
  id?: string;
@@ -138,6 +141,10 @@ const MECHANISM_EVENTS: Record<string, { fields: Record<string, boolean> }> = {
138
141
  * placement; see {@link checkAgentInput}). */
139
142
  const AGENT_INPUT_KEYS = ["prompt", "cwd", "continue", "model", "thinkingLevel"] as const;
140
143
 
144
+ /** The kit's test for a cut that settled `ready`, in every Chart beside `jr2.failure`: a cut's own
145
+ * outcomes are output, so a Chart routes on them, and a Chart has no other way to read one. */
146
+ export const READY_GUARD = "jr2.ready";
147
+
141
148
  /**
142
149
  * The Chart briefing (ADR-0070): jr2's text on how a Chart runs, for whoever writes one. It is the
143
150
  * grammar's root description, so it rides `GET /workflows/<wrapper>/chart-schema` and a pick's tool
@@ -158,6 +165,20 @@ export function chartBriefing(narrow?: Narrow): string {
158
165
  ? ' `src: "gate"` is the kit\'s Gate: the Chart waits there for an outside answer (a human, a webhook), ' +
159
166
  'one of its `events` with `audience: "external"`, not an Agent\'s Turn.'
160
167
  : "";
168
+ const fail = narrow?.fail
169
+ ? ' `src: "fail"` with `input: { reason }` ends the run as a Failure: work that did not succeed, with ' +
170
+ `the reason for a person; an \`onError\` with the guard \`${FAILURE_GUARD}\` catches it. A final state means done.`
171
+ : "";
172
+ // A declaration is names alone, so this says "a lent Effect that cuts".
173
+ const cut =
174
+ narrow === undefined || narrow.lendsEffects
175
+ ? ' In a Workspace with Repo Slots, work happens in a Worktree. A lent Effect that cuts one takes `input: { path: "/work/<slot>/<name>", ' +
176
+ 'branch: "<branch>" }`: `<name>` is one folder name that you choose, and it is not the branch. The Open body sets the cut\'s `expect`, ' +
177
+ "so its input has no `expect`. Give its `onDone` two edges: the guard " +
178
+ `\`${READY_GUARD}\` on the one that goes on, and one with no guard for a refused cut. Then write that same literal path as \`cwd\` in an ` +
179
+ "Agent's `input` and as `worktree` in an Effect's `input`: each Turn and each Effect there must say where it works. " +
180
+ "The slot's folder `/work/<slot>` is not a Worktree."
181
+ : "";
161
182
  const agents = mints
162
183
  ? `A state runs an Agent with \`invoke: { src, input: { prompt } }\`: \`src\` is a lent Agent's name${lent}. ` +
163
184
  "This Chart mints no Agent of its own: there is no `agents`."
@@ -165,7 +186,7 @@ export function chartBriefing(narrow?: Narrow): string {
165
186
  "a key of `agents`; a lent Agent needs no entry in `agents`, which mints new ones.";
166
187
  return (
167
188
  `A Chart is a workflow written as data: states, the events its Agents pick${mints ? "" : ", and the Agents it mints"}. ` +
168
- `${agents}${gate} An Agent's Turn ends only when it ` +
189
+ `${agents}${gate}${fail}${cut} An Agent's Turn ends only when it ` +
169
190
  "picks one of the Chart's `events`: declare each pick in `events` with an `input` schema " +
170
191
  '(`{ "type": "object", "properties": { "<field>": { "type": "string" } }, "required": ["<field>"] }`; a field not in ' +
171
192
  "`required` may be absent), and " +
@@ -232,7 +253,9 @@ export function pathSegments(path: string): Array<string | number> {
232
253
  type Narrow = {
233
254
  srcs: string[];
234
255
  lentAgents: string[];
256
+ lendsEffects: boolean;
235
257
  gate: boolean;
258
+ fail: boolean;
236
259
  guards: string[];
237
260
  actions: string[];
238
261
  delays: string[];
@@ -353,16 +376,25 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
353
376
  })
354
377
  .describe('A context path without the "context." root, like "round" or "ticket.notes".');
355
378
 
356
- // `jr2.sentBy` is the kit's routing guard (ADR-0015), and the one guard that is never lent.
379
+ // `jr2.sentBy` is the kit's routing guard (ADR-0015), and `jr2.failure` its test for a Failure on
380
+ // an `onError` (ADR-0073): every Chart has both, and neither is lent.
357
381
  const sentByArm = z.strictObject({ type: z.literal(SENT_BY), params: z.strictObject({ id: z.string() }) });
382
+ const failureArm = z.strictObject({ type: z.union([z.literal(FAILURE_GUARD), z.literal(READY_GUARD)]) });
358
383
  const lentGuardName = narrow
359
- ? enumOf(narrow.guards)
384
+ ? enumOf([...narrow.guards, FAILURE_GUARD, READY_GUARD])
360
385
  : z.string().refine((t) => t !== SENT_BY, { message: `"${SENT_BY}" takes params { id }` });
386
+ const paramGuardName = narrow
387
+ ? enumOf(narrow.guards)
388
+ : z
389
+ .string()
390
+ .refine((t) => t !== SENT_BY, { message: `"${SENT_BY}" takes params { id }` })
391
+ .refine((t) => t !== FAILURE_GUARD && t !== READY_GUARD, { message: "that guard takes no params" });
361
392
  const guard: z.ZodType<ChartGuard> = z
362
393
  .union([
363
394
  lentGuardName,
364
395
  sentByArm,
365
- z.strictObject({ type: lentGuardName, params: template.optional() }),
396
+ failureArm,
397
+ z.strictObject({ type: paramGuardName, params: template.optional() }),
366
398
  z.strictObject({
367
399
  get not() {
368
400
  return guard;
@@ -381,7 +413,7 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
381
413
  ])
382
414
  .meta({
383
415
  id: `${ID_PREFIX}ChartGuard`,
384
- description: "A lent guard by name, `jr2.sentBy`, or not/and/or of guards.",
416
+ description: `A lent guard by name, \`jr2.sentBy\`, \`${FAILURE_GUARD}\`, \`${READY_GUARD}\`, or not/and/or of guards.`,
385
417
  }) as never;
386
418
 
387
419
  const lentActionName = narrow
@@ -425,9 +457,14 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
425
457
  : z.string();
426
458
  const invoke = z
427
459
  .strictObject({
460
+ // A narrowed grammar names `fail` only where the body lends it: a model reads only what it can use.
428
461
  src: src.describe(
429
- "A lent Agent, Effect or Machine by its name; the kit's `gate` (waits for an outside answer: a human, a " +
430
- "webhook) where the body lends it; or a key of this Chart's `agents`.",
462
+ narrow && !narrow.fail
463
+ ? "A lent Agent, Effect or Machine by its name; the kit's `gate` (waits for an outside answer: a human, a " +
464
+ "webhook) where the body lends it; or a key of this Chart's `agents`."
465
+ : "A lent Agent, Effect or Machine by its name; the kit's `gate` (waits for an outside answer: a human, a " +
466
+ "webhook) or `fail` (ends the run as a Failure, with `input: { reason }`) where the body lends it; or a " +
467
+ "key of this Chart's `agents`.",
431
468
  ),
432
469
  id: name("invoke id").optional(),
433
470
  input: template.optional().describe("An Agent takes `{ prompt }`; an Effect or a Machine takes its own input."),
@@ -526,9 +563,17 @@ function grammar(narrow?: Narrow): z.ZodType<ChartText> {
526
563
  .describe(chartBriefing(narrow)) as unknown as z.ZodType<ChartText>;
527
564
  }
528
565
 
566
+ // A door field or a pick param that IS this schema is a Chart field: `isChartSchema` tells, and
567
+ // jr2 reads a Chart wrapper's one-shot door off it (ADR-0070).
529
568
  /**
530
- * The Chart grammar (ADR-0070), as zod. A door field or a pick param that IS this schema is a
531
- * Chart field: `isChartSchema` tells, and jr2 reads a Chart wrapper's one-shot door off it.
569
+ * The Chart grammar, as a zod schema. Make a field of the run input of a Chart wrapper this schema,
570
+ * as in `z.object({ topic: z.string(), chart: chartSchema })`. Then a run input can carry a Chart,
571
+ * and the wrapper can keep Charts under names: Chart files in `workflows/<wrapper>/`, and runtime
572
+ * Charts. For the pick of a planner, use `chartSchemaFor`, which knows the names that the Open body
573
+ * lends.
574
+ *
575
+ * @see docs/guides/run-a-chart-by-name.md
576
+ * @see docs/concepts/charts.md
532
577
  */
533
578
  export const chartSchema: z.ZodType<ChartText> = guarded(grammar());
534
579
 
@@ -578,13 +623,38 @@ export function chartBriefingOf(schema: unknown): string | undefined {
578
623
 
579
624
  const narrowedSchemas = new WeakMap<OpenBodyDeclaration, z.ZodType<ChartText>>();
580
625
 
626
+ // Either way it is a Chart field (`isChartSchema`), and the door still admits the Chart against
627
+ // the lent set the body has at its start. One schema per declaration (ADR-0070).
581
628
  /**
582
- * The Chart grammar as zod, narrowed to one Open body (ADR-0070): the lent `src` names, guard,
583
- * action and delay names, and the allowed models and Workspace accesses are enums. A planner's
584
- * pick whose field is this schema gets the narrowed grammar as its tool parameters, so the model
585
- * is constrained when it writes the Chart; a Chart wrapper's door may take it too. Either way it
586
- * is a Chart field (`isChartSchema`), and the door still admits the Chart against the lent set the
587
- * body has at its start. One schema per declaration.
629
+ * The Chart grammar for one Open body, as a zod schema. The names that the Open body lends, and the
630
+ * models and the Workspace accesses that it allows, are the only values that the schema takes.
631
+ * Make it the parameter of the pick of a planner. The model then gets the grammar as the
632
+ * parameters of its tool, and the Briefing tells it how a Chart runs. The door still checks each
633
+ * Chart before it runs.
634
+ *
635
+ * @param decl - The declaration of the Open body that the Chart binds.
636
+ * @returns The schema of a Chart for that Open body.
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * import { z } from "zod";
641
+ * import { chartSchemaFor, defineEvent, type OpenBodyDeclaration } from "@jr2/orchestrator";
642
+ *
643
+ * const declaration: OpenBodyDeclaration = {
644
+ * lends: { agents: ["writer"], gate: true },
645
+ * allows: { models: ["anthropic/claude-haiku-5-5"], workspace: ["none"] },
646
+ * ceilings: { turns: 12, wallClock: "30m", transitions: 500, fanOut: 2, size: 65536 },
647
+ * };
648
+ *
649
+ * export const runChart = defineEvent({
650
+ * name: "run_chart",
651
+ * description: "Run this Chart. Its outcome is the answer to this call.",
652
+ * input: z.object({ chart: chartSchemaFor(declaration) }),
653
+ * });
654
+ * ```
655
+ *
656
+ * @see docs/guides/let-an-agent-write-a-chart.md
657
+ * @see docs/concepts/charts.md
588
658
  */
589
659
  export function chartSchemaFor(decl: OpenBodyDeclaration): z.ZodType<ChartText> {
590
660
  const hit = narrowedSchemas.get(decl);
@@ -607,6 +677,7 @@ function lendsOf(decl: OpenBodyDeclaration) {
607
677
  actions: decl.lends.actions ?? [],
608
678
  delays: decl.lends.delays ?? [],
609
679
  gate: decl.lends.gate === true,
680
+ fail: decl.lends.fail === true,
610
681
  };
611
682
  }
612
683
 
@@ -624,9 +695,11 @@ export function narrowedChartSchema(decl: OpenBodyDeclaration): Record<string, u
624
695
  function narrowOf(decl: OpenBodyDeclaration): Narrow {
625
696
  const l = lendsOf(decl);
626
697
  return {
627
- srcs: [...l.agents, ...l.effects, ...l.machines, ...(l.gate ? ["gate"] : [])],
698
+ srcs: [...l.agents, ...l.effects, ...l.machines, ...(l.gate ? ["gate"] : []), ...(l.fail ? ["fail"] : [])],
628
699
  lentAgents: l.agents,
700
+ lendsEffects: l.effects.length > 0,
629
701
  gate: l.gate,
702
+ fail: l.fail,
630
703
  guards: l.guards,
631
704
  actions: l.actions,
632
705
  delays: l.delays,
@@ -782,6 +855,17 @@ export type AdmittedChart = {
782
855
  size: number;
783
856
  };
784
857
 
858
+ /**
859
+ * Why a Chart's `cwd` is not a place to work, or undefined when it can be: a Repo Slot's folder is no
860
+ * Worktree. There, an Agent's search hits `default/`, and a relative write lands outside each
861
+ * Worktree (ADR-0057, ADR-0075).
862
+ */
863
+ export function slotFolderFault(cwd: string, slots: readonly string[]): string | undefined {
864
+ const slot = posix.normalize(cwd).match(/^\/work\/([^/]+)\/?$/)?.[1];
865
+ if (slot === undefined || !slots.includes(slot)) return undefined;
866
+ return `"${cwd}" is a Repo Slot's folder, not a Worktree; \`cwd\` is a Worktree that a cut made, "/work/${slot}/<name>", or "/work/${slot}/default" to read, or "/work"`;
867
+ }
868
+
785
869
  /** What the door knows beyond the declaration and the lent set. */
786
870
  export type AdmitOptions = {
787
871
  /**
@@ -790,6 +874,11 @@ export type AdmitOptions = {
790
874
  * `input.<key>` reference is checked against it; absent, `input` references are not checked.
791
875
  */
792
876
  inputKeys?: readonly string[];
877
+ /** The Repo Slots of the Workspace around the Open body, where the door can read them. Given and
878
+ * not empty, a Turn's `cwd` and an Effect's `worktree` are required, a literal path's slot is
879
+ * checked, and a literal `cwd` cannot be a slot's folder. Absent: the start of a Turn or an Effect
880
+ * says the same of a missing `cwd`, of a Worktree path, and of a `cwd` that is a slot's folder. */
881
+ repoSlots?: readonly string[];
793
882
  };
794
883
 
795
884
  const MAX_STATES = 200;
@@ -867,6 +956,9 @@ function issueLines(
867
956
 
868
957
  const pathText = (path: readonly PropertyKey[]): string => (path.length ? path.map(String).join(".") : "(chart)");
869
958
 
959
+ /** What an invoke's `src` names (rule 3). */
960
+ type InvokeKind = "agent" | "minted" | "effect" | "machine" | "gate" | "fail";
961
+
870
962
  /** One state of the Chart, indexed for the checks. */
871
963
  type Node = {
872
964
  key: string;
@@ -932,6 +1024,13 @@ export function admitChart(
932
1024
  return new Door(chart, decl, lent, options).admit(size);
933
1025
  }
934
1026
 
1027
+ /** What an Effect's input takes, for a line of the door. */
1028
+ function shapeText(shape: InputShape): string {
1029
+ const need = [...shape.required, ...(shape.oneOf ? [`exactly one of ${shape.oneOf.join(", ")}`] : [])].join(", ");
1030
+ const may = shape.optional.length ? `${need ? ", and " : ""}optionally ${shape.optional.join(", ")}` : "";
1031
+ return need || may ? `${need}${may}` : "nothing";
1032
+ }
1033
+
935
1034
  /** The checks from rule 3 on, over a Chart the grammar admitted. */
936
1035
  class Door {
937
1036
  private readonly lines: Line[] = [];
@@ -1064,11 +1163,12 @@ class Door {
1064
1163
  private lentText(): string {
1065
1164
  const actors = this.lentActors().map((a) => `${a.name} (${a.kind})`);
1066
1165
  const minted = Object.keys(this.chart.agents ?? {});
1067
- return `lent: ${names(actors)}; kit: ${this.lends.gate ? "gate" : "none"}; minted: ${names(minted)}`;
1166
+ const kit = [...(this.lends.gate ? ["gate"] : []), ...(this.lends.fail ? ["fail"] : [])];
1167
+ return `lent: ${names(actors)}; kit: ${kit.length ? kit.join(", ") : "none"}; minted: ${names(minted)}`;
1068
1168
  }
1069
1169
 
1070
1170
  /** What an invoke's `src` names, or undefined after a refusal line. */
1071
- private srcKind(src: string, path: string): "agent" | "minted" | "effect" | "machine" | "gate" | undefined {
1171
+ private srcKind(src: string, path: string): InvokeKind | undefined {
1072
1172
  const lentNow = (name: string) => name in this.lent.actors;
1073
1173
  const declared = [...this.lends.agents, ...this.lends.effects, ...this.lends.machines];
1074
1174
  if (declared.includes(src) && !lentNow(src)) {
@@ -1089,6 +1189,14 @@ class Door {
1089
1189
  );
1090
1190
  return undefined;
1091
1191
  }
1192
+ if (src === "fail") {
1193
+ if (this.lends.fail) return "fail";
1194
+ this.fail(
1195
+ 3,
1196
+ `${path}: "fail" is the kit's Part that ends the run as a Failure, and this Open body does not lend it (\`lends.fail: true\` would); ${this.lentText()}`,
1197
+ );
1198
+ return undefined;
1199
+ }
1092
1200
  if (Object.hasOwn(this.chart.agents ?? {}, src)) return "minted";
1093
1201
  this.fail(3, `${path}: "${src}" is not a lent actor or an Agent of this Chart; ${this.lentText()}`);
1094
1202
  return undefined;
@@ -1103,11 +1211,16 @@ class Door {
1103
1211
  this.sentBys.push({ id: (guard.params as { id: string }).id, path: `${path}.params.id` });
1104
1212
  return;
1105
1213
  }
1214
+ if (guard.type === FAILURE_GUARD || guard.type === READY_GUARD) {
1215
+ if (guard.params !== undefined) this.fail(3, `${path}.params: "${guard.type}" takes no params`);
1216
+ return;
1217
+ }
1106
1218
  this.checkGuardName(guard.type, `${path}.type`);
1107
1219
  if (guard.params !== undefined) this.checkRefs(guard.params, `${path}.params`, scope);
1108
1220
  }
1109
1221
 
1110
1222
  private checkGuardName(name: string, path: string): void {
1223
+ if (name === FAILURE_GUARD || name === READY_GUARD) return;
1111
1224
  const lent = this.lends.guards.filter((g) => g in this.lent.guards);
1112
1225
  if (this.lends.guards.includes(name) && !(name in this.lent.guards)) {
1113
1226
  this.fail(
@@ -1117,7 +1230,7 @@ class Door {
1117
1230
  } else if (!this.lends.guards.includes(name)) {
1118
1231
  this.fail(
1119
1232
  3,
1120
- `${path}: "${name}" is not a lent guard; lent guards: ${names(lent)} (and ${SENT_BY} with params { id })`,
1233
+ `${path}: "${name}" is not a lent guard; lent guards: ${names(lent)} (and ${SENT_BY} with params { id }, ${FAILURE_GUARD} and ${READY_GUARD})`,
1121
1234
  );
1122
1235
  }
1123
1236
  }
@@ -1262,7 +1375,7 @@ class Door {
1262
1375
  // --- rule 5: minted Agents ------------------------------------------------------------------------
1263
1376
 
1264
1377
  private checkAgents(): void {
1265
- const lentNames = new Set([...this.lends.agents, ...this.lends.effects, ...this.lends.machines, "gate"]);
1378
+ const lentNames = new Set([...this.lends.agents, ...this.lends.effects, ...this.lends.machines, "gate", "fail"]);
1266
1379
  for (const [slot, a] of Object.entries(this.chart.agents ?? {})) {
1267
1380
  const path = `agents.${slot}`;
1268
1381
  if (lentNames.has(slot)) {
@@ -1607,6 +1720,7 @@ class Door {
1607
1720
  private checkParties(): void {
1608
1721
  const agents = new Set<string>();
1609
1722
  const gates = new Set<string>();
1723
+ const slotStates = new Map<string, Node[]>();
1610
1724
  for (const node of this.nodes) {
1611
1725
  const invokes = listAt(node.state.invoke, `${node.doc}.invoke`);
1612
1726
  // A Gate's invoke id, as jr2Setup names an unnamed one: the state's key path.
@@ -1621,6 +1735,7 @@ class Door {
1621
1735
  if (kind === "gate") gates.add(inv.id ?? (unnamed > 1 ? `${key}.${ordinal++}` : key));
1622
1736
  if (kind !== "agent" && kind !== "minted") continue;
1623
1737
  agents.add(inv.src);
1738
+ slotStates.set(inv.src, [...(slotStates.get(inv.src) ?? []), node]);
1624
1739
  if (inv.onDone !== undefined) {
1625
1740
  this.fail(
1626
1741
  6,
@@ -1642,6 +1757,25 @@ class Door {
1642
1757
  }
1643
1758
  }
1644
1759
  }
1760
+ // One Agent, two Turns at once: one sender and one conversation cannot hold both.
1761
+ // One line for each Agent: every state of it that can be active with another of them.
1762
+ for (const [slot, states] of slotStates) {
1763
+ const clash = new Set<Node>();
1764
+ for (let i = 0; i < states.length; i++) {
1765
+ for (let j = i + 1; j < states.length; j++) {
1766
+ const [a, b] = [states[i]!, states[j]!];
1767
+ const together =
1768
+ a === b || isAncestorOrSelf(a, b) || isAncestorOrSelf(b, a) || nearestCommon(a, b)?.kind === "parallel";
1769
+ if (together) clash.add(a).add(b);
1770
+ }
1771
+ }
1772
+ if (clash.size === 0) continue;
1773
+ const docs = [...clash].map((n) => n.doc).join(", ");
1774
+ this.fail(
1775
+ 6,
1776
+ `${docs}: Agent "${slot}" would run two Turns at once: these states invoke it and can be active together (in parallel regions, one inside the other, or one state twice); an Agent runs one Turn at a time: invoke another Agent in one of them`,
1777
+ );
1778
+ }
1645
1779
  for (const { id, path } of this.sentBys) {
1646
1780
  if (id.includes("{{")) {
1647
1781
  this.fail(3, `${path}: a sentBy id is a literal party name, never a reference`);
@@ -1835,15 +1969,12 @@ class Door {
1835
1969
 
1836
1970
  // --- rule 8: lent input shapes --------------------------------------------------------------------
1837
1971
 
1838
- private checkInvokeInput(
1839
- inv: ChartInvoke,
1840
- kind: "agent" | "minted" | "effect" | "machine" | "gate",
1841
- path: string,
1842
- ): void {
1972
+ private checkInvokeInput(inv: ChartInvoke, kind: InvokeKind, path: string): void {
1843
1973
  const ipath = `${path}.input`;
1844
1974
  const input = inv.input;
1845
1975
  const isObject = typeof input === "object" && input !== null && !Array.isArray(input) && !("$ref" in input);
1846
1976
  if (kind === "machine") return;
1977
+ if (kind === "fail") return this.checkFailInput(input, isObject, ipath);
1847
1978
  if (kind === "gate") {
1848
1979
  if (input === undefined) return;
1849
1980
  if (!isObject) return this.fail(8, `${ipath}: a Gate's input is an object of gate (a name) and meta`);
@@ -1863,38 +1994,166 @@ class Door {
1863
1994
  }
1864
1995
  if (kind === "effect") {
1865
1996
  const logic = this.lent.actors[inv.src];
1866
- const keys = isEffect(logic) ? inputKeysOf(logic.effect) : undefined;
1997
+ const parts = isEffect(logic) ? logic.effect : undefined;
1998
+ const keys = parts ? inputKeysOf(parts) : undefined;
1999
+ if (keys === undefined && parts?.kind === "dispatch") {
2000
+ // A dispatch with no input schema has no keys to hold the Chart to: its input is the run's
2001
+ // body, and the callee's door is the check (ADR-0071). Its values are what any Effect's
2002
+ // input slot takes.
2003
+ if (input === undefined) return;
2004
+ if (!isObject) {
2005
+ return this.fail(
2006
+ 8,
2007
+ `${ipath}: the lent dispatch "${inv.src}" takes an object, the run's body — the callee's door checks it`,
2008
+ );
2009
+ }
2010
+ for (const [k, v] of Object.entries(input as Record<string, unknown>)) {
2011
+ if (typeof v !== "string" && !isRef(v))
2012
+ this.fail(8, `${ipath}.${k}: an Effect's input slot takes a string or a reference`);
2013
+ }
2014
+ return;
2015
+ }
1867
2016
  if (keys === undefined) {
1868
2017
  return this.fail(
1869
2018
  8,
1870
2019
  `${ipath}: the lent Effect "${inv.src}" has no input keys the door can read (an Open argv nobody bound)`,
1871
2020
  );
1872
2021
  }
1873
- if (input === undefined && keys.length === 0) return;
1874
- if (!isObject) {
1875
- return this.fail(8, `${ipath}: the lent Effect "${inv.src}" takes an object of exactly ${names(keys)}`);
2022
+ const shape = this.shapeHere(parts!);
2023
+ // A cut's `expect` is static, and a Chart cannot state it: an unstated one would let a planner's
2024
+ // name land on someone else's branch.
2025
+ if (parts?.kind === "command" && parts.cut && parts.cut.expect === undefined) {
2026
+ const fresh = this.lentNewCuts();
2027
+ this.fail(
2028
+ 8,
2029
+ `${ipath}: the lent cut "${inv.src}" does not state \`expect\`, so a Chart cannot use it; ` +
2030
+ (fresh.length
2031
+ ? `use a lent cut with \`expect: "new"\`: ${names(fresh)}`
2032
+ : `the Open body must state it (\`worktree({ expect: "new" })\` would); ${this.lentText()}`),
2033
+ );
2034
+ }
2035
+ // A push publishes the branch of a cut's row, which stays in the run's ledger, so a Chart that
2036
+ // cuts and a later Chart that pushes are the same pair: the test is what is lent, not what this
2037
+ // Chart invokes.
2038
+ const force = this.lentForcePush();
2039
+ if (parts?.kind === "command" && parts.cut?.expect === "existing" && force !== undefined) {
2040
+ const fresh = this.lentNewCuts();
2041
+ this.fail(
2042
+ 8,
2043
+ `${ipath}: the lent cut "${inv.src}" states \`expect: "existing"\` beside the lent force push "${force}", and together they can force-push any existing branch; ` +
2044
+ (fresh.length
2045
+ ? `use a lent cut with \`expect: "new"\`: ${names(fresh)}`
2046
+ : `this Open body lends no cut with \`expect: "new"\`, so a Chart cannot cut here (\`worktree({ expect: "new" })\` would); ${this.lentText()}`),
2047
+ );
1876
2048
  }
2049
+ if (input === undefined && shape.required.length === 0 && !shape.oneOf) return;
2050
+ const takes = shapeText(shape);
2051
+ if (!isObject) return this.fail(8, `${ipath}: the lent Effect "${inv.src}" takes an object: ${takes}`);
1877
2052
  const given = Object.keys(input);
1878
- const missing = keys.filter((k) => !given.includes(k));
2053
+ const missing = shape.required.filter((k) => !given.includes(k));
1879
2054
  const extra = given.filter((k) => !keys.includes(k));
1880
- if (missing.length || extra.length) {
2055
+ const ofOne = shape.oneOf?.filter((k) => given.includes(k)).length ?? 1;
2056
+ if (missing.length || extra.length || ofOne !== 1) {
1881
2057
  this.fail(
1882
2058
  8,
1883
- `${ipath}: the lent Effect "${inv.src}" takes exactly ${names(keys)}` +
2059
+ `${ipath}: the lent Effect "${inv.src}" takes ${takes}` +
1884
2060
  (missing.length ? `; missing: ${missing.join(", ")}` : "") +
1885
- (extra.length ? `; not its input: ${extra.join(", ")}` : ""),
2061
+ (extra.length ? `; not its input: ${extra.join(", ")}` : "") +
2062
+ (ofOne !== 1 ? `; give one of ${shape.oneOf!.join(", ")}` : ""),
1886
2063
  );
1887
2064
  }
2065
+ if (parts?.kind === "command" && parts.cut && given.includes("detach") && given.includes("from")) {
2066
+ this.fail(8, `${ipath}: a detached cut takes no \`from\``);
2067
+ }
1888
2068
  for (const k of given.filter((g) => keys.includes(g))) {
1889
2069
  const v = (input as Record<string, unknown>)[k];
1890
2070
  if (typeof v !== "string" && !isRef(v))
1891
2071
  this.fail(8, `${ipath}.${k}: an Effect's input slot takes a string or a reference`);
2072
+ else if ((k === "worktree" || k === "path") && typeof v === "string") this.checkLiteralPath(v, `${ipath}.${k}`);
1892
2073
  }
1893
2074
  return;
1894
2075
  }
1895
2076
  this.checkAgentInput(inv.src, kind, input, isObject, ipath);
1896
2077
  }
1897
2078
 
2079
+ /** An Effect's shape here: where the door knows the Workspace has Repo Slots, `worktree` is
2080
+ * required on what can name one. Elsewhere the start says so (`drive`). */
2081
+ private shapeHere(parts: Parameters<typeof inputShapeOf>[0]): InputShape {
2082
+ const shape = inputShapeOf(parts)!;
2083
+ if (!this.options.repoSlots?.length || !shape.optional.includes("worktree")) return shape;
2084
+ return {
2085
+ ...shape,
2086
+ required: [...shape.required, "worktree"],
2087
+ optional: shape.optional.filter((k) => k !== "worktree"),
2088
+ };
2089
+ }
2090
+
2091
+ /** The lent Effects, each with its parts. */
2092
+ private lentEffects(): Array<{ name: string; parts: Parameters<typeof inputShapeOf>[0] }> {
2093
+ return this.lends.effects.flatMap((name) => {
2094
+ const logic = this.lent.actors[name];
2095
+ return isEffect(logic) ? [{ name, parts: logic.effect }] : [];
2096
+ });
2097
+ }
2098
+
2099
+ /** The name of a lent push that forces, if one is lent. */
2100
+ private lentForcePush(): string | undefined {
2101
+ return this.lentEffects().find(({ parts }) => parts.kind === "push" && parts.force)?.name;
2102
+ }
2103
+
2104
+ /** The names of the lent cuts that make only a new branch. */
2105
+ private lentNewCuts(): string[] {
2106
+ return this.lentEffects()
2107
+ .filter(({ parts }) => parts.kind === "command" && parts.cut?.expect === "new")
2108
+ .map(({ name }) => name);
2109
+ }
2110
+
2111
+ /** A literal Worktree path: a wrong slot costs no pod. A path with a reference waits for its start. */
2112
+ private checkLiteralPath(value: string, path: string): void {
2113
+ if (referencesOf(value).length) return;
2114
+ const match = value.match(/^\/work\/([^/]+)\/([^/.-][^/]*)$/);
2115
+ const slots = this.options.repoSlots;
2116
+ if (match === null) this.fail(8, `${path}: "${value}" is not a Worktree path: /work/<slot>/<name>`);
2117
+ else if (slots !== undefined && !slots.includes(match[1]!)) {
2118
+ this.fail(8, `${path}: "${value}" names no Repo Slot of this Workspace; its Repo Slots: ${names([...slots])}`);
2119
+ }
2120
+ }
2121
+
2122
+ /** A literal `cwd`, see {@link slotFolderFault}. A `cwd` with a reference waits for its value: the
2123
+ * Open body asks the same question when the Turn starts. */
2124
+ private checkCwd(value: string, path: string): void {
2125
+ if (referencesOf(value).length) return;
2126
+ const fault = slotFolderFault(value, this.options.repoSlots ?? []);
2127
+ if (fault !== undefined) this.fail(8, `${path}: ${fault}`);
2128
+ }
2129
+
2130
+ /** The input of `fail` (ADR-0073): `{ reason, detail? }`, `reason` a text for a person (a string
2131
+ * or a reference), `detail` any value. */
2132
+ private checkFailInput(input: unknown, isObject: boolean, ipath: string): void {
2133
+ if (!isObject) {
2134
+ return this.fail(8, `${ipath}: \`fail\` takes { reason, detail? }: reason is a text for a person`);
2135
+ }
2136
+ for (const key of Object.keys(input as Record<string, unknown>)) {
2137
+ if (key !== "reason" && key !== "detail") {
2138
+ this.fail(8, `${ipath}.${key}: \`fail\` takes reason and detail only`);
2139
+ }
2140
+ }
2141
+ const reason = (input as Record<string, unknown>).reason;
2142
+ if (reason === undefined) this.fail(8, `${ipath}.reason: \`fail\` needs a reason, a text for a person`);
2143
+ else if ((typeof reason !== "string" || reason === "") && !isRef(reason)) {
2144
+ this.fail(8, `${ipath}.reason: a reason is a string that is not empty, or a reference`);
2145
+ }
2146
+ }
2147
+
2148
+ /** Does this Agent read `/work`? A `workspace: "none"` one frames nothing. */
2149
+ private worksInWorkspace(src: string, kind: "agent" | "minted"): boolean {
2150
+ if (kind === "agent") {
2151
+ const logic = this.lent.actors[src] as { definition?: { workspace?: unknown } } | undefined;
2152
+ return logic?.definition?.workspace !== "none";
2153
+ }
2154
+ return (this.chart.agents?.[src] as { workspace?: unknown } | undefined)?.workspace !== "none";
2155
+ }
2156
+
1898
2157
  /**
1899
2158
  * An Agent invoke's input is a Turn's Frame and Dials (ADR-0057): `prompt` (a string), and
1900
2159
  * optionally `cwd`, `continue: true`, a `model` the Open body allows, and a `thinkingLevel`.
@@ -1928,6 +2187,13 @@ class Door {
1928
2187
  else if (typeof obj.prompt !== "string")
1929
2188
  this.fail(8, `${ipath}.prompt: a prompt is a string (placeholders inside it are fine)`);
1930
2189
  if (obj.cwd !== undefined && typeof obj.cwd !== "string") this.fail(8, `${ipath}.cwd: a cwd is a string`);
2190
+ if (obj.cwd === undefined && this.options.repoSlots?.length && this.worksInWorkspace(src, kind)) {
2191
+ this.fail(
2192
+ 8,
2193
+ `${ipath}: ${who} works under a Workspace with Repo Slots, and a Turn there says where: \`cwd\` is a Worktree, or "/work"`,
2194
+ );
2195
+ }
2196
+ if (typeof obj.cwd === "string" && this.worksInWorkspace(src, kind)) this.checkCwd(obj.cwd, `${ipath}.cwd`);
1931
2197
  if (obj.continue !== undefined && obj.continue !== true)
1932
2198
  this.fail(8, `${ipath}.continue: \`continue\` is true or absent`);
1933
2199
  if (obj.model !== undefined) {
@@ -1953,9 +2219,10 @@ class Door {
1953
2219
  * The JSON Schema keywords a Chart event's payload may use (ADR-0070, rule 4). The Orchestrator
1954
2220
  * checks every pick against this schema in its own process, before the Allowed check, so a keyword
1955
2221
  * whose cost the door cannot bound is refused by name: `pattern` (a backtracking regex),
1956
- * `patternProperties`, `$ref`, the combinators and the conditionals.
2222
+ * `patternProperties`, `$ref`, the combinators and the conditionals. It is also the subset a
2223
+ * dispatch's slot schema and its callee's door are compared within (ADR-0071, door-compat.ts).
1957
2224
  */
1958
- const PAYLOAD_KEYWORDS = new Set([
2225
+ export const PAYLOAD_KEYWORDS: ReadonlySet<string> = new Set([
1959
2226
  "type",
1960
2227
  "properties",
1961
2228
  "required",
@@ -1981,6 +2248,39 @@ const PAYLOAD_KEYWORDS = new Set([
1981
2248
  * the payload name the event a Chart receives (registration.ts builds `{ type, ...payload }`).
1982
2249
  */
1983
2250
  function payloadSchemaFaults(schema: unknown): Array<{ path: PropertyKey[]; message: string }> {
2251
+ const out = subsetFaults(
2252
+ schema,
2253
+ (key) =>
2254
+ `"${key}" is not a keyword a Chart event's payload may use; it may use ${[...PAYLOAD_KEYWORDS].join(", ")}`,
2255
+ );
2256
+ const props = (schema as { properties?: unknown } | null)?.properties;
2257
+ if (typeof props === "object" && props !== null && Object.hasOwn(props, "type")) {
2258
+ out.push({
2259
+ path: ["properties", "type"],
2260
+ message: "a payload field named `type` is refused: jr2 writes the event's type, and the payload never names it",
2261
+ });
2262
+ }
2263
+ return out;
2264
+ }
2265
+
2266
+ /**
2267
+ * The faults of a door's JSON Schema, or of a dispatch slot's (ADR-0071): every keyword not in
2268
+ * {@link PAYLOAD_KEYWORDS} and a list-form `items` — the payload rules without the Chart's own one
2269
+ * (a field named `type` is a door's to have). A schema with a fault is reported "not compared":
2270
+ * outside the subset, a comparison would be a guess.
2271
+ */
2272
+ export function doorSchemaFaults(schema: unknown): Array<{ path: PropertyKey[]; message: string }> {
2273
+ return subsetFaults(
2274
+ schema,
2275
+ (key) => `"${key}" is outside the JSON Schema subset jr2 compares (${[...PAYLOAD_KEYWORDS].join(", ")})`,
2276
+ );
2277
+ }
2278
+
2279
+ /** The subset's faults: `outside` words the fault of a keyword not in it, for whose rule it is. */
2280
+ function subsetFaults(
2281
+ schema: unknown,
2282
+ outside: (key: string) => string,
2283
+ ): Array<{ path: PropertyKey[]; message: string }> {
1984
2284
  const out: Array<{ path: PropertyKey[]; message: string }> = [];
1985
2285
  const walk = (node: unknown, path: PropertyKey[]): void => {
1986
2286
  if (typeof node === "boolean" && path.length && path[path.length - 1] === "additionalProperties") return;
@@ -1993,7 +2293,7 @@ function payloadSchemaFaults(schema: unknown): Array<{ path: PropertyKey[]; mess
1993
2293
  if (!PAYLOAD_KEYWORDS.has(key)) {
1994
2294
  out.push({
1995
2295
  path: [...path, key],
1996
- message: `"${key}" is not a keyword a Chart event's payload may use; it may use ${[...PAYLOAD_KEYWORDS].join(", ")}`,
2296
+ message: outside(key),
1997
2297
  });
1998
2298
  continue;
1999
2299
  }
@@ -2012,13 +2312,6 @@ function payloadSchemaFaults(schema: unknown): Array<{ path: PropertyKey[]; mess
2012
2312
  }
2013
2313
  };
2014
2314
  walk(schema, []);
2015
- const props = (schema as { properties?: unknown } | null)?.properties;
2016
- if (typeof props === "object" && props !== null && Object.hasOwn(props, "type")) {
2017
- out.push({
2018
- path: ["properties", "type"],
2019
- message: "a payload field named `type` is refused: jr2 writes the event's type, and the payload never names it",
2020
- });
2021
- }
2022
2315
  return out;
2023
2316
  }
2024
2317
 
@@ -2032,6 +2325,12 @@ function wholeRefOf(v: unknown): string | undefined {
2032
2325
  return isRef(v) ? (v as { $ref: string }).$ref : undefined;
2033
2326
  }
2034
2327
 
2328
+ /** The nearest state at or above both `a` and `b`. */
2329
+ function nearestCommon(a: Node, b: Node): Node | undefined {
2330
+ for (let n: Node | undefined = a; n; n = n.parent) if (isAncestorOrSelf(n, b)) return n;
2331
+ return undefined;
2332
+ }
2333
+
2035
2334
  /** Is `a` the state `b` or a state above it? */
2036
2335
  function isAncestorOrSelf(a: Node, b: Node): boolean {
2037
2336
  for (let n: Node | undefined = b; n; n = n.parent) if (n === a) return true;