@jr2/orchestrator 0.6.0 → 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 +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- 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 +24 -8
- 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/fix-a-failed-converge.md +18 -6
- 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 +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- 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 +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- 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 +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- 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 +56 -1
- 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 +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- 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-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- 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-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- 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 +1 -1
- 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-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-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 +16 -7
- 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 +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- 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 +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- 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 +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- 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 +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- 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 +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- 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 +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- 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 -18
- 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 +4 -3
- 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 +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- 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 +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- 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 +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- 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 +17 -13
- 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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- 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 +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -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 +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- 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
|
@@ -181,7 +181,7 @@ jr2 run issues --input '{"workers":2}'
|
|
|
181
181
|
```text
|
|
182
182
|
…
|
|
183
183
|
→ context kind-jr2 / namespace my-instance
|
|
184
|
-
run
|
|
184
|
+
run 7c248a11-445a-4123-8ce2-834cb57706d7 (issues) — attached
|
|
185
185
|
→ active "discovering"
|
|
186
186
|
→ active "discovering"
|
|
187
187
|
→ active "discovering"
|
|
@@ -206,7 +206,8 @@ jr2 status <run id>
|
|
|
206
206
|
```text
|
|
207
207
|
…
|
|
208
208
|
→ context kind-jr2 / namespace my-instance
|
|
209
|
-
{"runId":"
|
|
209
|
+
{"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues",…,"gates":[{"gate":"source",…},{"gate":"issue-3.body.review",…},{"gate":"issue-2.body.review",…}]}
|
|
210
|
+
…
|
|
210
211
|
```
|
|
211
212
|
|
|
212
213
|
In the JSON that it prints, `gates` holds each open Gate of the run:
|
|
@@ -234,7 +235,7 @@ jr2 send <run id> --gate issue-2.body.review --event approve
|
|
|
234
235
|
```text
|
|
235
236
|
…
|
|
236
237
|
→ context kind-jr2 / namespace my-instance
|
|
237
|
-
delivered approve to gate "issue-2.body.review" on
|
|
238
|
+
delivered approve to gate "issue-2.body.review" on 7c248a11-445a-4123-8ce2-834cb57706d7
|
|
238
239
|
```
|
|
239
240
|
|
|
240
241
|
The Pool then calls `next` again, and starts a worker for the next issue.
|
|
@@ -255,12 +256,18 @@ jr2 send <run id> --gate source --event issues_changed
|
|
|
255
256
|
```text
|
|
256
257
|
…
|
|
257
258
|
→ context kind-jr2 / namespace my-instance
|
|
258
|
-
delivered issues_changed to gate "source" on
|
|
259
|
+
delivered issues_changed to gate "source" on 7c248a11-445a-4123-8ce2-834cb57706d7
|
|
259
260
|
```
|
|
260
261
|
|
|
261
|
-
Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A
|
|
262
|
+
Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A script can send the same
|
|
262
263
|
event with [`POST /runs/:runId/gates/:gate/events`](../reference/http-api.md#post-runs-runid-gates-gate-events).
|
|
263
264
|
|
|
265
|
+
A [Hook](../reference/glossary.md#hook) can tell the Pool too. Give the Source a `hooks` handler for the forge's event,
|
|
266
|
+
for example `github` and `issues.labeled`, that gives [`reread()`](../reference/authoring-surface/reread.md), or
|
|
267
|
+
[`skip(reason)`](../reference/authoring-surface/skip.md) for a label that is not `jr2`. Then each delivery of the Hook
|
|
268
|
+
makes the Pool call `next` at once. [Pools and Sources](../concepts/pools-and-sources.md#a-hook-reaches-a-source) tells
|
|
269
|
+
how.
|
|
270
|
+
|
|
264
271
|
## The end of the run
|
|
265
272
|
|
|
266
273
|
When the last worker finishes and no issue is ready, the run ends. In the first terminal, `jr2 run` prints the last
|
|
@@ -270,7 +277,7 @@ state of the Pool, then the status of the run as JSON:
|
|
|
270
277
|
…
|
|
271
278
|
→ active "discovering"
|
|
272
279
|
→ done "drained"
|
|
273
|
-
{"runId":"
|
|
280
|
+
{"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues","instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4","startedAt":"2026-11-06T19:20:02.886Z","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"status":"done","value":"drained","context":{"runInput":{"workers":2,"instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4"},"cap":2,"active":[],"items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{},"held":[],"ready":[],"claimed":{},"again":[],"latch":false,"taken":3,"carried":[],"refused":[],"status":"drained"},"children":[],"waiting":[],"held":[],"answers":[{"gate":"issue-2.body.review","type":"approve","at":"2026-11-06T19:21:49.638Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"source","type":"issues_changed","at":"2026-11-06T19:21:57.842Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-3.body.review","type":"approve","at":"2026-11-06T19:22:03.493Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-1.body.review","type":"approve","at":"2026-11-06T19:22:33.752Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}],"assembly":"f5bb024883b4","output":{"status":"drained","items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{}},"worktrees":[{"slot":"app","path":"/work/app/task","branch":"jr2/issue-3"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-2"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-1"}]}
|
|
274
281
|
```
|
|
275
282
|
|
|
276
283
|
In the JSON, `context.items` holds the output of each worker that finished, by item id. `context.failures` holds the
|
|
@@ -306,9 +313,8 @@ In the JSON, `context.items` holds the output of each worker that finished, by i
|
|
|
306
313
|
starts with ``task: no `branch` on the door and no Instance ID to derive one from``.
|
|
307
314
|
- Without `cap`, one worker runs at a time. Each `task` worker has its own
|
|
308
315
|
[Workspace](../reference/glossary.md#workspace). Thus the run has `cap` Sandboxes or fewer at one time.
|
|
309
|
-
- The Pool takes the `wake` event
|
|
310
|
-
|
|
311
|
-
`error: gate "source" is open, and the run does not take "<event>" now: no writing of it would move the run with this payload — not delivered (takes now: nothing)`.
|
|
316
|
+
- The Pool takes the `wake` event at any time while the run is open. While it waits, it calls `next` at once. While
|
|
317
|
+
`next` runs, it calls `next` once more after that call, so that the change is not lost.
|
|
312
318
|
- `onDrained` takes only `"final"`: the run ends.
|
|
313
319
|
|
|
314
320
|
## Read next
|
|
@@ -360,6 +360,8 @@ Dial. Thus:
|
|
|
360
360
|
- `jr2 up` gives each pod the model key of the providers that the definitions of its Agents name. For an Agent in a
|
|
361
361
|
Sandbox, use a Dial model of a provider that a definition in that Sandbox names. For a Menu-only Agent, use a provider
|
|
362
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.
|
|
363
365
|
|
|
364
366
|
## Continue a conversation
|
|
365
367
|
|
|
@@ -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()`
|
|
@@ -117,7 +117,7 @@ back to `listening`.
|
|
|
117
117
|
|
|
118
118
|
## See the runs
|
|
119
119
|
|
|
120
|
-
[`jr2 runs`](../reference/cli.md#runs) lists the two runs. The run of `triage` has `dispatchedBy
|
|
120
|
+
[`jr2 runs`](../reference/cli.md#runs) lists the two runs. The run of `triage` has `dispatchedBy`:
|
|
121
121
|
|
|
122
122
|
```sh
|
|
123
123
|
jr2 runs
|
|
@@ -126,29 +126,25 @@ jr2 runs
|
|
|
126
126
|
```text
|
|
127
127
|
…
|
|
128
128
|
→ context kind-jr2 / namespace my-instance
|
|
129
|
-
|
|
130
|
-
intake (machine)
|
|
131
|
-
ping (machine)
|
|
132
|
-
triage (machine)
|
|
133
|
-
[{"runId":"91ca85e1-5092-4e10-9ec6-d14e37811ca1","workflow":"intake","instanceId":"e51d124b-88e5-461c-b464-6a3660ee13dc","status":"active","value":"listening","context":{"started":["45b52048-0848-4015-ad56-6ff8ae33d817"],"request":"The greeting looks wrong sometimes.","last":{"outcome":"started","run":"45b52048-0848-4015-ad56-6ff8ae33d817"}},"children":[],"waiting":[],"held":[],"assembly":"3229103a2952","dispatched":[{"slot":"triage","run":"45b52048-0848-4015-ad56-6ff8ae33d817","at":"2026-10-24T06:20:43.465Z"}]},{"runId":"45b52048-0848-4015-ad56-6ff8ae33d817","dispatchedBy":{"run":"91ca85e1-5092-4e10-9ec6-d14e37811ca1","slot":"triage","effect":"08423831-751f-41e5-9d93-1fa063faaf61","depth":1},"workflow":"triage","instanceId":"a2ad6e54-6a52-45c2-81f4-89f408a27d1f","status":"active","value":"triaging","context":{"request":"The greeting looks wrong sometimes.","exchange":[]},"children":[],"waiting":[],"held":[],"assembly":"3229103a2952"}]
|
|
129
|
+
[{"runId":"e8a74de7-f3f3-4621-876e-e5f117af5fff","workflow":"triage","status":"active","startedBy":{"kind":"run","run":"f45bed48-9950-4e63-9cda-4df368e9a62f","slot":"triage"},"startedAt":"2026-11-06T19:23:24.220Z","assembly":"19d0bf0cbe10","openGates":0,"dispatchedBy":{"run":"f45bed48-9950-4e63-9cda-4df368e9a62f","slot":"triage","effect":"c63bce4d-e353-4fee-9035-6b13dfac4917","depth":1}},{"runId":"f45bed48-9950-4e63-9cda-4df368e9a62f","workflow":"intake","status":"active","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"startedAt":"2026-11-06T19:23:21.744Z","assembly":"19d0bf0cbe10","openGates":1}]
|
|
134
130
|
```
|
|
135
131
|
|
|
136
|
-
The first line, `…`, is the line `→ .env:`, with the keys that the CLI took from `.env`.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`intake`
|
|
132
|
+
The first line, `…`, is the line `→ .env:`, with the keys that the CLI took from `.env`. In `dispatchedBy`, `run` is the
|
|
133
|
+
run that started it, `slot` is the slot of the Effect, and `effect` is the id of that Effect in the record of the run
|
|
134
|
+
that started it. jr2 uses it as the key of the start, so that a start that jr2 sends again starts no second run. `depth`
|
|
135
|
+
is the number of runs before it in the chain. The list gives the newest start first, so `triage` comes before `intake`.
|
|
136
|
+
`jr2 status` of `intake` shows `dispatched`: each run that it started. Your ids, your time and your hash are different.
|
|
141
137
|
|
|
142
138
|
[`jr2 status`](../reference/cli.md#status) prints the link in both directions, on stderr. For the run of `intake`:
|
|
143
139
|
|
|
144
140
|
```text
|
|
145
|
-
dispatched:
|
|
141
|
+
dispatched: e8a74de7-f3f3-4621-876e-e5f117af5fff (triage)
|
|
146
142
|
```
|
|
147
143
|
|
|
148
144
|
For the run of `triage`:
|
|
149
145
|
|
|
150
146
|
```text
|
|
151
|
-
dispatched by:
|
|
147
|
+
dispatched by: f45bed48-9950-4e63-9cda-4df368e9a62f (triage), depth 1
|
|
152
148
|
```
|
|
153
149
|
|
|
154
150
|
The two runs are independent. A `CANCEL` of `intake` does not cancel the run of `triage`. Answer the `clarify` Gate of
|
|
@@ -174,7 +170,9 @@ To start a run on the Orchestrator of a different [Instance](../reference/glossa
|
|
|
174
170
|
definition: `target: { url: "https://ops.example.com", token: secret("ops-instance") }`.
|
|
175
171
|
|
|
176
172
|
- `url` is the origin of the other Orchestrator: the scheme, the host and the port. It has no path.
|
|
177
|
-
- `token` names a secret that holds the
|
|
173
|
+
- `token` names a secret that holds the token of a [Caller](../reference/glossary.md#caller) of the other Instance, with
|
|
174
|
+
the `start` scope for the Workflow, and also `read` when `wait` is `true`. A named Caller token of the other Instance
|
|
175
|
+
that has only these scopes is the safest ([Give a Caller a scope](give-a-caller-a-scope.md)). Put the secret in the
|
|
178
176
|
[`secrets`](../reference/config.md#secrets) list of `jr2.config.ts`, as for an `http` Effect. See
|
|
179
177
|
[Run a command or call a URL with an Effect](run-a-command-or-call-a-url.md).
|
|
180
178
|
- The Effect runs in the [Instance Effector](../reference/glossary.md#instance-effector), which holds the token.
|
|
@@ -201,7 +199,7 @@ the token together:
|
|
|
201
199
|
- The `url` of a target is an origin. The refusal starts with
|
|
202
200
|
`dispatch("<name>", { target }): "<url>" is not an origin — write the scheme and the host, with a port if it is not the scheme's, and no path, query or userinfo`.
|
|
203
201
|
- The `token` of a target is a secret:
|
|
204
|
-
`dispatch("<name>", { target }): the token is a secret("<name>") holding the target
|
|
202
|
+
`dispatch("<name>", { target }): the token is a secret("<name>") holding a Caller token of the target with start for the Workflow (ADR-0071)`
|
|
205
203
|
- An Open target is the whole target, not its secret:
|
|
206
204
|
``dispatch("<name>", { target }): secret(open) in a target — the target is the Open part, not its secret: leave the whole target Open (`target: open`) and let the composer bind its url and token together (ADR-0071)``
|
|
207
205
|
- With no target, the Assembly must have the Workflow. `jr2 up` stops:
|
|
@@ -214,11 +212,15 @@ the token together:
|
|
|
214
212
|
`warning: workflow "<workflow>" dispatches "<wrapper>/<name>": the Assembly has no Chart file workflows/<wrapper>/<name>.chart.json`.
|
|
215
213
|
- For a target, each problem that `jr2 up` finds is a warning. For example, when the other Orchestrator does not answer:
|
|
216
214
|
`warning: dispatch "<name>" at <url>: no answer from this host (<error>) — the other Instance may be down, or reached only from inside its cluster; while no door answers, the dispatch settles "unreachable" (ADR-0071)`
|
|
215
|
+
- When the input does not agree with the `input` schema of the slot, the invoke ends with an error, and no run starts.
|
|
216
|
+
The error starts with `dispatch "<slot>": its input does not match the slot's input schema —`. Before that, jr2 asks
|
|
217
|
+
for a run under the key of the start, with no target and with one. When a run has the key, the Effect gives that run,
|
|
218
|
+
and does not read the input. With a target, jr2 sends the key and no input, so the other Orchestrator starts nothing.
|
|
217
219
|
- When the Orchestrator starts no run, the Effect ends with `refused`, not with an error. `report` holds the lines of
|
|
218
220
|
the refusal, and `status` is its status code:
|
|
219
221
|
- `400`: the Workflow does not take the input.
|
|
220
222
|
- `404`: the current Assembly has no Workflow of that name.
|
|
221
|
-
- `409`: the named Chart cannot run now
|
|
223
|
+
- `409`: the named Chart cannot run now.
|
|
222
224
|
- `413`: the start is larger than the Effector reads.
|
|
223
225
|
- `422`: the door refuses the Chart in the input.
|
|
224
226
|
- `0`: the Effector refused the start before it sent it.
|
|
@@ -29,9 +29,9 @@ jr2 run ping --input '{"message":"hi"}'
|
|
|
29
29
|
|
|
30
30
|
```text
|
|
31
31
|
→ context <context> / namespace <namespace>
|
|
32
|
-
run
|
|
32
|
+
run 17b959ed-5200-4d98-adae-403dd706277b (ping) — attached
|
|
33
33
|
→ done "done"
|
|
34
|
-
{"runId":"
|
|
34
|
+
{"runId":"17b959ed-5200-4d98-adae-403dd706277b","workflow":"ping","instanceId":"32e6e954-8cc5-4b52-b6df-6c9ef6533192","startedAt":"2026-11-06T19:16:12.219Z","status":"done","value":"done","context":{"message":"hi","reply":"pong: hi"},"children":[],"waiting":[],"held":[],"assembly":"50e6f81e3bfb","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Your run id, your instance id and your `assembly` hash are different. When your Instance folder has a `.env` file, the
|
|
@@ -47,7 +47,7 @@ output starts with one more line, `→ .env:`, with the keys that the CLI took f
|
|
|
47
47
|
- `jr2 run` stays attached until the run ends. A run that waits at a [Gate](../reference/glossary.md#gate) does not end,
|
|
48
48
|
so `jr2 run` waits too. To answer the Gate, use a second terminal. See [Answer a Gate](answer-a-gate.md).
|
|
49
49
|
- When you stop `jr2 run`, for example with Ctrl-C, the run continues in the Orchestrator. To attach again, run
|
|
50
|
-
`jr2
|
|
50
|
+
`jr2 watch <run id>`.
|
|
51
51
|
- `jr2 run` exits with `0` only when the run ends with the status `done`. It exits with `1` for each other end:
|
|
52
52
|
`failed`, `error`, `cancelled`, `lost` and `drifted`, and also when the run stops before it ends. For a run that ends
|
|
53
53
|
`failed`, the line `failed: <reason>` on stderr gives the reason.
|
|
@@ -65,15 +65,15 @@ jr2 run ping --input '{"message":"hi"}' --detach
|
|
|
65
65
|
{"runId":"fc8b82d1-bd73-498d-9057-ef13cf962240"}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Use the run id, or its first 4 characters or more, with `jr2 status`, `jr2 logs` and `jr2 send`.
|
|
68
|
+
Use the run id, or its first 4 characters or more, with `jr2 status`, `jr2 watch`, `jr2 logs` and `jr2 send`.
|
|
69
69
|
|
|
70
70
|
## Start a run from the Console
|
|
71
71
|
|
|
72
|
-
You need
|
|
73
|
-
|
|
72
|
+
You need a credential with the `start` scope for the Workflow, such as the Instance token, to start a run from the
|
|
73
|
+
Console. [Open the Console](open-the-console.md) tells how to open it, how to get the token and how to log in.
|
|
74
74
|
|
|
75
75
|
1. Open the Console.
|
|
76
|
-
2. Type the
|
|
76
|
+
2. Type the token in the `Caller token` box in the nav, or click `log in`.
|
|
77
77
|
3. Push Enter. The badge beside the box shows `…`, then `live`.
|
|
78
78
|
4. In the Fleet rail on the left, find the Workflow. Click `start` beside its name.
|
|
79
79
|
5. Fill in the form. When the Machine declares its input, the form has one field for each property of the input. A field
|
|
@@ -94,7 +94,10 @@ cannot start one.
|
|
|
94
94
|
## Start a run with the HTTP API
|
|
95
95
|
|
|
96
96
|
Send [`POST /workflows/:name/runs`](../reference/http-api.md#post-workflows-name-runs) to the Orchestrator, with the
|
|
97
|
-
|
|
97
|
+
token of a [Caller](../reference/glossary.md#caller) that has the `start` scope for the Workflow. The body is the run
|
|
98
|
+
input. A script, a webhook handler or a CI job can start a run this way. A CI job uses a named Caller token, not the
|
|
99
|
+
Instance token: [Give a Caller a scope](give-a-caller-a-scope.md) makes it. The example below uses the Instance token,
|
|
100
|
+
because it can do everything.
|
|
98
101
|
|
|
99
102
|
`jr2 up` makes the Service `jr2-orchestrator` on port 4000, with no address outside the cluster. Thus a caller outside
|
|
100
103
|
the cluster uses a port forward, or an address that you make for the Service.
|
|
@@ -144,13 +147,14 @@ curl -s http://localhost:4000/workflows/ping -H "Accept: application/json"
|
|
|
144
147
|
```
|
|
145
148
|
|
|
146
149
|
[`GET /workflows`](../reference/http-api.md#get-workflows) gives all the Workflows, each with where it comes from, and
|
|
147
|
-
the named Charts of each Chart wrapper. It also needs no token. [`jr2
|
|
148
|
-
|
|
150
|
+
the named Charts of each Chart wrapper. It also needs no token. [`jr2 workflows`](../reference/cli.md#workflows) prints
|
|
151
|
+
the same list.
|
|
149
152
|
|
|
150
153
|
### Use the CLI without the Instance folder
|
|
151
154
|
|
|
152
155
|
The CLI can also speak to an Orchestrator at a URL. Then it does not look for the Instance folder or for a kubectl
|
|
153
|
-
context. Give the URL in `--url`, or in `JR2_URL`. Give the Instance token
|
|
156
|
+
context. Give the URL in `--url`, or in `JR2_URL`. Give a credential in `JR2_TOKEN`: the Instance token, or the token of
|
|
157
|
+
a [Caller](../reference/glossary.md#caller) with the `start` scope ([Give a Caller a scope](give-a-caller-a-scope.md)):
|
|
154
158
|
|
|
155
159
|
```sh
|
|
156
160
|
JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":"hi"}' --detach
|
|
@@ -177,10 +181,12 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
|
|
|
177
181
|
`the body must be a JSON object`.
|
|
178
182
|
- `jr2 run` starts no run when `--input` is not valid JSON. It prints `error: <message>`, where `<message>` is the
|
|
179
183
|
message of the JSON parser, and exits with `1`.
|
|
180
|
-
- The Orchestrator refuses a start
|
|
184
|
+
- The Orchestrator refuses a start with no valid credential, with the status code `401`: `unauthorized`.
|
|
181
185
|
- The Orchestrator refuses a start with the token of a [Sandbox](../reference/glossary.md#sandbox), with the status code
|
|
182
|
-
`403`: `
|
|
183
|
-
[Agent](../reference/glossary.md#agent) cannot start a run.
|
|
186
|
+
`403`: `this route needs a Caller, and a Sandbox token is not one: no Agent reaches run state, run control or a Gate`.
|
|
187
|
+
Thus an [Agent](../reference/glossary.md#agent) cannot start a run.
|
|
188
|
+
- The Orchestrator refuses a start from a Caller that does not have the `start` scope for the Workflow, with the status
|
|
189
|
+
code `403`: `the Caller <via>/<name> needs the "start" scope for Workflow "<workflow>" (ADR-0078)`
|
|
184
190
|
- `jr2 run` outside an Instance folder, with no `--url` and no `JR2_URL`, starts no run:
|
|
185
191
|
`error: not inside a jr2 instance — no jr2.config.ts found walking up from cwd`
|
|
186
192
|
- `jr2 run` starts no run when the namespace holds no Instance:
|
|
@@ -189,9 +195,9 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
|
|
|
189
195
|
``error: no kube context — `kind create cluster` (or point kubectl at one), then `jr2 up` ``
|
|
190
196
|
- When the connection to the Orchestrator ends before the run ends, for example when the Orchestrator stops, `jr2 run`
|
|
191
197
|
prints `error: <message>` and exits with `1`. The run continues when the Orchestrator starts again. To attach again,
|
|
192
|
-
run `jr2
|
|
198
|
+
run `jr2 watch <run id>`.
|
|
193
199
|
- When the Orchestrator holds the run but did not continue it, `jr2 run` exits with `1`:
|
|
194
|
-
``run <run id> is parked: stopped before it settled — `jr2
|
|
200
|
+
``run <run id> is parked: stopped before it settled — `jr2 watch <run id>` follows it once restored``
|
|
195
201
|
|
|
196
202
|
## Read next
|
|
197
203
|
|
|
@@ -201,7 +207,7 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
|
|
|
201
207
|
Orchestrator.
|
|
202
208
|
- [ADR-0033](../reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md): the input that a Machine
|
|
203
209
|
declares.
|
|
204
|
-
- [ADR-
|
|
205
|
-
|
|
210
|
+
- [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): the
|
|
211
|
+
Callers, and the `start` scope.
|
|
206
212
|
- [Answer a Gate](answer-a-gate.md)
|
|
207
213
|
- [Watch a run](watch-a-run.md)
|