@jr2/orchestrator 0.4.1 → 0.5.1

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.
Files changed (327) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +442 -0
  10. package/docs/concepts/architecture.md +230 -0
  11. package/docs/concepts/charts.md +143 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +254 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +603 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +289 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +222 -0
  20. package/docs/concepts/the-menu.md +378 -0
  21. package/docs/concepts/workspaces.md +678 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  27. package/docs/get-started/install.md +126 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +223 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/fix-a-failed-converge.md +361 -0
  34. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  35. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  36. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  37. package/docs/guides/look-inside-a-sandbox.md +174 -0
  38. package/docs/guides/mirror-the-kit-images.md +176 -0
  39. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  40. package/docs/guides/open-the-console.md +91 -0
  41. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  42. package/docs/guides/push-and-sign-a-branch.md +486 -0
  43. package/docs/guides/run-a-chart-by-name.md +270 -0
  44. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  45. package/docs/guides/run-one-worker-per-item.md +321 -0
  46. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  47. package/docs/guides/size-a-workspace.md +204 -0
  48. package/docs/guides/start-a-run-from-a-run.md +237 -0
  49. package/docs/guides/start-a-run.md +207 -0
  50. package/docs/guides/upgrade-the-kit-version.md +240 -0
  51. package/docs/guides/use-a-hosted-provider.md +208 -0
  52. package/docs/guides/use-a-local-model.md +313 -0
  53. package/docs/guides/watch-a-run.md +326 -0
  54. package/docs/guides/write-a-workflow.md +318 -0
  55. package/docs/index.md +44 -0
  56. package/docs/llms.txt +216 -0
  57. package/docs/reference/authoring-surface/agent.md +128 -0
  58. package/docs/reference/authoring-surface/answer.md +140 -0
  59. package/docs/reference/authoring-surface/arg.md +60 -0
  60. package/docs/reference/authoring-surface/basic.md +62 -0
  61. package/docs/reference/authoring-surface/bearer.md +53 -0
  62. package/docs/reference/authoring-surface/chartschema.md +23 -0
  63. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  64. package/docs/reference/authoring-surface/command.md +191 -0
  65. package/docs/reference/authoring-surface/customize.md +64 -0
  66. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  67. package/docs/reference/authoring-surface/defineevent.md +114 -0
  68. package/docs/reference/authoring-surface/dispatch.md +126 -0
  69. package/docs/reference/authoring-surface/fail.md +83 -0
  70. package/docs/reference/authoring-surface/file.md +122 -0
  71. package/docs/reference/authoring-surface/frompick.md +277 -0
  72. package/docs/reference/authoring-surface/http.md +159 -0
  73. package/docs/reference/authoring-surface/isfailure.md +58 -0
  74. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  75. package/docs/reference/authoring-surface/open.md +74 -0
  76. package/docs/reference/authoring-surface/openbody.md +86 -0
  77. package/docs/reference/authoring-surface/paramsof.md +125 -0
  78. package/docs/reference/authoring-surface/pool.md +186 -0
  79. package/docs/reference/authoring-surface/push.md +112 -0
  80. package/docs/reference/authoring-surface/secret.md +118 -0
  81. package/docs/reference/authoring-surface/sentby.md +83 -0
  82. package/docs/reference/authoring-surface/source.md +51 -0
  83. package/docs/reference/authoring-surface/text.md +62 -0
  84. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  85. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  86. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  87. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  88. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  89. package/docs/reference/authoring-surface/type-arg.md +19 -0
  90. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  91. package/docs/reference/authoring-surface/type-binding.md +62 -0
  92. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  93. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  94. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  95. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  96. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  97. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  98. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  99. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  100. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  101. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  102. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  103. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  104. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  105. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  106. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  107. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  108. package/docs/reference/authoring-surface/type-childat.md +12 -0
  109. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  110. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  111. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  112. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  113. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  114. package/docs/reference/authoring-surface/type-customize.md +109 -0
  115. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  116. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  117. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  118. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  119. package/docs/reference/authoring-surface/type-duration.md +15 -0
  120. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  121. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  122. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  124. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  125. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  126. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  127. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  128. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  129. package/docs/reference/authoring-surface/type-failure.md +19 -0
  130. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  131. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  132. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  133. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  134. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  135. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  136. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  137. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  138. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  139. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  140. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  141. package/docs/reference/authoring-surface/type-lends.md +38 -0
  142. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  143. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  144. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  145. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  146. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  147. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  148. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  149. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  150. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  151. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  152. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  153. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  154. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  155. package/docs/reference/authoring-surface/type-openof.md +12 -0
  156. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  157. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  158. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  159. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  160. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  161. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  162. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  163. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  164. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  165. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  166. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  167. package/docs/reference/authoring-surface/type-reached.md +12 -0
  168. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  169. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  170. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  171. package/docs/reference/authoring-surface/type-secret.md +25 -0
  172. package/docs/reference/authoring-surface/type-sender.md +19 -0
  173. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  174. package/docs/reference/authoring-surface/type-size.md +85 -0
  175. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  176. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  177. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  178. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  179. package/docs/reference/authoring-surface/type-text.md +19 -0
  180. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  181. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  182. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  183. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  184. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  185. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  186. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  187. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  188. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  189. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  190. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  191. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  192. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  193. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  194. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  195. package/docs/reference/authoring-surface/workspace.md +259 -0
  196. package/docs/reference/authoring-surface.md +197 -0
  197. package/docs/reference/cli.md +433 -0
  198. package/docs/reference/config.md +756 -0
  199. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  200. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  201. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  202. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  203. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  204. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  205. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  206. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  207. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  208. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  209. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  210. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  211. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  212. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  213. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  214. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  215. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  216. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  217. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  218. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  219. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  220. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  221. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  222. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  223. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  224. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  225. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  226. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  227. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  228. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  229. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  230. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  231. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  232. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  233. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  234. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  235. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  236. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  237. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  238. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  239. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  240. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  241. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  242. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  243. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  244. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  245. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  246. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  247. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  248. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  249. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  250. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  251. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  252. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  253. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  254. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  255. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  256. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  257. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  258. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  259. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  260. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  261. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  262. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  263. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  264. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  265. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  266. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  267. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  268. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  269. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  270. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  271. package/docs/reference/decisions.md +83 -0
  272. package/docs/reference/glossary.md +648 -0
  273. package/docs/reference/http-api.md +1348 -0
  274. package/docs/reference/machines.md +190 -0
  275. package/docs/reference/mechanism-events.md +72 -0
  276. package/docs/why.md +206 -0
  277. package/package.json +5 -3
  278. package/src/actor.ts +541 -79
  279. package/src/agent.ts +114 -38
  280. package/src/ambient.ts +2 -1
  281. package/src/assembly-checks.ts +189 -7
  282. package/src/cause.ts +234 -0
  283. package/src/chart.ts +169 -37
  284. package/src/charts.ts +34 -6
  285. package/src/config.ts +430 -178
  286. package/src/custodian.ts +74 -22
  287. package/src/customize.ts +188 -46
  288. package/src/dispatch-effect.ts +945 -0
  289. package/src/door-compat.ts +217 -0
  290. package/src/durability.ts +11 -4
  291. package/src/effect.ts +341 -44
  292. package/src/effector-wire.ts +160 -11
  293. package/src/fail.ts +209 -0
  294. package/src/harness-client.ts +541 -47
  295. package/src/http-effect.ts +495 -53
  296. package/src/http.ts +427 -16
  297. package/src/index.ts +60 -82
  298. package/src/instance.ts +68 -8
  299. package/src/internal.ts +84 -0
  300. package/src/machine-doc.ts +21 -3
  301. package/src/mechanism-routes.ts +168 -0
  302. package/src/model-keys.generated.ts +53 -0
  303. package/src/model-keys.ts +18 -58
  304. package/src/notices.ts +66 -13
  305. package/src/open-body.ts +159 -23
  306. package/src/open-effect.ts +81 -8
  307. package/src/open.ts +59 -6
  308. package/src/parts.ts +184 -13
  309. package/src/party.ts +68 -2
  310. package/src/pick.ts +463 -11
  311. package/src/pool.ts +247 -36
  312. package/src/registration.ts +83 -4
  313. package/src/repos.ts +2 -1
  314. package/src/run-host.ts +565 -31
  315. package/src/run-output.ts +38 -0
  316. package/src/sandbox-effect.ts +273 -38
  317. package/src/sandbox-kube.ts +3 -1
  318. package/src/secret-placement.ts +52 -17
  319. package/src/server.ts +3 -0
  320. package/src/setup.ts +239 -56
  321. package/src/size.ts +121 -9
  322. package/src/snapshot-store.ts +54 -10
  323. package/src/stub-harness.ts +44 -16
  324. package/src/timers.ts +14 -1
  325. package/src/vocabulary.ts +69 -14
  326. package/src/wire.ts +90 -16
  327. package/src/workspace.ts +697 -175
