@jr2/orchestrator 0.3.0 → 0.5.1

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 (354) hide show
  1. package/README.md +20 -2
  2. package/bin/server.ts +7 -4
  3. package/console/canvas.ts +27 -14
  4. package/console/components/app.ts +12 -1
  5. package/console/components/fleet.ts +52 -6
  6. package/console/components/machine-pane.ts +128 -2
  7. package/console/components/nav.ts +1 -1
  8. package/console/components/schema-form.ts +12 -4
  9. package/console/json-field.ts +23 -0
  10. package/console/main.ts +122 -39
  11. package/console/store.ts +321 -21
  12. package/console/style.css +111 -0
  13. package/docs/agents-block.md +5 -0
  14. package/docs/concepts/agents-and-turns.md +442 -0
  15. package/docs/concepts/architecture.md +230 -0
  16. package/docs/concepts/charts.md +143 -0
  17. package/docs/concepts/composition.md +357 -0
  18. package/docs/concepts/converge.md +254 -0
  19. package/docs/concepts/durable-runs.md +189 -0
  20. package/docs/concepts/effects.md +603 -0
  21. package/docs/concepts/gates.md +208 -0
  22. package/docs/concepts/machines-and-workflows.md +289 -0
  23. package/docs/concepts/pools-and-sources.md +230 -0
  24. package/docs/concepts/secrets-and-trust.md +222 -0
  25. package/docs/concepts/the-menu.md +378 -0
  26. package/docs/concepts/workspaces.md +678 -0
  27. package/docs/get-started/a-model-decides.md +563 -0
  28. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  29. package/docs/get-started/connect-a-model.md +248 -0
  30. package/docs/get-started/create-an-instance.md +194 -0
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  32. package/docs/get-started/install.md +126 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  34. package/docs/guides/answer-a-gate.md +304 -0
  35. package/docs/guides/bind-open-parts.md +223 -0
  36. package/docs/guides/cancel-a-run.md +146 -0
  37. package/docs/guides/clean-up.md +206 -0
  38. package/docs/guides/fix-a-failed-converge.md +361 -0
  39. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  41. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  42. package/docs/guides/look-inside-a-sandbox.md +174 -0
  43. package/docs/guides/mirror-the-kit-images.md +176 -0
  44. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  45. package/docs/guides/open-the-console.md +91 -0
  46. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  47. package/docs/guides/push-and-sign-a-branch.md +486 -0
  48. package/docs/guides/run-a-chart-by-name.md +270 -0
  49. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  50. package/docs/guides/run-one-worker-per-item.md +321 -0
  51. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  52. package/docs/guides/size-a-workspace.md +204 -0
  53. package/docs/guides/start-a-run-from-a-run.md +237 -0
  54. package/docs/guides/start-a-run.md +207 -0
  55. package/docs/guides/upgrade-the-kit-version.md +240 -0
  56. package/docs/guides/use-a-hosted-provider.md +208 -0
  57. package/docs/guides/use-a-local-model.md +313 -0
  58. package/docs/guides/watch-a-run.md +326 -0
  59. package/docs/guides/write-a-workflow.md +318 -0
  60. package/docs/index.md +44 -0
  61. package/docs/llms.txt +216 -0
  62. package/docs/reference/authoring-surface/agent.md +128 -0
  63. package/docs/reference/authoring-surface/answer.md +140 -0
  64. package/docs/reference/authoring-surface/arg.md +60 -0
  65. package/docs/reference/authoring-surface/basic.md +62 -0
  66. package/docs/reference/authoring-surface/bearer.md +53 -0
  67. package/docs/reference/authoring-surface/chartschema.md +23 -0
  68. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  69. package/docs/reference/authoring-surface/command.md +191 -0
  70. package/docs/reference/authoring-surface/customize.md +64 -0
  71. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  72. package/docs/reference/authoring-surface/defineevent.md +114 -0
  73. package/docs/reference/authoring-surface/dispatch.md +126 -0
  74. package/docs/reference/authoring-surface/fail.md +83 -0
  75. package/docs/reference/authoring-surface/file.md +122 -0
  76. package/docs/reference/authoring-surface/frompick.md +277 -0
  77. package/docs/reference/authoring-surface/http.md +159 -0
  78. package/docs/reference/authoring-surface/isfailure.md +58 -0
  79. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  80. package/docs/reference/authoring-surface/open.md +74 -0
  81. package/docs/reference/authoring-surface/openbody.md +86 -0
  82. package/docs/reference/authoring-surface/paramsof.md +125 -0
  83. package/docs/reference/authoring-surface/pool.md +186 -0
  84. package/docs/reference/authoring-surface/push.md +112 -0
  85. package/docs/reference/authoring-surface/secret.md +118 -0
  86. package/docs/reference/authoring-surface/sentby.md +83 -0
  87. package/docs/reference/authoring-surface/source.md +51 -0
  88. package/docs/reference/authoring-surface/text.md +62 -0
  89. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  90. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  91. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  92. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  93. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  94. package/docs/reference/authoring-surface/type-arg.md +19 -0
  95. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  96. package/docs/reference/authoring-surface/type-binding.md +62 -0
  97. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  98. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  99. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  100. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  101. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  102. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  103. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  104. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  105. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  106. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  107. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  108. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  109. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  110. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  111. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  112. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  113. package/docs/reference/authoring-surface/type-childat.md +12 -0
  114. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  115. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  116. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  117. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  118. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  119. package/docs/reference/authoring-surface/type-customize.md +109 -0
  120. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  121. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  122. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  124. package/docs/reference/authoring-surface/type-duration.md +15 -0
  125. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  126. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  127. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  128. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  129. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  130. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  131. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  132. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  133. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  134. package/docs/reference/authoring-surface/type-failure.md +19 -0
  135. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  136. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  137. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  138. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  139. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  140. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  141. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  142. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  143. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  144. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  145. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  146. package/docs/reference/authoring-surface/type-lends.md +38 -0
  147. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  148. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  149. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  150. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  151. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  152. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  153. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  154. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  155. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  156. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  157. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  158. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  159. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  160. package/docs/reference/authoring-surface/type-openof.md +12 -0
  161. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  162. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  163. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  164. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  165. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  166. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  167. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  168. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  169. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  170. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  171. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  172. package/docs/reference/authoring-surface/type-reached.md +12 -0
  173. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  174. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  175. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  176. package/docs/reference/authoring-surface/type-secret.md +25 -0
  177. package/docs/reference/authoring-surface/type-sender.md +19 -0
  178. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  179. package/docs/reference/authoring-surface/type-size.md +85 -0
  180. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  181. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  182. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  183. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  184. package/docs/reference/authoring-surface/type-text.md +19 -0
  185. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  186. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  187. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  188. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  189. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  190. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  191. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  192. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  193. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  194. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  195. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  196. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  197. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  198. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  199. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  200. package/docs/reference/authoring-surface/workspace.md +259 -0
  201. package/docs/reference/authoring-surface.md +197 -0
  202. package/docs/reference/cli.md +433 -0
  203. package/docs/reference/config.md +756 -0
  204. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  205. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  206. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  207. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  208. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  209. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  210. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  211. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  212. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  213. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  214. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  215. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  216. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  217. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  218. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  219. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  220. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  221. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  222. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  223. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  224. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  225. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  226. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  227. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  228. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  229. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  230. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  231. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  232. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  233. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  234. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  235. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  236. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  237. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  238. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  239. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  240. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  241. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  242. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  243. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  244. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  245. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  246. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  247. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  248. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  249. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  250. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  251. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  252. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  253. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  254. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  255. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  256. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  257. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  258. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  259. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  260. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  261. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  262. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  263. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  264. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  265. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  266. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  267. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  268. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  269. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  270. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  271. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  272. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  273. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  274. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  275. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  276. package/docs/reference/decisions.md +83 -0
  277. package/docs/reference/glossary.md +648 -0
  278. package/docs/reference/http-api.md +1348 -0
  279. package/docs/reference/machines.md +190 -0
  280. package/docs/reference/mechanism-events.md +72 -0
  281. package/docs/why.md +206 -0
  282. package/package.json +6 -4
  283. package/src/actor.ts +976 -61
  284. package/src/agent.ts +114 -38
  285. package/src/ambient.ts +4 -1
  286. package/src/assembly-checks.ts +347 -0
  287. package/src/assembly-tar.ts +494 -0
  288. package/src/assembly.ts +721 -0
  289. package/src/cause.ts +234 -0
  290. package/src/chart.ts +2182 -0
  291. package/src/charts.ts +748 -0
  292. package/src/config.ts +520 -162
  293. package/src/content-hash.ts +105 -0
  294. package/src/custodian.lua +6 -5
  295. package/src/custodian.ts +143 -39
  296. package/src/customize.ts +388 -55
  297. package/src/dispatch-effect.ts +945 -0
  298. package/src/door-compat.ts +217 -0
  299. package/src/durability.ts +11 -4
  300. package/src/effect-checks.ts +557 -0
  301. package/src/effect.ts +1202 -0
  302. package/src/effector-wire.ts +674 -0
  303. package/src/fail.ts +209 -0
  304. package/src/fingerprint.ts +4 -3
  305. package/src/gate.ts +8 -0
  306. package/src/git-clone.ts +65 -0
  307. package/src/git-push.ts +336 -0
  308. package/src/harness-client.ts +764 -48
  309. package/src/held-secrets.ts +82 -26
  310. package/src/holds.ts +156 -0
  311. package/src/http-effect.ts +1423 -0
  312. package/src/http.ts +916 -113
  313. package/src/images.ts +47 -21
  314. package/src/index.ts +62 -43
  315. package/src/instance.ts +505 -59
  316. package/src/internal.ts +84 -0
  317. package/src/kube-client.ts +333 -0
  318. package/src/machine-doc.ts +106 -6
  319. package/src/mechanism-routes.ts +168 -0
  320. package/src/model-keys.generated.ts +53 -0
  321. package/src/model-keys.ts +18 -58
  322. package/src/mount-digest.ts +53 -0
  323. package/src/names.ts +66 -8
  324. package/src/notices.ts +141 -0
  325. package/src/open-body.ts +1580 -0
  326. package/src/open-effect.ts +217 -0
  327. package/src/open.ts +59 -6
  328. package/src/parts.ts +709 -41
  329. package/src/party.ts +534 -0
  330. package/src/pick.ts +761 -0
  331. package/src/pool.ts +248 -37
  332. package/src/registration.ts +382 -19
  333. package/src/repo-fetch.ts +60 -85
  334. package/src/repos.ts +65 -75
  335. package/src/rest.ts +133 -0
  336. package/src/run-host.ts +2383 -115
  337. package/src/run-output.ts +38 -0
  338. package/src/sandbox-effect.ts +1126 -0
  339. package/src/sandbox-effector.ts +111 -0
  340. package/src/sandbox-kube.ts +1721 -0
  341. package/src/sandbox-watch.ts +260 -0
  342. package/src/secret-placement.ts +529 -0
  343. package/src/server.ts +284 -84
  344. package/src/setup.ts +819 -117
  345. package/src/size.ts +454 -0
  346. package/src/snapshot-store.ts +115 -20
  347. package/src/stub-harness.ts +49 -16
  348. package/src/sync-client.ts +54 -0
  349. package/src/timers.ts +191 -0
  350. package/src/tokens.ts +35 -4
  351. package/src/vocabulary.ts +135 -15
  352. package/src/wire.ts +238 -10
  353. package/src/workspace.ts +1195 -338
  354. package/src/sandbox-kubectl.ts +0 -1108
