@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
|
@@ -19,7 +19,7 @@ the file as `workflows/review.ts` in your [Instance](../reference/glossary.md#in
|
|
|
19
19
|
```ts
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import { assign } from "xstate";
|
|
22
|
-
import { agent, defineEvent, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
22
|
+
import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
23
23
|
|
|
24
24
|
const approve = defineEvent({
|
|
25
25
|
name: "approve",
|
|
@@ -42,6 +42,10 @@ const runInput = z.object({ branch: z.string(), sha: z.string(), model: z.string
|
|
|
42
42
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
43
43
|
type Context = BodyInput & { notes: string; summary: string };
|
|
44
44
|
|
|
45
|
+
// Two Worktrees of one clone: the coder's branch, and the commit that the reviewer reads.
|
|
46
|
+
const coderTree = (context: BodyInput) => `${context.workspace.repos.app}/work` as const;
|
|
47
|
+
const reviewTree = (context: BodyInput) => `${context.workspace.repos.app}/review` as const;
|
|
48
|
+
|
|
45
49
|
const body = jr2Setup({
|
|
46
50
|
types: {} as { input: BodyInput; context: Context },
|
|
47
51
|
events: [approve, requestChanges, finished],
|
|
@@ -64,21 +68,38 @@ const body = jr2Setup({
|
|
|
64
68
|
workspace: "none",
|
|
65
69
|
thinkingLevel: "low",
|
|
66
70
|
}),
|
|
71
|
+
cut: worktree(),
|
|
67
72
|
fail: fail(),
|
|
68
73
|
},
|
|
69
74
|
}).createMachine({
|
|
70
75
|
id: "review",
|
|
71
76
|
context: ({ input }) => ({ ...input, notes: "", summary: "" }),
|
|
72
77
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
73
|
-
initial: "
|
|
78
|
+
initial: "cutting",
|
|
74
79
|
states: {
|
|
80
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
81
|
+
cutting: {
|
|
82
|
+
invoke: {
|
|
83
|
+
src: "cut",
|
|
84
|
+
input: ({ context }) => ({ path: coderTree(context), branch: context.branch }),
|
|
85
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "detaching" }, { target: "notCut" }],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
// The second cut has no branch: the Worktree holds the commit, detached.
|
|
89
|
+
detaching: {
|
|
90
|
+
invoke: {
|
|
91
|
+
src: "cut",
|
|
92
|
+
input: ({ context }) => ({ path: reviewTree(context), detach: context.sha }),
|
|
93
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "reviewing" }, { target: "notCut" }],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
75
96
|
reviewing: {
|
|
76
97
|
invoke: {
|
|
77
98
|
src: "reviewer",
|
|
78
99
|
input: ({ context }) => ({
|
|
79
100
|
prompt: `Review commit ${context.sha}.`,
|
|
80
|
-
// The review Worktree holds the commit
|
|
81
|
-
cwd: context
|
|
101
|
+
// The review Worktree holds the commit, detached from the branch.
|
|
102
|
+
cwd: reviewTree(context),
|
|
82
103
|
// A Dial: this Turn thinks harder than the default of the model.
|
|
83
104
|
thinkingLevel: "high",
|
|
84
105
|
}),
|
|
@@ -93,7 +114,7 @@ const body = jr2Setup({
|
|
|
93
114
|
src: "coder",
|
|
94
115
|
input: ({ context }) => ({
|
|
95
116
|
prompt: `Change the code on the branch as the review asks:\n\n${context.notes}`,
|
|
96
|
-
cwd: context
|
|
117
|
+
cwd: coderTree(context),
|
|
97
118
|
// Each Turn of the coder continues the same conversation.
|
|
98
119
|
continue: true,
|
|
99
120
|
// A Dial: the model of this Turn, from the run input. Absent, the model of the definition.
|
|
@@ -115,6 +136,7 @@ const body = jr2Setup({
|
|
|
115
136
|
},
|
|
116
137
|
},
|
|
117
138
|
done: { type: "final" },
|
|
139
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
118
140
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
119
141
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
120
142
|
},
|
|
@@ -123,7 +145,6 @@ const body = jr2Setup({
|
|
|
123
145
|
export const machine = workspace(body, {
|
|
124
146
|
input: runInput,
|
|
125
147
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
126
|
-
spec: ({ input }) => ({ branch: input.branch, reviewSha: input.sha }),
|
|
127
148
|
});
|
|
128
149
|
```
|
|
129
150
|
|
|
@@ -132,7 +153,7 @@ export const machine = workspace(body, {
|
|
|
132
153
|
```ts
|
|
133
154
|
import { z } from "zod";
|
|
134
155
|
import { assign } from "xstate";
|
|
135
|
-
import { agent, defineEvent, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
156
|
+
import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
136
157
|
|
|
137
158
|
const approve = defineEvent({
|
|
138
159
|
name: "approve",
|
|
@@ -155,6 +176,10 @@ const runInput = z.object({ branch: z.string(), sha: z.string(), model: z.string
|
|
|
155
176
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
156
177
|
type Context = BodyInput & { notes: string; summary: string };
|
|
157
178
|
|
|
179
|
+
// Two Worktrees of one clone: the coder's branch, and the commit that the reviewer reads.
|
|
180
|
+
const coderTree = (context: BodyInput) => `${context.workspace.repos.app}/work` as const;
|
|
181
|
+
const reviewTree = (context: BodyInput) => `${context.workspace.repos.app}/review` as const;
|
|
182
|
+
|
|
158
183
|
const body = jr2Setup({
|
|
159
184
|
types: {} as { input: BodyInput; context: Context },
|
|
160
185
|
events: [approve, requestChanges, finished],
|
|
@@ -177,21 +202,38 @@ const body = jr2Setup({
|
|
|
177
202
|
workspace: "none",
|
|
178
203
|
thinkingLevel: "low",
|
|
179
204
|
}),
|
|
205
|
+
cut: worktree(),
|
|
180
206
|
fail: fail(),
|
|
181
207
|
},
|
|
182
208
|
}).createMachine({
|
|
183
209
|
id: "review",
|
|
184
210
|
context: ({ input }) => ({ ...input, notes: "", summary: "" }),
|
|
185
211
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
186
|
-
initial: "
|
|
212
|
+
initial: "cutting",
|
|
187
213
|
states: {
|
|
214
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
215
|
+
cutting: {
|
|
216
|
+
invoke: {
|
|
217
|
+
src: "cut",
|
|
218
|
+
input: ({ context }) => ({ path: coderTree(context), branch: context.branch }),
|
|
219
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "detaching" }, { target: "notCut" }],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
// The second cut has no branch: the Worktree holds the commit, detached.
|
|
223
|
+
detaching: {
|
|
224
|
+
invoke: {
|
|
225
|
+
src: "cut",
|
|
226
|
+
input: ({ context }) => ({ path: reviewTree(context), detach: context.sha }),
|
|
227
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "reviewing" }, { target: "notCut" }],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
188
230
|
reviewing: {
|
|
189
231
|
invoke: {
|
|
190
232
|
src: "reviewer",
|
|
191
233
|
input: ({ context }) => ({
|
|
192
234
|
prompt: `Review commit ${context.sha}.`,
|
|
193
|
-
// The review Worktree holds the commit
|
|
194
|
-
cwd: context
|
|
235
|
+
// The review Worktree holds the commit, detached from the branch.
|
|
236
|
+
cwd: reviewTree(context),
|
|
195
237
|
// A Dial: this Turn thinks harder than the default of the model.
|
|
196
238
|
thinkingLevel: "high",
|
|
197
239
|
}),
|
|
@@ -206,7 +248,7 @@ const body = jr2Setup({
|
|
|
206
248
|
src: "coder",
|
|
207
249
|
input: ({ context }) => ({
|
|
208
250
|
prompt: `Change the code on the branch as the review asks:\n\n${context.notes}`,
|
|
209
|
-
cwd: context
|
|
251
|
+
cwd: coderTree(context),
|
|
210
252
|
// Each Turn of the coder continues the same conversation.
|
|
211
253
|
continue: true,
|
|
212
254
|
// A Dial: the model of this Turn, from the run input. Absent, the model of the definition.
|
|
@@ -228,6 +270,7 @@ const body = jr2Setup({
|
|
|
228
270
|
},
|
|
229
271
|
},
|
|
230
272
|
done: { type: "final" },
|
|
273
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
231
274
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
232
275
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
233
276
|
},
|
|
@@ -236,7 +279,6 @@ const body = jr2Setup({
|
|
|
236
279
|
export const machine = workspace(body, {
|
|
237
280
|
input: runInput,
|
|
238
281
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
239
|
-
spec: ({ input }) => ({ branch: input.branch, reviewSha: input.sha }),
|
|
240
282
|
});
|
|
241
283
|
```
|
|
242
284
|
|
|
@@ -259,13 +301,13 @@ prompt and picks from its [Menu](../reference/glossary.md#menu). You can invoke
|
|
|
259
301
|
the example does, or in a [Machine](../reference/glossary.md#machine) with no Workspace.
|
|
260
302
|
|
|
261
303
|
A `"read"` Agent keeps bash, so a command can still change a file or the branch. To keep a reviewer away from the
|
|
262
|
-
branch, give its Turn
|
|
304
|
+
branch, give its Turn a detached [Worktree](../reference/glossary.md#worktree):
|
|
263
305
|
|
|
264
|
-
1.
|
|
265
|
-
2. In the Turn, set `cwd` to
|
|
306
|
+
1. Cut the Worktree with [`worktree()`](../reference/authoring-surface/worktree.md), with `{ path, detach }`.
|
|
307
|
+
2. In the Turn, set `cwd` to that path.
|
|
266
308
|
|
|
267
|
-
The
|
|
268
|
-
|
|
309
|
+
The Worktree holds the commit, with no branch. In the example, the path is `/work/app/review`, and the commit comes from
|
|
310
|
+
the run input. [Cut a Worktree](cut-a-worktree.md) tells more.
|
|
269
311
|
|
|
270
312
|
The access is a part of the identity of the Agent. Only the definition sets it. A Turn cannot change it.
|
|
271
313
|
|
|
@@ -290,7 +332,7 @@ Run [`jr2 up`](../reference/cli.md#up) to converge the new Workflow:
|
|
|
290
332
|
jr2 up
|
|
291
333
|
```
|
|
292
334
|
|
|
293
|
-
|
|
335
|
+
The `effects:` lines show the cut. The `seats:` lines show the picks of the three Agents.
|
|
294
336
|
|
|
295
337
|
To give a model to the coder's Turns, add `model` to the run input:
|
|
296
338
|
|
|
@@ -318,6 +360,8 @@ Dial. Thus:
|
|
|
318
360
|
- `jr2 up` gives each pod the model key of the providers that the definitions of its Agents name. For an Agent in a
|
|
319
361
|
Sandbox, use a Dial model of a provider that a definition in that Sandbox names. For a Menu-only Agent, use a provider
|
|
320
362
|
that a Menu-only definition of the Instance names.
|
|
363
|
+
- In the same way, `jr2 up` gives each pod the secret of each MCP server that the definitions of its Agents name. The
|
|
364
|
+
[MCP tools](../reference/glossary.md#mcp-tools) of an Agent come from its definition only. A Turn cannot change them.
|
|
321
365
|
|
|
322
366
|
## Continue a conversation
|
|
323
367
|
|
|
@@ -337,9 +381,11 @@ task, also on a Turn that continues. In the example, each `fixing` prompt gives
|
|
|
337
381
|
- jr2 refuses a Turn whose `input` has a key that a Turn does not have, such as `instructions` or `workspace`. Only the
|
|
338
382
|
definition sets those two. `<keys>` is each key that is not correct, in backticks, with a comma between them:
|
|
339
383
|
``agent "<slot>": the Turn's input carries <keys>, which a Turn has no use for — its input is its Frame (`prompt`, `cwd`), its Dials (`model`, `thinkingLevel`), and `continue` (ADR-0057)``
|
|
340
|
-
- Under a Workspace with
|
|
341
|
-
|
|
342
|
-
``turn <id>: agent "<slot>" works under a Workspace
|
|
384
|
+
- Under a Workspace with [Repo Slots](../reference/glossary.md#repo-slot), jr2 refuses a Turn of an Agent with Working
|
|
385
|
+
tools that sets no `cwd`:
|
|
386
|
+
``turn <id>: agent "<slot>" works under a Workspace with Repo Slots (<slots>) and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above them (ADR-0057)``
|
|
387
|
+
- `workspace()` refuses a body with an invoke that does not say where it works, when it can read the input:
|
|
388
|
+
``workspace(): state "<state>": "<slot>" names no `<key>`, and a Workspace with Repo Slots says where each Turn and Effect works: a Worktree, or `cwd: "/work"` for a Turn (ADR-0057)``
|
|
343
389
|
- The Harness refuses a `cwd` that is not an absolute path. The run gets `agent.fault`, with this reason:
|
|
344
390
|
``harness admission failed (400): agent "<slot>": the Turn's Frame carries a `cwd` that is not an absolute path — the Frame says WHERE this Turn works (ADR-0057), and a Turn rooted anywhere else would work in the wrong directory silently``
|
|
345
391
|
- The Harness refuses a model that is not in its catalog and not a model of the custom provider. The run gets
|
|
@@ -359,7 +405,8 @@ task, also on a Turn that continues. In the example, each `fixing` prompt gives
|
|
|
359
405
|
## Read next
|
|
360
406
|
|
|
361
407
|
- [Agents and Turns](../concepts/agents-and-turns.md): the definition, the Frame, the Dials and `continue`.
|
|
362
|
-
- [Workspaces](../concepts/workspaces.md): the Worktrees
|
|
408
|
+
- [Workspaces](../concepts/workspaces.md): the Worktrees.
|
|
409
|
+
- [Cut a Worktree](cut-a-worktree.md): a detached Worktree for a reviewer.
|
|
363
410
|
- [The Menu](../concepts/the-menu.md): the picks of an Agent.
|
|
364
411
|
- [Bind a shipped Machine's Open parts](bind-open-parts.md): bind an Open model.
|
|
365
412
|
- Reference: [`agent`](../reference/authoring-surface/agent.md),
|
|
@@ -14,26 +14,39 @@ it on a node, so the Sandbox always has it. Write it in the `resources` option o
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
import { z } from "zod";
|
|
17
|
-
import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
17
|
+
import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
18
18
|
|
|
19
19
|
const runInput = z.object({ branch: z.string() });
|
|
20
20
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
21
21
|
|
|
22
|
+
// The Worktree that the tests run in.
|
|
23
|
+
const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
|
|
24
|
+
|
|
22
25
|
const body = jr2Setup({
|
|
23
|
-
types: {} as { input: BodyInput },
|
|
26
|
+
types: {} as { input: BodyInput; context: BodyInput },
|
|
24
27
|
actors: {
|
|
28
|
+
cut: worktree(),
|
|
25
29
|
// JR2_CPUS holds the cpu limit of the container. Playwright does not read that limit.
|
|
26
30
|
e2e: command(["sh", "-c", "npx playwright test --workers=$JR2_CPUS"], { timeout: "30m" }),
|
|
27
31
|
fail: fail(),
|
|
28
32
|
},
|
|
29
33
|
}).createMachine({
|
|
30
34
|
id: "e2e",
|
|
35
|
+
context: ({ input }) => input,
|
|
31
36
|
on: { "workspace.lost": ".lost" },
|
|
32
|
-
initial: "
|
|
37
|
+
initial: "cutting",
|
|
33
38
|
states: {
|
|
39
|
+
cutting: {
|
|
40
|
+
invoke: {
|
|
41
|
+
src: "cut",
|
|
42
|
+
input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
|
|
43
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
34
46
|
testing: {
|
|
35
47
|
invoke: {
|
|
36
48
|
src: "e2e",
|
|
49
|
+
input: ({ context }) => ({ worktree: testTree(context) }),
|
|
37
50
|
onDone: [
|
|
38
51
|
{
|
|
39
52
|
guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
|
|
@@ -46,6 +59,7 @@ const body = jr2Setup({
|
|
|
46
59
|
passed: { type: "final" },
|
|
47
60
|
// Not final states: each one fails the run, and the run ends `failed`.
|
|
48
61
|
failed: { invoke: { src: "fail", input: { reason: "the Playwright tests did not pass" } } },
|
|
62
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
49
63
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
|
|
50
64
|
},
|
|
51
65
|
});
|
|
@@ -55,7 +69,6 @@ export const machine = workspace(body, {
|
|
|
55
69
|
// The Size of the whole Sandbox: 4 GiB of memory and 2 cpus. jr2 reserves all of it on a node.
|
|
56
70
|
resources: { limits: { memory: "4Gi", cpu: "2" } },
|
|
57
71
|
repos: { app: "https://github.com/you/app.git" },
|
|
58
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
59
72
|
});
|
|
60
73
|
```
|
|
61
74
|
|
|
@@ -160,8 +173,8 @@ The Harness can miss a fast increase. Then the kernel stops the whole container,
|
|
|
160
173
|
memory limit (OOMKilled, limit <memory>): the Workspace's processes passed the Harness container's memory limit and the kernel killed the container, the conversation with it. Use fewer workers, or give the Workspace a larger Size (ADR-0060, ADR-0061).
|
|
161
174
|
```
|
|
162
175
|
|
|
163
|
-
|
|
164
|
-
Size.
|
|
176
|
+
Each conversation that was in the Workspace at the kill gets a notice of it one time, and so does the next Turn of the
|
|
177
|
+
Agent whose Turn the kill cut. When you see either text again and again, state a larger Size.
|
|
165
178
|
|
|
166
179
|
## Rules
|
|
167
180
|
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Start a run for each alert
|
|
3
|
+
description: Make a standing Pool that starts one run for each alert that an outside service sends to a Hook.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Start a run for each alert
|
|
7
|
+
|
|
8
|
+
Start one run of a [Workflow](../reference/glossary.md#workflow) for each alert that an alert service sends to a
|
|
9
|
+
[Hook](../reference/glossary.md#hook).
|
|
10
|
+
|
|
11
|
+
The example has three Workflows and one Hook. The alert service sends each new issue to the Hook `sentry`. The Workflow
|
|
12
|
+
`alerts` is a standing [Pool](../reference/glossary.md#pool) of runs: its push Source holds each alert, and the Pool
|
|
13
|
+
starts one run of `triage` for each alert, with at most 8 live at one time. A `triage` run waits until a person takes
|
|
14
|
+
the alert or dismisses it.
|
|
15
|
+
|
|
16
|
+
`@jr2/orchestrator/hooks/webhook` has `webhook()`, which makes the kind of a service that jr2 ships no kind for. It is
|
|
17
|
+
part of `@jr2/orchestrator`, so your [Instance](../reference/glossary.md#instance) installs nothing more.
|
|
18
|
+
|
|
19
|
+
## Write the kind of the service
|
|
20
|
+
|
|
21
|
+
The alert service signs the raw body with a shared secret, and puts the HMAC-SHA256 in hex in the header
|
|
22
|
+
`sentry-hook-signature`. The header `sentry-hook-resource` and the field `action` give the event key, such as
|
|
23
|
+
`issue.created`. The header `request-id` is the id of the delivery. Save the kind as `hooks/sentry.ts`, beside
|
|
24
|
+
`workflows/`, because it is not a Workflow:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { webhook } from "@jr2/orchestrator/hooks/webhook";
|
|
29
|
+
|
|
30
|
+
// An alert service that jr2 ships no kind for. The service signs the raw body with a shared secret
|
|
31
|
+
// (sha256, hex) in the header sentry-hook-signature. The event key is the resource and its action,
|
|
32
|
+
// such as issue.created. The module is in `hooks/`, not in `workflows/`, because it is not a Workflow.
|
|
33
|
+
export const sentry = webhook({
|
|
34
|
+
id: "sentry",
|
|
35
|
+
events: {
|
|
36
|
+
"issue.created": z.looseObject({
|
|
37
|
+
data: z.looseObject({
|
|
38
|
+
issue: z.looseObject({ id: z.string(), title: z.string(), level: z.string() }),
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
verify: { mac: { header: "sentry-hook-signature" } },
|
|
43
|
+
key: { name: { header: "sentry-hook-resource" }, action: { json: "action" } },
|
|
44
|
+
delivery: { header: "request-id" },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
declare module "@jr2/orchestrator/hooks" {
|
|
48
|
+
interface HookKinds {
|
|
49
|
+
sentry: typeof sentry;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `declare module` block adds the kind to the types of the handlers. A handler for an event key that the kind does not
|
|
55
|
+
give does not compile.
|
|
56
|
+
|
|
57
|
+
## Write the Workflow of one alert
|
|
58
|
+
|
|
59
|
+
Save `workflows/triage.ts`. Its door takes the issue id and the title of one alert:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { assign } from "xstate";
|
|
63
|
+
import { z } from "zod";
|
|
64
|
+
import { defineEvent, jr2Setup } from "@jr2/orchestrator";
|
|
65
|
+
|
|
66
|
+
// The two answers of the person on call.
|
|
67
|
+
const take = defineEvent({
|
|
68
|
+
name: "take",
|
|
69
|
+
description: "I take this alert.",
|
|
70
|
+
input: z.object({ who: z.string().min(1) }),
|
|
71
|
+
});
|
|
72
|
+
const dismiss = defineEvent({
|
|
73
|
+
name: "dismiss",
|
|
74
|
+
description: "This alert needs no work. Say why.",
|
|
75
|
+
input: z.object({ reason: z.string().min(1) }),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
type Context = { issue: string; title: string; who?: string };
|
|
79
|
+
|
|
80
|
+
// One run for each alert. The run waits until a person takes the alert or dismisses it.
|
|
81
|
+
export const machine = jr2Setup({
|
|
82
|
+
types: {} as { input: { issue: string; title: string }; context: Context },
|
|
83
|
+
events: [take, dismiss],
|
|
84
|
+
}).createMachine({
|
|
85
|
+
id: "triage",
|
|
86
|
+
// The door: the alerts Pool sends this input for each alert.
|
|
87
|
+
input: z.object({ issue: z.string(), title: z.string() }),
|
|
88
|
+
context: ({ input }) => ({ issue: input.issue, title: input.title }),
|
|
89
|
+
initial: "waiting",
|
|
90
|
+
states: {
|
|
91
|
+
waiting: {
|
|
92
|
+
invoke: {
|
|
93
|
+
src: "gate",
|
|
94
|
+
input: ({ context }) => ({ meta: { issue: context.issue, title: context.title } }),
|
|
95
|
+
},
|
|
96
|
+
on: {
|
|
97
|
+
take: { target: "taken", actions: assign({ who: ({ event }) => event.who }) },
|
|
98
|
+
dismiss: "dismissed",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
taken: { type: "final" },
|
|
102
|
+
dismissed: { type: "final" },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Write the standing Pool
|
|
108
|
+
|
|
109
|
+
Save `workflows/alerts.ts`:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { z } from "zod";
|
|
113
|
+
import { carry, dispatch, pool, skip, source } from "@jr2/orchestrator";
|
|
114
|
+
import type {} from "../hooks/sentry.ts";
|
|
115
|
+
|
|
116
|
+
// The door of the triage Workflow: each alert is one run of it.
|
|
117
|
+
const triageDoor = z.object({ issue: z.string(), title: z.string() });
|
|
118
|
+
|
|
119
|
+
// A push Source: each Hook delivery carries an alert, and the Pool holds it until a slot is free.
|
|
120
|
+
const alerts = source({
|
|
121
|
+
item: z.object({ issue: z.string(), title: z.string(), level: z.string() }),
|
|
122
|
+
hooks: {
|
|
123
|
+
sentry: {
|
|
124
|
+
"issue.created": ({ payload }) => {
|
|
125
|
+
const issue = payload.data.issue;
|
|
126
|
+
if (issue.level !== "error" && issue.level !== "fatal") return skip(`level is ${issue.level}`);
|
|
127
|
+
return carry({ issue: issue.id, title: issue.title, level: issue.level });
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// A standing Pool of runs: one triage run for each alert, at most 8 live at once.
|
|
134
|
+
export const machine = pool(dispatch("triage", { input: triageDoor, wait: true }), {
|
|
135
|
+
id: "alerts",
|
|
136
|
+
standing: true,
|
|
137
|
+
source: alerts,
|
|
138
|
+
itemId: (alert) => `sentry-${alert.issue}`,
|
|
139
|
+
cap: 8,
|
|
140
|
+
itemInput: (alert) => ({ issue: alert.issue, title: alert.title }),
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- The Source has `item` and `hooks`, and no read: it is a push Source. Its handler gives `carry(item)` for an alert of
|
|
145
|
+
the level `error` or `fatal`, and `skip(reason)` for the other levels.
|
|
146
|
+
- The worker is `dispatch("triage", { input, wait: true })`, so each alert is a run of its own of `triage`.
|
|
147
|
+
- `itemId` is the key of the run. A second delivery of the same alert finds the run of the first one, and starts no
|
|
148
|
+
second run.
|
|
149
|
+
- `standing: true`: jr2 starts the run of the Pool, and keeps one live run of it.
|
|
150
|
+
|
|
151
|
+
## Declare the Hook
|
|
152
|
+
|
|
153
|
+
Add the Hook to `jr2.config.ts`, and its secret to the `secrets` list:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { defineConfig, secret } from "@jr2/orchestrator";
|
|
157
|
+
import { sentry } from "./hooks/sentry.ts";
|
|
158
|
+
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
// The Hook's secret is an entry of the list. jr2 up places SENTRY_HOOK in the Instance Effector.
|
|
161
|
+
secrets: [{ name: "SENTRY_HOOK", value: process.env.SENTRY_HOOK }],
|
|
162
|
+
hooks: {
|
|
163
|
+
// POST /hooks/sentry: the alert service signs each delivery with SENTRY_HOOK. The Hook brings
|
|
164
|
+
// items to the Pool of the alerts Workflow, through its source Gate, and to nothing more.
|
|
165
|
+
sentry: sentry({ secret: secret("SENTRY_HOOK"), may: { answer: ["alerts"] } }),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`may.answer` names `alerts`, because the Hook brings items to the Pool through the Pool's `source` Gate. Put the secret
|
|
171
|
+
in `.env`:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
SENTRY_HOOK=<secret>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`<secret>` is a long random value. Give the alert service the same value.
|
|
178
|
+
|
|
179
|
+
## Converge
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
jr2 up
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
…
|
|
187
|
+
standing: sentry → alerts → triage
|
|
188
|
+
…
|
|
189
|
+
secret SENTRY_HOOK: an Effect secret in the Instance Effector (hook sentry)
|
|
190
|
+
the Instance Effector: holds SENTRY_HOOK
|
|
191
|
+
…
|
|
192
|
+
standing alerts → triage: live, run 0d08bbbc-d34e-47fb-9b4b-8e788e328eb4, 0 held items
|
|
193
|
+
…
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`jr2 up` puts the secret in the [Instance Effector](../reference/glossary.md#instance-effector) only, which checks each
|
|
197
|
+
signature. jr2 starts the run of `alerts` at this publish. Your run id is different.
|
|
198
|
+
|
|
199
|
+
## Set up the alert service
|
|
200
|
+
|
|
201
|
+
The address of the Hook is `POST /hooks/sentry` on the Orchestrator. `jr2 up` makes the Service `jr2-orchestrator` on
|
|
202
|
+
port 4000, with no address outside the cluster. Give the Service an address that the alert service can reach, such as an
|
|
203
|
+
ingress. Then give the service the URL `https://<host>/hooks/sentry` and the secret.
|
|
204
|
+
|
|
205
|
+
## Send a test delivery
|
|
206
|
+
|
|
207
|
+
You can sign a delivery yourself. Forward a port to the Orchestrator, and keep this command open:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
kubectl -n <namespace> port-forward svc/jr2-orchestrator 4000:4000
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
In a second terminal, sign a body with the secret and send it:
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
BODY='{"action":"created","data":{"issue":{"id":"4505","title":"TypeError in checkout","level":"error"}}}'
|
|
217
|
+
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SENTRY_HOOK" | sed 's/^.* //')
|
|
218
|
+
curl -s -X POST http://localhost:4000/hooks/sentry \
|
|
219
|
+
-H "Content-Type: application/json" \
|
|
220
|
+
-H "sentry-hook-resource: issue" \
|
|
221
|
+
-H "request-id: test-1" \
|
|
222
|
+
-H "sentry-hook-signature: $SIG" \
|
|
223
|
+
-d "$BODY"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
{"delivery":"test-1","seen":false,"delivered":[],"refused":[],"ignored":0,"sources":[{"pool":"0d08bbbc-d34e-47fb-9b4b-8e788e328eb4","workflow":"alerts","source":"source","verdict":"held","key":"sentry-4505"}]}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The Pool holds the alert, and starts a run of `triage` for it when a slot is free. Send the same command again. The
|
|
231
|
+
answer has `"seen":true`, because the Hook got the id `test-1` before, and the Pool starts no second run.
|
|
232
|
+
|
|
233
|
+
## Follow the alerts
|
|
234
|
+
|
|
235
|
+
[`jr2 runs`](../reference/cli.md#runs) lists the run of `triage` for the alert.
|
|
236
|
+
[`jr2 status <run id>`](../reference/cli.md#status) of that run prints, in its `dispatched by:` line, the run of the
|
|
237
|
+
Pool, the key `sentry-4505`, and the Hook delivery that carried the alert. The person on call answers its Gate:
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
jr2 send <run id> --gate waiting --event take --input '{"who":"<you>"}'
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`<run id>` is the id of the `triage` run, and `<you>` is your name. To see what the door of the Hook did with each
|
|
244
|
+
delivery that jr2 keeps (`runs.retain`, 30 days by default), print its log:
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
jr2 hooks sentry
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
To stop the alerts for a time, cancel the run of `alerts`. A cancel of a standing Pool is a pause: the Hook's answer has
|
|
251
|
+
the verdict `paused`, and the Pool holds nothing. [`jr2 run alerts`](../reference/cli.md#run) resumes it.
|
|
252
|
+
|
|
253
|
+
## Rules
|
|
254
|
+
|
|
255
|
+
- `pool()` refuses a Pool of runs that does not stand:
|
|
256
|
+
``pool("<id>"): a Pool of runs stands: give it `standing: true`. A Pool of runs that a Caller starts and that ends is not offered: its itemId is a key that lives for runs.retain past the run, so a second batch in that time does no work (ADR-0017)``
|
|
257
|
+
- `pool()` refuses a `dispatch` slot without `wait: true`:
|
|
258
|
+
``pool("<id>"): its dispatch slot for "<workflow>" has no `wait: true` — with `started` alone, the Pool cannot know when a run ends, and `cap` counts nothing (ADR-0017)``
|
|
259
|
+
- `pool()` refuses a `dispatch` slot with a `target`:
|
|
260
|
+
``pool("<id>"): its dispatch slot for "<workflow>" has a `target` — a Pool of runs on another Orchestrator is not designed: remove the target (ADR-0017)``
|
|
261
|
+
- `pool()` refuses a Pool of runs without `cap`:
|
|
262
|
+
``pool("<id>"): a Pool of runs states its `cap`, a whole number above 0: the most live runs at once (ADR-0017)``
|
|
263
|
+
- `pool()` refuses a push Source on a Pool that does not stand:
|
|
264
|
+
`pool("<id>"): a push Source needs a standing Pool of runs: a Pool that ends when it drains would end at once (ADR-0017)`
|
|
265
|
+
- `jr2 up` refuses a standing Pool that is not the top Machine of its Workflow:
|
|
266
|
+
`refusing: workflow "<workflow>": machine "<machine>" composes the standing Pool "<id>" — a standing Pool is the top Machine of its Workflow, because jr2 keeps one run of each standing Workflow: move the Pool to a file of its own in workflows/ (ADR-0017)`
|
|
267
|
+
- `jr2 up` warns when two standing Pools dispatch to one Workflow:
|
|
268
|
+
``warning: the standing Pools "<pool>" and "<pool>" each dispatch to "<workflow>": `cap` is for each Pool, so one item that two Sources carry can take a slot in each, and the caps add (ADR-0017)``
|
|
269
|
+
- The Orchestrator refuses `jr2 run` of a standing Workflow whose run is live, with the status code `409`:
|
|
270
|
+
``"<workflow>" is a standing Workflow, and its run <run id> is live: jr2 keeps its run, and `jr2 run` only resumes a paused one (ADR-0017)``
|
|
271
|
+
- The Orchestrator refuses a `dispatch` to a standing Workflow:
|
|
272
|
+
`"<workflow>" is a standing Workflow: jr2 starts its run, and a dispatch cannot (ADR-0017)`
|
|
273
|
+
- When the Pool holds `hold` items (1000 by default), the door answers 429 with the verdict `hold-full`, and the service
|
|
274
|
+
can send the delivery again.
|
|
275
|
+
- While the Pool waits for the back-off of a hand-over, it has no live run, and the door answers 503.
|
|
276
|
+
- A Hook whose `may.answer` does not name the Workflow of the Pool brings it nothing.
|
|
277
|
+
|
|
278
|
+
## Read next
|
|
279
|
+
|
|
280
|
+
- [Pools and Sources](../concepts/pools-and-sources.md)
|
|
281
|
+
- [Callers and Hooks](../concepts/callers-and-hooks.md)
|
|
282
|
+
- [Write a Hook kind](write-a-hook-kind.md), for a service that `webhook()` cannot read
|
|
283
|
+
- [Start a run from a run](start-a-run-from-a-run.md)
|
|
284
|
+
- Reference: [`pool()`](../reference/authoring-surface/pool.md), [`source()`](../reference/authoring-surface/source.md),
|
|
285
|
+
[`carry()`](../reference/authoring-surface/carry.md), [`skip()`](../reference/authoring-surface/skip.md),
|
|
286
|
+
[`dispatch()`](../reference/authoring-surface/dispatch.md) and [`webhook()`](../reference/hooks.md#webhook)
|
|
287
|
+
- [ADR-0017](../reference/decisions/0017-pool-over-a-source-port.md): a standing Pool, and a Hook that brings items to
|
|
288
|
+
it
|
|
289
|
+
- [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): a
|
|
290
|
+
Hook, and `webhook()`
|