package/src/actor.ts CHANGED
@@ -57,7 +57,8 @@ import { SENDER_KEY } from "@jr2/agent-protocol";
57
57
  import { agentAddress, continuedIid, allowedPicks, resolveAccepts, runBindingOf } from "./registration.ts";
58
58
  import type { NoticeScope } from "./notices.ts";
59
59
  import { restOf } from "./rest.ts";
60
- import { SUBMISSION_HARNESS_RESTARTED, SUBMISSION_RUNAWAY, type Notice } from "./wire.ts";
60
+ import { SUBMISSION_HARNESS_RESTARTED, SUBMISSION_RUNAWAY, type Notice, type PodMemoryKill } from "./wire.ts";
61
+ import type { AbortCause } from "./wire.ts";
61
62
 
62
63
  /**
63
64
  * One admitted Submission — the durable re-attach handle (ADR-0016). The wire fields are
@@ -80,6 +81,13 @@ export type AgentAdmission = {
80
81
  * aborts the LIVE conversation, never the dead original the ledger is keyed by.
81
82
  */
82
83
  instanceId?: string;
84
+ /**
85
+ * The Turn that ledgered this admission — the input's `turnId`, stamped by the ACTOR at ledger
86
+ * time. The ledger is keyed by `instanceId`, which a continued Turn shares with the Turn before
87
+ * it, so a restore compares this with the restored input's own: a mismatch is an admission of an
88
+ * EARLIER Turn, and the restored Turn was not admitted yet (ADR-0016).
89
+ */
90
+ turnId?: string;
83
91
  /**
84
92
  * The Harness container's restart count when this admission was answered (ADR-0021) — stamped by
85
93
  * the ACTOR, for a Turn in a Sandbox Workspace whose watch had spoken. A count above it means
@@ -87,6 +95,25 @@ export type AgentAdmission = {
87
95
  * mark.
88
96
  */
89
97
  harnessRestarts?: number;
98
+ /**
99
+ * When this admission's Turn started — stamped by the ACTOR on every admission of a Turn in a
100
+ * Workspace, so a restore reads back the real start, not the restore's: a kernel kill is held against it
101
+ * (ADR-0061), and a guard kill too when the Turn's first read of the pod's kills failed.
102
+ */
103
+ turnStarted?: string;
104
+ /**
105
+ * The newest guard kill in the pod's list when the Turn's first read of it was answered
106
+ * (ADR-0062), or {@link NO_KILLS} for an empty list — stamped by the ACTOR on every admission of
107
+ * a Turn in a Workspace. A nudge or a wake carries only kills after it, so it never tells the Agent of a kill
108
+ * from before its Turn. Absent when that first read failed.
109
+ */
110
+ killMark?: string;
111
+ /**
112
+ * The Turn's task, its Frame's prompt — stamped by the ACTOR on every admission of the Turn,
113
+ * because a restore drops `prompt` from the input: a re-admission onto a conversation the
114
+ * Harness lost must send the task again, and has no other copy of it (ADR-0062).
115
+ */
116
+ task?: string;
90
117
  /**
91
118
  * The Agent and the Harness base URL this admission was answered by — stamped by the ACTOR at
92
119
  * ledger time, like `instanceId`. Together with `instanceId` they name the conversation on its
@@ -137,6 +164,8 @@ export type AgentTurnInput = {
137
164
  * ADR-0028's containment claim, which per-invocation escalation would void.
138
165
  */
139
166
  model?: string;
167
+ /** Omitted → the definition's, else `off`. A model with no `off` runs at the level its provider
168
+ * chooses; the printed conversation names it. */
140
169
  thinkingLevel?: ThinkingLevel;
141
170
  /**
142
171
  * Continue this Agent's ONE conversation in this Machine instance (ADR-0016, ADR-0057) — the
@@ -182,6 +211,14 @@ export type AgentRunInput = {
182
211
  * is what the Harness route, the minted iid, the markers and the telemetry all name. */
183
212
  agentName: string;
184
213
  instanceId: string;
214
+ /**
215
+ * This Turn's Turn ID — minted by the input mapper, one per invocation, and persisted with the
216
+ * input (ADR-0016). Two Turns that continue one conversation share `instanceId`, the ledger's
217
+ * key, and not this: the actor stamps it on every admission it ledgers, and a restore
218
+ * re-attaches only an admission that this Turn ledgered. Absent (a plain `setup()` machine that
219
+ * passes finalized input), the key alone decides, as before.
220
+ */
221
+ turnId?: string;
185
222
  /**
186
223
  * The Harness base URL, EXPLICIT (ADR-0016): only for a workspace-less run (the mechanics
187
224
  * tier's stub Harness, a dev endpoint — just a URL, ADR-0011). Inside a `workspace()` leave it
@@ -235,13 +272,26 @@ export type AgentRunInput = {
235
272
  menu?: readonly string[];
236
273
  };
237
274
 
238
- /** Telemetry sent up when the run is out of options: the Submission settled failed/aborted
239
- * (infra fault after the turn's own provider retries — ADR-0027), the no-signal nudge budget ran
240
- * dry, or the runaway reroll budget did (ADR-0035). The ONE terminal event (ADR-0016) — where it
241
- * routes is workflow policy. */
275
+ // Sent when the run is out of options: the Submission settled failed/aborted (infra fault after
276
+ // the turn's own provider retries — ADR-0027), the no-signal nudge budget ran dry, or the runaway
277
+ // reroll budget did (ADR-0035). The ONE terminal event (ADR-0016) — where it routes is workflow
278
+ // policy.
279
+ /** jr2 sends `agent.fault` to the Machine when an Agent's Turn cannot go on: the Harness failed
280
+ * the Turn, or the Agent ended its Turns with no pick after every nudge. The Machine decides what
281
+ * to do next. Route it in the state that invokes the Agent, or in a state above it, with
282
+ * `on: { "agent.fault": … }`; one route on the root is a route for each state. `jr2 check` and
283
+ * `jr2 up` refuse a state that invokes an Agent, or an Open body that can run one, with no route for
284
+ * it. When the event comes and no transition takes it (the route's guard is false), the run ends
285
+ * `error`, with the event, the state and the reason in its fault. In a Chart the route is optional:
286
+ * when no transition of the Chart takes the event, the Open body sends it, unchanged, to the Machine
287
+ * that invoked the body, whose route takes it. */
242
288
  export type FaultTelemetry = {
243
289
  type: "agent.fault";
290
+ /** The Instance ID of the Agent's conversation that failed: the same id on each Turn that
291
+ * continues it. */
244
292
  instanceId: string;
293
+ /** Why the Turn failed, as text for a person or a log: the Harness's error, or that the Agent
294
+ * ended its Turns without a pick after every nudge. Route on the event, not on this text. */
245
295
  reason: string;
246
296
  };
