@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,168 @@
1
+ // A mechanism event must have a route (ADR-0074). jr2 sends `agent.fault` to the Machine that
2
+ // invoked an Agent when the Agent's Turn cannot go on (ADR-0016), and `workspace()` sends
3
+ // `workspace.lost` to its body when the Sandbox is gone (ADR-0012), and an Open body sends a
4
+ // Chart's unrouted `agent.fault` to the Machine that invoked it (ADR-0070). Each is a relayed
5
+ // event, and xstate drops an event that no active state handles. So the walk refuses a state that cannot
6
+ // route one, and the run host faults a run in which none took one.
7
+ //
8
+ // - `refuseUnroutedAgentFault`: a state that invokes an Agent, or an Open body that can run one
9
+ // (it lends an Agent or allows a model), when that state and all its ancestors have no
10
+ // `agent.fault` route. In `jr2Setup().createMachine` and in the load walk (`partsOf`), beside the
11
+ // lost-output refusal, so `jr2 check`, `jr2 up` and the Orchestrator's load show it. Not for a
12
+ // compiled Chart: there the route is optional, and the Open body sends an `agent.fault` that no
13
+ // transition of the Chart takes to the Machine that invoked it (open-body.ts; ADR-0070).
14
+ // - `refuseUnroutedLost`: a non-final leaf state of a `workspace()` body, when that state and all
15
+ // its ancestors have no `workspace.lost` route. In `workspace()`, the one place that knows that a
16
+ // Machine is a body. A compound or parallel state is not judged by itself: an event goes to the
17
+ // active leaves first, so a route on each leaf (or above it) is a route for the compound state.
18
+ // A final state of the root ends the body, so it is not judged. A nested final state is judged
19
+ // when its compound state has no `onDone`: the body then stays in the compound state, and a
20
+ // route on the compound state or above it is the route. The line names the compound state. A
21
+ // final state of a parallel region is not judged: the other regions are active, and their leaves
22
+ // are judged.
23
+ // - `unroutedMechanismEvent`: the runtime half, for a route that the walk cannot judge (a guard
24
+ // that is false when the event comes). The run host reads it off each microstep.
25
+ //
26
+ // A route is any transition for the event: one with actions only counts, because it can be a
27
+ // deliberate `reenter: true` that invokes the Agent again. jr2 does not judge what a route does.
28
+ // A wildcard (`"*"`, `"agent.*"`) that takes the event is a route too, as it is at runtime.
29
+
30
+ import type { AnyStateMachine, StateNode } from "xstate";
31
+ import { isAgent } from "./agent.ts";
32
+ import { isOpenBody, openBodyDeclarationOf } from "./open-body.ts";
33
+
34
+ /** The two mechanism events that must have a route. */
35
+ const ROUTED_MECHANISM_EVENTS = ["agent.fault", "workspace.lost"] as const;
36
+
37
+ type Node = StateNode<any, any>;
38
+
39
+ /** Does a descriptor of a state's `on` take this event type? xstate's own match: the exact type,
40
+ * `"*"`, or a partial wildcard like `"agent.*"`. */
41
+ function takes(descriptor: string, type: string): boolean {
42
+ if (descriptor === type || descriptor === "*") return true;
43
+ return descriptor.endsWith(".*") && type.startsWith(descriptor.slice(0, -1));
44
+ }
45
+
46
+ /** Does this state, or a state above it, have a route for `type`? */
47
+ function routedAt(node: Node, type: string): boolean {
48
+ for (let at: Node | undefined = node; at !== undefined; at = at.parent) {
49
+ for (const descriptor of at.transitions.keys()) if (takes(descriptor, type)) return true;
50
+ }
51
+ return false;
52
+ }
53
+
54
+ /** Every state node of the tree below (and with) `node`, in document order. */
55
+ function statesOf(node: Node): Node[] {
56
+ const out: Node[] = [node];
57
+ const children = Object.values(node.states as Record<string, Node>).sort((a, b) => a.order - b.order);
58
+ for (const child of children) out.push(...statesOf(child));
59
+ return out;
60
+ }
61
+
62
+ /** The text of one or more state ids, quoted. */
63
+ function quoted(ids: readonly string[]): string {
64
+ return ids.map((id) => `"${id}"`).join(", ");
65
+ }
66
+
67
+ /** Is this slot an Open body that can run an Agent whose `agent.fault` comes to the Machine? One
68
+ * that lends an Agent or lets a Chart add its own: the body sends the Machine a Chart's unrouted
69
+ * `agent.fault`. A lent Machine routes its own Agents, and the walk judges it by itself. */
70
+ function runsChartAgents(logic: unknown): boolean {
71
+ if (!isOpenBody(logic)) return false;
72
+ const decl = openBodyDeclarationOf(logic);
73
+ return (decl.lends.agents?.length ?? 0) > 0 || decl.allows.models.length > 0;
74
+ }
75
+
76
+ /**
77
+ * Refuse a Machine with a state that invokes an Agent, or an Open body that can run one, when that
78
+ * state and all its ancestors have no `agent.fault` route (ADR-0074). The slots are the Machine's
79
+ * own `actors`. Throws one line that names each state, the event and the route to write.
80
+ */
81
+ export function refuseUnroutedAgentFault(machine: AnyStateMachine): void {
82
+ const actors = (machine.implementations.actors ?? {}) as Record<string, unknown>;
83
+ const agents: string[] = [];
84
+ const bodies: string[] = [];
85
+ for (const node of statesOf(machine.root as Node)) {
86
+ if (routedAt(node, "agent.fault")) continue;
87
+ const srcs = node.invoke.map((inv) => (typeof inv.src === "string" ? actors[inv.src] : undefined));
88
+ if (srcs.some((logic) => isAgent(logic))) agents.push(node.id);
89
+ else if (srcs.some((logic) => runsChartAgents(logic))) bodies.push(node.id);
90
+ }
91
+ if (!agents.length && !bodies.length) return;
92
+ const clause = (ids: string[], what: string) =>
93
+ `${ids.length > 1 ? "states" : "state"} ${quoted(ids)} ${ids.length > 1 ? "invoke" : "invokes"} ${what}`;
94
+ const what = [
95
+ ...(agents.length ? [clause(agents, "an Agent")] : []),
96
+ ...(bodies.length ? [clause(bodies, "an Open body that can run an Agent")] : []),
97
+ ].join(", and ");
98
+ const many = agents.length + bodies.length > 1;
99
+ throw new Error(
100
+ `machine "${machine.id}": ${what}, and no route for "agent.fault" is on ${many ? "them" : "it"} or on a state ` +
101
+ `above — jr2 sends agent.fault when the Agent's Turn cannot go on, and with no route the run faults: route it ` +
102
+ `on that state or on a state above it, as on: { "agent.fault": "<the state that handles it>" } (ADR-0074)`,
103
+ );
104
+ }
105
+
106
+ /** Does the body stay in this final state? It does when the final state is nested, its compound
107
+ * state has no `onDone`, and that compound state is no region of a parallel state. */
108
+ function staysIn(final: Node): boolean {
109
+ const parent = final.parent;
110
+ if (parent === undefined || parent.parent === undefined) return false;
111
+ if (parent.parent.type === "parallel") return false;
112
+ return !parent.transitions.has(`xstate.done.state.${parent.id}`);
113
+ }
114
+
115
+ /**
116
+ * Refuse a `workspace()` body with a state that is not final, that it can stay in, when that state
117
+ * and all its ancestors have no `workspace.lost` route (ADR-0074). Throws one line that names each
118
+ * state, the event and the route to write.
119
+ */
120
+ export function refuseUnroutedLost(body: AnyStateMachine): void {
121
+ // The leaves: an atomic state, a root with no child state, or a final state that the body can stay
122
+ // in. A history state is no state the body is in.
123
+ const unrouted = statesOf(body.root as Node)
124
+ .filter((node) => Object.keys(node.states).length === 0 && node.type !== "history")
125
+ .filter((node) => node.type !== "final" || staysIn(node))
126
+ .filter((node) => !routedAt(node, "workspace.lost"))
127
+ // The body stays in the compound state of a final state that it can stay in, and that state is
128
+ // not final: the line names it.
129
+ .map((node) => (node.type === "final" ? node.parent!.id : node.id));
130
+ if (!unrouted.length) return;
131
+ const many = unrouted.length > 1;
132
+ throw new Error(
133
+ `workspace(): the body "${body.id}" has ${many ? "states" : "a state"} ${quoted(unrouted)} that ` +
134
+ `${many ? "are" : "is"} not final, and no route for "workspace.lost" is on ${many ? "them" : "it"} or on a ` +
135
+ `state above — jr2 sends workspace.lost to the body when its Sandbox is gone, and with no route the run ` +
136
+ `faults: route it on the body's root, as on: { "workspace.lost": "<the state that handles it>" } (ADR-0074)`,
137
+ );
138
+ }
139
+
140
+ /** The parts of an `@xstate.microstep` inspection event that the runtime check reads. */
141
+ export type MicrostepView = {
142
+ actorRef: { id: string; getSnapshot(): unknown; logic?: unknown };
143
+ event: { type: string; reason?: unknown };
144
+ snapshot?: unknown;
145
+ _transitions?: readonly unknown[];
146
+ };
147
+
148
+ /**
149
+ * The fault of a microstep in which a Machine got `agent.fault` or `workspace.lost` and took no
150
+ * transition for it (ADR-0074), or undefined for every other microstep. The text names the event,
151
+ * the Machine and its state, and the event's `reason`. For `workspace.lost` this is the body's
152
+ * microstep: the wrapper always takes its own transition, to forward the event to the body.
153
+ */
154
+ export function unroutedMechanismEvent(ev: MicrostepView): string | undefined {
155
+ const type = ev.event?.type;
156
+ if (!(ROUTED_MECHANISM_EVENTS as readonly string[]).includes(type)) return undefined;
157
+ if (ev._transitions === undefined || ev._transitions.length > 0) return undefined;
158
+ const snapshot = (ev.snapshot ?? ev.actorRef.getSnapshot()) as { value?: unknown; status?: string } | undefined;
159
+ if (snapshot?.status !== undefined && snapshot.status !== "active") return undefined;
160
+ const machine = (ev.actorRef.logic as { id?: unknown } | undefined)?.id;
161
+ const name = typeof machine === "string" ? machine : ev.actorRef.id;
162
+ const reason = typeof ev.event.reason === "string" && ev.event.reason !== "" ? ev.event.reason : "no reason given";
163
+ return (
164
+ `machine "${name}" got "${type}" in state ${JSON.stringify(snapshot?.value ?? null)}, and no transition took ` +
165
+ `it (${reason}) — no route for "${type}" is on that state or on a state above, or the guard of each route ` +
166
+ `was false: route it with a transition that is always taken (ADR-0074)`
167
+ );
168
+ }
@@ -0,0 +1,53 @@
1
+ // Made from @earendil-works/pi-ai 1.4.0 by `just model-keys` (packages/harness/scripts/model-keys.ts).
2
+ // Do not edit: run the recipe at each pi bump. `model-keys.ts` says what the table means (ADR-0059).
3
+
4
+ import type { ModelKey } from "./model-keys.ts";
5
+
6
+ export const MODEL_KEYS: Readonly<Record<string, ModelKey>> = {
7
+ // No default host: the calls go to more than one host: bedrock-runtime.us-east-1.amazonaws.com, bedrock-runtime.eu-central-1.amazonaws.com.
8
+ AWS_BEARER_TOKEN_BEDROCK: { providers: ["amazon-bedrock"] },
9
+ ANT_LING_API_KEY: { providers: ["ant-ling"], host: "api.ant-ling.com" },
10
+ ANTHROPIC_AUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
11
+ ANTHROPIC_OAUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
12
+ ANTHROPIC_API_KEY: { providers: ["anthropic"], host: "api.anthropic.com" },
13
+ // No default host: the catalog names no base URL.
14
+ AZURE_OPENAI_API_KEY: { providers: ["azure"] },
15
+ BASETEN_API_KEY: { providers: ["baseten"], host: "inference.baseten.co" },
16
+ CEREBRAS_API_KEY: { providers: ["cerebras"], host: "api.cerebras.ai" },
17
+ // No default host: a base URL is a template.
18
+ CLOUDFLARE_API_KEY: { providers: ["cloudflare-ai-gateway", "cloudflare-workers-ai"] },
19
+ DEEPSEEK_API_KEY: { providers: ["deepseek"], host: "api.deepseek.com" },
20
+ FIREWORKS_API_KEY: { providers: ["fireworks"], host: "api.fireworks.ai" },
21
+ COPILOT_GITHUB_TOKEN: { providers: ["github-copilot"], host: "api.individual.githubcopilot.com" },
22
+ GEMINI_API_KEY: { providers: ["google"], host: "generativelanguage.googleapis.com" },
23
+ // No default host: a base URL is a template.
24
+ GOOGLE_CLOUD_API_KEY: { providers: ["google-vertex"] },
25
+ GROQ_API_KEY: { providers: ["groq"], host: "api.groq.com" },
26
+ HF_TOKEN: { providers: ["huggingface"], host: "router.huggingface.co" },
27
+ KIMI_API_KEY: { providers: ["kimi-coding"], host: "api.kimi.com" },
28
+ META_API_KEY: { providers: ["meta"], host: "api.meta.ai" },
29
+ MINIMAX_API_KEY: { providers: ["minimax"], host: "api.minimax.io" },
30
+ MINIMAX_CN_API_KEY: { providers: ["minimax-cn"], host: "api.minimaxi.com" },
31
+ MISTRAL_API_KEY: { providers: ["mistral"], host: "api.mistral.ai" },
32
+ // No default host: the calls go to more than one host: api.moonshot.ai, api.moonshot.cn.
33
+ MOONSHOT_API_KEY: { providers: ["moonshotai", "moonshotai-cn"] },
34
+ NVIDIA_API_KEY: { providers: ["nvidia"], host: "integrate.api.nvidia.com" },
35
+ OPENAI_API_KEY: { providers: ["openai"], host: "api.openai.com" },
36
+ OPENCODE_API_KEY: { providers: ["opencode", "opencode-go"], host: "opencode.ai" },
37
+ OPENROUTER_API_KEY: { providers: ["openrouter"], host: "openrouter.ai" },
38
+ QWEN_TOKEN_PLAN_API_KEY: {
39
+ providers: ["qwen-token-plan", "qwen-token-plan-individual"],
40
+ host: "token-plan.ap-southeast-1.maas.aliyuncs.com",
41
+ },
42
+ QWEN_TOKEN_PLAN_CN_API_KEY: { providers: ["qwen-token-plan-cn"], host: "token-plan.cn-beijing.maas.aliyuncs.com" },
43
+ RADIUS_API_KEY: { providers: ["radius"], host: "radius.pi.dev" },
44
+ TOGETHER_API_KEY: { providers: ["together"], host: "api.together.ai" },
45
+ AI_GATEWAY_API_KEY: { providers: ["vercel-ai-gateway"], host: "ai-gateway.vercel.sh" },
46
+ XAI_API_KEY: { providers: ["xai"], host: "api.x.ai" },
47
+ XIAOMI_API_KEY: { providers: ["xiaomi"], host: "api.xiaomimimo.com" },
48
+ XIAOMI_TOKEN_PLAN_AMS_API_KEY: { providers: ["xiaomi-token-plan-ams"], host: "token-plan-ams.xiaomimimo.com" },
49
+ XIAOMI_TOKEN_PLAN_CN_API_KEY: { providers: ["xiaomi-token-plan-cn"], host: "token-plan-cn.xiaomimimo.com" },
50
+ XIAOMI_TOKEN_PLAN_SGP_API_KEY: { providers: ["xiaomi-token-plan-sgp"], host: "token-plan-sgp.xiaomimimo.com" },
51
+ ZAI_API_KEY: { providers: ["zai"], host: "api.z.ai" },
52
+ ZAI_CODING_CN_API_KEY: { providers: ["zai-coding-cn"], host: "open.bigmodel.cn" },
53
+ };
package/src/model-keys.ts CHANGED
@@ -1,17 +1,29 @@
1
1
  // The known model keys (ADR-0059): every env var pi's catalog reads a provider's credential from,
