@jr2/orchestrator 0.4.1 → 0.6.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/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +206 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- 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-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +24 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
- 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 +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
package/src/actor.ts
CHANGED
|
@@ -50,14 +50,16 @@ import {
|
|
|
50
50
|
type ThinkingLevel,
|
|
51
51
|
type WorkspaceAccess,
|
|
52
52
|
} from "./agent.ts";
|
|
53
|
+
import type { AnyFramePath } from "./worktree.ts";
|
|
53
54
|
import { ambientHandlesFor, type AmbientHandles } from "./ambient.ts";
|
|
54
55
|
import type { HarnessRestarts, MemoryKill } from "./workspace.ts";
|
|
55
56
|
import { INSTANCE_HARNESS_SERVICE } from "./names.ts";
|
|
56
57
|
import { SENDER_KEY } from "@jr2/agent-protocol";
|
|
57
|
-
import { agentAddress, continuedIid, allowedPicks, resolveAccepts, runBindingOf } from "./registration.ts";
|
|
58
|
+
import { agentAddress, agentSlot, continuedIid, allowedPicks, resolveAccepts, runBindingOf } from "./registration.ts";
|
|
58
59
|
import type { NoticeScope } from "./notices.ts";
|
|
59
60
|
import { restOf } from "./rest.ts";
|
|
60
|
-
import { SUBMISSION_HARNESS_RESTARTED, SUBMISSION_RUNAWAY, type Notice } from "./wire.ts";
|
|
61
|
+
import { SUBMISSION_HARNESS_RESTARTED, SUBMISSION_RUNAWAY, type Notice, type PodMemoryKill } from "./wire.ts";
|
|
62
|
+
import type { AbortCause } from "./wire.ts";
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* One admitted Submission — the durable re-attach handle (ADR-0016). The wire fields are
|
|
@@ -80,6 +82,13 @@ export type AgentAdmission = {
|
|
|
80
82
|
* aborts the LIVE conversation, never the dead original the ledger is keyed by.
|
|
81
83
|
*/
|
|
82
84
|
instanceId?: string;
|
|
85
|
+
/**
|
|
86
|
+
* The Turn that ledgered this admission — the input's `turnId`, stamped by the ACTOR at ledger
|
|
87
|
+
* time. The ledger is keyed by `instanceId`, which a continued Turn shares with the Turn before
|
|
88
|
+
* it, so a restore compares this with the restored input's own: a mismatch is an admission of an
|
|
89
|
+
* EARLIER Turn, and the restored Turn was not admitted yet (ADR-0016).
|
|
90
|
+
*/
|
|
91
|
+
turnId?: string;
|
|
83
92
|
/**
|
|
84
93
|
* The Harness container's restart count when this admission was answered (ADR-0021) — stamped by
|
|
85
94
|
* the ACTOR, for a Turn in a Sandbox Workspace whose watch had spoken. A count above it means
|
|
@@ -87,6 +96,25 @@ export type AgentAdmission = {
|
|
|
87
96
|
* mark.
|
|
88
97
|
*/
|
|
89
98
|
harnessRestarts?: number;
|
|
99
|
+
/**
|
|
100
|
+
* When this admission's Turn started — stamped by the ACTOR on every admission of a Turn in a
|
|
101
|
+
* Workspace, so a restore reads back the real start, not the restore's: a kernel kill is held against it
|
|
102
|
+
* (ADR-0061), and a guard kill too when the Turn's first read of the pod's kills failed.
|
|
103
|
+
*/
|
|
104
|
+
turnStarted?: string;
|
|
105
|
+
/**
|
|
106
|
+
* The newest guard kill in the pod's list when the Turn's first read of it was answered
|
|
107
|
+
* (ADR-0062), or {@link NO_KILLS} for an empty list — stamped by the ACTOR on every admission of
|
|
108
|
+
* a Turn in a Workspace. A nudge or a wake carries only kills after it, so it never tells the Agent of a kill
|
|
109
|
+
* from before its Turn. Absent when that first read failed.
|
|
110
|
+
*/
|
|
111
|
+
killMark?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The Turn's task, its Frame's prompt — stamped by the ACTOR on every admission of the Turn,
|
|
114
|
+
* because a restore drops `prompt` from the input: a re-admission onto a conversation the
|
|
115
|
+
* Harness lost must send the task again, and has no other copy of it (ADR-0062).
|
|
116
|
+
*/
|
|
117
|
+
task?: string;
|
|
90
118
|
/**
|
|
91
119
|
* The Agent and the Harness base URL this admission was answered by — stamped by the ACTOR at
|
|
92
120
|
* ledger time, like `instanceId`. Together with `instanceId` they name the conversation on its
|
|
@@ -116,15 +144,15 @@ export type AgentTurnInput = {
|
|
|
116
144
|
/**
|
|
117
145
|
* The other half of the FRAME (ADR-0057) — WHERE this Turn works: the absolute directory the
|
|
118
146
|
* Harness roots the Working tools at. Per Turn by nature, which is why no definition names one:
|
|
119
|
-
* a Worktree path (`/work/<slot>/<
|
|
147
|
+
* a Worktree path (`/work/<slot>/<name>`) exists only once a Machine has cut it (ADR-0075).
|
|
120
148
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
149
|
+
* Required under Repo Slots: a Turn with no `cwd` there is REFUSED before admission, and a Turn
|
|
150
|
+
* that wants `/work` states it. A `workspace: "none"` Agent frames no directory (it has no Working
|
|
151
|
+
* tools to root — ADR-0028), and a Workspace with no Repo Slot, or a run with no Workspace at all,
|
|
152
|
+
* frames nothing and the Harness keeps `/work`. A relative path is refused at admission, by the
|
|
153
|
+
* Harness.
|
|
126
154
|
*/
|
|
127
|
-
cwd?:
|
|
155
|
+
cwd?: AnyFramePath;
|
|
128
156
|
/**
|
|
129
157
|
* This turn's DIALS (ADR-0018) — how hard to run, layered over the definition's own
|
|
130
158
|
* values. One definition value may be carried by several Machines, so the same persona
|
|
@@ -137,6 +165,8 @@ export type AgentTurnInput = {
|
|
|
137
165
|
* ADR-0028's containment claim, which per-invocation escalation would void.
|
|
138
166
|
*/
|
|
139
167
|
model?: string;
|
|
168
|
+
/** Omitted → the definition's, else `off`. A model with no `off` runs at the level its provider
|
|
169
|
+
* chooses; the printed conversation names it. */
|
|
140
170
|
thinkingLevel?: ThinkingLevel;
|
|
141
171
|
/**
|
|
142
172
|
* Continue this Agent's ONE conversation in this Machine instance (ADR-0016, ADR-0057) — the
|
|
@@ -182,6 +212,14 @@ export type AgentRunInput = {
|
|
|
182
212
|
* is what the Harness route, the minted iid, the markers and the telemetry all name. */
|
|
183
213
|
agentName: string;
|
|
184
214
|
instanceId: string;
|
|
215
|
+
/**
|
|
216
|
+
* This Turn's Turn ID — minted by the input mapper, one per invocation, and persisted with the
|
|
217
|
+
* input (ADR-0016). Two Turns that continue one conversation share `instanceId`, the ledger's
|
|
218
|
+
* key, and not this: the actor stamps it on every admission it ledgers, and a restore
|
|
219
|
+
* re-attaches only an admission that this Turn ledgered. Absent (a plain `setup()` machine that
|
|
220
|
+
* passes finalized input), the key alone decides, as before.
|
|
221
|
+
*/
|
|
222
|
+
turnId?: string;
|
|
185
223
|
/**
|
|
186
224
|
* The Harness base URL, EXPLICIT (ADR-0016): only for a workspace-less run (the mechanics
|
|
187
225
|
* tier's stub Harness, a dev endpoint — just a URL, ADR-0011). Inside a `workspace()` leave it
|
|
@@ -235,13 +273,26 @@ export type AgentRunInput = {
|
|
|
235
273
|
menu?: readonly string[];
|
|
236
274
|
};
|
|
237
275
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
// Sent when the run is out of options: the Submission settled failed/aborted (infra fault after
|
|
277
|
+
// the turn's own provider retries — ADR-0027), the no-signal nudge budget ran dry, or the runaway
|
|
278
|
+
// reroll budget did (ADR-0035). The ONE terminal event (ADR-0016) — where it routes is workflow
|
|
279
|
+
// policy.
|
|
280
|
+
/** jr2 sends `agent.fault` to the Machine when an Agent's Turn cannot go on: the Harness failed
|
|
281
|
+
* the Turn, or the Agent ended its Turns with no pick after every nudge. The Machine decides what
|
|
282
|
+
* to do next. Route it in the state that invokes the Agent, or in a state above it, with
|
|
283
|
+
* `on: { "agent.fault": … }`; one route on the root is a route for each state. `jr2 check` and
|
|
284
|
+
* `jr2 up` refuse a state that invokes an Agent, or an Open body that can run one, with no route for
|
|
285
|
+
* it. When the event comes and no transition takes it (the route's guard is false), the run ends
|
|
286
|
+
* `error`, with the event, the state and the reason in its fault. In a Chart the route is optional:
|
|
287
|
+
* when no transition of the Chart takes the event, the Open body sends it, unchanged, to the Machine
|
|
288
|
+
* that invoked the body, whose route takes it. */
|
|
242
289
|
export type FaultTelemetry = {
|
|
243
290
|
type: "agent.fault";
|
|
291
|
+
/** The Instance ID of the Agent's conversation that failed: the same id on each Turn that
|
|
292
|
+
* continues it. */
|
|
244
293
|
instanceId: string;
|
|
294
|
+
/** Why the Turn failed, as text for a person or a log: the Harness's error, or that the Agent
|
|
295
|
+
* ended its Turns without a pick after every nudge. Route on the event, not on this text. */
|
|
245
296
|
reason: string;
|
|
246
297
|
};
|
|
247
298
|
|
|
@@ -267,24 +318,39 @@ export interface AgentRunPort {
|
|
|
267
318
|
admit(input: AgentRunInput, opts: AgentAdmitOptions): Promise<AgentAdmission>;
|
|
268
319
|
/**
|
|
269
320
|
* Follow an admitted submission until it settles. Resolving means the submission completed;
|
|
270
|
-
* rejecting means it settled failed/aborted (or the conversation is gone).
|
|
321
|
+
* rejecting means it settled failed/ended/aborted (or the conversation is gone).
|
|
271
322
|
*/
|
|
272
323
|
settle(admission: AgentAdmission, opts?: AgentSettleOptions): Promise<void>;
|
|
273
324
|
/**
|
|
274
325
|
* Does the Harness still hold this conversation (ADR-0062)? Asked before a continued Turn whose
|
|
275
326
|
* conversation was admitted before: a Harness that restarted between the Turns lost it with no
|
|
276
327
|
* fault to say so, and the Turn is told its conversation is new. Answers `true` whenever it
|
|
277
|
-
* cannot tell — unknown is never loss.
|
|
328
|
+
* cannot tell — unknown is never loss. A wrong `true` is caught at the admission that follows:
|
|
329
|
+
* it says `kept`, and a Harness that keeps no such conversation refuses it.
|
|
278
330
|
*/
|
|
279
331
|
holds(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<boolean>;
|
|
332
|
+
/**
|
|
333
|
+
* The pod's guard kills (ADR-0061, ADR-0062), every one with `told`: the conversations whose
|
|
334
|
+
* running `bash` call answered its verdict. A stream is read only while a Submission's wait
|
|
335
|
+
* runs, and a kill while none runs, or after the wait stopped, reaches no reader there. Asked before a Turn's admission into a
|
|
336
|
+
* Workspace. A read changes nothing on the Harness; the notice ledger keeps each kill's id.
|
|
337
|
+
* Answers `undefined` whenever it cannot tell — not `[]`, which says the list is empty — and the
|
|
338
|
+
* kill stays on the Harness for the next read.
|
|
339
|
+
*/
|
|
340
|
+
memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined>;
|
|
280
341
|
/**
|
|
281
342
|
* End the instance's in-flight (and queued) work — the turn is over (ADR-0024). Resolving means
|
|
282
343
|
* the intent is RECORDED, not that the submission has settled; jr2 never observes that outcome,
|
|
283
344
|
* because the actor is already stopped by the time this is called. The actor passes no `signal`
|
|
284
345
|
* for exactly that reason — its own controller is already aborted — but the option is here for
|
|
285
|
-
* parity with the other two.
|
|
346
|
+
* parity with the other two. `cause: "pick"` says the Turn's own pick ended it, so the Harness
|
|
347
|
+
* settles the Submission `ended`; with no cause it settles `aborted`.
|
|
286
348
|
*/
|
|
287
|
-
abort(
|
|
349
|
+
abort(
|
|
350
|
+
agentName: string,
|
|
351
|
+
instanceId: string,
|
|
352
|
+
opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
|
|
353
|
+
): Promise<void>;
|
|
288
354
|
}
|
|
289
355
|
|
|
290
356
|
/** What every admission carries beside the input: the Agent definition this Turn runs (ADR-0049),
|
|
@@ -293,22 +359,66 @@ export interface AgentRunPort {
|
|
|
293
359
|
* Machine edit that changed it. */
|
|
294
360
|
export type AgentAdmitOptions = {
|
|
295
361
|
definition: AgentDefinition;
|
|
362
|
+
/** This Agent's slot (ADR-0076): the actor path of its Machine and its agent name — the
|
|
363
|
+
* registration's. The Harness keeps it with the conversation and states it on each control
|
|
364
|
+
* request; it never derives it from an Instance ID, whose actor ids may hold a `/`. */
|
|
365
|
+
slot: string;
|
|
296
366
|
signal?: AbortSignal;
|
|
297
367
|
/** The pending notices in this admission's scope (ADR-0062), for the Briefing's Turn part. Not
|
|
298
368
|
* on the persisted input either: they are the Orchestrator's, and delivered once. */
|
|
299
369
|
notices?: Notice[];
|
|
370
|
+
/** The admission is for a conversation that `holds` said the Harness keeps (ADR-0062): the
|
|
371
|
+
* re-admission after a cut, and a continued Turn. That "kept" can be a timeout's, so a Harness
|
|
372
|
+
* that keeps none refuses it and seats none; `admit` then rejects with an error whose `notKept`
|
|
373
|
+
* is true (the wire client's `ConversationNotKept`). */
|
|
374
|
+
kept?: boolean;
|
|
300
375
|
};
|
|
301
376
|
|
|
302
|
-
/** A memory kill by the Harness's own guard (ADR-0061, layer 4), as the
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
|
|
377
|
+
/** A memory kill by the Harness's own guard (ADR-0061, layer 4), as the Orchestrator reads it on
|
|
378
|
+
* an updates stream — the twin of {@link ListedKill}, which it reads in the pod's list:
|
|
379
|
+
* the guard's `peak` and `limit` — absent when what the stream said was not a size — and the
|
|
380
|
+
* kill's `id`, the same on every stream that carries it and in the pod's list
|
|
381
|
+
* (`AgentRunPort.memoryKills`), so every witness of one kill is one notice. `legacy`: an older
|
|
382
|
+
* Harness's event, which has no id; `id` is then the source the Orchestrator raised such an event
|
|
383
|
+
* with before, its place on the stream, so a replayed read is still the same kill. */
|
|
384
|
+
export type StreamedKill = { id: string; legacy?: true; peak?: string; limit?: string };
|
|
385
|
+
|
|
386
|
+
/** A kill read from the pod's list, as the port answers it: the wire's, but `peak` and `limit` are
|
|
387
|
+
* absent when the list's were not sizes — the kill is kept, and worded with no sizes (ADR-0062). */
|
|
388
|
+
export type ListedKill = Omit<PodMemoryKill, "peak" | "limit"> & { peak?: string; limit?: string };
|
|
389
|
+
|
|
390
|
+
/** The source a guard kill's notice is raised with: the Workspace and the kill's id. Both paths use
|
|
391
|
+
* it — the stream and the pod's list — so the ledger's `seen` makes them one notice. */
|
|
392
|
+
const guardSource = (sandbox: string, id: string): string => `${sandbox}@guard:${id}`;
|
|
393
|
+
|
|
394
|
+
/** The source a kernel kill's notice is raised with (ADR-0061, layer 5): the Workspace and when the
|
|
395
|
+
* Harness container ended, which names that one end. Both paths use it — the fault of a Turn the
|
|
396
|
+
* kill ended, and the read before an admission — so the ledger's `seen` makes them one notice. */
|
|
397
|
+
const kernelSource = (sandbox: string, at: string): string => `${sandbox}@${at}`;
|
|
398
|
+
|
|
399
|
+
/** How long the read of the pod's kills may take before it counts as none. */
|
|
400
|
+
const MEMORY_KILLS_READ_MS = 5_000;
|
|
401
|
+
|
|
402
|
+
/** How long "does the Harness still hold this conversation?" is asked again before the answer is
|
|
403
|
+
* taken as "held" — unknown is never loss, but a container that is starting is not unknown yet. */
|
|
404
|
+
const HOLDS_READ_MS = 30_000;
|
|
405
|
+
|
|
406
|
+
/** A `killMark` for a list that was empty: every kill read after it is the Turn's. Kill ids start
|
|
407
|
+
* `k-`, so it is never one. */
|
|
408
|
+
export const NO_KILLS = "none";
|
|
409
|
+
|
|
410
|
+
/** How far the pod's clock may run behind this one when a kill's `at` is held against a Turn's
|
|
411
|
+
* start — only when the Turn has no `killMark`: a kill stamped up to this much before the start
|
|
412
|
+
* still counts as in the Turn, so a slow pod clock does not hide a kill of this Turn. The two
|
|
413
|
+
* clocks are a node's and the Orchestrator's, both kept by the cluster's time service; one second
|
|
414
|
+
* is far more than they drift, and far less than a Turn. */
|
|
415
|
+
const KILL_CLOCK_SKEW_MS = 1_000;
|
|
306
416
|
|
|
307
417
|
/** What following a settlement may be told beside the settlement itself. */
|
|
308
418
|
export type AgentSettleOptions = {
|
|
309
419
|
signal?: AbortSignal;
|
|
310
420
|
/** The Harness's guard killed the Agent's processes during this Submission (ADR-0061). */
|
|
311
|
-
onMemoryLimit?: (kill:
|
|
421
|
+
onMemoryLimit?: (kill: StreamedKill) => void;
|
|
312
422
|
};
|
|
313
423
|
|
|
314
424
|
/** Build a port for one invocation from its serializable input (ADR-0011 static-import doctrine),
|
|
@@ -335,6 +445,17 @@ export type AgentRunOptions = {
|
|
|
335
445
|
* pathological, which belongs with the workflow's fault routing.
|
|
336
446
|
*/
|
|
337
447
|
runawayBudget?: number;
|
|
448
|
+
/**
|
|
449
|
+
* How long the read of the pod's guard kills (ADR-0062) may take before it counts as none and
|
|
450
|
+
* the Turn is admitted without it. Default 5000 ms; a seam for tests, never an author dial.
|
|
451
|
+
*/
|
|
452
|
+
memoryKillsReadMs?: number;
|
|
453
|
+
/**
|
|
454
|
+
* How long the actor asks whether a restarted Harness still holds a conversation before it
|
|
455
|
+
* takes "held" for an answer: the new container may not listen yet (ADR-0062). Default 30 s; a
|
|
456
|
+
* seam for tests, never an author dial.
|
|
457
|
+
*/
|
|
458
|
+
holdsReadMs?: number;
|
|
338
459
|
};
|
|
339
460
|
|
|
340
461
|
/**
|
|
@@ -403,6 +524,12 @@ function isLostConversation(err: unknown): boolean {
|
|
|
403
524
|
return typeof err === "object" && err !== null && (err as { lost?: unknown }).lost === true;
|
|
404
525
|
}
|
|
405
526
|
|
|
527
|
+
/** An admission for a kept conversation that the Harness refused: it keeps none (ADR-0062). Read
|
|
528
|
+
* structurally, as {@link isLostConversation} is: the wire client's `ConversationNotKept`. */
|
|
529
|
+
function isNotKept(err: unknown): boolean {
|
|
530
|
+
return typeof err === "object" && err !== null && (err as { notKept?: unknown }).notKept === true;
|
|
531
|
+
}
|
|
532
|
+
|
|
406
533
|
/**
|
|
407
534
|
* The nudge (ADR-0006, absorbed here by ADR-0016). It names no picks: the Harness writes the Turn
|
|
408
535
|
* part after it, stating the Allowed picks in the model's own tool names and narrowed by the guards
|
|
@@ -433,20 +560,15 @@ const READMIT_PROMPT =
|
|
|
433
560
|
* an ABSENT one, and a directory a state named is never second-guessed — a Menu-only Agent handed
|
|
434
561
|
* one simply has no Working tools to root there (ADR-0028/0031).
|
|
435
562
|
*
|
|
436
|
-
* Absent, a Menu-only Agent frames no directory
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* ADR-0057 was written for
|
|
441
|
-
* `/work`.
|
|
563
|
+
* Absent, a Menu-only Agent frames no directory, and a Workspace with no Repo Slot, or a run outside
|
|
564
|
+
* any Workspace, frames nothing, so the Harness keeps `/work`, the folder a slotless Workspace's
|
|
565
|
+
* steps share. Under Repo Slots the Workspace only clones, so no Worktree exists for a default to
|
|
566
|
+
* name, and the kit gives no slot a meaning: an absent `cwd` is REFUSED, because a wrong guess is
|
|
567
|
+
* the silent failure ADR-0057 was written for (ADR-0075).
|
|
442
568
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* the reviewer its path as "cwd and prompt"). A `workspace: "read"` Agent that frames nothing
|
|
447
|
-
* under a one-slot Workspace therefore works in the branch checkout with `write`/`edit` withheld
|
|
448
|
-
* and `bash` kept — the tool layer's hint, not containment (ADR-0028): containment is the
|
|
449
|
-
* detached review Worktree, and only a reviewing state that requested one can frame it.
|
|
569
|
+
* `workspace: "read"` withholds `write`/`edit` and keeps `bash`: the tool layer's hint, not
|
|
570
|
+
* containment (ADR-0028). The containment is a detached Worktree that the state cuts and names as
|
|
571
|
+
* its `cwd`.
|
|
450
572
|
*
|
|
451
573
|
* The refusal throws at invoke, like the Open-model fence: loudly, at the first Turn, before a
|
|
452
574
|
* surface is registered or a pod is spent.
|
|
@@ -461,12 +583,11 @@ function frameCwd(
|
|
|
461
583
|
if (workspace === "none") return undefined;
|
|
462
584
|
const slots = Object.keys(ambient?.repos ?? {});
|
|
463
585
|
if (slots.length === 0) return undefined;
|
|
464
|
-
|
|
586
|
+
// A default here is ADR-0057's incident: an Agent's search hits `default/`, and a relative write
|
|
587
|
+
// lands outside each Worktree. An author who wants `/work` states it.
|
|
465
588
|
throw new Error(
|
|
466
|
-
`turn ${instanceId}: agent "${input.agentName}" works under a Workspace
|
|
467
|
-
|
|
468
|
-
`in (\`cwd: context.workspace.repos.<slot>\`, one of those), because the kit gives no slot a ` +
|
|
469
|
-
`meaning (ADR-0051) and a Turn's Frame is where it works (ADR-0057)`,
|
|
589
|
+
`turn ${instanceId}: agent "${input.agentName}" works under a Workspace with Repo Slots (${slots.join(", ")}) ` +
|
|
590
|
+
'and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above them (ADR-0057)',
|
|
470
591
|
);
|
|
471
592
|
}
|
|
472
593
|
|
|
@@ -477,7 +598,10 @@ function frameCwd(
|
|
|
477
598
|
*
|
|
478
599
|
* The brand is a RUNTIME property, read back through `isAgent`, and deliberately NOT part of this
|
|
479
600
|
* type: the unit-test seam is `provide({ actors: { coder: fake } })` (ADR-0049), and a required
|
|
480
|
-
* `definition` here would make every fake carry a definition it never uses.
|
|
601
|
+
* `definition` here would make every fake carry a definition it never uses.
|
|
602
|
+
*
|
|
603
|
+
* @internal
|
|
604
|
+
*/
|
|
481
605
|
export type AgentLogic = CallbackActorLogic<
|
|
482
606
|
AgentRunReceiveEvent,
|
|
483
607
|
AgentTurnInput | (AgentTurnInput & AgentTurnPlacement) | AgentRunInput
|
|
@@ -506,6 +630,8 @@ export function agentActorWith(
|
|
|
506
630
|
): AgentLogic {
|
|
507
631
|
const nudgeBudget = options.nudgeBudget ?? 2;
|
|
508
632
|
const runawayBudget = options.runawayBudget ?? 1;
|
|
633
|
+
const memoryKillsReadMs = options.memoryKillsReadMs ?? MEMORY_KILLS_READ_MS;
|
|
634
|
+
const holdsReadMs = options.holdsReadMs ?? HOLDS_READ_MS;
|
|
509
635
|
// Typed as the union so BOTH shapes typecheck on an invoke: jr2Setup machines write
|
|
510
636
|
// AgentTurnInput (and the config wrapper finalizes it before the actor ever runs); plain
|
|
511
637
|
// setup() machines must pass the finalized shape themselves — checked loudly below.
|
|
@@ -574,7 +700,7 @@ export function agentActorWith(
|
|
|
574
700
|
|
|
575
701
|
// The Frame's other half (ADR-0057): WHERE this Turn works. Resolved HERE, before any surface
|
|
576
702
|
// is registered and before a pod is spent, because the answer is a fact about the run — a
|
|
577
|
-
// Worktree
|
|
703
|
+
// Worktree exists only once a run has cut it, so no definition could have named it.
|
|
578
704
|
const cwd = frameCwd(instanceId, input, workspace, ambient);
|
|
579
705
|
const framed: AgentRunInput = cwd === undefined ? input : { ...input, cwd };
|
|
580
706
|
|
|
@@ -605,8 +731,15 @@ export function agentActorWith(
|
|
|
605
731
|
// This Agent's continued conversation in this Machine (ADR-0057) — the base id the epoch ledger
|
|
606
732
|
// burns. This Turn is PART of it only when it continues it: a fresh Turn is new by definition.
|
|
607
733
|
const conversation = continuedIid(self._parent, binding.runId, input.agentName);
|
|
734
|
+
// This Agent's slot (ADR-0076): the same identity without the run id. The Harness states it on
|
|
735
|
+
// every control request, and the Orchestrator speaks here only for it.
|
|
736
|
+
const slot = agentSlot(self._parent, input.agentName);
|
|
608
737
|
// The iids whose surface the Harness has read (see `carried` below).
|
|
609
738
|
const served = new Set<string>();
|
|
739
|
+
// Per iid, the notices a step boundary's surface read reserved and the Harness has not yet
|
|
740
|
+
// acknowledged (ADR-0062): by the id the read named, every id it stands for — equal notices
|
|
741
|
+
// are said once — and what each was raised as, for `reraiseUnheard`.
|
|
742
|
+
const boundary = new Map<string, Map<string, Array<{ id: string; raised: { notice: Notice; source?: string } }>>>();
|
|
610
743
|
const registerSurface = (iid: string) =>
|
|
611
744
|
disposers.push(
|
|
612
745
|
binding.table.register({
|
|
@@ -617,6 +750,9 @@ export function agentActorWith(
|
|
|
617
750
|
defs,
|
|
618
751
|
menu,
|
|
619
752
|
sandbox,
|
|
753
|
+
slot,
|
|
754
|
+
boundaryNotices: () => readBoundary(iid),
|
|
755
|
+
noticesHeard: (ids) => boundaryHeard(iid, ids),
|
|
620
756
|
deliver: (event) => {
|
|
621
757
|
signaled = true;
|
|
622
758
|
// The settlement-pick marker (ADR-0023), BEFORE the delivery moves the Machine: the pick
|
|
@@ -654,7 +790,8 @@ export function agentActorWith(
|
|
|
654
790
|
// When this Turn started: a memory kill that ended the Harness before it is not this Turn's
|
|
655
791
|
// (ADR-0061). Undefined from a backend that cannot see the container — and never throws:
|
|
656
792
|
// naming the fault is a courtesy, the fault itself lands either way.
|
|
657
|
-
|
|
793
|
+
// Read back on a restore: the start is the Turn's, not this process's.
|
|
794
|
+
const turnStarted = input.attach?.turnStarted ? new Date(input.attach.turnStarted) : new Date();
|
|
658
795
|
const memoryFault = (name: string): Promise<MemoryKill | undefined> =>
|
|
659
796
|
binding.sandbox?.memoryFault(name, turnStarted).catch(() => undefined) ?? Promise.resolve(undefined);
|
|
660
797
|
// Replaced after a Harness restart that cut the Turn while its state was busy: the cut aborted it.
|
|
@@ -696,11 +833,22 @@ export function agentActorWith(
|
|
|
696
833
|
// LIVE conversation stamped on the record, so a restore settle-follows the live submission
|
|
697
834
|
// AND re-addresses it (see `currentIid` above). The notices the admission carried are
|
|
698
835
|
// delivered by this same write (ADR-0062).
|
|
699
|
-
|
|
836
|
+
// The conversation is a member of its Workspace's audiences from this save on (ADR-0062).
|
|
837
|
+
const ledger = (admission: AgentAdmission, delivered: readonly string[], taker: string) =>
|
|
700
838
|
binding.recordAdmission?.(
|
|
701
839
|
instanceId,
|
|
702
|
-
{
|
|
840
|
+
{
|
|
841
|
+
...admission,
|
|
842
|
+
instanceId: currentIid,
|
|
843
|
+
agent: input.agentName,
|
|
844
|
+
endpoint,
|
|
845
|
+
...(input.turnId === undefined ? {} : { turnId: input.turnId }),
|
|
846
|
+
},
|
|
703
847
|
delivered,
|
|
848
|
+
taker,
|
|
849
|
+
noticeWorkspace === undefined
|
|
850
|
+
? undefined
|
|
851
|
+
: { workspace: noticeWorkspace, slot, continued: Boolean(input.continuation) && taker === conversationIid() },
|
|
704
852
|
);
|
|
705
853
|
|
|
706
854
|
// Where this Turn hears notices (ADR-0062). Its continued conversation's — the base id the
|
|
@@ -711,46 +859,313 @@ export function agentActorWith(
|
|
|
711
859
|
const noticeScope: NoticeScope = {
|
|
712
860
|
...(input.continuation ? { conversation } : {}),
|
|
713
861
|
...(noticeWorkspace !== undefined ? { workspace: noticeWorkspace } : {}),
|
|
862
|
+
slot,
|
|
714
863
|
};
|
|
715
|
-
|
|
864
|
+
// The Instance ID of the continued conversation this Turn rides — its epoch's spelling, which
|
|
865
|
+
// the minted `instanceId` is. A fresh Turn has none.
|
|
866
|
+
const conversationIid = (): string | undefined => (input.continuation ? instanceId : undefined);
|
|
867
|
+
// No `agent`: the notice names no Turn — what the pod's list says (below).
|
|
868
|
+
// No `limit`: the sizes read were not sizes, and the notice says none.
|
|
869
|
+
const memoryLimit = ({ limit, peak, agent }: { limit?: string; peak?: string; agent?: string }): Notice => ({
|
|
716
870
|
kind: "memory-limit",
|
|
717
871
|
scope: "workspace",
|
|
718
|
-
agent:
|
|
719
|
-
...(peak === undefined ? {} : { peak }),
|
|
720
|
-
limit,
|
|
872
|
+
...(agent === undefined ? {} : { agent }),
|
|
873
|
+
...(limit === undefined ? {} : { limit, ...(peak === undefined ? {} : { peak }) }),
|
|
721
874
|
});
|
|
722
|
-
// The Harness's guard killed this Workspace's processes
|
|
723
|
-
//
|
|
724
|
-
|
|
875
|
+
// The Harness's guard killed this Workspace's processes while this Turn's Submission ran
|
|
876
|
+
// (ADR-0061, layer 4), read on its stream while the wait is live — so the notice can name this
|
|
877
|
+
// Turn's Agent. A stopped actor's last poll names nothing: the state that asked for the Turn has
|
|
878
|
+
// exited, and the pod's list carries the kill. Keyed by the kill's id, so a second running
|
|
879
|
+
// conversation's stream, or the pod's list, adds nothing.
|
|
880
|
+
// The kill cut this Turn (D4 rule 2): its Agent's next Turn retries the broken work, and hears it.
|
|
881
|
+
const onMemoryLimit = (kill: StreamedKill): void => {
|
|
882
|
+
if (stopped || noticeWorkspace === undefined) return;
|
|
883
|
+
binding.raiseNotice?.(
|
|
884
|
+
memoryLimit({ limit: kill.limit, peak: kill.peak, agent: input.agentName }),
|
|
885
|
+
noticeWorkspace,
|
|
886
|
+
kill.legacy ? kill.id : guardSource(noticeWorkspace, kill.id),
|
|
887
|
+
[],
|
|
888
|
+
{ retry: { slot, cut: currentIid }, at: new Date().toISOString() },
|
|
889
|
+
);
|
|
890
|
+
};
|
|
891
|
+
// …but a stream is not every kill's path. A kill while no Submission ran — a process an earlier
|
|
892
|
+
// Turn left running, killed while the workflow waited at a Gate — is on no stream, and a kill
|
|
893
|
+
// after a wait stopped (the Agent's state exited, its abort not yet landed) is on a stream that
|
|
894
|
+
// no one reads. The Harness keeps every kill, so the pod's list is read before notices are
|
|
895
|
+
// taken, and this admission carries what it finds. Keyed as on the stream: the ledger's `seen`,
|
|
896
|
+
// persisted with the run, makes a kill read on both paths, or read again after a restart of
|
|
897
|
+
// either side, one notice. The notice names no Turn: the list cannot say whose Turn a kill
|
|
898
|
+
// ended. One read raises at most one notice, the newest kill's; the older kills in it are only
|
|
899
|
+
// marked seen, so they never give a notice. A read that fails or times out is none: the list
|
|
900
|
+
// keeps the kill, and the next admission reads again.
|
|
901
|
+
//
|
|
902
|
+
// The Turn's first read sets its `killMark`, the newest kill it saw, ledgered with every
|
|
903
|
+
// admission of the Turn. Answers the sources a nudge or a wake of THIS conversation may carry:
|
|
904
|
+
// the kills after that mark — none from before the Turn, which would tell the Agent that
|
|
905
|
+
// processes it started in this Turn died — and after the newest kill its own `bash` call
|
|
906
|
+
// answered, whose answer said all its processes died. Those stay pending for the next Turn.
|
|
907
|
+
// Only when the first read failed is the Turn's start time the mark, held against `at`.
|
|
908
|
+
// A kernel kill (ADR-0061, layer 5) ends the Harness container, so no list keeps it and no
|
|
909
|
+
// stream carries it. A Turn it ended faults on it, and the fault raises its notice (below). A
|
|
910
|
+
// kill while no Turn ran faults nothing: the restarted Harness rebuilds its conversations. But
|
|
911
|
+
// the operator publishes the container's last end on the Sandbox, and the watch keeps it until
|
|
912
|
+
// the next end. So before each admission into the Workspace, a last end `OOMKilled` raises the
|
|
913
|
+
// notice, keyed by that end as the fault keys it: one notice for one kill, whichever path saw
|
|
914
|
+
// it first, and none again after a restart of either side. It names no Turn: the end cannot say
|
|
915
|
+
// whose Turn it was. A watch that has not listed yet says nothing; the next admission asks again.
|
|
916
|
+
// Not after a restart this Turn's watch saw: the Turn faults on it, or is re-admitted, and the
|
|
917
|
+
// fault's notice names this Turn's Agent — a raise here would take its source first.
|
|
918
|
+
const hearKernelKill = async (): Promise<void> => {
|
|
919
|
+
if (restarted !== undefined) return;
|
|
920
|
+
const last = heardRestarts?.lastTerminated;
|
|
921
|
+
if (noticeWorkspace === undefined || last?.reason !== OOM_KILLED || last.finishedAt === undefined) return;
|
|
922
|
+
const kill = await binding.sandbox
|
|
923
|
+
?.memoryFault(noticeWorkspace, new Date(last.finishedAt))
|
|
924
|
+
.catch(() => undefined);
|
|
925
|
+
if (kill?.at === undefined) return;
|
|
926
|
+
binding.raiseNotice?.(
|
|
927
|
+
memoryLimit({ limit: kill.limit }),
|
|
928
|
+
noticeWorkspace,
|
|
929
|
+
kernelSource(noticeWorkspace, kill.at),
|
|
930
|
+
[],
|
|
931
|
+
{ at: kill.at },
|
|
932
|
+
);
|
|
933
|
+
};
|
|
934
|
+
let killMark = input.attach?.killMark;
|
|
935
|
+
let firstRead = input.attach === undefined;
|
|
936
|
+
// `known: false`: the read could not tell, so which kills this conversation was told is not
|
|
937
|
+
// known either — an admission of the same Turn then takes no guard kill at all.
|
|
938
|
+
const hearPodKills = async (): Promise<{ known: boolean; unsaid: string[]; said: string[] }> => {
|
|
939
|
+
const none = { known: true, unsaid: [], said: [] };
|
|
940
|
+
if (noticeWorkspace === undefined) return none;
|
|
941
|
+
const first = firstRead;
|
|
942
|
+
firstRead = false;
|
|
943
|
+
let kills: ListedKill[] | undefined;
|
|
944
|
+
try {
|
|
945
|
+
kills = await client.memoryKills({
|
|
946
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(memoryKillsReadMs)]),
|
|
947
|
+
});
|
|
948
|
+
} catch (err) {
|
|
949
|
+
// Stopped while it read: nothing more is admitted, and nothing is taken.
|
|
950
|
+
if (controller.signal.aborted) throw err;
|
|
951
|
+
return { ...none, known: false };
|
|
952
|
+
}
|
|
953
|
+
// Could not tell: no mark — an empty list is not what was seen — so the clock is the mark.
|
|
954
|
+
if (kills === undefined) return { ...none, known: false };
|
|
955
|
+
if (first) killMark = kills.at(-1)?.id ?? NO_KILLS;
|
|
956
|
+
const newest = kills.at(-1);
|
|
957
|
+
if (newest === undefined) return none;
|
|
958
|
+
binding.raiseNotice?.(
|
|
959
|
+
memoryLimit({ limit: newest.limit, peak: newest.peak }),
|
|
960
|
+
noticeWorkspace,
|
|
961
|
+
guardSource(noticeWorkspace, newest.id),
|
|
962
|
+
kills.slice(0, -1).map((kill) => guardSource(noticeWorkspace, kill.id)),
|
|
963
|
+
{ at: newest.at, told: newest.told.map((t) => t.instanceId) },
|
|
964
|
+
);
|
|
965
|
+
applyTold(kills);
|
|
966
|
+
const toldHere = (kill: ListedKill): boolean =>
|
|
967
|
+
kill.told.some((t) => t.agent === input.agentName && t.instanceId === currentIid);
|
|
968
|
+
// The first kill after the Turn's mark. A mark no longer in the bounded list is older than
|
|
969
|
+
// every kill in it.
|
|
970
|
+
let from = 0;
|
|
971
|
+
kills.forEach((kill, i) => {
|
|
972
|
+
if (kill.id === killMark || toldHere(kill)) from = i + 1;
|
|
973
|
+
});
|
|
974
|
+
const inThisTurn = (kill: ListedKill): boolean =>
|
|
975
|
+
killMark !== undefined || Date.parse(kill.at) >= turnStarted.getTime() - KILL_CLOCK_SKEW_MS;
|
|
976
|
+
const unsaid = kills.slice(from).filter(inThisTurn);
|
|
977
|
+
return {
|
|
978
|
+
known: true,
|
|
979
|
+
unsaid: unsaid.map((kill) => guardSource(noticeWorkspace, kill.id)),
|
|
980
|
+
said: kills.filter((kill) => !unsaid.includes(kill)).map((kill) => guardSource(noticeWorkspace, kill.id)),
|
|
981
|
+
};
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// The conversations each listed kill's `bash` answer told leave its audience (D4 rule 3) — a
|
|
985
|
+
// kill raised off a stream before its answer returned is found here too.
|
|
986
|
+
const applyTold = (kills: readonly ListedKill[]): void => {
|
|
725
987
|
if (noticeWorkspace === undefined) return;
|
|
726
|
-
|
|
988
|
+
const told = kills
|
|
989
|
+
.filter((kill) => kill.told.length > 0)
|
|
990
|
+
.map((kill) => ({ source: guardSource(noticeWorkspace, kill.id), takers: kill.told.map((t) => t.instanceId) }));
|
|
991
|
+
if (told.length > 0) binding.noticesTold?.(told);
|
|
992
|
+
};
|
|
993
|
+
// The step boundary's read (ADR-0062): this conversation's pending notices in their audience,
|
|
994
|
+
// reserved for this Turn until the Harness acknowledges the append. A guard kill's audience
|
|
995
|
+
// leaves out the conversations its `bash` answer told, which only the pod's list says — so when
|
|
996
|
+
// one is among them, the list is read first, and a read that could not tell takes no guard kill.
|
|
997
|
+
const readBoundary = async (iid: string): Promise<Array<{ id: string; notice: Notice }>> => {
|
|
998
|
+
if (stopped || iid !== currentIid || binding.takeNotices === undefined) return [];
|
|
999
|
+
const scope: NoticeScope = { ...noticeScope, taker: iid };
|
|
1000
|
+
if (noticeWorkspace !== undefined) {
|
|
1001
|
+
const guard = guardSource(noticeWorkspace, "");
|
|
1002
|
+
if ((binding.peekNotices?.(scope) ?? []).some((p) => p.source?.startsWith(guard))) {
|
|
1003
|
+
let kills: ListedKill[] | undefined;
|
|
1004
|
+
try {
|
|
1005
|
+
kills = await client.memoryKills({
|
|
1006
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(memoryKillsReadMs)]),
|
|
1007
|
+
});
|
|
1008
|
+
} catch {
|
|
1009
|
+
kills = undefined;
|
|
1010
|
+
}
|
|
1011
|
+
if (kills === undefined) scope.exceptPrefix = guard;
|
|
1012
|
+
else applyTold(kills);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (stopped || iid !== currentIid) return [];
|
|
1016
|
+
const taken = binding.takeNotices(scope);
|
|
1017
|
+
const held = boundary.get(iid) ?? new Map();
|
|
1018
|
+
const said = new Map<string, string>();
|
|
1019
|
+
const out: Array<{ id: string; notice: Notice }> = [];
|
|
1020
|
+
taken.ids.forEach((id, i) => {
|
|
1021
|
+
const raised = taken.raised?.[i] ?? { notice: taken.notices[i]! };
|
|
1022
|
+
const key = JSON.stringify(raised.notice);
|
|
1023
|
+
const first = said.get(key);
|
|
1024
|
+
if (first === undefined) {
|
|
1025
|
+
said.set(key, id);
|
|
1026
|
+
held.set(id, [{ id, raised }]);
|
|
1027
|
+
out.push({ id, notice: raised.notice });
|
|
1028
|
+
} else held.get(first)!.push({ id, raised });
|
|
1029
|
+
});
|
|
1030
|
+
if (held.size > 0) boundary.set(iid, held);
|
|
1031
|
+
return out;
|
|
1032
|
+
};
|
|
1033
|
+
// A step boundary whose notices the Harness did not append — a stopped model with no Allowed
|
|
1034
|
+
// picks, or a Turn ending as it read (`between-steps.ts`) — holds them until the next admission
|
|
1035
|
+
// of the conversation, which takes them again: a wake or a nudge carries them (ADR-0062).
|
|
1036
|
+
const releaseBoundary = (iid: string): void => {
|
|
1037
|
+
const held = boundary.get(iid);
|
|
1038
|
+
if (held === undefined) return;
|
|
1039
|
+
boundary.delete(iid);
|
|
1040
|
+
binding.releaseNotices?.(
|
|
1041
|
+
[...held.values()].flatMap((hs) => hs.map((h) => h.id)),
|
|
1042
|
+
iid,
|
|
1043
|
+
);
|
|
1044
|
+
};
|
|
1045
|
+
// The Harness appended them: heard in this conversation, in one save. An id this read did not
|
|
1046
|
+
// name changes nothing.
|
|
1047
|
+
const boundaryHeard = (iid: string, ids: readonly string[]): number => {
|
|
1048
|
+
const held = boundary.get(iid);
|
|
1049
|
+
if (held === undefined) return 0;
|
|
1050
|
+
const heard = ids.flatMap((id) => held.get(id) ?? []);
|
|
1051
|
+
for (const id of ids) held.delete(id);
|
|
1052
|
+
if (held.size === 0) boundary.delete(iid);
|
|
1053
|
+
if (heard.length === 0) return 0;
|
|
1054
|
+
binding.acknowledgeNotices?.(
|
|
1055
|
+
heard.map((h) => h.id),
|
|
1056
|
+
iid,
|
|
1057
|
+
);
|
|
1058
|
+
const bucket = carriedBy.get(iid) ?? { heard: [], unheard: [], task: false, fresh: [] };
|
|
1059
|
+
bucket.heard.push(...heard.map((h) => h.raised));
|
|
1060
|
+
carriedBy.set(iid, bucket);
|
|
1061
|
+
return ids.filter((id) => heard.some((h) => h.id === id)).length;
|
|
727
1062
|
};
|
|
728
1063
|
|
|
729
1064
|
// The Turn's admission and a reroll's carry the pending notices in scope — a reroll's
|
|
730
|
-
// conversation is fresh, and has heard nothing. A nudge's
|
|
731
|
-
//
|
|
732
|
-
//
|
|
1065
|
+
// conversation is fresh, and has heard nothing. A nudge's and a wake's carry only the guard
|
|
1066
|
+
// kills of this Turn that came after its own `bash` call's last kill verdict (`hearPodKills`):
|
|
1067
|
+
// they re-prompt the Turn within itself (ADR-0016), so a kill that verdict covers is already in
|
|
1068
|
+
// its context, and is for the next Turn in the Workspace (ADR-0062). A later kill — while its
|
|
1069
|
+
// model wrote text, while it waited for its wake, or answered in another conversation — this
|
|
1070
|
+
// Agent was never told. Reserved
|
|
733
1071
|
// first, so no concurrent admission carries them too; delivered by the ledger write; released
|
|
734
1072
|
// when the admission fails, for the next one to carry.
|
|
735
1073
|
//
|
|
736
1074
|
// What the last carrying admission took, and under which iid: delivered is not HEARD. If this
|
|
737
1075
|
// actor ends the Turn before the Harness reads that iid's surface, the Harness finds the Turn
|
|
738
1076
|
// over and settles it with no model asked (ADR-0026) — so `abandon` raises them again.
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1077
|
+
// Per conversation the Turn admitted on: the notices its admissions carried — `unheard` since
|
|
1078
|
+
// the Harness last read its surface, `heard` before that — and whether an unheard one carried
|
|
1079
|
+
// the Turn's task. Collected across the Turn, so a re-admission onto a lost conversation tells
|
|
1080
|
+
// the Agent all of them again.
|
|
1081
|
+
type Raised = { notice: Notice; source?: string };
|
|
1082
|
+
// `fresh`: a fresh Turn's "this conversation is new", which rides an admission as `extra` and so
|
|
1083
|
+
// is kept in no ledger — an unread one is carried again by the next re-admission.
|
|
1084
|
+
const carriedBy = new Map<string, { heard: Raised[]; unheard: Raised[]; task: boolean; fresh: Notice[] }>();
|
|
1085
|
+
// `notices`: "turn" takes every notice in scope; "same-turn" too, but the guard kills the Turn's own
|
|
1086
|
+
// conversation was told (a re-admission after a cut continues the same Turn); "nudge" takes the
|
|
1087
|
+
// guard kills this conversation was not told alone.
|
|
1088
|
+
// `include`: sources restored for this admission, taken whatever the scope leaves out. `task`:
|
|
1089
|
+
// this admission's prompt holds the Turn's task. `kept`: the admission is for a conversation
|
|
1090
|
+
// that `holds` said the Harness keeps; a Harness that keeps none refuses it (`isNotKept`), and
|
|
1091
|
+
// the refusal is thrown as any failed admission is.
|
|
1092
|
+
const admitTurn = async (
|
|
1093
|
+
turn: AgentRunInput,
|
|
1094
|
+
opts: {
|
|
1095
|
+
notices: "turn" | "same-turn" | "nudge";
|
|
1096
|
+
extra?: Notice[];
|
|
1097
|
+
include?: readonly string[];
|
|
1098
|
+
task?: boolean;
|
|
1099
|
+
kept?: boolean;
|
|
1100
|
+
} = { notices: "turn", task: true },
|
|
1101
|
+
) => {
|
|
1102
|
+
await hearKernelKill();
|
|
1103
|
+
const { known, unsaid, said } = await hearPodKills();
|
|
1104
|
+
releaseBoundary(turn.instanceId);
|
|
1105
|
+
// The conversation that takes a Workspace notice (ADR-0062): the one this admission rides, its
|
|
1106
|
+
// Instance ID. A continued Turn's is the continued conversation's; a fresh Turn's, a reroll's
|
|
1107
|
+
// and a burned conversation's successor's are each a new conversation, which has heard none.
|
|
1108
|
+
// A Workspace notice is delivered once in each, so every conversation in the Workspace hears
|
|
1109
|
+
// it — each child of a Pool has its own — and no conversation hears it twice.
|
|
1110
|
+
const taker = turn.instanceId;
|
|
1111
|
+
// A read that could not tell leaves the same Turn no way to know which kills its own `bash`
|
|
1112
|
+
// reported: it takes none, as a nudge takes none then, and they stay for the next Turn.
|
|
1113
|
+
const sameTurn: NoticeScope = known
|
|
1114
|
+
? { ...noticeScope, taker, except: said }
|
|
1115
|
+
: {
|
|
1116
|
+
...noticeScope,
|
|
1117
|
+
taker,
|
|
1118
|
+
...(noticeWorkspace === undefined ? {} : { exceptPrefix: guardSource(noticeWorkspace, "") }),
|
|
1119
|
+
};
|
|
1120
|
+
const scope: NoticeScope | undefined =
|
|
1121
|
+
opts.notices === "turn"
|
|
1122
|
+
? { ...noticeScope, taker, turn: true }
|
|
1123
|
+
: opts.notices === "same-turn"
|
|
1124
|
+
? sameTurn
|
|
1125
|
+
: unsaid.length > 0
|
|
1126
|
+
? { workspace: noticeWorkspace, taker, only: unsaid }
|
|
1127
|
+
: undefined;
|
|
1128
|
+
const none = { ids: [] as string[], notices: [] as Notice[], raised: [] as Raised[] };
|
|
1129
|
+
const restored =
|
|
1130
|
+
opts.include && opts.include.length > 0
|
|
1131
|
+
? (binding.takeNotices?.({ ...noticeScope, taker, only: opts.include }) ?? none)
|
|
1132
|
+
: none;
|
|
1133
|
+
const main = (scope ? binding.takeNotices?.(scope) : undefined) ?? none;
|
|
1134
|
+
const taken = {
|
|
1135
|
+
ids: [...restored.ids, ...main.ids],
|
|
1136
|
+
notices: [...restored.notices, ...main.notices],
|
|
1137
|
+
raised: [
|
|
1138
|
+
...(restored.raised ?? restored.notices.map((notice) => ({ notice }))),
|
|
1139
|
+
...(main.raised ?? main.notices.map((notice) => ({ notice }))),
|
|
1140
|
+
],
|
|
1141
|
+
};
|
|
742
1142
|
// `extra`: a notice this admission carries straight from the actor, never kept — the answer a
|
|
743
|
-
// cut pick never got (ADR-0031), whose one
|
|
744
|
-
|
|
1143
|
+
// cut pick never got (ADR-0031), whose one path to the model is this admission. One "this
|
|
1144
|
+
// conversation is new" is enough, whichever path brought it.
|
|
1145
|
+
let fresh = false;
|
|
1146
|
+
const notices = [...taken.notices, ...(opts.extra ?? [])].filter((n) => {
|
|
1147
|
+
if (n.kind !== "conversation-new") return true;
|
|
1148
|
+
if (fresh) return false;
|
|
1149
|
+
fresh = true;
|
|
1150
|
+
return true;
|
|
1151
|
+
});
|
|
1152
|
+
// `served` is per Instance ID, and a nudge or a wake reuses the Turn's: it must be read again
|
|
1153
|
+
// before this admission counts as heard (`reraiseUnheard`). An admission that fails puts the
|
|
1154
|
+
// mark back — what an earlier one carried was heard.
|
|
1155
|
+
const heard = served.has(turn.instanceId);
|
|
1156
|
+
served.delete(turn.instanceId);
|
|
745
1157
|
let admission: AgentAdmission;
|
|
746
1158
|
try {
|
|
747
1159
|
admission = await client.admit(turn, {
|
|
748
1160
|
definition,
|
|
1161
|
+
slot,
|
|
749
1162
|
signal: controller.signal,
|
|
750
1163
|
...(notices.length ? { notices } : {}),
|
|
1164
|
+
...(opts.kept ? { kept: true } : {}),
|
|
751
1165
|
});
|
|
752
1166
|
} catch (err) {
|
|
753
|
-
binding.releaseNotices?.(taken.ids);
|
|
1167
|
+
binding.releaseNotices?.(taken.ids, taker);
|
|
1168
|
+
if (heard) served.add(turn.instanceId);
|
|
754
1169
|
throw err;
|
|
755
1170
|
}
|
|
756
1171
|
// The mark is taken once the Harness has ANSWERED: a restart before the answer took the
|
|
@@ -759,23 +1174,62 @@ export function agentActorWith(
|
|
|
759
1174
|
admitted = true;
|
|
760
1175
|
restartMark = heardRestarts?.restartCount;
|
|
761
1176
|
if (restartMark !== undefined) admission = { ...admission, harnessRestarts: restartMark };
|
|
762
|
-
|
|
763
|
-
|
|
1177
|
+
// The Turn's task rides every ledgered admission: a restore drops `prompt`.
|
|
1178
|
+
const task = input.prompt ?? input.attach?.task;
|
|
1179
|
+
if (task !== undefined) admission = { ...admission, task };
|
|
1180
|
+
// A Turn in a Workspace keeps its start and its kill mark across a restore.
|
|
1181
|
+
if (noticeWorkspace !== undefined) {
|
|
1182
|
+
admission = {
|
|
1183
|
+
...admission,
|
|
1184
|
+
turnStarted: turnStarted.toISOString(),
|
|
1185
|
+
...(killMark === undefined ? {} : { killMark }),
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
ledger(admission, taken.ids, taker);
|
|
1189
|
+
const bucket = carriedBy.get(turn.instanceId) ?? { heard: [], unheard: [], task: false, fresh: [] };
|
|
1190
|
+
if (heard) {
|
|
1191
|
+
bucket.heard.push(...bucket.unheard);
|
|
1192
|
+
bucket.unheard = [];
|
|
1193
|
+
bucket.task = false;
|
|
1194
|
+
bucket.fresh = [];
|
|
1195
|
+
}
|
|
1196
|
+
bucket.unheard.push(...taken.raised);
|
|
1197
|
+
if (opts.task) bucket.task = true;
|
|
1198
|
+
if (!input.continuation) bucket.fresh.push(...(opts.extra ?? []).filter((n) => n.kind === "conversation-new"));
|
|
1199
|
+
carriedBy.set(turn.instanceId, bucket);
|
|
764
1200
|
return admission;
|
|
765
1201
|
};
|
|
766
|
-
// The notices
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1202
|
+
// The notices unheard admissions carried, pending again for the next Turn in their scope —
|
|
1203
|
+
// each WITH the source it was raised with, so a take by source finds it and no equal notice of
|
|
1204
|
+
// another source swallows it. `lost`: the current conversation, which heard the rest, is gone,
|
|
1205
|
+
// so they are unheard too. Answers the sources restored, which a re-admission of the same Turn
|
|
1206
|
+
// takes whatever else it leaves out.
|
|
1207
|
+
const reraiseUnheard = (lost = false): string[] => {
|
|
1208
|
+
const restored: string[] = [];
|
|
1209
|
+
for (const [iid, bucket] of carriedBy) {
|
|
1210
|
+
const gone = lost && iid === currentIid;
|
|
1211
|
+
const read = served.has(iid) && !gone;
|
|
1212
|
+
const again = [...(gone ? bucket.heard : []), ...(read ? [] : bucket.unheard)];
|
|
1213
|
+
if (gone) bucket.heard = [];
|
|
1214
|
+
if (!read) bucket.unheard = [];
|
|
1215
|
+
for (const { notice, source } of again) {
|
|
1216
|
+
const to = notice.scope === "conversation" ? conversation : noticeWorkspace;
|
|
1217
|
+
if (to === undefined) continue;
|
|
1218
|
+
// A Workspace notice is pending again for the conversation that carried it, which did not
|
|
1219
|
+
// hear it; the others it reached keep theirs.
|
|
1220
|
+
if (binding.restoreNotice) binding.restoreNotice(notice, to, source, iid);
|
|
1221
|
+
else binding.raiseNotice?.(notice, to);
|
|
1222
|
+
if (source !== undefined) restored.push(source);
|
|
1223
|
+
}
|
|
772
1224
|
}
|
|
773
|
-
|
|
1225
|
+
return restored;
|
|
774
1226
|
};
|
|
775
1227
|
|
|
776
1228
|
const abandon = () => {
|
|
777
1229
|
if (stopped) return;
|
|
778
1230
|
stopped = true;
|
|
1231
|
+
// What ended the Turn, read while its registration is still there (ADR-0024).
|
|
1232
|
+
const byPick = binding.turnEndedByPick?.(currentIid) === true;
|
|
779
1233
|
for (const dispose of disposers.splice(0)) dispose();
|
|
780
1234
|
// Stop consuming the stream. The remote end of the turn is the next paragraph.
|
|
781
1235
|
controller.abort();
|
|
@@ -784,12 +1238,24 @@ export function agentActorWith(
|
|
|
784
1238
|
// Pool cancelling a child. The one exception is the host stopping the run for its own
|
|
785
1239
|
// reasons, which ADR-0007's restore re-attaches to.
|
|
786
1240
|
if (binding.hostStopping) return;
|
|
1241
|
+
// A step boundary's notices the Harness never acknowledged are pending again (ADR-0062).
|
|
1242
|
+
for (const iid of [...boundary.keys()]) releaseBoundary(iid);
|
|
787
1243
|
reraiseUnheard();
|
|
1244
|
+
// The Turn ended: a fresh conversation leaves every audience in its Workspace, a continued one
|
|
1245
|
+
// waits for its next Turn (ADR-0062).
|
|
1246
|
+
if (noticeWorkspace !== undefined) {
|
|
1247
|
+
binding.noticeTurnEnded?.(noticeWorkspace, [...new Set([...carriedBy.keys(), currentIid])]);
|
|
1248
|
+
}
|
|
788
1249
|
// Fire-and-forget, and unreportable BY CONSTRUCTION: this actor is stopped, so there is no
|
|
789
1250
|
// `agent.fault` left to raise. An orphan that survives a failed abort 404s on every tool
|
|
790
1251
|
// call and settles on its own.
|
|
791
1252
|
const iid = currentIid;
|
|
792
|
-
|
|
1253
|
+
// The cause names the Submission it is about: the last one ledgered for this Turn. The ledger
|
|
1254
|
+
// is keyed by the invocation's iid, and stamps the live conversation (a reroll's).
|
|
1255
|
+
const ledgered = binding.admissionOf?.(instanceId);
|
|
1256
|
+
const submissionId = ledgered?.instanceId === iid ? ledgered.submissionId : undefined;
|
|
1257
|
+
const cause = byPick ? { cause: "pick" as const, ...(submissionId ? { submissionId } : {}) } : {};
|
|
1258
|
+
const aborting = client.abort(input.agentName, iid, cause).catch(() => {});
|
|
793
1259
|
pendingAborts.set(iid, aborting);
|
|
794
1260
|
void aborting.then(() => {
|
|
795
1261
|
if (pendingAborts.get(iid) === aborting) pendingAborts.delete(iid);
|
|
@@ -859,6 +1325,34 @@ export function agentActorWith(
|
|
|
859
1325
|
if (sub === undefined) finish();
|
|
860
1326
|
});
|
|
861
1327
|
|
|
1328
|
+
// The Harness keeps no such conversation (ADR-0062): it lost it while the Turn waited, and a
|
|
1329
|
+
// `kept` admission found none. Nothing of the Turn is left there, so the Turn is admitted as a
|
|
1330
|
+
// Turn on a new conversation: every notice in scope, `include` whatever the scope leaves out,
|
|
1331
|
+
// and `prompt`, which holds the Turn's task. A continued Turn is told its conversation is new as the
|
|
1332
|
+
// continue path tells it — the notice is kept for its conversation, which this admission takes.
|
|
1333
|
+
// A fresh Turn's conversation is its own: the notice rides this admission alone, so no later
|
|
1334
|
+
// continued Turn of another conversation is told its context is gone. `before` and `after`
|
|
1335
|
+
// ride beside it, in that order.
|
|
1336
|
+
const admitOntoLost = async (
|
|
1337
|
+
prompt: string,
|
|
1338
|
+
before: Notice[],
|
|
1339
|
+
after: Notice[],
|
|
1340
|
+
include: readonly string[],
|
|
1341
|
+
): Promise<AgentAdmission> => {
|
|
1342
|
+
const extra = [...before];
|
|
1343
|
+
const fresh: Notice = {
|
|
1344
|
+
kind: "conversation-new",
|
|
1345
|
+
scope: "conversation",
|
|
1346
|
+
reason: "the Harness no longer holds it",
|
|
1347
|
+
};
|
|
1348
|
+
if (input.continuation) binding.raiseNotice?.(fresh, conversation);
|
|
1349
|
+
else extra.push(fresh);
|
|
1350
|
+
return await admitTurn(
|
|
1351
|
+
{ ...framed, attach: undefined, instanceId: currentIid, prompt },
|
|
1352
|
+
{ notices: "turn", extra: [...extra, ...after], include, task: true },
|
|
1353
|
+
);
|
|
1354
|
+
};
|
|
1355
|
+
|
|
862
1356
|
// A Harness restart cut this Turn (ADR-0031). It is NOT an `agent.fault` when the Agent's state is
|
|
863
1357
|
// busy — something it started runs below it — or its pick's answer has not reached the model:
|
|
864
1358
|
// faulting would route the Machine out of the state and stop the work the Turn did not want
|
|
@@ -888,6 +1382,79 @@ export function agentActorWith(
|
|
|
888
1382
|
},
|
|
889
1383
|
]
|
|
890
1384
|
: [];
|
|
1385
|
+
// Kept until the re-admission is sure: a Turn that faults below still names the restart.
|
|
1386
|
+
const cutBy = restarted;
|
|
1387
|
+
// The restart that cut the Turn is the new mark at once: the watch may tell it again before
|
|
1388
|
+
// the re-admission is answered, and it must not cut the re-admission too.
|
|
1389
|
+
if (restarted !== undefined) restartMark = restarted.restartCount;
|
|
1390
|
+
restarted = undefined;
|
|
1391
|
+
controller = new AbortController();
|
|
1392
|
+
// The rebuilt Harness may not read the conversation back: then this Turn lands on a new one,
|
|
1393
|
+
// and is told (ADR-0062). Asked again while the new container may not listen yet, and taken
|
|
1394
|
+
// as "held" only when the time is over. That "held" can be wrong — a Harness in
|
|
1395
|
+
// CrashLoopBackOff answers nothing for longer — so the admission says it is for a kept
|
|
1396
|
+
// conversation, and a Harness that keeps none refuses it: then the Turn takes the path of a
|
|
1397
|
+
// lost conversation below.
|
|
1398
|
+
const kept = await client.holds(input.agentName, currentIid, {
|
|
1399
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(holdsReadMs)]),
|
|
1400
|
+
});
|
|
1401
|
+
if (stopped) return "stopped";
|
|
1402
|
+
// The task: a restore dropped `prompt`, and the ledgered admission kept it. A new
|
|
1403
|
+
// conversation with no task to send cannot be asked to "continue your task": the Turn faults.
|
|
1404
|
+
const task = input.prompt ?? input.attach?.task;
|
|
1405
|
+
// Whether the cut admission, never read, carried the task: then the model never read it.
|
|
1406
|
+
const taskUnheard = !served.has(currentIid) && carriedBy.get(currentIid)?.task === true;
|
|
1407
|
+
// A fresh Turn's "this conversation is new" that an unread re-admission carried rides this
|
|
1408
|
+
// one again: it is kept nowhere else. Read before the first admission below, which can fail.
|
|
1409
|
+
const unreadFresh: Notice[] = served.has(currentIid) ? [] : [...(carriedBy.get(currentIid)?.fresh ?? [])];
|
|
1410
|
+
// On a new conversation nothing of the Turn is left: the cut pick's answer rides after the task,
|
|
1411
|
+
// and after the unread "this conversation is new" that the cut admission carried.
|
|
1412
|
+
const readmitLost = (withTask: string, include: readonly string[]): Promise<AgentAdmission> =>
|
|
1413
|
+
admitOntoLost(`${READMIT_PROMPT}\n\n${withTask}`, unreadFresh, notice, include);
|
|
1414
|
+
// On a conversation the Harness still holds, it continues the same Turn. The task follows the
|
|
1415
|
+
// re-admit text when a cut admission carried it and was never read.
|
|
1416
|
+
const readmitKept = (include: readonly string[]): Promise<AgentAdmission> => {
|
|
1417
|
+
const withTask = taskUnheard && task !== undefined;
|
|
1418
|
+
return admitTurn(
|
|
1419
|
+
{
|
|
1420
|
+
...framed,
|
|
1421
|
+
attach: undefined,
|
|
1422
|
+
instanceId: currentIid,
|
|
1423
|
+
prompt: withTask ? `${READMIT_PROMPT}\n\n${task}` : READMIT_PROMPT,
|
|
1424
|
+
},
|
|
1425
|
+
{ notices: "same-turn", extra: [...unreadFresh, ...notice], include, task: withTask, kept: true },
|
|
1426
|
+
);
|
|
1427
|
+
};
|
|
1428
|
+
// What the cut admissions carried is pending again before this one takes, if the Harness never
|
|
1429
|
+
// read their surface — or all of it, if the conversation that heard it is gone. Those sources
|
|
1430
|
+
// ride this re-admission whatever its scope leaves out.
|
|
1431
|
+
let admission: AgentAdmission;
|
|
1432
|
+
if (!kept) {
|
|
1433
|
+
// The fault names the restart that cut the Turn, and asks whether it was a memory kill.
|
|
1434
|
+
if (task === undefined) {
|
|
1435
|
+
restarted = cutBy;
|
|
1436
|
+
return undefined;
|
|
1437
|
+
}
|
|
1438
|
+
admission = await readmitLost(task, reraiseUnheard(true));
|
|
1439
|
+
} else {
|
|
1440
|
+
const restored = reraiseUnheard(false);
|
|
1441
|
+
try {
|
|
1442
|
+
admission = await readmitKept(restored);
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
if (!isNotKept(err)) throw err;
|
|
1445
|
+
// "Held" was the timeout's: the Harness keeps no such conversation, and it seated none. The
|
|
1446
|
+
// failed admission put back what it took. Now the path of a lost conversation: what the
|
|
1447
|
+
// conversation heard is unheard, and the Turn is admitted with its task — or faults when
|
|
1448
|
+
// there is no task to send.
|
|
1449
|
+
if (stopped) return "stopped";
|
|
1450
|
+
if (task === undefined) {
|
|
1451
|
+
restarted = cutBy;
|
|
1452
|
+
return undefined;
|
|
1453
|
+
}
|
|
1454
|
+
admission = await readmitLost(task, [...restored, ...reraiseUnheard(true)]);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
// Said once an admission worked: a refused one that then faults re-admitted nothing.
|
|
891
1458
|
cuts++;
|
|
892
1459
|
binding.telemetry?.({
|
|
893
1460
|
kind: "retry",
|
|
@@ -895,22 +1462,6 @@ export function agentActorWith(
|
|
|
895
1462
|
attempt: cuts,
|
|
896
1463
|
reason: "re-admitted after a Harness restart",
|
|
897
1464
|
});
|
|
898
|
-
restarted = undefined;
|
|
899
|
-
controller = new AbortController();
|
|
900
|
-
// The rebuilt Harness may not read the conversation back: then this Turn lands on a new one,
|
|
901
|
-
// and is told (ADR-0062), as the continue path tells it below.
|
|
902
|
-
if (!(await client.holds(input.agentName, currentIid, { signal: controller.signal }))) {
|
|
903
|
-
binding.raiseNotice?.(
|
|
904
|
-
{ kind: "conversation-new", scope: "conversation", reason: "the Harness no longer holds it" },
|
|
905
|
-
conversation,
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
if (stopped) return "stopped";
|
|
909
|
-
// The notices pending for this Turn ride the re-admission, with the cut pick's answer after them.
|
|
910
|
-
const admission = await admitTurn(
|
|
911
|
-
{ ...framed, attach: undefined, instanceId: currentIid, prompt: READMIT_PROMPT },
|
|
912
|
-
{ notices: true, extra: notice },
|
|
913
|
-
);
|
|
914
1465
|
binding.heldAnswerDelivered?.(currentIid);
|
|
915
1466
|
return admission;
|
|
916
1467
|
};
|
|
@@ -961,17 +1512,30 @@ export function agentActorWith(
|
|
|
961
1512
|
// A continued conversation admitted before may be gone with no fault to say so: its
|
|
962
1513
|
// Harness restarted between the Turns. Then this Turn lands on a new one, and is told
|
|
963
1514
|
// (ADR-0062). A conversation never admitted has nothing to lose, so nothing is asked.
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
!(await client.holds(input.agentName, instanceId, { signal: controller.signal }))
|
|
968
|
-
) {
|
|
1515
|
+
// "Held" can be a timeout's, so the admission says it is for a kept conversation, and a
|
|
1516
|
+
// Harness that keeps none refuses it: then the Turn is told, and admitted again.
|
|
1517
|
+
const told = () =>
|
|
969
1518
|
binding.raiseNotice?.(
|
|
970
1519
|
{ kind: "conversation-new", scope: "conversation", reason: "the Harness no longer holds it" },
|
|
971
1520
|
conversation,
|
|
972
1521
|
);
|
|
973
|
-
|
|
974
|
-
|
|
1522
|
+
const asked = input.continuation && binding.admissionOf?.(instanceId) !== undefined;
|
|
1523
|
+
const kept =
|
|
1524
|
+
asked &&
|
|
1525
|
+
(await client.holds(input.agentName, instanceId, {
|
|
1526
|
+
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(holdsReadMs)]),
|
|
1527
|
+
}));
|
|
1528
|
+
if (asked && !kept) told();
|
|
1529
|
+
if (kept) {
|
|
1530
|
+
try {
|
|
1531
|
+
admission = await admitTurn(framed, { notices: "turn", task: true, kept: true });
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
if (!isNotKept(err)) throw err;
|
|
1534
|
+
if (stopped) return;
|
|
1535
|
+
told();
|
|
1536
|
+
admission = await admitTurn(framed);
|
|
1537
|
+
}
|
|
1538
|
+
} else admission = await admitTurn(framed);
|
|
975
1539
|
// The admission marker (ADR-0023): the Turn and its framing, once — a re-attach
|
|
976
1540
|
// continues a Turn already announced, and a nudge (below) is mechanism, not narrative
|
|
977
1541
|
// (its telemetry already rides the feed).
|
|
@@ -1045,15 +1609,36 @@ export function agentActorWith(
|
|
|
1045
1609
|
attempt: nudges,
|
|
1046
1610
|
reason: woken ? "picks allowed again" : "no-signal nudge",
|
|
1047
1611
|
});
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1612
|
+
// The nudge and the wake ask the same conversation, so they say `kept`: the Harness can
|
|
1613
|
+
// lose it while the Turn waits (the Instance Harness's restart is on no watch), and a
|
|
1614
|
+
// Harness that keeps none then refuses, never seating an empty conversation whose model
|
|
1615
|
+
// reads the nudge with no task. On the refusal the Turn takes the lost-conversation path,
|
|
1616
|
+
// as the re-admission after a cut does: admitted as a Turn with its task, or a fault when
|
|
1617
|
+
// there is no task to send. Its prompt is the task alone, as a continued Turn's is after
|
|
1618
|
+
// the same refusal: nothing was cut, so the re-admit text would not be true. The ask was
|
|
1619
|
+
// spent and said; the re-admission is said too.
|
|
1620
|
+
try {
|
|
1621
|
+
admission = await admitTurn(
|
|
1622
|
+
{
|
|
1623
|
+
...framed,
|
|
1624
|
+
attach: undefined,
|
|
1625
|
+
instanceId: currentIid,
|
|
1626
|
+
prompt: NUDGE_PROMPT,
|
|
1627
|
+
},
|
|
1628
|
+
{ notices: "nudge", task: false, kept: true },
|
|
1629
|
+
);
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
const task = input.prompt ?? input.attach?.task;
|
|
1632
|
+
if (!isNotKept(err) || task === undefined) throw err;
|
|
1633
|
+
if (stopped) return;
|
|
1634
|
+
admission = await admitOntoLost(task, [], [], reraiseUnheard(true));
|
|
1635
|
+
binding.telemetry?.({
|
|
1636
|
+
kind: "retry",
|
|
1637
|
+
child: self._parent?.id ?? self.id,
|
|
1638
|
+
attempt: nudges,
|
|
1639
|
+
reason: "re-admitted on a new conversation",
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1057
1642
|
}
|
|
1058
1643
|
} catch (err) {
|
|
1059
1644
|
if (stopped) return;
|
|
@@ -1086,10 +1671,13 @@ export function agentActorWith(
|
|
|
1086
1671
|
const kill = lostHarness && sandbox !== undefined ? await memoryFault(sandbox) : undefined;
|
|
1087
1672
|
if (stopped) return;
|
|
1088
1673
|
if (kill !== undefined && noticeWorkspace !== undefined) {
|
|
1674
|
+
// The kill cut this Turn (D4 rule 2): its Agent's next Turn retries the work, and hears it.
|
|
1089
1675
|
binding.raiseNotice?.(
|
|
1090
|
-
memoryLimit(kill.limit
|
|
1676
|
+
memoryLimit({ limit: kill.limit, agent: input.agentName }),
|
|
1091
1677
|
noticeWorkspace,
|
|
1092
|
-
kill.at === undefined ? undefined :
|
|
1678
|
+
kill.at === undefined ? undefined : kernelSource(noticeWorkspace, kill.at),
|
|
1679
|
+
[],
|
|
1680
|
+
{ retry: { slot, cut: currentIid }, ...(kill.at === undefined ? {} : { at: kill.at }) },
|
|
1093
1681
|
);
|
|
1094
1682
|
}
|
|
1095
1683
|
// A restart the Harness came back from keeps the conversation (ADR-0031); a 404 does not.
|