@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
|
@@ -119,7 +119,6 @@ import { body, type TaskInput } from "@jr2/machines";
|
|
|
119
119
|
|
|
120
120
|
const fixed = workspace(body, {
|
|
121
121
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
122
|
-
spec: ({ input }: { input: TaskInput }) => ({ branch: input.branch ?? "jr2/fix" }),
|
|
123
122
|
});
|
|
124
123
|
|
|
125
124
|
export const machine = customize(fixed, {
|
package/docs/why.md
CHANGED
|
@@ -47,7 +47,8 @@ cannot be the right one for each of them.
|
|
|
47
47
|
|
|
48
48
|
The You band of the diagram holds the clients of jr2. The [Orchestrator](reference/glossary.md#orchestrator) is an HTTP
|
|
49
49
|
API. The [jr2 CLI](reference/glossary.md#jr2-cli) and the [Console](reference/glossary.md#console) are its clients, and
|
|
50
|
-
your own app, such as a
|
|
50
|
+
your own app, such as a CI job or a bridge to a chat service, can be one too. An outside service, such as a forge, sends
|
|
51
|
+
its deliveries to a [Hook](reference/glossary.md#hook). For example, a client can start a run with
|
|
51
52
|
[`POST /workflows/:name/runs`](reference/http-api.md#post-workflows-name-runs). In the diagram, a
|
|
52
53
|
[Gate](reference/glossary.md#gate) in the state `approval` asks you, and you answer from any of these clients, with
|
|
53
54
|
[`POST /runs/:runId/gates/:gate/events`](reference/http-api.md#post-runs-runid-gates-gate-events).
|
|
@@ -115,8 +116,8 @@ It talks to the model, and it runs the tools of the Agent. The Agent can run eac
|
|
|
115
116
|
Agent that needs only its Menu has no Sandbox. Its Turns run on the
|
|
116
117
|
[Instance Harness](reference/glossary.md#instance-harness).
|
|
117
118
|
|
|
118
|
-
The Harness
|
|
119
|
-
one of the two containers beside
|
|
119
|
+
The Harness runs each command of an Agent confined with Landlock, so the command cannot read a credential, and it cannot
|
|
120
|
+
send a pick for a different Agent. jr2 keeps each credential in one of the two containers beside the Harness:
|
|
120
121
|
|
|
121
122
|
- The [Custodian](reference/glossary.md#custodian) holds each [Held secret](reference/glossary.md#held-secret): a key
|
|
122
123
|
that the Agents can use, but cannot read. The Harness holds only a [Stand-in](reference/glossary.md#stand-in) for it.
|
|
@@ -192,8 +193,9 @@ These steps follow one run of the example Machine in the diagram. You write a Ma
|
|
|
192
193
|
1. **You start a run.** You run `jr2 run feature`, with the ticket as the input of the run. The Machine starts in its
|
|
193
194
|
first state, `code`.
|
|
194
195
|
2. **The coder works in a Sandbox.** The state `code` is in a Workspace, so jr2 makes a Sandbox for it. The Sandbox
|
|
195
|
-
holds a [Worktree](reference/glossary.md#worktree)
|
|
196
|
-
runs the tests and commits. Then it picks `finish`, and the Machine moves to
|
|
196
|
+
holds a clone of your Repo, and the Machine cuts a [Worktree](reference/glossary.md#worktree) on the branch of the
|
|
197
|
+
run. The coder edits the files, runs the tests and commits. Then it picks `finish`, and the Machine moves to
|
|
198
|
+
`review`.
|
|
197
199
|
3. **The reviewer gets a Turn.** The state `review` gives the reviewer a prompt and its Menu: `approved` and `changes`.
|
|
198
200
|
The reviewer reads the change in the same Sandbox.
|
|
199
201
|
4. **The reviewer picks.** If it picks `approved`, the Machine moves to `approval`. If it picks `changes`, the Machine
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jr2/orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The jr2 Orchestrator: xstate runtime, HTTP API and Console, and the kit pieces a Workflow imports.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
"exports": {
|
|
32
32
|
".": "./src/index.ts",
|
|
33
33
|
"./internal": "./src/internal.ts",
|
|
34
|
+
"./hooks": "./src/hook-kinds/index.ts",
|
|
35
|
+
"./hooks/github": "./src/hook-kinds/github.ts",
|
|
36
|
+
"./hooks/gitlab": "./src/hook-kinds/gitlab.ts",
|
|
37
|
+
"./hooks/slack": "./src/hook-kinds/slack.ts",
|
|
38
|
+
"./hooks/discord": "./src/hook-kinds/discord.ts",
|
|
39
|
+
"./hooks/webhook": "./src/hook-kinds/webhook.ts",
|
|
34
40
|
"./tsconfig.instance.json": "./tsconfig.instance.json"
|
|
35
41
|
},
|
|
36
42
|
"types": "./src/index.ts",
|
|
@@ -38,15 +44,16 @@
|
|
|
38
44
|
"@hono/node-server": "^2.0.6",
|
|
39
45
|
"elkjs": "^0.11.1",
|
|
40
46
|
"hono": "^4.12.27",
|
|
47
|
+
"jose": "^6.2.3",
|
|
41
48
|
"preact": "^10.29.8",
|
|
42
49
|
"ts-blank-space": "^0.9.0",
|
|
43
50
|
"xstate": "^5.18.0",
|
|
44
51
|
"zod": "^4.4.3",
|
|
45
|
-
"@jr2/agent-protocol": "0.
|
|
52
|
+
"@jr2/agent-protocol": "0.7.0"
|
|
46
53
|
},
|
|
47
54
|
"devDependencies": {
|
|
48
55
|
"@types/node": "^26.0.1",
|
|
49
|
-
"@jr2/harness": "0.
|
|
56
|
+
"@jr2/harness": "0.7.0"
|
|
50
57
|
},
|
|
51
58
|
"files": [
|
|
52
59
|
"bin",
|
package/src/actor.ts
CHANGED
|
@@ -50,14 +50,21 @@ import {
|
|
|
50
50
|
type ThinkingLevel,
|
|
51
51
|
type WorkspaceAccess,
|
|
52
52
|
} from "./agent.ts";
|
|
53
|
+
import type { AnyFramePath } from "./worktree.ts";
|
|
53
54
|
import { ambientHandlesFor, type AmbientHandles } from "./ambient.ts";
|
|
54
55
|
import type { HarnessRestarts, MemoryKill } from "./workspace.ts";
|
|
55
56
|
import { INSTANCE_HARNESS_SERVICE } from "./names.ts";
|
|
56
57
|
import { SENDER_KEY } from "@jr2/agent-protocol";
|
|
57
|
-
import { agentAddress, continuedIid, allowedPicks, resolveAccepts, runBindingOf } from "./registration.ts";
|
|
58
|
-
import type { NoticeScope } from "./notices.ts";
|
|
58
|
+
import { agentAddress, agentSlot, continuedIid, allowedPicks, resolveAccepts, runBindingOf } from "./registration.ts";
|
|
59
|
+
import type { LedgerBody, NoticeScope } from "./notices.ts";
|
|
59
60
|
import { restOf } from "./rest.ts";
|
|
60
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
SUBMISSION_HARNESS_RESTARTED,
|
|
63
|
+
SUBMISSION_RUNAWAY,
|
|
64
|
+
type Notice,
|
|
65
|
+
type PodMemoryKill,
|
|
66
|
+
type SteeringMessage,
|
|
67
|
+
} from "./wire.ts";
|
|
61
68
|
import type { AbortCause } from "./wire.ts";
|
|
62
69
|
|
|
63
70
|
/**
|
|
@@ -122,6 +129,14 @@ export type AgentAdmission = {
|
|
|
122
129
|
*/
|
|
123
130
|
agent?: string;
|
|
124
131
|
endpoint?: string;
|
|
132
|
+
/**
|
|
133
|
+
* The pod and the slot of the conversation (ADR-0076) — stamped by the ACTOR at ledger time, like
|
|
134
|
+
* `agent`. The kept conversation's route takes entries for this conversation only from this pod's
|
|
135
|
+
* control token, for this slot (ADR-0023). It reads the ledger and not the live registration,
|
|
136
|
+
* because the ledger outlives the Turn and the run.
|
|
137
|
+
*/
|
|
138
|
+
sandbox?: string;
|
|
139
|
+
slot?: string;
|
|
125
140
|
};
|
|
126
141
|
|
|
127
142
|
/**
|
|
@@ -143,15 +158,15 @@ export type AgentTurnInput = {
|
|
|
143
158
|
/**
|
|
144
159
|
* The other half of the FRAME (ADR-0057) — WHERE this Turn works: the absolute directory the
|
|
145
160
|
* Harness roots the Working tools at. Per Turn by nature, which is why no definition names one:
|
|
146
|
-
* a Worktree path (`/work/<slot>/<
|
|
161
|
+
* a Worktree path (`/work/<slot>/<name>`) exists only once a Machine has cut it (ADR-0075).
|
|
147
162
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
163
|
+
* Required under Repo Slots: a Turn with no `cwd` there is REFUSED before admission, and a Turn
|
|
164
|
+
* that wants `/work` states it. A `workspace: "none"` Agent frames no directory (it has no Working
|
|
165
|
+
* tools to root — ADR-0028), and a Workspace with no Repo Slot, or a run with no Workspace at all,
|
|
166
|
+
* frames nothing and the Harness keeps `/work`. A relative path is refused at admission, by the
|
|
167
|
+
* Harness.
|
|
153
168
|
*/
|
|
154
|
-
cwd?:
|
|
169
|
+
cwd?: AnyFramePath;
|
|
155
170
|
/**
|
|
156
171
|
* This turn's DIALS (ADR-0018) — how hard to run, layered over the definition's own
|
|
157
172
|
* values. One definition value may be carried by several Machines, so the same persona
|
|
@@ -358,10 +373,16 @@ export interface AgentRunPort {
|
|
|
358
373
|
* Machine edit that changed it. */
|
|
359
374
|
export type AgentAdmitOptions = {
|
|
360
375
|
definition: AgentDefinition;
|
|
376
|
+
/** This Agent's slot (ADR-0076): the actor path of its Machine and its agent name — the
|
|
377
|
+
* registration's. The Harness keeps it with the conversation and states it on each control
|
|
378
|
+
* request; it never derives it from an Instance ID, whose actor ids may hold a `/`. */
|
|
379
|
+
slot: string;
|
|
361
380
|
signal?: AbortSignal;
|
|
362
381
|
/** The pending notices in this admission's scope (ADR-0062), for the Briefing's Turn part. Not
|
|
363
382
|
* on the persisted input either: they are the Orchestrator's, and delivered once. */
|
|
364
383
|
notices?: Notice[];
|
|
384
|
+
/** The Steering messages held for this Agent slot (ADR-0002), delivered by the rules of a notice. */
|
|
385
|
+
steering?: SteeringMessage[];
|
|
365
386
|
/** The admission is for a conversation that `holds` said the Harness keeps (ADR-0062): the
|
|
366
387
|
* re-admission after a cut, and a continued Turn. That "kept" can be a timeout's, so a Harness
|
|
367
388
|
* that keeps none refuses it and seats none; `admit` then rejects with an error whose `notKept`
|
|
@@ -555,20 +576,15 @@ const READMIT_PROMPT =
|
|
|
555
576
|
* an ABSENT one, and a directory a state named is never second-guessed — a Menu-only Agent handed
|
|
556
577
|
* one simply has no Working tools to root there (ADR-0028/0031).
|
|
557
578
|
*
|
|
558
|
-
* Absent, a Menu-only Agent frames no directory
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* ADR-0057 was written for
|
|
563
|
-
* frames nothing, so the Harness keeps `/work`, the folder a slotless Workspace's steps share.
|
|
579
|
+
* Absent, a Menu-only Agent frames no directory, and a Workspace with no Repo Slot, or a run outside
|
|
580
|
+
* any Workspace, frames nothing, so the Harness keeps `/work`, the folder a slotless Workspace's
|
|
581
|
+
* steps share. Under Repo Slots the Workspace only clones, so no Worktree exists for a default to
|
|
582
|
+
* name, and the kit gives no slot a meaning: an absent `cwd` is REFUSED, because a wrong guess is
|
|
583
|
+
* the silent failure ADR-0057 was written for (ADR-0075).
|
|
564
584
|
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* the reviewer its path as "cwd and prompt"). A `workspace: "read"` Agent that frames nothing
|
|
569
|
-
* under a one-slot Workspace therefore works in the branch checkout with `write`/`edit` withheld
|
|
570
|
-
* and `bash` kept — the tool layer's hint, not containment (ADR-0028): containment is the
|
|
571
|
-
* detached review Worktree, and only a reviewing state that requested one can frame it.
|
|
585
|
+
* `workspace: "read"` withholds `write`/`edit` and keeps `bash`: the tool layer's hint, not
|
|
586
|
+
* containment (ADR-0028). The containment is a detached Worktree that the state cuts and names as
|
|
587
|
+
* its `cwd`.
|
|
572
588
|
*
|
|
573
589
|
* The refusal throws at invoke, like the Open-model fence: loudly, at the first Turn, before a
|
|
574
590
|
* surface is registered or a pod is spent.
|
|
@@ -583,12 +599,11 @@ function frameCwd(
|
|
|
583
599
|
if (workspace === "none") return undefined;
|
|
584
600
|
const slots = Object.keys(ambient?.repos ?? {});
|
|
585
601
|
if (slots.length === 0) return undefined;
|
|
586
|
-
|
|
602
|
+
// A default here is ADR-0057's incident: an Agent's search hits `default/`, and a relative write
|
|
603
|
+
// lands outside each Worktree. An author who wants `/work` states it.
|
|
587
604
|
throw new Error(
|
|
588
|
-
`turn ${instanceId}: agent "${input.agentName}" works under a Workspace
|
|
589
|
-
|
|
590
|
-
`in (\`cwd: context.workspace.repos.<slot>\`, one of those), because the kit gives no slot a ` +
|
|
591
|
-
`meaning (ADR-0051) and a Turn's Frame is where it works (ADR-0057)`,
|
|
605
|
+
`turn ${instanceId}: agent "${input.agentName}" works under a Workspace with Repo Slots (${slots.join(", ")}) ` +
|
|
606
|
+
'and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above them (ADR-0057)',
|
|
592
607
|
);
|
|
593
608
|
}
|
|
594
609
|
|
|
@@ -701,7 +716,7 @@ export function agentActorWith(
|
|
|
701
716
|
|
|
702
717
|
// The Frame's other half (ADR-0057): WHERE this Turn works. Resolved HERE, before any surface
|
|
703
718
|
// is registered and before a pod is spent, because the answer is a fact about the run — a
|
|
704
|
-
// Worktree
|
|
719
|
+
// Worktree exists only once a run has cut it, so no definition could have named it.
|
|
705
720
|
const cwd = frameCwd(instanceId, input, workspace, ambient);
|
|
706
721
|
const framed: AgentRunInput = cwd === undefined ? input : { ...input, cwd };
|
|
707
722
|
|
|
@@ -732,8 +747,18 @@ export function agentActorWith(
|
|
|
732
747
|
// This Agent's continued conversation in this Machine (ADR-0057) — the base id the epoch ledger
|
|
733
748
|
// burns. This Turn is PART of it only when it continues it: a fresh Turn is new by definition.
|
|
734
749
|
const conversation = continuedIid(self._parent, binding.runId, input.agentName);
|
|
750
|
+
// This Agent's slot (ADR-0076): the same identity without the run id. The Harness states it on
|
|
751
|
+
// every control request, and the Orchestrator speaks here only for it.
|
|
752
|
+
const slot = agentSlot(self._parent, input.agentName);
|
|
735
753
|
// The iids whose surface the Harness has read (see `carried` below).
|
|
736
754
|
const served = new Set<string>();
|
|
755
|
+
// Per iid, the notices a step boundary's surface read reserved and the Harness has not yet
|
|
756
|
+
// acknowledged (ADR-0062): by the id the read named, every id it stands for — equal notices
|
|
757
|
+
// are said once — and what each was raised as, for `reraiseUnheard`.
|
|
758
|
+
const boundary = new Map<
|
|
759
|
+
string,
|
|
760
|
+
Map<string, Array<{ id: string; raised: { notice: LedgerBody; source?: string } }>>
|
|
761
|
+
>();
|
|
737
762
|
const registerSurface = (iid: string) =>
|
|
738
763
|
disposers.push(
|
|
739
764
|
binding.table.register({
|
|
@@ -744,6 +769,9 @@ export function agentActorWith(
|
|
|
744
769
|
defs,
|
|
745
770
|
menu,
|
|
746
771
|
sandbox,
|
|
772
|
+
slot,
|
|
773
|
+
boundaryNotices: () => readBoundary(iid),
|
|
774
|
+
noticesHeard: (ids) => boundaryHeard(iid, ids),
|
|
747
775
|
deliver: (event) => {
|
|
748
776
|
signaled = true;
|
|
749
777
|
// The settlement-pick marker (ADR-0023), BEFORE the delivery moves the Machine: the pick
|
|
@@ -824,7 +852,8 @@ export function agentActorWith(
|
|
|
824
852
|
// LIVE conversation stamped on the record, so a restore settle-follows the live submission
|
|
825
853
|
// AND re-addresses it (see `currentIid` above). The notices the admission carried are
|
|
826
854
|
// delivered by this same write (ADR-0062).
|
|
827
|
-
|
|
855
|
+
// The conversation is a member of its Workspace's audiences from this save on (ADR-0062).
|
|
856
|
+
const ledger = (admission: AgentAdmission, delivered: readonly string[], taker: string) =>
|
|
828
857
|
binding.recordAdmission?.(
|
|
829
858
|
instanceId,
|
|
830
859
|
{
|
|
@@ -832,9 +861,15 @@ export function agentActorWith(
|
|
|
832
861
|
instanceId: currentIid,
|
|
833
862
|
agent: input.agentName,
|
|
834
863
|
endpoint,
|
|
864
|
+
...(sandbox === undefined ? {} : { sandbox }),
|
|
865
|
+
slot,
|
|
835
866
|
...(input.turnId === undefined ? {} : { turnId: input.turnId }),
|
|
836
867
|
},
|
|
837
868
|
delivered,
|
|
869
|
+
taker,
|
|
870
|
+
noticeWorkspace === undefined
|
|
871
|
+
? undefined
|
|
872
|
+
: { workspace: noticeWorkspace, slot, continued: Boolean(input.continuation) && taker === conversationIid() },
|
|
838
873
|
);
|
|
839
874
|
|
|
840
875
|
// Where this Turn hears notices (ADR-0062). Its continued conversation's — the base id the
|
|
@@ -845,7 +880,11 @@ export function agentActorWith(
|
|
|
845
880
|
const noticeScope: NoticeScope = {
|
|
846
881
|
...(input.continuation ? { conversation } : {}),
|
|
847
882
|
...(noticeWorkspace !== undefined ? { workspace: noticeWorkspace } : {}),
|
|
883
|
+
slot,
|
|
848
884
|
};
|
|
885
|
+
// The Instance ID of the continued conversation this Turn rides — its epoch's spelling, which
|
|
886
|
+
// the minted `instanceId` is. A fresh Turn has none.
|
|
887
|
+
const conversationIid = (): string | undefined => (input.continuation ? instanceId : undefined);
|
|
849
888
|
// No `agent`: the notice names no Turn — what the pod's list says (below).
|
|
850
889
|
// No `limit`: the sizes read were not sizes, and the notice says none.
|
|
851
890
|
const memoryLimit = ({ limit, peak, agent }: { limit?: string; peak?: string; agent?: string }): Notice => ({
|
|
@@ -859,12 +898,15 @@ export function agentActorWith(
|
|
|
859
898
|
// Turn's Agent. A stopped actor's last poll names nothing: the state that asked for the Turn has
|
|
860
899
|
// exited, and the pod's list carries the kill. Keyed by the kill's id, so a second running
|
|
861
900
|
// conversation's stream, or the pod's list, adds nothing.
|
|
901
|
+
// The kill cut this Turn (D4 rule 2): its Agent's next Turn retries the broken work, and hears it.
|
|
862
902
|
const onMemoryLimit = (kill: StreamedKill): void => {
|
|
863
903
|
if (stopped || noticeWorkspace === undefined) return;
|
|
864
904
|
binding.raiseNotice?.(
|
|
865
905
|
memoryLimit({ limit: kill.limit, peak: kill.peak, agent: input.agentName }),
|
|
866
906
|
noticeWorkspace,
|
|
867
907
|
kill.legacy ? kill.id : guardSource(noticeWorkspace, kill.id),
|
|
908
|
+
[],
|
|
909
|
+
{ retry: { slot, cut: currentIid }, at: new Date().toISOString() },
|
|
868
910
|
);
|
|
869
911
|
};
|
|
870
912
|
// …but a stream is not every kill's path. A kill while no Submission ran — a process an earlier
|
|
@@ -906,6 +948,8 @@ export function agentActorWith(
|
|
|
906
948
|
memoryLimit({ limit: kill.limit }),
|
|
907
949
|
noticeWorkspace,
|
|
908
950
|
kernelSource(noticeWorkspace, kill.at),
|
|
951
|
+
[],
|
|
952
|
+
{ at: kill.at },
|
|
909
953
|
);
|
|
910
954
|
};
|
|
911
955
|
let killMark = input.attach?.killMark;
|
|
@@ -937,7 +981,9 @@ export function agentActorWith(
|
|
|
937
981
|
noticeWorkspace,
|
|
938
982
|
guardSource(noticeWorkspace, newest.id),
|
|
939
983
|
kills.slice(0, -1).map((kill) => guardSource(noticeWorkspace, kill.id)),
|
|
984
|
+
{ at: newest.at, told: newest.told.map((t) => t.instanceId) },
|
|
940
985
|
);
|
|
986
|
+
applyTold(kills);
|
|
941
987
|
const toldHere = (kill: ListedKill): boolean =>
|
|
942
988
|
kill.told.some((t) => t.agent === input.agentName && t.instanceId === currentIid);
|
|
943
989
|
// The first kill after the Turn's mark. A mark no longer in the bounded list is older than
|
|
@@ -956,6 +1002,86 @@ export function agentActorWith(
|
|
|
956
1002
|
};
|
|
957
1003
|
};
|
|
958
1004
|
|
|
1005
|
+
// The conversations each listed kill's `bash` answer told leave its audience (D4 rule 3) — a
|
|
1006
|
+
// kill raised off a stream before its answer returned is found here too.
|
|
1007
|
+
const applyTold = (kills: readonly ListedKill[]): void => {
|
|
1008
|
+
if (noticeWorkspace === undefined) return;
|
|
1009
|
+
const told = kills
|
|
1010
|
+
.filter((kill) => kill.told.length > 0)
|
|
1011
|
+
.map((kill) => ({ source: guardSource(noticeWorkspace, kill.id), takers: kill.told.map((t) => t.instanceId) }));
|
|
1012
|
+
if (told.length > 0) binding.noticesTold?.(told);
|
|
1013
|
+
};
|
|
1014
|
+
// The step boundary's read (ADR-0062): this conversation's pending notices in their audience,
|
|
1015
|
+
// reserved for this Turn until the Harness acknowledges the append. A guard kill's audience
|
|
1016
|
+
// leaves out the conversations its `bash` answer told, which only the pod's list says — so when
|
|
1017
|
+
// one is among them, the list is read first, and a read that could not tell takes no guard kill.
|
|
1018
|
+
const readBoundary = async (iid: string): Promise<Array<{ id: string; notice: LedgerBody }>> => {
|
|
1019
|
+
if (stopped || iid !== currentIid || binding.takeNotices === undefined) return [];
|
|
1020
|
+
const scope: NoticeScope = { ...noticeScope, taker: iid };
|
|
1021
|
+
if (noticeWorkspace !== undefined) {
|
|
1022
|
+
const guard = guardSource(noticeWorkspace, "");
|
|
1023
|
+
if ((binding.peekNotices?.(scope) ?? []).some((p) => p.source?.startsWith(guard))) {
|
|
1024
|
+
let kills: ListedKill[] | undefined;
|
|
1025
|
+
try {
|
|
1026
|
+
kills = await client.memoryKills({
|
|
1027
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(memoryKillsReadMs)]),
|
|
1028
|
+
});
|
|
1029
|
+
} catch {
|
|
1030
|
+
kills = undefined;
|
|
1031
|
+
}
|
|
1032
|
+
if (kills === undefined) scope.exceptPrefix = guard;
|
|
1033
|
+
else applyTold(kills);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (stopped || iid !== currentIid) return [];
|
|
1037
|
+
const taken = binding.takeNotices(scope);
|
|
1038
|
+
const held = boundary.get(iid) ?? new Map();
|
|
1039
|
+
const said = new Map<string, string>();
|
|
1040
|
+
const out: Array<{ id: string; notice: LedgerBody }> = [];
|
|
1041
|
+
taken.ids.forEach((id, i) => {
|
|
1042
|
+
const raised = taken.raised?.[i] ?? { notice: taken.notices[i]! };
|
|
1043
|
+
const key = JSON.stringify(raised.notice);
|
|
1044
|
+
const first = said.get(key);
|
|
1045
|
+
if (first === undefined) {
|
|
1046
|
+
said.set(key, id);
|
|
1047
|
+
held.set(id, [{ id, raised }]);
|
|
1048
|
+
out.push({ id, notice: raised.notice });
|
|
1049
|
+
} else held.get(first)!.push({ id, raised });
|
|
1050
|
+
});
|
|
1051
|
+
if (held.size > 0) boundary.set(iid, held);
|
|
1052
|
+
return out;
|
|
1053
|
+
};
|
|
1054
|
+
// A step boundary whose notices the Harness did not append — a stopped model with no Allowed
|
|
1055
|
+
// picks, or a Turn ending as it read (`between-steps.ts`) — holds them until the next admission
|
|
1056
|
+
// of the conversation, which takes them again: a wake or a nudge carries them (ADR-0062).
|
|
1057
|
+
const releaseBoundary = (iid: string): void => {
|
|
1058
|
+
const held = boundary.get(iid);
|
|
1059
|
+
if (held === undefined) return;
|
|
1060
|
+
boundary.delete(iid);
|
|
1061
|
+
binding.releaseNotices?.(
|
|
1062
|
+
[...held.values()].flatMap((hs) => hs.map((h) => h.id)),
|
|
1063
|
+
iid,
|
|
1064
|
+
);
|
|
1065
|
+
};
|
|
1066
|
+
// The Harness appended them: heard in this conversation, in one save. An id this read did not
|
|
1067
|
+
// name changes nothing.
|
|
1068
|
+
const boundaryHeard = (iid: string, ids: readonly string[]): number => {
|
|
1069
|
+
const held = boundary.get(iid);
|
|
1070
|
+
if (held === undefined) return 0;
|
|
1071
|
+
const heard = ids.flatMap((id) => held.get(id) ?? []);
|
|
1072
|
+
for (const id of ids) held.delete(id);
|
|
1073
|
+
if (held.size === 0) boundary.delete(iid);
|
|
1074
|
+
if (heard.length === 0) return 0;
|
|
1075
|
+
binding.acknowledgeNotices?.(
|
|
1076
|
+
heard.map((h) => h.id),
|
|
1077
|
+
iid,
|
|
1078
|
+
);
|
|
1079
|
+
const bucket = carriedBy.get(iid) ?? { heard: [], unheard: [], task: false, fresh: [] };
|
|
1080
|
+
bucket.heard.push(...heard.map((h) => h.raised));
|
|
1081
|
+
carriedBy.set(iid, bucket);
|
|
1082
|
+
return ids.filter((id) => heard.some((h) => h.id === id)).length;
|
|
1083
|
+
};
|
|
1084
|
+
|
|
959
1085
|
// The Turn's admission and a reroll's carry the pending notices in scope — a reroll's
|
|
960
1086
|
// conversation is fresh, and has heard nothing. A nudge's and a wake's carry only the guard
|
|
961
1087
|
// kills of this Turn that came after its own `bash` call's last kill verdict (`hearPodKills`):
|
|
@@ -973,7 +1099,7 @@ export function agentActorWith(
|
|
|
973
1099
|
// the Harness last read its surface, `heard` before that — and whether an unheard one carried
|
|
974
1100
|
// the Turn's task. Collected across the Turn, so a re-admission onto a lost conversation tells
|
|
975
1101
|
// the Agent all of them again.
|
|
976
|
-
type Raised = { notice:
|
|
1102
|
+
type Raised = { notice: LedgerBody; source?: string };
|
|
977
1103
|
// `fresh`: a fresh Turn's "this conversation is new", which rides an admission as `extra` and so
|
|
978
1104
|
// is kept in no ledger — an unread one is carried again by the next re-admission.
|
|
979
1105
|
const carriedBy = new Map<string, { heard: Raised[]; unheard: Raised[]; task: boolean; fresh: Notice[] }>();
|
|
@@ -996,26 +1122,34 @@ export function agentActorWith(
|
|
|
996
1122
|
) => {
|
|
997
1123
|
await hearKernelKill();
|
|
998
1124
|
const { known, unsaid, said } = await hearPodKills();
|
|
1125
|
+
releaseBoundary(turn.instanceId);
|
|
1126
|
+
// The conversation that takes a Workspace notice (ADR-0062): the one this admission rides, its
|
|
1127
|
+
// Instance ID. A continued Turn's is the continued conversation's; a fresh Turn's, a reroll's
|
|
1128
|
+
// and a burned conversation's successor's are each a new conversation, which has heard none.
|
|
1129
|
+
// A Workspace notice is delivered once in each, so every conversation in the Workspace hears
|
|
1130
|
+
// it — each child of a Pool has its own — and no conversation hears it twice.
|
|
1131
|
+
const taker = turn.instanceId;
|
|
999
1132
|
// A read that could not tell leaves the same Turn no way to know which kills its own `bash`
|
|
1000
1133
|
// reported: it takes none, as a nudge takes none then, and they stay for the next Turn.
|
|
1001
1134
|
const sameTurn: NoticeScope = known
|
|
1002
|
-
? { ...noticeScope, except: said }
|
|
1135
|
+
? { ...noticeScope, taker, except: said }
|
|
1003
1136
|
: {
|
|
1004
1137
|
...noticeScope,
|
|
1138
|
+
taker,
|
|
1005
1139
|
...(noticeWorkspace === undefined ? {} : { exceptPrefix: guardSource(noticeWorkspace, "") }),
|
|
1006
1140
|
};
|
|
1007
1141
|
const scope: NoticeScope | undefined =
|
|
1008
1142
|
opts.notices === "turn"
|
|
1009
|
-
? noticeScope
|
|
1143
|
+
? { ...noticeScope, taker, turn: true }
|
|
1010
1144
|
: opts.notices === "same-turn"
|
|
1011
1145
|
? sameTurn
|
|
1012
1146
|
: unsaid.length > 0
|
|
1013
|
-
? { workspace: noticeWorkspace, only: unsaid }
|
|
1147
|
+
? { workspace: noticeWorkspace, taker, only: unsaid }
|
|
1014
1148
|
: undefined;
|
|
1015
|
-
const none = { ids: [] as string[], notices: [] as
|
|
1149
|
+
const none = { ids: [] as string[], notices: [] as LedgerBody[], raised: [] as Raised[] };
|
|
1016
1150
|
const restored =
|
|
1017
1151
|
opts.include && opts.include.length > 0
|
|
1018
|
-
? (binding.takeNotices?.({ ...noticeScope, only: opts.include }) ?? none)
|
|
1152
|
+
? (binding.takeNotices?.({ ...noticeScope, taker, only: opts.include }) ?? none)
|
|
1019
1153
|
: none;
|
|
1020
1154
|
const main = (scope ? binding.takeNotices?.(scope) : undefined) ?? none;
|
|
1021
1155
|
const taken = {
|
|
@@ -1030,12 +1164,16 @@ export function agentActorWith(
|
|
|
1030
1164
|
// cut pick never got (ADR-0031), whose one path to the model is this admission. One "this
|
|
1031
1165
|
// conversation is new" is enough, whichever path brought it.
|
|
1032
1166
|
let fresh = false;
|
|
1033
|
-
const
|
|
1167
|
+
const bodies = [...taken.notices, ...(opts.extra ?? [])].filter((n) => {
|
|
1034
1168
|
if (n.kind !== "conversation-new") return true;
|
|
1035
1169
|
if (fresh) return false;
|
|
1036
1170
|
fresh = true;
|
|
1037
1171
|
return true;
|
|
1038
1172
|
});
|
|
1173
|
+
// The two kinds of body ride the admission apart (ADR-0062): a notice is a fact, and a
|
|
1174
|
+
// Steering message an author's text, which the Harness frames by its author.
|
|
1175
|
+
const notices = bodies.filter((n): n is Notice => n.kind !== "steering");
|
|
1176
|
+
const steering = bodies.filter((n): n is SteeringMessage => n.kind === "steering");
|
|
1039
1177
|
// `served` is per Instance ID, and a nudge or a wake reuses the Turn's: it must be read again
|
|
1040
1178
|
// before this admission counts as heard (`reraiseUnheard`). An admission that fails puts the
|
|
1041
1179
|
// mark back — what an earlier one carried was heard.
|
|
@@ -1045,12 +1183,14 @@ export function agentActorWith(
|
|
|
1045
1183
|
try {
|
|
1046
1184
|
admission = await client.admit(turn, {
|
|
1047
1185
|
definition,
|
|
1186
|
+
slot,
|
|
1048
1187
|
signal: controller.signal,
|
|
1049
1188
|
...(notices.length ? { notices } : {}),
|
|
1189
|
+
...(steering.length ? { steering } : {}),
|
|
1050
1190
|
...(opts.kept ? { kept: true } : {}),
|
|
1051
1191
|
});
|
|
1052
1192
|
} catch (err) {
|
|
1053
|
-
binding.releaseNotices?.(taken.ids);
|
|
1193
|
+
binding.releaseNotices?.(taken.ids, taker);
|
|
1054
1194
|
if (heard) served.add(turn.instanceId);
|
|
1055
1195
|
throw err;
|
|
1056
1196
|
}
|
|
@@ -1071,7 +1211,7 @@ export function agentActorWith(
|
|
|
1071
1211
|
...(killMark === undefined ? {} : { killMark }),
|
|
1072
1212
|
};
|
|
1073
1213
|
}
|
|
1074
|
-
ledger(admission, taken.ids);
|
|
1214
|
+
ledger(admission, taken.ids, taker);
|
|
1075
1215
|
const bucket = carriedBy.get(turn.instanceId) ?? { heard: [], unheard: [], task: false, fresh: [] };
|
|
1076
1216
|
if (heard) {
|
|
1077
1217
|
bucket.heard.push(...bucket.unheard);
|
|
@@ -1099,10 +1239,12 @@ export function agentActorWith(
|
|
|
1099
1239
|
if (gone) bucket.heard = [];
|
|
1100
1240
|
if (!read) bucket.unheard = [];
|
|
1101
1241
|
for (const { notice, source } of again) {
|
|
1102
|
-
const to = notice.scope === "conversation" ? conversation : noticeWorkspace;
|
|
1242
|
+
const to = notice.scope === "conversation" ? conversation : notice.scope === "slot" ? slot : noticeWorkspace;
|
|
1103
1243
|
if (to === undefined) continue;
|
|
1104
|
-
|
|
1105
|
-
|
|
1244
|
+
// A Workspace notice is pending again for the conversation that carried it, which did not
|
|
1245
|
+
// hear it; the others it reached keep theirs.
|
|
1246
|
+
if (binding.restoreNotice) binding.restoreNotice(notice, to, source, iid);
|
|
1247
|
+
else if (notice.kind !== "steering") binding.raiseNotice?.(notice, to);
|
|
1106
1248
|
if (source !== undefined) restored.push(source);
|
|
1107
1249
|
}
|
|
1108
1250
|
}
|
|
@@ -1122,7 +1264,14 @@ export function agentActorWith(
|
|
|
1122
1264
|
// Pool cancelling a child. The one exception is the host stopping the run for its own
|
|
1123
1265
|
// reasons, which ADR-0007's restore re-attaches to.
|
|
1124
1266
|
if (binding.hostStopping) return;
|
|
1267
|
+
// A step boundary's notices the Harness never acknowledged are pending again (ADR-0062).
|
|
1268
|
+
for (const iid of [...boundary.keys()]) releaseBoundary(iid);
|
|
1125
1269
|
reraiseUnheard();
|
|
1270
|
+
// The Turn ended: a fresh conversation leaves every audience in its Workspace, a continued one
|
|
1271
|
+
// waits for its next Turn (ADR-0062).
|
|
1272
|
+
if (noticeWorkspace !== undefined) {
|
|
1273
|
+
binding.noticeTurnEnded?.(noticeWorkspace, [...new Set([...carriedBy.keys(), currentIid])]);
|
|
1274
|
+
}
|
|
1126
1275
|
// Fire-and-forget, and unreportable BY CONSTRUCTION: this actor is stopped, so there is no
|
|
1127
1276
|
// `agent.fault` left to raise. An orphan that survives a failed abort 404s on every tool
|
|
1128
1277
|
// call and settles on its own.
|
|
@@ -1548,10 +1697,13 @@ export function agentActorWith(
|
|
|
1548
1697
|
const kill = lostHarness && sandbox !== undefined ? await memoryFault(sandbox) : undefined;
|
|
1549
1698
|
if (stopped) return;
|
|
1550
1699
|
if (kill !== undefined && noticeWorkspace !== undefined) {
|
|
1700
|
+
// The kill cut this Turn (D4 rule 2): its Agent's next Turn retries the work, and hears it.
|
|
1551
1701
|
binding.raiseNotice?.(
|
|
1552
1702
|
memoryLimit({ limit: kill.limit, agent: input.agentName }),
|
|
1553
1703
|
noticeWorkspace,
|
|
1554
1704
|
kill.at === undefined ? undefined : kernelSource(noticeWorkspace, kill.at),
|
|
1705
|
+
[],
|
|
1706
|
+
{ retry: { slot, cut: currentIid }, ...(kill.at === undefined ? {} : { at: kill.at }) },
|
|
1555
1707
|
);
|
|
1556
1708
|
}
|
|
1557
1709
|
// A restart the Harness came back from keeps the conversation (ADR-0031); a 404 does not.
|
package/src/agent.ts
CHANGED
|
@@ -58,9 +58,47 @@ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhi
|
|
|
58
58
|
* tells what each value gives. */
|
|
59
59
|
export type WorkspaceAccess = "write" | "read" | "none";
|
|
60
60
|
|
|
61
|
+
// One entry of an Agent's `mcp` list (ADR-0079): a server of the config's `mcp` block, by its name,
|
|
62
|
+
// and the allowlist. An object, so that `resources` can join `tools` later. The TYPE cannot say
|
|
63
|
+
// which servers take a list: that is the config's (remote or stdio), so `jr2 up` says it.
|
|
64
|
+
/**
|
|
65
|
+
* One MCP server that an Agent uses, and the tools that it may call there.
|
|
66
|
+
*
|
|
67
|
+
* - `server` is a name of the `mcp` block of `jr2.config.ts`. `jr2 up` refuses a name that the
|
|
68
|
+
* block does not declare, and prints the `customize()` line that binds the Agent to one it does.
|
|
69
|
+
* - `tools` is the allowlist. A remote server needs it: a list of tool names, or `"*"` for every
|
|
70
|
+
* tool. The Harness offers and runs only those tools. When Workspace code in the Agent's Sandbox
|
|
71
|
+
* can spend the server's secret, `jr2 up` takes only `"*"`, because a list would then not be a
|
|
72
|
+
* limit. A stdio server takes no `tools`: the Agent gets every tool of the server.
|
|
73
|
+
*
|
|
74
|
+
* The model reads each tool as `mcp__<server>__<tool>`. A tool name in the list must keep that name
|
|
75
|
+
* within `[A-Za-z0-9_-]` and 128 characters.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { agent } from "@jr2/orchestrator";
|
|
80
|
+
*
|
|
81
|
+
* export const triage = agent({
|
|
82
|
+
* model: "anthropic/claude-haiku-5-5",
|
|
83
|
+
* instructions: "You read the ticket and post a summary to the channel.",
|
|
84
|
+
* workspace: "none",
|
|
85
|
+
* mcp: [{ server: "slack-acme", tools: ["post_message", "read_channel"] }],
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @see docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md
|
|
90
|
+
*/
|
|
91
|
+
export type AgentMcpServer = {
|
|
92
|
+
/** A server name of the `mcp` block of `jr2.config.ts`. */
|
|
93
|
+
server: string;
|
|
94
|
+
/** The allowlist: tool names of the server, or `"*"` for every tool. Required for a remote
|
|
95
|
+
* server; absent for a stdio server. */
|
|
96
|
+
tools?: readonly string[] | "*";
|
|
97
|
+
};
|
|
98
|
+
|
|
61
99
|
// The plain-data Agent definition (ADR-0018): IDENTITY plus one default. A Turn's Frame — what it
|
|
62
|
-
// is about and where it works — is not here and cannot be (ADR-0057): a Worktree
|
|
63
|
-
// once a run has
|
|
100
|
+
// is about and where it works — is not here and cannot be (ADR-0057): a Worktree exists only
|
|
101
|
+
// once a run has cut it, so no definition written before the run could name one.
|
|
64
102
|
//
|
|
65
103
|
// Must stay JSON-serializable: the definition rides the Turn to the Harness (ADR-0049), so
|
|
66
104
|
// anything non-serializable would be silently lost — grow this contract deliberately. ADR-0028
|
|
@@ -119,17 +157,29 @@ export type AgentDefinition = {
|
|
|
119
157
|
* level in the invoke's `input`. */
|
|
120
158
|
thinkingLevel?: ThinkingLevel;
|
|
121
159
|
// The persona in one word (ADR-0028), deliberately not `tools` (that names the control-plane
|
|
122
|
-
// Menu, what it may SAY). `"read"` states intent and stops the honest path: bash stays, and
|
|
123
|
-
// detached
|
|
160
|
+
// Menu, what it may SAY). `"read"` states intent and stops the honest path: bash stays, and a
|
|
161
|
+
// detached Worktree is the containment. `"none"` frames no directory — only Working tools
|
|
124
162
|
// consume one — and places the Turn on the Instance Harness (ADR-0031).
|
|
125
163
|
/** What the Agent may do to the Workspace. Default: `"write"`.
|
|
126
164
|
* - `"write"`: all the Working tools (read, write, edit, bash, grep, glob).
|
|
127
165
|
* - `"read"`: no write or edit tool. The Agent keeps bash, so `"read"` does not stop a command
|
|
128
|
-
* that writes. To keep the Agent from changing the branch, frame its Turn in
|
|
129
|
-
* Worktree:
|
|
166
|
+
* that writes. To keep the Agent from changing the branch, frame its Turn in a detached
|
|
167
|
+
* Worktree: cut it with `{ path, detach: sha }`, then give that `path` as `cwd`.
|
|
130
168
|
* - `"none"`: no Working tools. The Agent only reads its prompt and picks from its Menu. Its Turn
|
|
131
169
|
* runs on the Instance Harness, not in a Sandbox, so it needs no `workspace()` around it. */
|
|
132
170
|
workspace?: WorkspaceAccess;
|
|
171
|
+
// Reach for tools (ADR-0079): names only. The url and the secret are the Instance's, in the
|
|
172
|
+
// config's `mcp` block, so a packaged Machine ships no credential and a composer rebinds the list.
|
|
173
|
+
/** The MCP servers whose tools the Agent gets, each with its allowlist: see
|
|
174
|
+
* {@link AgentMcpServer}. The tools join the Agent's tools, after its Working tools and before
|
|
175
|
+
* its Menu. A call to one is not a pick, and the Turn goes on after it. Default: none. A composer
|
|
176
|
+
* replaces the whole list with `customize(machine, { agents: { <slot>: { mcp: [...] } } })`. */
|
|
177
|
+
mcp?: readonly AgentMcpServer[];
|
|
178
|
+
// Read by the Orchestrator alone (ADR-0002): the admission strips it, and the model never reads it.
|
|
179
|
+
/** May a Caller send this Agent a Steering message? Default: `true`. With `false`,
|
|
180
|
+
* `POST /runs/:runId/steering` and `jr2 steer` refuse it, and `GET /runs/:runId/agents` says
|
|
181
|
+
* `steerable: false`. The Machine can still steer the Agent with the `steer` action. */
|
|
182
|
+
steerable?: boolean;
|
|
133
183
|
};
|
|
134
184
|
|
|
135
185
|
// The Agent as an author DECLARES it (ADR-0054): the definition, except that `model` may be Open —
|
package/src/ambient.ts
CHANGED
|
@@ -21,14 +21,12 @@ import type { AnyActorRef } from "xstate";
|
|
|
21
21
|
export type AmbientHandles = {
|
|
22
22
|
endpoint: string;
|
|
23
23
|
sandbox: string;
|
|
24
|
-
/** Every Repo Slot's
|
|
24
|
+
/** Every Repo Slot's folder, `/work/<slot>`, by slot (ADR-0051). */
|
|
25
25
|
repos: Record<string, string>;
|
|
26
26
|
/** Every Repo Slot's Binding url, by slot (ADR-0051): where a push goes. */
|
|
27
27
|
bindings?: Record<string, string>;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
/** Detached review-worktree paths, when the spec carried `reviewSha` (ADR-0028). */
|
|
31
|
-
review?: Record<string, string>;
|
|
28
|
+
/** Each Repo Slot's Binding base, where the Binding names one: a cut with no `from` starts here. */
|
|
29
|
+
bases?: Record<string, string>;
|
|
32
30
|
};
|
|
33
31
|
|
|
34
32
|
const byRef = new WeakMap<AnyActorRef, AmbientHandles>();
|