2
2
  // the catalog provider(s) that read it, and the one host those providers' calls go to by default.
3
3
  //
4
- // A COPY of pi's data at the exact pin `@jr2/harness` holds, because this package has no pi
5
- // dependency and must not grow one: `jr2 up` reads this table host-side (R1, and the default
6
- // `hosts` of a held secret), and the in-cluster Orchestrator never touches pi. The copy cannot
7
- // drift silently — `@jr2/harness`'s `model-keys.test.ts` checks it against pi itself, env names per
8
- // provider and every provider's default host, in the default gate. A pi bump that adds or moves a
9
- // provider fails there first, the same canary role Harness conformance plays (ADR-0027).
4
+ // pi's data at the exact pin `@jr2/harness` holds, made into plain data by a script, because this
5
+ // package has no pi dependency and must not grow one: `jr2 up` reads this table host-side (R1, and
6
+ // the default `hosts` of a held secret), and the in-cluster Orchestrator never touches pi. The table
7
+ // is `model-keys.generated.ts`, and `just model-keys` writes it from pi-ai at each pi bump
8
+ // (`packages/harness/scripts/model-keys.ts`). It cannot drift silently: `@jr2/harness`'s
9
+ // `model-keys.test.ts` checks it against pi itself, env names per provider and every provider's
10
+ // default host, in the default gate. A pi bump that adds or moves a provider fails there first, the
11
+ // same canary role Harness conformance plays (ADR-0027).
10
12
  //
