@jr2/orchestrator 0.4.1 → 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.
- package/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- 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
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +5 -3
- package/src/actor.ts +541 -79
- package/src/agent.ts +114 -38
- package/src/ambient.ts +2 -1
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +169 -37
- package/src/charts.ts +34 -6
- package/src/config.ts +430 -178
- package/src/custodian.ts +74 -22
- package/src/customize.ts +188 -46
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect.ts +341 -44
- package/src/effector-wire.ts +160 -11
- package/src/fail.ts +209 -0
- package/src/harness-client.ts +541 -47
- package/src/http-effect.ts +495 -53
- package/src/http.ts +427 -16
- package/src/index.ts +60 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +84 -0
- package/src/machine-doc.ts +21 -3
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/notices.ts +66 -13
- package/src/open-body.ts +159 -23
- package/src/open-effect.ts +81 -8
- package/src/open.ts +59 -6
- package/src/parts.ts +184 -13
- package/src/party.ts +68 -2
- package/src/pick.ts +463 -11
- package/src/pool.ts +247 -36
- package/src/registration.ts +83 -4
- package/src/repos.ts +2 -1
- package/src/run-host.ts +565 -31
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +273 -38
- package/src/sandbox-kube.ts +3 -1
- package/src/secret-placement.ts +52 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +239 -56
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/vocabulary.ts +69 -14
- package/src/wire.ts +90 -16
- package/src/workspace.ts +697 -175
package/src/setup.ts
CHANGED
|
@@ -51,33 +51,59 @@ import { gate } from "./gate.ts";
|
|
|
51
51
|
import { attachLent, isOpenBody, openBodyDeclarationOf, type LentParts } from "./open-body.ts";
|
|
52
52
|
import { refuseInlineParts } from "./parts.ts";
|
|
53
53
|
import { partyWarnings, refuseUnknownSentBy, SENT_BY, sentByGuard } from "./party.ts";
|
|
54
|
+
import { refuseLostOutput } from "./run-output.ts";
|
|
55
|
+
import { refuseUnroutedAgentFault } from "./mechanism-routes.ts";
|
|
54
56
|
import { boundEpoch, boundRunId, continuedIid } from "./registration.ts";
|
|
55
57
|
import { attachInputSchema, attachLoadWarnings, attachSeats, attachVocabulary } from "./vocabulary.ts";
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
|
-
* The
|
|
59
|
-
*
|
|
60
|
-
*
|
|
60
|
+
* The marker that `compileChart` puts on the config of a Chart's Machine (ADR-0070). In a Chart the
|
|
61
|
+
* `agent.fault` route is optional: the Open body sends an `agent.fault` that no transition takes to
|
|
62
|
+
* the Machine that invoked it, so `createMachine` does not refuse a Chart state with no route. Not on the Authoring
|
|
63
|
+
* surface (index.ts). The load walk (`partsOf`) refuses an author's Machine with no route, with or
|
|
64
|
+
* without the marker (ADR-0074).
|
|
61
65
|
*/
|
|
66
|
+
export const COMPILED_CHART: unique symbol = Symbol("jr2.compiledChart");
|
|
67
|
+
|
|
68
|
+
// Injected into every jr2Setup union. Dotted names by construction (`NAME_RE` forbids dots in
|
|
69
|
+
// workflow event names), so they can never collide with a def.
|
|
70
|
+
/** The events that jr2 itself sends into each Machine that `jr2Setup` makes. A Mechanism event's
|
|
71
|
+
* name has a dot, and a workflow event's name cannot, so the two never collide. */
|
|
62
72
|
export type MechanismEvent = FaultTelemetry | WorkspaceLost;
|
|
63
73
|
|
|
74
|
+
// A Workspace's Continuity broke (ADR-0021): its Sandbox is gone, or Lost. `reason` and `message`
|
|
75
|
+
// are for a body whose policy tells the causes apart, or only reports them; no policy needs them.
|
|
64
76
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
77
|
+
* jr2 sends `workspace.lost` to the body of a `workspace()` when its Sandbox is gone: its pod was
|
|
78
|
+
* evicted or deleted, or its node went. jr2 does not make the Sandbox again, and the commits that
|
|
79
|
+
* were not pushed go with it. The body decides what to do next: route the event with
|
|
80
|
+
* `on: { "workspace.lost": … }`, usually once, on the body's root. `workspace()` refuses a body
|
|
81
|
+
* with a state that is not final and has no route for it on itself or on a state above. When the
|
|
82
|
+
* event comes and no transition takes it (the route's guard is false), the run ends `error`, with
|
|
83
|
+
* the event, the state and the reason in its fault.
|
|
69
84
|
*/
|
|
70
|
-
export type WorkspaceLost = {
|
|
85
|
+
export type WorkspaceLost = {
|
|
86
|
+
type: "workspace.lost";
|
|
87
|
+
/** Why the Sandbox is gone, for a person or a log: the pod's reason (`Evicted`, `NodeShutdown`),
|
|
88
|
+
* the operator's (`PodDeleted`, `NodeLost`), or `Deleted`. A body can act without it. */
|
|
89
|
+
reason?: string;
|
|
90
|
+
/** More text about the loss, when jr2 has it. */
|
|
91
|
+
message?: string;
|
|
92
|
+
};
|
|
71
93
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
94
|
+
// The defs' derived types plus the mechanism's (ADR-0015).
|
|
95
|
+
/** The type of `event` in a Machine that `jr2Setup` makes: each workflow event in its `events`,
|
|
96
|
+
* and each Mechanism event. A workflow event that an Agent or a Gate sent has `event.sender`. An
|
|
97
|
+
* event that the Machine sent to itself (`raise`, `sendTo`) has no `sender`. */
|
|
75
98
|
export type WorkflowEvent<TDefs extends readonly EventDef[]> =
|
|
76
99
|
| (EventFrom<TDefs[number]> & { readonly sender?: Sender })
|
|
77
100
|
| MechanismEvent;
|
|
78
101
|
|
|
79
102
|
/** The guard jr2Setup registers for every Machine — `sentBy(id)` (ADR-0015) — in the typed guard
|
|
80
|
-
* map, so `sentBy("coder")` and `and([sentBy("coder"), "ready"])` type-check with no cast.
|
|
103
|
+
* map, so `sentBy("coder")` and `and([sentBy("coder"), "ready"])` type-check with no cast.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
81
107
|
type SentByGuards = { [SENT_BY]: { id: string } };
|
|
82
108
|
|
|
83
109
|
/** The jr2 actor every workflow can invoke by name without listing it (ADR-0015). `gate` is the
|
|
@@ -86,25 +112,154 @@ type SentByGuards = { [SENT_BY]: { id: string } };
|
|
|
86
112
|
const jr2Actors = { gate };
|
|
87
113
|
type JR2Actors = typeof jr2Actors;
|
|
88
114
|
|
|
89
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Consumer actors merge OVER the pre-registered set: same name → the consumer's logic wins.
|
|
117
|
+
*
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
90
120
|
type MergedActors<TActors extends Record<string, UnknownActorLogic>> = Omit<JR2Actors, keyof TActors> & TActors;
|
|
91
121
|
|
|
92
122
|
// Local equivalents of xstate's non-exported setup() mapped helpers (setup.d.ts) — same shapes,
|
|
93
123
|
// so the declared signature below composes with the public `ActionFunction`/`GuardPredicate`.
|
|
124
|
+
/** @internal */
|
|
94
125
|
type ToParameterizedObject<T extends Record<string, ParameterizedObject["params"] | undefined>> = {
|
|
95
126
|
[K in keyof T & string]: { type: K; params: T[K] };
|
|
96
127
|
}[keyof T & string];
|
|
128
|
+
/** @internal */
|
|
97
129
|
type ToProvidedActor<TActors extends Record<string, UnknownActorLogic>> = {
|
|
98
130
|
[K in keyof TActors & string]: { src: K; logic: TActors[K]; id: string | undefined };
|
|
99
131
|
}[keyof TActors & string];
|
|
100
132
|
|
|
101
133
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
134
|
+
* Set up a Machine that takes workflow events: the jr2 form of xstate's `setup()`. Write a Machine
|
|
135
|
+
* with it when an Agent or a Gate sends events into the Machine, or when the Machine declares a
|
|
136
|
+
* door. Call `.createMachine(config)` on the result, as with `setup()`. The Machine you get is a
|
|
137
|
+
* plain xstate Machine.
|
|
138
|
+
*
|
|
139
|
+
* What it adds to `setup()`:
|
|
140
|
+
* - `events` replaces `types.events`. The event type of the Machine comes from the
|
|
141
|
+
* `defineEvent` events you list, plus the events jr2 itself sends (`agent.fault`,
|
|
142
|
+
* `workspace.lost`). An event that a party sent has `event.sender`. A Machine that takes no
|
|
143
|
+
* workflow event leaves out `events`.
|
|
144
|
+
* - The `gate` actor is ready to invoke by name: `invoke: { src: "gate" }`.
|
|
145
|
+
* - Each Agent slot (`actors: { coder: agent(…) }`) gets its Menu from the states that invoke it.
|
|
146
|
+
* - The `sentBy(id)` guard is ready to use.
|
|
147
|
+
* - `createMachine` also takes `input`: a zod object for the input that starts a run. jr2 checks
|
|
148
|
+
* each start against it. Without it, a run can start with any input. The `setup()` of xstate
|
|
149
|
+
* does not read this `input`, so a Machine with a door uses `jr2Setup`, also when it takes no
|
|
150
|
+
* workflow event. `jr2 up` and the publish door refuse a registered Machine that has a zod
|
|
151
|
+
* `input` if `jr2Setup` did not make the Machine: ``workflow "<name>": machine "<id>" has a zod `input` on
|
|
152
|
+
* createMachine, but jr2Setup did not make this Machine, so no door reads that input …``
|
|
153
|
+
*
|
|
154
|
+
* The output of a run is the root `output` of `createMachine`, beside `id` and `states`, as in
|
|
155
|
+
* xstate v5. xstate does not read the `output` of a final state as the output of the Machine. It
|
|
156
|
+
* gives that output to the root `output` as `event.output`. Write
|
|
157
|
+
* `output: ({ event }) => event.output as Output` at the root, or make the output from `context`
|
|
158
|
+
* there. `createMachine` refuses an `output` on a final state directly below the root when the root
|
|
159
|
+
* has no `output` (see the list below).
|
|
160
|
+
*
|
|
161
|
+
* `createMachine` refuses a Machine that it cannot run as written. Among the refusals:
|
|
162
|
+
* - an `on` key that no listed event has: ``machine "<id>" handles event "<key>", which no def in
|
|
163
|
+
* jr2Setup({ events }) declares (declared: <names>) — a typo, or a missing defineEvent``
|
|
164
|
+
* - a listed event that no state takes: ``machine "<id>" declares "<name>", which no state handles
|
|
165
|
+
* — a pick no state takes is on no Menu and no Gate: handle it, or drop it from
|
|
166
|
+
* jr2Setup({ events })``
|
|
167
|
+
* - an entry of `events` that `defineEvent` did not make, or one name listed twice:
|
|
168
|
+
* ``machine "<id>": `events` entry is not a defineEvent() def: <entry>``,
|
|
169
|
+
* ``machine "<id>": duplicate event "<name>" in `events` ``
|
|
170
|
+
* - an `agent()`, an Effect or a `workspace()` written on `invoke.src` instead of under `actors`:
|
|
171
|
+
* ``machine "<id>": state "<state>" invokes <part> inline — a Part is a key of actors, so it has
|
|
172
|
+
* a name that the ledger, the report and customize() share: actors: { <name>: <factory>(...) },
|
|
173
|
+
* then invoke: { src: "<name>" }``. `<part>` is `an Agent`, `an Effect` or `a Workspace`.
|
|
174
|
+
* `<factory>` is `agent`, the Effect's kind (such as `command`), or `workspace`.
|
|
175
|
+
* - an `output` on a final state directly below the root, when the root has no `output`:
|
|
176
|
+
* ``machine "<id>": final state "<state>" declares an output, and the root has no output — xstate
|
|
177
|
+
* v5 gives a run only its root output, so this output reaches nothing and the run ends with none:
|
|
178
|
+
* write it at the root, as output: ({ context }) => ..., or keep it on the final state and pass it
|
|
179
|
+
* on with output: ({ event }) => event.output at the root``. With more than one such final state,
|
|
180
|
+
* it names each one and puts the words in the plural: ``final states "<state>", "<state>"
|
|
181
|
+
* declare an output``, ``these outputs reach nothing``, ``write one at the root`` and ``keep
|
|
182
|
+
* them on the final states and pass them on``. An `output` on a
|
|
183
|
+
* final state inside a compound state is legal: the `onDone` of that compound state gets it as
|
|
184
|
+
* `event.output`.
|
|
185
|
+
* - a parallel state at or below a state that invokes an Agent: ``machine "<id>": state "<state>"
|
|
186
|
+
* is parallel, at or below Agent "<slot>"'s state "<state>" — a parallel region below an Agent's
|
|
187
|
+
* state is reserved for picks the Agent does not wait for, which jr2 does not build; background
|
|
188
|
+
* work goes above the Agent's state``
|
|
189
|
+
* - a state below an Agent's state that invokes something and that nothing enters: ``machine
|
|
190
|
+
* "<id>": state "<state>" below Agent "<slot>"'s state "<state>" is entered by nothing, so what
|
|
191
|
+
* it invokes (<src>) never runs — target it from a pick or a route, or delete it``
|
|
192
|
+
* - a state that invokes an Agent when that state and each state above it have no `agent.fault`
|
|
193
|
+
* route: ``machine "<id>": state "<state>" invokes an Agent, and no route for "agent.fault" is
|
|
194
|
+
* on it or on a state above — jr2 sends agent.fault when the Agent's Turn cannot go on, and with
|
|
195
|
+
* no route the run faults: route it on that state or on a state above it, as on: {
|
|
196
|
+
* "agent.fault": "<the state that handles it>" } (ADR-0074)``. A state that invokes an Open body
|
|
197
|
+
* that lends an Agent or allows a model gets the same line, with `invokes an Open body that can
|
|
198
|
+
* run an Agent`: the body sends it a Chart's unrouted `agent.fault`. When both kinds have no route,
|
|
199
|
+
* one line names both, joined by `, and`. One route on the root is a route for each state. A
|
|
200
|
+
* Chart's Machine does not get this refusal: in a Chart the route is optional (ADR-0070).
|
|
201
|
+
*
|
|
202
|
+
* It also refuses a `sentBy` that names no party (see `sentBy`). It refuses a pick param that
|
|
203
|
+
* nothing reads, where the pick enters a state that invokes an Effect (see `fromPick`). Shapes that
|
|
204
|
+
* are legal but probably not what you mean, such as a mode that nothing enters, are warnings that
|
|
205
|
+
* `jr2 up` prints.
|
|
206
|
+
*
|
|
207
|
+
* @param def - What `setup()` takes, with `events` in place of `types.events`. A Machine that
|
|
208
|
+
* needs none of it calls `jr2Setup()`.
|
|
209
|
+
* @returns What `setup()` returns. Call `.createMachine(config)` on it.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { z } from "zod";
|
|
214
|
+
* import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
|
|
215
|
+
*
|
|
216
|
+
* const proposed = defineEvent({
|
|
217
|
+
* name: "proposed",
|
|
218
|
+
* description: "Call this with your plan.",
|
|
219
|
+
* input: z.object({ plan: z.string() }),
|
|
220
|
+
* });
|
|
221
|
+
* const approve = defineEvent({ name: "approve", input: z.object({}) });
|
|
222
|
+
* const reject = defineEvent({ name: "reject", input: z.object({ reason: z.string() }) });
|
|
223
|
+
*
|
|
224
|
+
* export const machine = jr2Setup({
|
|
225
|
+
* types: {} as { input: { goal: string }; context: { goal: string } },
|
|
226
|
+
* events: [proposed, approve, reject],
|
|
227
|
+
* actors: {
|
|
228
|
+
* planner: agent({
|
|
229
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
230
|
+
* instructions: "You plan small releases.",
|
|
231
|
+
* workspace: "none",
|
|
232
|
+
* }),
|
|
233
|
+
* fail: fail(),
|
|
234
|
+
* },
|
|
235
|
+
* }).createMachine({
|
|
236
|
+
* id: "plan",
|
|
237
|
+
* input: z.object({ goal: z.string() }),
|
|
238
|
+
* context: ({ input }) => ({ goal: input.goal }),
|
|
239
|
+
* on: { "agent.fault": ".failed" },
|
|
240
|
+
* initial: "planning",
|
|
241
|
+
* states: {
|
|
242
|
+
* planning: {
|
|
243
|
+
* invoke: { src: "planner", input: ({ context }) => ({ prompt: `Plan this: ${context.goal}` }) },
|
|
244
|
+
* on: { proposed: "review" },
|
|
245
|
+
* },
|
|
246
|
+
* review: {
|
|
247
|
+
* invoke: { src: "gate" },
|
|
248
|
+
* on: { approve: "approved", reject: "planning" },
|
|
249
|
+
* },
|
|
250
|
+
* approved: { type: "final" },
|
|
251
|
+
* failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
252
|
+
* },
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @see docs/guides/write-a-workflow.md
|
|
257
|
+
* @see docs/concepts/machines-and-workflows.md
|
|
258
|
+
* @see docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md
|
|
104
259
|
*/
|
|
105
260
|
export function jr2Setup<
|
|
106
261
|
TContext extends MachineContext = MachineContext,
|
|
107
|
-
const TDefs extends readonly EventDef[] = readonly
|
|
262
|
+
const TDefs extends readonly EventDef[] = readonly [],
|
|
108
263
|
TActors extends Record<string, UnknownActorLogic> = {},
|
|
109
264
|
TActions extends Record<string, ParameterizedObject["params"] | undefined> = {},
|
|
110
265
|
TGuards extends Record<string, ParameterizedObject["params"] | undefined> = {},
|
|
@@ -114,44 +269,56 @@ export function jr2Setup<
|
|
|
114
269
|
TOutput extends NonReducibleUnknown = NonReducibleUnknown,
|
|
115
270
|
TEmitted extends EventObject = EventObject,
|
|
116
271
|
TMeta extends MetaObject = MetaObject,
|
|
117
|
-
>(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
272
|
+
>(
|
|
273
|
+
def: {
|
|
274
|
+
/** The types of the Machine, as in `setup()`: `context`, `input`, `output`, `tags`, `emitted`
|
|
275
|
+
* and `meta`. Not `events`: the event type comes from `events`. */
|
|
276
|
+
types?: {
|
|
277
|
+
context?: TContext;
|
|
278
|
+
input?: TInput;
|
|
279
|
+
output?: TOutput;
|
|
280
|
+
tags?: TTag;
|
|
281
|
+
emitted?: TEmitted;
|
|
282
|
+
meta?: TMeta;
|
|
283
|
+
};
|
|
284
|
+
// The single source for types, validation and delivery.
|
|
285
|
+
/** The workflow events this Machine takes: each one made by `defineEvent`. Leave it out when the
|
|
286
|
+
* Machine takes none. Each listed event must be the name of an `on` key. Each `on` key must be
|
|
287
|
+
* listed, if it is not a jr2 or xstate event (an event with a `.` in its name). */
|
|
288
|
+
events?: TDefs;
|
|
289
|
+
/** The actors the states invoke by name, as in `setup()`: Agent slots (`agent(…)`), Effects
|
|
290
|
+
* (`command(…)`, `http(…)`, `push(…)`), child Machines, `workspace(…)` and plain xstate logic.
|
|
291
|
+
* The key is the slot's name. An actor named `gate` replaces jr2's Gate. */
|
|
292
|
+
actors?: TActors;
|
|
293
|
+
/** Named actions, as in `setup()`. Their `event` has the type of this Machine's events. */
|
|
294
|
+
actions?: {
|
|
295
|
+
[K in keyof TActions]: ActionFunction<
|
|
296
|
+
TContext,
|
|
297
|
+
WorkflowEvent<TDefs>,
|
|
298
|
+
WorkflowEvent<TDefs>,
|
|
299
|
+
TActions[K],
|
|
300
|
+
ToProvidedActor<MergedActors<TActors>>,
|
|
301
|
+
ToParameterizedObject<TActions>,
|
|
302
|
+
ToParameterizedObject<TGuards>,
|
|
303
|
+
TDelay,
|
|
304
|
+
TEmitted
|
|
305
|
+
>;
|
|
306
|
+
};
|
|
307
|
+
/** Named guards, as in `setup()`. `sentBy` is there already; do not name a guard `jr2.sentBy`. */
|
|
308
|
+
guards?: {
|
|
309
|
+
[K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>;
|
|
310
|
+
};
|
|
311
|
+
/** Named delays, as in `setup()`. */
|
|
312
|
+
delays?: {
|
|
313
|
+
[K in TDelay]: DelayConfig<
|
|
314
|
+
TContext,
|
|
315
|
+
WorkflowEvent<TDefs>,
|
|
316
|
+
ToParameterizedObject<TActions>["params"],
|
|
317
|
+
WorkflowEvent<TDefs>
|
|
318
|
+
>;
|
|
319
|
+
};
|
|
320
|
+
} = {},
|
|
321
|
+
): SetupReturn<
|
|
155
322
|
TContext,
|
|
156
323
|
WorkflowEvent<TDefs>,
|
|
157
324
|
MergedActors<TActors>,
|
|
@@ -165,6 +332,8 @@ export function jr2Setup<
|
|
|
165
332
|
TEmitted,
|
|
166
333
|
TMeta
|
|
167
334
|
> {
|
|
335
|
+
// Like `setup()`, but `types.events` is gone (ADR-0015): the event union derives from `events`
|
|
336
|
+
// (defineEvent defs, taken as values) plus the mechanism events.
|
|
168
337
|
const inner = setup({
|
|
169
338
|
types: def.types,
|
|
170
339
|
actors: { ...jr2Actors, ...(def.actors ?? {}) },
|
|
@@ -179,11 +348,16 @@ export function jr2Setup<
|
|
|
179
348
|
// machine receives at creation — and is pulled OFF before xstate sees it: a zod schema is
|
|
180
349
|
// not machine structure, and it must never reach the fingerprint/serialization paths that
|
|
181
350
|
// read `machine.config`. It is attached beside the vocabulary below.
|
|
182
|
-
|
|
351
|
+
// A compiled Chart (ADR-0070) is marked, and the marker is pulled off as `input` is.
|
|
352
|
+
const {
|
|
353
|
+
input: inputSchema,
|
|
354
|
+
[COMPILED_CHART]: compiledChart,
|
|
355
|
+
...machineConfig
|
|
356
|
+
} = config as { input?: z.ZodObject; [COMPILED_CHART]?: true } & Record<string, unknown>;
|
|
183
357
|
|
|
184
358
|
// Resolve the defs first (duplicates and reserved semantics fail HERE, naming the machine —
|
|
185
359
|
// the same loud failure `eventMap` gave the manifest, moved to machine-build time)…
|
|
186
|
-
const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events);
|
|
360
|
+
const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events ?? []);
|
|
187
361
|
|
|
188
362
|
// Every Open body slot (ADR-0070), found by its brand: each name it lends resolves against THIS
|
|
189
363
|
// setup's own parts, or the Machine is refused naming it. The resolved set is stamped on the slot,
|
|
@@ -231,6 +405,12 @@ export function jr2Setup<
|
|
|
231
405
|
|
|
232
406
|
// A Part written inline has no name (ADR-0049, ADR-0067): refused, with the slot form.
|
|
233
407
|
refuseInlineParts(machine);
|
|
408
|
+
// A final state's output with no root output (ADR-0015): the run would end with none.
|
|
409
|
+
refuseLostOutput(machine);
|
|
410
|
+
// A state that invokes an Agent, or an Open body that can run one, with no `agent.fault` route
|
|
411
|
+
// (ADR-0074): the run would wait for ever. Not in a compiled Chart: there the route is optional,
|
|
412
|
+
// and the Open body sends the event that no transition takes to the Machine that invoked it (ADR-0070).
|
|
413
|
+
if (compiledChart !== true) refuseUnroutedAgentFault(machine);
|
|
234
414
|
refuseParallelBelowAgents(machine, def.actors ?? {});
|
|
235
415
|
refuseUnknownSentBy(machine);
|
|
236
416
|
// Before the Effect checks: they ask who may send a writing (`mayTake`), which reads its audience.
|
|
@@ -894,6 +1074,9 @@ function wrapAgentInput(orig: unknown, derived: string[], agentName: string, men
|
|
|
894
1074
|
// jr2 mints every id (ADR-0016, ADR-0057): the Turn's input carries no id to honor, so
|
|
895
1075
|
// nothing a Machine writes can address a conversation jr2 did not derive.
|
|
896
1076
|
instanceId: mintIid(consumer, agentName, args.self),
|
|
1077
|
+
// One per invocation (ADR-0016): two Turns that continue one conversation share the iid, the
|
|
1078
|
+
// admission ledger's key, and a restore must not re-attach one Turn to the other's admission.
|
|
1079
|
+
turnId: randomUUID(),
|
|
897
1080
|
endpoint: consumer.endpoint,
|
|
898
1081
|
sandbox: consumer.sandbox,
|
|
899
1082
|
prompt: consumer.prompt,
|
package/src/size.ts
CHANGED
|
@@ -23,15 +23,73 @@
|
|
|
23
23
|
/** A Kubernetes quantity: `"2Gi"`, `"1500m"`, `"2"`, or a plain number (`2` cpus, bytes of memory). */
|
|
24
24
|
export type Quantity = string | number;
|
|
25
25
|
|
|
26
|
+
// A Size (ADR-0060, GLOSSARY.md "Size"): `limits.memory` and `limits.cpu`, nothing else. A field
|
|
27
|
+
// left out resolves down the chain (`resolveSize`) — the Instance's `sandbox.resources`, then the
|
|
28
|
+
// kit default (`KIT_SIZE`). The same shape is the User Container's split (`userSeatOf`,
|
|
29
|
+
// `splitSize`), where a field left out means the seat has no limit of its own for it and shares
|
|
30
|
+
// the pod's budget.
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* kit default of 2Gi and 1 cpu.
|
|
32
|
+
* The Size of a Workspace: the most memory and cpu that its whole Sandbox can use. jr2 reserves
|
|
33
|
+
* all of it on the node. Thus a Sandbox never gets less than its Size and never uses more.
|
|
30
34
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
35
|
+
* Write it as `{ limits: { memory, cpu } }`. You can write it in three places: the `resources`
|
|
36
|
+
* option of `workspace()`, `customize(machine, { resources })`, and `sandbox.resources` in
|
|
37
|
+
* `jr2.config.ts`. The Size is a fact of the Machine. The Instance's `sandbox.resources` applies
|
|
38
|
+
* only to a Workspace that states no Size. A field that neither states comes from the kit default:
|
|
39
|
+
* memory `2Gi`, cpu `1`.
|
|
40
|
+
*
|
|
41
|
+
* The same shape is the User Container's part of the Size, in `user: { image, resources }`. There,
|
|
42
|
+
* a field you leave out gives the User Container no limit of its own: it shares the pod's budget.
|
|
43
|
+
*
|
|
44
|
+
* It refuses any key but `limits.memory` and `limits.cpu`, where you write it. The message names
|
|
45
|
+
* the field: `<where>` is where you wrote the Size, for example `workspace(): resources`.
|
|
46
|
+
*
|
|
47
|
+
* - `<where>.requests is not accepted — jr2 reserves the whole Size, so every request equals its
|
|
48
|
+
* limit — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template
|
|
49
|
+
* size (ADR-0060)`
|
|
50
|
+
* - `<where>.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing else —
|
|
51
|
+
* Agent Substrate's template size (ADR-0060)`
|
|
52
|
+
* - `<where>.limits.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing
|
|
53
|
+
* else — Agent Substrate's template size (ADR-0060)`
|
|
54
|
+
* - `<where>.limits.memory: <value> is not a positive quantity (e.g. "2Gi", "512Mi")`, and for
|
|
55
|
+
* cpu, `<where>.limits.cpu: <value> is not a positive quantity (e.g. "2", "1500m")`
|
|
56
|
+
*
|
|
57
|
+
* `jr2 up` also refuses a Size that leaves the Harness container less than its floor. The message
|
|
58
|
+
* starts with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or
|
|
59
|
+
* with `the Size leaves the Harness container no cpu:`. The text that follows names each number
|
|
60
|
+
* and where it came from.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { customize, type Size } from "@jr2/orchestrator";
|
|
65
|
+
* import { task } from "@jr2/machines";
|
|
66
|
+
*
|
|
67
|
+
* const size: Size = { limits: { memory: "4Gi", cpu: "2" } };
|
|
68
|
+
*
|
|
69
|
+
* export const machine = customize(task, {
|
|
70
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
71
|
+
* agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
|
|
72
|
+
* resources: size,
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @see docs/guides/size-a-workspace.md
|
|
77
|
+
* @see docs/concepts/workspaces.md
|
|
78
|
+
* @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
|
|
33
79
|
*/
|
|
34
|
-
export type Size = {
|
|
80
|
+
export type Size = {
|
|
81
|
+
/** The two limits. Write `limits: {}` to state no field: each field then comes from the next
|
|
82
|
+
* source. */
|
|
83
|
+
limits: {
|
|
84
|
+
/** The memory limit, as a Kubernetes quantity: `"4Gi"`, `"512Mi"`, or a number of bytes.
|
|
85
|
+
* It must be more than zero. When you leave it out, it comes from the next source, as above. */
|
|
86
|
+
memory?: Quantity;
|
|
87
|
+
/** The cpu limit, as a Kubernetes quantity: `"2"`, `"1500m"`, or a number of cpus. It must be
|
|
88
|
+
* more than zero. jr2 enforces it. When you leave it out, it comes from the next source, as
|
|
89
|
+
* above. */
|
|
90
|
+
cpu?: Quantity;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
35
93
|
|
|
36
94
|
/** The kit's default Size (ADR-0060). Node gives each process a V8 heap of about 55% of its
|
|
37
95
|
* container limit, and `tsc` on a mid-size repo aborted below about 1.1Gi, so 1Gi fails a common
|
|
@@ -303,11 +361,65 @@ export function splitSize(size: ResolvedSize, user?: Size): SizeSplit {
|
|
|
303
361
|
|
|
304
362
|
// --- the User Container seat -------------------------------------------------------------------
|
|
305
363
|
|
|
364
|
+
// The User Container as a Machine states it (ADR-0005, ADR-0060): an image in ADR-0037's two
|
|
365
|
+
// shapes, and optionally its split of the Size. The object form exists for the split alone.
|
|
306
366
|
/**
|
|
307
|
-
* The User Container
|
|
308
|
-
*
|
|
367
|
+
* The User Container in its object form: its image, and its part of the Workspace's Size. The User
|
|
368
|
+
* Container is an optional container of your own in the Sandbox pod. It runs its own entrypoint
|
|
369
|
+
* with `/work` mounted, and jr2 never builds into it, probes it or sends it commands. Use it for a
|
|
370
|
+
* service that must run without an Agent, such as an sshd.
|
|
371
|
+
*
|
|
372
|
+
* Write it in the `user` option of `workspace()` or of `customize()`. That option also takes the
|
|
373
|
+
* image as a string; use this object form only to give the User Container a part of the Size. A
|
|
374
|
+
* `customize()` that names `user` replaces the whole User Container, its part of the Size
|
|
375
|
+
* included.
|
|
376
|
+
*
|
|
377
|
+
* It refuses, where you write it. `<where>` is `workspace()` or `customize()`:
|
|
378
|
+
*
|
|
379
|
+
* - an image that is not a non-empty string. The message starts with
|
|
380
|
+
* `` <where>: `user` must be a non-empty string (an image), or { image, resources? } ``.
|
|
381
|
+
* - `<where>: user.<key> is not accepted — the User Container is { image, resources? } (ADR-0005,
|
|
382
|
+
* ADR-0060)`
|
|
383
|
+
*
|
|
384
|
+
* `resources` is checked as a {@link Size}, with the same refusals.
|
|
385
|
+
*
|
|
386
|
+
* `jr2 up` refuses a part that leaves the Harness container less than its floor. The message starts
|
|
387
|
+
* with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or with
|
|
388
|
+
* `the Size leaves the Harness container no cpu:`. The text that follows names each number and
|
|
389
|
+
* where it came from.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* import { customize, type UserContainer } from "@jr2/orchestrator";
|
|
394
|
+
* import { task } from "@jr2/machines";
|
|
395
|
+
*
|
|
396
|
+
* const sshd: UserContainer = {
|
|
397
|
+
* image: "registry.example.com/sshd:1.0",
|
|
398
|
+
* resources: { limits: { memory: "256Mi", cpu: "250m" } },
|
|
399
|
+
* };
|
|
400
|
+
*
|
|
401
|
+
* export const machine = customize(task, {
|
|
402
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
403
|
+
* agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
|
|
404
|
+
* user: sshd,
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* @see docs/concepts/workspaces.md
|
|
409
|
+
* @see docs/guides/size-a-workspace.md
|
|
410
|
+
* @see docs/reference/decisions/0005-sandbox-pod-composition.md
|
|
411
|
+
* @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
|
|
309
412
|
*/
|
|
310
|
-
export type UserContainer = {
|
|
413
|
+
export type UserContainer = {
|
|
414
|
+
/** The image: a `file:` URL to a docker context that your module ships
|
|
415
|
+
* (`import.meta.resolve("./sshd")`), which `jr2 up` builds, or a registry ref. It must not be
|
|
416
|
+
* empty. */
|
|
417
|
+
image: string;
|
|
418
|
+
/** This container's part of the Workspace's Size, as a Size. jr2 takes it from the Harness
|
|
419
|
+
* container's part. A field you leave out gives this container no limit of its own for it: it
|
|
420
|
+
* shares the pod's budget. Default: no part. */
|
|
421
|
+
resources?: Size;
|
|
422
|
+
};
|
|
311
423
|
|
|
312
424
|
/** The `user` option in either shape, read as its two halves. */
|
|
313
425
|
export function userSeatOf(user: string | UserContainer | undefined): { image?: string; resources?: Size } {
|
package/src/snapshot-store.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// store is owed — with `:memory:` for tests. A persist survives a process crash, not a power loss
|
|
5
5
|
// (WAL, `synchronous=NORMAL`), and the write stays synchronous so the row is on disk before the
|
|
6
6
|
// Machine's next side effect runs. `markLost` records a run whose live state could not be
|
|
7
|
-
// re-hydrated (e.g. its flue handle is gone); a non-live row
|
|
7
|
+
// re-hydrated (e.g. its flue handle is gone), keeping any snapshot the row had; a non-live row
|
|
8
|
+
// lives a week, then `sweep` deletes it.
|
|
8
9
|
|
|
9
10
|
import { DatabaseSync } from "node:sqlite";
|
|
10
11
|
import type { StoredSnapshot } from "./durability.ts";
|
|
@@ -37,18 +38,31 @@ export interface SnapshotStore {
|
|
|
37
38
|
* silently to the live one, which makes resolution unsound.
|
|
38
39
|
*/
|
|
39
40
|
findIdsByPrefix(prefix: string, limit: number): Promise<string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* The run whose blob carries the idempotency key `effect` (`dispatchedBy.effect`, ADR-0071), in
|
|
43
|
+
* any status, or undefined: what answers a re-sent dispatch with the run it already started. A
|
|
44
|
+
* row `markLost` wrote with no save before it has no snapshot, and so no key; a swept row is gone
|
|
45
|
+
* with its key.
|
|
46
|
+
*/
|
|
47
|
+
findByDispatch(effect: string): Promise<string | undefined>;
|
|
40
48
|
save(runId: string, snapshot: unknown, status?: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Record a run whose live state could not be re-hydrated. An upsert: a row that was saved KEEPS
|
|
51
|
+
* its snapshot and its idempotency key, because a lost dispatched run must keep answering its
|
|
52
|
+
* key — a re-send from its caller would otherwise start a second run (ADR-0071). Only a run with
|
|
53
|
+
* no row before gets a row with no snapshot.
|
|
54
|
+
*/
|
|
41
55
|
markLost(runId: string, reason: string): Promise<void>;
|
|
42
56
|
/**
|
|
43
57
|
* Record a run this Orchestrator can no longer read (ADR-0030, ADR-0069) — refused, not resumed:
|
|
44
58
|
* the Assembly its snapshot names is not on the volume, was published for another Kit version or
|
|
45
59
|
* does not load, or its Machine's shape does not match the snapshot's stamp.
|
|
46
60
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
61
|
+
* An UPDATE of a row that is there, and it KEEPS the snapshot: a drifted run is intact and only
|
|
62
|
+
* unreadable by this Orchestrator, and it is the one a human most needs to inspect. Nulling it
|
|
63
|
+
* would also hide the run entirely: `read()` returns undefined for a null blob, so
|
|
64
|
+
* `jr2 status <id>` would answer `no run` — a refusal indistinguishable from a run that never
|
|
65
|
+
* existed. It keeps the row's idempotency key too (ADR-0071).
|
|
52
66
|
*/
|
|
53
67
|
markDrifted(runId: string, reason: string): Promise<void>;
|
|
54
68
|
close(): Promise<void>;
|
|
@@ -62,6 +76,20 @@ type Row = {
|
|
|
62
76
|
updated_at: string;
|
|
63
77
|
};
|
|
64
78
|
|
|
79
|
+
/** The lookup `findByDispatch` runs, exported so a test can ask the planner about THIS statement,
|
|
80
|
+
* not a copy of it: the column is indexed, so the plan is a seek. */
|
|
81
|
+
export const FIND_BY_DISPATCH_SQL = `SELECT run_id FROM machine_snapshots
|
|
82
|
+
WHERE dispatch_effect = ? ORDER BY rowid LIMIT 1`;
|
|
83
|
+
|
|
84
|
+
/** The idempotency key a blob carries at `dispatchedBy.effect` (ADR-0071), or null. */
|
|
85
|
+
function dispatchEffectOf(snapshot: unknown): string | null {
|
|
86
|
+
if (typeof snapshot !== "object" || snapshot === null) return null;
|
|
87
|
+
const by = (snapshot as { dispatchedBy?: unknown }).dispatchedBy;
|
|
88
|
+
if (typeof by !== "object" || by === null) return null;
|
|
89
|
+
const effect = (by as { effect?: unknown }).effect;
|
|
90
|
+
return typeof effect === "string" ? effect : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
export class SqliteSnapshotStore implements SnapshotStore {
|
|
66
94
|
private readonly db: DatabaseSync;
|
|
67
95
|
|
|
@@ -83,6 +111,16 @@ export class SqliteSnapshotStore implements SnapshotStore {
|
|
|
83
111
|
updated_at TEXT
|
|
84
112
|
)
|
|
85
113
|
`);
|
|
114
|
+
// The idempotency key of a dispatched run (ADR-0071) is copied out of its blob into a column of
|
|
115
|
+
// its own when `save` writes the row, and the column is indexed. Not an expression index over
|
|
116
|
+
// `json_extract(snapshot, …)`: that runs on every row and THROWS on a blob SQLite's JSON parser
|
|
117
|
+
// refuses (nested about a thousand deep), so one such row would fail its own save and every
|
|
118
|
+
// later `init()` over the volume. A column also costs no JSON parse a save.
|
|
119
|
+
const columns = this.db.prepare(`PRAGMA table_info(machine_snapshots)`).all() as Array<{ name: string }>;
|
|
120
|
+
if (!columns.some((c) => c.name === "dispatch_effect")) {
|
|
121
|
+
this.db.exec(`ALTER TABLE machine_snapshots ADD COLUMN dispatch_effect TEXT`);
|
|
122
|
+
}
|
|
123
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS machine_snapshots_dispatch_effect ON machine_snapshots(dispatch_effect)`);
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
async load(runId: string): Promise<StoredSnapshot | null> {
|
|
@@ -151,18 +189,24 @@ export class SqliteSnapshotStore implements SnapshotStore {
|
|
|
151
189
|
return rows.map((row) => row.run_id);
|
|
152
190
|
}
|
|
153
191
|
|
|
192
|
+
async findByDispatch(effect: string): Promise<string | undefined> {
|
|
193
|
+
const row = this.db.prepare(FIND_BY_DISPATCH_SQL).get(effect) as { run_id: string } | undefined;
|
|
194
|
+
return row?.run_id;
|
|
195
|
+
}
|
|
196
|
+
|
|
154
197
|
async save(runId: string, snapshot: unknown, status = "live"): Promise<void> {
|
|
155
198
|
this.db
|
|
156
199
|
.prepare(
|
|
157
|
-
`INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at)
|
|
158
|
-
VALUES (?, ?, ?, NULL, ?)
|
|
200
|
+
`INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at, dispatch_effect)
|
|
201
|
+
VALUES (?, ?, ?, NULL, ?, ?)
|
|
159
202
|
ON CONFLICT(run_id) DO UPDATE SET
|
|
160
203
|
status = excluded.status,
|
|
161
204
|
snapshot = excluded.snapshot,
|
|
162
205
|
reason = NULL,
|
|
163
|
-
updated_at = excluded.updated_at
|
|
206
|
+
updated_at = excluded.updated_at,
|
|
207
|
+
dispatch_effect = excluded.dispatch_effect`,
|
|
164
208
|
)
|
|
165
|
-
.run(runId, status, JSON.stringify(snapshot), new Date().toISOString());
|
|
209
|
+
.run(runId, status, JSON.stringify(snapshot), new Date().toISOString(), dispatchEffectOf(snapshot));
|
|
166
210
|
}
|
|
167
211
|
|
|
168
212
|
async markLost(runId: string, reason: string): Promise<void> {
|