@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.
- package/README.md +20 -2
- package/bin/server.ts +7 -4
- package/console/canvas.ts +27 -14
- package/console/components/app.ts +12 -1
- package/console/components/fleet.ts +52 -6
- package/console/components/machine-pane.ts +128 -2
- package/console/components/nav.ts +1 -1
- package/console/components/schema-form.ts +12 -4
- package/console/json-field.ts +23 -0
- package/console/main.ts +122 -39
- package/console/store.ts +321 -21
- package/console/style.css +111 -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 +6 -4
- package/src/actor.ts +976 -61
- package/src/agent.ts +114 -38
- package/src/ambient.ts +4 -1
- package/src/assembly-checks.ts +347 -0
- package/src/assembly-tar.ts +494 -0
- package/src/assembly.ts +721 -0
- package/src/cause.ts +234 -0
- package/src/chart.ts +2182 -0
- package/src/charts.ts +748 -0
- package/src/config.ts +520 -162
- package/src/content-hash.ts +105 -0
- package/src/custodian.lua +6 -5
- package/src/custodian.ts +143 -39
- package/src/customize.ts +388 -55
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +557 -0
- package/src/effect.ts +1202 -0
- package/src/effector-wire.ts +674 -0
- package/src/fail.ts +209 -0
- package/src/fingerprint.ts +4 -3
- package/src/gate.ts +8 -0
- package/src/git-clone.ts +65 -0
- package/src/git-push.ts +336 -0
- package/src/harness-client.ts +764 -48
- package/src/held-secrets.ts +82 -26
- package/src/holds.ts +156 -0
- package/src/http-effect.ts +1423 -0
- package/src/http.ts +916 -113
- package/src/images.ts +47 -21
- package/src/index.ts +62 -43
- package/src/instance.ts +505 -59
- package/src/internal.ts +84 -0
- package/src/kube-client.ts +333 -0
- package/src/machine-doc.ts +106 -6
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/mount-digest.ts +53 -0
- package/src/names.ts +66 -8
- package/src/notices.ts +141 -0
- package/src/open-body.ts +1580 -0
- package/src/open-effect.ts +217 -0
- package/src/open.ts +59 -6
- package/src/parts.ts +709 -41
- package/src/party.ts +534 -0
- package/src/pick.ts +761 -0
- package/src/pool.ts +248 -37
- package/src/registration.ts +382 -19
- package/src/repo-fetch.ts +60 -85
- package/src/repos.ts +65 -75
- package/src/rest.ts +133 -0
- package/src/run-host.ts +2383 -115
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +1126 -0
- package/src/sandbox-effector.ts +111 -0
- package/src/sandbox-kube.ts +1721 -0
- package/src/sandbox-watch.ts +260 -0
- package/src/secret-placement.ts +529 -0
- package/src/server.ts +284 -84
- package/src/setup.ts +819 -117
- package/src/size.ts +454 -0
- package/src/snapshot-store.ts +115 -20
- package/src/stub-harness.ts +49 -16
- package/src/sync-client.ts +54 -0
- package/src/timers.ts +191 -0
- package/src/tokens.ts +35 -4
- package/src/vocabulary.ts +135 -15
- package/src/wire.ts +238 -10
- package/src/workspace.ts +1195 -338
- package/src/sandbox-kubectl.ts +0 -1108
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# The Orchestrator is a Kit image, and an Instance publishes its Assembly
|
|
2
|
+
|
|
3
|
+
[ADR-0008](0008-orchestrator-as-deployed-app.md) and
|
|
4
|
+
[ADR-0019](0019-one-converging-command-against-the-current-context.md) made the Orchestrator the one image an Instance
|
|
5
|
+
produces: the engine and the Instance's `workflows/` in one content-addressed image, rebuilt and rolled on every edit to
|
|
6
|
+
a Machine, to `jr2.config.ts`, or to a Sandbox Dockerfile under `images/`. The Harness and the operator had already
|
|
7
|
+
become Kit images released at one version ([ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md),
|
|
8
|
+
[ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)), so the Orchestrator was the one
|
|
9
|
+
jr2-owned process whose image a user built. Three costs followed. Every Machine edit rolled the daemon that hosts every
|
|
10
|
+
run, which is the roll [ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md) moved the image map to a ConfigMap to
|
|
11
|
+
avoid. An installed Instance needed docker for `jr2 up` even when it had no `workspace()` and so no image of its own to
|
|
12
|
+
build. And a Machine edit stranded every parked run of that Workflow at the deploy
|
|
13
|
+
([ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md)), by design, because the new process could
|
|
14
|
+
resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split the one artifact into two.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
- **The Orchestrator is a Kit image.** `ghcr.io/snapwich/jr2-orchestrator:<kitversion>`, built content-addressed in a
|
|
19
|
+
checkout and published at the version with the Harness and the operator, mirrored by `jr2 kit push`, re-homed by
|
|
20
|
+
`kitRegistry` ([ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md),
|
|
21
|
+
[ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)). It holds `@jr2/orchestrator` and its
|
|
22
|
+
dependency closure and nothing of any Instance. The closure is installed hoisted at `/instance/node_modules`, and an
|
|
23
|
+
Assembly lives at `/instance/.jr2/assemblies/<hash>/`, so Node's walk up from any file in an Assembly reaches the
|
|
24
|
+
image's copies: that walk is the resolution rule of the next bullet. The third-party closure is pinned at the versions
|
|
25
|
+
the kit's own lockfile resolves (`deploy/orchestrator/closure.json`, checked against the lockfile by a test and by the
|
|
26
|
+
build), so one Kit tag names the same bytes on every day it is built, and the image runs the versions every test tier
|
|
27
|
+
runs. The image an Instance produced retires: an installed Instance with no `workspace()` needs no docker for
|
|
28
|
+
`jr2 up`.
|
|
29
|
+
- **An Instance publishes an Assembly.** The Assembly is the Instance's Workflows, its `jr2.config.ts`, the `file:`
|
|
30
|
+
image contexts its Machines name, and what they import, staged as one tree with the Kit's packages stripped (below),
|
|
31
|
+
sealed, and hashed. The tree is hoisted on both bundle paths of
|
|
32
|
+
[ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md) (`pnpm deploy` too), so the
|
|
33
|
+
Kit's packages are top-level entries the strip deletes, and no pnpm store name reaches the archive. The hash covers
|
|
34
|
+
the tree's structure as well as its bytes: every path in byte order, each file's content, each symlink's target, each
|
|
35
|
+
directory including an empty one, and no mode, so one hash names one Assembly on any host in any locale. The archive
|
|
36
|
+
is a ustar tarball with pax `path`/`linkpath` records for the names ustar cannot hold, relative symlinks only, every
|
|
37
|
+
entry refused that would escape the tree. `jr2 up` publishes it to the running Orchestrator over the Orchestrator's
|
|
38
|
+
own API, and only `jr2 up` does: an Agent never publishes code
|
|
39
|
+
([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)). The config rides the Assembly because it is TypeScript too
|
|
40
|
+
and the Orchestrator reads it at boot; what was Deployment env stays env (`registry`, `kitRegistry`, the Secret refs).
|
|
41
|
+
Rejected: one bundled file per Workflow, which breaks `import.meta.resolve` for a context a Machine ships and splits
|
|
42
|
+
one lockfile into many; a config ConfigMap published beside the Assembly, which is a second artifact that can disagree
|
|
43
|
+
with the first.
|
|
44
|
+
- **A package the Kit image ships resolves from the image, never from the Assembly.** The publisher strips
|
|
45
|
+
`@jr2/orchestrator` and its whole dependency closure from the staged tree, and the Orchestrator resolves them to its
|
|
46
|
+
own copies. Class identity holds by construction, not by dedupe: a second copy of `@jr2/orchestrator`, `xstate` or
|
|
47
|
+
`zod` is a Machine whose Menus are empty with no error anywhere
|
|
48
|
+
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)). "Its closure", not a list of
|
|
49
|
+
three, so a new kit dependency cannot reopen the hole. The Assembly keeps `@jr2/machines` and the Instance's own deps.
|
|
50
|
+
- **An Assembly names its exact Kit version, and the door refuses any other.** The Assembly's manifest names the
|
|
51
|
+
`@jr2/orchestrator` version it was written against; admission refuses a mismatch naming both numbers, the refusal of
|
|
52
|
+
[ADR-0056](0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md) moved to the door.
|
|
53
|
+
Not semver: the kit is 0.x and its minors break, `@jr2/machines` peers exact for the same reason, and a compatibility
|
|
54
|
+
range is a promise jr2 cannot test without a version matrix. Loosening is one line later; tightening after a range has
|
|
55
|
+
been promised is not. So one `jr2 up` that bumps the Kit version rolls the Orchestrator image first and publishes the
|
|
56
|
+
Assembly built against the new version second. The new image boots on the old pointer, whose record names the old Kit
|
|
57
|
+
version, and refuses it as the door would: between the roll and the publish the Orchestrator serves and holds no
|
|
58
|
+
current Assembly: for a kubelet ConfigMap period in a converge, and until the next `jr2 up` after a volume loss. In
|
|
59
|
+
that window `/agents/*` answers 503 for a surface the process does not host, never 404
|
|
60
|
+
([ADR-0026](0026-a-turn-that-is-over-is-never-prompted.md)). A boot with no current Assembly restores nothing, because
|
|
61
|
+
no config is bound in that window and a run that provisioned under none would fault for good; it leaves the live rows
|
|
62
|
+
untouched, announces them as `waiting`, and the first publish restores them, each inside the Assembly its snapshot
|
|
63
|
+
names. A run whose Assembly was published for the old Kit version is `drifted` at that publish, naming the hash and
|
|
64
|
+
both versions. That verdict is final, and the collection that follows removes the Assembly: an image rolled back
|
|
65
|
+
before the publish still finds every run live, an image rolled back after it does not.
|
|
66
|
+
- **The route is one noun and two verbs.** `PUT /assembly`, behind a **publish token**: one tarball, the Kit version and
|
|
67
|
+
the content hash in headers; `200 { hash, kitVersion, workflows, held, collected }` or `4xx` carrying the walk's
|
|
68
|
+
report, the old Assembly untouched. `held` lists every Assembly a live run holds, the current one first, each with the
|
|
69
|
+
runs that hold it; `collected` names the Assemblies the publish let go. The publish token is not the Instance token.
|
|
70
|
+
Publishing an Assembly is importing code into a process that holds the ServiceAccount, so it must cost what deploying
|
|
71
|
+
an image cost: cluster write access. The token lives in the Instance's Secret beside the Instance token, `jr2 up`
|
|
72
|
+
reads it through the kube API alone, and no env var and no `--url` path can supply it, so an ingress-exposed Instance
|
|
73
|
+
exposes no publish door. The Instance token keeps its meaning
|
|
74
|
+
([ADR-0032](0032-the-console-unlocks-with-the-instance-token.md)): start runs, answer Gates, read. `GET /assembly`,
|
|
75
|
+
public like `GET /workflows`: the current hash and Kit version, the Assemblies held with the runs that hold them, and,
|
|
76
|
+
when deployed, the digest of each ConfigMap the process reads through a mount (`jr2-images`, `jr2-held`), so a
|
|
77
|
+
publisher can see what the process reads. `jr2 status` prints it; `/healthz` prints the current hash where it printed
|
|
78
|
+
the content hash. A repeat publish of the current hash answers `200 { unchanged: true }` before the body is read;
|
|
79
|
+
`--force` sends the PUT anyway and gets the same answer, because the hash names bytes the Orchestrator already holds.
|
|
80
|
+
Rejected: an OCI artifact the Orchestrator pulls, which needs the registry that dropping docker removed; a ConfigMap,
|
|
81
|
+
at 1 MiB; a git ref the Orchestrator clones, when [ADR-0048](0048-the-orchestrator-boots-without-its-repos.md) just
|
|
82
|
+
took cloning out of the Orchestrator.
|
|
83
|
+
- **The walk runs twice, with different duties.** On the host, `jr2 up` keeps the typecheck
|
|
84
|
+
([ADR-0050](0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md)) and every duty that needs the
|
|
85
|
+
host or the cluster: it loads the Instance folder with the same `loadWorkflows` and `partsOf` the Orchestrator runs,
|
|
86
|
+
stages the Assembly before any cluster spend and refuses a converge whose staged Workflow set differs from the walked
|
|
87
|
+
one, builds and delivers `file:` Sandbox Images keyed by the digest of each context's staged copy (the bytes the door
|
|
88
|
+
and every provision hash; the folder's own copy may hold a `.env` or a `node_modules` that staging drops), writes the
|
|
89
|
+
images ConfigMap, materializes Secrets and held-secret Secrets, warms Repos, preflights the provider, and refuses
|
|
90
|
+
every Open part nobody bound in one listing, the listing the door produces too. Then it publishes, after the cluster
|
|
91
|
+
objects exist and after any image roll. At the door the Orchestrator unpacks to staging, refuses a Kit package carried
|
|
92
|
+
in the tree, imports the config and every `workflows/*.ts`, which is the first check (no `export const machine`, a
|
|
93
|
+
`jr2Setup.createMachine` refusal), and runs the same walk to refuse what it can see: a zod `input` that no door reads
|
|
94
|
+
(ADR-0033), an Open part, a secret the config does not declare, a `file:` digest absent from its map, a Vocabulary
|
|
95
|
+
error, a Kit version mismatch. The door checks are true when the Assembly lands because the cluster work came first,
|
|
96
|
+
and because the publish waits for the kubelet: a publish rolls no pod, and the ConfigMaps the converge wrote reach the
|
|
97
|
+
process on the kubelet's own period, so `jr2 up` compares the digests `GET /assembly` reports with the data it applied
|
|
98
|
+
and sends the PUT only once they agree. The server has no `tsc`: types are the author's duty and the typecheck is the
|
|
99
|
+
host's gate; a type error that survives shows as an import error at the door, named in the refusal.
|
|
100
|
+
- **A publish is atomic and reloads; it never rolls.** The Orchestrator unpacks to `assemblies/<hash>/` on its own
|
|
101
|
+
volume beside `state.db`, fsyncs, flips one `current` pointer, and reloads: the `reload()` the deployed entrypoint
|
|
102
|
+
never used. A crash before the flip leaves the old Assembly current; after it, the new. The pod rolls only when the
|
|
103
|
+
Kit version or its env changes; the Instance Secret reaches the process as env, so a pod-template digest of it rolls
|
|
104
|
+
the pod when a token is minted anew, and never otherwise. The entrypoint boots from the current Assembly and never
|
|
105
|
+
from the folder it starts in: a host-booted fixture publishes through the same door `jr2 up` does. Volume loss boots
|
|
106
|
+
an Orchestrator that serves, announces "no Assembly", and holds no Workflows until the next `jr2 up`, the pattern of
|
|
107
|
+
[ADR-0048](0048-the-orchestrator-boots-without-its-repos.md): degrade the capability, not the daemon.
|
|
108
|
+
- **A run holds the Assembly it started under.** A new run starts under the current Assembly. A live run keeps the one
|
|
109
|
+
it started under, through restarts too: the snapshot records the Assembly hash, and restore resolves the Workflow's
|
|
110
|
+
name inside that Assembly, imported once per Assembly and never registered, so `jr2 runs`, `jr2 run <name>` and
|
|
111
|
+
`GET /workflows/<name>/machine` are the current Assembly's alone. The Orchestrator holds the current Assembly and
|
|
112
|
+
every Assembly that a live run holds, and lets one go when no live run holds it, by reachability
|
|
113
|
+
([ADR-0039](0039-image-garbage-collects-by-reachability.md)): at boot after restore, after each publish, and on the
|
|
114
|
+
hourly sweep; a hold is the live row, so a run this process failed to restore still holds its Assembly; the hash the
|
|
115
|
+
`current` pointer names is a root even when this process cannot load it, so an image rolled back finds its Assembly; a
|
|
116
|
+
failed removal is reported and skipped, never allowed to abandon the rest. A held Assembly lends only its Machines:
|
|
117
|
+
the config that applies is always the current Assembly's, because config is reach and credentials, not a Machine, and
|
|
118
|
+
a held run must meet today's `git.credentials` fence, not the one it started under. The Repo pass binds the Repos of
|
|
119
|
+
every held Machine, so a held run's Workspace mounts what its own Machine binds. The cluster parts `jr2 up` derives
|
|
120
|
+
from the current walk alone are the current Assembly's too: the `file:` Sandbox Image digests in `jr2-images`, the
|
|
121
|
+
Effector manifests, the held-secret Secrets, and the roots of the image sweep. A held run that still needs one the
|
|
122
|
+
current Assembly dropped fails at that provision, naming the held Assembly; so a publish strands no run whose Machine
|
|
123
|
+
it changed, and a publish that drops a Sandbox Image context a parked run has not yet provisioned is the one edit that
|
|
124
|
+
still can. The Console draws a run on the current Machine; a held run's `assembly` is in its status, and a Console
|
|
125
|
+
that marks or draws held runs is a later decision. So a publish strands nothing: the "editing a workflow strands its
|
|
126
|
+
parked runs" consequence of [ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md) is retired, and
|
|
127
|
+
drift is what it always meant, "I can no longer read what I saved": an Assembly that cannot load after a Kit version
|
|
128
|
+
bump, or a volume that lost it. Such a run is `drifted` with a reason naming the Assembly hash and the import error;
|
|
129
|
+
no new status. Drift keeps the snapshot, as it always did; `lost`, which drops it, is for a blob with nothing to keep:
|
|
130
|
+
an empty snapshot, one that names no Workflow, or a live world that `reconcile` reports gone
|
|
131
|
+
([ADR-0007](0007-durable-machine-state.md)). A fault while reading a held Assembly (a permission, a record that does
|
|
132
|
+
not parse) is `failed`, not drift, and the row stays live and holding
|
|
133
|
+
([ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md)). Rejected: report the runs a publish will
|
|
134
|
+
strand and let them drift at the next restart, which turns today's loud stranding into a deferred, silent one.
|
|
135
|
+
- **The data plane is always wired.** `serverMain` decided at boot, from the walk, whether to wire Sandbox support at
|
|
136
|
+
all; a reload can publish the first `workspace()`. The Orchestrator always wires its kube client, and a provision on a
|
|
137
|
+
cluster where `jr2 up` deployed no operator (`operator.manage: false`) fails pointedly naming `jr2 up`. `jr2 up` keeps
|
|
138
|
+
deciding what to DEPLOY for the walk's Sandboxes from its host-side walk, the Sandbox Images and the cache agent;
|
|
139
|
+
wiring a client is free, deploying those is not. The one Sandbox watch starts when the current Assembly composes a
|
|
140
|
+
Sandbox, or at the first provision, so a cluster with no Sandbox kind is not watched for one.
|
|
141
|
+
- **The seal simplifies.** [ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md)'s rule stands: the staged tree
|
|
142
|
+
records nothing about where or when it was staged, so one hash names one Assembly. The `.bin` shims it rewrote are
|
|
143
|
+
deleted instead: the Orchestrator runs no bin from an Assembly (an Effect runs in an Effector,
|
|
144
|
+
[ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), and a path that names the Assembly's own
|
|
145
|
+
directory cannot be rewritten to a path that contains its own hash.
|
|
146
|
+
|
|
147
|
+
## Considered options
|
|
148
|
+
|
|
149
|
+
- **Keep the per-Instance image and roll less.** Rejected: nothing short of a separate artifact stops a Machine edit
|
|
150
|
+
from being a pod-template change, and the roll is what strands parked runs.
|
|
151
|
+
- **Let an Agent publish an Assembly through an Effect holding the Instance token, behind a Gate.** Rejected: the
|
|
152
|
+
Assembly is code the Orchestrator imports with its ServiceAccount and signing key in reach, so the only fence is who
|
|
153
|
+
may publish, and a reviewer approving generated code under time is a weak fence for a process that holds the cluster.
|
|
154
|
+
The agent path is data: [ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md).
|
|
155
|
+
- **Semver-compatible Assemblies.** Rejected above; one line to loosen later.
|
|
156
|
+
- **Keep only the current Assembly and report the runs a publish will strand.** Rejected above.
|
|
157
|
+
|
|
158
|
+
## Consequences
|
|
159
|
+
|
|
160
|
+
- The deployable unit of [ADR-0008](0008-orchestrator-as-deployed-app.md) is rewritten: a Kit image plus a published
|
|
161
|
+
Assembly. Under GitOps the manifests alone no longer reproduce the Workflows; the Instance repo plus `jr2 up` does,
|
|
162
|
+
which was already the record and already the CI path.
|
|
163
|
+
- `npm i -g @jr2/cli && jr2 init && jr2 up` works with no docker on the host for an Instance with no `workspace()`,
|
|
164
|
+
which closes the promise of [ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md) for the
|
|
165
|
+
common case. A Sandbox Dockerfile edit no longer rolls the Orchestrator.
|
|
166
|
+
- The content hash moves off the Deployment: the Deployment's image is the Kit's, and the Assembly hash is an
|
|
167
|
+
Orchestrator fact, read from `GET /assembly`, not a label `jr2 up` compares. The steady-state converge compares the
|
|
168
|
+
staged hash to the current one and publishes nothing when they agree.
|
|
169
|
+
- The `@dist` tier publishes the Orchestrator image to its registry with the other Kit images and drives the installed
|
|
170
|
+
CLI through a publish, so `PUT /assembly`, the stripping rule and the Kit version refusal execute there. The e2e
|
|
171
|
+
fixture boots the same entrypoint as a host process and publishes its instance through `PUT /assembly` with the
|
|
172
|
+
publish token it booted with, so every default-profile scenario crosses the door, and a restart boots from `current`.
|
|
173
|
+
The `@kind` tier builds the Orchestrator image in checkout mode as it builds the Harness.
|
|
174
|
+
- The GLOSSARY gains **Assembly**; **Kit version** becomes "the version the Instance pins, which is the tag of every Kit
|
|
175
|
+
image it deploys, the Orchestrator's own included"; "instance image" joins Kit image's avoid list.
|
|
176
|
+
- `RunBlob` gains the Assembly hash; a blob without one cannot be vouched for and is drift, as a blob without a shape
|
|
177
|
+
stamp was in [ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md). Greenfield: a stale volume is
|
|
178
|
+
deleted, not migrated.
|
|
179
|
+
- Many Assemblies held means many module trees in one process. They share the Kit's packages by the resolution rule, so
|
|
180
|
+
no identity problem; they are distinct directories, so Node's module cache keeps them apart.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# A Machine lends its parts to a Chart
|
|
2
|
+
|
|
3
|
+
The ask behind [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md) had a second
|
|
4
|
+
half: an Agent should be able to create a powerful workflow at run time and run it, without a commit to the Instance and
|
|
5
|
+
without a human redeploying anything, and it must not be able to take the system over. The first half settled that only
|
|
6
|
+
`jr2 up` publishes code, because a Machine is TypeScript the Orchestrator imports into a process that holds the
|
|
7
|
+
ServiceAccount, the signing key and the Instance token, and every fence jr2 built keeps the Agent away from that
|
|
8
|
+
process: it reaches its Machine through a container it cannot read
|
|
9
|
+
([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)), holds Stand-ins and never keys
|
|
10
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)), and spends a credential only through
|
|
11
|
+
an Effector ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). One `fromPromise` written by
|
|
12
|
+
an Agent and imported by the Orchestrator collapses all of them. So an Agent's workflow cannot be code. The grill of
|
|
13
|
+
2026-10-18 found that it does not need to be, and the prior art agrees: Step Functions keeps every `Resource` a constant
|
|
14
|
+
and puts all authority in the role; Argo's answer to "anyone who can submit a Workflow gets any ServiceAccount" was a
|
|
15
|
+
strict mode where a submitter may only reference a human-owned template; Temporal's own answer to "dynamic" is a trusted
|
|
16
|
+
interpreter walking untrusted JSON; Stately's v6 serializer stores a machine as data with implementations by name and
|
|
17
|
+
refuses inline code outright. In-process isolates (`node:vm`, `isolated-vm`, QuickJS in wasm) are argued against by
|
|
18
|
+
their own maintainers, and the escapes shipped in systems that used them were in the bridge.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
- **A Chart is a Machine written as data alone.** States, transitions, invokes, `after` delays, nested and parallel
|
|
23
|
+
states, a final state's output: xstate's own JSON-serializable config, with every guard, action and delay named
|
|
24
|
+
(`{ type, params }`) and every `invoke.src` a slot name. It carries no function, no import, no secret name, no url, no
|
|
25
|
+
argv, no image and no Repo, and it names no Turn's placement: the Open body places every Agent Turn its Chart starts
|
|
26
|
+
from its own input, as its wrapper placed it (the stub tier's explicit `endpoint`, or the ambient `workspace()`). The
|
|
27
|
+
grammar is version 1 of a kit schema, `chartSchema`, not xstate's config whole: a state's own `onDone`, a parallel
|
|
28
|
+
join and an `emit` action are not in it, a nested final state's output is refused because nothing reads it, and a
|
|
29
|
+
Chart is at most 200 states, 8 deep. A spike against xstate 5.32.2 bound such a chart as a child of a hand-written
|
|
30
|
+
wrapper, with lent actors, named guards and actions reading `params`, `sendParent`, `onDone` output and a snapshot
|
|
31
|
+
round-trip.
|
|
32
|
+
- **An Open body is the slot a Chart binds, and a Chart wrapper is the Machine that declares it.** A Machine declares a
|
|
33
|
+
body slot and leaves it Open for a run to bind, the one Open part that a run, not a composer, binds
|
|
34
|
+
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)), as a per-run Repo Slot is
|
|
35
|
+
bound from the door ([ADR-0051](0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md)). The declaration
|
|
36
|
+
states three things: what it **lends**, by slot name (Agents, Effects, child Machines, guards, actions, delays); what
|
|
37
|
+
a Chart may **define** (the models and the Workspace access its own Agents may have); and the **ceilings** (below).
|
|
38
|
+
Nothing a Chart names resolves anywhere but there. "Wrapper" unqualified keeps
|
|
39
|
+
[ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)'s sense, any Machine with a body slot,
|
|
40
|
+
`workspace()` and `pool()` included; a Chart wrapper is the one whose body a run binds.
|
|
41
|
+
- **A Chart mints routing, events and Agents; it borrows reach.** It mints its states and transitions; its Vocabulary,
|
|
42
|
+
each event `{ name, input: <JSON Schema>, audience? }`, built into zod at admission (zod 4 reads JSON Schema natively,
|
|
43
|
+
and the wire to the Harness was JSON Schema already), so a lent Agent's Menu derives from the Chart's events as from
|
|
44
|
+
any nested Machine's. The payload schema is a named subset: `type`, `properties`, `required`, `items`, `enum`,
|
|
45
|
+
`const`, `additionalProperties`, the numeric and length bounds, `title` and `description`; `pattern`, `$ref`, the
|
|
46
|
+
combinators and the conditionals are refused by name, because the Orchestrator checks every pick against the schema in
|
|
47
|
+
its own process, and a payload field named `type` is refused, because the event name is a constant the Orchestrator
|
|
48
|
+
writes after the payload. Its Gates it BORROWS: the kit's Gate is reach to the outside (a human's or a webhook's
|
|
49
|
+
answer into the run), so the Open body lends it whole, `lends.gate: true`, and a body that does not has no `gate` in
|
|
50
|
+
its grammar and refuses one at the door; a lent Gate's ids sit below the body's own actor path, so a Chart cannot take
|
|
51
|
+
the name of its wrapper's Gate. (Measured: with `gate` offered beside one lent Agent, three of five missed second
|
|
52
|
+
Charts reached for it as a worker.) And it mints its own Agents, because an Agent is five plain fields
|
|
53
|
+
([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)) and the Harness runs whatever definition
|
|
54
|
+
rides the Turn. A minted Agent's model must be one the Open body allows and the Assembly placed a key for;
|
|
55
|
+
`workspace: "none"` needs the Instance Harness the Assembly converged. Both are door refusals, never a 401 mid-run. It
|
|
56
|
+
borrows, by slot name only, every Effect (the url, argv and secret were bound once by the author and the Chart fills
|
|
57
|
+
only its input slots), every Repo and image, every secret, every child Machine, and every guard, action or delay
|
|
58
|
+
beyond the generic set. Lending is attenuated by construction; an author who lends
|
|
59
|
+
`command(["sh", "-c", arg("script")])` has lent a shell, and that is theirs to choose. A lent name may not be dotted,
|
|
60
|
+
`gate`, `fail`, a generic action's name or an integer, because those are the kit's; the Gate itself is lent as
|
|
61
|
+
`gate: true`, and the Part that fails the run as `fail: true`
|
|
62
|
+
([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)), because the Machine
|
|
63
|
+
decides which ends a Chart can choose. The guard `jr2.failure`, which tells a Failure from a defect on an `onError`,
|
|
64
|
+
is in every Chart, as `jr2.sentBy` is: it is a predicate and gives no capability.
|
|
65
|
+
- **Constants for authority, references for data.** `invoke.src`, guard and action `type`s, delay names and event names
|
|
66
|
+
are constants, checked against the lent set and the Chart's own declarations at admission. Data flows through
|
|
67
|
+
references: a placeholder `{{context.ticket.title}}`, or `{ "$ref": "event.summary" }`, in an invoke's `input`, an
|
|
68
|
+
action's `params`, a Gate's `meta` and the root `output`. A reference is a **path lookup** that jr2's own code
|
|
69
|
+
performs at invoke time: a list of property names walked on the run's context, event or input, which are plain data by
|
|
70
|
+
[ADR-0007](0007-durable-machine-state.md). There is no evaluation, no operator, no call; `{{context.round + 1}}` fails
|
|
71
|
+
the grammar. The roots are `context`, `event` and `input`, and nothing else; `event.output` and `event.error` are read
|
|
72
|
+
whole, and `event.error` is a line jr2 writes that names the invoke, never the error's own message, because a child's
|
|
73
|
+
error is a defect whose text never reaches a model
|
|
74
|
+
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)). A placeholder is read
|
|
75
|
+
from the Chart's own strings and never from data: a ticket body that contains `{{context.notes}}` lands in a prompt as
|
|
76
|
+
those characters; a lone placeholder yields the value itself, and one inside a string writes an object as JSON. Four
|
|
77
|
+
generic actions exist, `set { path, from }`, `append`, `increment` and `clear`, with `path` relative to the context;
|
|
78
|
+
everything else a Chart does is states, named guards and lent actions. The kit's own `task` builds its coder prompt
|
|
79
|
+
with a conditional on the round; in a Chart that is two states and a guard. A reference the door admitted but the
|
|
80
|
+
run's data lacks (a read before the state that writes it) settles the body `refused` with the reference named, never
|
|
81
|
+
as an error. A terminating, side-effect-free expression (CEL-class) is the named later addition, as a new reference
|
|
82
|
+
kind: adding one breaks no Chart, removing one would.
|
|
83
|
+
- **A Chart always runs under a Chart wrapper, and the Chart is the body actor's input.** Four doors, nothing else:
|
|
84
|
+
|
|
85
|
+
| Who | One-shot | Named |
|
|
86
|
+
| ------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
87
|
+
| HTTP (a human, a tool, an MCP) | `POST /workflows/<wrapper>/runs` with `chart` in the input | `PUT /workflows/<wrapper>/charts/<name>` once, then `POST /workflows/<wrapper>/charts/<name>/runs` |
|
|
88
|
+
| A Machine (an Agent) | a pick carries the Chart; a state invokes the Open body with it | the Open body's input carries `{ chart: { name } }`, the wrapper's own Chart by its short name, resolved at invoke |
|
|
89
|
+
|
|
90
|
+
One-shot, the Chart wrapper is the Workflow whose run it is. Named, the Chart is a child of its wrapper: its Workflow
|
|
91
|
+
name is `<wrapper>/<name>`, its routes live under the wrapper's, and `jr2 run task/review-loop` starts it as the
|
|
92
|
+
ordinary run-start does. A Chart never runs on its own: the Open body it binds is where its lent parts and ceilings
|
|
93
|
+
come from. Either way the Chart arrives as the body actor's input, so the grammar reaches a planner as the JSON Schema
|
|
94
|
+
of its pick for free (`chartSchema` is exported by `@jr2/orchestrator`, and `chartSchemaFor(declaration)` is the same
|
|
95
|
+
grammar narrowed to one Open body, for a planner's pick), **narrowed per wrapper**:
|
|
96
|
+
`GET /workflows/<wrapper>/chart-schema` is the grammar with the wrapper's lent slot names, guard, action and delay
|
|
97
|
+
names, and allowed models as `enum`s, derived from the two facts jr2 already reads. A human writing
|
|
98
|
+
`workflows/<wrapper>/<name>.chart.json` points an editor at it and gets validation and completion; a planner whose
|
|
99
|
+
pick is `{ chart }` gets it as its tool parameters, so the model is constrained at generation time, which is as close
|
|
100
|
+
to a type as a model can get. A Chart file is admitted by `jr2 up`'s host-side walk with the same door, so its errors
|
|
101
|
+
surface at the same command as `tsc`'s, one step later. That is what compile-time type safety becomes for a Chart: the
|
|
102
|
+
wrapper stays TypeScript and typechecked, and the Chart's references into it are checked at a door that sees the live
|
|
103
|
+
lent set, which a compiler would not. And a name is resolved to text at invoke time and admitted exactly as text would
|
|
104
|
+
be, and **a name is a binding**: a named Chart was created against one Chart wrapper and runs under that wrapper only,
|
|
105
|
+
which its name and its path both say. So the by-name path takes the short name alone, `review-loop`, and resolves it
|
|
106
|
+
among the Charts of the wrapper that is the run's root Workflow, seen through a transparent wrapper; a Chart wrapper
|
|
107
|
+
that another Machine invokes as a child resolves no name, because the run is not that wrapper's, and there is no way
|
|
108
|
+
to write another wrapper's. Portability belongs to the text alone: to run one Chart under another wrapper, pass the
|
|
109
|
+
text one-shot or publish it under that wrapper too. The body actor constructs the live Machine from the text at start
|
|
110
|
+
and again at restore, which is [ADR-0007](0007-durable-machine-state.md)'s own rule: the text and its hash persist in
|
|
111
|
+
the body's own snapshot, so the run holds what it ran even if the name is later deleted or republished, and the run's
|
|
112
|
+
shape stamp ([ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md)) stays its wrapper's, because the
|
|
113
|
+
Chart is the run's data and not its Machine's shape. At restore the body admits its text again against the parts lent
|
|
114
|
+
now and compiles it again; a hash that does not match the text, or a text the door now refuses, is drift naming the
|
|
115
|
+
body slot and the reason. A one-shot Chart lives nowhere but its run, and
|
|
116
|
+
[ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)'s pin covers it by
|
|
117
|
+
construction. Resolving by name is invoke-by-name, which
|
|
118
|
+
[ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md) rejected for Machines; a name here resolves to
|
|
119
|
+
data the Open body still binds, so nothing that decision protects is lost.
|
|
120
|
+
|
|
121
|
+
- **A Chart's author is an Agent, and jr2 briefs it on the Turn that may write one.** A Chart is written by a model at
|
|
122
|
+
run time: a planner whose pick carries one. A human who keeps a Chart file reads the same grammar through an editor,
|
|
123
|
+
and is the second audience, not the first. The narrowed grammar constrains the Chart's names, but it does not teach
|
|
124
|
+
what a Chart IS, and a measured planner guessed xstate: `onDone` on an Agent, no `events`, the lent Agent redefined
|
|
125
|
+
under `agents`, `input.change` for a prompt, `event.result.value` for a pick's field. Its first Chart drew a ten-line
|
|
126
|
+
report and admitted on the third or fourth. So jr2 says how a Chart runs BEFORE the first Chart, as the **Chart
|
|
127
|
+
briefing** (`CHART_BRIEFING`, `chartBriefing(narrow)`): one text, jr2's, naming no task and no pick, that reaches
|
|
128
|
+
every author by one channel each. It is the grammar's root `description`, so `GET /workflows/<wrapper>/chart-schema`
|
|
129
|
+
and a pick's tool parameters carry it, and a future MCP built on the schema carries it for free. A narrowed grammar's
|
|
130
|
+
briefing names the lent Agents, and a body that allows no model has NO `agents` in its grammar and a briefing that
|
|
131
|
+
says so: a planner reads a field it cannot fill as one to fill (measured: eight of ten first Charts minted under a
|
|
132
|
+
`never` model, and read "must not have additional properties" for it), and a Chart that brings `agents` anyway reads
|
|
133
|
+
at the door why there is none. And the Agent's surface MARKS a pick whose parameter is a Chart with that grammar's
|
|
134
|
+
briefing (`SurfaceEvent.briefing`, by schema identity), so the Harness says the text in the Turn part, right before
|
|
135
|
+
"Allowed now", on every Turn where that pick is Allowed, and on a restatement that opens it
|
|
136
|
+
([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)). Nowhere else: the standing Briefing stays
|
|
137
|
+
Chart-agnostic, the tools block stays the same bytes on every Turn
|
|
138
|
+
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), and a seat whose Menu
|
|
139
|
+
holds no Chart pick reads not a word of it. The cost is the author's to scope: a seat whose Menu holds a Chart pick
|
|
140
|
+
carries the grammar as cached tool parameters on every Turn, so an author who wants a planner that only sometimes
|
|
141
|
+
writes a Chart gives the pick to a seat of its own. The grammar's fields keep terse sentences at the places a planner
|
|
142
|
+
got wrong (`agents`, `invoke.input`, `invoke.onDone`, `events`, `output`), because a model reads a property's
|
|
143
|
+
description as it emits the property. The door's report stays complete, one line per fault, as the second teacher. And
|
|
144
|
+
a narrowed grammar shows ONE worked fragment, as JSON Schema `examples` on the state definition and on `events`: an
|
|
145
|
+
Agent state invoking the body's first lent Agent, and the declared pick with a `required` field that ends its Turn,
|
|
146
|
+
handled in `on` with a `target` and a `set`. It is the one shape every Agent state must have to run, so it steers no
|
|
147
|
+
path: not the number of states, their order, the prompts, loops, guards or minted Agents. A whole Chart as an example
|
|
148
|
+
was refused for that reason, since the instrument copies what it is shown (it wrote the briefing's `set` verbatim),
|
|
149
|
+
and a Chart template in the grammar would make every Chart that Chart. A body that lends no Agent shows nothing: there
|
|
150
|
+
is no real name. `examples` is advisory in JSON Schema and some providers strip it from tool parameters, so it is a
|
|
151
|
+
lever for an instrument that passes the schema through, never a contract.
|
|
152
|
+
|
|
153
|
+
- **A Chart under a name is a Workflow and a child of its wrapper, from one of two origins.** Its name is
|
|
154
|
+
`<wrapper>/<name>`, one rule for both, and `<name>` is lower-case letters, digits and hyphens, 63 at most. A **Chart
|
|
155
|
+
file** in the Assembly sits at `workflows/<wrapper>/<name>.chart.json` and carries the Chart alone: discovered one
|
|
156
|
+
level below the flat `workflows/*.ts` of
|
|
157
|
+
[ADR-0050](0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md), in a directory named after its
|
|
158
|
+
wrapper and holding nothing else jr2 reads (a `_` or `.` directory is a helper's, as ADR-0050 has it, and a
|
|
159
|
+
`.chart.json` at the flat level is a warning, never a Chart). Another file in such a directory is a warning too,
|
|
160
|
+
except in a docker context that a registered Machine names: a module ships its context beside it
|
|
161
|
+
([ADR-0037](0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md),
|
|
162
|
+
[ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so `import.meta.resolve("./python")` in
|
|
163
|
+
`workflows/pytest.ts` is `workflows/python/`, a folder jr2 reads, and the warning would be false. A Chart file is
|
|
164
|
+
published by `jr2 up`, which admits every Chart file on the host before any cluster work, reproducible from the
|
|
165
|
+
Instance repo, admitted by the same static door at the publish. A **runtime Chart** is
|
|
166
|
+
`PUT /workflows/<wrapper>/charts/<name>` and `DELETE` on the same path, the body the Chart alone, behind the Instance
|
|
167
|
+
token, not the publish token, because a Chart is data and cannot reach the process, which is the whole point of this
|
|
168
|
+
decision; it lives on the Orchestrator's volume at `.jr2/charts/<wrapper>/<name>.chart.json` beside the Assemblies,
|
|
169
|
+
written durably, and survives restarts. A live run holds its Chart through the text in its own snapshot, so a `DELETE`
|
|
170
|
+
while a run is live answers at once and the run goes on. It lists in `jr2 runs` and the Console with its origin shown:
|
|
171
|
+
`jr2 runs` keeps its result, the live runs, and prints the listing beside it as activity, with `--workflows` to make
|
|
172
|
+
the listing the result ([ADR-0009](0009-cli-and-instance-interface.md)); there is no verb for `PUT` or `DELETE`, an
|
|
173
|
+
MCP or `curl` is the client. `GET /workflows` lists each wrapper with its Charts inside its entry, not beside it, and
|
|
174
|
+
`GET /workflows/<wrapper>` carries each Open body's declaration. So a Workflow is one of three things: a Machine from
|
|
175
|
+
the Assembly, a Chart file from the Assembly, or a runtime Chart, and the last two are children of the first. **There
|
|
176
|
+
is no collision rule between a Machine and a Chart**: a Machine's name is a filename stem and holds no slash, a
|
|
177
|
+
Chart's name always holds one, and a wrapper missing from a new Assembly makes its Charts unrunnable by construction,
|
|
178
|
+
which the publish reports. Between the two origins there is one: a Chart file and a runtime Chart of one name are the
|
|
179
|
+
file's, and the runtime Chart is unrunnable, kept, until the file leaves the Assembly; a `PUT` to a name a file holds
|
|
180
|
+
is refused. Rejected: flat names with a collision refusal and the wrapper named in the `PUT` body, the shape this
|
|
181
|
+
decision first took; the binding was then a rule to check in two places instead of a fact the name and the path state.
|
|
182
|
+
**jr2 tells a Chart wrapper from two facts it already reads**, and the author marks nothing: the walk sees the Open
|
|
183
|
+
body on the registered Machine (a kit constructor in `actors`, as `agent()` is, seen through a transparent wrapper's
|
|
184
|
+
body), and the door schema ([ADR-0033](0033-a-machine-declares-the-input-that-starts-it.md)) shows which field is
|
|
185
|
+
`chartSchema`. A wrapper whose door has no Chart field is a wrapper for in-Machine Charts only, run by `jr2 run` like
|
|
186
|
+
any Workflow; one with a Chart field can also be run one-shot by HTTP and can have named Charts. `GET /workflows`
|
|
187
|
+
lists both facts. A named Chart's fixed input supplies every Chart-typed field of the wrapper's door and nothing else,
|
|
188
|
+
so a named Chart stays a Chart and not a preset of a Workflow's door, which would be a decision of its own; a Machine
|
|
189
|
+
with two Open bodies has two Chart fields and a named Chart fixes both, and a Chart, named or a field, is admitted
|
|
190
|
+
when one of the bodies admits it, each report line naming its body, because the door cannot tell which body a field
|
|
191
|
+
feeds. A `PUT` or a Chart file under a wrapper with no Chart door is refused, as is one under a Workflow that is no
|
|
192
|
+
wrapper at all. **An Assembly publish re-admits every named Chart** against the new Assembly, because a lent slot may
|
|
193
|
+
have gone: a Chart file that fails refuses the publish, since the file is the Instance's own; a runtime Chart that
|
|
194
|
+
fails is marked unrunnable and reported in the publish's answer, never deleted, because it is someone else's. An
|
|
195
|
+
unrunnable Chart still lists, with its reason, and a start of it answers 409. An MCP that lets a user's own agent
|
|
196
|
+
author and run Charts is a client of these routes plus `GET /workflows/<wrapper>` for the Open body's declaration and
|
|
197
|
+
`GET /runs/<id>/events`; whether the kit ships one is a product question, not this one.
|
|
198
|
+
- **Admission is static and mandatory, because xstate refuses nothing.** The spike found that an un-lent `invoke.src`
|
|
199
|
+
kills the WRAPPER actor with a `TypeError` that bypasses the body invoke's `onError`; an unknown action is silently
|
|
200
|
+
ignored, even in the development build; an unknown delay name fires at once; an unguarded `always` cycle exhausted the
|
|
201
|
+
heap. So the door checks, before `createActor`, in this order: the size cap, then the grammar, and if either fails
|
|
202
|
+
nothing else is read; then every fault of the rules that follow, reported together, each line with its path in the
|
|
203
|
+
Chart and what is lent or allowed. The names: every `src`, `type` and delay name against the lent set and the Chart's
|
|
204
|
+
own `agents`, and a delay below the wall clock. The events: every `on` key against the Chart's events and the events
|
|
205
|
+
against a handling state ([ADR-0011](0011-workflow-defined-events.md)'s rules), each payload within the schema subset
|
|
206
|
+
above. The minted Agents: every model against the allowed list and the placed keys, every Workspace access against the
|
|
207
|
+
allowed set. The structure: no `always` cycle without a guard, through parallel regions and history too; no `onDone`
|
|
208
|
+
on an Agent invoke, because a Turn never settles by itself, it ends on a pick in `on`; no Agent state that nothing can
|
|
209
|
+
end; no parallel state at or below an Agent's. An Agent state of a Chart needs no `agent.fault` route. A state of a
|
|
210
|
+
Machine needs one ([ADR-0074](0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md)). An
|
|
211
|
+
`agent.fault` that no transition of the Chart takes goes, unchanged, to the Machine that invoked the Open body, and
|
|
212
|
+
that Machine's `agent.fault` route takes it, as it takes the fault of an Agent of its own. So a Machine that invokes
|
|
213
|
+
an Open body that can run an Agent needs the route too, on that state or above it. A route in the Chart stays legal,
|
|
214
|
+
and it can read `{{event.reason}}`. The wrapper is the right place: a Chart under a body that lends no `fail` has no
|
|
215
|
+
correct end for a fault, because a final state means `done`
|
|
216
|
+
([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)) and a state that waits
|
|
217
|
+
holds the run until the wall clock. So the briefing does not teach the route, and the grammar's fragment does not show
|
|
218
|
+
it. And two checks that stand in for the types a Chart does not have. **Dataflow**: every reference a Chart makes,
|
|
219
|
+
`{{context.summary}}` or `{ "$ref": "event.notes" }`, must be supplied by something the door can see: the Chart's
|
|
220
|
+
initial `context`, the Chart's input (the wrapper's door less its Chart fields and the placement, or, for a body a
|
|
221
|
+
Machine invokes, what that invoke sends), the schema of the event the transition handles, or a `set`, `append` or
|
|
222
|
+
`increment` on that path or above it, where a `set` from one reference supplies what that reference supplies and
|
|
223
|
+
nothing deeper; a reference nothing supplies would resolve to an empty string in a prompt with no one the wiser, so it
|
|
224
|
+
is refused. The fields a Chart declares are its types. **Lent input shapes**: an Effect carries an `input` schema and
|
|
225
|
+
an Agent's Turn input is a fixed key set (`prompt`, `cwd`, `continue`, `model`, `thinkingLevel`, no placement), so the
|
|
226
|
+
literal parts of a Chart's `input` objects are validated at the door and the resolved object again at invoke time,
|
|
227
|
+
where a failure is that invoke's error and never a mid-Turn surprise. Last, the door compiles the Chart as the body
|
|
228
|
+
would: `jr2Setup`'s own refusals (less its `agent.fault` refusal), and the load warnings a hand-written Machine gets
|
|
229
|
+
(an unentered mode), are refusal lines for a Chart, because a planner reads a report and nobody reads a warning. A
|
|
230
|
+
refusal is the body's OUTPUT, `{ outcome: "refused", report }`, routed by the wrapper's `onDone` with a guard on
|
|
231
|
+
`outcome`, so a wrapper whose `onDone` returns to the planning state hands it to the planner as the output link in its
|
|
232
|
+
Chain ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)): "Chart refused:
|
|
233
|
+
`src: "linter"` is not a lent actor; lent: coder, reviewer, push", and the planner answers with a corrected Chart. It
|
|
234
|
+
is not the body's error, because a child's error is a defect that never reaches the model (ADR-0067, which stands); at
|
|
235
|
+
the HTTP door it is 422 with the same report. No new mechanism: an Agent reads its children's outputs through the
|
|
236
|
+
Chain already.
|
|
237
|
+
- **Counters are the Open body's.** Its declaration states the Chart's ceilings: Turns, every Agent Turn below the body,
|
|
238
|
+
a lent Machine's included; wall clock, at most xstate's timer bound of 2^31-1 ms; transitions, one microstep budget
|
|
239
|
+
for the whole run, since xstate has no loop guard; fan-out width, the children live at once; and Chart size in bytes,
|
|
240
|
+
at most 1 MiB. One bound is the kit's and not the declaration's: a context or an output above 4 MiB. A ceiling met
|
|
241
|
+
settles the body `{ outcome: "ceiling", ceiling }`, its children stopped, and a Turn one past the Turns ceiling never
|
|
242
|
+
starts. A spend ceiling checked before each model call is the right next one; jr2 meters no spend today, so it is
|
|
243
|
+
named and deferred.
|
|
244
|
+
- **The Console renders the run's Machine.** Every machine doc came from the definition, so a Chart's states would not
|
|
245
|
+
render. A per-run doc route exists, `GET /workflows/<name>/runs/<id>/machine`, and the run view draws the bound
|
|
246
|
+
Machine, keyed by the Charts the run binds now, so a planner's second Chart draws a second doc; a Chart the run cannot
|
|
247
|
+
draw (a drifted run) is named in the doc. A Chart one cannot watch is a Chart one cannot trust.
|
|
248
|
+
- **The trust statement.** Code is published by the Instance; a run supplies only data. The fence is structural, so it
|
|
249
|
+
holds whether the Agent is prompt-injected or the model is adversarial: data cannot reach the process, and a borrowed
|
|
250
|
+
Effect reaches only the host its author fixed. What remains is cost and routing abuse, which the counters bound, and a
|
|
251
|
+
Gate the Chart itself defines being approved on bad information, which is the author's Gate placement to decide.
|
|
252
|
+
|
|
253
|
+
## Considered options
|
|
254
|
+
|
|
255
|
+
- **Code, and trust the publisher**: an Agent publishes a Machine through an Effect holding the Instance token, behind a
|
|
256
|
+
human Gate. Rejected: the Gate is the only fence, on a process that holds the cluster, and a reviewer reading
|
|
257
|
+
generated code under time is not a fence
|
|
258
|
+
[ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md) would accept anywhere else.
|
|
259
|
+
- **Isolation**: a dynamic body's logic in an isolate or a worker with no secrets. Rejected: Node disclaims `vm` as a
|
|
260
|
+
security mechanism, `isolated-vm` is in maintenance mode with a bridge its author calls trivially crossed, QuickJS in
|
|
261
|
+
wasm is unaudited; the run host is in-process xstate, so this is a second Orchestrator and a coordination protocol;
|
|
262
|
+
and it buys a generality the body does not need.
|
|
263
|
+
- **Composition only, no minted Agents.** Rejected: an Agent is honestly data, and a Chart that cannot say "a tester
|
|
264
|
+
with these instructions" is not powerful enough to be worth the door.
|
|
265
|
+
- **An expression language from the start.** Rejected above: it is [ADR-0008](0008-orchestrator-as-deployed-app.md)'s
|
|
266
|
+
rejected DSL arriving through a side door; a reference kind can be added later without breaking a written Chart.
|
|
267
|
+
- **One-shot only, no Chart under a name.** Rejected: a test wants the same Chart on every run (the text already gives
|
|
268
|
+
that), but a person, another tool and an MCP want `jr2 run <name>`, a Console entry and a Chart that outlives the run
|
|
269
|
+
that wrote it; and a Chart under a name is a Workflow registered as DATA, which keeps
|
|
270
|
+
[ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)'s one publisher of CODE
|
|
271
|
+
true.
|
|
272
|
+
- **A Chart as an anonymous run with no Chart wrapper.** Rejected: every fence above is the wrapper's; and the run would
|
|
273
|
+
have no Workflow name for `jr2 status`, drift reporting and the Console.
|
|
274
|
+
- **A kit Effect that publishes a Chart from a Machine** (`publishChart`, over the Effector's signed wire, no token).
|
|
275
|
+
Deferred: nothing needs an Agent to keep a Chart yet; a Machine that wants one later runs it by name after a human or
|
|
276
|
+
a tool published it. If it comes, it is an Effect the author writes in, with or without a Gate, and never a side
|
|
277
|
+
effect of running an Open body.
|
|
278
|
+
|
|
279
|
+
## Consequences
|
|
280
|
+
|
|
281
|
+
- [ADR-0008](0008-orchestrator-as-deployed-app.md)'s "code, not config" is restated, not reversed: a human writes code;
|
|
282
|
+
what a run supplies is data. Its "dynamic plugin loading stays deferred" line retires: code is never loaded
|
|
283
|
+
dynamically, and a run may bind an Open body with a Chart.
|
|
284
|
+
- The walk learns a second mode. Today it is not `provide()`-aware and reads nothing a per-run binding adds; the door's
|
|
285
|
+
walk runs over the bound Chart, and the `jr2 up` walk reads an Open body's declaration (its lent parts, allowed
|
|
286
|
+
models, ceilings) so the keys and the Instance Harness a Chart could need are placed before any run: a body that lends
|
|
287
|
+
an Agent with a Workspace puts its allowed models on the pods, and one that allows `"none"` on the Instance Harness,
|
|
288
|
+
which is converged for it.
|
|
289
|
+
- `fingerprintOf` stays the wrapper's; the Chart's text and hash ride the body's own snapshot inside `RunBlob`, and
|
|
290
|
+
restore admits and compiles the text again. A Chart that no longer passes the door at restore is drift with that
|
|
291
|
+
reason. Under [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md) a live run
|
|
292
|
+
holds its Assembly and a publish takes no lent slot from it, so this drift guards a Kit change or a damaged volume,
|
|
293
|
+
not a later publish.
|
|
294
|
+
- The GLOSSARY gains **Chart**, **Chart wrapper**, **Open body** and **Reference**; **Workflow** gains its three
|
|
295
|
+
origins; **Open** no longer says a run never sees one. The Briefing's Turn part gains a pick's briefing
|
|
296
|
+
([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md) as amended): the Chart briefing is its first.
|
|
297
|
+
- The `@model` tier gains the claim `chart-refusal`: a planner whose pick is `run_chart` with the narrowed grammar is
|
|
298
|
+
asked for a Chart that names a slot the body does not lend, the door refuses it, and the planner's next Chart admits.
|
|
299
|
+
A Chart's prompts are its author's, so the claim measures jr2's text: the Chart briefing and the door's report. The
|
|
300
|
+
floor is 7 of 10, set ahead of a measured run. Measured 2026-10-19 on the home-lab instrument, one lever per run of
|
|
301
|
+
ten: with no briefing, 0, 4 and 2 trials (the first report was about twelve lines; the second Chart fixed most and not
|
|
302
|
+
all); with the briefing and a body that allowed no model, 0 (the first report fell to a median of three lines, but
|
|
303
|
+
eight first Charts minted an Agent under a `never` model and the measured pick hit the Menu's "must not have
|
|
304
|
+
additional properties"); with `agents` gone from that grammar, 1 of 10 (the second Chart's fault was now one line,
|
|
305
|
+
"context.summary is supplied by nothing", in six of nine, and every one of them WAS written, from a pick field the
|
|
306
|
+
Chart never put in `required`, which the line hid); with that line naming the write and the field, and the briefing
|
|
307
|
+
teaching `required`, 5 of 10, and every miss admitted on the third Chart; three of the five misses reached for the
|
|
308
|
+
kit's `gate`, offered in the `src` enum beside the one lent Agent, and the rest of the noise was the Chart sent as a
|
|
309
|
+
JSON string, which pi's validation refused ("chart: must be object") twelve times in ten trials. With the Gate a lend,
|
|
310
|
+
4 of 10 and no `gate` fault; in three of the six misses the measured pick was that string refusal, twenty times in ten
|
|
311
|
+
trials. The next run, otherwise unchanged, gave 7 of 10: the floor, and the baseline is written from that read run,
|
|
312
|
+
with the reading that the gain from 4 is the instrument's own variance across ten trials. A parse of the JSON-text
|
|
313
|
+
argument was tried in that run and measured inert, since every such string was malformed
|
|
314
|
+
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md), rejected). The misses of
|
|
315
|
+
the passing run were a Chart without `events`, a state `output` beside the Chart's, and one malformed string. A
|
|
316
|
+
thinking-on cell, run once to ask whether the misses are knowledge or care: 5 of 10 with three void trials (the first
|
|
317
|
+
Chart admitted more often), the same kinds of miss (events declared and handled by no state, `gate` named though no
|
|
318
|
+
grammar offered it), 8% longer per trial and a third more wall time for the voids. Not kept: it does not clear the
|
|
319
|
+
floor, and it answers the question: the misses are knowledge. With the worked fragment in the narrowed grammar, 9 of
|
|
320
|
+
10 with no void trial, in 15 minutes against 23: the one miss was a malformed string, the first reports fell to a
|
|
321
|
+
median of two lines, and the ten second Charts kept their own state and event names (two of ten reused the fragment's
|
|
322
|
+
`finished`). The baseline is that run's. What the next Chart gets wrong is in the trial evidence.
|
|
323
|
+
- Deferred, named: a spend ceiling; a CEL-class reference kind; `publishChart`.
|