11
13
  // `host` is present only when every model of every provider that reads the name goes to ONE static
12
14
  // host. It is absent when there is none: the base URL is a template (`{location}`, an account id),
13
15
  // the providers disagree (MOONSHOT_API_KEY serves two regions), or the host is regional (Bedrock).
14
16
  // A held secret with such a name must then say its `hosts` itself (§3.4 of the spec, R5).
17
+ //
18
+ // Two facts the data does not say. anthropic reads three names: AUTH_TOKEN goes as `Authorization:
19
+ // Bearer`, the other two as `x-api-key` (an `sk-ant-oat` value is an OAuth token, which a Stand-in
20
+ // never is). AWS_BEARER_TOKEN_BEDROCK is the bearer form of Bedrock auth, which pi reads through
21
+ // its credential chain; SigV4 credentials cannot be held at all — a signature is not a header a
22
+ // Stand-in can be swapped into — and `jr2 up` warns on them (`SIGNING_CREDENTIALS`).
23
+
24
+ import { MODEL_KEYS } from "./model-keys.generated.ts";
25
+
26
+ export { MODEL_KEYS };
15
27
 
16
28
  /** One known model-key env name. */
17
29
  export type ModelKey = {
@@ -21,58 +33,6 @@ export type ModelKey = {
21
33
  host?: string;
22
34
  };
23
35
 
24
- export const MODEL_KEYS: Readonly<Record<string, ModelKey>> = {
25
- // anthropic reads three names. AUTH_TOKEN goes as `Authorization: Bearer`, the other two as
26
- // `x-api-key` (an `sk-ant-oat` value is an OAuth token, which a Stand-in never is).
27
- ANTHROPIC_API_KEY: { providers: ["anthropic"], host: "api.anthropic.com" },
28
- ANTHROPIC_AUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
29
- ANTHROPIC_OAUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
30
- OPENAI_API_KEY: { providers: ["openai"], host: "api.openai.com" },
31
- GEMINI_API_KEY: { providers: ["google"], host: "generativelanguage.googleapis.com" },
32
- // `https://{location}-aiplatform.googleapis.com` — regional, so no default.
33
- GOOGLE_CLOUD_API_KEY: { providers: ["google-vertex"] },
34
- COPILOT_GITHUB_TOKEN: { providers: ["github-copilot"], host: "api.individual.githubcopilot.com" },
35
- // The bearer form of Bedrock auth. Regional, so no default. (SigV4 credentials cannot be held at
36
- // all — a signature is not a header a Stand-in can be swapped into — and `jr2 up` warns on them.)
37
- AWS_BEARER_TOKEN_BEDROCK: { providers: ["amazon-bedrock"] },
38
- ANT_LING_API_KEY: { providers: ["ant-ling"], host: "api.ant-ling.com" },
39
- QWEN_TOKEN_PLAN_API_KEY: {
40
- providers: ["qwen-token-plan"],
41
- host: "token-plan.ap-southeast-1.maas.aliyuncs.com",
42
- },
43
- QWEN_TOKEN_PLAN_CN_API_KEY: { providers: ["qwen-token-plan-cn"], host: "token-plan.cn-beijing.maas.aliyuncs.com" },
44
- // The catalog names no base URL: an Azure deployment is the user's own resource.
45
- AZURE_OPENAI_API_KEY: { providers: ["azure-openai-responses"] },
46
- NVIDIA_API_KEY: { providers: ["nvidia"], host: "integrate.api.nvidia.com" },
47
- DEEPSEEK_API_KEY: { providers: ["deepseek"], host: "api.deepseek.com" },
48
- GROQ_API_KEY: { providers: ["groq"], host: "api.groq.com" },
49
- CEREBRAS_API_KEY: { providers: ["cerebras"], host: "api.cerebras.ai" },
50
- XAI_API_KEY: { providers: ["xai"], host: "api.x.ai" },
51
- // pi reads the name, and its catalog lists no model for the provider at this pin.
52
- RADIUS_API_KEY: { providers: ["radius"] },
53
- OPENROUTER_API_KEY: { providers: ["openrouter"], host: "openrouter.ai" },
54
- AI_GATEWAY_API_KEY: { providers: ["vercel-ai-gateway"], host: "ai-gateway.vercel.sh" },
55
- ZAI_API_KEY: { providers: ["zai"], host: "api.z.ai" },
56
- ZAI_CODING_CN_API_KEY: { providers: ["zai-coding-cn"], host: "open.bigmodel.cn" },
57
- MISTRAL_API_KEY: { providers: ["mistral"], host: "api.mistral.ai" },
58
- MINIMAX_API_KEY: { providers: ["minimax"], host: "api.minimax.io" },
59
- MINIMAX_CN_API_KEY: { providers: ["minimax-cn"], host: "api.minimaxi.com" },
60
- // Two providers, two hosts (`.ai` and `.cn`): no single default.
61
- MOONSHOT_API_KEY: { providers: ["moonshotai", "moonshotai-cn"] },
62
- HF_TOKEN: { providers: ["huggingface"], host: "router.huggingface.co" },
63
- FIREWORKS_API_KEY: { providers: ["fireworks"], host: "api.fireworks.ai" },
64
- TOGETHER_API_KEY: { providers: ["together"], host: "api.together.ai" },
65
- // Two providers, one host.
66
- OPENCODE_API_KEY: { providers: ["opencode", "opencode-go"], host: "opencode.ai" },
67
- KIMI_API_KEY: { providers: ["kimi-coding"], host: "api.kimi.com" },
68
- // Account-templated base URLs.
69
- CLOUDFLARE_API_KEY: { providers: ["cloudflare-workers-ai", "cloudflare-ai-gateway"] },
70
- XIAOMI_API_KEY: { providers: ["xiaomi"], host: "api.xiaomimimo.com" },
71
- XIAOMI_TOKEN_PLAN_CN_API_KEY: { providers: ["xiaomi-token-plan-cn"], host: "token-plan-cn.xiaomimimo.com" },
72
- XIAOMI_TOKEN_PLAN_AMS_API_KEY: { providers: ["xiaomi-token-plan-ams"], host: "token-plan-ams.xiaomimimo.com" },
73
- XIAOMI_TOKEN_PLAN_SGP_API_KEY: { providers: ["xiaomi-token-plan-sgp"], host: "token-plan-sgp.xiaomimimo.com" },
74
- };
75
-
76
36
  /** Every catalog provider id some known model key belongs to — the set `harness.catalog` may name. */
77
37
  export const MODEL_KEY_PROVIDERS: ReadonlySet<string> = new Set(
78
38
  Object.values(MODEL_KEYS).flatMap((key) => key.providers),
@@ -0,0 +1,53 @@
1
+ // What a mounted ConfigMap holds, as one digest (ADR-0038, ADR-0069). A publish rolls no pod, so
2
+ // the Orchestrator reads `jr2-images` and `jr2-held` through mounts the kubelet refreshes on its
3
+ // own period. `jr2 up` digests the data it applied, `GET /assembly` reports the digest of what the
4
+ // process reads, and the publish waits until the two agree. Then the door's checks, and the
5
+ // provisions that follow the converge, read the data this converge wrote.
6
+
7
+ import { createHash } from "node:crypto";
8
+ import { readdir, readFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { HELD_CONFIGMAP, HELD_MOUNT, IMAGES_CONFIGMAP, IMAGES_MOUNT } from "./names.ts";
11
+
12
+ /** The ConfigMaps the Orchestrator reads through a mount, by name, and where each is mounted. */
13
+ export const MOUNTED_CONFIGMAPS: Readonly<Record<string, string>> = {
14
+ [IMAGES_CONFIGMAP]: IMAGES_MOUNT,
15
+ [HELD_CONFIGMAP]: HELD_MOUNT,
16
+ };
17
+
18
+ /** One digest of a ConfigMap's `data`: the same bytes give the same digest at both ends. */
19
+ export function configMapDigest(data: Readonly<Record<string, string>>): string {
20
+ const entries = Object.keys(data)
21
+ .sort()
22
+ .map((key) => [key, data[key]]);
23
+ return createHash("sha256").update(JSON.stringify(entries)).digest("hex").slice(0, 16);
24
+ }
25
+
26
+ /**
27
+ * The digest of a mounted ConfigMap, read from its mount (ADR-0069). The kubelet writes each key
28
+ * as a file and keeps its own entries under names that start with `.`, which are not data. An
29
+ * absent mount reads as `undefined`.
30
+ */
31
+ export async function readMountDigest(dir: string): Promise<string | undefined> {
32
+ let names: string[];
33
+ try {
34
+ names = await readdir(dir);
35
+ } catch {
36
+ return undefined;
37
+ }
38
+ const data: Record<string, string> = {};
39
+ for (const name of names.filter((n) => !n.startsWith("."))) data[name] = await readFile(join(dir, name), "utf8");
40
+ return configMapDigest(data);
41
+ }
42
+
43
+ /** Every mounted ConfigMap's digest, by name; a mount that is absent is left out. */
44
+ export async function readMountDigests(
45
+ mounts: Readonly<Record<string, string>> = MOUNTED_CONFIGMAPS,
46
+ ): Promise<Record<string, string>> {
47
+ const out: Record<string, string> = {};
48
+ for (const [name, dir] of Object.entries(mounts)) {
49
+ const digest = await readMountDigest(dir);
50
+ if (digest !== undefined) out[name] = digest;
51
+ }
52
+ return out;
53
+ }
package/src/names.ts CHANGED
@@ -9,7 +9,7 @@ export const ORCHESTRATOR_PORT = 4000;
9
9
  /** The instance-owned Secret: Instance token + signing key (+ orchestrator-side creds). */
10
10
  export const INSTANCE_SECRET = "jr2-instance";
11
11
 
12
- /** The Instance Harness's Deployment + Service name (ADR-0031): converged by `jr2 up` whenever any
12
+ /** The Instance Harness's StatefulSet + Service name (ADR-0031): converged by `jr2 up` whenever any
13
13
  * Agent definition declares `workspace: "none"`, and the deterministic Service DNS the Agent actor
14
14
  * resolves such a Turn to. Doubles as the delivery scope a Menu-only registration records — the
15
15
  * name the placement's Sandbox token is signed for (tokens.ts, ADR-0013), which its Custodian holds. The port is the
@@ -17,6 +17,19 @@ export const INSTANCE_SECRET = "jr2-instance";
17
17
  export const INSTANCE_HARNESS_SERVICE = "jr2-instance-harness";
18
18
  export const INSTANCE_HARNESS_PORT = 8080;
19
19
 
20
+ /** The Instance Effector (ADR-0068): its Deployment, headless Service, NetworkPolicy and Secret all
21
+ * carry this name. `jr2 up` converges them when a registered Machine places an Effect there. The
22
+ * port is the Effector's own (`JR2_EFFECTOR_PORT` default) — none of the Harness pod's ports. */
23
+ export const INSTANCE_EFFECTOR = "jr2-instance-effector";
24
+ export const EFFECTOR_PORT = 8082;
25
+
26
+ /** Where a Harness persists its conversations, and the env var that tells it so (ADR-0031): one
27
+ * fixed path in both placements — the Instance Harness's PersistentVolumeClaim (deploy.ts) and a
28
+ * Sandbox's emptyDir (sandbox-kube.ts), each mounted into the Harness container alone. */
29
+ export const CONVERSATIONS_DIR = "/conversations";
30
+ export const CONVERSATIONS_DIR_ENV = "JR2_CONVERSATIONS_DIR";
31
+ export const CONVERSATIONS_VOLUME = "conversations";
32
+
20
33
  /** The harness config ConfigMap the stock Harness boots from (ADR-0018): what this instance can
21
34
  * REACH — the custom model provider minus its key, and the catalog providers' gateways (ADR-0059). No Agents ride it: a Machine carries its own
22
35
  * (ADR-0049), and the definition rides each admission, so this holds deployment facts alone
@@ -50,17 +63,40 @@ export const CA_CONFIGMAP = "jr2-ca";
50
63
  /** Held secrets (ADR-0059). The per-Instance CA — its key included — that `jr2 up` alone reads over
51
64
  * the kube API, and that NO pod mounts: every leaf is issued before a pod starts. */
52
65
  export const HELD_CA_SECRET = "jr2-held-ca";
53
- /** The literal held values, one key per name — mounted into the Custodian and nothing else. */
66
+ /** The prefix of each pod's literal held values (`heldSecretsSecret`): one Secret per pod, one key
67
+ * per name it holds — mounted into that pod's Custodian and nothing else. */
54
68
  export const HELD_SECRETS_SECRET = "jr2-held-secrets";
55
- /** One leaf per bound `host:port` (`<stem>.crt`, `<stem>.key`) — the Custodian's alone. */
69
+ /** The literal held values of one pod (ADR-0059): `jr2-held-secrets-<pod>`. */
70
+ export function heldSecretsSecret(pod: string): string {
71
+ return `${HELD_SECRETS_SECRET}-${pod}`;
72
+ }
73
+ /** One leaf per bound `host:port` (`<stem>.crt`, `<stem>.key`) — the Custodians' alone. */
56
74
  export const HELD_TLS_SECRET = "jr2-held-tls";
57
- /** `held.json` (what `jr2 up` resolved) plus the Custodian's bootstrap and script. Always applied,
58
- * possibly holding no secret: every Harness pod runs a Custodian. */
75
+ /** What `jr2 up` DECLARES about held secrets: each pod's held manifest (`heldManifestKey` — names,
76
+ * bindings, headers; never a value) and the Custodian's program once (`CUSTODIAN_PROGRAM_KEY`).
77
+ * Nothing rendered per pod lives here: a shared object stops at 1 MiB, and Envoy's files are a
78
+ * pod's own, rendered where the pod is made (`sandboxCustodianConfigMap`,
79
+ * `INSTANCE_CUSTODIAN_CONFIGMAP`). Always applied, possibly holding no secret: every Harness pod
80
+ * runs a Custodian. */
59
81
  export const HELD_CONFIGMAP = "jr2-held";
60
- /** Where the Orchestrator reads `held.json`, per provision — a mount, so a held-secret edit reaches
61
- * future Sandboxes without rolling the Orchestrator (the `jr2-images` pattern, ADR-0038). */
82
+ /** Where the Orchestrator reads a Workspace's held manifest, per provision — a mount, so a
83
+ * held-secret edit reaches future Sandboxes without rolling the Orchestrator (the `jr2-images`
84
+ * pattern, ADR-0038). */
62
85
  export const HELD_MOUNT = "/etc/jr2/held";
63
- export const HELD_KEY = "held.json";
86
+ /** The `jr2-held` key of one pod's manifest. */
87
+ export const heldManifestKey = (pod: string): string => `held-${pod}.json`;
88
+ /** The `jr2-held` key of the Custodian's fixed Lua — `custodian.lua` as written, once per Instance;
89
+ * each pod's own script loads it by this name from its config mount. */
90
+ export const CUSTODIAN_PROGRAM_KEY = "program.lua";
91
+ /** The ConfigMap with the Instance Harness's Custodian's two rendered files (`envoy.json`,
92
+ * `custodian.lua`), written by `jr2 up`: the one pod it makes. */
93
+ export const INSTANCE_CUSTODIAN_CONFIGMAP = "jr2-held-custodian";
94
+ /** The ConfigMap with one Sandbox's Custodian's two rendered files, written by the Orchestrator at
95
+ * provision and owned by the Sandbox CR — the `<name>-token` family: it goes with the pod. */
96
+ export const sandboxCustodianConfigMap = (sandbox: string): string => `${sandbox}-custodian`;
97
+ /** The Instance Harness's pod, as its held objects are keyed. A Workspace's is `ws-<digest>`
98
+ * (`workspaceHeldPod`), so the two cannot collide. */
99
+ export const INSTANCE_HARNESS_HELD_POD = "instance";
64
100
 
65
101
  /** The Custodian's listeners (ADR-0059), all taken on every Harness pod (ADR-0005): the Harness's
66
102
  * HTTPS proxy on loopback, the kubelet's probe, and the control listener the Menu and the ask ride
@@ -100,3 +136,25 @@ export const ANNOTATION_REPO_LAST_ATTACHED = "jr2.dev/last-attached";
100
136
  * that started before the ask does not satisfy it. The Lease's shape: an annotation, written by
101
137
  * the Orchestrator, read by the operator. */
102
138
  export const askedAnnotation = (key: string): string => `jr2.dev/asked-${key}`;
139
+
140
+ /** The two PriorityClasses `jr2 up` creates, cluster-scoped, beside the CRDs (ADR-0060) — unless
141
+ * `priorityClasses` in jr2.config.ts names existing ones. `jr2-control` (100000,
142
+ * PreemptLowerPriority) is for the Orchestrator, the operator and the Repo cache agent: small, fixed,
143
+ * and needed by every Sandbox. `jr2-sandbox` (1000, preemptionPolicy Never) is for Sandboxes and the
144
+ * Instance Harness: an ordinary pod cannot preempt a live Workspace, and a waiting Sandbox evicts
145
+ * nobody. Both stay far below the system classes. */
146
+ export const PRIORITY_CLASS_CONTROL = "jr2-control";
147
+ export const PRIORITY_CLASS_SANDBOX = "jr2-sandbox";
148
+
149
+ /** The voluntary-disruption opt-outs (ADR-0060) a Sandbox pod and the Instance Harness pod carry:
150
+ * moving either loses work (`/work`; live conversations). No PodDisruptionBudget — `maxUnavailable:
151
+ * 0` blocks node upgrades without end. */
152
+ export const NO_DISRUPT_ANNOTATIONS = {
153
+ "cluster-autoscaler.kubernetes.io/safe-to-evict": "false",
154
+ "karpenter.sh/do-not-disrupt": "true",
155
+ } as const;
156
+
157
+ /** The API server's CA as the kubelet mounts it with the Pod's ServiceAccount. The Orchestrator
158
+ * Deployment points `NODE_EXTRA_CA_CERTS` at it, so the Orchestrator's own Kubernetes client
159
+ * (kube-client.ts, ADR-0063) trusts the API server with built-in `fetch` and no TLS code. */
160
+ export const SERVICE_ACCOUNT_CA = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
package/src/notices.ts ADDED
@@ -0,0 +1,141 @@
1
+ // The Orchestrator's notices (ADR-0062): what happened that the next Turn cannot find out for
2
+ // itself — a memory kill in its Workspace, a conversation that is new — kept as plain data in the
3
+ // run's persisted state (ADR-0007), beside the admission and epoch ledgers, and handed to the next
4
+ // admission in scope. The Harness writes every word; this is only the keeping.
5
+ //
6
+ // Delivered ONCE, and "delivered" means LEDGERED: a notice leaves the pending set in the same save
7
+ // that records the admission it rode. A restart before that save finds it still pending and the
8
+ // restored Turn's admission carries it again; a restart after it does not. Between `take` and that
9
+ // save the notice is RESERVED, in memory only, so a second admission in the same scope cannot carry
10
+ // it too — and a restart forgets reservations along with everything else in memory.
11
+
12
+ import type { Notice } from "./wire.ts";
13
+
14
+ /** Where an admission may hear notices: its continued conversation's base Instance ID (ADR-0057)
15
+ * and its Workspace's Sandbox name. Either may be absent — a fresh Turn has no conversation to be
16
+ * told about, and a Menu-only Agent works in no Workspace. `only`, when set, narrows the take to
17
+ * the notices raised by these sources: what a nudge or a wake carries (ADR-0062). `except` leaves
18
+ * out the notices raised by these: what a re-admission of the same Turn was told already;
19
+ * `exceptPrefix` every notice whose source starts so — all guard kills, when that is not known. */
20
+ export type NoticeScope = {
21
+ conversation?: string;
22
+ workspace?: string;
23
+ only?: readonly string[];
24
+ except?: readonly string[];
25
+ exceptPrefix?: string;
26
+ };
27
+
28
+ /** One pending notice: the notice, and the conversation or Workspace it goes to. `source` names
29
+ * the event that raised it, so the same event raised twice — two Turns lost to one kernel kill, a
30
+ * stream read replayed after a restore — is one notice. */
31
+ export type PendingNotice = { id: string; notice: Notice; to: string; source?: string };
32
+
33
+ /** The persisted form (`RunBlob.notices`). `seen` is every source ever raised, so a delivered
34
+ * notice is not raised again by a late or replayed witness of the same event. */
35
+ export type NoticeLedgerState = { pending: PendingNotice[]; seen: string[] };
36
+
37
+ /** What one admission takes: the notices it carries, and their ids for the ledger write. */
38
+ export type TakenNotices = {
39
+ ids: string[];
40
+ notices: Notice[];
41
+ /** Every notice taken, with the source it was raised with — what `restore` puts back when no
42
+ * model heard the admission. */
43
+ raised?: Array<{ notice: Notice; source?: string }>;
44
+ };
45
+
46
+ export class NoticeLedger {
47
+ readonly #pending: PendingNotice[];
48
+ readonly #seen: Set<string>;
49
+ readonly #reserved = new Set<string>();
50
+ readonly #newId: () => string;
51
+
52
+ constructor(state: NoticeLedgerState | undefined, newId: () => string) {
53
+ this.#pending = [...(state?.pending ?? [])];
54
+ this.#seen = new Set(state?.seen ?? []);
55
+ this.#newId = newId;
56
+ }
57
+
58
+ /** Raise a notice for `to` (a conversation's base id, or a Sandbox name — the notice's own
59
+ * `scope` says which). `older` are the sources of earlier events that this one tells the same as —
60
+ * a burst of guard kills, read at once: they are marked raised and give no notice of their own.
61
+ * Answers whether anything changed — a notice pending, or a source marked — so the caller saves
62
+ * only then. */
63
+ raise(notice: Notice, to: string, source?: string, older: readonly string[] = []): boolean {
64
+ let changed = false;
65
+ for (const seen of older) {
66
+ if (this.#seen.has(seen)) continue;
67
+ this.#seen.add(seen);
68
+ changed = true;
69
+ }
70
+ if (source !== undefined) {
71
+ if (this.#seen.has(source)) return changed;
72
+ this.#seen.add(source);
73
+ } else {
74
+ // A notice with no source is one notice with a pending twin that has none either. A notice
75
+ // WITH a source keeps its own entry, so a take by source (`only`, `except`) finds it; equal
76
+ // ones are one notice in the admission (`take`).
77
+ const same = JSON.stringify(notice);
78
+ if (this.#pending.some((p) => p.source === undefined && p.to === to && JSON.stringify(p.notice) === same)) {
79
+ return changed;
80
+ }
81
+ }
82
+ this.#pending.push({ id: this.#newId(), notice, to, ...(source === undefined ? {} : { source }) });
83
+ return true;
84
+ }
85
+
86
+ /** Reserve every unreserved notice in this admission's scope, in the order raised. Equal notices —
87
+ * two kills that tell the same thing — are said once, and every id is reserved. */
88
+ take(scope: NoticeScope): TakenNotices {
89
+ const taken = this.#pending.filter(
90
+ (p) =>
91
+ !this.#reserved.has(p.id) &&
92
+ (scope.only === undefined || (p.source !== undefined && scope.only.includes(p.source))) &&
93
+ (scope.except === undefined || p.source === undefined || !scope.except.includes(p.source)) &&
94
+ (scope.exceptPrefix === undefined || p.source === undefined || !p.source.startsWith(scope.exceptPrefix)) &&
95
+ (p.notice.scope === "conversation" ? p.to === scope.conversation : p.to === scope.workspace),
96
+ );
97
+ for (const p of taken) this.#reserved.add(p.id);
98
+ const said = new Set<string>();
99
+ const notices = taken.flatMap((p) => {
100
+ const key = JSON.stringify(p.notice);
101
+ if (said.has(key)) return [];
102
+ said.add(key);
103
+ return [p.notice];
104
+ });
105
+ return {
106
+ ids: taken.map((p) => p.id),
107
+ notices,
108
+ raised: taken.map((p) => ({ notice: p.notice, ...(p.source === undefined ? {} : { source: p.source }) })),
109
+ };
110
+ }
111
+
112
+ /** Put back a notice an admission carried that no model heard, with the source it was raised
113
+ * with — `seen` already holds it, so a raise would drop it. Nothing changes when a notice with
114
+ * that source is pending already. Answers whether anything changed. */
115
+ restore(notice: Notice, to: string, source?: string): boolean {
116
+ if (source === undefined) return this.raise(notice, to);
117
+ if (this.#pending.some((p) => p.source === source && p.to === to)) return false;
118
+ this.#seen.add(source);
119
+ this.#pending.push({ id: this.#newId(), notice, to, source });
120
+ return true;
121
+ }
122
+
123
+ /** The admission that carried them was ledgered: they are delivered. */
124
+ delivered(ids: readonly string[]): void {
125
+ for (const id of ids) {
126
+ this.#reserved.delete(id);
127
+ const at = this.#pending.findIndex((p) => p.id === id);
128
+ if (at !== -1) this.#pending.splice(at, 1);
129
+ }
130
+ }
131
+
132
+ /** The admission that carried them failed: they are pending again, for the next one. */
133
+ release(ids: readonly string[]): void {
134
+ for (const id of ids) this.#reserved.delete(id);
135
+ }
136
+
137
+ /** The persisted form — reserved notices included, because reserved is not delivered. */
138
+ state(): NoticeLedgerState {
139
+ return { pending: [...this.#pending], seen: [...this.#seen] };
140
+ }
141
+ }