247
297
 
@@ -267,24 +317,39 @@ export interface AgentRunPort {
267
317
  admit(input: AgentRunInput, opts: AgentAdmitOptions): Promise<AgentAdmission>;
268
318
  /**
269
319
  * Follow an admitted submission until it settles. Resolving means the submission completed;
270
- * rejecting means it settled failed/aborted (or the conversation is gone).
320
+ * rejecting means it settled failed/ended/aborted (or the conversation is gone).
271
321
  */
272
322
  settle(admission: AgentAdmission, opts?: AgentSettleOptions): Promise<void>;
273
323
  /**
274
324
  * Does the Harness still hold this conversation (ADR-0062)? Asked before a continued Turn whose
275
325
  * conversation was admitted before: a Harness that restarted between the Turns lost it with no
276
326
  * fault to say so, and the Turn is told its conversation is new. Answers `true` whenever it
277
- * cannot tell — unknown is never loss.
327
+ * cannot tell — unknown is never loss. A wrong `true` is caught at the admission that follows:
328
+ * it says `kept`, and a Harness that keeps no such conversation refuses it.
278
329
  */
279
330
  holds(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<boolean>;
331
+ /**
332
+ * The pod's guard kills (ADR-0061, ADR-0062), every one with `told`: the conversations whose
333
+ * running `bash` call answered its verdict. A stream is read only while a Submission's wait
334
+ * runs, and a kill while none runs, or after the wait stopped, reaches no reader there. Asked before a Turn's admission into a
335
+ * Workspace. A read changes nothing on the Harness; the notice ledger keeps each kill's id.
336
+ * Answers `undefined` whenever it cannot tell — not `[]`, which says the list is empty — and the
337
+ * kill stays on the Harness for the next read.
338
+ */
339
+ memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined>;
280
340
  /**
281
341
  * End the instance's in-flight (and queued) work — the turn is over (ADR-0024). Resolving means
282
342
  * the intent is RECORDED, not that the submission has settled; jr2 never observes that outcome,
283
343
  * because the actor is already stopped by the time this is called. The actor passes no `signal`
284
344
  * for exactly that reason — its own controller is already aborted — but the option is here for
285
- * parity with the other two.
345
+ * parity with the other two. `cause: "pick"` says the Turn's own pick ended it, so the Harness
346
+ * settles the Submission `ended`; with no cause it settles `aborted`.
286
347
  */
287
- abort(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<void>;
348
+ abort(
349
+ agentName: string,
350
+ instanceId: string,
351
+ opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
352
+ ): Promise<void>;
288
353
  }
289
354
 
290
355
  /** What every admission carries beside the input: the Agent definition this Turn runs (ADR-0049),
@@ -297,18 +362,58 @@ export type AgentAdmitOptions = {
297
362
  /** The pending notices in this admission's scope (ADR-0062), for the Briefing's Turn part. Not
298
363
  * on the persisted input either: they are the Orchestrator's, and delivered once. */
299
364
  notices?: Notice[];
365
+ /** The admission is for a conversation that `holds` said the Harness keeps (ADR-0062): the
366
+ * re-admission after a cut, and a continued Turn. That "kept" can be a timeout's, so a Harness
367
+ * that keeps none refuses it and seats none; `admit` then rejects with an error whose `notKept`
368
+ * is true (the wire client's `ConversationNotKept`). */
369
+ kept?: boolean;
300
370
  };
301
371
 
302
- /** A memory kill by the Harness's own guard (ADR-0061, layer 4), as the updates stream reports it:
303
- * the guard's `peak` and `limit`, and `source` — where on which conversation's stream the event
304
- * sits, so a replayed read of it is the same kill. */
305
- export type GuardKill = { peak: string; limit: string; source: string };
372
+ /** A memory kill by the Harness's own guard (ADR-0061, layer 4), as the Orchestrator reads it on
373
+ * an updates stream — the twin of {@link ListedKill}, which it reads in the pod's list:
374
+ * the guard's `peak` and `limit` — absent when what the stream said was not a size — and the
375
+ * kill's `id`, the same on every stream that carries it and in the pod's list
376
+ * (`AgentRunPort.memoryKills`), so every witness of one kill is one notice. `legacy`: an older
377
+ * Harness's event, which has no id; `id` is then the source the Orchestrator raised such an event
378
+ * with before, its place on the stream, so a replayed read is still the same kill. */
379
+ export type StreamedKill = { id: string; legacy?: true; peak?: string; limit?: string };
380
+
381
+ /** A kill read from the pod's list, as the port answers it: the wire's, but `peak` and `limit` are
382
+ * absent when the list's were not sizes — the kill is kept, and worded with no sizes (ADR-0062). */
383
+ export type ListedKill = Omit<PodMemoryKill, "peak" | "limit"> & { peak?: string; limit?: string };
384
+
385
+ /** The source a guard kill's notice is raised with: the Workspace and the kill's id. Both paths use
386
+ * it — the stream and the pod's list — so the ledger's `seen` makes them one notice. */
387
+ const guardSource = (sandbox: string, id: string): string => `${sandbox}@guard:${id}`;
388
+
389
+ /** The source a kernel kill's notice is raised with (ADR-0061, layer 5): the Workspace and when the
390
+ * Harness container ended, which names that one end. Both paths use it — the fault of a Turn the
391
+ * kill ended, and the read before an admission — so the ledger's `seen` makes them one notice. */
392
+ const kernelSource = (sandbox: string, at: string): string => `${sandbox}@${at}`;
393
+
394
+ /** How long the read of the pod's kills may take before it counts as none. */
395
+ const MEMORY_KILLS_READ_MS = 5_000;
396
+
397
+ /** How long "does the Harness still hold this conversation?" is asked again before the answer is
398
+ * taken as "held" — unknown is never loss, but a container that is starting is not unknown yet. */
399
+ const HOLDS_READ_MS = 30_000;
400
+
401
+ /** A `killMark` for a list that was empty: every kill read after it is the Turn's. Kill ids start
402
+ * `k-`, so it is never one. */
403
+ export const NO_KILLS = "none";
404
+
405
+ /** How far the pod's clock may run behind this one when a kill's `at` is held against a Turn's
406
+ * start — only when the Turn has no `killMark`: a kill stamped up to this much before the start
407
+ * still counts as in the Turn, so a slow pod clock does not hide a kill of this Turn. The two
408
+ * clocks are a node's and the Orchestrator's, both kept by the cluster's time service; one second
409
+ * is far more than they drift, and far less than a Turn. */
410
+ const KILL_CLOCK_SKEW_MS = 1_000;
306
411
 
307
412
  /** What following a settlement may be told beside the settlement itself. */
308
413
  export type AgentSettleOptions = {
309
414
  signal?: AbortSignal;
310
415
  /** The Harness's guard killed the Agent's processes during this Submission (ADR-0061). */
311
- onMemoryLimit?: (kill: GuardKill) => void;
416
+ onMemoryLimit?: (kill: StreamedKill) => void;
312
417
  };
313
418
 
314
419
  /** Build a port for one invocation from its serializable input (ADR-0011 static-import doctrine),
@@ -335,6 +440,17 @@ export type AgentRunOptions = {
335
440
  * pathological, which belongs with the workflow's fault routing.
336
441
  */
337
442
  runawayBudget?: number;
443
+ /**
444
+ * How long the read of the pod's guard kills (ADR-0062) may take before it counts as none and
445
+ * the Turn is admitted without it. Default 5000 ms; a seam for tests, never an author dial.
446
+ */
447
+ memoryKillsReadMs?: number;
448
+ /**
449
+ * How long the actor asks whether a restarted Harness still holds a conversation before it
450
+ * takes "held" for an answer: the new container may not listen yet (ADR-0062). Default 30 s; a
451
+ * seam for tests, never an author dial.
452
+ */
453
+ holdsReadMs?: number;
338
454
  };
