@jr2/orchestrator 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -45,11 +45,13 @@ loudly narrated, safe to re-run:
|
|
|
45
45
|
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). Referenced-but-unmanaged Secrets
|
|
46
46
|
(`envFrom` refs, git credentials) are **preflighted**: `up` fails naming the missing Secret with the exact creation
|
|
47
47
|
hint — converting the `CreateContainerConfigError` hang into an immediate error. Sealed Secrets/External Secrets ride
|
|
48
|
-
this seam untouched: jr2's contract is "a Secret named X exists", however it got there.
|
|
49
|
-
is a held secret (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
this seam untouched: jr2's contract is "a Secret named X exists", however it got there. `up` also refuses a held
|
|
49
|
+
secret's name that is a key of an `envFrom` Secret, and names the Secret, the key and the held secret (ADR-0059, R14).
|
|
50
|
+
A credential the Harness uses is a held secret
|
|
51
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): `up` writes a literal value into
|
|
52
|
+
`jr2-held-secrets-<pod>` for each pod that names it, which only that pod's Custodian mounts, and checks that a
|
|
53
|
+
referenced Secret and key exist. Like the signing key, the held-secret CA (`jr2-held-ca`) is kept across converges —
|
|
54
|
+
minted on first need, never rotated by `up` — because a live pod trusts it.
|
|
53
55
|
- **Repos**: `up`'s walk of the registered Machines collects every bound Repo Slot (ADR-0049, ADR-0051) — to refuse an
|
|
54
56
|
open slot nobody bound, and to see which urls need a key. The Orchestrator creates one `Repo` resource per url at
|
|
55
57
|
boot, and the operator's cache agent clones each onto the nodes that need it (ADR-0004). There is no `repos` config
|
|
@@ -59,11 +61,17 @@ loudly narrated, safe to re-run:
|
|
|
59
61
|
in-cluster deploy keypair (the recommended default, public key printed to register), a local key, or one pasted on
|
|
60
62
|
stdin ([ADR-0047](0047-the-git-ssh-key-source-is-the-users-choice.md)) — declining bails. A personal key never enters
|
|
61
63
|
a cluster silently: only by that explicit, warned choice.
|
|
62
|
-
- **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
- **Landlock**: `up` refuses to converge while a Sandbox node lacks Landlock. It reads the pods of the Repo cache
|
|
65
|
+
agent's DaemonSet, which probe Landlock on each Sandbox node, within the rollout wait it already makes for the cache.
|
|
66
|
+
It is an ordinary converge failure: it names each node whose agent reports none, the probe's reason, and the fix (keep
|
|
67
|
+
the node out with `sandbox.nodeSelector` or a taint, ADR-0052, or enable Landlock); a run after the fix converges
|
|
68
|
+
([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)).
|
|
69
|
+
- **Provider preflight**: `up` checks each provider that a carried model names, and the tool list of each remote MCP
|
|
70
|
+
server that an Agent names (below). It checks from inside the cluster, with one trivial tool-call completion for a
|
|
71
|
+
model. An endpoint that the pods cannot reach, a wrong or missing key, or a vLLM without `--enable-auto-tool-choice`
|
|
72
|
+
then fails at converge time, not as `agent.fault` in a run. Reachability is the user's concern (`localhost` never
|
|
73
|
+
works from a pod; a LAN address does on kind). The check must take the path that the Harness takes. If it does not, a
|
|
74
|
+
pass means nothing.
|
|
67
75
|
- **One path for every provider.** `up` checks the custom provider (`harness.provider`, ADR-0018) and a provider of
|
|
68
76
|
pi's catalog (for example `anthropic/…`) in the same way. A key of either is a held secret, and only a Custodian can
|
|
69
77
|
use it ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). So no key enters a pod
|
|
@@ -75,14 +83,17 @@ loudly narrated, safe to re-run:
|
|
|
75
83
|
the config, each model's result says why, in words.
|
|
76
84
|
- **The check pod.** It is a Harness pod with no Harness in it. It has a Custodian and the stock Harness image's
|
|
77
85
|
`provider-preflight.ts`. The function that composes the Custodian of every Harness pod composes this Custodian too.
|
|
78
|
-
Its token is a value that the Orchestrator never signed.
|
|
79
|
-
|
|
86
|
+
Its token is a value that the Orchestrator never signed. Its Stand-ins are secret and its own: `jr2 up` mints them
|
|
87
|
+
with the signing key, as it does for the Instance Harness
|
|
88
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The check pod runs no Workspace
|
|
89
|
+
code, so it may hold them in env. `provider-preflight.ts` gets the env of the Harness container, with the Stand-ins
|
|
90
|
+
in it: the harness config, `harness.env`, `harness.envFrom`, the Stand-ins, the proxy and the trust. pi resolves
|
|
80
91
|
each model and uses its api, as in a Turn. The `harness.catalog` gateway applies. The key never enters the check
|
|
81
92
|
container, its argv or its log.
|
|
82
93
|
- **Its own objects.** The check pod reads no object of the Instance. `up` writes copies from the values on the host:
|
|
83
94
|
- a ConfigMap with the Custodian's files, the program and the harness config;
|
|
84
95
|
- a ConfigMap with the trust bundles;
|
|
85
|
-
- a Secret with the token and the literal values of the held secrets;
|
|
96
|
+
- a Secret with the token, the check pod's Stand-ins and the literal values of the held secrets;
|
|
86
97
|
- a Secret with the leaves;
|
|
87
98
|
- a Secret with the literal `harness.env` values.
|
|
88
99
|
|
|
@@ -96,8 +107,8 @@ loudly narrated, safe to re-run:
|
|
|
96
107
|
model runs where the Instance Harness runs.
|
|
97
108
|
- The held set: a pod that holds a secret sends its HTTPS through its Custodian, and a tunnel carries a host that no
|
|
98
109
|
secret is bound to. A pod that holds no secret sends its HTTPS direct. So the check pod holds the held set of the
|
|
99
|
-
first pod of its class, and the keys that the other pods of the class hold. A class
|
|
100
|
-
check pod that holds nothing.
|
|
110
|
+
first pod of its class, and the keys and the MCP server secrets that the other pods of the class hold. A class
|
|
111
|
+
that holds nothing gets a check pod that holds nothing.
|
|
101
112
|
|
|
102
113
|
When no pod of the class holds a key of the provider, the check pod holds none. Then pi refuses the call
|
|
103
114
|
(`not configured`), and the converge fails. A signing credential in `harness.env` reaches the check pod, as it
|
|
@@ -105,6 +116,16 @@ loudly narrated, safe to re-run:
|
|
|
105
116
|
knows, and for which jr2 knows no key name, is checked with no held key, as a Turn runs it. The check pod has the
|
|
106
117
|
priority class of the Sandbox tier, as the Harness pods have (ADR-0060).
|
|
107
118
|
|
|
119
|
+
- **MCP servers.** The check pod also checks each remote MCP server that an Agent of its class names
|
|
120
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). It
|
|
121
|
+
reads the server's `tools/list` through its Custodian, with the Stand-in of the server's secret, as a Turn reads it.
|
|
122
|
+
An allowlisted tool name that the list does not have fails the converge, and the result names the server and the
|
|
123
|
+
tool. A server that refuses the request (a 401 or a 403: a wrong or expired token) fails the converge, as a model
|
|
124
|
+
key that the provider refuses does. A server that does not answer is a warning: the first Turn of a conversation
|
|
125
|
+
fails with a named error when the server does not answer then. A stdio server is not checked: it runs as Workspace
|
|
126
|
+
code in the Sandbox, and the check pod runs none. Each server has 60 s for its whole listing, after the models. Two
|
|
127
|
+
literals set it, `PREFLIGHT_MCP_BUDGET_SECONDS` in `preflight-line.ts` and `CHECK_MCP_SECONDS` in `up.ts`, and a
|
|
128
|
+
test holds them equal. The pod writes the list, and `up` compares it with the allowlists.
|
|
108
129
|
- **The request.** The check's request shares these with a Turn's: the model, its api, its endpoint and key, the
|
|
109
130
|
completion limit (what pi-ai sends for a Turn), the retries, no forced tool, and the level of a definition with no
|
|
110
131
|
`thinkingLevel` (`off`: no `reasoning` is sent). It does not share these: it has no system prompt, one tool, no
|
|
@@ -185,16 +206,18 @@ loudly narrated, safe to re-run:
|
|
|
185
206
|
|
|
186
207
|
It never deletes the objects of another Instance.
|
|
187
208
|
|
|
188
|
-
- **Skipping it.** `jr2 up --skip-provider-preflight` runs no check pod
|
|
189
|
-
example when a model loads for longer than the check waits.
|
|
190
|
-
fixed. The skip is never silent: `up` writes one warning line,
|
|
191
|
-
|
|
192
|
-
because no flag of `up` has one (CI gives `--yes`
|
|
193
|
-
`agent.fault`.
|
|
209
|
+
- **Skipping it.** `jr2 up --skip-provider-preflight` runs no check pod, so it checks no model and no MCP server's
|
|
210
|
+
tool list. A check can fail when the model works, for example when a model loads for longer than the check waits.
|
|
211
|
+
That fault must not stop every converge until jr2 is fixed. The skip is never silent: `up` writes one warning line,
|
|
212
|
+
which names each model and each MCP server that it did not check. `up` still deletes the leftovers of earlier runs,
|
|
213
|
+
because their Secrets can hold a key. The flag has no config key, because no flag of `up` has one (CI gives `--yes`
|
|
214
|
+
as a flag). A model that does not work then fails in a run, as `agent.fault`.
|
|
194
215
|
- **A limit.** The check pod has labels of its own. It does not get the labels of a Harness pod: the Instance Harness
|
|
195
216
|
Service selects its pods by label, and would send Turns to the check pod. So a NetworkPolicy that selects Harness
|
|
196
217
|
pods by label does not select the check pod. When such a policy closes the egress of the Harness pods, the check can
|
|
197
|
-
pass while a Turn fails.
|
|
218
|
+
pass while a Turn fails. jr2's own egress policy, which closes the cloud metadata addresses
|
|
219
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)), gives the check pod one
|
|
220
|
+
of its own, so it does not have this limit.
|
|
198
221
|
|
|
199
222
|
**`jr2 down`** removes the instance from the cluster. It asks for confirmation unless `--yes` is given (for CI). `--all`
|
|
200
223
|
takes the operator too.
|
|
@@ -217,8 +240,9 @@ takes the operator too.
|
|
|
217
240
|
instance error with "not deployed here — right context?". This is the guardrail against silently pointing at the wrong
|
|
218
241
|
cluster, without a state file that can itself go stale.
|
|
219
242
|
- **CLI transport**: run-verbs port-forward the Orchestrator Service via the kube API for the duration of the command
|
|
220
|
-
and read the Instance token from its Secret — kube RBAC is the real gate. `--url`/`JR2_URL` (+ token env
|
|
221
|
-
|
|
243
|
+
and read the Instance token from its Secret — kube RBAC is the real gate. `--url`/`JR2_URL` (+ token env, any Caller's
|
|
244
|
+
credential, [ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)) is the escape
|
|
245
|
+
hatch for ingress-exposed instances and callers without cluster creds.
|
|
222
246
|
- **A dead ADDRESS and an absent IDENTITY are two faults, and resolution names which.** They share a verb and a context
|
|
223
247
|
but not a fix: one is the network, the other is `jr2 up`. A cluster that answered and holds no instance Secret is "not
|
|
224
248
|
deployed here — right context?"; a cluster that could not answer at all is "cannot reach the cluster — context X: …",
|
|
@@ -262,9 +286,11 @@ from env.
|
|
|
262
286
|
|
|
263
287
|
## Consequences
|
|
264
288
|
|
|
265
|
-
- The everyday story is two commands ever: `jr2 init`, `jr2 up`. The
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
- The everyday story is two commands ever: `jr2 init`, `jr2 up`. The rest of the CLI is in ADR-0009's table, and the
|
|
290
|
+
workspace verbs `workspace ls|shell|forward|rm` are among them.
|
|
291
|
+
- **The workspace verbs are not run-verbs.** They address the Sandbox CRs of the current context with its kube
|
|
292
|
+
credentials alone: no port-forward to the Orchestrator, no Instance token and no Caller scope. So they work when the
|
|
293
|
+
Orchestrator is down, and kube RBAC on the Sandbox CRs and their pods is the whole gate.
|
|
268
294
|
- **Simplification must not surprise**: `up` prompts exactly when meeting a cluster that isn't yet home, and run-verbs
|
|
269
295
|
print the context they're talking to — the ambient-context magic stays visible.
|
|
270
296
|
- The snapshot store (sqlite) moves onto a PVC in the instance's namespace; `.jr2/` shrinks to scratch.
|
|
@@ -31,7 +31,8 @@ end. And a restarted Harness answered a `continue` with no memory of the convers
|
|
|
31
31
|
Sandbox goes to the terminal phase **`Lost`**, with the pod's own reason and message (for a deleted pod, the last it
|
|
32
32
|
saw). It never returns to `Pending` and is never given a second pod: `work` is an `emptyDir`, so a new pod could
|
|
33
33
|
continue nothing, and it would hold a whole Size for nobody while hiding the loss. The CR stays, so `jr2 status` can
|
|
34
|
-
say why, until teardown or the idle GC.
|
|
34
|
+
say why, until teardown or the idle GC. A Sandbox delete waits for its pod, because a retry can take the name of a pod
|
|
35
|
+
that is still going away.
|
|
35
36
|
- **The operator judges identity, because it knows which pod it created.** It still publishes `status.podUID`; the
|
|
36
37
|
Orchestrator reads `Lost`, not a UID comparison.
|
|
37
38
|
- **The watch answers; the lease asserts.** The Orchestrator watches its Sandboxes
|
|
@@ -56,6 +57,19 @@ end. And a restarted Harness answered a `continue` with no memory of the convers
|
|
|
56
57
|
attached pod. Its lifetime is the state's lifetime, which xstate already manages: it re-invokes on snapshot restore,
|
|
57
58
|
so restore-reconcile stops being a special case and becomes the first tick of the normal loop; and it stops on every
|
|
58
59
|
exit — body final, run stopped, run faulted.
|
|
60
|
+
- **The Lease marks its hold on the CR.** The CR's first write sets the annotation `jr2.dev/hold: held`, and each
|
|
61
|
+
renewal writes it again. When the Lease stops for the body's final state, a fault, a cancel or a parent that leaves
|
|
62
|
+
the Workspace's state, it sets `jr2.dev/hold: released` and renews no more. A defect that a parent catches does not
|
|
63
|
+
stop the Lease, because xstate stops no child of a Machine that ended in error: at its next renewal the Lease sees
|
|
64
|
+
that its Workspace is over, and sets `released` then. A Lease does not set `released` on a name that a live Workspace
|
|
65
|
+
of the run holds: a parent that catches the defect can invoke the same Machine again, and the new Workspace gets the
|
|
66
|
+
same Sandbox name and a Lease of its own. A step of the wrapper from one state to the next writes nothing: the next
|
|
67
|
+
state has a Lease of its own. A park (`RunHost.stop`, [ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)) and a
|
|
68
|
+
process shutdown write nothing, so a crash keeps the mark `held` and the restored run's Lease writes it again. A
|
|
69
|
+
restore that ends a run `drifted` or `lost` without hosting it starts no Lease, so the Orchestrator sets `released` on
|
|
70
|
+
each CR with that run's `jr2.dev/run` label. So `jr2 workspace rm` tells a held Sandbox from a free one with the
|
|
71
|
+
cluster alone ([ADR-0009](0009-cli-and-instance-interface.md)): the keepalive's age cannot tell it, because a renewal
|
|
72
|
+
can be late by more than one interval and a few missed renewals are survivable.
|
|
59
73
|
|
|
60
74
|
That last point deletes machinery rather than adding it. Three mechanisms with three different owners and lifetimes —
|
|
61
75
|
the process-global heartbeat map, the one-shot probe, and `release(runId)` (which label-queried the cluster to stop
|
|
@@ -97,4 +111,4 @@ behavior `release()` was written to produce is emergent.
|
|
|
97
111
|
independently. This falls out of invoking it beside the body rather than owning it at the host.
|
|
98
112
|
- `SandboxPort` has no optional method: `renew` is a write, `continuity` is a subscription to the watch, and
|
|
99
113
|
`memoryFault` (ADR-0061) answers undefined from a backend that cannot see the container. The `jr2.dev/run` label
|
|
100
|
-
survives for `jr2
|
|
114
|
+
survives for the `jr2 workspace` verbs and as the watch's selector, no longer load-bearing for lease bookkeeping.
|
|
@@ -34,8 +34,8 @@ can get away with fetching once, because a person can press a button; an API can
|
|
|
34
34
|
so there is no gap to detect and no cursor to resume from. Same reconciliation idiom as the Lease (ADR-0021) and
|
|
35
35
|
`jr2 up` (ADR-0019): say the whole truth, repeatedly, and let the reader converge.
|
|
36
36
|
- **Two granularities, one vocabulary.** The per-run feed stays — tracking exactly one run is a legitimate thing to
|
|
37
|
-
want, and it is what `jr2 run` and `jr2
|
|
38
|
-
|
|
37
|
+
want, and it is what `jr2 run` and `jr2 watch` do. Every run-scoped frame carries `runId` even where the route makes
|
|
38
|
+
it redundant, so one parser serves both.
|
|
39
39
|
- **`gone` is a fact, not an inference.** A run can leave the live set _without_ a terminal status: `stop()`
|
|
40
40
|
deliberately leaves the stored status `"live"` so a later `restore()` picks the run back up. A client watching for
|
|
41
41
|
`status === "done"` would show a stopped run as live forever, so departure is stated outright.
|
|
@@ -48,7 +48,7 @@ can get away with fetching once, because a person can press a button; an API can
|
|
|
48
48
|
which is why it is wired to the handler's exit.
|
|
49
49
|
- **A settled run is the client's own memory.** A run appears in the page's `settled` list iff _this page_ watched it
|
|
50
50
|
leave. After a hard reload they are gone, and that is correct rather than a defect: serving a finished run's state to
|
|
51
|
-
an unauthenticated reader is a read-through, which stays behind the
|
|
51
|
+
an unauthenticated reader is a read-through, which stays behind the `read` scope on `/runs/:id` (ADR-0014).
|
|
52
52
|
- **`jr2 visualize` is deleted.** Its job was to find an orchestrator, forward a port, and open a browser. With the page
|
|
53
53
|
now useful on its own, that is one `kubectl port-forward` the reader already knows how to run — and the command's
|
|
54
54
|
ephemeral-fallback path was quietly forwarding to a _different_ instance than the deployed one. Its one unique piece
|
|
@@ -61,7 +61,7 @@ can get away with fetching once, because a person can press a button; an API can
|
|
|
61
61
|
tick, and a reader that keeps up drains them before any ping judges it. Closing is safe because the feed is
|
|
62
62
|
level-triggered: the client reconnects and receives the whole current truth, having missed no state — only activity
|
|
63
63
|
(Emits, retries) that fell in the gap. A page reconnects with EventSource's own retry; the CLI (`jr2 run`,
|
|
64
|
-
`jr2
|
|
64
|
+
`jr2 watch`) re-attaches any feed that ends before a terminal status, and never reads such an end as the run's —
|
|
65
65
|
except `gone`: a run stopped before it settled (parked, ADR-0025) reads through as `active`, so its one-shot feed says
|
|
66
66
|
`gone` after the status, and the CLI ends there instead of re-attaching in a loop. The server keeps no per-client
|
|
67
67
|
queue and no slow-client accounting beyond counting the subscriber's writes (scaling review R10, 2026-09-30).
|
|
@@ -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,33 +179,47 @@ 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
|
|
|
123
189
|
- **Per-`agentRun` containers, or a pod per Agent.** Rejected on lifetime before merit: the container list is fixed when
|
|
124
190
|
the CR is created, before the body has run, while `agentRun` is a per-turn invocation. Pre-declaring per _Agent_ is
|
|
125
|
-
possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
(ADR-0004
|
|
191
|
+
possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005). It
|
|
192
|
+
buys no isolation — containers in a pod share the network namespace, so it does not even fix the port contention that
|
|
193
|
+
motivates it (ADR-0005:31) — and it does not remove the git contention, because the containers share one `/work` and
|
|
194
|
+
one clone (ADR-0004). What it actually buys is `kubectl logs -c <agent>`: a container, to avoid a prefix.
|
|
129
195
|
- **A trace from the Custodian.** Tempting, and wrong on the facts. The Custodian carries the Menu's reads and the
|
|
130
196
|
Agent's picks — a handful of deliveries per turn — and the model calls, and it logs names only, never a body
|
|
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,24 +233,25 @@ 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
|
-
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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.
|
|
243
|
+
- **The conversations of concurrent Agents interleave in one log.** The author chooses how many Agents work in a
|
|
244
|
+
Workspace ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), so two conversations can
|
|
245
|
+
print at one time on one container's stdout. The prefix keeps them apart: every line carries its label and its iid, so
|
|
246
|
+
a reader keeps one conversation's lines by its iid.
|
|
167
247
|
- **Truncation is load-bearing, not cosmetic.** Kubelet rotates container logs at 10Mi by default and drops the
|
|
168
248
|
remainder. jr's filter capped `Bash` at 100 chars and unknown tools at 150 and reduced `Write`/`Edit` to a path; that
|
|
169
249
|
was readability, and it is now also what keeps a long turn inside the window.
|
|
170
|
-
- **Live tails may miss partial output.** Text and thinking deltas are best-effort live progress, with
|
|
171
|
-
authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive.
|
|
172
|
-
watcher; anything wanting exactness reads the completed message.
|
|
173
|
-
- **`jr2 logs`
|
|
174
|
-
|
|
175
|
-
- **The
|
|
176
|
-
|
|
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
|
@@ -34,8 +34,7 @@ state. A prompt fix cannot be relied on and cannot even be shown to have worked.
|
|
|
34
34
|
Workspace; the Machine's states are sequential, but the Agents are not. Throughout `reviewing`, the orphaned coder was
|
|
35
35
|
still live in the worktree the reviewer was reviewing — and still live at `humanReview`, the park that deliberately
|
|
36
36
|
retains the Sandbox so a human can exec in and inspect the diff. In the runs we watched, the orphan only read. Nothing
|
|
37
|
-
prevented it writing. An un-ended turn is
|
|
38
|
-
single-threaded.
|
|
37
|
+
prevented it writing. An un-ended turn is a concurrent writer in a Workspace that the Machine did not put there.
|
|
39
38
|
|
|
40
39
|
## Decision
|
|
41
40
|
|
|
@@ -190,41 +189,40 @@ single-threaded.
|
|
|
190
189
|
is local to its Machine: two Machines of one tree, and the workers of a pool, which run one body, may each have a
|
|
191
190
|
child of one id, and one never reads or changes the other's entry. A child started on an event that names no Agent
|
|
192
191
|
anywhere else is no Agent's. The other Agent's pick keeps its default answer, and when the Agent the event names has
|
|
193
|
-
no pick being answered, it sets nothing.
|
|
194
|
-
invoke as the pick that sent the event or started the
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
enqueued
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
state
|
|
226
|
-
|
|
227
|
-
was, and is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An
|
|
192
|
+
no pick being answered, it sets nothing. One Agent slot can be invoked by two states that are not active together (two
|
|
193
|
+
states one after the other). Then it shapes the pick at the same invoke as the pick that sent the event or started the
|
|
194
|
+
child, and nothing otherwise: the done event of a child that the earlier state's pick started, after that pick's Turn
|
|
195
|
+
ended (its child runs on), shapes no pick, and the later Turn's pick keeps its default answer. When the event names no
|
|
196
|
+
Agent (a timer, a Gate's answer, an outside send, the done event of a child no Agent's pick started), it shapes the
|
|
197
|
+
pick in its own send; outside one, it shapes the one such pick outstanding only while one Agent's state is active.
|
|
198
|
+
Outside a pick's own send, while two Agents' states are active, it sets nothing, whatever is outstanding: the event
|
|
199
|
+
cannot say whose pick it is. On an entry or an exit below no Agent's state it sets nothing, whatever is outstanding,
|
|
200
|
+
and the load walk warns, naming the Agent states it could be moved under: an entry or exit there is no transition an
|
|
201
|
+
Agent's event takes. One action object written in two places runs from each by that place's rule alone, never by their
|
|
202
|
+
sum: written below two Agents' states, from A's state it shapes only A's pick and from B's only B's; written below A's
|
|
203
|
+
state and on a transition below none, from A's state it shapes only A's pick and from the transition the pick of the
|
|
204
|
+
Agent the event names; written on an entry or exit below no Agent's state as well, it sets nothing as that entry or
|
|
205
|
+
exit. An `answer` that an `enqueueActions` enqueues, at any depth, runs from the place the enqueuing action is
|
|
206
|
+
written, by that place's rule: enqueued on an entry or exit below no Agent's state it sets nothing, and enqueued below
|
|
207
|
+
an Agent's state it shapes only that Agent's pick. The load walk cannot see inside an `enqueueActions`, so it does not
|
|
208
|
+
warn on one enqueued on an entry or exit below no Agent's state. An `answer` that runs while no pick of that Agent is
|
|
209
|
+
being answered (a state's first entry, a later Turn's re-entry, another Agent's region of a parallel state) sets
|
|
210
|
+
nothing. A reply comes from a child invoked on an active state below the Agent's state, or from a party whose Gate
|
|
211
|
+
invoke id or Agent slot is invoked there (its sender, ADR-0015). The events the Machine sent itself (a `raise`, a
|
|
212
|
+
`sendTo(self)`, a timer) are no reply, and neither is an event from outside that set (a Gate in a sibling region, a
|
|
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
|
+
defect, so by default its message never reaches the model. The operator reads it: while a pick is outstanding, an
|
|
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 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
|
|
228
226
|
outstanding pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain
|
|
229
227
|
receipt with its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is
|
|
230
228
|
the state it was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a
|
|
@@ -252,7 +250,7 @@ single-threaded.
|
|
|
252
250
|
since the pick, the answer carries none. A restored hold is the Turn of the first registration of its address in the
|
|
253
251
|
restored tree: a restore that ends that Turn and starts another at the same address before anything settles answers
|
|
254
252
|
the pick with `turnComplete`. There is no default time bound on a held Turn; the Console shows each outstanding pick
|
|
255
|
-
"
|
|
253
|
+
"outstanding since".
|
|
256
254
|
- **The one exception is the Orchestrator ending it for its own reasons**, which is ADR-0007's requirement and exactly
|
|
257
255
|
one place: `RunHost.stop()`. Process shutdown never stops actors at all (`instance.ts` calls `host.close()`, which
|
|
258
256
|
ends observation feeds and nothing else), and restore is a fresh process. So "the host did this" is a flag it sets,
|