@@ -0,0 +1,922 @@
1
+ ---
2
+ title: Have your Assistant write a Machine
3
+ description: Ask your Assistant for a Machine, read its graph in the Console, then run it and approve the push.
4
+ ---
5
+
6
+ # Have your Assistant write a Machine
7
+
8
+ On this page, your [Assistant](../reference/glossary.md#assistant) writes a [Machine](../reference/glossary.md#machine)
9
+ for you. You read its graph before it runs. At the end, a [Workflow](../reference/glossary.md#workflow) named `fix`
10
+ fixes the bug in the tutorial fixture. It tests the fix, waits for you, and pushes the branch to your fork.
11
+
12
+ You do not write the Machine. You read it. An Assistant writes most Machines. It reads the jr2 docs that your
13
+ [Instance](../reference/glossary.md#instance) holds. Your work is to read the graph and to decide whether the work
14
+ happens in the correct way.
15
+
16
+ ## Before you start
17
+
18
+ - Do [An Agent in a Sandbox](an-agent-in-a-sandbox.md) first. This page uses its fork, its token and its
19
+ `workflows/task.ts`.
20
+ - You need an Assistant: Claude Code, Codex or Cursor.
21
+ - Keep the port forward to the [Console](../reference/glossary.md#console) from
22
+ [Create an Instance](create-an-instance.md). If it stopped, start it again:
23
+
24
+ ```sh
25
+ kubectl -n my-instance port-forward svc/jr2-orchestrator 4000:4000
26
+ ```
27
+
28
+ ## Give the token to jr2
29
+
30
+ On the last page, you pushed the branch by hand. On this page, a `push` [Effect](../reference/glossary.md#effect) pushes
31
+ it. The Effect uses the token that the [`git.credentials`](../reference/config.md#git-credentials) entry of
32
+ `jr2.config.ts` names. That entry names `JR2_GIT_TOKEN`.
33
+
34
+ Add the token from the last page to `.env`. Replace `<token>` with the token:
35
+
36
+ ```sh
37
+ echo 'JR2_GIT_TOKEN=<token>' >> .env
38
+ ```
39
+
40
+ The command prints nothing. `.env` is in `.gitignore`, so git does not keep the token.
41
+
42
+ > **Note**
43
+ >
44
+ > The [Agent](../reference/glossary.md#agent) never gets this token. The push Effect uses it, after you approve the work.
45
+ > The [Repo](../reference/glossary.md#repo) cache also uses it to clone your fork. See
46
+ > [Secrets and trust](../concepts/secrets-and-trust.md).
47
+
48
+ ## Open your Assistant in the Instance folder
49
+
50
+ Start your Assistant in `my-instance`. It reads `AGENTS.md` when it starts. Claude Code reads `CLAUDE.md`, which imports
51
+ `AGENTS.md`.
52
+
53
+ `jr2 init` wrote `AGENTS.md`. `jr2 up` keeps the text between its two markers equal to the docs of the
54
+ [Kit version](../reference/glossary.md#kit-version) of the Instance. Look at the start of that text:
55
+
56
+ ```sh
57
+ head -3 AGENTS.md
58
+ ```
59
+
60
+ ```text
61
+ <!-- jr2 up writes the text between the jr2-docs markers. Write your own instructions after the END marker. -->
62
+ <!-- BEGIN:jr2-docs -->
63
+ jr2 is newer than your training data. Before you write a Machine or change jr2.config.ts, read the bundled docs in node_modules/@jr2/orchestrator/docs/. They are the docs of this Instance's Kit version, <version>. Start with guides/write-a-workflow.md and with reference/authoring-surface.md, the contents page of the Authoring surface. Read one page with each call of your file tool, because a file tool cuts off a long output. Before you give a change to the user, run `npx --no jr2 check` in the Instance folder. It does the typecheck and the load of each Workflow that `jr2 up` does first, with no cluster, and it gives the same refusals.
64
+ ```
65
+
66
+ The block also holds an index of those docs. They are the docs of the Kit version that your `package.json` pins. Thus
67
+ your Assistant reads the API that your Instance runs, not the API that its training data holds.
68
+
69
+ ## Ask for the Machine
70
+
71
+ Give your Assistant this prompt. Replace `<you>` with your GitHub user name. Replace `<version>` with the Kit version.
72
+
73
+ ```text
74
+ Write a workflow named fix, in workflows/fix.ts. A coder Agent works in a Workspace on my fork, https://github.com/<you>/jr2.git, from the ref v<version>, in the Repo Slot target. It uses the model that workflows/task.ts uses. The run input is { prompt }, as for the task workflow. When the coder finishes, run node --test in docs/site/tutorial/greet in its Worktree. If the tests fail, send the output back to the coder. If they pass, a human reviews at a Gate named review, with the events request_changes (with notes) and approve. On request_changes, the coder continues with the notes. On approve, push the branch to my fork.
75
+ ```
76
+
77
+ Each Assistant writes a different file, and the same Assistant can write a different file on each try. This page cannot
78
+ show your file. It shows one result that typechecks, and it tells you what to look for in the graph. Your file can
79
+ differ in its names, its comments and its text, and still be correct. [Read one result](#read-one-result) walks through
80
+ it.
81
+
82
+ ## Converge the Instance
83
+
84
+ Run [`jr2 up`](../reference/cli.md#up):
85
+
86
+ ```sh
87
+ jr2 up
88
+ ```
89
+
90
+ `jr2 up` typechecks the Instance first. Then it loads each Workflow and walks its Machine. If your Assistant wrote a
91
+ Machine that does not compile, `jr2 up` stops before it builds anything:
92
+
93
+ ```text
94
+ …
95
+ typecheck: tsc --noEmit (the instance's own compiler)
96
+ refusing: this instance does not typecheck — nothing was built and nothing was applied
97
+ workflows/fix.ts(69,87): error TS2339: Property 'app' does not exist on type 'Record<"target", string>'.
98
+ ```
99
+
100
+ The lines after it are the errors of `tsc`. Here, the Machine reads a Repo Slot, `app`, that its Workspace does not
101
+ have. Give them to your Assistant. Then run `jr2 up` again.
102
+
103
+ The block tells your Assistant to run [`jr2 check`](../reference/cli.md#check) before it gives you the file. `jr2 check`
104
+ does these two steps of `jr2 up`, with no cluster, and gives the same refusals. Thus an Assistant that cannot run
105
+ `jr2 up` finds these errors itself.
106
+
107
+ When `fix` is correct, `jr2 up` tells you what the Machine runs outside itself, and who may send each event. Your lines
108
+ can differ when your Assistant used other names. Other Workflows of the Instance add lines too:
109
+
110
+ ```text
111
+ …
112
+ typecheck: tsc --noEmit (the instance's own compiler)
113
+ effects: workflow "fix" — what it runs outside itself (ADR-0067)
114
+ tests: command ["node","--test","docs/site/tutorial/greet/test/*.test.js"] — 300s, in the Harness
115
+ publish: push — 300s, in the Sandbox Effector
116
+ seats: workflow "fix" — who may send what, per state (ADR-0015)
117
+ fix.working agent:coder: [finish]
118
+ fix.review gate: [approve, request_changes]
119
+ …
120
+ Workspace of workflow "fix": Sandbox Effector (image the Sandbox Image) holds no Effect secret; pushes Repo Slot "target" with git.credentials "*"
121
+ …
122
+ warning: git.credentials "*": the Secret "jr2-git-ssh" does not exist in namespace my-instance — a push that needs it is refused at provision until it exists and jr2 up runs again (ADR-0051)
123
+ git.credentials "*": push Secret jr2-git-push-684888c0 — pushes over https
124
+ …
125
+ ```
126
+
127
+ Read these lines as a first check:
128
+
129
+ - `effects` shows two Effects: a command and a push. A Workflow that pushes with no
130
+ [Gate](../reference/glossary.md#gate) before the push shows the same lines. The graph shows where the push is.
131
+ - `seats` shows that the coder can send only `finish`. Only the Gate in `review` can send `approve` and
132
+ `request_changes`.
133
+ - The `warning` line is about git over ssh. The `jr2-git-ssh` Secret is for an ssh key, and it does not exist. Your fork
134
+ uses `https://`, so the push does not need it. The last line says that the push uses the token over https.
135
+
136
+ If `JR2_GIT_TOKEN` is not set, `jr2 up` stops before it applies the
137
+ [Orchestrator](../reference/glossary.md#orchestrator), with this error:
138
+
139
+ ```text
140
+ …
141
+ git.credentials "*": its token's variable JR2_GIT_TOKEN is not set — set it in .env
142
+ error: Workspace of workflow "fix": the push of Repo Slot "target" (https://github.com/<you>/jr2.git): git.credentials entry "*" cannot push — its token's variable JR2_GIT_TOKEN is not set — set it in .env (ADR-0051)
143
+ ```
144
+
145
+ ## Read the graph before you run it
146
+
147
+ This step is the most important step of the page. The Console draws a Machine as a graph, and you can read it before it
148
+ runs. Read how the work happens before you read the code that comes out of it.
149
+
150
+ Open the graph of `fix` in the Console:
151
+
152
+ ```text
153
+ http://localhost:4000/workflows/fix
154
+ ```
155
+
156
+ The Console shows the Machine with no run. The outer boxes are the states of the
157
+ [Workspace](../reference/glossary.md#workspace): `placing`, `provisioning`, `attaching`, `running`, `teardown`,
158
+ `failing` and `done`. jr2 makes them. The body of your Assistant's Machine is the box `▣ body`, inside `running`. Read
159
+ that box.
160
+
161
+ A box is a state. A line `⚙ <name>` in a box is what the state runs. An arrow is a transition. Its label is the event
162
+ that moves the run, and `[<name>]` is the guard that it must pass. Look for these facts:
163
+
164
+ 1. **The coder works in one state.** `working` runs `⚙ coder`. The arrow `finish` is the coder's only pick. The coder
165
+ cannot push, and it cannot approve its own work.
166
+ 2. **The tests decide.** `testing` runs `⚙ tests`. The arrow `done: testing [testsPassed]` goes to `review`. The other
167
+ `done: testing` arrow goes back to `working`. No arrow goes from `testing` to `pushing`.
168
+ 3. **You decide at the Gate.** `review` runs `⚙ gate`. Its arrows are `approve` and `request_changes`. The run waits
169
+ there until you send one of them.
170
+ 4. **The push comes only after `approve`.** `pushing` runs `⚙ publish`. The only arrow into `pushing` is `approve`.
171
+ 5. **Each failure has a place to go.** `agent.fault` goes from `working` to `review`. A push that fails goes back to
172
+ `review`. The line `↺ workspace.lost` in the `▣ body` box applies to every state of the body. The graph does not show
173
+ its target. In this file, it goes to `lost`, which invokes `fail`: the run ends `failed`.
174
+
175
+ Open **who may send what** in the pane of the graph. It shows the same seats as `jr2 up`:
176
+
177
+ ```text
178
+ who may send what · 2 seats
179
+ fix.working agent:coder finish
180
+ fix.review gate approve, request_changes
181
+ ```
182
+
183
+ An arrow can show `[?]` in place of a guard name. That guard is a function with no name. Read the guard in the file. You
184
+ can also ask your Assistant to give it a name.
185
+
186
+ Also look for a loop with no exit. In this result, `working` and `testing` can loop until the tests pass, and no count
187
+ stops them. If you want a limit, ask your Assistant for one. Then read the graph again.
188
+
189
+ ## Run fix
190
+
191
+ Start the run in your first terminal. The prompt is the task text from the last page:
192
+
193
+ ```sh
194
+ jr2 run fix --input '{"prompt":"In docs/site/tutorial/greet, one test fails. Fix the bug in docs/site/tutorial/greet/src/greet.js. To check your fix, run node --test in docs/site/tutorial/greet. Do not change files outside that folder. Do not run the tests of the repo root."}'
195
+ ```
196
+
197
+ [`jr2 run`](../reference/cli.md#run) stays attached until the run ends. Your run id is different:
198
+
199
+ ```text
200
+ …
201
+ → context kind-jr2 / namespace my-instance
202
+ run b22cef63-1523-45f3-9c51-36c54b79ce36 (fix) — attached
203
+ → active "placing"
204
+ → active "provisioning"
205
+ → active "attaching"
206
+ → active "running"
207
+ …
208
+ ```
209
+
210
+ Watch the run in the Console. The Console lights the state that the run is in. The run goes through `working` and
211
+ `testing`, and it can go back to `working` when the tests fail. Then it waits at `review`.
212
+
213
+ ## Read the Gate
214
+
215
+ In a second terminal, read the Gate. Replace `<run id>` with the run id from the first terminal, or with its first 4
216
+ characters or more:
217
+
218
+ ```sh
219
+ jr2 status <run id>
220
+ ```
221
+
222
+ [`jr2 status`](../reference/cli.md#status) prints the status of the run as one line of JSON. Its `gates` list holds the
223
+ Gate of `review`. Its `gate` is `body.review`. Give that value to `--gate`. The `meta` of the Gate gives the coder's
224
+ `summary`, the `branch` and the `worktree`:
225
+
226
+ ```text
227
+ …
228
+ → context kind-jr2 / namespace my-instance
229
+ {…,"gates":[{"gate":"body.review",…,"meta":{"summary":"Fixed the \"trims the name\" test failure in docs/site/tutorial/greet/src/greet.js by adding .trim() to the name parsing. The name \" ada \" was not being trimmed, causing the greeting to include extra whitespace. Changed `args.find((a) => !a.startsWith(\"-\")) ?? \"world\"` to `(args.find((a) => !a.startsWith(\"-\")) ?? \"world\").trim()`.","branch":"jr2/fix-336ae477-2a46-49c2-b142-b81e705bb3f8","worktree":"/work/target/jr2-fix-336ae477-2a46-49c2-b142-b81e705bb3f8"}}]}
230
+ ```
231
+
232
+ The `summary` is in the words of the model, so your summary is different.
233
+
234
+ ## Approve
235
+
236
+ Send `approve` to the Gate with [`jr2 send`](../reference/cli.md#send):
237
+
238
+ ```sh
239
+ jr2 send <run id> --gate body.review --event approve
240
+ ```
241
+
242
+ ```text
243
+ …
244
+ → context kind-jr2 / namespace my-instance
245
+ delivered approve to gate "body.review" on b22cef63-1523-45f3-9c51-36c54b79ce36
246
+ ```
247
+
248
+ The run goes to `pushing`, and the push Effect pushes the branch. Then the run ends, and jr2 removes the
249
+ [Sandbox](../reference/glossary.md#sandbox). In the first terminal, `jr2 run` prints its last line, then the final
250
+ status as one line of JSON:
251
+
252
+ ```text
253
+ …
254
+ → active "teardown"
255
+ → done "done"
256
+ {"runId":"b22cef63-1523-45f3-9c51-36c54b79ce36","workflow":"fix",…,"output":{"outcome":"pushed","branch":"jr2/fix-336ae477-2a46-49c2-b142-b81e705bb3f8","commit":"03f8800ec105c98f612bd2c55687bb92db80357a"}}
257
+ ```
258
+
259
+ In that JSON, `context.output` and `output` are the output of `fix`. In this result, it is
260
+ `{"outcome":"pushed","branch":"jr2/fix-<instance id>","commit":"<commit>"}`. `<instance id>` is the seed
261
+ [Instance ID](../reference/glossary.md#instance-id) of the run. It is not the run id. `<commit>` is the commit on your
262
+ fork.
263
+
264
+ ## Find the branch on your fork
265
+
266
+ List the branches of `fix` on your fork:
267
+
268
+ ```sh
269
+ git ls-remote https://github.com/<you>/jr2.git 'jr2/fix-*'
270
+ ```
271
+
272
+ ```text
273
+ <commit> refs/heads/jr2/fix-<instance id>
274
+ ```
275
+
276
+ You can also open the branch on GitHub. The commit holds the coder's fix of `docs/site/tutorial/greet/src/greet.js`.
277
+
278
+ ## Read one result
279
+
280
+ This is one `fix.ts` that does what the prompt asks, with the model of the tab:
281
+
282
+ **Anthropic**
283
+
284
+ ```ts
285
+ import { assign } from "xstate";
286
+ import { z } from "zod";
287
+ import {
288
+ agent,
289
+ command,
290
+ defineEvent,
291
+ fail,
292
+ jr2Setup,
293
+ push,
294
+ workspace,
295
+ type CommandOutput,
296
+ type HostInjectedInput,
297
+ type PushOutput,
298
+ type Workspaced,
299
+ } from "@jr2/orchestrator";
300
+
301
+ // The run input: what you give `jr2 run fix --input`. It has the `prompt` of the run input of `task`.
302
+ const door = z.object({
303
+ prompt: z.string().describe("What to do, in prose. The coder's first Turn is this prompt."),
304
+ });
305
+ type Door = z.infer<typeof door>;
306
+
307
+ // The coder's one pick. It is a tool on the coder's Menu.
308
+ const finish = defineEvent({
309
+ name: "finish",
310
+ description: "End the turn: the fix is committed on the branch and ready for the tests.",
311
+ input: z.object({ summary: z.string().describe("What you did, in a few sentences.") }),
312
+ });
313
+ // Your two replies at the `review` Gate.
314
+ const approve = defineEvent({
315
+ name: "approve",
316
+ description: "Accept the work. jr2 pushes the branch to the fork.",
317
+ input: z.object({}),
318
+ });
319
+ const requestChanges = defineEvent({
320
+ name: "request_changes",
321
+ description: "Send the work back to the coder with notes. The coder continues the same conversation.",
322
+ input: z.object({ notes: z.string().describe("What to change, in prose.") }),
323
+ });
324
+
325
+ // The body gets the run input, the seed Instance ID of the run, and the Workspace handles of its one Repo Slot.
326
+ type BodyInput = Workspaced<Door & HostInjectedInput, "target">;
327
+ type Context = BodyInput & {
328
+ // The next Turn is the first of its conversation: it must carry the whole task.
329
+ fresh: boolean;
330
+ // The text of the next Turn after the first: the failed tests, or your notes.
331
+ feedback?: string;
332
+ // The coder's summary of its last Turn, for the Gate.
333
+ summary?: string;
334
+ // Why the run is at the Gate with no summary: a Turn that failed, or a push that did not happen.
335
+ reason?: string;
336
+ // The commit on the fork, after the push.
337
+ commit?: string;
338
+ };
339
+ // The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
340
+ type Output = { outcome: "pushed"; branch: string; commit: string };
341
+
342
+ // Two guards. A guard with a name shows that name on its arrow in the Console's graph.
343
+ function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
344
+ return event.output.outcome === "exited" && event.output.code === 0;
345
+ }
346
+ function pushed({ event }: { event: { output: PushOutput } }): boolean {
347
+ return event.output.outcome === "pushed";
348
+ }
349
+
350
+ const body = jr2Setup({
351
+ types: {} as { context: Context; input: BodyInput; output: Output },
352
+ events: [finish, approve, requestChanges],
353
+ actors: {
354
+ coder: agent({
355
+ model: "anthropic/claude-haiku-5-5",
356
+ description: "Fixes one bug in its own Workspace, and commits the fix.",
357
+ workspace: "write",
358
+ instructions: `You are a software engineer. You fix one bug, in a container of your own.
359
+
360
+ - Work only in the worktree that your turn names. Your tools are rooted there.
361
+ - Commit your fix on the branch. You cannot push. jr2 pushes the branch after a human approves it.
362
+ - Make the smallest change that fixes the bug.
363
+ - End your turn with exactly one call to \`finish\`.`,
364
+ }),
365
+ // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
366
+ tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
367
+ // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
368
+ publish: push(),
369
+ // Fail the run: the end for work that did not succeed. A final state means `done`.
370
+ fail: fail(),
371
+ },
372
+ }).createMachine({
373
+ id: "fix",
374
+ context: ({ input }) => ({ ...input, fresh: true }),
375
+ initial: "working",
376
+ // The Sandbox is gone, and every commit that was not pushed went with it.
377
+ on: { "workspace.lost": { target: ".lost" } },
378
+ states: {
379
+ working: {
380
+ invoke: {
381
+ src: "coder",
382
+ input: ({ context }) => ({
383
+ prompt: promptOf(context),
384
+ cwd: context.workspace.repos.target,
385
+ // Each Turn continues the coder's one conversation, so the coder remembers its work.
386
+ continue: true,
387
+ }),
388
+ },
389
+ on: {
390
+ finish: {
391
+ target: "testing",
392
+ actions: assign({
393
+ summary: ({ event }) => event.summary,
394
+ feedback: undefined,
395
+ reason: undefined,
396
+ fresh: false,
397
+ }),
398
+ },
399
+ // jr2 could not get a pick from the coder. A human decides at the Gate.
400
+ "agent.fault": {
401
+ target: "review",
402
+ actions: assign({ reason: ({ event }) => event.reason, summary: undefined, fresh: true }),
403
+ },
404
+ },
405
+ },
406
+ testing: {
407
+ invoke: {
408
+ src: "tests",
409
+ onDone: [
410
+ { guard: testsPassed, target: "review" },
411
+ {
412
+ target: "working",
413
+ actions: assign({ feedback: ({ event }) => testFeedback(event.output) }),
414
+ },
415
+ ],
416
+ },
417
+ },
418
+ // The run waits here for you. Its Gate id is body.review: the Workspace runs this Machine as body.
419
+ review: {
420
+ invoke: {
421
+ src: "gate",
422
+ input: ({ context }) => ({
423
+ meta: {
424
+ ...(context.summary !== undefined ? { summary: context.summary } : {}),
425
+ ...(context.reason !== undefined ? { reason: context.reason } : {}),
426
+ branch: context.workspace.branch,
427
+ worktree: context.workspace.repos.target,
428
+ },
429
+ }),
430
+ },
431
+ on: {
432
+ approve: { target: "pushing" },
433
+ request_changes: {
434
+ target: "working",
435
+ actions: assign({
436
+ feedback: ({ event }) => `A human reviewed your work and asks for changes:\n${event.notes}`,
437
+ summary: undefined,
438
+ }),
439
+ },
440
+ },
441
+ },
442
+ pushing: {
443
+ invoke: {
444
+ src: "publish",
445
+ onDone: [
446
+ {
447
+ guard: pushed,
448
+ target: "done",
449
+ actions: assign({ commit: ({ event }) => publishedOf(event.output) }),
450
+ },
451
+ // Nothing was pushed, or jr2 cannot tell. The Sandbox stays, and the run waits at the Gate again.
452
+ { target: "review", actions: assign({ reason: ({ event }) => pushFailure(event.output) }) },
453
+ ],
454
+ },
455
+ },
456
+ done: {
457
+ type: "final",
458
+ output: ({ context }): Output => ({
459
+ outcome: "pushed",
460
+ branch: context.workspace.branch,
461
+ commit: context.commit ?? "",
462
+ }),
463
+ },
464
+ // Not a final state: the run ends `failed`, and jr2 deletes the Sandbox.
465
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
466
+ },
467
+ // The output of the run. xstate reads only this root `output` as the output of the Machine. It
468
+ // gets the output of the final state that ended the run as `event.output`.
469
+ output: ({ event }) => event.output as Output,
470
+ });
471
+
472
+ // The text of each Turn. The first Turn of a conversation carries the task. Each later Turn carries only what is new.
473
+ function promptOf(context: Context): string {
474
+ const feedback = context.feedback ?? "";
475
+ if (!context.fresh) return feedback || "Continue.";
476
+ const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
477
+ return (
478
+ `${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
479
+ `Work in ${context.workspace.repos.target}, on the branch ${context.workspace.branch}. ` +
480
+ "Commit your fix there, then call finish."
481
+ );
482
+ }
483
+
484
+ // What the coder reads when the tests fail: the end of the output of `node --test`.
485
+ function testFeedback(output: CommandOutput): string {
486
+ const text = output.outcome === "unknown" ? output.reason : output.tail;
487
+ return (
488
+ `The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
489
+ "Fix the code, commit, then call finish."
490
+ );
491
+ }
492
+
493
+ // The commit that the fork has now.
494
+ function publishedOf(output: PushOutput): string {
495
+ return output.outcome === "pushed" ? output.published : "";
496
+ }
497
+
498
+ // Why the push did not happen, in the words of jr2 and git.
499
+ function pushFailure(output: PushOutput): string {
500
+ return output.outcome === "pushed" ? "" : `The push did not happen (${output.outcome}): ${output.reason}`;
501
+ }
502
+
503
+ export const machine = workspace(body, {
504
+ input: door,
505
+ repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
506
+ // One branch for each run, from its seed Instance ID: `input.instanceId`.
507
+ spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
508
+ });
509
+ ```
510
+
511
+ **Local**
512
+
513
+ ```ts
514
+ import { assign } from "xstate";
515
+ import { z } from "zod";
516
+ import {
517
+ agent,
518
+ command,
519
+ defineEvent,
520
+ fail,
521
+ jr2Setup,
522
+ push,
523
+ workspace,
524
+ type CommandOutput,
525
+ type HostInjectedInput,
526
+ type PushOutput,
527
+ type Workspaced,
528
+ } from "@jr2/orchestrator";
529
+
530
+ // The run input: what you give `jr2 run fix --input`. It has the `prompt` of the run input of `task`.
531
+ const door = z.object({
532
+ prompt: z.string().describe("What to do, in prose. The coder's first Turn is this prompt."),
533
+ });
534
+ type Door = z.infer<typeof door>;
535
+
536
+ // The coder's one pick. It is a tool on the coder's Menu.
537
+ const finish = defineEvent({
538
+ name: "finish",
539
+ description: "End the turn: the fix is committed on the branch and ready for the tests.",
540
+ input: z.object({ summary: z.string().describe("What you did, in a few sentences.") }),
541
+ });
542
+ // Your two replies at the `review` Gate.
543
+ const approve = defineEvent({
544
+ name: "approve",
545
+ description: "Accept the work. jr2 pushes the branch to the fork.",
546
+ input: z.object({}),
547
+ });
548
+ const requestChanges = defineEvent({
549
+ name: "request_changes",
550
+ description: "Send the work back to the coder with notes. The coder continues the same conversation.",
551
+ input: z.object({ notes: z.string().describe("What to change, in prose.") }),
552
+ });
553
+
554
+ // The body gets the run input, the seed Instance ID of the run, and the Workspace handles of its one Repo Slot.
555
+ type BodyInput = Workspaced<Door & HostInjectedInput, "target">;
556
+ type Context = BodyInput & {
557
+ // The next Turn is the first of its conversation: it must carry the whole task.
558
+ fresh: boolean;
559
+ // The text of the next Turn after the first: the failed tests, or your notes.
560
+ feedback?: string;
561
+ // The coder's summary of its last Turn, for the Gate.
562
+ summary?: string;
563
+ // Why the run is at the Gate with no summary: a Turn that failed, or a push that did not happen.
564
+ reason?: string;
565
+ // The commit on the fork, after the push.
566
+ commit?: string;
567
+ };
568
+ // The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
569
+ type Output = { outcome: "pushed"; branch: string; commit: string };
570
+
571
+ // Two guards. A guard with a name shows that name on its arrow in the Console's graph.
572
+ function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
573
+ return event.output.outcome === "exited" && event.output.code === 0;
574
+ }
575
+ function pushed({ event }: { event: { output: PushOutput } }): boolean {
576
+ return event.output.outcome === "pushed";
577
+ }
578
+
579
+ const body = jr2Setup({
580
+ types: {} as { context: Context; input: BodyInput; output: Output },
581
+ events: [finish, approve, requestChanges],
582
+ actors: {
583
+ coder: agent({
584
+ model: "local/qwen3.6-35b-a3b",
585
+ description: "Fixes one bug in its own Workspace, and commits the fix.",
586
+ workspace: "write",
587
+ instructions: `You are a software engineer. You fix one bug, in a container of your own.
588
+
589
+ - Work only in the worktree that your turn names. Your tools are rooted there.
590
+ - Commit your fix on the branch. You cannot push. jr2 pushes the branch after a human approves it.
591
+ - Make the smallest change that fixes the bug.
592
+ - End your turn with exactly one call to \`finish\`.`,
593
+ }),
594
+ // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
595
+ tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
596
+ // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
597
+ publish: push(),
598
+ // Fail the run: the end for work that did not succeed. A final state means `done`.
599
+ fail: fail(),
600
+ },
601
+ }).createMachine({
602
+ id: "fix",
603
+ context: ({ input }) => ({ ...input, fresh: true }),
604
+ initial: "working",
605
+ // The Sandbox is gone, and every commit that was not pushed went with it.
606
+ on: { "workspace.lost": { target: ".lost" } },
607
+ states: {
608
+ working: {
609
+ invoke: {
610
+ src: "coder",
611
+ input: ({ context }) => ({
612
+ prompt: promptOf(context),
613
+ cwd: context.workspace.repos.target,
614
+ // Each Turn continues the coder's one conversation, so the coder remembers its work.
615
+ continue: true,
616
+ }),
617
+ },
618
+ on: {
619
+ finish: {
620
+ target: "testing",
621
+ actions: assign({
622
+ summary: ({ event }) => event.summary,
623
+ feedback: undefined,
624
+ reason: undefined,
625
+ fresh: false,
626
+ }),
627
+ },
628
+ // jr2 could not get a pick from the coder. A human decides at the Gate.
629
+ "agent.fault": {
630
+ target: "review",
631
+ actions: assign({ reason: ({ event }) => event.reason, summary: undefined, fresh: true }),
632
+ },
633
+ },
634
+ },
635
+ testing: {
636
+ invoke: {
637
+ src: "tests",
638
+ onDone: [
639
+ { guard: testsPassed, target: "review" },
640
+ {
641
+ target: "working",
642
+ actions: assign({ feedback: ({ event }) => testFeedback(event.output) }),
643
+ },
644
+ ],
645
+ },
646
+ },
647
+ // The run waits here for you. Its Gate id is body.review: the Workspace runs this Machine as body.
648
+ review: {
649
+ invoke: {
650
+ src: "gate",
651
+ input: ({ context }) => ({
652
+ meta: {
653
+ ...(context.summary !== undefined ? { summary: context.summary } : {}),
654
+ ...(context.reason !== undefined ? { reason: context.reason } : {}),
655
+ branch: context.workspace.branch,
656
+ worktree: context.workspace.repos.target,
657
+ },
658
+ }),
659
+ },
660
+ on: {
661
+ approve: { target: "pushing" },
662
+ request_changes: {
663
+ target: "working",
664
+ actions: assign({
665
+ feedback: ({ event }) => `A human reviewed your work and asks for changes:\n${event.notes}`,
666
+ summary: undefined,
667
+ }),
668
+ },
669
+ },
670
+ },
671
+ pushing: {
672
+ invoke: {
673
+ src: "publish",
674
+ onDone: [
675
+ {
676
+ guard: pushed,
677
+ target: "done",
678
+ actions: assign({ commit: ({ event }) => publishedOf(event.output) }),
679
+ },
680
+ // Nothing was pushed, or jr2 cannot tell. The Sandbox stays, and the run waits at the Gate again.
681
+ { target: "review", actions: assign({ reason: ({ event }) => pushFailure(event.output) }) },
682
+ ],
683
+ },
684
+ },
685
+ done: {
686
+ type: "final",
687
+ output: ({ context }): Output => ({
688
+ outcome: "pushed",
689
+ branch: context.workspace.branch,
690
+ commit: context.commit ?? "",
691
+ }),
692
+ },
693
+ // Not a final state: the run ends `failed`, and jr2 deletes the Sandbox.
694
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
695
+ },
696
+ // The output of the run. xstate reads only this root `output` as the output of the Machine. It
697
+ // gets the output of the final state that ended the run as `event.output`.
698
+ output: ({ event }) => event.output as Output,
699
+ });
700
+
701
+ // The text of each Turn. The first Turn of a conversation carries the task. Each later Turn carries only what is new.
702
+ function promptOf(context: Context): string {
703
+ const feedback = context.feedback ?? "";
704
+ if (!context.fresh) return feedback || "Continue.";
705
+ const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
706
+ return (
707
+ `${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
708
+ `Work in ${context.workspace.repos.target}, on the branch ${context.workspace.branch}. ` +
709
+ "Commit your fix there, then call finish."
710
+ );
711
+ }
712
+
713
+ // What the coder reads when the tests fail: the end of the output of `node --test`.
714
+ function testFeedback(output: CommandOutput): string {
715
+ const text = output.outcome === "unknown" ? output.reason : output.tail;
716
+ return (
717
+ `The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
718
+ "Fix the code, commit, then call finish."
719
+ );
720
+ }
721
+
722
+ // The commit that the fork has now.
723
+ function publishedOf(output: PushOutput): string {
724
+ return output.outcome === "pushed" ? output.published : "";
725
+ }
726
+
727
+ // Why the push did not happen, in the words of jr2 and git.
728
+ function pushFailure(output: PushOutput): string {
729
+ return output.outcome === "pushed" ? "" : `The push did not happen (${output.outcome}): ${output.reason}`;
730
+ }
731
+
732
+ export const machine = workspace(body, {
733
+ input: door,
734
+ repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
735
+ // One branch for each run, from its seed Instance ID: `input.instanceId`.
736
+ spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
737
+ });
738
+ ```
739
+
740
+ Read it in the order of the graph. Each part below links to its entry in the Reference.
741
+
742
+ **The events.** Each [`defineEvent`](../reference/authoring-surface/defineevent.md) is an event that the Machine takes.
743
+ `finish` is in a state that invokes the coder, so it is a tool on the coder's [Menu](../reference/glossary.md#menu).
744
+ `approve` and `request_changes` are in a state that invokes the Gate, so only the Gate takes them. This file sets no
745
+ `audience` on its events, so the place of each event says who sends it. The `defineEvent` field `audience` can change
746
+ that. The seats show the result.
747
+
748
+ ```ts
749
+ // The coder's one pick. It is a tool on the coder's Menu.
750
+ const finish = defineEvent({
751
+ name: "finish",
752
+ description: "End the turn: the fix is committed on the branch and ready for the tests.",
753
+ input: z.object({ summary: z.string().describe("What you did, in a few sentences.") }),
754
+ });
755
+ // Your two replies at the `review` Gate.
756
+ const approve = defineEvent({
757
+ name: "approve",
758
+ description: "Accept the work. jr2 pushes the branch to the fork.",
759
+ input: z.object({}),
760
+ });
761
+ const requestChanges = defineEvent({
762
+ name: "request_changes",
763
+ description: "Send the work back to the coder with notes. The coder continues the same conversation.",
764
+ input: z.object({ notes: z.string().describe("What to change, in prose.") }),
765
+ });
766
+ ```
767
+
768
+ **The Effects.** `tests` and `publish` are actor slots, as the coder is. A state runs one when it invokes it.
769
+ [`command()`](../reference/authoring-surface/command.md) takes a list of arguments, not a line of shell text. It runs in
770
+ the [Worktree](../reference/glossary.md#worktree) of the one [Repo Slot](../reference/glossary.md#repo-slot). A command
771
+ runs in the root of the Worktree, so `tests` names the test files by their path from that root.
772
+ [`push()`](../reference/authoring-surface/push.md) pushes the tip of the coder's branch.
773
+ [`fail()`](../reference/authoring-surface/fail.md) is a Part, not an Effect. The `lost` state invokes it to end the run
774
+ `failed`, because a final state means `done`.
775
+
776
+ ```ts
777
+ // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
778
+ tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
779
+ // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
780
+ publish: push(),
781
+ // Fail the run: the end for work that did not succeed. A final state means `done`.
782
+ fail: fail(),
783
+ ```
784
+
785
+ **The coder's state.** `working` invokes the coder. Each [Turn](../reference/glossary.md#turn) gets a prompt and the
786
+ folder to work in. `continue: true` keeps one conversation, so the coder remembers its last Turn. `finish` is the
787
+ coder's pick: the run goes to `testing`. [`agent.fault`](../reference/mechanism-events.md#agent-fault) is an event from
788
+ jr2, when it cannot get a pick from the coder.
789
+
790
+ ```ts
791
+ working: {
792
+ invoke: {
793
+ src: "coder",
794
+ input: ({ context }) => ({
795
+ prompt: promptOf(context),
796
+ cwd: context.workspace.repos.target,
797
+ // Each Turn continues the coder's one conversation, so the coder remembers its work.
798
+ continue: true,
799
+ }),
800
+ },
801
+ on: {
802
+ finish: {
803
+ target: "testing",
804
+ actions: assign({
805
+ summary: ({ event }) => event.summary,
806
+ feedback: undefined,
807
+ reason: undefined,
808
+ fresh: false,
809
+ }),
810
+ },
811
+ // jr2 could not get a pick from the coder. A human decides at the Gate.
812
+ "agent.fault": {
813
+ target: "review",
814
+ actions: assign({ reason: ({ event }) => event.reason, summary: undefined, fresh: true }),
815
+ },
816
+ },
817
+ },
818
+ ```
819
+
820
+ **The tests.** Every outcome of a command is output. A failed test is output too. The `onDone` list routes on that
821
+ output. The first transition whose guard passes is the one that the run takes. The second transition has no guard, so it
822
+ catches every other outcome. See [`CommandOutput`](../reference/authoring-surface/type-commandoutput.md).
823
+
824
+ ```ts
825
+ testing: {
826
+ invoke: {
827
+ src: "tests",
828
+ onDone: [
829
+ { guard: testsPassed, target: "review" },
830
+ {
831
+ target: "working",
832
+ actions: assign({ feedback: ({ event }) => testFeedback(event.output) }),
833
+ },
834
+ ],
835
+ },
836
+ },
837
+ ```
838
+
839
+ **The Gate.** `review` invokes `gate`. jr2 makes the id of the Gate from the invoke ids. `workspace()` runs your Machine
840
+ as `body`, and the state is `review`, so the id is `body.review`. You give `--gate body.review` to `jr2 send`. See
841
+ [the id of a Gate](../concepts/gates.md#the-id). `meta` is what `jr2 status` and the Console show you at the Gate.
842
+
843
+ ```ts
844
+ // The run waits here for you. Its Gate id is body.review: the Workspace runs this Machine as body.
845
+ review: {
846
+ invoke: {
847
+ src: "gate",
848
+ input: ({ context }) => ({
849
+ meta: {
850
+ ...(context.summary !== undefined ? { summary: context.summary } : {}),
851
+ ...(context.reason !== undefined ? { reason: context.reason } : {}),
852
+ branch: context.workspace.branch,
853
+ worktree: context.workspace.repos.target,
854
+ },
855
+ }),
856
+ },
857
+ on: {
858
+ approve: { target: "pushing" },
859
+ request_changes: {
860
+ target: "working",
861
+ actions: assign({
862
+ feedback: ({ event }) => `A human reviewed your work and asks for changes:\n${event.notes}`,
863
+ summary: undefined,
864
+ }),
865
+ },
866
+ },
867
+ },
868
+ ```
869
+
870
+ **The push.** `pushing` invokes `publish`. A push that fails is output too: `rejected`, `diverged` or `unknown`. Here it
871
+ goes back to `review`, and the Sandbox stays. If the run went to a final state, jr2 would remove the Sandbox and the
872
+ commits in it. See [`PushOutput`](../reference/authoring-surface/type-pushoutput.md).
873
+
874
+ ```ts
875
+ pushing: {
876
+ invoke: {
877
+ src: "publish",
878
+ onDone: [
879
+ {
880
+ guard: pushed,
881
+ target: "done",
882
+ actions: assign({ commit: ({ event }) => publishedOf(event.output) }),
883
+ },
884
+ // Nothing was pushed, or jr2 cannot tell. The Sandbox stays, and the run waits at the Gate again.
885
+ { target: "review", actions: assign({ reason: ({ event }) => pushFailure(event.output) }) },
886
+ ],
887
+ },
888
+ },
889
+ ```
890
+
891
+ **The Workspace.** [`workspace()`](../reference/authoring-surface/workspace.md) runs the body in a Sandbox. `input` is
892
+ the schema of the run input that `jr2 run --input` sends. `repos` binds the Repo Slot `target` to your fork, at the
893
+ release tag. `spec` gives the branch. The input of `spec` also holds `instanceId`, an id that jr2 makes for each run, so
894
+ each run gets a branch of its own.
895
+
896
+ ```ts
897
+ export const machine = workspace(body, {
898
+ input: door,
899
+ repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
900
+ // One branch for each run, from its seed Instance ID: `input.instanceId`.
901
+ spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
902
+ });
903
+ ```
904
+
905
+ ## What happened
906
+
907
+ - Your Assistant wrote a Machine from the docs that your Instance holds. `jr2 up` keeps the index of those docs in
908
+ `AGENTS.md`, so the docs match the Kit version of the Instance.
909
+ - `jr2 up` typechecked the Machine and walked it before it built anything. It showed the Effects and the seats.
910
+ - You read the graph before the run. The graph showed that the push comes only after your `approve`.
911
+ - A `command` Effect ran the tests in the Sandbox, and a `push` Effect pushed the branch. Each Effect runs in a state of
912
+ the graph, so you can see where it runs. See [Effects](../concepts/effects.md).
913
+ - The coder and you sent different events. In this Machine, the place of each event decides who sends it. See
914
+ [The Menu](../concepts/the-menu.md) and [Gates](../concepts/gates.md).
915
+
916
+ To change a Machine with your Assistant on kind, see
917
+ [Iterate on a Machine on kind](../guides/iterate-on-a-machine-on-kind.md). The decision about the docs in the Kit is
918
+ [ADR-0072](../reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md).
919
+
920
+ ## Next
921
+
922
+ Read [Machines and Workflows](../concepts/machines-and-workflows.md).