@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# The Harness prints the conversation, and the reasoning is the part that prints
|
|
1
|
+
# The Harness prints the conversation and the Orchestrator keeps it, and the reasoning is the part that prints
|
|
2
2
|
|
|
3
3
|
A running workflow is watchable and its Agents are not. The visualizer shows which states are lit (ADR-0022); the run
|
|
4
4
|
feed carries status and Emits (ADR-0009). What an Agent is actually _doing_ — what it read, what it tried, what it
|
|
@@ -16,12 +16,18 @@ both user and assistant messages, plus text, thinking, and tool-call content
|
|
|
16
16
|
([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)) — every line of that jq filter,
|
|
17
17
|
replayable from any offset. The gap was a read decision, not a missing capability, and the fix is not new plumbing.
|
|
18
18
|
|
|
19
|
+
**A conversation that lives only in its pod is gone when a person needs it.** A run that failed overnight is read the
|
|
20
|
+
next day. By then its Sandbox is torn down (ADR-0012) or reaped (ADR-0021), and the log plane, if the cluster has one,
|
|
21
|
+
is not jr2's and is not always there. So the print stays for the cluster's log plane, and the Orchestrator also keeps
|
|
22
|
+
each conversation for as long as it keeps its run.
|
|
23
|
+
|
|
19
24
|
## Decision
|
|
20
25
|
|
|
21
26
|
- **The Harness prints, and `kubectl logs` reads.** The Harness writes its own conversation to container stdout —
|
|
22
27
|
in-process; the loopback subscription this originally required retired with the foreign runtime (ADR-0027).
|
|
23
|
-
`kubectl logs`, `stern`, `k9s`, and any log shipper work on it
|
|
24
|
-
|
|
28
|
+
`kubectl logs`, `stern`, `k9s`, and any log shipper work on it, with no jr2 surface between them and the pod. The
|
|
29
|
+
print stays when the Orchestrator keeps the conversation (below): a cluster that ships its logs reads every
|
|
30
|
+
conversation in one place, with its own retention.
|
|
25
31
|
- **The Harness is the right seat because it already holds both halves of the label.** It knows its Agent's name and the
|
|
26
32
|
iid arrives per Submission. So a line is `[<agent>] [<iid>] …` with no lookup, no correlation table, and no
|
|
27
33
|
orchestrator round trip. Every line of a body carries both, the later lines of a body that spans lines too. The iid is
|
|
@@ -34,11 +40,14 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
34
40
|
invisible to every other jr2 process there.
|
|
35
41
|
- **Reasoning prints, and so does each pick's answer; a Working tool's result does not.** Prompts, assistant text,
|
|
36
42
|
thinking, tool calls with truncated inputs, and the answer to each Menu pick. The file contents, command output, and
|
|
37
|
-
API responses a Working tool returns never print
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
API responses a Working tool returns never print, and neither does the result of an MCP tool
|
|
44
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), for
|
|
45
|
+
the same reasons. An MCP call prints as `<server>/<tool>`, so it never prints as a pick does. This is one cut serving
|
|
46
|
+
two purposes, which is why it is a boundary and not an unfinished implementation: a Working tool's results are
|
|
47
|
+
whatever the Agent happened to read, which is where a leaked secret lands, and they are also most of the bytes. A
|
|
48
|
+
pick's answer is one bounded text per pick, and the bullet below states what it can still carry. Every user message
|
|
49
|
+
the model reads prints as a prompt — the Frame's prompt with its Turn part, a nudge, and a Turn part the Harness
|
|
50
|
+
restated at a step boundary
|
|
42
51
|
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), which prints once as
|
|
43
52
|
`[<agent>] [<iid>] [prompt] <jr2-turn>` where the model read it.
|
|
44
53
|
- **A pick's answer prints after its call, as the model received it.** A Menu call's result is jr2's own text: the
|
|
@@ -91,6 +100,63 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
91
100
|
- **The writer structures, the reader colorizes.** Lines carry a parseable prefix and no ANSI. jr's colors were correct
|
|
92
101
|
for an fzf preview, which is a terminal; `kubectl logs` output is piped, grepped, and ingested, where escape codes are
|
|
93
102
|
corruption. A future reader may colorize on `isatty`.
|
|
103
|
+
- **The Harness also sends the conversation's entries to the Orchestrator, during the Turn.** The entries are jr2's own
|
|
104
|
+
shape, not pi's: one entry for each body that the print makes, with the same cut — a prompt, the model's text, its
|
|
105
|
+
thinking, a call with its input cut to the print's bound, a pick's answer, a mechanic line — and never a Working
|
|
106
|
+
tool's result or an MCP tool's result. A body that spans lines is one entry. Only a body that carries an iid is an
|
|
107
|
+
entry. The narrative echo (below) is not: the Orchestrator already holds those events in its feed. An entry is
|
|
108
|
+
`{ id, seq, at, agent, kind, text }`: `kind` is the body's label (`prompt`, `text`, `thinking`, a tool's printed name,
|
|
109
|
+
`<pick> answer`, `compacted`), `text` is the rest of the body, and the Orchestrator sets `agent` from its ledger.
|
|
110
|
+
`seq` goes up for as long as the iid is kept. A new record starts it at the clock, not at 0, because a conversation
|
|
111
|
+
that the Harness lost with its pod starts again under the same iid, and its new entries must come after the old ones.
|
|
112
|
+
An entry is made when it is complete (at `message_end` for the model's words), so text and thinking deltas are not in
|
|
113
|
+
it. The Harness writes each entry locally first, and then sends the entries in batches to the Orchestrator while the
|
|
114
|
+
Turn runs, on `POST /agents/<iid>/conversation`, through its Custodian, with its pod's control token
|
|
115
|
+
([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md),
|
|
116
|
+
[ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): a Sandbox's control token, or the
|
|
117
|
+
Instance Harness's own control token, which is signed for its placement. The Instance token is never one of them. The
|
|
118
|
+
route takes entries only for an iid that the run's admission ledger places in the token's pod, and only when the slot
|
|
119
|
+
that the Harness states is the ledger's
|
|
120
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). It checks the ledger, not
|
|
121
|
+
the live registration: the ledger outlives the Turn and the run's end, so a batch that comes after the Turn's sweep or
|
|
122
|
+
after the run ended is still kept, and those are the entries that tell how a run ended. The Harness starts a send of
|
|
123
|
+
its pending entries when the model's answer ends (before its picks run) and at a Turn's end, and does not wait for it.
|
|
124
|
+
On SIGTERM it tries again for a few seconds. In a Sandbox the Custodian can exit first
|
|
125
|
+
([ADR-0001](0001-operator-owned-generic-sandbox.md)), so the entries since the last send (about one second) can be
|
|
126
|
+
lost. While a live run's ledger does not name the iid yet (the batch came before the admission's save), the route
|
|
127
|
+
answers 409 and the Harness sends the batch again. The Instance-token door of ADR-0013 does not open it: a holder of
|
|
128
|
+
the Instance token can speak as an Agent, but it cannot write an Agent's kept conversation. Each entry's `seq` makes a
|
|
129
|
+
send idempotent: the Orchestrator keeps each `seq` of a conversation once, so a batch that the Harness sends again
|
|
130
|
+
after a failure or a restart adds nothing twice. A batch that fails stays in the local copy and goes with the next
|
|
131
|
+
batch. Menu-only Agents send the same way, from the Instance Harness
|
|
132
|
+
([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). The kept conversation holds what the print holds:
|
|
133
|
+
no Working tool's result and no MCP tool's result, but the residual of a pick's answer stated above (a Held value that
|
|
134
|
+
an upstream echoed into a `command` Effect's output, an Effect secret in a form that the scrub does not catch). The
|
|
135
|
+
Orchestrator keeps that residual for `runs.retain`, behind `read`.
|
|
136
|
+
- **The Orchestrator keeps it in `conversations.db`.** It is a second SQLite file beside `state.db` on the
|
|
137
|
+
Orchestrator's state volume ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)), with
|
|
138
|
+
plain JSON rows, so a person or a tool can query it with SQLite alone. It is a second file because its writes are
|
|
139
|
+
larger and more frequent than a snapshot's, and its sweep frees pages: neither must slow the snapshot's synchronous
|
|
140
|
+
write. The file sets `auto_vacuum = INCREMENTAL` at its creation, and each sweep frees the pages that it emptied, so
|
|
141
|
+
the file gets smaller after a sweep. A kept conversation has a cap of 8 MiB. When an entry would go above it, the
|
|
142
|
+
Orchestrator keeps no more entries of that conversation and writes one marker entry that says so, and the
|
|
143
|
+
conversation's `cut` is true. The cap keeps the start of a long conversation, which is the part that tells why the
|
|
144
|
+
Agent did what it did. A kept conversation goes with its run: when ADR-0065's sweep deletes the run's row, it deletes
|
|
145
|
+
the run's conversations, and the space guard there deletes the oldest kept conversations of ended runs first. There is
|
|
146
|
+
no compression in the kept conversations now. Compression can come later and stays internal: Node's own zstd on the
|
|
147
|
+
entry text, with the queryable columns kept.
|
|
148
|
+
- **`jr2 logs` and the Console read the kept conversation.** `GET /runs/:runId/conversations` lists a run's
|
|
149
|
+
conversations, `[{ agent, slot, instanceId, open, bytes, cut }]`.
|
|
150
|
+
`GET /runs/:runId/conversations/:instanceId?after=<seq>` gives the entries after a sequence number, and `.../events`
|
|
151
|
+
follows a conversation as SSE while it is open (the iid is URL-encoded in the path, because it holds `/`). A
|
|
152
|
+
conversation is open while a Turn of it is registered on a live run; the feed ends a few seconds after it is not open,
|
|
153
|
+
so the batch that a Turn's end sends is still read. A conversation that the space guard swept lists
|
|
154
|
+
`swept: "no space"`, and its run says `swept: "conversation swept: no space"` in `GET /runs/:runId` and `GET /runs`.
|
|
155
|
+
The `read` scope opens all three ([ADR-0014](0014-observation-is-open-run-state-is-not.md)).
|
|
156
|
+
`jr2 logs <run> [agent] [-f]` prints the run's conversations, or one Agent slot's, in the printed form of this ADR,
|
|
157
|
+
Menu-only Agents too, and `-f` follows them until the run ends ([ADR-0009](0009-cli-and-instance-interface.md)). The
|
|
158
|
+
Console shows a conversation in its bottom drawer ([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)).
|
|
159
|
+
An ended run reads the same as a live one, for as long as the run is kept.
|
|
94
160
|
- **A Harness also prints the run's narrative — feed events the Orchestrator pushes to an echo endpoint.** A Workspace's
|
|
95
161
|
pod log should read as the whole story of the run that owns it: not just the conversations hosted there, but the
|
|
96
162
|
decisions and Emits that happened around them — a ticket claimed before the Workspace existed, a Menu pick a
|
|
@@ -113,10 +179,10 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
113
179
|
transition. The replay buffer follows the same rule: it keeps the markers and Emits (bounded) and the latest status,
|
|
114
180
|
so the preamble reads "the story so far, and where the run stands now" — old statuses are not interleaved between old
|
|
115
181
|
markers; the live tail keeps the interleaving (scaling review R10, 2026-09-30).
|
|
116
|
-
- **
|
|
117
|
-
ADR-
|
|
118
|
-
|
|
119
|
-
|
|
182
|
+
- **The print lasts as long as the pod; the kept conversation lasts as long as the run.** A pod's log goes with its
|
|
183
|
+
Sandbox (ADR-0012, ADR-0021) and rotates at the kubelet's bound; a cluster that ships logs keeps them by its own
|
|
184
|
+
rules, and no jr2 behavior depends on that. The kept conversation is jr2's: it is kept while its run is kept
|
|
185
|
+
(`runs.retain`, ADR-0065), and `jr2 logs` reads it after the Sandbox is gone.
|
|
120
186
|
|
|
121
187
|
## Considered options
|
|
122
188
|
|
|
@@ -131,15 +197,29 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
131
197
|
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The Agent's working tools execute in
|
|
132
198
|
the Harness, never touching it. A Custodian trace shows `action=intercept … method=POST` and none of the four things
|
|
133
199
|
worth watching.
|
|
134
|
-
- **
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
- **Keep no conversation past its pod.** The Harness serves the stream from any offset for the life of the pod
|
|
201
|
+
(ADR-0027), and a copy costs growth, a retention policy and a redaction surface. Rejected: a conversation that goes
|
|
202
|
+
with its pod is gone when a person needs it most, after a run failed. What a copy costs is bounded: `runs.retain` sets
|
|
203
|
+
the retention (ADR-0065), a cap for each conversation and a space guard bound the growth, and a kept conversation
|
|
204
|
+
holds only what the print holds: never a Working tool's or an MCP tool's result, but the residual of a pick's answer
|
|
205
|
+
stated above, behind `read` for `runs.retain`.
|
|
206
|
+
- **The Orchestrator reads the Harness's stream and writes the kept conversation.** Rejected: the Orchestrator holds a
|
|
207
|
+
stream only while it waits on a Submission, so a restart of the Orchestrator loses what the stream sent in the gap,
|
|
208
|
+
and the Orchestrator would have to render pi's events, which is the Harness's craft. The Harness makes the print, so
|
|
209
|
+
it makes the entries, with the same cut.
|
|
210
|
+
- **Read the conversation from the pod logs.** Rejected: the Orchestrator has no RBAC on Pods and must not get it for
|
|
211
|
+
this, and the kubelet rotates a log at 10Mi, so a long Turn loses its start before anyone reads it.
|
|
212
|
+
- **Copy the conversation at the end of the run.** Rejected: a pod that dies (an eviction, a lost node, a memory kill)
|
|
213
|
+
takes the copy with it, and that is the run a person wants to read. The entries go to the Orchestrator during the
|
|
214
|
+
Turn.
|
|
215
|
+
- **A separate archive service.** Rejected for now: every system with a run store keeps its history in that store, fed
|
|
216
|
+
by the worker during the run, and a separate archive (Tekton Results, Temporal's archival, Argo's archive) is an
|
|
217
|
+
optional extra beside it. jr2 has a run store, so the store keeps the conversations, with no new component to deploy.
|
|
218
|
+
- **Files, one for each conversation.** Rejected: a sweep, a cap, a list with sizes and a read after a sequence number
|
|
219
|
+
are each a query in SQLite, and each is code of jr2's own over files. SQLite is already the Orchestrator's store
|
|
220
|
+
(ADR-0065), and it gives the space back with an incremental vacuum.
|
|
221
|
+
- **`sqlite-zstd` for compression.** Rejected: it is a native extension under the LGPL, which its author calls not ready
|
|
222
|
+
for production. Compression, when it comes, is Node's own zstd on the entry text.
|
|
143
223
|
- **Print a Working tool's results, truncated.** Rejected: the first 200 bytes of a leaked config is where the good
|
|
144
224
|
stuff is, and the volume it re-admits is what kubelet's 10Mi rotation would then silently discard — losing the _start_
|
|
145
225
|
of a long turn, which is the part worth reading. A `command` Effect's output does print, inside its pick's answer and
|
|
@@ -153,12 +233,13 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
153
233
|
ADR-0058), so Emit payloads may ride it — the tokenless observation band still carries Emit types alone. What it does
|
|
154
234
|
widen is the pod-log surface: a run's narrative becomes readable by whoever can read the Workspace pod's logs, the
|
|
155
235
|
same audience the conversation bullet below already grants.
|
|
156
|
-
- **A scoped exception to ADR-0014**, recorded there. Observation is open and everything sensitive sits behind
|
|
157
|
-
|
|
158
|
-
logs.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
236
|
+
- **A scoped exception to ADR-0014**, recorded there. Observation is open and everything sensitive sits behind a
|
|
237
|
+
Caller's scope; the print puts an Agent's reasoning, and the answers jr2 gave its picks, in front of whoever can read
|
|
238
|
+
pod logs. The kept conversation is not part of the exception: it is behind `read`. Excluding a Working tool's results
|
|
239
|
+
bounds it to what an Agent reasons aloud and what jr2 told it, rather than what it happened to read; what jr2 told it
|
|
240
|
+
includes an Effect's output, so a `command` Effect's output and a Held value echoed into it reach the log as they
|
|
241
|
+
reach the model, and so does an Effect secret in a form an Effector's scrub does not catch (a transformed secret, a
|
|
242
|
+
partial echo; ADR-0068). Fine on kind; a real decision on a shared cluster.
|
|
162
243
|
- **The conversations of concurrent Agents interleave in one log.** The author chooses how many Agents work in a
|
|
163
244
|
Workspace ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), so two conversations can
|
|
164
245
|
print at one time on one container's stdout. The prefix keeps them apart: every line carries its label and its iid, so
|
|
@@ -166,10 +247,11 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
166
247
|
- **Truncation is load-bearing, not cosmetic.** Kubelet rotates container logs at 10Mi by default and drops the
|
|
167
248
|
remainder. jr's filter capped `Bash` at 100 chars and unknown tools at 150 and reduced `Write`/`Edit` to a path; that
|
|
168
249
|
was readability, and it is now also what keeps a long turn inside the window.
|
|
169
|
-
- **Live tails may miss partial output.** Text and thinking deltas are best-effort live progress, with
|
|
170
|
-
authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive.
|
|
171
|
-
watcher; anything wanting exactness reads the completed message.
|
|
172
|
-
- **`jr2 logs`
|
|
173
|
-
|
|
174
|
-
- **The
|
|
175
|
-
|
|
250
|
+
- **Live tails of the print may miss partial output.** Text and thinking deltas are best-effort live progress, with
|
|
251
|
+
`message_end` authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive.
|
|
252
|
+
Correct for a watcher; anything wanting exactness reads the kept conversation, which holds the completed message.
|
|
253
|
+
- **`jr2 logs` prints conversations, and `jr2 watch` prints a run's status feed**
|
|
254
|
+
([ADR-0009](0009-cli-and-instance-interface.md)). A pod's log stays `kubectl logs`.
|
|
255
|
+
- **The Orchestrator's state volume holds the kept conversations.** Its default size is `5Gi` (`runs.volume`, ADR-0065).
|
|
256
|
+
- **The Console's conversation drawer has a source.** It reads the kept conversation through the routes above; it is a
|
|
257
|
+
reader of one store, not a second way of collecting the data.
|
package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md
CHANGED
|
@@ -213,43 +213,44 @@ prevented it writing. An un-ended turn is a concurrent writer in a Workspace tha
|
|
|
213
213
|
root Gate, an outside send), nor a child's error: every outcome of work is its output, and an error is a jr2 or config
|
|
214
214
|
defect, so by default its message never reaches the model. The operator reads it: while a pick is outstanding, an
|
|
215
215
|
error from a child below the Agent's state goes on the run's feed (`child-error`: the Agent, its pick, the child and
|
|
216
|
-
the message; Instance-token band, as `fault` is), and `jr2 run` and `jr2
|
|
217
|
-
told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a state
|
|
218
|
-
prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came back for
|
|
219
|
-
never that nothing ran, because a timer or an outside event may have stopped work that did run. The pick's
|
|
220
|
-
answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the one tool
|
|
221
|
-
(ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final as it
|
|
222
|
-
— replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's state,
|
|
223
|
-
`POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that enters
|
|
224
|
-
state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it was,
|
|
225
|
-
is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An
|
|
226
|
-
pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain
|
|
227
|
-
its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is
|
|
228
|
-
was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a
|
|
229
|
-
straight back, and an answered pick back at Rest) ends its state part with the same words, "Your turn is not
|
|
230
|
-
the plain receipt once said "the workflow is still in the state that asked for this turn, so it is not over
|
|
231
|
-
real Haiku 5.5 runs stopped after it, because "it" can be the workflow or the Turn. The refusals and the
|
|
232
|
-
cut or unsent pick keep their own words. A route that leaves the Agent's state ends the Turn like any
|
|
233
|
-
reads `turnComplete`, the next state's prompt is the author's, and the answer is a `pick-answer`
|
|
234
|
-
that picked: the next Turn of the conversation the picking Turn continued (`continue: true`,
|
|
235
|
-
picking Turn's conversation is never heard again, and a later continued Turn of the same slot never
|
|
236
|
-
its answer goes nowhere: the ledger keeps no notice for it. The route of a reply includes the events
|
|
237
|
-
itself AT ONCE while it runs (a `raise`, or a `sendTo(self)` with no delay, in `onDone` or in the
|
|
238
|
-
a reply whose route leaves the Agent's state that way is the answer, never stopped. Something
|
|
239
|
-
leave the Agent's state while the pick's work still runs below it — a timer the Machine set
|
|
240
|
-
named delay, a delayed `raise` or `sendTo(self)`), an event from outside the states below,
|
|
241
|
-
own end. The exit stops that work (an invoke is stopped when its state exits), and the
|
|
242
|
-
poll reads "The workflow left the state that asked for this turn while it ran your
|
|
243
|
-
work before it finished: your turn is over.", and the next continued Turn's notice
|
|
244
|
-
finish: the workflow left the state that asked for your last turn while it ran, and that
|
|
245
|
-
last reply an earlier link gave, if any, and state it: an earlier link's reply is never
|
|
246
|
-
was stopped. The run host tells a fired timer from an event sent at once by the
|
|
247
|
-
the actor system arms, cancels and fires (`timers.ts`, the same xstate
|
|
248
|
-
at Rest INSIDE the Agent's state — a per-step timeout `after` on the
|
|
249
|
-
|
|
250
|
-
restored hold is the Turn of the first registration of its address in the
|
|
251
|
-
and starts another at the same address before anything settles answers
|
|
252
|
-
default time bound on a held Turn; the Console shows each outstanding pick
|
|
216
|
+
the message; Instance-token band, as `fault` is), and `jr2 run` and `jr2 watch` print it. An author who wants the
|
|
217
|
+
Agent told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a state
|
|
218
|
+
whose prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came back for
|
|
219
|
+
it"): never that nothing ran, because a timer or an outside event may have stopped work that did run. The pick's
|
|
220
|
+
`POST` answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the one tool
|
|
221
|
+
call (ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final as it
|
|
222
|
+
starts — replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's state,
|
|
223
|
+
the `POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that enters
|
|
224
|
+
a state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it was,
|
|
225
|
+
and is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An
|
|
226
|
+
outstanding pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain
|
|
227
|
+
receipt with its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is
|
|
228
|
+
the state it was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a
|
|
229
|
+
guard straight back, and an answered pick back at Rest) ends its state part with the same words, "Your turn is not
|
|
230
|
+
over.": the plain receipt once said "the workflow is still in the state that asked for this turn, so it is not over
|
|
231
|
+
yet", and real Haiku 5.5 runs stopped after it, because "it" can be the workflow or the Turn. The refusals and the
|
|
232
|
+
notices of a cut or unsent pick keep their own words. A route that leaves the Agent's state ends the Turn like any
|
|
233
|
+
other: the poll reads `turnComplete`, the next state's prompt is the author's, and the answer is a `pick-answer`
|
|
234
|
+
notice to the Agent that picked: the next Turn of the conversation the picking Turn continued (`continue: true`,
|
|
235
|
+
ADR-0062). A FRESH picking Turn's conversation is never heard again, and a later continued Turn of the same slot never
|
|
236
|
+
made the pick, so its answer goes nowhere: the ledger keeps no notice for it. The route of a reply includes the events
|
|
237
|
+
the Machine sends itself AT ONCE while it runs (a `raise`, or a `sendTo(self)` with no delay, in `onDone` or in the
|
|
238
|
+
next state's entry): a reply whose route leaves the Agent's state that way is the answer, never stopped. Something
|
|
239
|
+
other than the route can leave the Agent's state while the pick's work still runs below it — a timer the Machine set
|
|
240
|
+
(a Turn timeout `after`, a named delay, a delayed `raise` or `sendTo(self)`), an event from outside the states below,
|
|
241
|
+
a Gate's event, the Agent's own end. The exit stops that work (an invoke is stopped when its state exits), and the
|
|
242
|
+
answer says so (`stopped`): the poll reads "The workflow left the state that asked for this turn while it ran your
|
|
243
|
+
pick, and that stopped the pick's work before it finished: your turn is over.", and the next continued Turn's notice
|
|
244
|
+
reads "Your pick `X` did not finish: the workflow left the state that asked for your last turn while it ran, and that
|
|
245
|
+
stopped it." Both keep the last reply an earlier link gave, if any, and state it: an earlier link's reply is never
|
|
246
|
+
dropped because a later link was stopped. The run host tells a fired timer from an event sent at once by the
|
|
247
|
+
scheduler's own table: it watches what the actor system arms, cancels and fires (`timers.ts`, the same xstate
|
|
248
|
+
internals as the timer table, ADR-0007). Back at Rest INSIDE the Agent's state — a per-step timeout `after` on the
|
|
249
|
+
child state that exits the work — the answer is the last reply since the pick, with the Allowed picks; with no reply
|
|
250
|
+
since the pick, the answer carries none. A restored hold is the Turn of the first registration of its address in the
|
|
251
|
+
restored tree: a restore that ends that Turn and starts another at the same address before anything settles answers
|
|
252
|
+
the pick with `turnComplete`. There is no default time bound on a held Turn; the Console shows each outstanding pick
|
|
253
|
+
"outstanding since".
|
|
253
254
|
- **The one exception is the Orchestrator ending it for its own reasons**, which is ADR-0007's requirement and exactly
|
|
254
255
|
one place: `RunHost.stop()`. Process shutdown never stops actors at all (`instance.ts` calls `host.close()`, which
|
|
255
256
|
ends observation feeds and nothing else), and restore is a fresh process. So "the host did this" is a flag it sets,
|
|
@@ -33,16 +33,21 @@ carved out for "the Orchestrator ending a run for its own reasons" was being spe
|
|
|
33
33
|
- **`stop()` stays public with no production caller, and is deliberately not on the wire.** It is correct, it is what
|
|
34
34
|
the durability suite drives, and it is what a future graceful shutdown should call. What nobody asked for is a
|
|
35
35
|
user-facing "park this run" verb.
|
|
36
|
+
- **A cancel of a standing run is also a pause.** The run ends `cancelled` as any run does, and jr2 does not start its
|
|
37
|
+
Workflow again until `jr2 run <workflow>` resumes it, with the items it held
|
|
38
|
+
([ADR-0017](0017-pool-over-a-source-port.md)). Without the pause, jr2 would start the standing run again at the next
|
|
39
|
+
boot or publish, and a person could not stop it. The runs that it dispatched go on, as for any dispatch.
|
|
36
40
|
- **A cancelled run's Workspaces are destroyed, unless the cancel says `keep`.** The run is over, so nothing can use
|
|
37
41
|
them, and each holds its whole Size (ADR-0060) — a placed pod left to the idle GC is capacity for nobody. The
|
|
38
42
|
wrapper's `teardown` is a STATE, entered when the body reaches final, and an actor stop skips it; so the host destroys
|
|
39
43
|
every Sandbox the run owns after the stop, the same idempotent delete `teardown` runs.
|
|
40
44
|
`POST /runs/:id/events { type: "CANCEL", keep: true }` — `jr2 send <run> --event CANCEL --keep` — leaves them for
|
|
41
|
-
inspection instead: with no Lease they age out at the operator's idle timeout, and `jr2
|
|
42
|
-
Whether to keep is a fact of the moment of the cancel, never a
|
|
43
|
-
before ([ADR-0012](0012-workspace-wrapper-machine.md)'s
|
|
44
|
-
chose, and the pod is the evidence. A **failed** run
|
|
45
|
-
|
|
45
|
+
inspection instead: with no Lease they age out at the operator's idle timeout, and `jr2 workspace shell` reaches them
|
|
46
|
+
until then; `jr2 workspace rm` removes one sooner. Whether to keep is a fact of the moment of the cancel, never a
|
|
47
|
+
config key. A **faulted** run keeps its Workspaces as before ([ADR-0012](0012-workspace-wrapper-machine.md)'s
|
|
48
|
+
destroy-less terminal, ADR-0021): a fault is the case nobody chose, and the pod is the evidence. A **failed** run
|
|
49
|
+
deletes them, as a `done` run does: a Failure is an end that the Machine chose
|
|
50
|
+
([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)).
|
|
46
51
|
|
|
47
52
|
## Considered options
|
|
48
53
|
|
package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md
CHANGED
|
@@ -57,8 +57,8 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
|
|
|
57
57
|
([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md)). The model sent
|
|
58
58
|
`bash` with `git add … && git commit …` and the pick `publish` in one model answer. In 3 of 10 trials, `publish`
|
|
59
59
|
pushed before the commit existed. A `write` and a `bash` test run in one model answer had the same race. The rule is
|
|
60
|
-
for all tools
|
|
61
|
-
the same time. Four facts follow:
|
|
60
|
+
for all tools: the Working tools, the MCP tools and the Menu. The cost is that independent reads of one model answer
|
|
61
|
+
do not run at the same time. Four facts follow:
|
|
62
62
|
- Only the first pick of a model answer runs
|
|
63
63
|
([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)). The model wrote a later one before it
|
|
64
64
|
read the first one's answer. The seam states two more facts, so that the Turn can find the first pick by itself. The
|
|
@@ -177,10 +177,11 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
|
|
|
177
177
|
long as its run** (ADR-0031). The Harness persists each conversation to a directory as it goes and rebuilds it on
|
|
178
178
|
boot, so a Harness restart inside a living pod keeps it; a Submission in flight at the restart settles `failed`.
|
|
179
179
|
What is durable across a lost pod is Orchestrator-side — the admission ledger and snapshot
|
|
180
|
-
([ADR-0007](0007-durable-machine-state.md)),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`
|
|
180
|
+
([ADR-0007](0007-durable-machine-state.md)), `wait`'s reconnect-from-offset, and the kept conversation, which a
|
|
181
|
+
person reads and no Turn continues from ([ADR-0023](0023-the-harness-prints-the-conversation.md)). A conversation
|
|
182
|
+
that is gone surfaces as 404 → fault → workflow policy, the same family as `workspace.lost`. Provider-stream retry
|
|
183
|
+
moves inside the turn: the Harness states 2 retries, and pi-ai runs them (`maxRetries`) — the seat ADR-0016
|
|
184
|
+
delegated to flue's `durability{}`.
|
|
184
185
|
- **[ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)'s enabling fact is reproduced
|
|
185
186
|
as explicit code.** Each Submission reads `$JR2_CUSTODIAN_URL/agents/<iid>/surface` afresh and presents it to pi as
|
|
186
187
|
the Menu, so the menu is per-turn with no push channel, no turn index, no second port. Menu tools keep the
|
|
@@ -31,9 +31,12 @@ definition can state.
|
|
|
31
31
|
Workspace (it is this ADR's title). A string field, JSON-serializable, riding the existing ConfigMap channel unchanged
|
|
32
32
|
(ADR-0018's constraint holds).
|
|
33
33
|
- **`"none"` is the Menu-only Agent — and the value also names the Harness that hosts the Agent.** `"none"` withholds
|
|
34
|
-
the entire Working toolset: the Agent converses
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
the entire Working toolset: the Agent converses, picks from its Menu and calls the MCP tools of the remote servers it
|
|
35
|
+
names
|
|
36
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), and
|
|
37
|
+
runs no code — the decisioning persona (read inputs, pick the next event). "Menu-only" means "no Working tools": such
|
|
38
|
+
an Agent can also hold MCP tools. Because such an Agent touches no Workspace, it does not run in one:
|
|
39
|
+
`"write"`/`"read"` place a Turn on the enclosing Workspace's Harness; `"none"` places it on the
|
|
37
40
|
[Instance Harness](0031-menu-only-agents-run-on-the-instance-harness.md), always — even when a `workspace()` encloses
|
|
38
41
|
the invocation. Placement is definitional, which is this ADR's thesis extended one step: what an Agent may do to the
|
|
39
42
|
Workspace decides where it runs. (`cwd` is moot for `"none"` — only Working tools consume it.)
|
|
@@ -50,6 +53,18 @@ definition can state.
|
|
|
50
53
|
- **Two layers, one structure: the tool list is the hint, the worktree is the guarantee** — the same shape as ADR-0024's
|
|
51
54
|
receipt (hint) and abort (guarantee). Neither layer alone: tools without the worktree leak through bash; the worktree
|
|
52
55
|
without tools invites the model to fight its checkout instead of declining politely.
|
|
56
|
+
- **An MCP allowlist is a guarantee about the credential, and jr2 offers it only where it can keep it.** An Agent's MCP
|
|
57
|
+
tools ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md))
|
|
58
|
+
spend the server's token, so the allowlist guards that token, not the Workspace. A remote server needs a list (tool
|
|
59
|
+
names, or `"*"`): the Harness offers and runs only those tools. Where Workspace code of the Sandbox in which the
|
|
60
|
+
Agent's Turns run can spend that secret, Workspace code can call every tool, so `jr2 up` refuses a list and takes only
|
|
61
|
+
`"*"`. That is the case when the Sandbox's `workspace()` grants the secret, when a credentialed `command` of that
|
|
62
|
+
Workspace names it, or when an `http` Effect of that Workspace whose body is a Worktree file names it: the Agent
|
|
63
|
+
writes that body ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). A `"none"` Agent's
|
|
64
|
+
Turns run on the Instance Harness, where no Workspace code runs, so it keeps its list. A stdio server runs as
|
|
65
|
+
Workspace code and gets no list. The token's own scopes are the outer limit. This is not the shape of
|
|
66
|
+
`workspace: "read"` above: there the tool list is the hint, because `bash` can write; here the list is the guarantee,
|
|
67
|
+
because no Workspace code can spend the secret.
|
|
53
68
|
- **A state before the reviewing state cuts the review Worktree on each round.** The sha under review moves between
|
|
54
69
|
rounds, so each round cuts the one folder again at the new sha. The cut forces the folder to the commit and cleans it
|
|
55
70
|
with `-ffd`. Ignored files stay, because the reviewer runs the tests. The folder's path frames the reviewer's turn —
|
|
@@ -79,10 +94,11 @@ definition can state.
|
|
|
79
94
|
- **The reviewer definition becomes `workspace: "read"`**, and the prose ban stays in its instructions as intent — the
|
|
80
95
|
half of the contract a model reads — no longer as the mechanism. A decisioner becomes `workspace: "none"`.
|
|
81
96
|
- **The definition stays plain data.** No code enters the ConfigMap channel; composition by re-export/spread (ADR-0018)
|
|
82
|
-
is preserved.
|
|
83
|
-
|
|
84
|
-
- **GLOSSARY.md arbitrates the
|
|
85
|
-
|
|
97
|
+
is preserved. Tool _implementations_ stay out of the contract — this decision adds a restriction vocabulary, not an
|
|
98
|
+
extension one. The extension is MCP: an Agent names servers, and a server holds the code (ADR-0079).
|
|
99
|
+
- **GLOSSARY.md arbitrates the senses of "tools"**: the Menu (control plane — what an Agent may say), Working tools
|
|
100
|
+
(data plane — what it may do) and MCP tools (what it may do through a service, ADR-0079) are glossary entries;
|
|
101
|
+
unqualified "tools" joins the Avoid lists.
|
|
86
102
|
- **The `@kind` tier gains the containment scenario**: from the review worktree, a write probe followed by a commit must
|
|
87
103
|
leave the branch ref and the coder's worktree untouched.
|
|
88
104
|
- **The zero-support guard stays available to workflows**: a workflow can still have both Agents report the sha and gate
|
|
@@ -21,12 +21,19 @@ whole history again. It also left the history calling tools the request no longe
|
|
|
21
21
|
|
|
22
22
|
## Decision
|
|
23
23
|
|
|
24
|
-
- **The Menu is the whole of what an Agent can ever be offered in its Machine, and it is the
|
|
25
|
-
union of the derived sets of every state that invokes an Agent of that name — the same scope as its
|
|
26
|
-
conversation's id (ADR-0057). `jr2Setup.createMachine` derives it in ADR-0015's walk, in a fixed order, so
|
|
27
|
-
byte-stable for every Turn of the conversation and for every fresh conversation of that Agent. The descriptions
|
|
28
|
-
the event defs' own and do not vary by state, so a def's description must hold at every writing of it the Agent
|
|
29
|
-
reach; the load warns where its writings on one path differ (ADR-0015).
|
|
24
|
+
- **The Menu is the whole of what an Agent can ever be offered in its Machine, and it is the Menu's part of the tools
|
|
25
|
+
block.** It is the union of the derived sets of every state that invokes an Agent of that name — the same scope as its
|
|
26
|
+
continued conversation's id (ADR-0057). `jr2Setup.createMachine` derives it in ADR-0015's walk, in a fixed order, so
|
|
27
|
+
it is byte-stable for every Turn of the conversation and for every fresh conversation of that Agent. The descriptions
|
|
28
|
+
are the event defs' own and do not vary by state, so a def's description must hold at every writing of it the Agent
|
|
29
|
+
can reach; the load warns where its writings on one path differ (ADR-0015).
|
|
30
|
+
- **An Agent's MCP tools are fixed for the conversation too.** The Harness reads a server's tools at the conversation's
|
|
31
|
+
first Turn, keeps the allowed ones sorted by name, records them with the conversation, and never reads them again for
|
|
32
|
+
it, across a park and a restart
|
|
33
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
|
|
34
|
+
tools block is the Working tools, then the MCP tools, then the Menu, and it is the same bytes for every Turn of the
|
|
35
|
+
conversation. Any fixed order keeps the cache. A tool that the server took away stays in the block, and its call fails
|
|
36
|
+
with jr2's text.
|
|
30
37
|
- **What one Turn may pick is its Allowed picks**: the invoking state's derived set (its own, its ancestors' and its
|
|
31
38
|
modes' picks — ADR-0015), narrowed to what the live Machine would currently accept **from this Agent**: xstate's
|
|
32
39
|
selection for an event that carries the Agent's sender, a guard that reads the payload counted as passing, must move
|
|
@@ -117,7 +124,8 @@ block was not perfect either: once the model invented a tool it had never been g
|
|
|
117
124
|
cross-state `continue`.
|
|
118
125
|
- **The Machine's whole agent-audience vocabulary as the Menu.** Rejected: it offers picks only another Agent's states
|
|
119
126
|
handle.
|
|
120
|
-
- **
|
|
127
|
+
- **Narrow the Menu per Turn, with the Menu tools last so that only the tail of the block changes.** Rejected: measured,
|
|
128
|
+
a change at the tail of the tools block misses as completely as one at its head.
|
|
121
129
|
- **Mask at decode time** (OpenAI `allowed_tools`, logit masking). Deferred: it is provider-specific — Anthropic has no
|
|
122
130
|
subset form, and changing `tool_choice` there drops the messages cache; vLLM's Chat Completions refuses it. The design
|
|
123
131
|
must hold without it; where it is cache-safe, it can be added in the presenter.
|
|
@@ -141,9 +149,9 @@ block was not perfect either: once the model invented a tool it had never been g
|
|
|
141
149
|
|
|
142
150
|
## Consequences
|
|
143
151
|
|
|
144
|
-
- **One state can allow different picks as its context changes or its active mode changes; the Menu
|
|
145
|
-
The conformance tier asserts it: two Submissions with different Allowed picks send a byte-identical
|
|
146
|
-
(ADR-0027).
|
|
152
|
+
- **One state can allow different picks as its context changes or its active mode changes; the Menu and the MCP tools do
|
|
153
|
+
not change.** The conformance tier asserts it: two Submissions with different Allowed picks send a byte-identical
|
|
154
|
+
tools block (ADR-0027).
|
|
147
155
|
- **A restatement is appended, never rewritten.** Conformance asserts that each request of a Submission is a byte prefix
|
|
148
156
|
of the next, with the tools and the system prompt unchanged, and that the restatement sits right after the model
|
|
149
157
|
answer's last tool result.
|
|
@@ -89,8 +89,9 @@ to ask about.
|
|
|
89
89
|
([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)) is the run's data, not its Machine's shape: the Chart's
|
|
90
90
|
text and its hash ride the body's own snapshot, restore admits the text again and compares the hash, and a mismatch or
|
|
91
91
|
a refusal is drift naming the body slot and the reason, after the stamp check and in the same words.
|
|
92
|
-
- **A drifted run is
|
|
93
|
-
and `jr2 status <id
|
|
92
|
+
- **A drifted run is in `jr2 runs --status drifted`** and in the Console's list of ended runs, with its reason
|
|
93
|
+
([ADR-0009](0009-cli-and-instance-interface.md)), and `jr2 status <id>` reads it whole. It is not in the default
|
|
94
|
+
listing, which is the live runs.
|
|
94
95
|
- **The digest is only as complete as the walk.** A state running `enqueueActions` can spawn children no static analysis
|
|
95
96
|
recovers (`machine-doc.ts`'s `opaqueStates`); the opacity is folded into the hash, but what it hides is not. Drift
|
|
96
97
|
inside such a subtree can go unnoticed.
|
|
@@ -39,19 +39,23 @@ built.
|
|
|
39
39
|
the Harness has exactly one route to its Menu — the Custodian on `localhost` — and forking that path for one pod buys
|
|
40
40
|
a divergence in the component this ADR keeps deliberately uniform. The pod is composed by the same function a
|
|
41
41
|
Sandbox's is ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)), so its Custodian
|
|
42
|
-
carries the Instance Harness's model calls the same way. It holds
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(
|
|
48
|
-
|
|
42
|
+
carries the Instance Harness's model calls the same way. It holds what the `"none"` Agents it hosts name — the keys
|
|
43
|
+
their providers read, and the secrets of the remote MCP servers they name
|
|
44
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)) — and
|
|
45
|
+
none of the held secrets a `workspace()` lists: a `"none"` Agent cannot execute code, so it had no way to use the
|
|
46
|
+
rest. A `"none"` Agent names no stdio MCP server, because the Instance Harness has no Landlock domain to start one in.
|
|
47
|
+
Its held objects are its own (`held-instance.json`, `jr2-held-secrets-instance`). Its held secrets get secret
|
|
48
|
+
Stand-ins, as in every Harness pod: `jr2 up` mints them with the signing key when it writes that pod's Secrets, as it
|
|
49
|
+
does for the Instance Effector's key ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
|
|
50
|
+
It starts no confined child, so this is for consistency, not a boundary. Defense-in-depth is the bonus, not the
|
|
51
|
+
reason.
|
|
49
52
|
- **The Instance Effector is the second Instance-level pod, beside it**
|
|
50
53
|
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), on the same convention: `jr2 up`
|
|
51
|
-
converges it when a registered Machine places an Effect there — an `http
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
converges it when a registered Machine places an Effect there — an `http`, an `mcp` or a targeted `dispatch` Effect —
|
|
55
|
+
or a Hook needs it (a `mac` verifier, or a kind that declares `posts`), and deletes it otherwise. It runs the same
|
|
56
|
+
`jr2-harness` image with `/opt/jr2/bin/effector` as its command, holds no conversation and runs no Agent, and is a
|
|
57
|
+
Deployment, not a StatefulSet: it keeps nothing, and a rollout must run the new pod beside the old one. No config key
|
|
58
|
+
names, sizes, addresses, or enables it.
|
|
55
59
|
- **No image config at all.** The Harness image was once configured as `sandbox.image` because the Sandbox pod was the
|
|
56
60
|
only place a Harness ran — a misnomer once the Instance Harness exists, and config is the wrong seat regardless:
|
|
57
61
|
`jr2 up` builds and resolves every image it deploys ([ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md)), and
|
|
@@ -71,13 +75,14 @@ pointers only; memory snapshots are tied to one template version, so the state w
|
|
|
71
75
|
change is a volume the actor reloads on a cold boot. These decisions take that shape.
|
|
72
76
|
|
|
73
77
|
- **A conversation is persisted to a directory, and rebuilt from it on boot.** The Harness writes each conversation as
|
|
74
|
-
it goes: jr2's own record — the stream log, the Settlements, the history messages,
|
|
75
|
-
engine part, in a folder the engine names (`engine/<name>`), which the record does not
|
|
76
|
-
transcript (ADR-0027): `engine/pi/transcript.jsonl`, the pi-ai messages the model read and
|
|
77
|
-
reads jr2's record first; an engine that cannot read the engine part (another engine,
|
|
78
|
-
the history messages. The record is the contract; the engine part is not. A Submission
|
|
79
|
-
settles `failed` (the typed error `harness_restarted`, which the Agent's Actor reads as "Turn
|
|
80
|
-
stream, so the Orchestrator reads a Settlement, not a 404, and the conversation's next Turn
|
|
78
|
+
it goes: jr2's own record — the stream log, the Settlements, the history messages, the conversation's MCP tools, in
|
|
79
|
+
the wire's shapes — plus the engine part, in a folder the engine names (`engine/<name>`), which the record does not
|
|
80
|
+
read. Today it is jr2's transcript (ADR-0027): `engine/pi/transcript.jsonl`, the pi-ai messages the model read and
|
|
81
|
+
each Compaction. Rebuilding reads jr2's record first; an engine that cannot read the engine part (another engine,
|
|
82
|
+
another version) continues from the history messages. The record is the contract; the engine part is not. A Submission
|
|
83
|
+
in flight at the restart settles `failed` (the typed error `harness_restarted`, which the Agent's Actor reads as "Turn
|
|
84
|
+
lost") on the rebuilt stream, so the Orchestrator reads a Settlement, not a 404, and the conversation's next Turn
|
|
85
|
+
continues it.
|
|
81
86
|
- **A restart while the Agent's state is busy cuts the Turn and is no fault.** When the Agent's state is not at Rest —
|
|
82
87
|
its pick is outstanding, or work it started in runs below it — or its pick's answer was read and never reached the
|
|
83
88
|
model, the Agent's Actor raises no `agent.fault` for a restart: the Machine would route it and stop the work the Turn
|