@jr2/orchestrator 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -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 +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -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 +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -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 +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -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 +206 -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 +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -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 +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -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-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -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 +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -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 +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -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 +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -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 +95 -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-lateroutcome.md +21 -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-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -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 +22 -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 +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -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 +84 -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 +24 -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 +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -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 +27 -0
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -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 +175 -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 +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -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 +287 -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 +101 -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 +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -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 +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -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 +193 -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 +126 -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 +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -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 +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -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 +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -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 +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -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 +334 -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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- 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 +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- 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/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# A run starts a run through a `dispatch` Effect
|
|
2
|
+
|
|
3
|
+
[ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md) made composition `import` plus invoke, one run
|
|
4
|
+
and one run id, and rejected a run that starts a run "for no requirement that needs a second run id". The grill of
|
|
5
|
+
2026-10-20 brought the requirement: a long-running Machine that watches the outside (a poll of GitHub issues, a webhook
|
|
6
|
+
into a Gate) and, when something arrives, runs an existing Workflow on it, where that Workflow needs its own run: its
|
|
7
|
+
own row in `jr2 runs`, its own durability and cancel, its own Workspaces, a life that is not the watcher's, and perhaps
|
|
8
|
+
an Orchestrator that is not the watcher's. GitHub's `workflow_dispatch` through the API, an Argo Events sensor that
|
|
9
|
+
submits a Workflow, and Temporal's own advice for a child in another namespace (an Activity that starts a workflow
|
|
10
|
+
through the client) are all the same shape: a separate, independent run, started as an act of the caller. That is not
|
|
11
|
+
composition and does not replace it. The two uses stay two mechanisms.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
- **`dispatch` is a fourth Effect kind**, beside `command`, `http` and `push`
|
|
16
|
+
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)): an actor slot,
|
|
17
|
+
`actors: { review: dispatch("review", { input?, wait?, target? }) }`, with a ledger row, at-most-once, stopped when
|
|
18
|
+
its state exits, `fromPick` between a pick and it, and its output the answer to the pick that led to it. An Agent that
|
|
19
|
+
"calls a workflow" is an ordinary pick whose state invokes the slot; nothing a model reads changes, so there is no
|
|
20
|
+
`@model` claim. **The callee is named as `jr2 run` names it**, `review` or `task/review-loop`, a string constant on
|
|
21
|
+
the slot and never an `arg()` a model fills: the name is authority, the input is data. A run needs a Workflow name
|
|
22
|
+
(status, drift, the Console), so a registered Workflow is the only thing a dispatch can start, which is why a name is
|
|
23
|
+
right here where ADR-0049 rejects it for a child: invoke-by-name resolves to a Machine inside the caller's run; a
|
|
24
|
+
dispatch resolves to a run of its own. One slot per callee; a Machine that calls two Workflows declares two slots.
|
|
25
|
+
- **The invoke's input is the run's body, whole.** `input: ({ context }) => ({ ticket })` or
|
|
26
|
+
`input: fromPick(review, ({ context }) => ({ repo: context.repo }))` is the JSON that `POST /workflows/<name>/runs`
|
|
27
|
+
validates ([ADR-0033](0033-a-machine-declares-the-input-that-starts-it.md)), the same body `jr2 run --input` sends. A
|
|
28
|
+
one-shot Chart start is nothing special, `{ chart }` in the body; a named Chart is a name. Every knob is the slot's
|
|
29
|
+
(`wait`, `target`, `input`), so a run-start option that arrives later is a slot option or a door field, never a
|
|
30
|
+
wrapper around the body. Accepted with it: the pick's schema and the callee's door are two schemas the author keeps
|
|
31
|
+
aligned, and the idiom is to derive one from the other (`reviewDoor.pick({ branch: true })`); the handoff is by
|
|
32
|
+
reference, a url, a branch and a commit the callee's own Workspace clones, never a Worktree file, because the Effect
|
|
33
|
+
runs where no Worktree is; no secret rides the body (GitHub's `secrets: inherit` has no counterpart: the callee's
|
|
34
|
+
Assembly bound its own); `wait` is static, so a Machine that needs both forms declares two slots; the body is plain
|
|
35
|
+
JSON; and the callee's host-injected fields (`instanceId`) are the callee's host's, written after the parse. `input`
|
|
36
|
+
on the slot is an optional zod schema: the author's claim about the callee's door, which types the mapper and
|
|
37
|
+
`fromPick` and is what `jr2 up` compares (below). Without it the mapper returns `unknown` and the door alone checks.
|
|
38
|
+
- **Two placements, read from the target, and the author writes none.** With no `target` the Effect runs in the
|
|
39
|
+
Orchestrator's own process and starts the run through the run host and the ledger directly, no wire and no token: the
|
|
40
|
+
Orchestrator holds the Instance token already
|
|
41
|
+
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)) and is the single writer of runs, so a
|
|
42
|
+
start that went out through an Effector and back in over HTTP would carry a token for nothing. That is a fourth
|
|
43
|
+
Placement, `orchestrator`, and this is the one Effect that has it. With a target,
|
|
44
|
+
`target: { url, token: secret("ops-instance") }`, the Effect runs in the Instance Effector, which gains a `dispatch`
|
|
45
|
+
kind, holds the token as an Effect secret and calls the foreign door; the url is an origin, scheme and host and a
|
|
46
|
+
port, and a path, a query or userinfo in it is refused at build, since the Effector appends the door's path and the
|
|
47
|
+
origin is what its fence (`effector.json`) names. A packaged Machine leaves it `target: open` and the composer binds
|
|
48
|
+
the url and the token together, one Open part, with `customize(m, { effects: { task: { target: { url, token } } } })`
|
|
49
|
+
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)); `secret(open)` inside a
|
|
50
|
+
target is refused, since the whole target is the part. Another Instance may run another Kit version, so the HTTP door
|
|
51
|
+
is the only contract there. The token is the Instance token, which unlocks all control
|
|
52
|
+
([ADR-0032](0032-the-console-unlocks-with-the-instance-token.md)); a start-only token is a token kind of its own and
|
|
53
|
+
is deferred.
|
|
54
|
+
- **Two settlements, `started` by default, and every outcome is output.** `{ outcome: "started", run }` settles when the
|
|
55
|
+
Orchestrator accepts the run; the watcher goes round again. `wait: true` settles
|
|
56
|
+
`{ outcome: "settled", run, status, output }` when that run ends, `status` in the store's own words (`done`, `failed`,
|
|
57
|
+
`error`, `cancelled`, `lost`, `drifted`, [ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)) and `output` the callee
|
|
58
|
+
Machine's (for `failed`, the Failure's `{ reason, detail }`,
|
|
59
|
+
[ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)), which `GET /runs/<id>`
|
|
60
|
+
serves for a run that is no longer live from this decision on (a live run serves `active` and no output). The slot's
|
|
61
|
+
`timeout` bounds the whole Effect, the start alone or the start and the wait: 30 s by default without `wait`, 10 min
|
|
62
|
+
with it, counted from the intent row's first write, so a restore never starts the bound over, and armed again in
|
|
63
|
+
steps, so a bound past Node's 24.8-day timer holds. A run still live when it runs out settles `status: "live"`, the
|
|
64
|
+
status the run had when the Effect stopped waiting: in-process the run host says it; in the Instance Effector the last
|
|
65
|
+
poll must have read the run live, and a wait whose last poll got no answer is `unknown`. In-process the run host tells
|
|
66
|
+
the wait through a settle hook keyed by the run id, not by its hosting, so a caller restored before its callee hears
|
|
67
|
+
it when the callee settles, and hears `lost` or `drifted` when restore marks it so; foreign, the Instance Effector
|
|
68
|
+
polls `GET /runs/<id>` every 5 s as a long-running Effect, which it keeps across an Orchestrator restart already, and
|
|
69
|
+
a DELETE of the Effect ends that poll and never touches the run. No callback from the callee: a callback needs the
|
|
70
|
+
callee to hold its caller's address and a token for it, which inverts the trust. A door refusal is OUTPUT,
|
|
71
|
+
`{ outcome: "refused", status, report }`, with the door's status — 400 for bad input, 404 for a name the current
|
|
72
|
+
Assembly lacks, 409 for an unrunnable named Chart or a key another Workflow's run holds, 422 for a Chart the door
|
|
73
|
+
refuses, 413 for a start over the bytes an Effector reads, 0 when the Effector refused it before anything was sent —
|
|
74
|
+
as a Chart refusal is ([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)): the body may have come from a model,
|
|
75
|
+
and the report must be able to reach it through the Chain. A target no replica could reach is
|
|
76
|
+
`{ outcome: "unreachable" }`, output, as an `http` Effect's is (ADR-0067): nothing was started. The invoke's error is
|
|
77
|
+
kept for defects: no run host or Effector, input that fails the slot's schema, a refused or malformed wire.
|
|
78
|
+
- **The two runs are independent.** Cancelling the caller never cancels the callee; the caller's end never ends it;
|
|
79
|
+
leaving the state stops a wait and never the run; the callee's Workspaces are its own. Temporal's parent-close policy
|
|
80
|
+
(`TERMINATE` by default, `REQUEST_CANCEL`, `ABANDON`) exists because a child there is both a run and a child, and its
|
|
81
|
+
default bites on every continue-as-new of a long-lived parent; here the coupled case is `import`, so there is no
|
|
82
|
+
policy. The link is a record. The callee's row carries `dispatchedBy { run, slot, effect, depth, instance? }`: the
|
|
83
|
+
caller's run and slot, the caller's ledger row id (the idempotency key, below), the dispatched run's own depth in the
|
|
84
|
+
chain, one below its caller and stated by the caller, so one meaning on the wire, in the binding and on the row, and
|
|
85
|
+
for a foreign caller its Orchestrator's name as opaque text — its namespace, never an Instance ID, which names an
|
|
86
|
+
Agent's conversation. The open band carries the routing (`run`, `slot`, `depth`, `instance`) and never the key, which
|
|
87
|
+
names the caller's ledger and stays behind the token. The caller's ledger row carries the run it started, written to
|
|
88
|
+
the intent row the moment the start's answer names it and kept past an abort, and the caller's status lists them as
|
|
89
|
+
`dispatched [{ slot, run, at }]`, the chain's other direction. `jr2 runs` carries `dispatchedBy` after the id,
|
|
90
|
+
`jr2 status` prints `dispatched by:` and `dispatched:` lines, and the Console shows both. The word is **dispatched
|
|
91
|
+
by**. The callee reads nothing of its caller from its input: a Machine that wants to tell its callee something passes
|
|
92
|
+
it as input like any caller.
|
|
93
|
+
- **A chain of dispatches is unbounded.** A run may dispatch a Workflow that dispatches another, or itself: a run that
|
|
94
|
+
dispatches its own successor and ends is Temporal's continue-as-new, the honest way to keep a watcher's snapshot
|
|
95
|
+
small, and a depth ceiling would refuse it. A runaway chain is a resource problem the cluster owner's quota and
|
|
96
|
+
[ADR-0064](0064-a-workspace-waits-for-capacity.md)'s capacity wait bound; the depth on the row makes it readable. A
|
|
97
|
+
depth ceiling is named and deferred beside ADR-0070's spend ceiling.
|
|
98
|
+
- **At-most-once across the wire, by construction.** ADR-0067 sends a start once where it may have arrived, and a
|
|
99
|
+
foreign start whose answer was lost had an unknown outcome. The ledger row's id rides every start as an idempotency
|
|
100
|
+
key, out of band — the `jr2-dispatched-by` header, the `dispatchedBy` record as JSON, never a body field the callee's
|
|
101
|
+
door would have to reserve — and `POST /workflows/<name>/runs` asks the key before anything else, so a re-sent start
|
|
102
|
+
answers its run even when the name would be refused now: 201 with the run for a new one, 200 with
|
|
103
|
+
`{ runId, instanceId, existing: true }` for a repeat, 409 when a run of ANOTHER Workflow holds the key (the caller
|
|
104
|
+
asked for one Workflow and would wait on a run of another), 400 when the header does not parse, and 500, never 404,
|
|
105
|
+
for any other throw, since a foreign caller reads a 404 as "nothing was started" (ADR-0033 as amended). The key lives
|
|
106
|
+
in the callee's blob, and the store copies it into a `dispatch_effect` column of its own at every save and indexes
|
|
107
|
+
that, not an expression index over `json_extract`: SQLite's JSON parser refuses a blob nested about a thousand deep,
|
|
108
|
+
and such an index would fail that row's save and every later open of the volume. A run restore marks `lost` keeps its
|
|
109
|
+
row and its blob, so its key keeps answering; the weekly sweep
|
|
110
|
+
([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-a-week.md)) removes the key with the row, and a re-send
|
|
111
|
+
after it starts a new run: known, and not guarded. In-process the ledger is consulted directly, through the same start
|
|
112
|
+
under the same key, with the starts in flight held so two arrivals of one key before the first save are one run. So a
|
|
113
|
+
dispatch is idempotent whatever its author writes: its intent row says `idempotent: true`, a restore that cannot tell
|
|
114
|
+
whether its start arrived sends it again under its key with what is left of the bound, the Instance Effector sends a
|
|
115
|
+
start whose answer was lost again under its key with backoff until the bound, and the Effector's rollout grace is
|
|
116
|
+
never stretched by a dispatch's wait. Once the answer names the run, the intent row carries it, the Effector's
|
|
117
|
+
`running` state carries it on the wire, and a restore with it starts nothing: it settles `started` at once, or resumes
|
|
118
|
+
the wait. Without it a watcher double-starts a run after a restart, which a human notices as two pull requests. What
|
|
119
|
+
it does not guard: a door at a Kit version before this decision ignores the header, so a re-send to it starts a second
|
|
120
|
+
run, and neither the Effector nor the preflight can tell. A capability check is named and deferred.
|
|
121
|
+
- **`jr2 up` checks what it can see, and the severity follows who owns both sides.** For a same-Instance name the walk
|
|
122
|
+
resolves it in the Assembly and REFUSES a miss, naming the fix: on the host, among the Machines and the Chart files,
|
|
123
|
+
and a slashed name whose wrapper exists but whose Chart the host cannot see is a warning there, since a runtime Chart
|
|
124
|
+
lives on the Orchestrator's volume alone; at the publish door, among all three, and there the miss refuses the
|
|
125
|
+
publish. A dispatch of a runtime Chart under a wrapper the same publish adds takes two publishes, the wrapper first,
|
|
126
|
+
then the Chart's PUT, then the dispatch, and the refusal says so. A Workflow is named by its filename stem, so a stem
|
|
127
|
+
outside `[a-z0-9-]` is refused at load, naming the rename: `jr2 run`, the door's path and a `dispatch` slot name one
|
|
128
|
+
set of Workflows. ADR-0067's "a pick param nothing reads" check runs at `createMachine`, before any callee is known,
|
|
129
|
+
so it reads the slot's `input` schema's keys, and with no schema it reads nothing and skips; the Chart door, for a
|
|
130
|
+
lent dispatch with no schema, takes any object. The walk then compares the slot's `input` schema, if given, with the
|
|
131
|
+
callee's door and REFUSES a mismatch. For a foreign target it does `GET /workflows/<name>` from the host with NO
|
|
132
|
+
token, since the route is open band ([ADR-0014](0014-observation-is-open-run-state-is-not.md)) and the token is the
|
|
133
|
+
Effector's to carry, compares the served JSON Schema with the slot's schema the same way, and WARNS on no reach, a
|
|
134
|
+
miss, a mismatch, an answer that is no Workflow detail or a redirect, never refuses: the other Instance may be down,
|
|
135
|
+
reached only from inside its cluster, or published later, and a refusal would couple two Instances' publish order. The
|
|
136
|
+
token's own refusal is a run's to answer. **A match is compatibility, one direction**: every required field of the
|
|
137
|
+
door is in the slot's schema, required there too, with a compatible type, within the door's `enum`, `const` and
|
|
138
|
+
bounds, and every field of the slot's schema exists in the door, since a plain zod door strips the rest silently; the
|
|
139
|
+
slot's schema is read as what the driver sends (`io: "output"`, its defaults filled), the door as what it takes. It
|
|
140
|
+
compares within the JSON Schema subset ADR-0070 names (`type`, `properties`, `required`, `items`, `enum`, `const`, the
|
|
141
|
+
bounds); a schema that uses something outside it (`oneOf`, `$ref`) is reported "not compared", never guessed at, a
|
|
142
|
+
warning at `jr2 up` which the publish door, with no warnings channel, drops. **A dispatch resolves at run time against
|
|
143
|
+
the current Assembly**, as `jr2 run` does: a dispatched run is a new run and starts under the Assembly current then
|
|
144
|
+
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)), which may be newer than
|
|
145
|
+
the caller's. A publish that keeps the caller and drops the callee is refused by the walk, as above; one that drops
|
|
146
|
+
both leaves a live caller to answer `refused` with the 404's report when it dispatches; pinning a dispatch to the
|
|
147
|
+
caller's Assembly would run a Workflow the Instance no longer publishes.
|
|
148
|
+
- **Tests.** The mechanics tier (socket-free, the stub Harness; `features/dispatch.feature`) owns the in-process path:
|
|
149
|
+
the ledger row and the listing both ways, `wait` and the served output, restore mid-wait with one run, cancel of the
|
|
150
|
+
caller with the callee live, the 400 and the 404 as output, the publish refusal of a miss, and two HTTP starts under
|
|
151
|
+
one key as one run. One `@kind` scenario has the Instance dispatch to ITSELF by url and token, so the Instance
|
|
152
|
+
Effector's foreign path, the key and the poll run with no second cluster, across an Orchestrator restart. No `@model`
|
|
153
|
+
claim: the four new pick answer texts are rendered by the generic path every Effect's answer takes.
|
|
154
|
+
|
|
155
|
+
## Considered options
|
|
156
|
+
|
|
157
|
+
- **Keep ADR-0049 and compose.** `import` plus invoke, a `workspace()` nested where the callee needs its own Sandbox,
|
|
158
|
+
and `pool()` for fan-out. It is still the answer for a coupled child. Rejected for this use because the callee cannot
|
|
159
|
+
have its own row, its own cancel, a life past the caller's, or another Orchestrator.
|
|
160
|
+
- **A bare `http` Effect against the door.** Works today for a foreign Instance. Rejected: it puts the Instance token in
|
|
161
|
+
the author's hands for the same-Instance case, gives `jr2 up` nothing to check, and has no idempotency key.
|
|
162
|
+
- **A sub-run as a child actor with its own id** (Temporal's child workflow). Rejected as ADR-0049 did: cross-run
|
|
163
|
+
correlation and cancel propagation inside one actor tree, for a run that must be independent anyway.
|
|
164
|
+
- **A callback from the callee for `wait`.** Rejected above: it inverts who holds the token.
|
|
165
|
+
- **A depth ceiling, or a static refusal of self-dispatch.** Rejected above: both refuse continue-as-new.
|
|
166
|
+
- **The body wrapped as `{ input, options }`.** Rejected: it taxes every dispatch now for a run-start option the door
|
|
167
|
+
has never had.
|
|
168
|
+
- **One `dispatch()` slot that takes the name as input.** Rejected: the name is authority and stays a constant.
|
|
169
|
+
|
|
170
|
+
## Consequences
|
|
171
|
+
|
|
172
|
+
- ADR-0049's "no sub-run" is restated, not reversed: a nested Machine is reached by `import`, never by name; a run that
|
|
173
|
+
starts a run is a `dispatch`, by name, and the two are different uses. ADR-0067 gains the kind and a fourth Placement,
|
|
174
|
+
`orchestrator`. ADR-0068's Instance Effector gains the `dispatch` kind, its stop route stops a dispatch's wait too,
|
|
175
|
+
and its `running` state carries the run. ADR-0033's door gains an idempotency key.
|
|
176
|
+
- The GLOSSARY's **Effect** gains "or starts a run", and `_Avoid_: sub-run, child run, nested run, spawned run` for the
|
|
177
|
+
relation, which is **dispatched by**; its **Effector** and **Instance Effector** entries name the in-process exception
|
|
178
|
+
and the new kind.
|
|
179
|
+
- `RunStatus` gains `output` for a run that is no longer live, `dispatchedBy` and `dispatched`; `RunObservation` the
|
|
180
|
+
routing of both, without the key.
|
|
181
|
+
- An Open body may lend a `dispatch` slot as it lends any Effect (`lends.effects`), so a Chart can start a run of a
|
|
182
|
+
Workflow its wrapper chose; the Chart fills the body only.
|
|
183
|
+
- Deferred, named: a start-only token; a depth ceiling; a capability check for a door that ignores the key.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# The docs ship inside the Kit, and an Instance's `AGENTS.md` indexes them
|
|
2
|
+
|
|
3
|
+
Most Machines will be written by an Assistant, not by a human: the human asks for a Machine and reviews its graph. No
|
|
4
|
+
model's training data holds jr2's API, so an Assistant needs the docs for the exact Kit version the Instance resolves,
|
|
5
|
+
and it must find them without being told to look. The docs-site grill of 2026-10 settled where those docs live and how
|
|
6
|
+
an Assistant reaches them. Its evidence is Vercel's evals on APIs that are not in training data: a compressed docs index
|
|
7
|
+
inline in `AGENTS.md` passed 100% of cases; skills passed 79% at best, because the agent did not invoke the skill in 56%
|
|
8
|
+
of cases; no docs passed 53% ([Vercel](https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals)).
|
|
9
|
+
Next.js ships the same design: its docs as Markdown inside the `next` package, and a managed block in the project's
|
|
10
|
+
`AGENTS.md` ([Next.js](https://nextjs.org/docs/app/guides/ai-agents)).
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- **One Markdown source, three outputs.**
|
|
15
|
+
- **The bundle.** `@jr2/orchestrator` ships `docs/`: every page of the sitemap, the generated Reference, the Glossary
|
|
16
|
+
and the Decisions, as plain Markdown. No site-generator syntax reaches it. It is the main channel for an Assistant.
|
|
17
|
+
Each page is at its slug, such as `concepts/gates.md`, and each ADR is at `reference/decisions/<file>`. An Assistant
|
|
18
|
+
reads a page with its file tool, which cuts off a long file. So the Authoring surface is two parts:
|
|
19
|
+
`reference/authoring-surface.md`, a contents page that gives each name its kind and the first sentence of its TSDoc,
|
|
20
|
+
and `reference/authoring-surface/<anchor>.md`, one page for each export and for each type that a signature names.
|
|
21
|
+
One page of all the names is about 200 KB, and the contents page is less than 25 KB. No line of the contents page or
|
|
22
|
+
of an entry page is longer than 1,000 characters, so the type plumbing is a list, not a paragraph. When the contents
|
|
23
|
+
page passes 25 KB, first make the long first sentences shorter; if it still passes, give each group of the contents
|
|
24
|
+
page a page of its own, and make the contents page a list of the groups. The first sentence ends at a stop, not at a
|
|
25
|
+
colon, because the words before a colon often tell nothing alone ("An invoke."). A link to an entry goes to its
|
|
26
|
+
page. Each of the other generated Reference pages (CLI, config, machines, mechanism events, HTTP API) stays one
|
|
27
|
+
page. Each is less than 30 KB, the size that one read of a file tool shows, with one exception: the HTTP API page is
|
|
28
|
+
about 50 KB. It is one page because its routes come first and are less than 30 KB, so one read shows every route;
|
|
29
|
+
the types of the bodies come after the routes, and an Assistant reads one of them with an offset or by its heading.
|
|
30
|
+
When the routes pass 30 KB, give the types a page of their own. The alternative, a split of the HTTP API page now,
|
|
31
|
+
is rejected because a route and the types it names are then on two pages, and one read shows all routes already. The
|
|
32
|
+
Glossary and the Decisions are not generated, and this rule does not hold them: an ADR can be about 50 KB, and an
|
|
33
|
+
Assistant reads the one ADR that a page links to.
|
|
34
|
+
- **The `AGENTS.md` block.** A compressed index of the bundle, and an instruction in five parts. jr2 is newer than
|
|
35
|
+
your training data, so read the bundled docs before you write a Machine or config. Start with the guide "Write a
|
|
36
|
+
workflow" and the contents page of the Authoring surface. Read one page with each call of the file tool. Run
|
|
37
|
+
`npx --no jr2 check` before you give a change to the user. A final state means that the work succeeded; when it did
|
|
38
|
+
not, invoke `fail()`, and do not throw to fail. The Failure rule is in the block because no refusal can teach it:
|
|
39
|
+
the walk cannot see that a final state means "this did not succeed", or that a promise throws only to fail, and in
|
|
40
|
+
the eval of [ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md) three of the
|
|
41
|
+
four trials wrote a failure that ended `done` or `error`. The two entry pages in one read are more than about 30 KB,
|
|
42
|
+
and a file tool then shows only the start of the output. The check is in the block because `npm run typecheck` loads
|
|
43
|
+
no module. Thus it does not find a refusal of `createMachine`, `defineEvent` or `workspace()`. An Assistant that may
|
|
44
|
+
not run `jr2 up` gets those refusals only from `jr2 check` (ADR-0009). The command has `--no` because npx with no
|
|
45
|
+
TTY installs a missing package with no prompt. The package `jr2` on the npm registry is not jr2. With `--no`, npx
|
|
46
|
+
stops when the Instance has no local `jr2` bin, for example before `npm install`. `npm run check` also does not
|
|
47
|
+
fetch, but an Instance from an older scaffold has no `check` script. The index names each file of a directory, but
|
|
48
|
+
the Decisions folder and the Authoring surface folder each have one entry, which names the contents page of the
|
|
49
|
+
folder. Thus the line stays short.
|
|
50
|
+
- **The site**, on GitHub Pages. HTML for humans, and every page also at a `.md` URL, plus `llms.txt`, for tools that
|
|
51
|
+
read over the network.
|
|
52
|
+
- **The docs ride `@jr2/orchestrator`, not a package of their own.** The Kit version _is_ the `@jr2/orchestrator` the
|
|
53
|
+
Instance resolves, so the bundle matches the code the Assistant writes against by construction. There is nothing else
|
|
54
|
+
to pin.
|
|
55
|
+
- **`jr2 init` writes the block, and `jr2 up` keeps it current.** `init` writes `AGENTS.md` with the block between two
|
|
56
|
+
markers, and a `CLAUDE.md` that holds `@AGENTS.md`, because Claude Code reads `AGENTS.md` only when no `CLAUDE.md`
|
|
57
|
+
exists. `init` takes the block from the bundle of the CLI's own `@jr2/orchestrator` when that is the version it pins;
|
|
58
|
+
without that bundle it leaves the markers empty. An `AGENTS.md` or a `CLAUDE.md` that exists stays as it is, and
|
|
59
|
+
`init` says what to add to it. `up` rewrites the text between the markers when it differs from its Kit version's
|
|
60
|
+
block, changes nothing outside them, and prints one line: `AGENTS.md: jr2 block updated for <version>`. It does this
|
|
61
|
+
before the cluster work, so a converge that fails still updates the block. `up` is the verb because every upgrade ends
|
|
62
|
+
in `up`. A folder without the markers has opted out: `up` never creates the file or the markers, and no config key
|
|
63
|
+
exists for it. A broken marker pair is reported, and `up` does not change the file.
|
|
64
|
+
- **The Reference is generated from the code wherever the code holds the facts.**
|
|
65
|
+
|
|
66
|
+
| Page | Source |
|
|
67
|
+
| ------------------------------------------------------- | ------------------------------------------------------ |
|
|
68
|
+
| Authoring surface: a contents page, and a page per name | TSDoc on the exports, exactly the root's list |
|
|
69
|
+
| `@jr2/machines` | TSDoc on the exports |
|
|
70
|
+
| `jr2.config.ts` | TSDoc on the `JR2Config` fields |
|
|
71
|
+
| CLI | a structured command table, which also prints `--help` |
|
|
72
|
+
| HTTP API | the route table in `http.ts`, made data |
|
|
73
|
+
| Mechanism events | the code that defines them |
|
|
74
|
+
| Glossary, Decisions | `GLOSSARY.md` and `docs/adr/`, rendered as they are |
|
|
75
|
+
|
|
76
|
+
The TSDoc on an Authoring surface export is written for the author: what it does, what it refuses, what to read next,
|
|
77
|
+
and one example. Maintainer reasoning goes in `//` comments in the body. The packages ship their `.ts` source, so an
|
|
78
|
+
Assistant that follows an import into `node_modules` reads the same text the site shows. The Authoring surface is
|
|
79
|
+
exactly what `@jr2/orchestrator` exports from its root: what the kit's packages share is internal even where it can be
|
|
80
|
+
imported, and `@jr2/machines` is not part of it, because it ships Machines, not names to author with.
|
|
81
|
+
|
|
82
|
+
- **`docs:build` assembles the bundle at pack time.** Its inputs are the hand-written pages in `docs/site/`, the
|
|
83
|
+
generated Reference, `GLOSSARY.md`, and `docs/adr/`. It runs in `@jr2/orchestrator`'s `prepack` and before the site
|
|
84
|
+
build. Its output is gitignored, and no generated file is committed. A link that leaves the docs set becomes a GitHub
|
|
85
|
+
URL at the release tag; a link that resolves nowhere fails the build. The `@dist` tier asserts that the packed bundle
|
|
86
|
+
exists and that the `AGENTS.md` from `jr2 init` indexes it.
|
|
87
|
+
- **The site describes the latest release and deploys on its tag**, in a `pages` job in `release.yml` that needs the
|
|
88
|
+
release job, so a failed gate deploys nothing. It does not wait for the staged packages' approval
|
|
89
|
+
([ADR-0055](0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md)).
|
|
90
|
+
- **VitePress builds the site, from the pages as they are.** The source format is VitePress's own Markdown, so a page
|
|
91
|
+
renders on GitHub and in the site, and an error names the file that a writer edits. `docs:build` accepts four forms of
|
|
92
|
+
it, and makes each one plain for the bundle: an aside, the provider tabs, a code group, and a `<<<` code import. It
|
|
93
|
+
refuses every other form, so no generator syntax reaches the bundle. The provider tabs are `::: tabs key:provider`
|
|
94
|
+
from `vitepress-plugin-tabs`, not VitePress's `::: code-group`: a provider tab holds prose as well as code, and the
|
|
95
|
+
plugin keeps the reader's choice on each page, which a code group does not. `docs:build` refuses tabs with another
|
|
96
|
+
key, and a `<<<` of a file outside `docs/site/examples/`, which `pnpm typecheck` checks. A `mermaid` fence is a theme
|
|
97
|
+
component that imports mermaid only on a page with a diagram. The site has local search, a `sitemap.xml`, and the
|
|
98
|
+
`.md` URL of each page in a `<link rel="alternate">`.
|
|
99
|
+
|
|
100
|
+
## Considered options
|
|
101
|
+
|
|
102
|
+
- **Skills.** Rejected as the channel for API knowledge: the Vercel evals above, and an agent decides whether to invoke
|
|
103
|
+
a skill, where it always reads `AGENTS.md`. Skills stay possible for a multi-step job, such as an upgrade.
|
|
104
|
+
- **A docs MCP server.** Rejected: one more process to run for text that can sit on disk. MCP is for acting on a running
|
|
105
|
+
system, not for reading docs.
|
|
106
|
+
- **The site and `llms.txt` only.** Rejected: the site describes the latest release, and an Instance can pin an older
|
|
107
|
+
one. An Assistant also fetches over the network inconsistently, and some run without network.
|
|
108
|
+
- **A separate `@jr2/docs` package.** Rejected: the Instance must install it and pin it to the Kit version, and a
|
|
109
|
+
mismatch is silent. Inside `@jr2/orchestrator`, it cannot mismatch.
|
|
110
|
+
- **The Authoring surface as one page.** Rejected: an Assistant's file tool cuts it off, so it reads the first entries
|
|
111
|
+
and not the rest.
|
|
112
|
+
- **An entry page named by its export, such as `Customize.md`.** Rejected: `customize` and `Customize` are both exports,
|
|
113
|
+
and on a file system that ignores case (macOS, Windows) the two are one file. A page is named by its anchor, which is
|
|
114
|
+
lower case and starts with `type-` for a type.
|
|
115
|
+
- **A hand-written Reference.** Rejected: it drifts from the code, and the Reference is what an Assistant trusts most.
|
|
116
|
+
- **Copies of the Glossary and the ADRs committed inside the package**, kept equal by a drift check. Rejected: two
|
|
117
|
+
copies of one text, to avoid one pack-time step.
|
|
118
|
+
- **`AGENTS.md` written once by `init`.** Rejected: it goes stale at the first upgrade. **A `jr2 docs` command to
|
|
119
|
+
refresh it** is rejected too: a step that someone must remember.
|
|
120
|
+
- **Deploy when npm serves the version** — an hourly job that compares `npm view` with the deployed version. Rejected:
|
|
121
|
+
more machinery to close a window of minutes.
|
|
122
|
+
- **Astro Starlight.** Built to the same bundle, and compared with VitePress on the same pages. It had better search and
|
|
123
|
+
a lighter page, which VitePress gets with a small search fix and a mermaid component. Rejected: its tabs and code
|
|
124
|
+
imports are MDX components, so 28 pages become `.mdx` that GitHub shows as source text, and a page carries more than
|
|
125
|
+
twice the generator lines.
|
|
126
|
+
|
|
127
|
+
## Consequences
|
|
128
|
+
|
|
129
|
+
- `jr2 up` writes into the Instance folder, and only between the markers.
|
|
130
|
+
- `@jr2/orchestrator` gains its first pack-time step. Its source still ships as `.ts`; only `docs/` is assembled.
|
|
131
|
+
- The Orchestrator's Kit image packs `@jr2/orchestrator` with `npm pack --ignore-scripts`, and its build context drops
|
|
132
|
+
`docs/`: the `prepack` needs pnpm and the whole repo, which the image stage has not, and the Orchestrator's process
|
|
133
|
+
never reads the bundle. So the image holds no docs, and a local `docs:build` does not move its tag
|
|
134
|
+
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)).
|
|
135
|
+
- The tarball grows by the docs, most of it the ADRs (about 0.7 MB of text today).
|
|
136
|
+
- A TSDoc edit on the Authoring surface is a docs edit. `--help` and the CLI Reference come from one table, so they
|
|
137
|
+
cannot disagree.
|
|
138
|
+
- A docs fix goes live with the next release, not on merge.
|
|
139
|
+
- A rejected stage leaves the site describing a version that npm does not serve, until the next tag. Accepted: the
|
|
140
|
+
window is short, and a rejected stage is rare.
|
|
141
|
+
- The bundle is plain Markdown, so a change of the site generator changes `build/vitepress.ts` and the pages' forms, and
|
|
142
|
+
nothing that an Assistant reads.
|
|
143
|
+
- VitePress 1.x is on Vite 5, with dev-server advisories that the site cannot fix. The site is static, so they reach
|
|
144
|
+
only `docs:dev`. A move to VitePress 2 is a later change.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# A Machine fails a run with `fail()`, and an uncaught Failure ends it `failed`
|
|
2
|
+
|
|
3
|
+
A run ended `done` when its Machine got to a final state, and that did not say that the work succeeded. No Machine could
|
|
4
|
+
end a run as a failure. An eval in which an Assistant writes jr2 Workflows showed the gap: three of the four
|
|
5
|
+
ticket-to-pr trials wanted a failure that is not `done`. Two threw from a plain `fromPromise` ("the planner changed the
|
|
6
|
+
code", "the push failed"). The run then ended `error`, with the message in `fault`, no output and the Sandbox kept, so
|
|
7
|
+
an operator could not tell a kit bug from a failed ticket. One trial wrote `failed` and `lost` final states with the
|
|
8
|
+
comment "the run faults rather than lie about a decision", and that run ended `done`. The fourth put a
|
|
9
|
+
`failed: "<reason>"` variant in its output union, which every reader of the run then had to know about.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
- **A Failure is the end that a Machine chooses for work that did not succeed.** A Machine makes one with the Part
|
|
14
|
+
`fail()`: `actors: { fail: fail() }`, invoked as `src: "fail"` from any state, at any depth. Its input is
|
|
15
|
+
`{ reason: string; detail?: unknown }`: `reason` is text for a person, and `detail` is optional JSON for a machine
|
|
16
|
+
reader. The Part throws an error with a brand. The brand is a registry key (`Symbol.for`), because an Instance can
|
|
17
|
+
load a second copy of the kit, and both copies must read the same brand. The kit does not export it, and an author
|
|
18
|
+
makes a Failure only with `fail()`. It is a Part and not an Effect, because it does no work outside the Machine. The
|
|
19
|
+
slot name is the author's; the docs use `fail`, never `abort`, which is a Settlement's word and the wire's.
|
|
20
|
+
- **A Failure goes up by xstate's own error rules.** It goes from the state that invokes `fail` to each parent Machine
|
|
21
|
+
in turn, and an `onError` on the way catches it, as a `catch` does. `isFailure(error)` tells a Failure from a defect
|
|
22
|
+
and narrows it to the payload:
|
|
23
|
+
`onError: [{ guard: ({ event }) => isFailure(event.error), target: "replan" }, { target: "defect" }]`. In a Chart, the
|
|
24
|
+
same guard is `jr2.failure`: every Chart has it, as every Chart has `jr2.sentBy`, because it is a predicate and gives
|
|
25
|
+
no capability.
|
|
26
|
+
- **An uncaught Failure ends the run `failed`.** `failed` is a Run status of its own, beside `done` and `error`. `fault`
|
|
27
|
+
stays empty, and the run's output is the payload, not a value of the Machine's output type. `jr2 status` and the
|
|
28
|
+
Console show the reason, and the Console gives `failed` a color that is not the red of `error`.
|
|
29
|
+
- **A Failure deletes the Sandbox.** A Failure is a chosen end, as `done` is, and each kept Sandbox holds its whole Size
|
|
30
|
+
([ADR-0060](0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md)) until its Lease lapses. `workspace()`
|
|
31
|
+
catches a Failure from its body: `running` gets `onError` guarded by `isFailure` to a `failing` state, which runs the
|
|
32
|
+
same `destroy` as `teardown` and then invokes `fail` again with the same payload. So `workspace()` still owns the
|
|
33
|
+
Sandbox's life ([ADR-0012](0012-workspace-wrapper-machine.md)), a caught Failure leaves no Sandbox behind, and the
|
|
34
|
+
parent gets the Failure unchanged. A defect still goes up with no `onError`, and its Sandbox stays as evidence. The
|
|
35
|
+
host deletes every Sandbox of a run that ends `failed`, after the stop, as `cancel` does
|
|
36
|
+
([ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)); after the wrapper's own delete, that is only a backstop. When a
|
|
37
|
+
Machine above catches the Failure, the host stops the children of the Machine that the Failure ended, and after the
|
|
38
|
+
stop it deletes their Sandboxes, because a stop skips `teardown`. It does not delete a name that a live Workspace of
|
|
39
|
+
the run holds then, and nor does a Workspace that was stopped in `placing`
|
|
40
|
+
([ADR-0064](0064-a-workspace-waits-for-capacity.md)): a parent that invokes the same Machine again gives the new
|
|
41
|
+
Workspace the same invoke id, and so the same Sandbox name. That new Workspace takes over the Sandbox and its
|
|
42
|
+
Worktrees, as a restore does, with the work of the failed attempt still in it. To keep the work for a person to read,
|
|
43
|
+
the Machine waits at a Gate before it fails.
|
|
44
|
+
- **`jr2 run` exits 0 only for `done`.** It exits 1 for every other end: `failed`, `error`, `cancelled`, `lost`,
|
|
45
|
+
`drifted`, and a run that was parked before it settled. Exit code 2 stays the CLI's code for a usage error. The status
|
|
46
|
+
line on stderr tells which end it was, and `jr2 status --json` tells a script.
|
|
47
|
+
- **A `dispatch` parent reads `failed` as a status.** `settled.status` gets `failed`, on the in-process path and in the
|
|
48
|
+
foreign `DispatchOutput` shape ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), and `output` is the
|
|
49
|
+
payload.
|
|
50
|
+
- **Each state that can fail the run is static.** The walk finds each state that invokes a `fail()` slot, also in a
|
|
51
|
+
nested Machine. `jr2 up`, `jr2 check` and the Machine doc list them, as they list Gates and Effects.
|
|
52
|
+
- **A Machine lends `fail` to a Chart as it lends `gate`**: `lends: { fail: true }`
|
|
53
|
+
([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)). The Machine decides if a Chart can end its run as a
|
|
54
|
+
Failure, and it can catch one with an `onError` on its Open body state.
|
|
55
|
+
- **A plain throw stays a defect.** Logic that throws ends the run `error`, as before. jr2 cannot see in the walk that a
|
|
56
|
+
promise only throws, so the docs say it: to fail, invoke `fail`; never throw to fail, and a final state means `done`.
|
|
57
|
+
- **A decision of a person fails the run only when it ends the run.** The rule is about the state where the run ends. A
|
|
58
|
+
`reject` that sends the work back is a usual route. A decision that ends the run, such as a `reject` at a Gate or a
|
|
59
|
+
`roll_back`, goes to a state that invokes `fail` with the reason of the person, not to a final state, and
|
|
60
|
+
`{ outcome: "rejected" }` leaves the output unions. A Machine whose job is the decision itself, such as a triage where
|
|
61
|
+
"no" is a correct answer, keeps a final state and puts the decision in its output.
|
|
62
|
+
- **The shipped `task` Machine fails when its Sandbox is lost.** Its `lost` state invokes `fail` and is no longer final,
|
|
63
|
+
and `outcome` leaves `TaskOutput`, which is `{ branch }`. A parent that read `outcome: "lost"` now catches the
|
|
64
|
+
Failure. `pool()` is such a parent: it keeps a worker's Failure in `failures` and goes on, so that one lost worker
|
|
65
|
+
does not delete the Sandboxes of its siblings ([ADR-0017](0017-pool-over-a-source-port.md)). When it ends with
|
|
66
|
+
failures, `drained` or `deadlocked`, it fails the run with the reason `"<n> of <m> items failed"` and the detail
|
|
67
|
+
`{ status, items, failures }`, unless its option `failures` is `"report"`: then it ends `done`, with the failures in
|
|
68
|
+
its output. The docs examples whose final states mean "this did not succeed" change in the same way.
|
|
69
|
+
- **A restart replays a Failure.** The state that invokes `fail` persists as any state does
|
|
70
|
+
([ADR-0007](0007-durable-machine-state.md)), and on restore `fail` runs again and throws again.
|
|
71
|
+
|
|
72
|
+
## Considered options
|
|
73
|
+
|
|
74
|
+
- **A failure is only the Workflow's output** (the fourth trial's shape). Rejected: the `jr2 run` exit code, the Sandbox
|
|
75
|
+
decision and the Console must react the same way for every Workflow, and none of them can read a Workflow's own output
|
|
76
|
+
shape. Each CI script would parse the output of each Workflow.
|
|
77
|
+
- **`jr2 run --fail-on <path>`.** Rejected: it puts a fact of the run in one client, so the Console and `dispatch` do
|
|
78
|
+
not see it.
|
|
79
|
+
- **A tag, `run.failed`, on a top-level final state.** Plain xstate and static, and it was this ADR's first form.
|
|
80
|
+
Rejected: only the root's final state can give the status, so each level between the failure and the root must carry
|
|
81
|
+
it. xstate's done event carries only the output, so `workspace()` would need a second, tagged final state and a fact
|
|
82
|
+
in its context; the Chart wrapper would need a new outcome; each nested Machine would map its child's output to a
|
|
83
|
+
tagged final state of its own. A throw needs none of that, because xstate already sends an unhandled error up through
|
|
84
|
+
each parent.
|
|
85
|
+
- **An output shape that jr2 reads**, such as `{ failed: reason }`. Rejected: it gives jr2's meaning to a key in the
|
|
86
|
+
author's own data, which is hidden mechanism ([ADR-0015](0015-authoring-surface-absorbs-the-mechanism.md)), and it
|
|
87
|
+
takes part of the output type from the author.
|
|
88
|
+
- **The unbranded throw** that the trials wrote. Rejected: it ends `error` with no output, and nobody can tell it from a
|
|
89
|
+
defect.
|
|
90
|
+
- **A separate exit code for `failed`.** Rejected: CI needs to know that the run did not succeed, and the status tells
|
|
91
|
+
which end it was.
|
|
92
|
+
- **A typed `fail<T>()`** with an input schema. Not now: it adds a type parameter to the Authoring surface, and no trial
|
|
93
|
+
needed a typed `detail`.
|
|
94
|
+
- **`fail` in every Chart, with no lend.** Rejected: the Machine decides which ends a Chart can choose, as it decides if
|
|
95
|
+
a Chart can wait at a Gate.
|
|
96
|
+
- **A failed run keeps its Sandbox**, as a fault does. Rejected: a fault keeps it because nobody chose that end; a
|
|
97
|
+
Failure is chosen, a CI loop of failed tickets would fill the cluster, and a Gate keeps the work when the author wants
|
|
98
|
+
it.
|
|
99
|
+
- **Another word than `failed`**, because a Settlement has a `failed` value too. Rejected: `failed` is the word CI and
|
|
100
|
+
people use, the two are about different things (a run and a Submission), and each glossary entry names its subject.
|
|
101
|
+
|
|
102
|
+
## Consequences
|
|
103
|
+
|
|
104
|
+
- **`onError` carries two things**: a defect or a Failure, which `isFailure` separates
|
|
105
|
+
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)). A broad
|
|
106
|
+
`onError: "retry"` catches a Failure too, as a broad `catch` does.
|
|
107
|
+
- **A failed run's output has the kit's shape**, so `DispatchOutput` has a branch for `failed` and a parent reads the
|
|
108
|
+
payload there.
|
|
109
|
+
- **An uncaught Failure stops the tree** as an error does: exit actions and `teardown` above the failing state do not
|
|
110
|
+
run, and the host does their cleanup.
|
|
111
|
+
- **`done` now means that the work succeeded**, by the convention that the docs teach: a Machine that did not succeed
|
|
112
|
+
fails.
|
|
113
|
+
- The e2e and `@model` settle loops treat every status that is not `live` as settled (`features/lib/run-settled.ts`), so
|
|
114
|
+
a `failed`, `cancelled` or `lost` run does not poll until the timeout. The Workflow fixtures of the `@kind` and
|
|
115
|
+
`@model` tiers that end in a `faulted` or `lost` final state invoke `fail`. A trial's note prints the Failure of a
|
|
116
|
+
`failed` run: `EXPLAINED_STATUSES` in `features/lib/model-status.ts` lists `failed` with the other statuses that it
|
|
117
|
+
explains, and `model-finished.ts` counts only `done` as finished. The Chart fixtures keep their final states: no
|
|
118
|
+
wrapper that runs them lends `fail`, and they test the door and the routing, not how a run ends.
|
|
119
|
+
- The change touches what a model reads (the docs, the AGENTS.md index, the fixtures' Machines), so the `@model` tier
|
|
120
|
+
runs before it is done
|
|
121
|
+
([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md)), and the agents-md
|
|
122
|
+
eval runs against it.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# An unrouted mechanism event is refused at load and faults the run
|
|
2
|
+
|
|
3
|
+
jr2 sends `agent.fault` to the Machine that invoked an Agent when the Agent's Turn cannot go on
|
|
4
|
+
([ADR-0016](0016-agent-turn-mechanics-are-internal.md)), and `workspace()` sends `workspace.lost` to its body when the
|
|
5
|
+
Sandbox is gone ([ADR-0012](0012-workspace-wrapper-machine.md)). Both go by `sendBack`, and xstate drops an event that
|
|
6
|
+
no active state handles. So a Machine that does not route `agent.fault` waits for ever in the Agent's state with a dead
|
|
7
|
+
Agent, and a body that does not route `workspace.lost` shows healthy on the feed with no Sandbox
|
|
8
|
+
([ADR-0022](0022-observation-is-a-level-triggered-feed.md)). Six of the 12 trials of an eval in which an Assistant
|
|
9
|
+
writes jr2 Workflows did not route `agent.fault`. Each of those Machines loaded, and each could wait for ever.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
- **The walk refuses a Machine with no route for a mechanism event.** It refuses a state that invokes an Agent, or an
|
|
14
|
+
Open body that lends an Agent or allows a model, when that state and all its ancestors have no `agent.fault` route,
|
|
15
|
+
and a state of a `workspace()` body that the body can stay in when that state and all its ancestors have no
|
|
16
|
+
`workspace.lost` route. The states that a body can stay in are each leaf that is not final, and each compound state
|
|
17
|
+
whose nested final state has no `onDone` on the compound state: the body then stays in the compound state. An event
|
|
18
|
+
goes to the active leaves first, so a route on each leaf, or above it, is a route for the compound state above them. A
|
|
19
|
+
final state of the root ends the body, and a final state of a parallel region leaves the other regions active, so
|
|
20
|
+
neither is judged. One route on the root satisfies each rule. The `agent.fault` refusal is in
|
|
21
|
+
`jr2Setup().createMachine`, beside the lost-output refusal, so `jr2 check`, `jr2 up` and the Orchestrator's load all
|
|
22
|
+
show it. The `workspace.lost` refusal is in `workspace()`, which is the one place that knows that a Machine is a body.
|
|
23
|
+
- **At runtime, a mechanism event that no transition took faults the run `error`.** The run host sees it in the
|
|
24
|
+
inspection stream: a microstep for `agent.fault` or `workspace.lost` with no transitions, in the Machine that got the
|
|
25
|
+
event (for `workspace.lost`, the body, because the wrapper always takes its own transition to forward it). `fault`
|
|
26
|
+
names the event, the state and the event's `reason`. This catches the route that the walk cannot judge: a guard that
|
|
27
|
+
is false when the event comes.
|
|
28
|
+
- **In a Chart, the `agent.fault` route is optional, and the Machine that invoked the Open body routes the rest.** The
|
|
29
|
+
Open body sends an `agent.fault` that no transition of the Chart takes to the Machine that invoked it, unchanged:
|
|
30
|
+
`{ type: "agent.fault", instanceId, reason }`, with the Instance ID of the Chart's Agent
|
|
31
|
+
([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)). It does not step the Chart. That Machine's `agent.fault`
|
|
32
|
+
route takes the event, as it takes the fault of an Agent of its own, and the exit from the Open body's state stops the
|
|
33
|
+
body. A route that does not exit the state keeps the Chart where it is, and the Chart's Agent is gone: the Chart goes
|
|
34
|
+
on only on its other events, or ends at its wall-clock ceiling, as a route with actions only does to a state of a
|
|
35
|
+
Machine. With no route that takes it, the runtime rule above faults the run. So one route on the root of a Machine is
|
|
36
|
+
the route for its own Agents and for the Agents of its Charts. An Agent of a child Machine sends to that Machine. The
|
|
37
|
+
Chart door does not refuse a Chart state with no route, and `jr2Setup().createMachine` does not refuse the Machine
|
|
38
|
+
that a Chart compiles to. Under an Open body that lends no `fail`, a Chart has no correct state for the route: a final
|
|
39
|
+
state means `done`, and a state that waits holds the run until the wall clock. The Chart wrapper, which is TypeScript
|
|
40
|
+
and checked by the walk, decides.
|
|
41
|
+
- **Routed means that the runtime took a transition.** A route with actions only counts, because it can be a deliberate
|
|
42
|
+
`reenter: true` that invokes the Agent again. jr2 does not judge what a route does.
|
|
43
|
+
- **An unrouted mechanism event ends the run `error`, never `failed`.** The Machine did not choose that end, so it is a
|
|
44
|
+
fault and not a Failure ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)),
|
|
45
|
+
and its Sandbox stays as evidence.
|
|
46
|
+
|
|
47
|
+
## Considered options
|
|
48
|
+
|
|
49
|
+
- **Drop the event, as before.** Rejected: the run waits for ever and looks live, and nothing tells a person why.
|
|
50
|
+
- **Only the walk.** Rejected: a guarded route that is false at runtime still drops the event.
|
|
51
|
+
- **Only the runtime.** Rejected: half of the trials would load and then fault on the first Agent fault, when the walk
|
|
52
|
+
can tell the author before the first run.
|
|
53
|
+
- **Make `agent.fault` the Agent actor's error**, so that xstate faults the parent when no `onError` handles it.
|
|
54
|
+
Rejected: ADR-0016 and the Chart vocabulary route the event by name, and `onError` already carries defects and
|
|
55
|
+
Failures; an Agent's fault would be a third thing on it.
|
|
56
|
+
- **A Chart's unrouted `agent.fault` ends the Open body as an error**, which the Chart wrapper catches with an `onError`
|
|
57
|
+
on its Open body state. Rejected: that error and a defect of the body are the same plain error, so the Chart wrapper
|
|
58
|
+
cannot fail the run for a Chart Agent's fault without failing it for a defect too, and one Machine routes the fault of
|
|
59
|
+
its own Agent on `agent.fault` and the fault of a Chart's Agent on `onError`.
|
|
60
|
+
- **A mark on that error**, such as `isAgentFault(error)`, with a matching Chart guard. Rejected: a new name on the
|
|
61
|
+
Authoring surface, and still two routes for one event.
|
|
62
|
+
- **Require the route in a Chart too**, with a line of the Chart door. Rejected: under an Open body that lends no
|
|
63
|
+
`fail`, the planner can write no correct target, and the trials of the `chart-refusal` claim wrote a `faulted` final
|
|
64
|
+
state, which ends the run `done`. A route the planner must write also adds a rule to the text that a model reads.
|
|
65
|
+
|
|
66
|
+
## Consequences
|
|
67
|
+
|
|
68
|
+
- The text "When no state routes it, xstate drops the event, and the run stays in the Agent's state" in the
|
|
69
|
+
`FaultTelemetry` TSDoc (`actor.ts`) and in `mechanism-events.md` changes to this rule.
|
|
70
|
+
- A Machine in the docs, the templates or the fixtures that does not route a mechanism event gets a route, or the walk
|
|
71
|
+
refuses it.
|
|
72
|
+
- A Chart needs no route, so the Chart fixtures and the Charts of the docs keep none that they do not use, and the Chart
|
|
73
|
+
briefing and the grammar's fragment do not show one. Their Chart wrappers route `agent.fault`, or the walk refuses
|
|
74
|
+
them.
|
|
75
|
+
- An Assistant learns the rule from the refusal line of `jr2 check`, so the line names the state, the event and the
|
|
76
|
+
route to write.
|