339
455
 
340
456
  /**
@@ -403,6 +519,12 @@ function isLostConversation(err: unknown): boolean {
403
519
  return typeof err === "object" && err !== null && (err as { lost?: unknown }).lost === true;
404
520
  }
405
521
 
522
+ /** An admission for a kept conversation that the Harness refused: it keeps none (ADR-0062). Read
523
+ * structurally, as {@link isLostConversation} is: the wire client's `ConversationNotKept`. */
524
+ function isNotKept(err: unknown): boolean {
525
+ return typeof err === "object" && err !== null && (err as { notKept?: unknown }).notKept === true;
526
+ }
527
+
406
528
  /**
407
529
  * The nudge (ADR-0006, absorbed here by ADR-0016). It names no picks: the Harness writes the Turn
408
530
  * part after it, stating the Allowed picks in the model's own tool names and narrowed by the guards
@@ -437,8 +559,8 @@ const READMIT_PROMPT =
437
559
  * slot's Worktree, which privileges nothing because there is nothing to choose between (ADR-0051);
438
560
  * a Workspace with more REFUSES the Turn, naming the slots in declaration order and the line that
439
561
  * ends it, because the kit gives no slot a meaning and a wrong guess is the silent failure
440
- * ADR-0057 was written for; and a run outside any Workspace frames nothing, so the Harness keeps
441
- * `/work`.
562
+ * ADR-0057 was written for; and a Workspace with no Repo Slot, or a run outside any Workspace,
563
+ * frames nothing, so the Harness keeps `/work`, the folder a slotless Workspace's steps share.
442
564
  *
443
565
  * Resolution counts SLOTS, and a slot's Worktree is the writable branch checkout. The detached
444
566
  * review Worktree (`AmbientHandles.review`, ADR-0028's reviewer seat) is never resolved to: it is
@@ -477,7 +599,10 @@ function frameCwd(
477
599
  *
478
600
  * The brand is a RUNTIME property, read back through `isAgent`, and deliberately NOT part of this
479
601
  * 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. */
602
+ * `definition` here would make every fake carry a definition it never uses.
603
+ *
604
+ * @internal
605
+ */
481
606
  export type AgentLogic = CallbackActorLogic<
482
607
  AgentRunReceiveEvent,
483
608
  AgentTurnInput | (AgentTurnInput & AgentTurnPlacement) | AgentRunInput
