@jr2/orchestrator 0.5.1 → 0.7.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/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- 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-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- 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 +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Observation is open; run state and control need
|
|
1
|
+
# Observation is open; run state and control need a Caller's scope
|
|
2
2
|
|
|
3
3
|
[ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md) minted two tokens and scoped them
|
|
4
4
|
for **delivery** — a Sandbox token may deliver to its own agent registrations, an Instance token may deliver to gates.
|
|
@@ -16,20 +16,44 @@ Building `jr2 visualize` (the Machine renderer and its live run highlighting) fo
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
open /workflows, /workflows/:name/machine, /viz/* structure
|
|
19
|
+
/console/config (the login's issuers, ADR-0032) public values
|
|
19
20
|
/workflows/:name/runs, …/runs/:runId/events OBSERVATION (new)
|
|
20
21
|
authenticated /agents/:iid/* the Agent's surface [Sandbox token]
|
|
21
|
-
|
|
22
|
-
POST /workflows/:name/runs starting a run (ADR-0058)
|
|
23
|
-
/runs/:id/gates/:gate/events gates (ADR-0011/0013)
|
|
22
|
+
guarded each route below, opened by one scope of a Caller run state + control [a Caller, ADR-0078]
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
- **Run state and run control are
|
|
27
|
-
Sandbox token — the one credential that sits in a pod an Agent shares a network namespace with — could
|
|
28
|
-
run's context (other features' branches, ticket bodies, review verdicts, Harness endpoints) and
|
|
29
|
-
Neither is on the Agent's surface, no more than a gate is: ADR-0013 scopes that token to
|
|
30
|
-
recorded against its _own_ Sandbox. This closes the same class of hole ADR-0013 closed
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
- **Run state and run control are a Caller's, and each route needs one scope.** `authenticated` accepts _any_ token we
|
|
26
|
+
minted, so a Sandbox token — the one credential that sits in a pod an Agent shares a network namespace with — could
|
|
27
|
+
read **every** run's context (other features' branches, ticket bodies, review verdicts, Harness endpoints) and
|
|
28
|
+
**CANCEL any run**. Neither is on the Agent's surface, no more than a gate is: ADR-0013 scopes that token to
|
|
29
|
+
delivering into registrations recorded against its _own_ Sandbox. This closes the same class of hole ADR-0013 closed
|
|
30
|
+
for gates, on the routes it did not name. The guarded band takes only a Caller
|
|
31
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)): a Sandbox token is never
|
|
32
|
+
one. Each route of the band needs one scope, checked against the Workflow of the run or the door that it addresses:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
start POST /workflows/:name/runs, POST /workflows/:wrapper/charts/:name/runs
|
|
36
|
+
read GET /runs (summaries of the runs of its Workflows, live and ended), GET /runs/:id (the whole status),
|
|
37
|
+
GET /runs/:id/events, GET /runs/:id/conversations[/:iid[/events]] (the kept conversations, ADR-0023)
|
|
38
|
+
answer POST /runs/:id/gates/:gate/events, GET /gates (the open Gates it may answer), and the `gates` of GET /runs/:id
|
|
39
|
+
cancel POST /runs/:id/events (CANCEL)
|
|
40
|
+
steer POST /runs/:id/steering, GET /runs/:id/agents (the slot list { agent, open, steerable }, ADR-0002)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`answer` alone opens a run's `gates` and their routing, and never its `context`: a person who answers a Gate reads the
|
|
44
|
+
Gate's `meta`, which the author wrote for that person. So `GET /runs/:id` gives `read` the status and `answer` the
|
|
45
|
+
`gates`; with `answer` alone the answer is the run's observation (below) and its `gates`. `GET /runs` leaves out the
|
|
46
|
+
runs of the Workflows that the Caller may not read; it refuses no run, and refuses only a Caller with no `read` scope
|
|
47
|
+
at all (ADR-0078). `answer` can name single Gates of a Workflow (ADR-0078). Then the gate POST refuses another Gate of
|
|
48
|
+
that Workflow, and `GET /gates` (its entries are ADR-0011's) and the `gates` of `GET /runs/:id` list only the Gates
|
|
49
|
+
that the Caller may answer. Neither gives the run's `context`. `GET /runs/resolve` answers ids to a Caller with any
|
|
50
|
+
scope, for the Workflows of its scopes. `GET /whoami` answers any Caller. A route that no scope names stays the
|
|
51
|
+
Instance token's: the runtime Chart routes (`PUT` and `DELETE /workflows/:wrapper/charts/:name`), `GET /repos`, a
|
|
52
|
+
Hook's log (`GET /hooks/:name`, which can hold a payload's fields and a handler's words), and the Instance-token door
|
|
53
|
+
on `/agents/:iid/*` (ADR-0013) except the conversation route, which takes only a control token
|
|
54
|
+
([ADR-0023](0023-the-harness-prints-the-conversation.md)). `POST /hooks/:name` is outside the bands: the Hook's own
|
|
55
|
+
verifier authenticates it ([ADR-0011](0011-workflow-defined-events.md)). The band is named for what it checks, not for
|
|
56
|
+
a role — "operator" in this repo is the Kubernetes operator (ADR-0001).
|
|
33
57
|
|
|
34
58
|
- **The observer gets a PROJECTION, not a credential.** `RunObservation` is
|
|
35
59
|
`{ runId, workflow, status, value, children }` — identity, and where in the Machine the run is. What is absent is the
|
|
@@ -48,16 +72,15 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
|
|
|
48
72
|
|
|
49
73
|
## Considered options
|
|
50
74
|
|
|
51
|
-
- **Give the page a token.** It would
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
- **Give the page a token.** It would be a credential — gates, cancel, every run's context — handed to whatever can load
|
|
76
|
+
a URL. Putting it in `jr2.config.ts` (checked in) is worse still, and the token is minted per boot, so the page would
|
|
77
|
+
not have it anyway. Rejected: the visualizer needs to _see_ a run, and seeing is not a subset of the credential that
|
|
78
|
+
steers it. That mismatch is the whole reason a projection exists.
|
|
55
79
|
- **Keep `/runs*` on `authenticated` and filter in the handler.** Rejected: the guard would then depend on every future
|
|
56
80
|
handler remembering to redact. The band is a middleware precisely so the default is refusal.
|
|
57
81
|
- **A fourth "observer" token.** Rejected: it buys nothing today (the observation surface carries no secret) and costs a
|
|
58
82
|
minting, distribution, and revocation story. If observation ever needs to be _private_ — a deployed jr2 Application on
|
|
59
|
-
a shared network —
|
|
60
|
-
callers.
|
|
83
|
+
a shared network — the guarded band and ADR-0078's scopes are already where it goes.
|
|
61
84
|
|
|
62
85
|
## Consequences
|
|
63
86
|
|
|
@@ -66,11 +89,11 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
|
|
|
66
89
|
now **403**. The test pinned the hole.
|
|
67
90
|
- **The API is a cluster Service** (ADR-0019), so "open" means open to whoever can reach it — in-cluster peers, a
|
|
68
91
|
port-forward, or an ingress — deliberately, since what is open is structure plus lit states. Anything that would
|
|
69
|
-
embarrass a user if seen belongs
|
|
70
|
-
- **The CLI is unaffected.** It holds the Instance token from the in-cluster Secret (ADR-0019)
|
|
71
|
-
routes, as before.
|
|
72
|
-
- **
|
|
73
|
-
|
|
92
|
+
embarrass a user if seen belongs in the guarded band, and that is where it is.
|
|
93
|
+
- **The CLI is unaffected.** It holds the Instance token from the in-cluster Secret (ADR-0019), which has every scope,
|
|
94
|
+
and reads the guarded routes, as before.
|
|
95
|
+
- **A Caller without kube credentials is ADR-0078's:** a named token, a JWT, or a browser login. The band split holds
|
|
96
|
+
for every Caller; what the guarded band checks is one scope for each route.
|
|
74
97
|
|
|
75
98
|
- **One scoped exception, and it does not live on this surface at all (ADR-0023).** The Harness prints each Agent's
|
|
76
99
|
conversation — prompts, assistant text, thinking, tool calls, and the answer jr2 gave each Menu pick — to container
|
|
@@ -87,9 +110,12 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
|
|
|
87
110
|
form; then it prints there as the model read it. The answer also carries an Effect's output as the model read it: a
|
|
88
111
|
`command` Effect's output prints there up to the bound, and a Held value an upstream echoed into that output prints
|
|
89
112
|
with it (ADR-0023, ADR-0059). What remains, deliberately, is that on a shared cluster the population who can read an
|
|
90
|
-
Agent's reasoning, and what its picks answered, is whoever can read pod logs.
|
|
91
|
-
|
|
113
|
+
Agent's reasoning, and what its picks answered, is whoever can read pod logs. The Orchestrator's kept copy of the same
|
|
114
|
+
conversation is not part of the exception: it is on this surface, behind `read`, and `jr2 logs` and the Console read
|
|
115
|
+
it there (ADR-0023). Thus `read` opens the same residual of a pick's answer for as long as the run is kept
|
|
116
|
+
(`runs.retain`, ADR-0065). If the pod-log audience becomes unacceptable, the print is what to turn off, and the record
|
|
117
|
+
stays.
|
|
92
118
|
|
|
93
|
-
Evidence: `packages/orchestrator/src/http.ts` (the
|
|
94
|
-
`RunChild`), `test/auth.test.ts` (the boundary), `test/
|
|
95
|
-
any depth of the child tree).
|
|
119
|
+
Evidence: `packages/orchestrator/src/http.ts` (the middlewares), `run-host.ts` (`observe` / `RunObservation` /
|
|
120
|
+
`RunChild`), `test/auth.test.ts` (the boundary), `test/callers.test.ts` (each scope on its routes),
|
|
121
|
+
`test/http-viz.test.ts` (a planted secret never reaches the feed, at any depth of the child tree).
|
|
@@ -15,11 +15,13 @@ else.
|
|
|
15
15
|
|
|
16
16
|
- **The rule.** A name is on the surface when an author writes it in a workflow module or in `jr2.config.ts`. No other
|
|
17
17
|
name is. A name that an author only gets by inference stays off the surface: the event union that `jr2Setup` derives,
|
|
18
|
-
a Gate's input, an Agent's Frame. The `gate` actor is off the surface too. `jr2Setup` registers it, and a state
|
|
19
|
-
invokes it as `src: "gate"`, so no author writes the value.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
a Gate's input, an Agent's Frame. The plain `gate` actor is off the surface too. `jr2Setup` registers it, and a state
|
|
19
|
+
invokes it as `src: "gate"`, so no author writes the value. The `gate()` factory of a Gate slot that hears a Hook is
|
|
20
|
+
on the surface, because an author writes it in `actors` ([ADR-0011](0011-workflow-defined-events.md)); the walk knows
|
|
21
|
+
such a slot by identity, not only by `src === "gate"`. A type is on the surface when an author must write its name,
|
|
22
|
+
for example `Workspaced`, `HostInjectedInput`, `AgentDefinition`, and the types of the parts that `customize()` takes.
|
|
23
|
+
`JR2Config` is on the surface too: the `jr2.config.ts` Reference documents its fields, and an author names it to type
|
|
24
|
+
a config that is built outside `defineConfig()`.
|
|
23
25
|
- **Named exports only.** `src/index.ts` names each export (`export { a } from …`, `export type { T } from …`), in
|
|
24
26
|
groups by topic, and has no `export *`. Thus an Assistant that reads the file sees the full surface, and each change
|
|
25
27
|
to the surface is a line in a diff.
|
|
@@ -73,14 +75,14 @@ another Agent slot (a nested Turn: its picks are its own) or a `gate` (its trans
|
|
|
73
75
|
leaves out everything below that child; a child that runs anything else stays in. A state that invokes `gate` gets its
|
|
74
76
|
accepted set from its own and its ancestors' transitions and those written on its plain children, by the same descent:
|
|
75
77
|
position gives a Gate the writings below it that no other party answers for, as it gives an Agent its modes (the Pool's
|
|
76
|
-
|
|
77
|
-
code. Mechanics: the **vocabulary** derivation is static, in `jr2Setup.createMachine` —
|
|
78
|
-
invoke's `input` to append the derived names, so names still ride serializable input and the
|
|
79
|
-
invoke-time validation are unchanged. The same walk unions the derived sets of every state
|
|
80
|
-
name into that Agent's **Menu** — its tools, fixed for a conversation so the provider's
|
|
81
|
-
of state. The walk reads transition _keys_ and so cannot see guards, which is why what a
|
|
82
|
-
top of it: `agentSurface` asks the invoking machine's guards, and the Turn is told its
|
|
83
|
-
set, less every event that the live snapshot, in its active mode, would not take
|
|
78
|
+
`source` Gate, on its root, takes the writing on `parked`). The consumer names neither, and the Menu's `mcp__jr2__` tool
|
|
79
|
+
names appear nowhere in workflow code. Mechanics: the **vocabulary** derivation is static, in `jr2Setup.createMachine` —
|
|
80
|
+
a config walk wraps each invoke's `input` to append the derived names, so names still ride serializable input and the
|
|
81
|
+
ADR-0007 restore path and invoke-time validation are unchanged. The same walk unions the derived sets of every state
|
|
82
|
+
that invokes an Agent of one name into that Agent's **Menu** — its tools, fixed for a conversation so the provider's
|
|
83
|
+
prompt cache survives a change of state. The walk reads transition _keys_ and so cannot see guards, which is why what a
|
|
84
|
+
Turn may pick is not static on top of it: `agentSurface` asks the invoking machine's guards, and the Turn is told its
|
|
85
|
+
**Allowed picks** — the derived set, less every event that the live snapshot, in its active mode, would not take
|
|
84
86
|
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md) owns the rationale).
|
|
85
87
|
Authoring is untouched by the split: a workflow names no tools in either leg. The same walk feeds `jr2 visualize` ("this
|
|
86
88
|
state's agent can call X, Y"). Dotted names (`agent.*`, `workspace.lost`, `xstate.*`, `after`) are mechanically
|
|
@@ -89,7 +91,9 @@ excluded.
|
|
|
89
91
|
**A transition belongs to the party that its state, or the nearest state above it, invokes.** The party is the Agent
|
|
90
92
|
slot or the `gate` that state invokes, or both when one state invokes both; root transitions, with no such state above,
|
|
91
93
|
belong to both. That is who may send an event whose def leaves `audience` unset
|
|
92
|
-
(`defineEvent({ audience?: "agent" | "external" | "any" })`, no default)
|
|
94
|
+
(`defineEvent({ audience?: "agent" | "external" | "any" })`, no default). One case reads as `external`: an event in a
|
|
95
|
+
Gate slot's `delivers` whose def leaves the audience unset, so a Hook's event is never on the Menu of an Agent below
|
|
96
|
+
that Gate ([ADR-0011](0011-workflow-defined-events.md)):
|
|
93
97
|
|
|
94
98
|
| Transition written on | Unset audience: who may send |
|
|
95
99
|
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
@@ -143,8 +147,14 @@ sends there, and every case with the Agent's state at Rest.
|
|
|
143
147
|
**Load checks on modes and audience.** `createMachine` refuses a registered event that no state handles: it is in no
|
|
144
148
|
derived set, so it is on no Menu and no Gate. It refuses a parallel region below a state that invokes an Agent — the
|
|
145
149
|
Agent's state itself being parallel included, since its regions are below it: the shape is reserved for picks the Agent
|
|
146
|
-
does not wait for, which jr2 does not build, and background work goes above the Agent's state. It
|
|
147
|
-
|
|
150
|
+
does not wait for, which jr2 does not build, and background work goes above the Agent's state. It refuses one Agent slot
|
|
151
|
+
invoked by two states that can be active together — two parallel regions, one state inside the other, or one state
|
|
152
|
+
twice: a slot is one Agent of the Machine, so its two Turns would have one sender, which `sentBy` cannot tell apart, and
|
|
153
|
+
one conversation, which `continue` cannot give to both. Two Agents at once are two slots (one definition can fill both),
|
|
154
|
+
a child Machine in each region, or a Pool ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
|
|
155
|
+
jr2 refuses this shape and does not support it as a special case, because jr2 can remove a refusal later without
|
|
156
|
+
breaking a Machine, but a refusal that it adds later breaks each Machine that loads now. It warns — the Machine stays
|
|
157
|
+
legal; the doc carries the warning, `jr2 up` prints it and the Console shows it with the seats — about a mode that
|
|
148
158
|
nothing enters (not initial, and no state the Machine can enter has a transition into it or a state inside it — so
|
|
149
159
|
neither the mode's own `initial` nor its transition into itself counts: its picks are on the Menu and never allowed; a
|
|
150
160
|
transition into a history state enters that history state's default target, shallow or deep, or its parent's initial
|
|
@@ -21,7 +21,7 @@ restore re-attaches to a Submission whose model is already fixed server-side and
|
|
|
21
21
|
`agentRun` walks `self._parent` to the nearest registered ancestor. Invoked-actor registration (not an entry action) is
|
|
22
22
|
what makes this restore-safe: invoked actors restart on snapshot restore, entry actions do not. Not under a workspace →
|
|
23
23
|
fail loudly, unless input carries an explicit `endpoint` (the dev-stub, workspace-less path). Body-facing workspace
|
|
24
|
-
handles are `{ repos
|
|
24
|
+
handles are `{ repos }`: each is a Repo Slot's folder ([ADR-0012](0012-workspace-wrapper-machine.md)).
|
|
25
25
|
|
|
26
26
|
**[ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)'s token scoping survives and
|
|
27
27
|
strengthens**: the registration records the Sandbox resolved from the enclosing wrapper — the same deterministic
|
|
@@ -108,12 +108,14 @@ modes — `continue` governs only what a _new invocation_ means. The author name
|
|
|
108
108
|
conversation's identity is already on the page. (This corrects the jr-parity exercise's "same iid = jr's resume
|
|
109
109
|
machinery, free" — an inversion of jr's actual semantics.)
|
|
110
110
|
|
|
111
|
-
Two live invocations of one continued iid
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
Two live invocations of one continued iid are refused: one live surface per address. The load walk refuses the shape
|
|
112
|
+
that makes them, one Agent slot in two states that can be active together
|
|
113
|
+
([ADR-0015](0015-authoring-surface-absorbs-the-mechanism.md)), and the registration table refuses a second registration
|
|
114
|
+
of one address as a second guard. A Submission that arrives behind a live one on the Harness queues: prompts for one
|
|
115
|
+
instance enter one per-instance queue in admission order, and a Submission is promoted only when it is the first
|
|
116
|
+
unsettled one for its conversation (ADR-0027 states this as jr2's own semantics). A queued prompt waits behind work that
|
|
117
|
+
may never finish, which is why [ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md) both ends the
|
|
118
|
+
turn and orders the abort ahead of the next `send`.
|
|
117
119
|
|
|
118
120
|
The minting doctrine — addresses are computed by jr2, never by the workflow — covers gate ids too: a gate with no
|
|
119
121
|
authored id derives one from its actor path below the run root, through the same walk `mintIid` uses (`actorPath` in
|
|
@@ -9,33 +9,219 @@ done — was ~90 lines of hand-rolled bookkeeping in the jr-parity exercise (`ac
|
|
|
9
9
|
`spawnChild` top-level or visualize goes blind" footgun). No xstate-ecosystem primitive exists for this (verified: the
|
|
10
10
|
canonical v5 pattern _is_ the hand-rolled loop), so jr2 ships one.
|
|
11
11
|
|
|
12
|
+
The webhook round of 2026-11-03 added the other half: work that starts from the outside. An alert service sends one
|
|
13
|
+
alert, and each alert needs one run of a triage Workflow. A forge labels an issue, and each labeled issue needs one run
|
|
14
|
+
of a ticket Workflow. A Hook ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md))
|
|
15
|
+
never starts a run itself. It reaches two things that exist: a Gate, which answers a run
|
|
16
|
+
([ADR-0011](0011-workflow-defined-events.md)), and a Source, which brings items. So this ADR gives the Source a push
|
|
17
|
+
half, lets a Pool's worker be a run of another Workflow, and lets such a Pool stand.
|
|
18
|
+
|
|
12
19
|
## `pool(worker, spec)` — a machine factory, like `workspace()`
|
|
13
20
|
|
|
14
|
-
`pool(worker, { source, itemId, cap, itemInput, onDrained, failures })` returns a plain machine that owns
|
|
21
|
+
`pool(worker, { source, itemId, cap, itemInput, onDrained, failures, standing })` returns a plain machine that owns
|
|
15
22
|
spawn-under-cap, stable child identity, typed completion collection (`DoneActorEvent<Output, string>` + the
|
|
16
|
-
`xstate.done.actor.*` wildcard — zero casts), `stopChild` bookkeeping, the
|
|
17
|
-
top-level `spawnChild` lives in jr2's pool code, once — the visualize footgun is dissolved by construction, and
|
|
23
|
+
`xstate.done.actor.*` wildcard — zero casts), `stopChild` bookkeeping, the Source's Gate, and the re-query timer. The
|
|
24
|
+
one top-level `spawnChild` lives in jr2's pool code, once — the visualize footgun is dissolved by construction, and
|
|
18
25
|
everything durable is a registered string src (xstate cannot persist inline-src children, which independently forces
|
|
19
26
|
this shape). The pool composes: it returns a plain machine and can itself be nested as a child. The typed idioms
|
|
20
27
|
(wildcard done events, `assertEvent`, outcome-in-context root output) ship as documented patterns regardless, so
|
|
21
28
|
outgrowing the pool means hand-rolling without penalty, not fighting a framework.
|
|
22
29
|
|
|
30
|
+
**The worker is a child Machine or a `dispatch(...)` slot, never a bare Workflow name.** A child Machine runs inside the
|
|
31
|
+
Pool's run, as it always did. A `dispatch` slot ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)) makes
|
|
32
|
+
each item a run of its own: `pool(dispatch("triage", { input: triageDoor, wait: true }), { … })`. The two worker kinds
|
|
33
|
+
differ in five places — a worker's defect, a cancel of the Pool, what `customize` reaches, the life of an item's key,
|
|
34
|
+
and where the Gates are — and each difference is one that ADR-0071 states between a child and a dispatched run. So they
|
|
35
|
+
belong to the worker kind, and the word `dispatch` at the call site shows them. `pool("triage", …)` would hide all five
|
|
36
|
+
behind a string, so jr2 does not offer it. Inside, one claim loop serves both kinds with two internal policies.
|
|
37
|
+
|
|
23
38
|
## The source port is generalized; Work Source is one adapter
|
|
24
39
|
|
|
25
|
-
The pool draws from a **Source** port
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
The pool draws from a **Source** port in one of three forms:
|
|
41
|
+
|
|
42
|
+
- **Pull**: `next({ active, settled, input }) → item | null`, plus an optional `wake` event def (for `jr2 send`) and
|
|
43
|
+
`pollEvery` (the set changes under the Pool). `input` is the Pool's input and `settled` holds the ids of the finished
|
|
44
|
+
and failed items, so a Source can fan out over a plan with no state of its own.
|
|
45
|
+
- **Read**: `read`, an `http` Effect marked `idempotent: true` or a promise (`pool()` refuses another Effect,
|
|
46
|
+
[ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md), as a Gate's look does),
|
|
47
|
+
and `items`, which turns its answer into the items in order (by default the answer itself, when it is a list). The
|
|
48
|
+
read's input is the Pool's input. It is the re-queried set, read through an Effect, so the read can spend a secret
|
|
49
|
+
that the Instance Effector holds ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), which
|
|
50
|
+
a `next` in the Orchestrator cannot. A promise read holds no secret, and the default tier reads with one. A failed
|
|
51
|
+
read gives no items.
|
|
52
|
+
- **Push**: `item`, a zod schema, and `hooks` whose handlers carry each item. Nothing can be read again: each item comes
|
|
53
|
+
in a delivery, and the Pool holds it (below).
|
|
54
|
+
|
|
55
|
+
A pull or a read Source can hear a Hook too. Its `hooks` handlers give `reread()`: read the set again now. **When a
|
|
56
|
+
Source can read, the read is the truth**: a delivery only says "read again", and an item that lost its label while it
|
|
57
|
+
waited is not in the next read. A handler of a Source has the shape of a Gate's (by Hook kind, then event key) and reads
|
|
58
|
+
`{ payload, caller, delivery, input }`. It gives `reread()`, `carry(item)` or `skip(reason)`, and never a bare
|
|
59
|
+
`undefined`: a delivery that brings nothing has a reason that a person can read in the Hook's answer, the hook log and
|
|
60
|
+
`jr2 hooks`. A throw is a skip with the error as its reason. `skip` is for a Source's handlers only; a Gate's handler
|
|
61
|
+
gives an event or `undefined`. A verdict that the Source's form cannot take (`carry` from a pull or a read Source,
|
|
62
|
+
`reread` from a push Source) and an item that the `item` schema refuses are each a skip, with the reason. **An item
|
|
63
|
+
whose `itemId` throws or gives no id gets no worker, in each form of Source.** In a delivery to a push Source, it is a
|
|
64
|
+
skip with the reason. In a read, the read skips that item, with a `pool` mark `skipped` on the feed that says which item
|
|
65
|
+
and why, and it claims the rest. A read at each `pollEvery` finds the item again, so jr2 marks each such item one time
|
|
66
|
+
in a run's life. Else one item with no id would make each read give no items, with no mark, and no item of that Source
|
|
67
|
+
would start. A `next` gives one item at a time, so its item with no id ends the run `error`, and the streak below pauses
|
|
68
|
+
the Pool when that does not stop. Mechanically the push half is the Pool's `source` Gate, which heard only `wake`
|
|
69
|
+
before, so the Hook's fan-in reaches it as it reaches any open Gate (ADR-0011). A Source may also give
|
|
70
|
+
`reply(verdict, { caller })`: pure, from its own verdict to plain text or `undefined`, which the person who acted on a
|
|
71
|
+
chat service reads, and nobody else; a `skipped` text shows only when no other target gave one (ADR-0011). The verdict
|
|
72
|
+
is the line of `sources`, and for `held`, `existing` and `hold-full` also the parsed `item`. A paused Pool has no run,
|
|
73
|
+
so no `reply` runs for `paused`. The reply after the work is a plain `http` Effect of the Machine.
|
|
74
|
+
|
|
75
|
+
The claim loop has three paths: `next`, the read, and the held items. A `reread`, a `wake` or a settled worker that
|
|
76
|
+
comes during a read (or during a call of `next`) sets a latch, and the loop reads once more after that read, so no
|
|
77
|
+
change is lost. Reads coalesce: one read claims every free slot, and the loop does not read after each claim and each
|
|
78
|
+
settled item one by one. The Pool drops a known id before it claims, so a read that finds an active or settled item
|
|
79
|
+
starts nothing. For a Pool of runs, an item that started a run has one record of "settled": the store's key check
|
|
80
|
+
(below). An item that ended with no run (`start-refused`, `dispatch-error`, `unreachable`) has no row in the store, so
|
|
81
|
+
the Pool counts its key as known for the rest of its run's life: else the read that its own settle starts would claim it
|
|
82
|
+
again at once, with no pause and no end, and one item that the callee's door refuses would hold the Orchestrator's event
|
|
83
|
+
loop. The next run of the Pool (a hand-over or a resume) tries such an item again, so a publish that fixes the door or
|
|
84
|
+
the `itemInput` takes it. The Pool keeps no settled keys past its own run's life: it keeps the run and the status of
|
|
85
|
+
each item that ended in that life, which the bound of a standing run limits, and which a `next` reads as `settled`. A
|
|
86
|
+
`next` that gives an item whose run ended before gets that run (`existing`), whose wait ends at once, so the item
|
|
87
|
+
settles and is not asked for again.
|
|
29
88
|
|
|
30
|
-
|
|
89
|
+
tk's claim actor is one adapter; GLOSSARY.md's **Work Source** reframes as _the Source adapter for tickets_, and the
|
|
90
|
+
Ready-set doctrine (re-query, never materialize) survives as that adapter's semantics, not the primitive's. A Work
|
|
91
|
+
Source — `tk`, GitHub issues, Jira, a task queue — is a behavior port, not a canonical data schema: verbs like
|
|
31
92
|
`claimNext`, `updateStatus` and `comment`, each gated by a capability the adapter advertises. It owns the dependency
|
|
32
93
|
graph and hands out the Ready-set, the currently-unblocked items in dependency order, so the Machine never encodes a
|
|
33
94
|
dependency edge. The Ready-set mutates during execution — items can be reopened or created mid-run — so it is not a
|
|
34
|
-
static FIFO queue.
|
|
95
|
+
static FIFO queue. An adapter can give both halves of a Source: its read and its Hook's handlers, so the mapping from
|
|
96
|
+
the service's data to an item is written once.
|
|
97
|
+
|
|
98
|
+
## A pushed item is held until the Pool claims it
|
|
99
|
+
|
|
100
|
+
- **The Pool holds each carried item in its run's blob**, in the order of arrival, until it claims it. An item that
|
|
101
|
+
leaves for a worker is no longer held: the worker, by its key, is the record. The Hook's answer leaves after the save
|
|
102
|
+
that records the item, so a `held` answer is durable.
|
|
103
|
+
- **The key check comes before the hold.** An item whose key a held item, an active worker, or a run of the worker's
|
|
104
|
+
Workflow holds is `existing`, and the answer names the run when one holds it.
|
|
105
|
+
- **`hold` bounds the held items**, 1000 by default. Above it, the Hook's answer is 429 (for a chat command, 200 with a
|
|
106
|
+
text that says why, ADR-0011) and the hook log keeps the delivery. The 1000 is not measured.
|
|
107
|
+
- **What a push loses**: a delivery while the Orchestrator is down, while no Assembly is current (503), while the Pool
|
|
108
|
+
is paused (`paused`), and above `hold`. A read Source loses nothing, because the next read finds the item.
|
|
109
|
+
- **A 429 does not claim the delivery's id.** The hook log keeps it as `hold-full`, and a repeat is found only by a
|
|
110
|
+
delivery that the door claimed (ADR-0011), so the service's send again is heard: a Gate that took it passes it over by
|
|
111
|
+
the run's answers, and a Pool that holds its item answers `existing`.
|
|
112
|
+
- **A Source's verdict on a delivery** is one of `held`, `existing`, `hold-full`, `reread`, `skipped` and `paused`, and
|
|
113
|
+
it goes in the delivery's outcome as a line of `sources` (ADR-0011).
|
|
114
|
+
|
|
115
|
+
## A Pool of runs, and a Pool that stands
|
|
116
|
+
|
|
117
|
+
A Pool whose worker is a `dispatch` slot keys each item's run by `itemId`, scoped to the callee Workflow
|
|
118
|
+
([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)): every Pool, every hand-over and every `jr2 run` that
|
|
119
|
+
gives the same key for that Workflow gets the same run while its row lives, live and for `runs.retain` after it ends
|
|
120
|
+
([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)). `cap` counts the live runs that
|
|
121
|
+
this Pool dispatched, so its slot must say `wait: true`: with `started` alone, the Pool cannot know when a run ends. A
|
|
122
|
+
run that ends in any way frees its slot. A run that ends `failed`, `error`, `cancelled`, `lost` or `drifted`, or a start
|
|
123
|
+
that the door refuses, is a failure of its item, and never the Pool's `error`: the run is not the Pool's child. A cancel
|
|
124
|
+
of the Pool never cancels its runs (ADR-0071), and `customize(pool, { agents })` reaches nothing in them
|
|
125
|
+
([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)). The Pool spawns its slot with the item's id as
|
|
126
|
+
the spawn id, and the Effect sends that id as the key, not its ledger row id. The run's `dispatchedBy` gains `key`, and,
|
|
127
|
+
for a carried item, `hook: { name, delivery, onBehalfOf? }`, so `jr2 status` prints which Pool, key and delivery started
|
|
128
|
+
it. Neither is on the open band (ADR-0014): a key is the workflow's data. A run that a read claimed names no delivery:
|
|
129
|
+
the Pool's feed says why an item did or did not start, with a `pool` mark for each start (`started`), each end
|
|
130
|
+
(`settled`, with the run's status, `start-refused` or `dispatch-error`), each held item that a hand-over drops
|
|
131
|
+
(`carry-refused`) and each item of a read that has no id (`skipped`). A `wait` that the slot's timeout ends while the
|
|
132
|
+
run is still live (`status: "live"`) is sent again under its key, so the wait follows the run and `cap` still counts it.
|
|
133
|
+
|
|
134
|
+
**`standing: true` makes a Pool of runs stand.** It is two things:
|
|
135
|
+
|
|
136
|
+
- **Parking.** A standing Pool never ends by itself. It has no `drained` and no `deadlocked` end, no output and no
|
|
137
|
+
`failures` mode. When it has no work it waits for a delivery, a `reread` or its `pollEvery`. An item that ends is a
|
|
138
|
+
`settled` mark on the feed, with the run's status.
|
|
139
|
+
- **The standing run.** jr2 keeps one live run of the Workflow under the current Assembly
|
|
140
|
+
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)). Nobody starts it: jr2
|
|
141
|
+
starts it at boot and at the publish that adds it, and `jr2 run` of it is refused, except to resume it (below). A
|
|
142
|
+
dispatch to a standing Workflow is refused 409. jr2 **hands it over** to a new run at every publish, at drift, after
|
|
143
|
+
`error` (with back-off), and when its blob passes its bound. At every publish, because the Assembly is one hash and a
|
|
144
|
+
handler has no stamp of its own. The bound is 500 items taken or one day: each item taken is a dispatch, which keeps a
|
|
145
|
+
row in the Effect ledger for the run's life (ADR-0067), about 217 bytes, so 500 is about 110 KB a save. The bound
|
|
146
|
+
counts the items that the run took, not the keys that a hand-over carried to it: each carried key is sent again, so a
|
|
147
|
+
count of the ledger's rows would hand a run with a `cap` at the bound over again at once. **A run at the bound in
|
|
148
|
+
which no item that it took itself started a run is handed over as a run that ended `error` is**: after the back-off,
|
|
149
|
+
and it counts in the streak below. An item that ends with no run (`start-refused`, `dispatch-error`, `unreachable`,
|
|
150
|
+
`unknown`) is known only for its run's life (above), so a backlog of as many such items as the bound fills the bound
|
|
151
|
+
again in each new run. A hand-over at once would then loop with no pause and no end, and would hold the event loop.
|
|
152
|
+
jr2 ends that run `done`, with no `handedOverTo`, and starts the next run after the back-off. A run in which one item
|
|
153
|
+
that it took started a run (or found its run under its key) is handed over at once, and the count starts again. A
|
|
154
|
+
carried key does not count: the hand-over sends it again and it finds a run that an earlier run started, so a callee
|
|
155
|
+
run that lives for hours would hide the loop for as long as it lives. While each item that the run took is still in
|
|
156
|
+
flight, jr2 does not yet know, so it waits for the save of the first answer. The back-off before the hand-over after
|
|
157
|
+
the `n`-th counted run in a row is 1 s, 4 s, then 16 s. The numbers are not measured. The hand-over carries the held
|
|
158
|
+
items, parsed by the new Assembly's `item` schema (an item that does not parse is dropped, with a `carry-refused`
|
|
159
|
+
mark), and the active keys: the new run sends each one again with `wait: true`, the door answers `existing`, and the
|
|
160
|
+
wait follows that run, so `cap` still counts it. A held item is mapped by the new `itemInput`. A hand-over ends the
|
|
161
|
+
old run `done`, and its record carries `handedOverTo: <new run id>`: a standing Pool has no output, so its stint is
|
|
162
|
+
done. The start of the new run and the end of the old one are one synchronous step. A delivery that a Source hears
|
|
163
|
+
across that step (its key lookup awaits the store) goes on to the new run's Source, so the door never claims a
|
|
164
|
+
delivery that no run holds. jr2 keeps the **head** of each standing Workflow in a table of the store: its newest run,
|
|
165
|
+
when that run started, how many counted runs in a row ended, and why it is paused. jr2 writes the head in the same
|
|
166
|
+
step as the start. The sweep of rows older than `runs.retain`
|
|
167
|
+
([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)) never deletes the run that a head
|
|
168
|
+
names, so a paused Pool keeps its held items for as long as it is paused. A Workflow that a publish removes, or that
|
|
169
|
+
no longer stands, gets no hand-over: its live run holds its Assembly and runs on, as any run does (ADR-0069), until a
|
|
170
|
+
person cancels it. A standing run has no Caller: its record's `startedBy` is `{ kind: "jr2", why }`, with `why` one of
|
|
171
|
+
`boot`, `publish`, `hand-over` and `resume`
|
|
172
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)).
|
|
173
|
+
- **A cancel is a pause.** `jr2 send <run> --event CANCEL` of a standing run ends that run `cancelled`
|
|
174
|
+
([ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)) and pauses its Workflow: jr2 starts it again at no boot and no
|
|
175
|
+
publish until `jr2 run <workflow>` resumes it. `jr2 up` and `jr2 status` print the Pool as paused, with the number of
|
|
176
|
+
items it holds. They read it from `GET /assembly` and from the answer of the publish, whose `standing` list gives each
|
|
177
|
+
standing Workflow's state (`live`, `paused` or `waiting`), its run and its count of held items. It holds no key and no
|
|
178
|
+
item, so it is on the open band with the held Assemblies ([ADR-0014](0014-observation-is-open-run-state-is-not.md)). A
|
|
179
|
+
paused Pool has no live run: its held items and active keys stay in the blob of the cancelled run. A resume is a
|
|
180
|
+
hand-over from that run, so the new run takes them and claims under `cap`, and a read Source reads again. The new run
|
|
181
|
+
records the Caller of the `jr2 run` that resumed it as `resumedBy`. On the wire the resume is
|
|
182
|
+
`POST /workflows/<workflow>/runs`, which answers `201 { runId, instanceId, resumed: true }`, and 409 while the
|
|
183
|
+
Workflow is live or waits out a back-off. `jr2 run <workflow> --drop-held` (`?drop-held=true`) resumes with no held
|
|
184
|
+
item, so a person can remove a poison item or a queue that they paused on purpose; a read Source then finds again what
|
|
185
|
+
is still true. A delivery to a paused Pool gets `paused`, and nothing is held, so a push Source loses it and a read
|
|
186
|
+
Source finds it at the resume. A delivery to a standing Pool in its back-off (no live run, and not paused) makes the
|
|
187
|
+
door answer 503 (ADR-0011), so the service sends it again. **One rule counts a run in the streak**, for each end that
|
|
188
|
+
hands over after the back-off: `error`, `failed` (a `fail()` of the Machine,
|
|
189
|
+
[ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md), which jr2 treats as
|
|
190
|
+
`error`) and the bound with no run started. A run in which an item that it took itself, not a carried key, started a
|
|
191
|
+
run starts the count again, and is handed over after 1 s. Any other run counts. So a run that took only items that the
|
|
192
|
+
callee's door refused, and then ended `error`, counts. After 3 hand-overs from counted runs in a row, jr2 pauses the
|
|
193
|
+
Pool when the next counted run ends, so a poison item, or a backlog that the callee's door refuses, cannot loop
|
|
194
|
+
without end. The pause says why: `errors` when the last run ended `error` or `failed`, and `bound` when it took its
|
|
195
|
+
bound; `jr2 status` and the Orchestrator's log line name that end. The 3 is not measured. The Pool's context keeps the
|
|
196
|
+
keys that the hand-over carried, so jr2 tells them from the items that the run took.
|
|
197
|
+
|
|
198
|
+
**What the load walk refuses**, with its fix in the message:
|
|
199
|
+
|
|
200
|
+
- `standing: true` on a Pool of child Machines: a child cannot be handed over.
|
|
201
|
+
- `standing: true` on a Pool that is not the top Machine of its Workflow: jr2 keeps one run for each standing Workflow,
|
|
202
|
+
so the Workflow is the Pool. The fix is to move the Pool to its own file in `workflows/`.
|
|
203
|
+
- A Pool of runs that does not stand. A batch of runs (a Pool of runs that a Caller starts and that ends) is not offered
|
|
204
|
+
now: its `itemId` is a key that lives for `runs.retain` past the run, so a second batch in that time does no work. To
|
|
205
|
+
add it later does not break anything.
|
|
206
|
+
- A Pool of runs whose `dispatch` slot has no `wait: true`, or has a `target`: a foreign worker is not designed.
|
|
207
|
+
- A Pool of runs with no `cap`. The author must choose it; a Pool of child Machines keeps its default of 1.
|
|
208
|
+
- A push Source on a Pool that ends when it drains, which would end `drained` at once. The types refuse it too.
|
|
209
|
+
- A worker that is a bare Workflow name.
|
|
210
|
+
|
|
211
|
+
**What `jr2 up` prints and warns.** For each standing Pool, it prints the chain from each Hook that reaches it to the
|
|
212
|
+
callee: `sentry → alerts → triage`. It warns when two standing Pools dispatch to one Workflow: `cap` is per Pool, so one
|
|
213
|
+
issue with two labels can take a slot in each, and the caps add.
|
|
214
|
+
|
|
215
|
+
**The callee must end the level.** A read Source re-reads a level, such as "the open issues with the label `jr2`". When
|
|
216
|
+
the callee's run ends and the label stays, the key keeps the item from a second run while the run's row lives.
|
|
217
|
+
`runs.retain` after the run ended (30 days by default), the row is swept, and the next read starts the item again. So
|
|
218
|
+
the callee (or the person) removes the label. A drifted callee holds its key for `runs.retain`, and nothing starts that
|
|
219
|
+
item again without a person.
|
|
35
220
|
|
|
36
221
|
## Terminal triage is three-valued, surfaced as status
|
|
37
222
|
|
|
38
|
-
Forced by parking-is-retention (ADR-0012) plus workflows whose children park on gates (escalation, human review)
|
|
223
|
+
Forced by parking-is-retention (ADR-0012) plus workflows whose children park on gates (escalation, human review). It
|
|
224
|
+
holds for a Pool that does not stand:
|
|
39
225
|
|
|
40
226
|
- **drained** — source empty and all children settled → final, or the run ends `failed` when items failed (below);
|
|
41
227
|
- **deadlocked** — open-but-never-ready items with nothing progressing → surfaced distinctly (jr exit 2), not an
|
|
@@ -67,3 +253,33 @@ is a correct answer. The default is `"fail"` because `done` means that the work
|
|
|
67
253
|
not exit 0 while every item failed. The deadlock is the usual end of a dependency backlog with a failure: a Source over
|
|
68
254
|
a tracker keeps the failed item open, so its dependents never become ready. The `status` in the detail tells the two
|
|
69
255
|
ends apart, and the two details have one shape.
|
|
256
|
+
|
|
257
|
+
## Tests
|
|
258
|
+
|
|
259
|
+
The default e2e tier (`features/standing-pool.feature`) and `pool-sources.test.ts` prove the standing Pool of runs: a
|
|
260
|
+
push Source, the held items and `hold`, `cap`, `reread`, the latch and merged reads, a restart and a hand-over with no
|
|
261
|
+
item lost or started twice, a hand-over across a changed callee door, and the key per Workflow. They read with a
|
|
262
|
+
promise, because the default tier has no Instance Effector. The `@kind` tier proves a read through an `http` Effect.
|
|
263
|
+
|
|
264
|
+
## Considered options
|
|
265
|
+
|
|
266
|
+
- **An Intake**: a kit-shipped `intake({ routes })`, each `route({ start, input, hooks, look, cap })`, that jr2 keeps as
|
|
267
|
+
a **Standing Workflow**. Rejected: it needs three concepts (Intake, route, Start) and two GLOSSARY terms, and reuses
|
|
268
|
+
nothing that an author knows about a Pool, though it is a Pool inside. It writes the mapping from a delivery to a
|
|
269
|
+
start twice, once in the handler and once in the look, and its look starts an issue that lost its label while it
|
|
270
|
+
waited. One intake run serves every route, so its fault stops every route; a standing Pool for each Source keeps a
|
|
271
|
+
fault in one Source.
|
|
272
|
+
- **A direct door**: a Hook that starts a run itself (`door()`, or a Listener's `startOn(machine, kind, …)`). Rejected:
|
|
273
|
+
it is the smallest form for one alert, but it cannot grow. A cap, a read of a level, a queue and an answer after a
|
|
274
|
+
start are each state over time, which a door does not have, and each needs a standing Machine. So an Instance gets two
|
|
275
|
+
ways to start a run. It also makes a Hook more than a door that checks and decides nothing (ADR-0078).
|
|
276
|
+
- **A router that the author writes**: a hand-written standing Machine with `dispatch({ key })`, regions and a table of
|
|
277
|
+
runs in flight. Rejected: about 90 lines of mechanism for 25 lines of intent. When a judge wrote one alert's router
|
|
278
|
+
from it, the result had no hand-over and no routes for a child: its table went stale at each publish, and the first
|
|
279
|
+
refused start faulted it. A run holds its Assembly, so a router keeps its old handlers after a publish; a router that
|
|
280
|
+
ends `error` has no open Gate, so each later delivery reaches nothing; and a Kit bump drifts it and loses its backlog.
|
|
281
|
+
- **Two factories over one Source port**: `pool()` for children and batches, and a second factory for standing Pools of
|
|
282
|
+
runs. Rejected: the split removes three rows from the table of differences, and costs a GLOSSARY term for the second
|
|
283
|
+
factory, which is the Intake again.
|
|
284
|
+
- **A batch Pool of runs now.** Rejected for now, as above: no scenario needs it, and its key makes a second batch
|
|
285
|
+
within `runs.retain` do nothing. It adds later without a break.
|
package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md
CHANGED
|
@@ -7,9 +7,9 @@ hands: the [ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-
|
|
|
7
7
|
Submission — forget it and the Agent is silently mute, the same footgun class ADR-0016 removed from workflows), the
|
|
8
8
|
ADR-0004/0012 sandbox geography (working tools execute in the Harness container against `/work`), the dependency pin
|
|
9
9
|
that must match the Orchestrator's wire, and the image contracts the operator imposes (numeric uid, `:8080` = Ready, git
|
|
10
|
-
present). The only content a user genuinely owns is the
|
|
11
|
-
GLOSSARY.md already promised the split — an Agent is a definition jr2 maps into the Harness, and the Instance
|
|
12
|
-
holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of the things it can).
|
|
10
|
+
present). The only content a user genuinely owns is the Agent's definition: model + instructions, and the MCP servers it
|
|
11
|
+
uses. GLOSSARY.md already promised the split — an Agent is a definition jr2 maps into the Harness, and the Instance
|
|
12
|
+
folder holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of the things it can).
|
|
13
13
|
|
|
14
14
|
## Decision
|
|
15
15
|
|
|
@@ -53,6 +53,12 @@ holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of
|
|
|
53
53
|
Stand-in. `harness.provider.apiKey` is one implicitly: the held secret `JR2_PROVIDER_API_KEY`, bound to `baseUrl`'s
|
|
54
54
|
host, so it leaves the `jr2-harness-env` Secret and `baseUrl` must be `https://` when a key is set. A known model key
|
|
55
55
|
in `harness.env`, or in an `envFrom` Secret, is refused.
|
|
56
|
+
- **MCP is the one extension contract for tools.** Beyond its Working tools and its Menu, an Agent gets tools only from
|
|
57
|
+
the MCP servers that its definition names (`mcp`), and jr2 has no tool API of its own
|
|
58
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
|
|
59
|
+
definition names a server by the name that the `mcp` block of `jr2.config.ts` gives it, never by its url or its
|
|
60
|
+
secret: as `harness` declares reach for models, the `mcp` block declares reach for tools, and the definition makes the
|
|
61
|
+
choice.
|
|
56
62
|
- **A catalog provider can be moved to a gateway, and stays a catalog provider.** `harness.catalog.<id>.baseUrl`
|
|
57
63
|
replaces the base URL of every model of one pi catalog provider — a LiteLLM gateway in front of Bedrock, for
|
|
58
64
|
`anthropic/…`. The catalog keeps its limits, cost and thinking levels; only the endpoint moves. It is reach, not a
|
|
@@ -99,11 +105,13 @@ Definitions keep converge-time safety; overrides pay the later check for the fle
|
|
|
99
105
|
|
|
100
106
|
## Consequences
|
|
101
107
|
|
|
102
|
-
- The definition contract is data-only: what an Agent may do to the Workspace is part of it (`access`, ADR-0028)
|
|
103
|
-
|
|
104
|
-
hatch to a foreign harness project
|
|
108
|
+
- The definition contract is data-only: what an Agent may do to the Workspace is part of it (`access`, ADR-0028), and so
|
|
109
|
+
are the MCP servers it uses (ADR-0079); an author's own tool is an MCP server. Skills and subagents have no seat yet.
|
|
110
|
+
When someone needs them, the contract grows deliberately — there is no eject hatch to a foreign harness project
|
|
111
|
+
(ADR-0027 deleted it).
|
|
105
112
|
- Because definitions are plain data, stock ones compose with no API: a Machine carries `agent(coder)`, and a consumer
|
|
106
|
-
retunes it with `customize(machine, { agents: { coder: { model: "…" } } })` (ADR-0049, ADR-0009)
|
|
113
|
+
retunes it with `customize(machine, { agents: { coder: { model: "…" } } })` (ADR-0049, ADR-0009), or rebinds its
|
|
114
|
+
servers with `{ mcp: [...] }` in the same place.
|
|
107
115
|
- An invalid definition fails at admission, loudly, naming the slot: a model no registry resolves, a malformed field. A
|
|
108
116
|
Machine with no Agent slots is valid — a `workspace()` body parking a Sandbox (ADR-0012) invokes none. Readiness is
|
|
109
117
|
binding `:8080`, with no boot build ahead of it (ADR-0027).
|