@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
@@ -0,0 +1,458 @@
1
+ # An Effect is an actor slot, and runs at most once unless marked idempotent
2
+
3
+ An Effect is work a Machine does outside itself. A Machine already runs Agents and Gates as actors (ADR-0049); work such
4
+ as running the tests had no place of its own, so an author either asked an Agent to do it (and trusted its word) or
5
+ wrote a raw xstate promise that a restart ran again. jr2 builds four kinds: `command`, run a program in the Workspace;
6
+ `http`, call a URL — with a JSON or text body, or a Worktree file as its body; `push`, push a Worktree's branch to its
7
+ Repo Slot's Binding; and `dispatch`, start a run of a registered Workflow, on this Orchestrator or another's
8
+ ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)). A fifth, `worktree()`, is a kit Effect on the
9
+ `command` machinery that cuts a Worktree ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
10
+ An Effect that holds a secret runs in an Effector
11
+ ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). A `command` whose env names a secret —
12
+ the credentialed `command` — runs there too, its child confined (below). A `dispatch` with no target runs in the
13
+ Orchestrator's own process, the one Effect that does: it starts the run through the run host and holds no secret.
14
+
15
+ ## Decision
16
+
17
+ - **An Effect is an actor slot beside `agent()`.** `actors: { tests: command(["pnpm", "test"]) }`, invoked as
18
+ `src: "tests"`, with an `onDone` that routes on its output. xstate gives the rest: the Effect stops when its state
19
+ exits, its input persists with the snapshot, a `src` typo is a compile error, and `.provide()` is the unit-test seam.
20
+ The slot carries its parts as plain data — kind, placement, timeout; a `command`'s argv and the secrets its env names;
21
+ an `http` Effect's method, fixed host, the names of the secrets it names and whether its body is a Worktree file; a
22
+ `push`'s `pin`, `sign`, `squash`, `force` and `slot`; a cut's `expect` — which `jr2 up` prints and the Machine doc
23
+ carries, read without running anything. A packaged Machine may leave an `http` url (whatever its body) or a
24
+ `command`'s argv Open (`open` in its place), and the name of one secret its auth, a header or a command's env names
25
+ (`secret(open)`; never in a url), for the composer to bind once with
26
+ `customize(machine, { effects: { <slot>: { url } | { argv } | { secret } } })` — one object with both keys for an
27
+ Effect with two Open parts ([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md));
28
+ the slot then carries the Effect built again with the bound parts, and an Effect still Open runs nothing: its state's
29
+ `onError`. An Effect written inline (`invoke: { src: http({...}) }`) is refused at build and at load, with the slot
30
+ form in the message: xstate gives it a generated key that moves when a state is renamed, so the ledger, the report and
31
+ the Machine doc would name it by a number, and `customize()` — `provide`, keyed on `actors` — could not reach it. The
32
+ same rule holds for `agent()` and `workspace()` (ADR-0049): a Part is a key of `actors`, and every Effect kind is one
33
+ (the brand `isEffect` reads).
34
+ - **Between a pick and its work: `fromPick`, `paramsOf`, `answer`.** All optional; a plain mapper is legal.
35
+ `fromPick(def, fill)` is the `input` of a state a pick enters: the pick's params flow into the Effect's input by name,
36
+ `fill` returns only what the Machine adds, and the sum is checked against the Effect's input type — the security
37
+ boundary in one expression (what the Agent supplies, what the Machine adds). The type check catches a missing or
38
+ mistyped key; it accepts an extra one, so the load walk refuses a param that is no key of the input (below). An Open
39
+ argv's input is one string per key, so the type check catches neither for it: a slot the bound argv names that the
40
+ input does not supply faults when the Effect starts (ADR-0054). The state a pick enters is a child of the Agent's
41
+ state when the Agent must read the Effect's output as its answer, or a sibling when the pick ends the Agent's work
42
+ (the Turn ends before the Effect runs, and no Agent reads the output). A child state is necessary but not sufficient:
43
+ the route after the Effect must also stay in the Agent's state (`onDone: "ready"`), because a route that leaves it
44
+ ends the Turn when the Effect ends, and no Agent reads the output. The `fromPick` docs show both shapes, the child one
45
+ with a route back to `ready` and a `finish` pick, because one shape alone left the author to guess the other. The
46
+ alternative, a child state whose `onDone` leaves, is rejected for the example: it behaves almost as the sibling does,
47
+ and an author who copies it to see a result gets nothing. `paramsOf(event, def)` reads the same params for a plain
48
+ mapper. `answer(({ context, event, chain }) => …)` is an xstate action that sets what the Agent reads when its pick is
49
+ answered (ADR-0024); `chain` is every reply since the pick, oldest first, each a link typed by its slot
50
+ (`{ kind: "output", src, output }`) or by the Machine's events (`{ kind: "event", event }`); its order and length are
51
+ not typed. A link is a reply from outside the Machine; the events it sent itself are skipped. The value is JSON on the
52
+ wire and in the hold; one with no JSON form sets nothing. An `answer` shapes only the pick of the Agent whose state it
53
+ is written below (on a state's entry or exit, or on a transition of a state, at or below that Agent's state). On a
54
+ transition below no Agent's state it shapes the pick of the Agent whose event took the transition — the sender of a
55
+ pick, or the Agent whose pick started the child whose done or error event it is — and the other Agent's pick keeps its
56
+ default answer; when one Agent slot is invoked by two states, it shapes the pick at the same invoke as the pick that
57
+ sent the event or started the child, and none when that pick is not being answered; an event that names no Agent
58
+ shapes the pick in its own send, and outside one shapes no pick while two Agents' states are active; on an entry or an
59
+ exit below no Agent's state it sets nothing. One action object written in several places runs from each by that
60
+ place's rule alone, never by their sum (ADR-0024): as each Machine's actor is created, before its first state is
61
+ entered, the run host gives each place a copy of its own (`placeAnswers`, `packages/orchestrator/src/pick.ts`). An
62
+ `answer` an `enqueueActions` enqueues runs from the place the enqueuing action is written: the copy given to that
63
+ place hands each action its `collect` enqueues, at any depth, the same seat. The load walk cannot see inside an
64
+ `enqueueActions`, so one enqueued on an entry or exit below no Agent's state sets nothing and is not warned. That
65
+ writes xstate internals — a state node's `entry` and `exit` lists, the `actions` of the transition objects xstate
66
+ selects from `transitions`, `always` and `initial`, and an `enqueueActions`'s `collect` — as ADR-0007's re-arm reads
67
+ the timer table; raw-xstate tests that pin those shapes are the canary for an xstate bump. A child's error is no link,
68
+ and by default the Agent is not told (ADR-0024); an author who wants it told writes `answer(...)` on the `onError`
69
+ route. An Effect never takes that route for an outcome: every outcome is output. A Machine that decides from an
70
+ outcome that its work did not succeed fails with `fail()`
71
+ ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)), whose Failure also
72
+ arrives on `onError`, at the invoke of a child Machine; `isFailure` tells it from a defect.
73
+ - **argv, never shell text.** Each entry is one argument; `arg("x")` is one argument filled from the invoke's input. The
74
+ Harness spawns `argv[0]` with no shell. `sh -c` is the author's explicit choice, in the argv, and its result is not a
75
+ trusted verdict.
76
+ - **Placement follows the kind and what it names; it is never authored.** A `command` that names no secret, an `http`
77
+ Effect whose body is not a Worktree file, and a `push` each have one place: the Harness container, the Instance
78
+ Effector, and the Sandbox Effector of the Workspace whose body invokes it. A `command` that names a secret, and an
79
+ `http` whose body is a Worktree file, are placed in that Sandbox Effector — the command runs there with its child
80
+ confined (below). A `dispatch` with no target runs in the Orchestrator's own process, the fourth Placement,
81
+ `orchestrator`, and the one Effect that has it; with a target it runs in the Instance Effector
82
+ ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)). There is no `placement:` key and no kind per
83
+ placement: naming a secret places a command, the body places an `http`, and the target places a `dispatch` — one name
84
+ per thing, so there is no `upload` kind beside it (ADR-0068).
85
+ - **A `command` that names no secret runs in the Harness container of its Workspace's Sandbox**, as a confined child of
86
+ the Harness process ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)): the
87
+ Agent's user, the Sandbox Image's tools, the same confined env and Stand-ins as the Agent's `bash` (ADR-0059), and no
88
+ credential of its own. The Harness serves it on `/exec/:id`, behind the same bearer as every Harness route (ADR-0058);
89
+ the Instance Harness holds no Workspace and refuses it (ADR-0031). Outside a `workspace()` a command is refused at
90
+ invoke. Its cwd is the Worktree its input names (`worktree`), which is required under Repo Slots, because a Workspace
91
+ that only clones has no Worktree for a default to name (ADR-0075). In a Workspace with no Repo Slot its cwd is
92
+ `/work`, which the Workspace's steps share — named in the start, never left to the Harness, whose own cwd is the
93
+ Sandbox Image's `WORKDIR`.
94
+ - **`worktree()` runs on the `command` machinery.** `actors: { cut: worktree({ expect? }) }`, with the input
95
+ `{ path, branch, from? }` or `{ path, detach }`: a kit program in the Harness container, its git confined (ADR-0076),
96
+ no new wire kind, marked idempotent. Its outcomes are `ready | refused | unknown`, and its output is the contract in
97
+ ADR-0075. Each thing that an Agent's word can cause settles `refused`, with nothing changed, and never goes to
98
+ `onError`. A settled `ready` row in the ledger holds the cut's Repo Slot, path and branch, and it is the one thing a
99
+ `push`, a credentialed `command` and an `http` file body read for their target.
100
+ - **Every outcome is output**: `exited | timeout | killed | unknown` for a `command` (`killed` with the signal that
101
+ ended it; a stopped credentialed command's is `exited` with the stop's signal, D1);
102
+ `response | unreachable | refused | unknown` for an `http` Effect; `pushed | rejected | diverged | unknown` for a
103
+ `push`. A command not found is `exited` 127, and a bad Worktree adds no outcome: a `command` settles `exited` 127 with
104
+ the reason in its tail, and an `http` file body settles `refused`. Every union here is open: `worktree()`, `push`,
105
+ `command` and `http` each hold a stand-in member, so that no author can write an exhaustive check and a later outcome
106
+ is no break. A start over the bytes an Effector reads (ADR-0068) is sent nowhere and settles as its kind's own outcome
107
+ — an `http` `refused`, a `push` `rejected` at stage `start` (nothing pushed), a credentialed `command` `exited` 127
108
+ with the reason in its tail, the Effector's own form for a command that could not start — since an Agent's param (a
109
+ squash message, an argv part) can reach the bound; a path segment that is `.`, `..` or empty, and a file body's path
110
+ that is a symlink, leaves the Worktree, is absolute, is a FIFO or has more than one link, are `refused` with nothing
111
+ sent — an Agent's param can cause each. `onError` is only a jr2 or config defect: no Workspace, a `command` with no
112
+ `worktree` under Repo Slots, an argv slot with no value, an input that misses the definition's schema, no Effector to
113
+ send to, an Open part nobody bound.
114
+ - **An `http` Effect is a definition plus one function.**
115
+ `http({ method, url, headers?, auth?, input?, request?, body?, timeout?, maxBody?, idempotent? })`. The url's scheme
116
+ and host are literal — a secret, named with `secret("name")`, may sit only in its path, through the `text` template,
117
+ and one after a `?` or a `#` is refused when the Machine loads — so the host is fixed by the definition and never a
118
+ param. A url left Open is fixed by the composer's `customize()` instead, once and before any run, under the same rules
119
+ (ADR-0054); a file body does not change that rule. `auth` is `bearer(secret(…))`, `basic(user, password)` or a `text`.
120
+ A secret the auth or a header names may be `secret(open)`, whose name the composer binds once in the same way; one in
121
+ the url is refused. `request(input)` returns the run's part alone: `path` segments and a JSON or text body. So an
122
+ Agent's param reaches a path segment or a body, never the host, a header or a secret. It runs in the Instance
123
+ Effector, which builds the request and holds the secrets (ADR-0068); the Orchestrator signs the call and holds none.
124
+ Its body may instead be a Worktree file, `body: file(path, { segments?, contentType? })`: the file's path relative to
125
+ the Worktree its input names (`worktree`), which needs a cut's settled row, as a `push` does — or to `/work` itself in
126
+ a Workspace with no Repo Slot (ADR-0051), the folder its steps share — and the url's path segments — each a value or a
127
+ function of the input; no `request` is written beside it, since `file()` carries the run's part. Such an `http` is
128
+ placed in the Sandbox Effector of the Workspace whose body invokes it, and `jr2 up` refuses one that no `workspace()`
129
+ invokes, where no Worktree exists to read. It is sent with `PUT`, `POST` or `PATCH`. The Sandbox Effector opens the
130
+ file with `openat2` beneath the work root — `RESOLVE_BENEATH`, `RESOLVE_NO_SYMLINKS`, `RESOLVE_NO_MAGICLINKS`,
131
+ `RESOLVE_NO_XDEV`, non-blocking — then `fstat`: a symlink at any component (one that stays inside included), `..`, an
132
+ absolute path, a FIFO, a directory and a file with more than one link are refused, in words, as output. The content
133
+ type is `file()`'s `contentType` or a `content-type` header of the definition's, not both: a definition with both is
134
+ refused when the Machine loads ("write one of them", as for `auth` beside an `authorization` header). With neither it
135
+ is `application/octet-stream`. A later body form (a multipart file and fields) is one more body on `http`, never a
136
+ kind.
137
+ - **A credentialed `command`: `command(argv, { env: { NAME: secret("x") | secret("x").path }, pin? })`.** Naming a
138
+ secret is what places it: it runs in the Sandbox Effector of the Workspace whose body invokes it, with no `placement:`
139
+ key — and so does an env that names `secret(open)`, whose name the composer binds (ADR-0054). Its child runs as the
140
+ Effector's uid beside the Effector's key, the secrets it does not name and the push credentials, and its argv may run
141
+ a file the Agent wrote, so the Effector confines the child with Landlock before `execve` (ADR-0068): it may read and
142
+ run the image's system paths, read and write its own scratch (its `HOME` and its `TMPDIR`; never `/tmp`, which every
143
+ child would share), and read the secret files it names — nothing else. A node whose kernel cannot confine it runs
144
+ none: the Effector of a Workspace that holds one does not become ready there, and the provision fails naming the node
145
+ (ADR-0068). Its input names its Worktree (`worktree`), and that path needs a cut's settled row with a branch, as a
146
+ `push` does: a path alone does not name the target of a credential. With none it settles `exited` 127, and nothing
147
+ runs. It runs against one commit: the tip of that row's branch when it starts, or — with `pin` — the commit the
148
+ Effect's input names (`{ sha }`, one a Gate approved). That commit is checked out into a fresh scratch of the Effect's
149
+ own by the push's import procedure (below), so the Agent's uncommitted edits are never seen; the answer carries
150
+ `dirty: true` beside the output when the Worktree held some (a status in jr2's clean repository over a copy of the
151
+ Worktree's index, against the Worktree's own HEAD — never the pin, so the commits the Agent made after an approved one
152
+ are not edits — read as data; when it cannot tell, it says dirty). The child's env is what the author named — a
153
+ secret's value, or its file's path — plus `PATH`, `HOME` and `TMPDIR`, and nothing else; `PATH` is the Effector
154
+ container's own, as its image set it (the tools are the author's), and `argv[0]` is looked up there; `HOME` and
155
+ `TMPDIR` are the scratch's, and the scratch is removed once the outcome is read. Its output is scrubbed as it streams,
156
+ before it is cut to its tail (ADR-0068). The Agent's files are the author's choice to run — a tool's plugins, a
157
+ `local-exec`, a `credential_process` — so "no program the Agent's files name runs" is not claimed for it. Its claims
158
+ are three: the Agent never reads or spends the secret (a container it cannot exec in, and a child confined from the
159
+ files of every other secret and of the Effector's key), the tool sees a pinned commit and not the live Worktree, and
160
+ the output is scrubbed. Landlock governs files, not `connect()`, and a GPG-signed push holds the signing key on a
161
+ `gpg-agent` socket while it signs; so the Sandbox Effector never runs the two at once: one lock over two classes,
162
+ `gpg` and `command`, and a waiter that settles timed out past its own timeout (ADR-0068). A Workspace may hold both. A
163
+ Workspace with no Repo Slot has no commit to run it against, so there it is refused — by `jr2 up` and at invoke, with
164
+ the fix: declare a slot, or name no secret, and it runs in `/work` beside the Agent (ADR-0051). A run of the live
165
+ files under a credential is the claim this kind exists not to make.
166
+ - **A `push` pushes a Worktree's branch to its Repo Slot's Binding**, from the Sandbox Effector:
167
+ `push({ pin?, sign?, squash?, force?, slot?, timeout? })`, with `worktree` in its input and no `to` and no `branch`.
168
+ `slot` stays static: a Machine with one slot leaves it out, and a Machine with two names it. The Orchestrator finds
169
+ the cut's settled row in the run's ledger, by the path and this Sandbox, and tells the Effector which branch to
170
+ publish. An Agent can change all of git in the pod, and it cannot change that row. With a read of HEAD,
171
+ `git switch main` would publish `main`. With an input key, a Chart could aim at any remote branch. A push with no row,
172
+ a row of another slot, or a detached Worktree is `rejected` at stage `start`. The ledger key is (slot, branch). The
173
+ local name equals the remote name, so an Agent, a Gate and a person in the pod all see the real branch. Its credential
174
+ is the push credential of the `git.credentials` entry the Repo Slot's url falls under (ADR-0051): the provision
175
+ matches the url the run binds — static, per-run or `customize()`-bound, one path — and names that entry's push Secret
176
+ on the Sandbox, and the Effector, which holds only the entries named there, matches the push's url as the fence does.
177
+ git never runs with the Agent's repository as its repository: each call makes a new bare repository in the Effector's
178
+ scratch, configured by jr2 under a cleared environment; it reads that branch as data (a loose ref or `packed-refs`,
179
+ opened beneath with no symlink, bounded); it imports the Agent's objects as data — one `pack-objects` over the Agent's
180
+ object store with a deadline (a FIFO in place of an object blocks an open forever), `index-pack --strict`, and a
181
+ connectivity check — borrowing only from the node's Repo cache; and it pushes `<commit>:refs/heads/<branch>` to the
182
+ Binding url, never to a remote of the Agent's config, and never to a local path (git's local transport runs that
183
+ repository's config and hooks, and a path in the pod can be the Agent's own repository; only the binary's tests can
184
+ allow one). So no config, hook, attribute or program of the Agent's runs; a test plants each in the Agent's repository
185
+ and proves none ran. A token goes Basic over https through a credential helper only — an `http://` url with a token is
186
+ `rejected` with nothing sent — and the helper is the Effector binary (`effector git-credential`), which reads the
187
+ token's file when git asks for the Binding's own host over https and answers nothing for any other; the clean
188
+ repository follows no redirect, so neither the push nor the token reaches an origin outside the entry; an ssh key is
189
+ copied into the call's directory (a Secret mount's mode is one ssh refuses), with `known_hosts` checked strictly. The
190
+ commit is pinned when the push starts: the tip of that branch, never HEAD, or with `pin` the commit the input names
191
+ (`{ sha }`); the Effector's first answer names it, and it is written to the intent row. A pinned commit the Agent
192
+ pruned fails the import and is never substituted. `squash` makes one commit with the input's `{ message }`, on the
193
+ published base: the Agent's tree, its author line and date, the Machine's message. It is a new commit, so it needs a
194
+ committer. With `sign`, the signer is the committer, as for every signed copy. Without it (signing is optional, and
195
+ `squash` is an option of its own), the matching `git.credentials` entry's `committer: { name, email }` commits it, at
196
+ the Effector's clock — or, when the entry names no committer, its `sign` field's name and email. `jr2 up` refuses
197
+ `squash` without `sign` only when the matched entry has neither `sign` nor `committer`, naming both fixes (a
198
+ `committer`, or a signed push and its `sign` field), and the provision refuses it the same way for a slot the run or a
199
+ `customize()` binds. `force` replaces what the remote holds. A Workspace with no Repo Slot has no branch to push, so a
200
+ `push` there is refused, by `jr2 up` and at invoke (ADR-0051).
201
+ - **`sign`: nothing unsigned leaves.** The Effector re-creates the Agent's commits, each signed with the entry's `sign`
202
+ key — ssh (`ssh-keygen -Y sign`) by default, GPG allowed; a GPG key with a passphrase is refused, by name. The author
203
+ line, its date included, stays the Agent's; the committer is the entry's name and email at the Effector's clock. The
204
+ new commits are found by ancestry: the published tip must be an ancestor of the Agent's tip. When it is not, a
205
+ read-only fallback pairs the published commits with the Agent's — `pair`, the newest of the ledger's pushes whose
206
+ published commit is the remote's tip (its published commit and its source), then `cherry`, each published commit after
207
+ the merge base paired with one Agent commit by a stable patch-id, author line and message (a merge by its tree and its
208
+ non-first parents as mapped by the pairs made before it, the rule the map below keys a merge by) — and when nothing
209
+ pairs cleanly the push is `diverged` and nothing is pushed — with `force` too: `force` replaces a remote branch that
210
+ moved, and never stands in for a pairing. One case is neither: a squash push whose published tip is this squash's
211
+ exact image — the source's tree, author line and encoding, the Machine's message, as its one parent the newest pair's
212
+ published commit (on a first push, the merge base with the base), the committer name and email of jr2's squash
213
+ committer (the signer with `sign`, else the entry's `committer`, above), and with `sign` a signature that verifies
214
+ against the slot's signing key — is this push's own commit, sent again by a restore after the Effector lost it while
215
+ the push in flight had no settled row; it settles `pushed`, `unchanged`, with the sync bundle and its own `squashed`.
216
+ A full comparison of those fields, not a pairing. Dates and signature bytes are not compared: the restore sends the
217
+ push at a later clock, and a signature need not be deterministic. A tip with this squash's tree, parent, author line
218
+ and message under another committer, or with `sign` and no signature that verifies against the slot's key, is not
219
+ jr2's own: the push is `rejected`, with no sync bundle and nothing pushed, and its reason says which. Then, on every
220
+ push and before anything is signed, the same map runs over the new commits, oldest first, and a commit that has a
221
+ published twin is replaced by that twin in the rewritten history; only the unmapped commits are signed. The twin is a
222
+ ledger pair, taken as it is — each names the one exact commit a push published for its source, a signed copy or a
223
+ squashed commit; the pairs of every push of the (slot, branch) are read, each only while the remote still holds its
224
+ published commit, the newest push's when two name one source — or else a published commit after the commit's merge
225
+ base with the remote's tip, each taken once, that has the commit's cherry key, read with the commit's parents as
226
+ mapped so far (a merge of the Agent's own side branch keys on the published copies of its non-first parents), and is a
227
+ faithful image of it: the commit's tree, and as parents the commit's own parents as mapped so far. Every commit the
228
+ remote lacks must be one the call signed, or the push is `diverged`. `pushed` carries the published commit, its
229
+ source, what the remote held before, how the new commits were found (`ancestry | pair | cherry`), `unchanged` when the
230
+ remote already held it, `newCommits` (how many commits the published commit adds to what jr2 found held, the remote's
231
+ branch and the Repo cache's refs as the push read them; `0` when jr2 found the published commit already held — the
232
+ remote's branch or the Repo cache), `squashed: signed | unsigned` when the published commit is one squashed commit
233
+ (absent for signed copies; it names what the published commit is, not what the push's options ask for: a commit the
234
+ call made is named by its options, and the published tip that an earlier push made, which `pair` or `cherry` maps, by
235
+ that push's own `squashed`, which its pair carries), and — whenever the published commit is not the Agent's own — the
236
+ published commits as a bundle for the sync step (`git bundle create` of the published tip, with the Agent's tip as its
237
+ prerequisite: the new commits, and the trees and blobs git adds beside them, which the Agent's store may already
238
+ hold). A push that publishes nothing new carries one too: when `pair` or `cherry` maps the published tip itself (a
239
+ restore that sends a pinned push again, a push after a sync that did not finish), or the published tip is this
240
+ squash's own (a restore that sends a squash push again), the Agent's branch still holds its own commits. The bundle
241
+ never reaches the Machine. A push sends every pair the ledger holds for its Workspace and its (slot, branch) — one
242
+ branch on one remote — oldest push first (`pairs`, omitted when there is none): each push that published, with its
243
+ published commit, its source and its `squashed`; the Effector stays stateless. The pair stands for the published tip
244
+ only while the remote still holds that push: a remote that moved past it (another writer) is paired by `cherry` or is
245
+ `diverged`, never grafted onto. The Agent is never told to rebase: a rebase repair loses a reworded message and
246
+ duplicates empty commits and merges. A published commit is never signed twice, for every signer and key type: a side
247
+ branch the Agent made before the push and merges later carries the unsigned original of a commit the remote holds
248
+ signed, which ancestry calls new, and the map replaces it by its twin — a merge the Agent made before the first push
249
+ included, and, through the ledger's pairs, a side branch made before a squash and merged by any later push, whose
250
+ commits the squashed commit stands for: the squash's pair maps its source to the squashed commit however many pushes
251
+ came after it, so a side branch merged by one push and a branch made on it merged by the next publish only what is
252
+ new. The pair maps a squash's SOURCE only, and that is a named limit: a side branch made at an earlier commit of the
253
+ squashed range publishes the commits it descends from as signed copies beside the squashed commit. Those commits were
254
+ never published, none is signed twice, and the trees are the Agent's; such a commit has no faithful twin, and mapped
255
+ to the squashed commit the push would publish a false diff. The committer date stays the Effector's clock; a
256
+ deterministic re-sign (the Agent's committer date) was rejected, since it rests on one key type's property, not GPG's
257
+ or ECDSA's. A key alone does not pair: a commit the Agent reverts and applies again with cherry-pick has the key of
258
+ the first one, and the first one's published copy has older parents, so paired with it the push would offer that older
259
+ copy as the new tip — `rejected` as non-fast-forward without `force`, a rewind of the remote with it. The faithful
260
+ image is the rule; the cherry fallback keeps its own, that the last paired commit holds the published tip's tree. The
261
+ map's cost is bounded by the push's size.
262
+ - **The sync: after every push whose published commit is not the Agent's own, the Agent's branch moves to it** — signed
263
+ copies, or one squashed commit, signed or not; the decision keys on `published` ≠ `source`, never on `sign` — in the
264
+ Harness container (`POST /sync`, behind the Harness bearer, ADR-0058), from the bundle the Effector wrote, which the
265
+ Orchestrator relays — never a shared volume: the Worktree stays read-only on the credentialed side. Under a cleared
266
+ environment and a config that names no hook, no fsmonitor, no alternate-refs command and no maintenance — and with its
267
+ git confined, as every process it starts in `/work` (ADR-0076), because a filter or a textconv in the shared
268
+ `.git/config` runs in git's process — the Harness verifies and unbundles the bundle (objects only), checks that the
269
+ published tip is a commit with the source's tree (it reads no signature), answers `busy` when a rebase of the branch,
270
+ or a merge, cherry-pick, revert or sequence in a worktree whose HEAD is the branch, is in progress, and otherwise
271
+ copies each commit the Agent made after the source as its raw object with only its parents changed, then moves the
272
+ branch with one `git update-ref <new> <old>` — a compare-and-swap, tried again from the new tip at most 3 times — so
273
+ files, index, HEAD and stash do not change. Its answer is a field of the push's output beside the outcome:
274
+ `sync: synced | already | skipped | busy | not-synced`, with `syncReason` when it moved nothing (a push that published
275
+ the Agent's own commit is `skipped`, with no call and no bundle; every other push is synced, one that published
276
+ nothing new included, so a plain push of the slot after a squash is a fast-forward of the squashed commit). A Harness
277
+ that cannot be reached answers `not-synced`; the push stays `pushed`. When the last push of a (slot, branch) answered
278
+ `busy` or `not-synced`, jr2 runs its sync again before the next push of that key, in the Worktree of that push and
279
+ with its bundle, which its row keeps until a sync finished or a newer push of that key published. The push never
280
+ trusts the sync's answer: the Agent owns its repository and can move the branch at any time, so the next push derives
281
+ everything again from the remote and the Agent's objects. After such a push the Agent reads, beside the output, what
282
+ moved — signed copies of its commits with new ids, or its commits squashed into one commit, named "signed" only when
283
+ it is — and that jr2 moved its branch to it, or did not and why; that its files, index and stash did not change; and
284
+ that amending, rebasing or resetting the pushed commits makes the next push `diverged`. The Harness keys that
285
+ paragraph on a synced push whose published commit is not the Agent's, and its words on `squashed`. A push in which jr2
286
+ counted no new commit (`newCommits: 0`) is still `pushed`, because the push ran and the remote's branch may have moved
287
+ (a new branch at a commit the remote held on another branch). But `pushed` alone does not tell the Agent that its work
288
+ did not go out: in a real Haiku 5.5 run, the model sent a `git commit` that failed and the push in one answer, the
289
+ push published the branch at a commit the remote already held, and the model read `pushed` as its work published. So
290
+ beside the output the Agent reads that jr2 counted no new commit in this push, that it found the published commit
291
+ already on the remote or in jr2's copy of the remote, and that a change it did not commit before the push is not in
292
+ this push. The words name where jr2 looked because the count reads the Repo cache, which fetches without pruning: a
293
+ commit the remote dropped can stay there, and then a count of 0 is not a fact about the remote, which is why the first
294
+ clause names what jr2 counted. No @model claim reaches `newCommits: 0`, so no measurement has read these words. When
295
+ the signed-copies or squash paragraph above also applies, it names "an earlier push" as the one that signed or
296
+ squashed, so the two do not contradict. The Harness keys the sentence on `newCommits: 0` alone, and says nothing with
297
+ no count. The count is optional on the wire: a live Sandbox keeps the runtime it started with (ADR-0037), so after
298
+ `jr2 up` its older Effector answers `pushed` with no `newCommits`, and a required field would fault a push that
299
+ reached the remote. The count's input to `git rev-list --stdin` is revisions only (`^<sha>` for each tip the remote
300
+ held), never `--not`: git takes an option there only from 2.42, and the Sandbox Effector runs its image's git — the
301
+ Sandbox Image's, unless the `workspace()` names one — which can be older (Debian bookworm's is 2.39). A distinct
302
+ outcome (`unchanged`, `up-to-date`) was rejected: every consumer of a push keys on `pushed` (the ledger's pairs, the
303
+ sync, the Machines' guards), and a push that made a new branch did change the remote.
304
+ - **The process group ends with the exec.** Each exec leads its own group; its timeout, a kill, and its top process's
305
+ exit each SIGKILL the group, so nothing that stays in the group outlives it. In the Harness container a descendant
306
+ that leaves the group (`setsid`) is not killed; it holds no credential. A credentialed command's descendant cannot
307
+ leave: its Effect's keeper and the Sandbox Effector are subreapers, and every process an Effect started, a `setsid`
308
+ escapee included, is killed and reaped before the Effect settles, while a live command's daemon is not another
309
+ Effect's to kill (ADR-0068). A descendant that keeps the output open does not hold the outcome. A bounded tail
310
+ (default 4096 characters) is the output; the full stream goes to the run's feed (Instance-token band).
311
+ - **A write-ahead ledger; no second fire the author did not mark.** One row per Effect in the run's blob (ADR-0007,
312
+ ADR-0065): an `intent` row saved at the write, with the snapshot that holds the Effect, before the request leaves; a
313
+ `settled` row with the output, saved at the write just before the transition it drives and kept by the save that
314
+ carries that transition, so a restart that loses the transition's save still delivers the output and runs nothing. On
315
+ restore: settled → the output is delivered; no row → nothing was sent, so it starts; intent → per kind. A `command`
316
+ that names no secret re-attaches to the exec by id, or settles `unknown` when the Harness no longer knows it (it
317
+ restarted); it is never run again by jr2: it runs the Agent's code, which may spend a Stand-in. A cut is the
318
+ exception: it runs the kit's program and is marked idempotent, so a restore or a Harness restart sends it again with
319
+ the same id. A `push` intent row names its Sandbox and, once the Effector's first answer names it, the pinned commit:
320
+ a restore asks the Effector for the id and follows it, and when the Effector no longer holds it the push is sent again
321
+ with only that commit — never the Agent's tip now — because its run is a function of the commit and its credential,
322
+ what the intent row records; with no commit recorded it settles `unknown`. A credentialed command follows the push's
323
+ rule: its intent row names its Sandbox and, once the first answer names it, the pinned commit, and one the Effector
324
+ lost is sent again with only that commit, since its run is a function of (commit, argv, secrets). An `http` intent row
325
+ names its placement — the Instance Effector, or the Sandbox of a file-body `http` — and no replica: the Effect asks
326
+ every replica of that Effector for its id and follows the one that holds it; when none does, it is sent again only
327
+ when the author marked it `idempotent`, and any other settles `unknown`. Retry is the author's choice — a route back
328
+ into the state, or `idempotent` for a call that carries its own idempotency key or only reads. No row is dropped: each
329
+ settled or aborted row keeps the Effect's id, kind and outcome for the run's life, the record of what the run's
330
+ Effects did. A settled row's output tail is dropped once the save that carries its transition is on disk; the full
331
+ stream is on the feed. An `http` response's body and headers are its tail: the row keeps the outcome, the status, the
332
+ content type and the size, and the settled line on the feed carries the whole response. A `push` row keeps its Repo
333
+ Slot, its branch, its published commit, its source, its `squashed` and its sync's answer whole — every later push of
334
+ its Workspace and (slot, branch) sends them as one of its pairs, and the newest push's sync, when it did not finish,
335
+ runs again — and drops git's words; it drops its bundle once its sync answered `synced`, `already` or `skipped`, and
336
+ keeps it while the sync is `busy` or `not-synced` only as long as it is the newest push of its (slot, branch) that
337
+ published, because the sync that runs again moves the branch from it and only the newest push's sync runs again. Once
338
+ a newer push of that key published, the older row's bundle goes at the next save: one bundle per (slot, branch) at
339
+ most. A cut's `ready` row keeps its Repo Slot, its path and its branch whole, because a later Effect reads them. So an
340
+ Effect in a loop grows the ledger by one short row per pass, never by its tails. One exception, bounded by one pick:
341
+ while a pick is outstanding, its hold keeps the `chain` whole, tails included, because an `answer` action may read it
342
+ after a restore; a loop below the Agent's state then grows the hold by one tail per pass. The hold drops its chain
343
+ when the pick is answered.
344
+ - **A start is sent once where it may have arrived.** A Harness runs one id once only within its own process; a
345
+ restarted Harness knows no id. So the Orchestrator sends a start again only when it certainly never left (the
346
+ connection was refused, the name did not resolve). Any other failure — a reset, a 5xx, no answer — may follow a start
347
+ the Harness ran: the Orchestrator reads the exec by id instead, follows it when the Harness knows it, and settles
348
+ `unknown` when it does not. It then sends a kill for that id, which keeps a start still on the wire from running. An
349
+ `http` start follows the same rule over the Effector's replicas: one that was never reached, or that answered it is
350
+ stopping (it starts nothing then), is sent to another; any other failure asks every replica first, and the restore
351
+ rule above decides when none holds it. A replica that stops answering for an Effect it was followed on — gone, or
352
+ restarted — is the same case.
353
+ - **D1: an Effect is stopped when its state exits.** A `command` that names no secret: the exec's group is killed. The
354
+ kill is sent again until the Harness keeps it, for at most the command's timeout (past it the Harness's own timer has
355
+ ended the group); a kill that never reaches it is said on the feed. A fast exit can send the kill while the start is
356
+ still on the wire: the Harness keeps a kill of an id it does not know, and the start that arrives after it runs
357
+ nothing. A credentialed `command`: when its actor stops before its process ends — the state exits, or the run is
358
+ cancelled — the Orchestrator sends one `DELETE /effects/<id>` on the Effect's own path to its Sandbox Effector, signed
359
+ like every Effector request (ADR-0068), so nothing in the Harness container can send it. The Effector sends SIGTERM to
360
+ the command's process group, waits 5 s, then sends SIGKILL, and runs its settle loop (ADR-0068's subreaper rule), so
361
+ no process the command started outlives it; the Effect settles `exited` with the signal the stop sent last — SIGTERM,
362
+ or SIGKILL past the wait — and, when the program caught SIGTERM and exited, with its code too. A stop that comes
363
+ before the program starts — while the command waits for a GPG-signing push of its Sandbox Effector to end, or while
364
+ its pinned commit is checked out — signals nothing: the Effect settles `unknown`, "its state exited before the command
365
+ started; it did not run". What the DELETE answers is said on the feed after the abort's line: "its Sandbox Effector
366
+ stopped it: exited with SIGTERM"; "its Sandbox Effector stopped it:" and the Effector's reason, for one that did not
367
+ run; the outcome it had already ended with; that the Effector had not settled it when the stop answered, outcome
368
+ unknown; that no replica held the Effect, so a start still on the way runs nothing; or that the stop did not reach the
369
+ Effector and the command may run until its timeout. A run that has ended (a cancel, a final state) has no feed: the
370
+ line goes to the Orchestrator's log, as does a kill that never reached a Harness. The abort goes to the ledger
371
+ (`aborted`, saved with the transition that exited the state — a cancel's final save carries it) and the feed, "outcome
372
+ unknown"; the Machine does not get it — the state that asked has left. An `http` Effect has no stop route: its state's
373
+ exit stops the polling, and the abort goes to the ledger and the feed the same way, "the Effector may still send it;
374
+ outcome unknown". The Effector finishes the call and drops an outcome nobody reads. A file-body `http` and a `push`
375
+ have none either: each is one request that ends on its own, and one a state exit cut runs on in the Sandbox Effector
376
+ to its end; the abort says "the Sandbox Effector may still run it; outcome unknown". Two stops are not exits: the host
377
+ parking the run (`hostStopping`, ADR-0024) and an Orchestrator shutdown (it stops no actor). Neither stops a command;
378
+ the Effect then re-attaches on restore, or settles `unknown`. There is no "finally": cleanup is a state.
379
+ - **Must-finish is a pattern.** An Effect that must not be cut goes in its own state, whose only exit is its `onDone`,
380
+ and that state handles the ancestor's events itself.
381
+ - **No fire-and-forget form.** Two patterns keep D1: an Effect in its own state; or a second invoke with no `onDone`
382
+ beside the state's work — it fires on state entry, a fast exit can cut it, and below an Agent's state it holds Rest
383
+ until it settles. `spawnChild` is not used for an Effect: it is not stopped on exit, it runs again on restore, and it
384
+ is orphaned after a final state — each breaks D1 or the ledger. A `dispatch` without `wait` is not the exception it
385
+ looks like: the Effect is the START, which settles when the run is accepted; the run it started is a run of its own,
386
+ not the Effect's child, and D1 never reached it (ADR-0071).
387
+ - **Rest counts an Effect like any child.** A running Effect below an Agent's state holds the Agent's pick (ADR-0024); a
388
+ settled Effect with no `onDone` is Rest. An Effect is not a long-lived invoke, and the load walk does not warn on one.
389
+ - **The load walk checks what a pick hands an Effect** (ADR-0015): a pick param nothing reads — per state, so any invoke
390
+ of the state may read it, a second invoke beside the Effect included (with `fromPick`, one that is no key of the
391
+ Effect's input and that `fill` does not read; a guard on the entering transition and an `assign` count as reads; a
392
+ pick enters what an eventless `always` moves on to; across an `after`, only a guard or `assign` before the timer reads
393
+ it; a Gate's event is no pick and is not checked) and a `fill` that overwrites a param are refused; code the probe
394
+ cannot or does not run — a mapper that throws in it, an action other than an `assign` — and a pick that targets a
395
+ history state that may enter an Effect's state are a warning; a state below an Agent's state that invokes something
396
+ and that nothing enters is refused; and an `answer` on an entry or an exit below no Agent's state, which shapes no
397
+ pick, is a warning that names the Agent states it could be moved under. An Open argv's slots are unknown when the
398
+ Machine is built, so every param counts as read by it then; `customize()` runs the pick-param check again when it
399
+ binds the argv, refuses there a param the bound argv does not read, and gives the bound Machine the warnings the walk
400
+ gives for that argv (ADR-0054).
401
+
402
+ - **The load walk refuses an Effect's `onDone` with no unguarded last arm.** A type cannot see a missing arm, and with
403
+ no match a run stays in that state and shows no fault.
404
+
405
+ ## Considered options
406
+
407
+ - **Run every command in the Sandbox Effector.** Rejected: a command that names no secret runs the Workspace's own code
408
+ with the Sandbox Image's tools and the Agent's user, and gains nothing from a container the Agent cannot reach. Only a
409
+ command that names a secret is placed there, because a tool that signs with its credential (`terraform apply`, `aws`
410
+ with SigV4) cannot be given a Stand-in (ADR-0059); there its child is confined from the Effector's mount (above).
411
+ - **A `placement:` key on a command.** Rejected: naming a secret is the placement, and a key could disagree with it.
412
+ - **A trailer on each signed commit naming its source.** Rejected: forges show it, and the map must be read off every
413
+ published commit or an unsigned one leaks; ancestry, the sync and the read-only fallback need no stored map.
414
+ - **Take a command as a shell string.** Rejected: an Agent's param in a string is a shell injection.
415
+ - **Repeat a command whose outcome is unknown.** Rejected for a command that names no secret: it may have acted (a push
416
+ from a script, a spent Stand-in), and its run is the live Worktree's, which no row records. The Machine gets `unknown`
417
+ and decides. A credentialed command runs against a pinned commit its intent row holds, so a restore that found no
418
+ Effect sends it again with that commit (above).
419
+ - **Sync through a volume the Effector writes and the Harness container reads.** Rejected: the Worktree is read-only on
420
+ the credentialed side so that nothing there is a channel into the Agent's container; the bundle rides the
421
+ Orchestrator's one call instead.
422
+ - **Tell the Agent to rebase onto the published commits.** Rejected: measured on a real repository, a rebase repair
423
+ loses a reworded message, drops a re-applied commit, and duplicates empty commits and merges.
424
+ - **A fire-and-forget Effect (`spawnChild`, or a flag).** Rejected for the reasons above; nothing the Machine starts may
425
+ outlive the state that asked for it.
426
+ - **An `upload()` kind beside `http({ body: file() })`.** Rejected: two spellings of one Effect, and a kind that, not a
427
+ body, decided the placement. One kind, `http`, whatever its body.
428
+
429
+ ## Consequences
430
+
431
+ - A command a Harness restart cut settles `unknown`; the stream before the restart is on the feed.
432
+ - An Orchestrator restart mid-command gives one run: the restored Effect reads the exec from its current end and settles
433
+ with its output.
434
+ - An `http` call an Orchestrator restart caught in flight gives one send: the restored Effect finds it on the replica
435
+ that holds it. One the Effector lost gives `unknown`, or a second send when it is `idempotent`.
436
+ - A push the Orchestrator restart caught in flight gives one push of the pinned commit; one the Effector lost is pushed
437
+ again with that commit alone. A credentialed command the Effector lost runs again with its pinned commit, so its tool
438
+ can run twice against one commit.
439
+ - A squash push sent again takes as its own only a remote tip that jr2's squash committer could have made: another
440
+ writer's copy of the squash is `rejected`, never synced. The committer name and email are text anyone can write, so an
441
+ unsigned squash's own commit is told from a copy by its content and that text alone; a signed one's also by a
442
+ signature that only the slot's key makes.
443
+ - A state exit stops a credentialed command where it runs, and not a file-body `http` or a `push`: the Effector's stop
444
+ route is the command's alone (ADR-0068), so for those two D1 holds for the Machine — it does not get the output — and
445
+ not for the outside world. The stop is sent once, at the exit: a DELETE that reaches the Effector before the command's
446
+ start (a fast exit while the start is still on the wire) is kept there, and the start that arrives after it runs
447
+ nothing (ADR-0068), as a Harness keeps a kill.
448
+ - The Orchestrator reaches a Sandbox Effector by a headless Service the provision writes beside its key, owned by the
449
+ Sandbox CR (`fx-` in place of the Sandbox name's `ws-`): its name resolves to the pod's own address, the one each
450
+ request is signed for (ADR-0068), and it publishes that address while the pod is not ready, so a restore can ask the
451
+ Effector for an Effect while the Harness beside it restarts. The operator's Service names the Harness's port only and
452
+ is unchanged.
453
+ - The `@kind` tier pushes to a remote of its own: the seed's sshd serves a push remote, and the tier's scenarios push
454
+ signed over ssh and verify the published commit with the signing key as the one allowed signer, read the Agent's
455
+ branch and Worktree after the sync, and push again after the Agent amended the pushed commit (`diverged`). A token
456
+ push over https has no remote there. Its credentialed-command scenarios read the secret from env and from its file,
457
+ scrubbed, say `dirty` over an uncommitted edit, show a child of the child denied the Effector's key, and show a
458
+ command whose state exited gone from the Effector container.