@@ -506,6 +631,8 @@ export function agentActorWith(
506
631
  ): AgentLogic {
507
632
  const nudgeBudget = options.nudgeBudget ?? 2;
508
633
  const runawayBudget = options.runawayBudget ?? 1;
634
+ const memoryKillsReadMs = options.memoryKillsReadMs ?? MEMORY_KILLS_READ_MS;
635
+ const holdsReadMs = options.holdsReadMs ?? HOLDS_READ_MS;
509
636
  // Typed as the union so BOTH shapes typecheck on an invoke: jr2Setup machines write
510
637
  // AgentTurnInput (and the config wrapper finalizes it before the actor ever runs); plain
511
638
  // setup() machines must pass the finalized shape themselves — checked loudly below.
@@ -654,7 +781,8 @@ export function agentActorWith(
654
781
  // When this Turn started: a memory kill that ended the Harness before it is not this Turn's
655
782
  // (ADR-0061). Undefined from a backend that cannot see the container — and never throws:
656
783
  // naming the fault is a courtesy, the fault itself lands either way.
657
- const turnStarted = new Date();
784
+ // Read back on a restore: the start is the Turn's, not this process's.
785
+ const turnStarted = input.attach?.turnStarted ? new Date(input.attach.turnStarted) : new Date();
658
786
  const memoryFault = (name: string): Promise<MemoryKill | undefined> =>
659
787
  binding.sandbox?.memoryFault(name, turnStarted).catch(() => undefined) ?? Promise.resolve(undefined);
660
788
  // Replaced after a Harness restart that cut the Turn while its state was busy: the cut aborted it.
@@ -699,7 +827,13 @@ export function agentActorWith(
699
827
  const ledger = (admission: AgentAdmission, delivered: readonly string[]) =>
700
828
  binding.recordAdmission?.(
701
829
  instanceId,
702
- { ...admission, instanceId: currentIid, agent: input.agentName, endpoint },
830
+ {
831
+ ...admission,
832
+ instanceId: currentIid,
833
+ agent: input.agentName,
834
+ endpoint,
835
+ ...(input.turnId === undefined ? {} : { turnId: input.turnId }),
836
+ },
703
837
  delivered,
704
838
  );
705
839
 
@@ -712,45 +846,212 @@ export function agentActorWith(
712
846
  ...(input.continuation ? { conversation } : {}),
713
847
  ...(noticeWorkspace !== undefined ? { workspace: noticeWorkspace } : {}),
714
848
  };
715
- const memoryLimit = (limit: string, peak?: string): Notice => ({
849
+ // No `agent`: the notice names no Turn — what the pod's list says (below).
850
+ // No `limit`: the sizes read were not sizes, and the notice says none.
851
+ const memoryLimit = ({ limit, peak, agent }: { limit?: string; peak?: string; agent?: string }): Notice => ({
716
852
  kind: "memory-limit",
717
853
  scope: "workspace",
718
- agent: input.agentName,
719
- ...(peak === undefined ? {} : { peak }),
720
- limit,
854
+ ...(agent === undefined ? {} : { agent }),
855
+ ...(limit === undefined ? {} : { limit, ...(peak === undefined ? {} : { peak }) }),
721
856
  });
722
- // The Harness's guard killed this Workspace's processes during the Turn (ADR-0061, layer 4):
723
- // the Agent heard it in its `bash` answer; the next Turn in the Workspace hears it here.
724
- const onMemoryLimit = (kill: GuardKill): void => {
725
- if (noticeWorkspace === undefined) return;
726
- binding.raiseNotice?.(memoryLimit(kill.limit, kill.peak), noticeWorkspace, kill.source);
857
+ // The Harness's guard killed this Workspace's processes while this Turn's Submission ran
858
+ // (ADR-0061, layer 4), read on its stream while the wait is live — so the notice can name this
859
+ // Turn's Agent. A stopped actor's last poll names nothing: the state that asked for the Turn has
860
+ // exited, and the pod's list carries the kill. Keyed by the kill's id, so a second running
861
+ // conversation's stream, or the pod's list, adds nothing.
862
+ const onMemoryLimit = (kill: StreamedKill): void => {
863
+ if (stopped || noticeWorkspace === undefined) return;
864
+ binding.raiseNotice?.(
865
+ memoryLimit({ limit: kill.limit, peak: kill.peak, agent: input.agentName }),
866
+ noticeWorkspace,
867
+ kill.legacy ? kill.id : guardSource(noticeWorkspace, kill.id),
868
+ );
869
+ };
870
+ // …but a stream is not every kill's path. A kill while no Submission ran — a process an earlier
871
+ // Turn left running, killed while the workflow waited at a Gate — is on no stream, and a kill
872
+ // after a wait stopped (the Agent's state exited, its abort not yet landed) is on a stream that
873
+ // no one reads. The Harness keeps every kill, so the pod's list is read before notices are
874
+ // taken, and this admission carries what it finds. Keyed as on the stream: the ledger's `seen`,
875
+ // persisted with the run, makes a kill read on both paths, or read again after a restart of
876
+ // either side, one notice. The notice names no Turn: the list cannot say whose Turn a kill
877
+ // ended. One read raises at most one notice, the newest kill's; the older kills in it are only
878
+ // marked seen, so they never give a notice. A read that fails or times out is none: the list
879
+ // keeps the kill, and the next admission reads again.
880
+ //
881
+ // The Turn's first read sets its `killMark`, the newest kill it saw, ledgered with every
882
+ // admission of the Turn. Answers the sources a nudge or a wake of THIS conversation may carry:
883
+ // the kills after that mark — none from before the Turn, which would tell the Agent that
884
+ // processes it started in this Turn died — and after the newest kill its own `bash` call
885
+ // answered, whose answer said all its processes died. Those stay pending for the next Turn.
886
+ // Only when the first read failed is the Turn's start time the mark, held against `at`.
887
+ // A kernel kill (ADR-0061, layer 5) ends the Harness container, so no list keeps it and no
888
+ // stream carries it. A Turn it ended faults on it, and the fault raises its notice (below). A
889
+ // kill while no Turn ran faults nothing: the restarted Harness rebuilds its conversations. But
890
+ // the operator publishes the container's last end on the Sandbox, and the watch keeps it until
891
+ // the next end. So before each admission into the Workspace, a last end `OOMKilled` raises the
892
+ // notice, keyed by that end as the fault keys it: one notice for one kill, whichever path saw
893
+ // it first, and none again after a restart of either side. It names no Turn: the end cannot say
894
+ // whose Turn it was. A watch that has not listed yet says nothing; the next admission asks again.
895
+ // Not after a restart this Turn's watch saw: the Turn faults on it, or is re-admitted, and the
896
+ // fault's notice names this Turn's Agent — a raise here would take its source first.
897
+ const hearKernelKill = async (): Promise<void> => {
898
+ if (restarted !== undefined) return;
899
+ const last = heardRestarts?.lastTerminated;
900
+ if (noticeWorkspace === undefined || last?.reason !== OOM_KILLED || last.finishedAt === undefined) return;
901
+ const kill = await binding.sandbox
902
+ ?.memoryFault(noticeWorkspace, new Date(last.finishedAt))
903
+ .catch(() => undefined);
904
+ if (kill?.at === undefined) return;
905
+ binding.raiseNotice?.(
906
+ memoryLimit({ limit: kill.limit }),
907
+ noticeWorkspace,
908
+ kernelSource(noticeWorkspace, kill.at),
909
+ );
910
+ };
911
+ let killMark = input.attach?.killMark;
912
+ let firstRead = input.attach === undefined;
913
+ // `known: false`: the read could not tell, so which kills this conversation was told is not
914
+ // known either — an admission of the same Turn then takes no guard kill at all.
915
+ const hearPodKills = async (): Promise<{ known: boolean; unsaid: string[]; said: string[] }> => {
916
+ const none = { known: true, unsaid: [], said: [] };
917
+ if (noticeWorkspace === undefined) return none;
918
+ const first = firstRead;
919
+ firstRead = false;
920
+ let kills: ListedKill[] | undefined;
921
+ try {
922
+ kills = await client.memoryKills({
923
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(memoryKillsReadMs)]),
924
+ });
925
+ } catch (err) {
926
+ // Stopped while it read: nothing more is admitted, and nothing is taken.
927
+ if (controller.signal.aborted) throw err;
928
+ return { ...none, known: false };
929
+ }
930
+ // Could not tell: no mark — an empty list is not what was seen — so the clock is the mark.
931
+ if (kills === undefined) return { ...none, known: false };
932
+ if (first) killMark = kills.at(-1)?.id ?? NO_KILLS;
933
+ const newest = kills.at(-1);
934
+ if (newest === undefined) return none;
935
+ binding.raiseNotice?.(
936
+ memoryLimit({ limit: newest.limit, peak: newest.peak }),
937
+ noticeWorkspace,
938
+ guardSource(noticeWorkspace, newest.id),
939
+ kills.slice(0, -1).map((kill) => guardSource(noticeWorkspace, kill.id)),
940
+ );
941
+ const toldHere = (kill: ListedKill): boolean =>
942
+ kill.told.some((t) => t.agent === input.agentName && t.instanceId === currentIid);
943
+ // The first kill after the Turn's mark. A mark no longer in the bounded list is older than
944
+ // every kill in it.
945
+ let from = 0;
946
+ kills.forEach((kill, i) => {
947
+ if (kill.id === killMark || toldHere(kill)) from = i + 1;
948
+ });
949
+ const inThisTurn = (kill: ListedKill): boolean =>
950
+ killMark !== undefined || Date.parse(kill.at) >= turnStarted.getTime() - KILL_CLOCK_SKEW_MS;
951
+ const unsaid = kills.slice(from).filter(inThisTurn);
952
+ return {
953
+ known: true,
954
+ unsaid: unsaid.map((kill) => guardSource(noticeWorkspace, kill.id)),
955
+ said: kills.filter((kill) => !unsaid.includes(kill)).map((kill) => guardSource(noticeWorkspace, kill.id)),
956
+ };
727
957
  };
728
958
 
729
959
  // 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 does not: it re-prompts the Turn
731
- // within itself (ADR-0016), whose Agent read a guard kill in its own `bash` answer, and a notice
732
- // spent there never reaches the next Turn in the Workspace it is for (ADR-0062). Reserved
960
+ // conversation is fresh, and has heard nothing. A nudge's and a wake's carry only the guard
961
+ // kills of this Turn that came after its own `bash` call's last kill verdict (`hearPodKills`):
962
+ // they re-prompt the Turn within itself (ADR-0016), so a kill that verdict covers is already in
963
+ // its context, and is for the next Turn in the Workspace (ADR-0062). A later kill — while its
964
+ // model wrote text, while it waited for its wake, or answered in another conversation — this
965
+ // Agent was never told. Reserved
733
966
  // first, so no concurrent admission carries them too; delivered by the ledger write; released
734
967
  // when the admission fails, for the next one to carry.
735
968
  //
736
969
  // What the last carrying admission took, and under which iid: delivered is not HEARD. If this
737
970
  // actor ends the Turn before the Harness reads that iid's surface, the Harness finds the Turn
738
971
  // over and settles it with no model asked (ADR-0026) — so `abandon` raises them again.
739
- let carried: { iid: string; notices: Notice[] } | undefined;
740
- const admitTurn = async (turn: AgentRunInput, opts: { notices: boolean; extra?: Notice[] } = { notices: true }) => {
741
- const taken = (opts.notices ? binding.takeNotices?.(noticeScope) : undefined) ?? { ids: [], notices: [] };
972
+ // Per conversation the Turn admitted on: the notices its admissions carried — `unheard` since
973
+ // the Harness last read its surface, `heard` before that — and whether an unheard one carried
974
+ // the Turn's task. Collected across the Turn, so a re-admission onto a lost conversation tells
975
+ // the Agent all of them again.
976
+ type Raised = { notice: Notice; source?: string };
977
+ // `fresh`: a fresh Turn's "this conversation is new", which rides an admission as `extra` and so
978
+ // is kept in no ledger — an unread one is carried again by the next re-admission.
979
+ const carriedBy = new Map<string, { heard: Raised[]; unheard: Raised[]; task: boolean; fresh: Notice[] }>();
980
+ // `notices`: "turn" takes every notice in scope; "same-turn" too, but the guard kills the Turn's own
981
+ // conversation was told (a re-admission after a cut continues the same Turn); "nudge" takes the
982
+ // guard kills this conversation was not told alone.
983
+ // `include`: sources restored for this admission, taken whatever the scope leaves out. `task`:
984
+ // this admission's prompt holds the Turn's task. `kept`: the admission is for a conversation
985
+ // that `holds` said the Harness keeps; a Harness that keeps none refuses it (`isNotKept`), and
986
+ // the refusal is thrown as any failed admission is.
987
+ const admitTurn = async (
988
+ turn: AgentRunInput,
989
+ opts: {
990
+ notices: "turn" | "same-turn" | "nudge";
991
+ extra?: Notice[];
992
+ include?: readonly string[];
993
+ task?: boolean;
994
+ kept?: boolean;
995
+ } = { notices: "turn", task: true },
996
+ ) => {
997
+ await hearKernelKill();
998
+ const { known, unsaid, said } = await hearPodKills();
999
+ // A read that could not tell leaves the same Turn no way to know which kills its own `bash`
1000
+ // reported: it takes none, as a nudge takes none then, and they stay for the next Turn.
1001
+ const sameTurn: NoticeScope = known
1002
+ ? { ...noticeScope, except: said }
1003
+ : {
1004
+ ...noticeScope,
1005
+ ...(noticeWorkspace === undefined ? {} : { exceptPrefix: guardSource(noticeWorkspace, "") }),
1006
+ };
1007
+ const scope: NoticeScope | undefined =
1008
+ opts.notices === "turn"
1009
+ ? noticeScope
1010
+ : opts.notices === "same-turn"
1011
+ ? sameTurn
1012
+ : unsaid.length > 0
1013
+ ? { workspace: noticeWorkspace, only: unsaid }
1014
+ : undefined;
1015
+ const none = { ids: [] as string[], notices: [] as Notice[], raised: [] as Raised[] };
1016
+ const restored =
1017
+ opts.include && opts.include.length > 0
1018
+ ? (binding.takeNotices?.({ ...noticeScope, only: opts.include }) ?? none)
1019
+ : none;
1020
+ const main = (scope ? binding.takeNotices?.(scope) : undefined) ?? none;
1021
+ const taken = {
1022
+ ids: [...restored.ids, ...main.ids],
1023
+ notices: [...restored.notices, ...main.notices],
1024
+ raised: [
1025
+ ...(restored.raised ?? restored.notices.map((notice) => ({ notice }))),
1026
+ ...(main.raised ?? main.notices.map((notice) => ({ notice }))),
1027
+ ],
1028
+ };
742
1029
  // `extra`: a notice this admission carries straight from the actor, never kept — the answer a
743
- // cut pick never got (ADR-0031), whose one road to the model is this admission.
744
- const notices = [...taken.notices, ...(opts.extra ?? [])];
1030
+ // cut pick never got (ADR-0031), whose one path to the model is this admission. One "this
1031
+ // conversation is new" is enough, whichever path brought it.
1032
+ let fresh = false;
1033
+ const notices = [...taken.notices, ...(opts.extra ?? [])].filter((n) => {
1034
+ if (n.kind !== "conversation-new") return true;
1035
+ if (fresh) return false;
1036
+ fresh = true;
1037
+ return true;
1038
+ });
1039
+ // `served` is per Instance ID, and a nudge or a wake reuses the Turn's: it must be read again
1040
+ // before this admission counts as heard (`reraiseUnheard`). An admission that fails puts the
1041
+ // mark back — what an earlier one carried was heard.
1042
+ const heard = served.has(turn.instanceId);
1043
+ served.delete(turn.instanceId);
745
1044
  let admission: AgentAdmission;
746
1045
  try {
747
1046
  admission = await client.admit(turn, {
748
1047
  definition,
749
1048
  signal: controller.signal,
750
1049
  ...(notices.length ? { notices } : {}),
1050
+ ...(opts.kept ? { kept: true } : {}),
751
1051
  });
752
1052
  } catch (err) {
753
1053
  binding.releaseNotices?.(taken.ids);
1054
+ if (heard) served.add(turn.instanceId);
754
1055
  throw err;
755
1056
  }
756
1057
  // The mark is taken once the Harness has ANSWERED: a restart before the answer took the
@@ -759,23 +1060,60 @@ export function agentActorWith(
759
1060
  admitted = true;
760
1061
  restartMark = heardRestarts?.restartCount;
761
1062
  if (restartMark !== undefined) admission = { ...admission, harnessRestarts: restartMark };
1063
+ // The Turn's task rides every ledgered admission: a restore drops `prompt`.
1064
+ const task = input.prompt ?? input.attach?.task;
1065
+ if (task !== undefined) admission = { ...admission, task };
1066
+ // A Turn in a Workspace keeps its start and its kill mark across a restore.
1067
+ if (noticeWorkspace !== undefined) {
1068
+ admission = {
1069
+ ...admission,
1070
+ turnStarted: turnStarted.toISOString(),
1071
+ ...(killMark === undefined ? {} : { killMark }),
1072
+ };
1073
+ }
762
1074
  ledger(admission, taken.ids);
763
- if (taken.notices.length) carried = { iid: turn.instanceId, notices: taken.notices };
1075
+ const bucket = carriedBy.get(turn.instanceId) ?? { heard: [], unheard: [], task: false, fresh: [] };
1076
+ if (heard) {
1077
+ bucket.heard.push(...bucket.unheard);
1078
+ bucket.unheard = [];
1079
+ bucket.task = false;
1080
+ bucket.fresh = [];
1081
+ }
1082
+ bucket.unheard.push(...taken.raised);
1083
+ if (opts.task) bucket.task = true;
1084
+ if (!input.continuation) bucket.fresh.push(...(opts.extra ?? []).filter((n) => n.kind === "conversation-new"));
1085
+ carriedBy.set(turn.instanceId, bucket);
764
1086
  return admission;
765
1087
  };
766
- // The notices an unheard admission carried, pending again for the next Turn in their scope.
767
- const reraiseUnheard = (): void => {
768
- if (carried === undefined || served.has(carried.iid)) return;
769
- for (const notice of carried.notices) {
770
- const to = notice.scope === "conversation" ? conversation : noticeWorkspace;
771
- if (to !== undefined) binding.raiseNotice?.(notice, to);
1088
+ // The notices unheard admissions carried, pending again for the next Turn in their scope —
1089
+ // each WITH the source it was raised with, so a take by source finds it and no equal notice of
1090
+ // another source swallows it. `lost`: the current conversation, which heard the rest, is gone,
1091
+ // so they are unheard too. Answers the sources restored, which a re-admission of the same Turn
1092
+ // takes whatever else it leaves out.
1093
+ const reraiseUnheard = (lost = false): string[] => {
1094
+ const restored: string[] = [];
1095
+ for (const [iid, bucket] of carriedBy) {
1096
+ const gone = lost && iid === currentIid;
1097
+ const read = served.has(iid) && !gone;
1098
+ const again = [...(gone ? bucket.heard : []), ...(read ? [] : bucket.unheard)];
1099
+ if (gone) bucket.heard = [];
1100
+ if (!read) bucket.unheard = [];
1101
+ for (const { notice, source } of again) {
1102
+ const to = notice.scope === "conversation" ? conversation : noticeWorkspace;
1103
+ if (to === undefined) continue;
1104
+ if (binding.restoreNotice) binding.restoreNotice(notice, to, source);
1105
+ else binding.raiseNotice?.(notice, to);
1106
+ if (source !== undefined) restored.push(source);
1107
+ }
772
1108
  }
773
- carried = undefined;
1109
+ return restored;
774
1110
  };
775
1111
 
776
1112
  const abandon = () => {
777
1113
  if (stopped) return;
778
1114
  stopped = true;
1115
+ // What ended the Turn, read while its registration is still there (ADR-0024).
1116
+ const byPick = binding.turnEndedByPick?.(currentIid) === true;
779
1117
  for (const dispose of disposers.splice(0)) dispose();
780
1118
  // Stop consuming the stream. The remote end of the turn is the next paragraph.
781
1119
  controller.abort();
@@ -789,7 +1127,12 @@ export function agentActorWith(
789
1127
  // `agent.fault` left to raise. An orphan that survives a failed abort 404s on every tool
790
1128
  // call and settles on its own.
791
1129
  const iid = currentIid;
792
- const aborting = client.abort(input.agentName, iid).catch(() => {});
1130
+ // The cause names the Submission it is about: the last one ledgered for this Turn. The ledger
1131
+ // is keyed by the invocation's iid, and stamps the live conversation (a reroll's).
1132
+ const ledgered = binding.admissionOf?.(instanceId);
1133
+ const submissionId = ledgered?.instanceId === iid ? ledgered.submissionId : undefined;
1134
+ const cause = byPick ? { cause: "pick" as const, ...(submissionId ? { submissionId } : {}) } : {};
1135
+ const aborting = client.abort(input.agentName, iid, cause).catch(() => {});
793
1136
  pendingAborts.set(iid, aborting);
794
1137
  void aborting.then(() => {
795
1138
  if (pendingAborts.get(iid) === aborting) pendingAborts.delete(iid);
@@ -859,6 +1202,34 @@ export function agentActorWith(
859
1202
  if (sub === undefined) finish();
860
1203
  });
861
1204
 
1205
+ // The Harness keeps no such conversation (ADR-0062): it lost it while the Turn waited, and a
1206
+ // `kept` admission found none. Nothing of the Turn is left there, so the Turn is admitted as a
1207
+ // Turn on a new conversation: every notice in scope, `include` whatever the scope leaves out,
1208
+ // and `prompt`, which holds the Turn's task. A continued Turn is told its conversation is new as the
1209
+ // continue path tells it — the notice is kept for its conversation, which this admission takes.
1210
+ // A fresh Turn's conversation is its own: the notice rides this admission alone, so no later
1211
+ // continued Turn of another conversation is told its context is gone. `before` and `after`
1212
+ // ride beside it, in that order.
1213
+ const admitOntoLost = async (
1214
+ prompt: string,
1215
+ before: Notice[],
1216
+ after: Notice[],
1217
+ include: readonly string[],
1218
+ ): Promise<AgentAdmission> => {
1219
+ const extra = [...before];
1220
+ const fresh: Notice = {
1221
+ kind: "conversation-new",
1222
+ scope: "conversation",
1223
+ reason: "the Harness no longer holds it",
1224
+ };
1225
+ if (input.continuation) binding.raiseNotice?.(fresh, conversation);
1226
+ else extra.push(fresh);
1227
+ return await admitTurn(
1228
+ { ...framed, attach: undefined, instanceId: currentIid, prompt },
1229
+ { notices: "turn", extra: [...extra, ...after], include, task: true },
1230
+ );
1231
+ };
1232
+
862
1233
  // A Harness restart cut this Turn (ADR-0031). It is NOT an `agent.fault` when the Agent's state is
863
1234
  // busy — something it started runs below it — or its pick's answer has not reached the model:
864
1235
  // faulting would route the Machine out of the state and stop the work the Turn did not want
@@ -888,6 +1259,79 @@ export function agentActorWith(
888
1259
  },
889
1260
  ]
890
1261
  : [];
1262
+ // Kept until the re-admission is sure: a Turn that faults below still names the restart.
1263
+ const cutBy = restarted;
1264
+ // The restart that cut the Turn is the new mark at once: the watch may tell it again before
1265
+ // the re-admission is answered, and it must not cut the re-admission too.
1266
+ if (restarted !== undefined) restartMark = restarted.restartCount;
1267
+ restarted = undefined;
1268
+ controller = new AbortController();
1269
+ // The rebuilt Harness may not read the conversation back: then this Turn lands on a new one,
1270
+ // and is told (ADR-0062). Asked again while the new container may not listen yet, and taken
1271
+ // as "held" only when the time is over. That "held" can be wrong — a Harness in
1272
+ // CrashLoopBackOff answers nothing for longer — so the admission says it is for a kept
1273
+ // conversation, and a Harness that keeps none refuses it: then the Turn takes the path of a
1274
+ // lost conversation below.
1275
+ const kept = await client.holds(input.agentName, currentIid, {
1276
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(holdsReadMs)]),
1277
+ });
1278
+ if (stopped) return "stopped";
1279
+ // The task: a restore dropped `prompt`, and the ledgered admission kept it. A new
1280
+ // conversation with no task to send cannot be asked to "continue your task": the Turn faults.
1281
+ const task = input.prompt ?? input.attach?.task;
1282
+ // Whether the cut admission, never read, carried the task: then the model never read it.
1283
+ const taskUnheard = !served.has(currentIid) && carriedBy.get(currentIid)?.task === true;
1284
+ // A fresh Turn's "this conversation is new" that an unread re-admission carried rides this
1285
+ // one again: it is kept nowhere else. Read before the first admission below, which can fail.
1286
+ const unreadFresh: Notice[] = served.has(currentIid) ? [] : [...(carriedBy.get(currentIid)?.fresh ?? [])];
1287
+ // On a new conversation nothing of the Turn is left: the cut pick's answer rides after the task,
1288
+ // and after the unread "this conversation is new" that the cut admission carried.
1289
+ const readmitLost = (withTask: string, include: readonly string[]): Promise<AgentAdmission> =>
1290
+ admitOntoLost(`${READMIT_PROMPT}\n\n${withTask}`, unreadFresh, notice, include);
1291
+ // On a conversation the Harness still holds, it continues the same Turn. The task follows the
1292
+ // re-admit text when a cut admission carried it and was never read.
1293
+ const readmitKept = (include: readonly string[]): Promise<AgentAdmission> => {
1294
+ const withTask = taskUnheard && task !== undefined;
1295
+ return admitTurn(
1296
+ {
1297
+ ...framed,
1298
+ attach: undefined,
1299
+ instanceId: currentIid,
1300
+ prompt: withTask ? `${READMIT_PROMPT}\n\n${task}` : READMIT_PROMPT,
1301
+ },
1302
+ { notices: "same-turn", extra: [...unreadFresh, ...notice], include, task: withTask, kept: true },
1303
+ );
1304
+ };
1305
+ // What the cut admissions carried is pending again before this one takes, if the Harness never
1306
+ // read their surface — or all of it, if the conversation that heard it is gone. Those sources
1307
+ // ride this re-admission whatever its scope leaves out.
1308
+ let admission: AgentAdmission;
1309
+ if (!kept) {
1310
+ // The fault names the restart that cut the Turn, and asks whether it was a memory kill.
1311
+ if (task === undefined) {
1312
+ restarted = cutBy;
1313
+ return undefined;
1314
+ }
1315
+ admission = await readmitLost(task, reraiseUnheard(true));
1316
+ } else {
1317
+ const restored = reraiseUnheard(false);
1318
+ try {
1319
+ admission = await readmitKept(restored);
1320
+ } catch (err) {
1321
+ if (!isNotKept(err)) throw err;
1322
+ // "Held" was the timeout's: the Harness keeps no such conversation, and it seated none. The
1323
+ // failed admission put back what it took. Now the path of a lost conversation: what the
1324
+ // conversation heard is unheard, and the Turn is admitted with its task — or faults when
1325
+ // there is no task to send.
1326
+ if (stopped) return "stopped";
1327
+ if (task === undefined) {
1328
+ restarted = cutBy;
1329
+ return undefined;
1330
+ }
1331
+ admission = await readmitLost(task, [...restored, ...reraiseUnheard(true)]);
1332
+ }
1333
+ }
1334
+ // Said once an admission worked: a refused one that then faults re-admitted nothing.
891
1335
  cuts++;
892
1336
  binding.telemetry?.({
893
1337
  kind: "retry",
@@ -895,22 +1339,6 @@ export function agentActorWith(
895
1339
  attempt: cuts,
896
1340
  reason: "re-admitted after a Harness restart",
897
1341
  });
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
1342
  binding.heldAnswerDelivered?.(currentIid);
915
1343
  return admission;
916
1344
  };
@@ -961,17 +1389,30 @@ export function agentActorWith(
961
1389
  // A continued conversation admitted before may be gone with no fault to say so: its
962
1390
  // Harness restarted between the Turns. Then this Turn lands on a new one, and is told
963
1391
  // (ADR-0062). A conversation never admitted has nothing to lose, so nothing is asked.
964
- if (
965
- input.continuation &&
966
- binding.admissionOf?.(instanceId) !== undefined &&
967
- !(await client.holds(input.agentName, instanceId, { signal: controller.signal }))
968
- ) {
1392
+ // "Held" can be a timeout's, so the admission says it is for a kept conversation, and a
1393
+ // Harness that keeps none refuses it: then the Turn is told, and admitted again.
1394
+ const told = () =>
969
1395
  binding.raiseNotice?.(
970
1396
  { kind: "conversation-new", scope: "conversation", reason: "the Harness no longer holds it" },
971
1397
  conversation,
972
1398
  );
973
- }
974
- admission = await admitTurn(framed);
1399
+ const asked = input.continuation && binding.admissionOf?.(instanceId) !== undefined;
1400
+ const kept =
1401
+ asked &&
1402
+ (await client.holds(input.agentName, instanceId, {
1403
+ signal: AbortSignal.any([controller.signal, AbortSignal.timeout(holdsReadMs)]),
1404
+ }));
1405
+ if (asked && !kept) told();
1406
+ if (kept) {
1407
+ try {
1408
+ admission = await admitTurn(framed, { notices: "turn", task: true, kept: true });
1409
+ } catch (err) {
1410
+ if (!isNotKept(err)) throw err;
1411
+ if (stopped) return;
1412
+ told();
1413
+ admission = await admitTurn(framed);
1414
+ }
1415
+ } else admission = await admitTurn(framed);
975
1416
  // The admission marker (ADR-0023): the Turn and its framing, once — a re-attach
976
1417
  // continues a Turn already announced, and a nudge (below) is mechanism, not narrative
977
1418
  // (its telemetry already rides the feed).
@@ -1045,15 +1486,36 @@ export function agentActorWith(
1045
1486
  attempt: nudges,
1046
1487
  reason: woken ? "picks allowed again" : "no-signal nudge",
1047
1488
  });
1048
- admission = await admitTurn(
1049
- {
1050
- ...framed,
1051
- attach: undefined,
1052
- instanceId: currentIid,
1053
- prompt: NUDGE_PROMPT,
1054
- },
1055
- { notices: false },
1056
- );
1489
+ // The nudge and the wake ask the same conversation, so they say `kept`: the Harness can
1490
+ // lose it while the Turn waits (the Instance Harness's restart is on no watch), and a
1491
+ // Harness that keeps none then refuses, never seating an empty conversation whose model
1492
+ // reads the nudge with no task. On the refusal the Turn takes the lost-conversation path,
1493
+ // as the re-admission after a cut does: admitted as a Turn with its task, or a fault when
1494
+ // there is no task to send. Its prompt is the task alone, as a continued Turn's is after
1495
+ // the same refusal: nothing was cut, so the re-admit text would not be true. The ask was
1496
+ // spent and said; the re-admission is said too.
1497
+ try {
1498
+ admission = await admitTurn(
1499
+ {
1500
+ ...framed,
1501
+ attach: undefined,
1502
+ instanceId: currentIid,
1503
+ prompt: NUDGE_PROMPT,
1504
+ },
1505
+ { notices: "nudge", task: false, kept: true },
1506
+ );
1507
+ } catch (err) {
1508
+ const task = input.prompt ?? input.attach?.task;
1509
+ if (!isNotKept(err) || task === undefined) throw err;
1510
+ if (stopped) return;
1511
+ admission = await admitOntoLost(task, [], [], reraiseUnheard(true));
1512
+ binding.telemetry?.({
1513
+ kind: "retry",
1514
+ child: self._parent?.id ?? self.id,
1515
+ attempt: nudges,
1516
+ reason: "re-admitted on a new conversation",
1517
+ });
1518
+ }
1057
1519
  }
1058
1520
  } catch (err) {
1059
1521
  if (stopped) return;
@@ -1087,9 +1549,9 @@ export function agentActorWith(
1087
1549
  if (stopped) return;
1088
1550
  if (kill !== undefined && noticeWorkspace !== undefined) {
1089
1551
  binding.raiseNotice?.(
1090
- memoryLimit(kill.limit ?? "unknown"),
1552
+ memoryLimit({ limit: kill.limit, agent: input.agentName }),
1091
1553
  noticeWorkspace,
1092
- kill.at === undefined ? undefined : `${noticeWorkspace}@${kill.at}`,
1554
+ kill.at === undefined ? undefined : kernelSource(noticeWorkspace, kill.at),
1093
1555
  );
1094
1556
  }
1095
1557
  // A restart the Harness came back from keeps the conversation (ADR-0031); a 404 does not.