@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
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
An Effect is work a Machine does outside itself. A Machine already runs Agents and Gates as actors (ADR-0049); work such
|
|
4
4
|
as running the tests had no place of its own, so an author either asked an Agent to do it (and trusted its word) or
|
|
5
|
-
wrote a raw xstate promise that a restart ran again. jr2 builds
|
|
5
|
+
wrote a raw xstate promise that a restart ran again. jr2 builds five kinds: `command`, run a program in the Workspace;
|
|
6
6
|
`http`, call a URL — with a JSON or text body, or a Worktree file as its body; `push`, push a Worktree's branch to its
|
|
7
|
-
Repo Slot's Binding;
|
|
8
|
-
([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
(
|
|
12
|
-
the
|
|
13
|
-
|
|
7
|
+
Repo Slot's Binding; `dispatch`, start a run of a registered Workflow, on this Orchestrator or another's
|
|
8
|
+
([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)); and `mcp`, call a tool of an MCP server, or read its
|
|
9
|
+
resource or get its prompt
|
|
10
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). Beside
|
|
11
|
+
them, `worktree()` is a kit Effect on the `command` machinery that cuts a Worktree
|
|
12
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). An Effect that holds a secret runs in an
|
|
13
|
+
Effector ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). A `command` whose env names a
|
|
14
|
+
secret — the credentialed `command` — runs there too, its child confined (below). A `dispatch` with no target runs in
|
|
15
|
+
the Orchestrator's own process, the one Effect that does: it starts the run through the run host and holds no secret.
|
|
14
16
|
|
|
15
17
|
## Decision
|
|
16
18
|
|
|
@@ -19,18 +21,20 @@ Orchestrator's own process, the one Effect that does: it starts the run through
|
|
|
19
21
|
exits, its input persists with the snapshot, a `src` typo is a compile error, and `.provide()` is the unit-test seam.
|
|
20
22
|
The slot carries its parts as plain data — kind, placement, timeout; a `command`'s argv and the secrets its env names;
|
|
21
23
|
an `http` Effect's method, fixed host, the names of the secrets it names and whether its body is a Worktree file; a
|
|
22
|
-
`push`'s `pin`, `sign`, `squash`, `force` and `slot`;
|
|
23
|
-
carries, read without running anything. A packaged Machine may leave an
|
|
24
|
-
`
|
|
25
|
-
(`secret(open)`; never in a url), for the
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(
|
|
24
|
+
`push`'s `pin`, `sign`, `squash`, `force` and `slot`; an `mcp` Effect's operation, server and tool; a cut's `expect` —
|
|
25
|
+
which `jr2 up` prints and the Machine doc carries, read without running anything. A packaged Machine may leave an
|
|
26
|
+
`http` url (whatever its body), a `command`'s argv, or an `mcp` Effect's server or tool Open (`open` in its place),
|
|
27
|
+
and the name of one secret its auth, a header or a command's env names (`secret(open)`; never in a url), for the
|
|
28
|
+
composer to bind once with
|
|
29
|
+
`customize(machine, { effects: { <slot>: { url } | { argv } | { secret } | { server } | { tool } } })` — one object
|
|
30
|
+
with both keys for an Effect with two Open parts
|
|
31
|
+
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)); the slot then carries the
|
|
32
|
+
Effect built again with the bound parts, and an Effect still Open runs nothing: its state's `onError`. An Effect
|
|
33
|
+
written inline (`invoke: { src: http({...}) }`) is refused at build and at load, with the slot form in the message:
|
|
34
|
+
xstate gives it a generated key that moves when a state is renamed, so the ledger, the report and the Machine doc
|
|
35
|
+
would name it by a number, and `customize()` — `provide`, keyed on `actors` — could not reach it. The same rule holds
|
|
36
|
+
for `agent()` and `workspace()` (ADR-0049): a Part is a key of `actors`, and every Effect kind is one (the brand
|
|
37
|
+
`isEffect` reads).
|
|
34
38
|
- **Between a pick and its work: `fromPick`, `paramsOf`, `answer`.** All optional; a plain mapper is legal.
|
|
35
39
|
`fromPick(def, fill)` is the `input` of a state a pick enters: the pick's params flow into the Effect's input by name,
|
|
36
40
|
`fill` returns only what the Machine adds, and the sum is checked against the Effect's input type — the security
|
|
@@ -79,9 +83,10 @@ Orchestrator's own process, the one Effect that does: it starts the run through
|
|
|
79
83
|
`http` whose body is a Worktree file, are placed in that Sandbox Effector — the command runs there with its child
|
|
80
84
|
confined (below). A `dispatch` with no target runs in the Orchestrator's own process, the fourth Placement,
|
|
81
85
|
`orchestrator`, and the one Effect that has it; with a target it runs in the Instance Effector
|
|
82
|
-
([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)).
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)). An `mcp` Effect runs in the Instance Effector,
|
|
87
|
+
because in v1 it calls a remote server only. There is no `placement:` key and no kind per placement: naming a secret
|
|
88
|
+
places a command, the body places an `http`, and the target places a `dispatch` — one name per thing, so there is no
|
|
89
|
+
`upload` kind beside it (ADR-0068).
|
|
85
90
|
- **A `command` that names no secret runs in the Harness container of its Workspace's Sandbox**, as a confined child of
|
|
86
91
|
the Harness process ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)): the
|
|
87
92
|
Agent's user, the Sandbox Image's tools, the same confined env and Stand-ins as the Agent's `bash` (ADR-0059), and no
|
|
@@ -100,17 +105,18 @@ Orchestrator's own process, the one Effect that does: it starts the run through
|
|
|
100
105
|
- **Every outcome is output**: `exited | timeout | killed | unknown` for a `command` (`killed` with the signal that
|
|
101
106
|
ended it; a stopped credentialed command's is `exited` with the stop's signal, D1);
|
|
102
107
|
`response | unreachable | refused | unknown` for an `http` Effect; `pushed | rejected | diverged | unknown` for a
|
|
103
|
-
`push
|
|
104
|
-
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`
|
|
113
|
-
|
|
108
|
+
`push`; `returned | refused | unreachable | input-required | unknown` for an `mcp` Effect. A command not found is
|
|
109
|
+
`exited` 127, and a bad Worktree adds no outcome: a `command` settles `exited` 127 with the reason in its tail, and an
|
|
110
|
+
`http` file body settles `refused`. Every union here is open: `worktree()`, `push`, `command` and `http` each hold a
|
|
111
|
+
stand-in member, so that no author can write an exhaustive check and a later outcome is no break; the `mcp` union
|
|
112
|
+
holds one too. A start over the bytes an Effector reads (ADR-0068) is sent nowhere and settles as its kind's own
|
|
113
|
+
outcome — an `http` `refused`, a `push` `rejected` at stage `start` (nothing pushed), a credentialed `command`
|
|
114
|
+
`exited` 127 with the reason in its tail, the Effector's own form for a command that could not start — since an
|
|
115
|
+
Agent's param (a squash message, an argv part) can reach the bound; a path segment that is `.`, `..` or empty, and a
|
|
116
|
+
file body's path that is a symlink, leaves the Worktree, is absolute, is a FIFO or has more than one link, are
|
|
117
|
+
`refused` with nothing sent — an Agent's param can cause each. `onError` is only a jr2 or config defect: no Workspace,
|
|
118
|
+
a `command` with no `worktree` under Repo Slots, an argv slot with no value, an input that misses the definition's
|
|
119
|
+
schema, no Effector to send to, an Open part nobody bound.
|
|
114
120
|
- **An `http` Effect is a definition plus one function.**
|
|
115
121
|
`http({ method, url, headers?, auth?, input?, request?, body?, timeout?, maxBody?, idempotent? })`. The url's scheme
|
|
116
122
|
and host are literal — a secret, named with `secret("name")`, may sit only in its path, through the `text` template,
|
|
@@ -134,6 +140,40 @@ Orchestrator's own process, the one Effect that does: it starts the run through
|
|
|
134
140
|
refused when the Machine loads ("write one of them", as for `auth` beside an `authorization` header). With neither it
|
|
135
141
|
is `application/octet-stream`. A later body form (a multipart file and fields) is one more body on `http`, never a
|
|
136
142
|
kind.
|
|
143
|
+
- **An `mcp` Effect has the shape of `http`, with a server and a tool in place of a url.**
|
|
144
|
+
`mcp.call(server, tool, { input?, timeout?, maxBody?, idempotent? })`,
|
|
145
|
+
`mcp.read(server, uri, { timeout?, maxBody?, idempotent? })` and
|
|
146
|
+
`mcp.prompt(server, name, { input?, timeout?, maxBody?, idempotent? })`
|
|
147
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)).
|
|
148
|
+
`server` is a name of the `mcp` block of `jr2.config.ts`, so the server's url and its secret are the config's and
|
|
149
|
+
never a param; the secret is the Effect's Effect secret, placed in the Instance Effector (ADR-0068). `server` can be
|
|
150
|
+
Open for each of the three; `tool` for `mcp.call`; a read's `uri` and a prompt's `name` are the author's. `jr2 up`
|
|
151
|
+
refuses an `mcp` Effect whose server the `mcp` block does not declare, or whose server is a stdio server, and prints
|
|
152
|
+
the `mcp` block entry that declares a remote server, and the `customize()` line for an Open server. `input` is the
|
|
153
|
+
schema of the tool's arguments (a prompt's arguments are strings), and the invoke's input is the arguments, which
|
|
154
|
+
`fromPick` fills from a pick's params: an Agent's param reaches the arguments only. An Effect's `timeout` replaces its
|
|
155
|
+
server's `timeout` for that Effect; with none, the server's applies (60 seconds by default). The outcomes:
|
|
156
|
+
`returned { isError, text, structured?, truncated? }` — the server answered the call, `isError` being the tool's own,
|
|
157
|
+
`structured` its `structuredContent`, typed `unknown`, and `truncated` set when the Effector cut `text` or dropped
|
|
158
|
+
`structured` at the bound (ADR-0068), and a reason keeps at most 1 KiB of a server's error message; `refused` — the
|
|
159
|
+
request did not run: the server answered a 3xx or a 4xx status (a 401 or a 403 included) at any step, the handshake
|
|
160
|
+
too, refused the handshake, or answered the request with the JSON-RPC error `-32700`, `-32600`, `-32601` or `-32602`
|
|
161
|
+
(a request it could not read, an unknown method, an unknown tool, bad arguments), or answered a read with `-32002`
|
|
162
|
+
(the resource does not exist); or the Effector sent nothing because the server or its secret is not in its
|
|
163
|
+
`effector.json`, the second fence behind the `jr2 up` refusal; `unreachable` — nothing of the request was sent: the
|
|
164
|
+
name did not resolve, or the connection, TLS or the handshake failed otherwise (a 5xx, the timeout, an answer that is
|
|
165
|
+
not MCP's, a handshake that names an MCP revision the client does not speak); `input-required` — the server asked for
|
|
166
|
+
input, which jr2 does not give, so the Machine routes it: the error `-32042` (URL elicitation required), after which
|
|
167
|
+
the tool did not run, or an `elicitation/create` request, which the client declared no capability for and which comes
|
|
168
|
+
while the tool runs, so the tool may have begun; jr2 does not send the request again, and a Machine that invokes the
|
|
169
|
+
Effect again after a Gate can run a part of the tool two times; `unknown` — the request was sent and no answer came
|
|
170
|
+
(the timeout, a reset, a stream that ended first and that the server did not give again from its last event id), or
|
|
171
|
+
the server answered with a 5xx or with another JSON-RPC error, after which the tool may have acted. `idempotent` is
|
|
172
|
+
the author's mark alone; the server's `idempotentHint` is ignored. `mcp.read` and `mcp.prompt` settle the same
|
|
173
|
+
outcomes, and their `returned` is `{ text, structured?, truncated? }`: for a read, `text` joins the text of the
|
|
174
|
+
resource's contents and `structured` is the `contents` list, with a blob given only as its `mimeType` and size; for a
|
|
175
|
+
prompt, `text` joins the text of the prompt's messages and `structured` is the `messages` list. An `output:` schema
|
|
176
|
+
for `structured` is Later.
|
|
137
177
|
- **A credentialed `command`: `command(argv, { env: { NAME: secret("x") | secret("x").path }, pin? })`.** Naming a
|
|
138
178
|
secret is what places it: it runs in the Sandbox Effector of the Workspace whose body invokes it, with no `placement:`
|
|
139
179
|
key — and so does an env that names `secret(open)`, whose name the composer binds (ADR-0054). Its child runs as the
|
|
@@ -321,26 +361,26 @@ Orchestrator's own process, the one Effect that does: it starts the run through
|
|
|
321
361
|
with only that commit — never the Agent's tip now — because its run is a function of the commit and its credential,
|
|
322
362
|
what the intent row records; with no commit recorded it settles `unknown`. A credentialed command follows the push's
|
|
323
363
|
rule: its intent row names its Sandbox and, once the first answer names it, the pinned commit, and one the Effector
|
|
324
|
-
lost is sent again with only that commit, since its run is a function of (commit, argv, secrets). An `http`
|
|
325
|
-
names its placement — the Instance Effector, or the Sandbox of a file-body `http` — and no replica: the
|
|
326
|
-
every replica of that Effector for its id and follows the one that holds it; when none does, it is sent
|
|
327
|
-
when the author marked it `idempotent`, and any other settles `unknown`. Retry is the author's choice — a
|
|
328
|
-
into the state, or `idempotent` for a call that carries its own idempotency key or only reads. No row is
|
|
329
|
-
settled or aborted row keeps the Effect's id, kind and outcome for the run's life, the record of what
|
|
330
|
-
Effects did. A settled row's output tail is dropped once the save that carries its transition is on disk;
|
|
331
|
-
stream is on the feed. An `http` response's body and headers are its tail: the row keeps the outcome, the
|
|
332
|
-
content type and the size, and the settled line on the feed carries the whole response. A `push` row keeps
|
|
333
|
-
Slot, its branch, its published commit, its source, its `squashed` and its sync's answer whole — every later
|
|
334
|
-
its Workspace and (slot, branch) sends them as one of its pairs, and the newest push's sync, when it did not
|
|
335
|
-
runs again — and drops git's words; it drops its bundle once its sync answered `synced`, `already` or
|
|
336
|
-
keeps it while the sync is `busy` or `not-synced` only as long as it is the newest push of its (slot,
|
|
337
|
-
published, because the sync that runs again moves the branch from it and only the newest push's sync runs
|
|
338
|
-
a newer push of that key published, the older row's bundle goes at the next save: one bundle per (slot,
|
|
339
|
-
most. A cut's `ready` row keeps its Repo Slot, its path and its branch whole, because a later Effect reads
|
|
340
|
-
Effect in a loop grows the ledger by one short row per pass, never by its tails. One exception, bounded by
|
|
341
|
-
while a pick is outstanding, its hold keeps the `chain` whole, tails included, because an `answer` action
|
|
342
|
-
after a restore; a loop below the Agent's state then grows the hold by one tail per pass. The hold drops
|
|
343
|
-
when the pick is answered.
|
|
364
|
+
lost is sent again with only that commit, since its run is a function of (commit, argv, secrets). An `http` or `mcp`
|
|
365
|
+
intent row names its placement — the Instance Effector, or the Sandbox of a file-body `http` — and no replica: the
|
|
366
|
+
Effect asks every replica of that Effector for its id and follows the one that holds it; when none does, it is sent
|
|
367
|
+
again only when the author marked it `idempotent`, and any other settles `unknown`. Retry is the author's choice — a
|
|
368
|
+
route back into the state, or `idempotent` for a call that carries its own idempotency key or only reads. No row is
|
|
369
|
+
dropped: each settled or aborted row keeps the Effect's id, kind and outcome for the run's life, the record of what
|
|
370
|
+
the run's Effects did. A settled row's output tail is dropped once the save that carries its transition is on disk;
|
|
371
|
+
the full stream is on the feed. An `http` response's body and headers are its tail: the row keeps the outcome, the
|
|
372
|
+
status, the content type and the size, and the settled line on the feed carries the whole response. A `push` row keeps
|
|
373
|
+
its Repo Slot, its branch, its published commit, its source, its `squashed` and its sync's answer whole — every later
|
|
374
|
+
push of its Workspace and (slot, branch) sends them as one of its pairs, and the newest push's sync, when it did not
|
|
375
|
+
finish, runs again — and drops git's words; it drops its bundle once its sync answered `synced`, `already` or
|
|
376
|
+
`skipped`, and keeps it while the sync is `busy` or `not-synced` only as long as it is the newest push of its (slot,
|
|
377
|
+
branch) that published, because the sync that runs again moves the branch from it and only the newest push's sync runs
|
|
378
|
+
again. Once a newer push of that key published, the older row's bundle goes at the next save: one bundle per (slot,
|
|
379
|
+
branch) at most. A cut's `ready` row keeps its Repo Slot, its path and its branch whole, because a later Effect reads
|
|
380
|
+
them. So an Effect in a loop grows the ledger by one short row per pass, never by its tails. One exception, bounded by
|
|
381
|
+
one pick: while a pick is outstanding, its hold keeps the `chain` whole, tails included, because an `answer` action
|
|
382
|
+
may read it after a restore; a loop below the Agent's state then grows the hold by one tail per pass. The hold drops
|
|
383
|
+
its chain when the pick is answered.
|
|
344
384
|
- **A start is sent once where it may have arrived.** A Harness runs one id once only within its own process; a
|
|
345
385
|
restarted Harness knows no id. So the Orchestrator sends a start again only when it certainly never left (the
|
|
346
386
|
connection was refused, the name did not resolve). Any other failure — a reset, a 5xx, no answer — may follow a start
|
package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
CHANGED
|
@@ -9,6 +9,15 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
9
9
|
|
|
10
10
|
## Decision
|
|
11
11
|
|
|
12
|
+
- **What an Effector is for.** It holds the secrets that the Orchestrator must not hold, and it makes every call that
|
|
13
|
+
jr2 makes to the outside for a Machine or a Hook: every `http` and `mcp` Effect runs in an Effector
|
|
14
|
+
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)), and the
|
|
15
|
+
Orchestrator's one call out of the Instance is the fetch of a JWT issuer's public keys (and of its discovery document,
|
|
16
|
+
which names where the keys are), which spends no secret
|
|
17
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). It serves two clients: a
|
|
18
|
+
Machine's Effects, in both placements, and the Orchestrator's Hook door, in the Instance Effector only, which uses two
|
|
19
|
+
operations: the `mac` check and one `http` call (below). It knows no Hook kind and no service. The reason above holds
|
|
20
|
+
for both clients, and a Hook's secret adds one: it can forge a delivery that answers a Gate.
|
|
12
21
|
- **One static Go binary, `effector`.** A top-level Go module, `effector/`, beside `operator/`: the same toolchain and
|
|
13
22
|
lint, `make test` and `make lint`, the standard library only. Go because the Effector is the trust boundary, and the
|
|
14
23
|
safe file opener (`os.Root`, `openat2`) is Go's. It is configured by its env, which names where its own mount holds
|
|
@@ -48,25 +57,46 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
48
57
|
(`the remote could not be reached: ssh: …`), with both in the detail: git's own words for an ssh failure name no
|
|
49
58
|
cause.
|
|
50
59
|
|
|
51
|
-
- **Two placements, one binary.** The Instance Effector runs `http` with no Worktree file,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
the
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
- **Two placements, one binary.** The Instance Effector runs `http` with no Worktree file, `mcp`
|
|
61
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), and
|
|
62
|
+
`dispatch` toward another Orchestrator ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), the other
|
|
63
|
+
Orchestrator's Caller token being an Effect secret it holds. It also serves the Hook door (below). A Sandbox Effector
|
|
64
|
+
— the binary with `JR2_EFFECTOR_WORK` set, a read-only view of the Agent's `/work` — runs an `http` whose body is a
|
|
65
|
+
Worktree file, `push` and a credentialed `command` (ADR-0067); the Instance Effector refuses the last two, and a file
|
|
66
|
+
body, as a defect, and a Sandbox Effector refuses a `dispatch` and an `mcp` Effect. Under Repo Slots each of the three
|
|
67
|
+
needs a cut's settled row in the run's ledger, because a path alone does not name the target of a credential: the
|
|
68
|
+
Orchestrator reads the row before it starts the Effect, and with no row it sends nothing and settles the kind's own
|
|
69
|
+
outcome ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). Its env names where its mount
|
|
70
|
+
holds the rest: `JR2_EFFECTOR_REPOS` (the node's Repo caches, read-only), `JR2_EFFECTOR_GIT` (the push credentials:
|
|
71
|
+
one directory per `git.credentials` entry the Sandbox's pushes spend, the entry's own Secret as the operator projects
|
|
72
|
+
it — `entry.json`, what the entry is; `identity` and `known_hosts` for an ssh key; `password` for a token;
|
|
73
|
+
`sign.identity` or `sign.private.asc` for the signing key; see the Sandbox Effector's objects below),
|
|
74
|
+
`JR2_EFFECTOR_SCRATCH` (its own writable scratch). What jr2 guarantees is stated per kind: for `push` and a file body,
|
|
75
|
+
git never takes config or hooks from the Agent's repository and no program the Agent's files name runs — the
|
|
76
|
+
clean-repository procedure of ADR-0067 and the opener below. jr2's own programs (`git`, `ssh`, `ssh-keygen`, `gpg`,
|
|
77
|
+
`gpg-agent`) run by an absolute path found in one fixed PATH, `/usr/local/bin:/usr/bin:/bin`, never by the PATH the
|
|
78
|
+
image set: an image that puts a directory of its own first does not choose which `git` runs. For a credentialed
|
|
79
|
+
`command` the Agent's files are the author's choice to run, and the claims are the next bullet's.
|
|
80
|
+
- **The Instance Effector serves the Hook door with two operations, and the Orchestrator never holds a Hook's secret.**
|
|
81
|
+
A Hook with a `mac` verifier
|
|
82
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)) names its secret as an
|
|
83
|
+
Effect names one, and the secret is placed in the Instance Effector alone.
|
|
84
|
+
- **The `mac` check.** The Orchestrator sends, on the signed wire (below), the bytes to check, the signature, the
|
|
85
|
+
secret's name and the algorithm. The Hook's kind gives the bytes, and the door makes every check that needs no
|
|
86
|
+
secret itself, so the Effector reads no header and no timestamp. The Effector computes an HMAC over the bytes with
|
|
87
|
+
that secret, compares it with the signature in constant time, and answers only "match" or "no match". It returns no
|
|
88
|
+
secret and no digest of one.
|
|
89
|
+
- **One `http` call**: a Hook kind's follow-up, such as the reply to a Slack click, which the door posts to the
|
|
90
|
+
click's `response_url` ([ADR-0011](0011-workflow-defined-events.md)). It is the `http` kind on the same wire, with
|
|
91
|
+
no secret, to a host that the Hook's kind declares, which `jr2 up` writes into `effector.json` for each Hook of that
|
|
92
|
+
kind, and it is sent once. It has no slot, no ledger row and no run.
|
|
93
|
+
|
|
94
|
+
`jr2 up` converges the Instance Effector when a Hook has a `mac` verifier or its kind declares a follow-up host, as it
|
|
95
|
+
does when an Effect is placed there. The `token` and `ed25519` verifiers run in the Orchestrator: a digest and a
|
|
96
|
+
public key cannot sign anything, so they need no Effector, but a follow-up of their kind goes through it. The check is
|
|
97
|
+
`POST /mac`, beside the Effects' routes and never an Effect: it has no id, starts nothing and is answered at once, and
|
|
98
|
+
a secret the Effector does not hold is a defect (400), as an Effect's is. A Sandbox Effector does not serve the route.
|
|
99
|
+
|
|
70
100
|
- **A credentialed `command`'s child is confined with Landlock.** The child runs as the Effector's own uid in the
|
|
71
101
|
Effector's container, where the placement key, every placed secret and the push and signing keys are files that uid
|
|
72
102
|
can read, and its program is often the Agent's code (`npm run deploy`, `make`, `./scripts/release.sh`, a tool's
|
|
@@ -204,8 +234,9 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
204
234
|
`effector/internal/wire` and zod schemas in `@jr2/orchestrator`, the one definition each TypeScript type is inferred
|
|
205
235
|
from. Go decodes with unknown fields disallowed. The golden fixtures in `effector/testdata/wire/` keep the two equal:
|
|
206
236
|
one JSON file per request shape (`request.*`), per answer and outcome (`response.*`), per shape its decoder must
|
|
207
|
-
refuse (`reject.*`),
|
|
208
|
-
TypeScript tests read the same files by a relative
|
|
237
|
+
refuse (`reject.*`), the two signatures (`signature.*`), and the `mac` check's request, answers and refusals
|
|
238
|
+
(`mac.*`). Go decodes each request and encodes each response; the TypeScript tests read the same files by a relative
|
|
239
|
+
path, encode the requests and decode the responses.
|
|
209
240
|
- **Signed requests and signed answers, and no bearer.** The key is the placement's,
|
|
210
241
|
`HMAC(signing key, "effector:" + placement)`, as ADR-0058 derives the Harness bearer: the Orchestrator stores nothing.
|
|
211
242
|
The Effector reads it from a file in its own mount. A request carries `X-Jr2-Time` (Unix milliseconds), `X-Jr2-Nonce`,
|
|
@@ -283,7 +314,7 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
283
314
|
`truncated` past it. A body that is not text is never returned: its size and type are.
|
|
284
315
|
- `unreachable`: nothing was sent — the name did not resolve, the connection or TLS failed.
|
|
285
316
|
- `refused`: the Effector refused it and sent nothing — a path segment that is `.`, `..` or empty (an Agent's param
|
|
286
|
-
can cause it, so it is output, not a fault), or a host and secrets that no Effect placed there names. The
|
|
317
|
+
can cause it, so it is output, not a fault), or a host and secrets that no Effect or Hook placed there names. The
|
|
287
318
|
Orchestrator settles an `http` start longer than 4 MiB `refused` the same way, before it sends anything, and a file
|
|
288
319
|
body with no cut's row (ADR-0075); a `push` and a `command` have no `refused`.
|
|
289
320
|
- `unknown`: the connection was made and no answer came (the timeout, a reset): the host may have acted.
|
|
@@ -294,28 +325,45 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
294
325
|
``Its answer: the output of `<slot>`: <the output's JSON>.``, already scrubbed. An `answer` action shapes it instead
|
|
295
326
|
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)).
|
|
296
327
|
|
|
328
|
+
- **The `mcp` kind.** The Effector holds a small MCP client of its own: Go, the standard library only, Streamable HTTP,
|
|
329
|
+
protocol `2025-11-25`
|
|
330
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). Each
|
|
331
|
+
Effect is one session: the handshake, one request (`tools/call`, `resources/read` or `prompts/get`), and a DELETE of
|
|
332
|
+
the session, so a replica keeps no session between Effects and the replica rules of `http` hold unchanged. The start
|
|
333
|
+
names the server and nothing else of it: the server's url, the name of its secret and its `timeout` are in
|
|
334
|
+
`effector.json`, which `jr2 up` writes from the `mcp` block, and the secret goes as `Authorization: Bearer <value>`.
|
|
335
|
+
The client declares no capability, so a server that asks for input gets none: the Effect settles `input-required`. An
|
|
336
|
+
answer is read as JSON, or as an event stream until the message that answers the request. A result's `text` and its
|
|
337
|
+
`structured` are each bounded to the start's `maxBody`, 64 KiB by default as for an `http` body; past it the Effector
|
|
338
|
+
cuts the text (a `structured` past the bound is dropped, not cut into JSON that does not parse) and sets `truncated`.
|
|
339
|
+
Every string of the output is scrubbed, as an `http` body is. A redirect is not followed. The outcomes and their
|
|
340
|
+
meaning are ADR-0067's.
|
|
297
341
|
- **The host is fixed, in the Effector.** The Effector's config (`effector.json`, written by `jr2 up` from the Effects'
|
|
298
342
|
plain data) lists each scheme, host and port an Effect placed there names, with the secrets that Effect names — the
|
|
299
|
-
host its definition fixes,
|
|
343
|
+
host its definition fixes, the host of an `mcp` Effect's server url in `jr2.config.ts`, or for a url the package left
|
|
344
|
+
Open, the one the composer's `customize()` bound
|
|
300
345
|
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)); `jr2 up` refuses a Machine
|
|
301
|
-
whose Open url nothing bound, so no fence entry is ever a guess.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
346
|
+
whose Open url nothing bound, so no fence entry is ever a guess. Each Hook adds the hosts that its kind declares for a
|
|
347
|
+
follow-up (the `slack` kind: `https://hooks.slack.com`), with no secret (above). A request to another target, or with
|
|
348
|
+
another secret, is `refused`. A redirect is the response and is never followed: it would send the request, and its
|
|
349
|
+
`Authorization`, to a host the definition did not fix. No proxy is read from the environment. The trust is the system
|
|
350
|
+
roots and the instance's `caBundle` ([ADR-0020](0020-private-ca-trust-is-a-named-concept.md)).
|
|
305
351
|
- **One list of secrets; placement is read, never declared, and naming is placement on both sides.** The top-level
|
|
306
352
|
`secrets` list replaces `harness.heldSecrets`; its entry is ADR-0059's
|
|
307
353
|
(`{ name, value | valueFrom.secretKeyRef, hosts?, headers?, paths? }`). A pod gets the secrets something in it names:
|
|
308
|
-
a Workspace's pod the Held secrets its `workspace()` lists under `secrets: [...]
|
|
309
|
-
providers read
|
|
310
|
-
with `secret("name")` (an `http` Effect:
|
|
311
|
-
Effector
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
`
|
|
317
|
-
`
|
|
318
|
-
|
|
354
|
+
a Workspace's pod the Held secrets its `workspace()` lists under `secrets: [...]`, the model keys its Agents'
|
|
355
|
+
providers read and the secrets of the MCP servers its Agents name; the Instance Harness the model keys and the MCP
|
|
356
|
+
server secrets of its `"none"` Agents only; an Effector what its Effects name with `secret("name")` (an `http` Effect:
|
|
357
|
+
the Instance Effector; an `mcp` Effect, which names its server's secret through the server: the Instance Effector; an
|
|
358
|
+
`http` whose body is a Worktree file: the Sandbox Effector of the Workspace whose body invokes it; a credentialed
|
|
359
|
+
command: that Sandbox Effector), and the name a composer binds to an Effect's `secret(open)` with `customize()`
|
|
360
|
+
(ADR-0054) is placed the same way, in that Effect's Effector alone, once bound — `jr2 up` refuses an Open secret
|
|
361
|
+
nothing bound. A Hook with a `mac` verifier places its secret in the Instance Effector the same way. `hosts`,
|
|
362
|
+
`headers` and `paths` are a Held secret's binding, never its placement (ADR-0059). One derivation
|
|
363
|
+
(`secretPlacementsOf`) places both kinds, and the held manifest, the Custodian config and the held values' Secret of
|
|
364
|
+
each Harness pod come from it. Both is both, and `jr2 up` reports it per pod. An entry nothing names goes nowhere, and
|
|
365
|
+
`jr2 up` warns: `secrets[i] (<name>) is named by nothing; goes nowhere`. There is no `placement:` key: naming is the
|
|
366
|
+
placement. `jr2 up` prints one line per secret with its placements, and one per pod with what it holds
|
|
319
367
|
(`Workspace of workflow "coding": Custodian holds ANTHROPIC_API_KEY (provider), JIRA_TOKEN`,
|
|
320
368
|
`the Instance Effector: holds jira`,
|
|
321
369
|
`Workspace of workflow "coding": Sandbox Effector (image the Sandbox Image) holds aws; pushes Repo Slot "app" with git.credentials "*"`
|
|
@@ -330,9 +378,10 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
330
378
|
short value, for a Secret edited after the converge. A secret reaches only the pods where something names it: a
|
|
331
379
|
literal is written into the placement's own Secret, and a `valueFrom` is projected from the user's Secret by
|
|
332
380
|
reference, never copied. `git.credentials` stays its own list: a fence keyed by url prefix, not a named value.
|
|
333
|
-
- **The Instance Effector's objects.** `jr2 up` converges them when a registered Machine places an Effect there
|
|
334
|
-
|
|
335
|
-
`instance effector:
|
|
381
|
+
- **The Instance Effector's objects.** `jr2 up` converges them when a registered Machine places an Effect there or a
|
|
382
|
+
Hook has a `mac` verifier or a kind that declares a follow-up host, and deletes them otherwise; it reports either
|
|
383
|
+
(`instance effector: converging (<workflow>/<slot> or hook <name>; secrets …; grace …)` or `instance effector: none`).
|
|
384
|
+
All four are named `jr2-instance-effector`:
|
|
336
385
|
- A **Deployment**, one replica, rolling with surge and no gap, so a rollout runs the old pod beside the new one: the
|
|
337
386
|
`jr2-harness` image, command `/opt/jr2/bin/effector`, non-root, no privilege escalation, every capability dropped, a
|
|
338
387
|
read-only root filesystem, an emptyDir scratch as `$HOME`, no ServiceAccount token, the control tier's PriorityClass
|
|
@@ -347,12 +396,13 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
347
396
|
- A **NetworkPolicy** of `jr2-sandbox-ingress`'s form ([ADR-0058](0058-a-harness-answers-the-orchestrator-alone.md)):
|
|
348
397
|
ingress from this Instance's Orchestrator only. Egress stays open: the pod calls the hosts its Effects name, and
|
|
349
398
|
holds no Custodian.
|
|
350
|
-
- The **Secret**: each literal Effect secret placed there, `effector.json` (the targets
|
|
351
|
-
`effectorKey(signing key, "instance")`, which `jr2 up` derives from
|
|
352
|
-
nothing stored by the Orchestrator: the Kubernetes Secret is the store,
|
|
353
|
-
files under `/run/jr2/secrets`, mode 0400; the kubelet makes them readable
|
|
354
|
-
one container. `harness.env` and `harness.envFrom` may not name this Secret,
|
|
355
|
-
a Sandbox's `<sandbox>-effector`, or a `git.credentials` entry's `jr2-git-*`
|
|
399
|
+
- The **Secret**: each literal Effect secret placed there, `effector.json` (the targets, and the MCP servers that its
|
|
400
|
+
`mcp` Effects name), and the placement's key, `effectorKey(signing key, "instance")`, which `jr2 up` derives from
|
|
401
|
+
the kept signing key. "Nothing stored" means nothing stored by the Orchestrator: the Kubernetes Secret is the store,
|
|
402
|
+
as for every credential. The secrets are files under `/run/jr2/secrets`, mode 0400; the kubelet makes them readable
|
|
403
|
+
by the pod's `fsGroup`, and the pod has one container. `harness.env` and `harness.envFrom` may not name this Secret,
|
|
404
|
+
nor a Workspace's `jr2-effector-<pod>`, a Sandbox's `<sandbox>-effector`, or a `git.credentials` entry's `jr2-git-*`
|
|
405
|
+
(ADR-0059's R13).
|
|
356
406
|
- **A Workspace's Sandbox Effector's objects** (ADR-0059). `jr2 up` reads, for each `workspace()`, the Effects placed
|
|
357
407
|
there below it, down to the next `workspace()` — every `push`, `http` whose body is a Worktree file and credentialed
|
|
358
408
|
`command`, from their plain data, statically — and writes two objects for a Workspace with one, none for one without.
|
|
@@ -403,6 +453,10 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
|
|
|
403
453
|
- **A StatefulSet for the Instance Effector**, as the Instance Harness is. Rejected: a one-replica StatefulSet deletes
|
|
404
454
|
the old pod before it creates the new one, so a rollout would cut an Effect in flight; the Effector keeps nothing a
|
|
405
455
|
stable name or volume would hold.
|
|
456
|
+
- **Check a Hook's `mac` in the Orchestrator.** Rejected: the Orchestrator runs every Machine's code, and an HMAC secret
|
|
457
|
+
in its process can sign a forged delivery. The check costs one call on the signed wire for each delivery.
|
|
458
|
+
- **A verifier container of its own** for the Hook door. Rejected: it would be the same binary, with the same secret
|
|
459
|
+
mount and the same signed wire, so a second pod adds a Deployment, a key and a NetworkPolicy and closes nothing.
|
|
406
460
|
- **A `placement:` key on a secret.** Rejected: naming a secret in an Effect is what places it, and a key would be a
|
|
407
461
|
second statement that can disagree with the first.
|
|
408
462
|
|
|
@@ -71,15 +71,16 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
|
|
|
71
71
|
an image cost: cluster write access. The token lives in the Instance's Secret beside the Instance token, `jr2 up`
|
|
72
72
|
reads it through the kube API alone, and no env var and no `--url` path can supply it, so an ingress-exposed Instance
|
|
73
73
|
exposes no publish door. The Instance token keeps its meaning
|
|
74
|
-
([ADR-0032](0032-the-console-unlocks-with-
|
|
74
|
+
([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)): start runs, answer Gates, read. `GET /assembly`,
|
|
75
75
|
public like `GET /workflows`: the current hash and Kit version, the Assemblies held with the runs that hold them, and,
|
|
76
76
|
when deployed, the digest of each ConfigMap the process reads through a mount (`jr2-images`, `jr2-held`), so a
|
|
77
|
-
publisher can see what the process reads
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
publisher can see what the process reads, and each standing Workflow's state and count of held items
|
|
78
|
+
([ADR-0017](0017-pool-over-a-source-port.md)). `jr2 status` prints it; `/healthz` prints the current hash where it
|
|
79
|
+
printed the content hash. A repeat publish of the current hash answers `200 { unchanged: true }` before the body is
|
|
80
|
+
read; `--force` sends the PUT anyway and gets the same answer, because the hash names bytes the Orchestrator already
|
|
81
|
+
holds. Rejected: an OCI artifact the Orchestrator pulls, which needs the registry that dropping docker removed; a
|
|
82
|
+
ConfigMap, at 1 MiB; a git ref the Orchestrator clones, when
|
|
83
|
+
[ADR-0048](0048-the-orchestrator-boots-without-its-repos.md) just took cloning out of the Orchestrator.
|
|
83
84
|
- **The walk runs twice, with different duties.** On the host, `jr2 up` keeps the typecheck
|
|
84
85
|
([ADR-0050](0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md)) and every duty that needs the
|
|
85
86
|
host or the cluster: it loads the Instance folder with the same `loadWorkflows` and `partsOf` the Orchestrator runs,
|
|
@@ -121,8 +122,22 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
|
|
|
121
122
|
Effector manifests, the held-secret Secrets, and the roots of the image sweep. A held run that still needs one the
|
|
122
123
|
current Assembly dropped fails at that provision, naming the held Assembly; so a publish strands no run whose Machine
|
|
123
124
|
it changed, and a publish that drops a Sandbox Image context a parked run has not yet provisioned is the one edit that
|
|
124
|
-
still can.
|
|
125
|
-
|
|
125
|
+
still can. A held run's `assembly` is in its status and in its `GET /runs` summary. The Console marks a run whose
|
|
126
|
+
Assembly is not the current one, "older Assembly · <hash>", and draws it on the run's own Machine, not on the current
|
|
127
|
+
Machine, from `GET /workflows/:name/runs/:runId/machine`; so the lit states are always states of the run's own Machine
|
|
128
|
+
([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)). For a live run the route reads the Machine from
|
|
129
|
+
the Assembly that the run holds. An ended run holds no Assembly, so the run's terminal write keeps its Machine, as
|
|
130
|
+
structure with the Charts that it bound last compiled in, in a table of its own in `state.db`, once for each distinct
|
|
131
|
+
doc: the key is a digest of the kept doc. The Machine digest of
|
|
132
|
+
[ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md) is not the key, because it hashes only what a
|
|
133
|
+
restore needs, and two Machines that differ in a guard, a description or an Effect's command would share one copy. The
|
|
134
|
+
Console draws every ended run from the route, because the status of an ended run binds no Chart. The run's row holds
|
|
135
|
+
only the key, so a thousand runs of one Workflow on one Assembly keep one copy, and the terminal write adds a row only
|
|
136
|
+
for a doc that the table does not have. The route serves that copy for as long as the row is kept
|
|
137
|
+
([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)), and the run sweep deletes a doc
|
|
138
|
+
that no kept row names. So the collection rule above does not change for history: an ended run needs no Assembly to be
|
|
139
|
+
drawn. A `drifted` run has no Machine to keep, because its Assembly could not load: the Console shows no graph for it,
|
|
140
|
+
and shows its drift reason in place of the graph. So a publish strands nothing: the "editing a workflow strands its
|
|
126
141
|
parked runs" consequence of [ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md) is retired, and
|
|
127
142
|
drift is what it always meant, "I can no longer read what I saved": an Assembly that cannot load after a Kit version
|
|
128
143
|
bump, or a volume that lost it. Such a run is `drifted` with a reason naming the Assembly hash and the import error;
|
|
@@ -154,6 +169,14 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
|
|
|
154
169
|
The agent path is data: [ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md).
|
|
155
170
|
- **Semver-compatible Assemblies.** Rejected above; one line to loosen later.
|
|
156
171
|
- **Keep only the current Assembly and report the runs a publish will strand.** Rejected above.
|
|
172
|
+
- **Hold each Assembly that a kept run names, to draw an ended run.** Rejected: an ended run needs only its Machine's
|
|
173
|
+
structure, not the code of the Assembly, and a hold for each kept row would keep every Assembly for `runs.retain` and
|
|
174
|
+
grow the volume with each publish. The run's structure is kept instead.
|
|
175
|
+
- **Keep the Machine in each ended run's row.** Rejected: every run of a Workflow on one Assembly would store the same
|
|
176
|
+
structure again, the space guard never frees rows, and the copy would add work to the synchronous terminal write. One
|
|
177
|
+
copy for each distinct structure, named by the row, costs neither.
|
|
178
|
+
- **Draw no graph for an ended run whose Assembly is gone.** Rejected: the graph of a failed run is what a person reads
|
|
179
|
+
first, and the copy in the row is small.
|
|
157
180
|
|
|
158
181
|
## Consequences
|
|
159
182
|
|
|
@@ -49,16 +49,21 @@ their own maintainers, and the escapes shipped in systems that used them were in
|
|
|
49
49
|
answer into the run), so the Open body lends it whole, `lends.gate: true`, and a body that does not has no `gate` in
|
|
50
50
|
its grammar and refuses one at the door; a lent Gate's ids sit below the body's own actor path, so a Chart cannot take
|
|
51
51
|
the name of its wrapper's Gate. (Measured: with `gate` offered beside one lent Agent, three of five missed second
|
|
52
|
-
Charts reached for it as a worker.) And it mints its own Agents, because an Agent is
|
|
52
|
+
Charts reached for it as a worker.) And it mints its own Agents, because an Agent is plain fields
|
|
53
53
|
([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)) and the Harness runs whatever definition
|
|
54
|
-
rides the Turn. A minted Agent
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
rides the Turn. A minted Agent names no MCP server: the Chart grammar refuses `mcp`, because an MCP server with its
|
|
55
|
+
secret is reach, and MCP tools reach a Chart only through a lent Agent
|
|
56
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). A
|
|
57
|
+
minted Agent's model must be one the Open body allows and the Assembly placed a key for; `workspace: "none"` needs the
|
|
58
|
+
Instance Harness the Assembly converged. Both are door refusals, never a 401 mid-run. It borrows, by slot name only,
|
|
59
|
+
every Effect (the url, argv, MCP server and tool, and secret were bound once by the author and the Chart fills only
|
|
60
|
+
its input slots), every Repo and image, every secret, every child Machine, and every guard, action or delay beyond the
|
|
61
|
+
generic set. Lending is attenuated by construction; an author who lends `command(["sh", "-c", arg("script")])` has
|
|
62
|
+
lent a shell, and that is theirs to choose. A lent name may not be dotted, `gate`, `fail`, a generic action's name or
|
|
63
|
+
an integer, because those are the kit's; the Gate itself is lent as `gate: true`, and a Gate slot that hears a Hook
|
|
64
|
+
([ADR-0011](0011-workflow-defined-events.md)) is lent by its slot name in `lends.gates`, as an Effect is in
|
|
65
|
+
`lends.effects`; the door refuses a lent Gate slot whose `delivers` names an event that is not in the Chart's
|
|
66
|
+
Vocabulary. The Part that fails the run is lent as `fail: true`
|
|
62
67
|
([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)), because the Machine
|
|
63
68
|
decides which ends a Chart can choose. The guards `jr2.failure`, which tells a Failure from a defect on an `onError`,
|
|
64
69
|
and `jr2.ready`, which says that a cut settled `ready`
|
|
@@ -172,10 +177,9 @@ their own maintainers, and the escapes shipped in systems that used them were in
|
|
|
172
177
|
token, not the publish token, because a Chart is data and cannot reach the process, which is the whole point of this
|
|
173
178
|
decision; it lives on the Orchestrator's volume at `.jr2/charts/<wrapper>/<name>.chart.json` beside the Assemblies,
|
|
174
179
|
written durably, and survives restarts. A live run holds its Chart through the text in its own snapshot, so a `DELETE`
|
|
175
|
-
while a run is live answers at once and the run goes on. It lists in `jr2
|
|
176
|
-
|
|
177
|
-
the
|
|
178
|
-
MCP or `curl` is the client. `GET /workflows` lists each wrapper with its Charts inside its entry, not beside it, and
|
|
180
|
+
while a run is live answers at once and the run goes on. It lists in `jr2 workflows` and the Console with its origin
|
|
181
|
+
shown ([ADR-0009](0009-cli-and-instance-interface.md)); there is no verb for `PUT` or `DELETE`, an MCP or `curl` is
|
|
182
|
+
the client. `GET /workflows` lists each wrapper with its Charts inside its entry, not beside it, and
|
|
179
183
|
`GET /workflows/<wrapper>` carries each Open body's declaration. So a Workflow is one of three things: a Machine from
|
|
180
184
|
the Assembly, a Chart file from the Assembly, or a runtime Chart, and the last two are children of the first. **There
|
|
181
185
|
is no collision rule between a Machine and a Chart**: a Machine's name is a filename stem and holds no slash, a
|