@nylorun/runtime 0.26.0-beta → 0.28.0-beta

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 (379) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +18 -14
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.d.ts +4 -3
  12. package/dist/api/a2a/card.js +4 -4
  13. package/dist/api/a2a/endpoint.js +3 -4
  14. package/dist/api/a2a/protocol.js +4 -0
  15. package/dist/api/a2a/routes.d.ts +2 -6
  16. package/dist/api/a2a/routes.js +30 -25
  17. package/dist/api/ag-ui/endpoint.js +2 -3
  18. package/dist/api/ag-ui/routes.js +8 -11
  19. package/dist/api/components.d.ts +6789 -3405
  20. package/dist/api/components.js +43 -30
  21. package/dist/api/gate/keys.js +7 -4
  22. package/dist/api/gate/routes.d.ts +6 -2
  23. package/dist/api/gate/routes.js +15 -11
  24. package/dist/api/http/app.js +9 -4
  25. package/dist/api/http/body.d.ts +17 -3
  26. package/dist/api/http/body.js +35 -7
  27. package/dist/api/http/respond.js +6 -2
  28. package/dist/api/http/routes/access.js +5 -4
  29. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  30. package/dist/api/http/routes/agent-manifests.js +135 -0
  31. package/dist/api/http/routes/agents.d.ts +11 -0
  32. package/dist/api/http/routes/agents.js +418 -0
  33. package/dist/api/http/routes/artifacts.js +21 -9
  34. package/dist/api/http/routes/coverage.d.ts +4 -4
  35. package/dist/api/http/routes/coverage.js +13 -7
  36. package/dist/api/http/routes/files.d.ts +2 -1
  37. package/dist/api/http/routes/files.js +31 -5
  38. package/dist/api/http/routes/mcp.js +3 -3
  39. package/dist/api/http/routes/me.d.ts +1 -1
  40. package/dist/api/http/routes/me.js +1 -2
  41. package/dist/api/http/routes/memories.d.ts +9 -0
  42. package/dist/api/http/routes/memories.js +152 -0
  43. package/dist/api/http/routes/reads.d.ts +10 -111
  44. package/dist/api/http/routes/reads.js +51 -29
  45. package/dist/api/http/routes/sessions.js +26 -54
  46. package/dist/api/http/routes/tenant.d.ts +2 -2
  47. package/dist/api/http/routes/tenant.js +77 -24
  48. package/dist/api/http/routes/triggers.d.ts +10 -0
  49. package/dist/api/http/routes/triggers.js +180 -0
  50. package/dist/api/inbound-files.d.ts +3 -9
  51. package/dist/api/inbound-files.js +18 -24
  52. package/dist/api/openapi.js +73 -47
  53. package/dist/artifacts/disk.d.ts +10 -14
  54. package/dist/artifacts/disk.js +99 -95
  55. package/dist/artifacts/files.d.ts +13 -4
  56. package/dist/artifacts/files.js +27 -9
  57. package/dist/artifacts/folder-upload.d.ts +75 -0
  58. package/dist/artifacts/folder-upload.js +263 -0
  59. package/dist/artifacts/folders.d.ts +14 -8
  60. package/dist/artifacts/folders.js +19 -11
  61. package/dist/artifacts/links.js +7 -6
  62. package/dist/artifacts/parts.d.ts +9 -14
  63. package/dist/artifacts/parts.js +11 -9
  64. package/dist/artifacts/pipe.d.ts +15 -0
  65. package/dist/artifacts/pipe.js +71 -0
  66. package/dist/artifacts/quota.d.ts +36 -0
  67. package/dist/artifacts/quota.js +44 -0
  68. package/dist/artifacts/service.d.ts +25 -1
  69. package/dist/artifacts/service.js +72 -27
  70. package/dist/blob/fs.js +2 -1
  71. package/dist/blob/s3.d.ts +6 -2
  72. package/dist/blob/s3.js +9 -7
  73. package/dist/blob/types.d.ts +1 -1
  74. package/dist/blob/types.js +1 -1
  75. package/dist/core/flow-host.d.ts +0 -1
  76. package/dist/core/limits.d.ts +11 -35
  77. package/dist/core/limits.js +5 -73
  78. package/dist/core/sandbox-routes.js +0 -1
  79. package/dist/core/turn-manifest.d.ts +1 -2
  80. package/dist/core/turn-manifest.js +4 -28
  81. package/dist/execution/memory.d.ts +8 -5
  82. package/dist/execution/memory.js +8 -6
  83. package/dist/execution/types.d.ts +1 -6
  84. package/dist/execution/types.js +0 -5
  85. package/dist/gates/contract.d.ts +3 -2
  86. package/dist/gates/contract.js +1 -1
  87. package/dist/gates/egress-credentials.js +5 -4
  88. package/dist/gates/egress.d.ts +13 -13
  89. package/dist/gates/egress.js +19 -21
  90. package/dist/gates/fixture-model.js +13 -2
  91. package/dist/gates/handler.d.ts +1 -3
  92. package/dist/gates/handler.js +4 -3
  93. package/dist/gates/http-client.d.ts +4 -3
  94. package/dist/gates/http-client.js +5 -7
  95. package/dist/gates/http-tool.d.ts +24 -4
  96. package/dist/gates/http-tool.js +41 -13
  97. package/dist/gates/in-process.d.ts +2 -1
  98. package/dist/gates/in-process.js +17 -9
  99. package/dist/gates/inflight.d.ts +2 -2
  100. package/dist/gates/inflight.js +3 -2
  101. package/dist/gates/journal.js +5 -7
  102. package/dist/gates/mcp-handler.d.ts +4 -3
  103. package/dist/gates/mcp-handler.js +15 -5
  104. package/dist/gates/meter.js +6 -5
  105. package/dist/gates/model-gate.d.ts +11 -2
  106. package/dist/gates/policy.d.ts +34 -6
  107. package/dist/gates/policy.js +66 -18
  108. package/dist/gates/provider-gate.d.ts +22 -0
  109. package/dist/gates/provider-gate.js +30 -0
  110. package/dist/gates/tool-calls.d.ts +1 -3
  111. package/dist/gates/tool-calls.js +2 -3
  112. package/dist/gates/tool-client.d.ts +1 -1
  113. package/dist/gates/tool-client.js +5 -6
  114. package/dist/gates/tool-contract.d.ts +8 -5
  115. package/dist/gates/tool-contract.js +1 -1
  116. package/dist/gates/tool-gate.d.ts +18 -7
  117. package/dist/gates/tool-gate.js +49 -7
  118. package/dist/harness/calls.d.ts +27 -25
  119. package/dist/harness/calls.js +40 -46
  120. package/dist/harness/executors.d.ts +10 -3
  121. package/dist/harness/executors.js +53 -23
  122. package/dist/harness/main.js +2 -2
  123. package/dist/harness/pod.d.ts +2 -2
  124. package/dist/harness/pod.js +7 -6
  125. package/dist/harness/run-tokens.d.ts +4 -1
  126. package/dist/harness/run-tokens.js +6 -2
  127. package/dist/harness/service.d.ts +17 -14
  128. package/dist/harness/service.js +21 -35
  129. package/dist/harness/setup.d.ts +0 -2
  130. package/dist/harness/setup.js +21 -15
  131. package/dist/harness/ws-client.d.ts +2 -2
  132. package/dist/harness/ws-client.js +7 -5
  133. package/dist/harness/ws-port.d.ts +3 -4
  134. package/dist/harness/ws-port.js +3 -4
  135. package/dist/harness-api/artifacts.d.ts +20 -0
  136. package/dist/harness-api/artifacts.js +478 -0
  137. package/dist/harness-api/in-process.d.ts +2 -2
  138. package/dist/harness-api/in-process.js +18 -16
  139. package/dist/harness-api/record.d.ts +10 -9
  140. package/dist/harness-api/record.js +113 -157
  141. package/dist/harness-api/renew.d.ts +1 -1
  142. package/dist/harness-api/renew.js +2 -2
  143. package/dist/harness-api/server.d.ts +1 -16
  144. package/dist/harness-api/server.js +86 -73
  145. package/dist/harness-api/start.d.ts +27 -5
  146. package/dist/harness-api/start.js +102 -19
  147. package/dist/harness-api/tool-journal.d.ts +15 -10
  148. package/dist/harness-api/tool-journal.js +26 -24
  149. package/dist/harness-api/workspace.d.ts +0 -4
  150. package/dist/harness-api/workspace.js +4 -47
  151. package/dist/harness-api/ws-server.js +10 -10
  152. package/dist/host/create-host.js +2 -3
  153. package/dist/host/execution.js +2 -3
  154. package/dist/host/gates.d.ts +2 -9
  155. package/dist/host/gates.js +15 -12
  156. package/dist/host/http.d.ts +2 -4
  157. package/dist/host/http.js +5 -29
  158. package/dist/host/main.js +10 -3
  159. package/dist/host/operate.js +12 -3
  160. package/dist/host/stack-config.d.ts +11 -0
  161. package/dist/host/stack-config.js +21 -1
  162. package/dist/index.d.ts +0 -1
  163. package/dist/index.js +0 -1
  164. package/dist/infra/index.js +2 -1
  165. package/dist/infra/readiness.d.ts +0 -2
  166. package/dist/infra/readiness.js +4 -3
  167. package/dist/keys/client.d.ts +1 -0
  168. package/dist/keys/client.js +2 -3
  169. package/dist/keys/contract.d.ts +0 -2
  170. package/dist/keys/contract.js +0 -2
  171. package/dist/keys/egress-ca.d.ts +0 -2
  172. package/dist/keys/egress-ca.js +2 -3
  173. package/dist/keys/keys.d.ts +15 -4
  174. package/dist/keys/keys.js +22 -1
  175. package/dist/keys/webhooks.d.ts +25 -0
  176. package/dist/keys/webhooks.js +62 -0
  177. package/dist/keys/x509.d.ts +8 -4
  178. package/dist/keys/x509.js +14 -5
  179. package/dist/management-openapi.json +3043 -1954
  180. package/dist/mcp/connect.d.ts +35 -26
  181. package/dist/mcp/connect.js +112 -141
  182. package/dist/mcp/pool.d.ts +27 -22
  183. package/dist/mcp/pool.js +34 -48
  184. package/dist/mcp/preview.d.ts +12 -5
  185. package/dist/mcp/preview.js +43 -26
  186. package/dist/mcp/snapshot.d.ts +35 -86
  187. package/dist/mcp/snapshot.js +93 -135
  188. package/dist/memory/service.d.ts +97 -0
  189. package/dist/memory/service.js +335 -0
  190. package/dist/memory/tool.d.ts +36 -0
  191. package/dist/memory/tool.js +84 -0
  192. package/dist/model/auth-store.js +5 -3
  193. package/dist/model/capabilities.d.ts +1 -1
  194. package/dist/model/catalog.d.ts +9 -36
  195. package/dist/model/catalog.js +6 -33
  196. package/dist/model/descriptor.d.ts +51 -0
  197. package/dist/model/descriptor.js +83 -0
  198. package/dist/model/models.d.ts +0 -3
  199. package/dist/model/models.js +3 -5
  200. package/dist/model/native-blocks.js +1 -1
  201. package/dist/model/pi-model.d.ts +16 -5
  202. package/dist/model/pi-model.js +86 -105
  203. package/dist/openapi.json +13653 -9986
  204. package/dist/reads/cursor.js +3 -1
  205. package/dist/reads/types.d.ts +3 -3
  206. package/dist/record/agent.d.ts +27 -0
  207. package/dist/record/agent.js +46 -0
  208. package/dist/record/append.d.ts +34 -2
  209. package/dist/record/append.js +50 -1
  210. package/dist/record/index.d.ts +2 -2
  211. package/dist/record/index.js +2 -2
  212. package/dist/redact.d.ts +1 -1
  213. package/dist/redact.js +3 -2
  214. package/dist/sandbox/egress-token.d.ts +3 -3
  215. package/dist/sandbox/egress-token.js +3 -3
  216. package/dist/sandbox/join.js +8 -7
  217. package/dist/sandbox/manager.d.ts +41 -41
  218. package/dist/sandbox/manager.js +112 -44
  219. package/dist/sandbox/placement.d.ts +2 -2
  220. package/dist/sandbox/placement.js +7 -10
  221. package/dist/sandbox/pods/client.d.ts +1 -10
  222. package/dist/sandbox/pods/client.js +8 -4
  223. package/dist/sandbox/pods/lifecycle.d.ts +25 -17
  224. package/dist/sandbox/pods/lifecycle.js +51 -29
  225. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  226. package/dist/sandbox/pods/network-gate.js +7 -7
  227. package/dist/sandbox/pods/reconcile.js +14 -3
  228. package/dist/sandbox/pods/spec.js +8 -5
  229. package/dist/sandbox/policy.d.ts +7 -1
  230. package/dist/sandbox/policy.js +10 -3
  231. package/dist/sandbox/resolve.d.ts +11 -6
  232. package/dist/sandbox/resolve.js +19 -9
  233. package/dist/sandbox/setup.d.ts +6 -2
  234. package/dist/sandbox/setup.js +47 -14
  235. package/dist/sandbox/share.d.ts +5 -14
  236. package/dist/sandbox/share.js +5 -34
  237. package/dist/sandbox/skills.d.ts +7 -13
  238. package/dist/sandbox/skills.js +16 -31
  239. package/dist/sandbox/tenant-config.d.ts +2 -2
  240. package/dist/sandbox/tenant-config.js +4 -3
  241. package/dist/sandbox/tools.d.ts +7 -1
  242. package/dist/sandbox/tools.js +26 -26
  243. package/dist/sandbox/types.d.ts +10 -3
  244. package/dist/sandbox/types.js +9 -0
  245. package/dist/store/postgres/connect.d.ts +6 -5
  246. package/dist/store/postgres/connect.js +14 -11
  247. package/dist/store/postgres/control.js +13 -13
  248. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  249. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  250. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  251. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  252. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  253. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  254. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  255. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  256. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  257. package/dist/store/postgres/drizzle/meta/_journal.json +63 -0
  258. package/dist/store/postgres/reads.js +17 -82
  259. package/dist/store/postgres/record-writer.d.ts +4 -4
  260. package/dist/store/postgres/record-writer.js +9 -9
  261. package/dist/store/postgres/schema.d.ts +1382 -151
  262. package/dist/store/postgres/schema.js +191 -37
  263. package/dist/store/postgres/store.js +384 -105
  264. package/dist/store/postgres/tenant.js +1 -2
  265. package/dist/store/types.d.ts +172 -57
  266. package/dist/store/types.js +0 -1
  267. package/dist/streams/relay/core.d.ts +3 -3
  268. package/dist/streams/relay/core.js +9 -10
  269. package/dist/tenant/advance.js +41 -39
  270. package/dist/tenant/agent-manifests.d.ts +72 -0
  271. package/dist/tenant/agent-manifests.js +359 -0
  272. package/dist/tenant/agent-versions.d.ts +126 -0
  273. package/dist/tenant/agent-versions.js +389 -0
  274. package/dist/tenant/agents.d.ts +230 -0
  275. package/dist/tenant/{environments.js → agents.js} +493 -328
  276. package/dist/tenant/auth.d.ts +1 -1
  277. package/dist/tenant/auth.js +3 -3
  278. package/dist/tenant/commands.d.ts +63 -2
  279. package/dist/tenant/commands.js +240 -90
  280. package/dist/tenant/context.d.ts +57 -33
  281. package/dist/tenant/context.js +6 -2
  282. package/dist/tenant/definition-files.d.ts +25 -4
  283. package/dist/tenant/definition-files.js +85 -47
  284. package/dist/tenant/effects.js +15 -51
  285. package/dist/tenant/ephemeral.js +2 -1
  286. package/dist/tenant/history.d.ts +1 -1
  287. package/dist/tenant/history.js +1 -1
  288. package/dist/tenant/host-token.d.ts +3 -3
  289. package/dist/tenant/host-token.js +11 -10
  290. package/dist/tenant/http.d.ts +5 -8
  291. package/dist/tenant/http.js +9 -0
  292. package/dist/tenant/identity-file.d.ts +29 -6
  293. package/dist/tenant/identity-file.js +45 -15
  294. package/dist/tenant/issuers.d.ts +5 -5
  295. package/dist/tenant/issuers.js +34 -26
  296. package/dist/tenant/jwt.d.ts +8 -2
  297. package/dist/tenant/jwt.js +10 -2
  298. package/dist/tenant/limits.d.ts +87 -0
  299. package/dist/tenant/limits.js +172 -0
  300. package/dist/tenant/memories.d.ts +38 -0
  301. package/dist/tenant/memories.js +119 -0
  302. package/dist/tenant/module.d.ts +4 -1
  303. package/dist/tenant/module.js +9 -8
  304. package/dist/tenant/outbound.d.ts +11 -14
  305. package/dist/tenant/outbound.js +10 -15
  306. package/dist/tenant/reset.js +2 -2
  307. package/dist/tenant/run-grants.d.ts +6 -0
  308. package/dist/tenant/run-grants.js +7 -2
  309. package/dist/tenant/run-token.d.ts +0 -4
  310. package/dist/tenant/run-token.js +6 -11
  311. package/dist/tenant/runtime-limits.d.ts +24 -0
  312. package/dist/tenant/runtime-limits.js +27 -0
  313. package/dist/tenant/runtime.d.ts +1 -1
  314. package/dist/tenant/runtime.js +38 -43
  315. package/dist/tenant/session-streams.js +7 -6
  316. package/dist/tenant/session.d.ts +1 -1
  317. package/dist/tenant/session.js +1 -1
  318. package/dist/tenant/sessions.d.ts +28 -45
  319. package/dist/tenant/sessions.js +89 -209
  320. package/dist/tenant/signing-keys.d.ts +1 -1
  321. package/dist/tenant/signing-keys.js +5 -6
  322. package/dist/tenant/status.js +10 -10
  323. package/dist/tenant/streams.js +18 -20
  324. package/dist/tenant/sweep.d.ts +1 -21
  325. package/dist/tenant/sweep.js +51 -51
  326. package/dist/tenant/transcript.d.ts +70 -23
  327. package/dist/tenant/transcript.js +28 -22
  328. package/dist/tenant/triggers.d.ts +143 -0
  329. package/dist/tenant/triggers.js +715 -0
  330. package/dist/tenant/turn-timing.d.ts +11 -1
  331. package/dist/tenant/turn-timing.js +3 -1
  332. package/dist/tenant/types.d.ts +9 -14
  333. package/dist/tenant/worker.d.ts +4 -10
  334. package/dist/tenant/worker.js +10 -14
  335. package/dist/traces/ids.js +5 -1
  336. package/dist/traces/map.d.ts +10 -7
  337. package/dist/traces/map.js +16 -12
  338. package/dist/traces/otlp.d.ts +3 -3
  339. package/dist/traces/otlp.js +5 -3
  340. package/dist/traces/projector.d.ts +9 -4
  341. package/dist/traces/projector.js +13 -7
  342. package/dist/traces/read.js +6 -1
  343. package/dist/tuning.d.ts +390 -0
  344. package/dist/tuning.js +677 -0
  345. package/dist/vault/headers.js +4 -2
  346. package/dist/vault/service.d.ts +13 -8
  347. package/dist/vault/service.js +61 -17
  348. package/dist/version.d.ts +1 -1
  349. package/dist/version.js +1 -1
  350. package/package.json +13 -12
  351. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  352. package/dist/api/http/routes/environment-manifests.js +0 -118
  353. package/dist/api/http/routes/environments.d.ts +0 -11
  354. package/dist/api/http/routes/environments.js +0 -332
  355. package/dist/artifacts/export.d.ts +0 -8
  356. package/dist/artifacts/export.js +0 -204
  357. package/dist/artifacts/media-types.d.ts +0 -30
  358. package/dist/artifacts/media-types.js +0 -137
  359. package/dist/artifacts/workspace.d.ts +0 -60
  360. package/dist/artifacts/workspace.js +0 -45
  361. package/dist/media.d.ts +0 -10
  362. package/dist/media.js +0 -58
  363. package/dist/record/environment.d.ts +0 -25
  364. package/dist/record/environment.js +0 -44
  365. package/dist/sandbox/runtime-tools.d.ts +0 -9
  366. package/dist/sandbox/runtime-tools.js +0 -16
  367. package/dist/sandbox/session-sandbox.d.ts +0 -16
  368. package/dist/sandbox/session-sandbox.js +0 -77
  369. package/dist/tenant/artifact-tool.d.ts +0 -13
  370. package/dist/tenant/artifact-tool.js +0 -243
  371. package/dist/tenant/environment-manifests.d.ts +0 -57
  372. package/dist/tenant/environment-manifests.js +0 -200
  373. package/dist/tenant/environments.d.ts +0 -174
  374. package/dist/tenant/skill-tool.d.ts +0 -20
  375. package/dist/tenant/skill-tool.js +0 -67
  376. package/dist/tenant/tool-results.d.ts +0 -57
  377. package/dist/tenant/tool-results.js +0 -298
  378. package/dist/tenant/tool-search.d.ts +0 -39
  379. package/dist/tenant/tool-search.js +0 -103
@@ -1,22 +1,41 @@
1
+ import { hostTuning } from "../../tuning.js";
2
+ import { DEFAULT_SANDBOX_IMAGE } from "../policy.js";
1
3
  /**
2
- * The pullable reference of the image id a container status names (`repo@sha256:…`), or
3
- * undefined for one without a repository, such as an image loaded onto the node.
4
+ * The repository an image reference names, as a container runtime writes it: the tag and digest
5
+ * dropped, Docker Hub's host and `library/` filled in (`python:3.13-slim` is
6
+ * `docker.io/library/python`).
4
7
  */
5
- export function imageDigestOf(imageID) {
8
+ export function repositoryOf(reference) {
9
+ let name = reference.replace(/^docker-pullable:\/\//, "");
10
+ const at = name.indexOf("@");
11
+ if (at >= 0)
12
+ name = name.slice(0, at);
13
+ const colon = name.lastIndexOf(":");
14
+ if (colon > name.lastIndexOf("/"))
15
+ name = name.slice(0, colon);
16
+ const parts = name.split("/");
17
+ const first = parts[0] ?? "";
18
+ const hasHost = parts.length > 1 && (first.includes(".") || first.includes(":") || first === "localhost");
19
+ if (hasHost)
20
+ return name;
21
+ return parts.length === 1 ? `docker.io/library/${name}` : `docker.io/${name}`;
22
+ }
23
+ /**
24
+ * The pullable reference of the image id a container status names (`repo@sha256:…`), when it is
25
+ * a digest of `requested`'s repository; undefined for an id without a repository, or with one
26
+ * the node made up for an image loaded onto it, which no registry serves.
27
+ */
28
+ export function imageDigestOf(imageID, requested) {
6
29
  const id = imageID?.replace(/^docker-pullable:\/\//, "");
7
30
  if (!id)
8
31
  return undefined;
9
32
  const at = id.indexOf("@sha256:");
10
- return at > 0 && /^[0-9a-f]{64}$/.test(id.slice(at + 8)) ? id : undefined;
33
+ if (at <= 0 || !/^[0-9a-f]{64}$/.test(id.slice(at + 8)))
34
+ return undefined;
35
+ return repositoryOf(id) === repositoryOf(requested) ? id : undefined;
11
36
  }
12
37
  /** A setup failure's `reason` starts with this (`sandbox/setup.ts`); it is sticky. */
13
38
  export const SETUP_FAILED = "setup:";
14
- /** A pod not ready this long after it was asked to run has failed. */
15
- export const READY_DEADLINE_MS = 5 * 60_000;
16
- /** How soon to look again while the pod changes. */
17
- export const POLL_MS = 2_000;
18
- /** How soon to look again when the sandboxes service does not answer. */
19
- export const UNREACHABLE_RETRY_MS = 5_000;
20
39
  /** The service's operation id of the row's current spec. */
21
40
  export function opIdOf(pod) {
22
41
  return `${pod.k8sName}.r${pod.rev}`;
@@ -28,6 +47,9 @@ function gone(status) {
28
47
  const event = (type, payload) => ({ type, payload });
29
48
  export function decide(facts) {
30
49
  const { row, status, now } = facts;
50
+ // A pod not ready `readyDeadlineMs` after it was asked to run has failed; look again after
51
+ // `pollMs` while it changes, or `unreachableRetryMs` when the sandboxes service does not answer.
52
+ const { readyDeadlineMs, pollMs, unreachableRetryMs } = hostTuning().pods;
31
53
  const pod = row.pod;
32
54
  const patch = {};
33
55
  const events = [];
@@ -49,17 +71,17 @@ export function decide(facts) {
49
71
  if (pod.retiring !== undefined) {
50
72
  const retiring = facts.retiring;
51
73
  if (retiring === undefined)
52
- again(UNREACHABLE_RETRY_MS);
74
+ again(unreachableRetryMs);
53
75
  else if (gone(retiring))
54
76
  patch.retiring = null;
55
77
  else {
56
78
  if (retiring.exists && !retiring.deleting)
57
79
  deletes.push(pod.retiring);
58
- again(POLL_MS);
80
+ again(pollMs);
59
81
  }
60
82
  }
61
83
  if (status === undefined) {
62
- again(UNREACHABLE_RETRY_MS);
84
+ again(unreachableRetryMs);
63
85
  return done();
64
86
  }
65
87
  if (pod.desired === "deleted") {
@@ -69,7 +91,7 @@ export function decide(facts) {
69
91
  deletes.push(pod.k8sName);
70
92
  if (pod.observed !== "deleting")
71
93
  patch.observed = "deleting";
72
- again(POLL_MS);
94
+ again(pollMs);
73
95
  return done();
74
96
  }
75
97
  // Lost is terminal: only a reset (a new name) leaves it.
@@ -77,7 +99,7 @@ export function decide(facts) {
77
99
  if (status.exists && !status.deleting)
78
100
  deletes.push(pod.k8sName);
79
101
  if (status.exists)
80
- again(POLL_MS);
102
+ again(pollMs);
81
103
  return done();
82
104
  }
83
105
  let desired = pod.desired;
@@ -91,7 +113,7 @@ export function decide(facts) {
91
113
  patch.desired = desired;
92
114
  patch.rev = rev;
93
115
  patch.hostEpoch = pod.hostEpoch + 1;
94
- events.push(event("environment.suspended", { reason: "idle" }));
116
+ events.push(event("agent.suspended", { reason: "idle" }));
95
117
  }
96
118
  else
97
119
  arm.push({ timer: "idle", at: due });
@@ -99,23 +121,23 @@ export function decide(facts) {
99
121
  // An incarnation a pod has joined, whose Sandbox or volume is gone: lost.
100
122
  if (pod.podUid !== undefined && (!status.exists || status.deleting || status.volume === "missing")) {
101
123
  const reason = !status.exists
102
- ? "The Environment's pod and volume were deleted outside the Runtime"
124
+ ? "The Agent's pod and volume were deleted outside the Runtime"
103
125
  : status.deleting
104
- ? "The Environment's pod is being deleted outside the Runtime"
105
- : "The Environment's volume is gone";
126
+ ? "The Agent's pod is being deleted outside the Runtime"
127
+ : "The Agent's volume is gone";
106
128
  patch.observed = "lost";
107
129
  patch.reason = reason;
108
130
  patch.hostEpoch = (patch.hostEpoch ?? pod.hostEpoch) + 1;
109
- events.push(event("environment.lost", { reason }));
131
+ events.push(event("agent.lost", { reason }));
110
132
  if (status.exists && !status.deleting)
111
133
  deletes.push(pod.k8sName);
112
134
  if (status.exists)
113
- again(POLL_MS);
135
+ again(pollMs);
114
136
  return done();
115
137
  }
116
138
  if (status.deleting) {
117
139
  // The previous incarnation of this name is still going (an Environment deleted and created again).
118
- again(POLL_MS);
140
+ again(pollMs);
119
141
  return done();
120
142
  }
121
143
  const opId = opIdOf({ k8sName: pod.k8sName, rev });
@@ -133,7 +155,7 @@ export function decide(facts) {
133
155
  // A pod started by this apply (a create, a resume) is the Environment's first, not a relaunch.
134
156
  if (desired === "running" && pod.podUid !== undefined)
135
157
  patch.podUid = null;
136
- again(POLL_MS);
158
+ again(pollMs);
137
159
  return done({
138
160
  apply: { mode: desired === "running" ? "Running" : "Suspended", rotateJoin: !status.exists },
139
161
  });
@@ -149,7 +171,7 @@ export function decide(facts) {
149
171
  patch.reason = null;
150
172
  }
151
173
  // The image it runs, pinned by digest from now on (Q14).
152
- const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID) : undefined;
174
+ const digest = pod.imageDigest === undefined ? imageDigestOf(status.imageID, facts.image) : undefined;
153
175
  if (digest !== undefined)
154
176
  patch.imageDigest = digest;
155
177
  return done();
@@ -157,17 +179,17 @@ export function decide(facts) {
157
179
  const startedAt = Date.parse(pod.startedAt ?? row.updatedAt);
158
180
  if (pod.observed === "failed")
159
181
  return done();
160
- if (now - startedAt >= READY_DEADLINE_MS) {
182
+ if (now - startedAt >= readyDeadlineMs) {
161
183
  const reason = [status.reason, status.message].filter(Boolean).join(": ") ||
162
- `The pod was not ready within ${READY_DEADLINE_MS / 60_000} minutes`;
184
+ `The pod was not ready within ${Math.round(readyDeadlineMs / 1000)} s`;
163
185
  patch.observed = "failed";
164
186
  patch.reason = reason;
165
- events.push(event("environment.failed", { reason }));
187
+ events.push(event("agent.failed", { reason }));
166
188
  return done();
167
189
  }
168
190
  if (pod.observed !== "creating")
169
191
  patch.observed = "creating";
170
- again(POLL_MS);
192
+ again(pollMs);
171
193
  return done();
172
194
  }
173
195
  // Suspended, applied: suspended once its pod is gone.
@@ -176,6 +198,6 @@ export function decide(facts) {
176
198
  patch.observed = "suspended";
177
199
  return done();
178
200
  }
179
- again(POLL_MS);
201
+ again(pollMs);
180
202
  return done();
181
203
  }
@@ -2,20 +2,19 @@ export interface BlockedAddress {
2
2
  readonly host: string;
3
3
  readonly port: number;
4
4
  }
5
- /** Consecutive failed connections that prove an address blocked. */
6
- export declare const CONFIRMATIONS = 3;
7
5
  /** The addresses a pod's NetworkPolicy must block: the API server's service address. */
8
6
  export declare function blockedAddresses(env: Readonly<Record<string, string | undefined>>): BlockedAddress[];
9
7
  /** Whether a TCP connection to `address` opens within `timeoutMs`. */
10
8
  export declare function opens(address: BlockedAddress, timeoutMs: number): Promise<boolean>;
11
9
  export interface NetworkGateOptions {
12
10
  readonly addresses: readonly BlockedAddress[];
13
- /** Give up after this long. Default 120 s. */
11
+ /** Give up after this long. Default `harness.networkPolicyTimeoutMs` (Host tuning, 120 s). */
14
12
  readonly timeoutMs?: number;
15
- /** One connection attempt. Default 1 s. */
13
+ /** One connection attempt. Default `harness.networkProbeMs` (1 s). */
16
14
  readonly attemptMs?: number;
17
- /** Between attempts after a connection opened. Default 250 ms. */
15
+ /** Between attempts after a connection opened. Default `harness.networkPauseMs` (250 ms). */
18
16
  readonly pauseMs?: number;
17
+ /** Consecutive failed connections that prove an address blocked. Default `harness.networkConfirmations` (3). */
19
18
  readonly confirmations?: number;
20
19
  readonly log?: (message: string, fields: Record<string, unknown>) => void;
21
20
  }
@@ -4,12 +4,11 @@
4
4
  * (`10.96.0.1:443`) for 0.5 to 5 s after it started (8a). So before the engine joins, or runs
5
5
  * anything, it connects to addresses the policy must block (the API server's service address,
6
6
  * `KUBERNETES_SERVICE_HOST:KUBERNETES_SERVICE_PORT`, which the kubelet always sets) until each
7
- * fails `CONFIRMATIONS` times in a row (refused, unreachable or timed out). A connection that
7
+ * fails `harness.networkConfirmations` times in a row (Host tuning) (refused, unreachable or timed out). A connection that
8
8
  * succeeds starts the count again. Past `timeoutMs` the engine exits, and the pod restarts it.
9
9
  */
10
10
  import { connect } from "node:net";
11
- /** Consecutive failed connections that prove an address blocked. */
12
- export const CONFIRMATIONS = 3;
11
+ import { hostTuning } from "../../tuning.js";
13
12
  /** The addresses a pod's NetworkPolicy must block: the API server's service address. */
14
13
  export function blockedAddresses(env) {
15
14
  const override = env.NYLORUN_SANDBOX_BLOCKED_PROBE;
@@ -45,19 +44,20 @@ export function opens(address, timeoutMs) {
45
44
  }
46
45
  /** Resolves once every address is blocked; rejects past the timeout. */
47
46
  export async function awaitNetworkPolicy(options) {
47
+ const tuning = hostTuning().harness;
48
48
  const started = Date.now();
49
- const deadline = started + (options.timeoutMs ?? 120_000);
50
- const needed = options.confirmations ?? CONFIRMATIONS;
49
+ const deadline = started + (options.timeoutMs ?? tuning.networkPolicyTimeoutMs);
50
+ const needed = options.confirmations ?? tuning.networkConfirmations;
51
51
  for (const address of options.addresses) {
52
52
  let failures = 0;
53
53
  let opened = 0;
54
54
  while (failures < needed) {
55
55
  if (Date.now() > deadline)
56
56
  throw new Error(`The pod's NetworkPolicy is not in force: ${address.host}:${address.port} still answered ${opened} time(s)`);
57
- if (await opens(address, options.attemptMs ?? 1_000)) {
57
+ if (await opens(address, options.attemptMs ?? tuning.networkProbeMs)) {
58
58
  failures = 0;
59
59
  opened += 1;
60
- await new Promise((resolve) => setTimeout(resolve, options.pauseMs ?? 250));
60
+ await new Promise((resolve) => setTimeout(resolve, options.pauseMs ?? tuning.networkPauseMs));
61
61
  }
62
62
  else
63
63
  failures += 1;
@@ -15,12 +15,15 @@
15
15
  * infrastructure errors throw; Restate retries those.
16
16
  */
17
17
  import { randomBytes } from "node:crypto";
18
+ import { DEFAULT_SANDBOX_IMAGE } from "../policy.js";
18
19
  import { effectiveSandboxConfig, readSandboxConfig } from "../tenant-config.js";
19
20
  import { podSandboxSpecOf } from "../resolve.js";
20
21
  import { sha256Hex } from "../join.js";
21
22
  import { recoverLostVolume } from "../../artifacts/disk.js";
22
23
  import { decide } from "./lifecycle.js";
23
24
  import { podLifecycleConfig, podSpecOf } from "./spec.js";
25
+ import { hostTuning } from "../../tuning.js";
26
+ import { idleMsOf, revertStart } from "../../tenant/agent-versions.js";
24
27
  export async function reconcileSandbox(ctx, sandboxId, trigger) {
25
28
  const pods = ctx.pods;
26
29
  if (!pods || ctx.closed)
@@ -49,9 +52,11 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
49
52
  return undefined;
50
53
  }
51
54
  };
52
- const lifecycle = podLifecycleConfig(config, row.recipe);
55
+ // While a start would apply a new version (track T3), it idles at most `tenant.upgradeIdleMs`.
56
+ const lifecycle = { ...podLifecycleConfig(config, row.recipe), idleMs: idleMsOf(config, row) };
53
57
  const decision = decide({
54
58
  row,
59
+ image: row.applied.image ?? DEFAULT_SANDBOX_IMAGE,
55
60
  config: lifecycle,
56
61
  status: await status(row.pod.k8sName),
57
62
  ...(row.pod.retiring ? { retiring: await status(row.pod.retiring) } : {}),
@@ -116,7 +121,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
116
121
  message: error instanceof Error ? error.message : String(error),
117
122
  });
118
123
  // Nothing of the decision is written: the next reconcile decides again.
119
- return { retryAfterMs: decision.retryAfterMs ?? 5_000 };
124
+ return { retryAfterMs: decision.retryAfterMs ?? hostTuning().pods.reconcileRetryMs };
120
125
  }
121
126
  }
122
127
  const written = await ctx.store.tx(async (t) => {
@@ -137,9 +142,15 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
137
142
  if (Object.keys(finalPatch).length > 0)
138
143
  await t.updateEnvironmentPod(sandboxId, finalPatch, new Date().toISOString());
139
144
  for (const item of decision.events)
140
- await t.environmentEvent(sandboxId, item.type, item.payload);
145
+ await t.agentEvent(sandboxId, item.type, item.payload);
141
146
  if (epochMoved)
142
147
  await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
148
+ // A pod that failed to start on a new version goes back to the version it ran (track T3).
149
+ if (finalPatch.observed === "failed" && current.startCheck) {
150
+ const failed = (await t.environment(sandboxId, { lock: true }));
151
+ await revertStart(t, failed, finalPatch.reason ?? "The pod failed", new Date().toISOString());
152
+ return { kind: "changed" };
153
+ }
143
154
  return { kind: "written" };
144
155
  });
145
156
  if (written.kind === "changed")
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import { parseSandboxDuration, parseSandboxSize } from "@nylorun/core/define";
7
7
  import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
8
- import { memoryMiBOf } from "../policy.js";
8
+ import { idleMsOf, memoryMiBOf } from "../policy.js";
9
+ import { podTuningEnv } from "../../tuning.js";
9
10
  import { opIdOf } from "./lifecycle.js";
10
11
  /** The pod's grace period to stop when the Tenant sets none (D34). */
11
12
  export const DEFAULT_STOP_GRACE_SECONDS = 10;
@@ -19,13 +20,13 @@ export const POD_HARNESS_ROOT = "/harness";
19
20
  */
20
21
  export function podLifecycleConfig(config, recipe) {
21
22
  const stopGrace = config.lifecycle?.stopGrace;
22
- const tenantIdle = parseSandboxDuration(config.limits?.idle ?? "15m") ?? 15 * 60_000;
23
+ const tenantIdle = idleMsOf({ ...(config.limits?.idle === undefined ? {} : { idle: config.limits.idle }) });
23
24
  const own = recipe?.lifecycle.idle === undefined ? undefined : parseSandboxDuration(recipe.lifecycle.idle);
25
+ const graceMs = stopGrace === undefined ? undefined : parseSandboxDuration(stopGrace);
24
26
  return {
25
27
  idleMs: own === undefined ? tenantIdle : Math.min(own, tenantIdle),
26
- stopGraceSeconds: stopGrace === undefined
27
- ? DEFAULT_STOP_GRACE_SECONDS
28
- : Math.max(1, Math.min(30, Math.round((parseSandboxDuration(stopGrace) ?? 10_000) / 1000))),
28
+ // Kubernetes counts the grace in whole seconds, at least one.
29
+ stopGraceSeconds: graceMs === undefined ? DEFAULT_STOP_GRACE_SECONDS : Math.max(1, Math.round(graceMs / 1000)),
29
30
  };
30
31
  }
31
32
  /** The volume's size in GiB. */
@@ -53,6 +54,8 @@ export function podSpecOf(input) {
53
54
  env: {
54
55
  NYLORUN_SANDBOX_ID: input.environmentId,
55
56
  NYLORUN_HARNESS_ROOT: POD_HARNESS_ROOT,
57
+ // The `harness` and `engine` tuning the operator set: the pod's engine runs with them.
58
+ ...podTuningEnv(),
56
59
  },
57
60
  ...(input.joinToken ? { joinToken: input.joinToken } : {}),
58
61
  };
@@ -4,10 +4,16 @@ import type { ResolvedNetwork } from "./types.js";
4
4
  export declare const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
5
5
  export declare const DEFAULT_SANDBOX_CPUS = 2;
6
6
  export declare const DEFAULT_SANDBOX_MEMORY_MIB = 1024;
7
- export declare const DEFAULT_SANDBOX_IDLE_MS: number;
7
+ /** The default of the Tenant's Environment setting `limits.idle` (`environments.config`, D36). */
8
+ export declare const DEFAULT_SANDBOX_IDLE = "15m";
8
9
  /** Package registries and code hosts. Concrete names so every backend can express them. */
9
10
  export declare const DEV_PRESET_HOSTS: readonly string[];
10
11
  export declare function resolveNetwork(manifest: SandboxManifest | undefined): ResolvedNetwork;
12
+ /**
13
+ * How long the sandbox may sit idle: its spec's `idle`, which `sandboxSpecOf` sets from the
14
+ * Environment's `lifecycle.idle` within the Tenant's `limits.idle`; else that setting's default
15
+ * (`DEFAULT_SANDBOX_IDLE`, D36).
16
+ */
11
17
  export declare function idleMsOf(manifest: SandboxManifest | undefined): number;
12
18
  export declare function memoryMiBOf(manifest: SandboxManifest | undefined): number;
13
19
  export declare function describeNetwork(network: ResolvedNetwork): string;
@@ -1,9 +1,11 @@
1
1
  import { parseSandboxDuration, parseSandboxSize, } from "@nylorun/core/define";
2
+ import { hostTuning } from "../tuning.js";
2
3
  /** Docker Official Image: Debian with bash, coreutils and Python 3; small first pull. */
3
4
  export const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
4
5
  export const DEFAULT_SANDBOX_CPUS = 2;
5
6
  export const DEFAULT_SANDBOX_MEMORY_MIB = 1024;
6
- export const DEFAULT_SANDBOX_IDLE_MS = 15 * 60_000;
7
+ /** The default of the Tenant's Environment setting `limits.idle` (`environments.config`, D36). */
8
+ export const DEFAULT_SANDBOX_IDLE = "15m";
7
9
  /** Package registries and code hosts. Concrete names so every backend can express them. */
8
10
  export const DEV_PRESET_HOSTS = Object.freeze([
9
11
  // npm and yarn
@@ -50,14 +52,19 @@ export function resolveNetwork(manifest) {
50
52
  suffixes: Object.freeze([...suffixes].sort()),
51
53
  });
52
54
  }
55
+ /**
56
+ * How long the sandbox may sit idle: its spec's `idle`, which `sandboxSpecOf` sets from the
57
+ * Environment's `lifecycle.idle` within the Tenant's `limits.idle`; else that setting's default
58
+ * (`DEFAULT_SANDBOX_IDLE`, D36).
59
+ */
53
60
  export function idleMsOf(manifest) {
54
- return (manifest?.idle && parseSandboxDuration(manifest.idle)) || DEFAULT_SANDBOX_IDLE_MS;
61
+ return parseSandboxDuration(manifest?.idle ?? DEFAULT_SANDBOX_IDLE) ?? parseSandboxDuration(DEFAULT_SANDBOX_IDLE);
55
62
  }
56
63
  export function memoryMiBOf(manifest) {
57
64
  const bytes = manifest?.resources?.memory
58
65
  ? parseSandboxSize(manifest.resources.memory)
59
66
  : undefined;
60
- return bytes ? Math.max(128, Math.ceil(bytes / 1024 ** 2)) : DEFAULT_SANDBOX_MEMORY_MIB;
67
+ return bytes ? Math.max(hostTuning().pods.memoryFloorMiB, Math.ceil(bytes / 1024 ** 2)) : DEFAULT_SANDBOX_MEMORY_MIB;
61
68
  }
62
69
  export function describeNetwork(network) {
63
70
  const extra = network.preset === "dev"
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Resolves an Environment's recipe (`PUT /v1/environments/{id}`) against its Tenant's limits,
3
- * and the sandbox spec a recipe gives a session's pinned manifest (its `runtime` block) and the pod. Pure: the
2
+ * Resolves an Agent's recipe (`PUT /v1/agents/{id}`) against its Tenant's limits,
3
+ * and the sandbox spec a recipe gives a session (`Session.sandbox`) and the pod. Pure: the
4
4
  * caller supplies the configuration. Every problem is reported, not only the first, so one
5
5
  * response lists all of them.
6
6
  */
7
- import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
7
+ import type { EnvironmentFile, EnvironmentKind, EnvironmentLifecycle, EnvironmentLimits, EnvironmentRecipe, EnvironmentSecretSlots, EnvironmentSetupCommand } from "@nylorun/core/contracts";
8
8
  import { type SandboxManifest } from "@nylorun/core/define";
9
9
  import type { PodSandboxSpec } from "./pods/spec.js";
10
10
  import { type EffectiveSandboxConfig } from "./tenant-config.js";
@@ -25,6 +25,11 @@ export interface RecipeRequest {
25
25
  readonly files?: readonly EnvironmentFile[];
26
26
  /** The secret slots (D64): passed through for every kind. */
27
27
  readonly secrets?: EnvironmentSecretSlots;
28
+ /**
29
+ * Artifact and skill limits: passed through for every kind, as set. The caller checks them
30
+ * against the Tenant's (`tenant/limits.ts`), which this module does not read.
31
+ */
32
+ readonly limits?: EnvironmentLimits;
28
33
  }
29
34
  export type RecipeResolution = {
30
35
  readonly kind: "recipe";
@@ -33,11 +38,11 @@ export type RecipeResolution = {
33
38
  readonly kind: "error";
34
39
  readonly errors: readonly string[];
35
40
  };
36
- /** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
41
+ /** The recipe `request` asks for an Agent of `kind`, or every reason it cannot have it. */
37
42
  export declare function resolveRecipe(kind: EnvironmentKind, request: RecipeRequest, config: EffectiveSandboxConfig): RecipeResolution;
38
43
  /**
39
- * The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
40
- * workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
44
+ * The sandbox spec of a recipe: what a session pins when it opens and the virtual
45
+ * workspace runs. Idle is the Agent's `lifecycle.idle`, else the Tenant's.
41
46
  */
42
47
  export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
43
48
  /** The pod's spec of a recipe: its sandbox spec and its volume's size. */
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_HARNESS } from "@nylorun/core/contracts";
2
2
  import { parseSandboxDuration } from "@nylorun/core/define";
3
3
  import { memoryMiB } from "./tenant-config.js";
4
- /** The recipe `request` asks for an Environment of `kind`, or every reason it cannot have it. */
4
+ /** The recipe `request` asks for an Agent of `kind`, or every reason it cannot have it. */
5
5
  export function resolveRecipe(kind, request, config) {
6
6
  const { limits } = config;
7
7
  const errors = [];
@@ -9,7 +9,7 @@ export function resolveRecipe(kind, request, config) {
9
9
  // Any harness id (R4.1): placement decides where it may run, when a session opens.
10
10
  const harness = request.harness ?? DEFAULT_HARNESS;
11
11
  if (pod && harness !== DEFAULT_HARNESS && request.image === undefined)
12
- errors.push(`harness ${harness} needs image: a pod Environment runs its image as the harness.`);
12
+ errors.push(`harness ${harness} needs image: a pod Agent runs its image as the harness.`);
13
13
  const lifecycle = request.lifecycle ?? {};
14
14
  const idle = lifecycle.idle === undefined ? undefined : parseSandboxDuration(lifecycle.idle);
15
15
  if (idle !== undefined && idle > (parseSandboxDuration(limits.idle) ?? Infinity))
@@ -17,15 +17,18 @@ export function resolveRecipe(kind, request, config) {
17
17
  if (kind === "none") {
18
18
  for (const field of ["image", "resources", "storage", "network", "setup", "files"])
19
19
  if (request[field] !== undefined)
20
- errors.push(`${field} is for an Environment with a sandbox: kind none has none.`);
20
+ errors.push(`${field} is for an Agent with a sandbox: kind none has none.`);
21
21
  if (errors.length > 0)
22
22
  return { kind: "error", errors };
23
- return { kind: "recipe", recipe: { harness, lifecycle: { ...lifecycle }, ...slotsOf(request) } };
23
+ return {
24
+ kind: "recipe",
25
+ recipe: { harness, lifecycle: { ...lifecycle }, ...limitsOf(request), ...slotsOf(request) },
26
+ };
24
27
  }
25
28
  if (request.image !== undefined && !pod)
26
- errors.push("image is for kind pod: a virtual Environment runs no images.");
29
+ errors.push("image is for kind pod: a virtual Agent runs no images.");
27
30
  if (request.storage !== undefined && !pod)
28
- errors.push("storage is for kind pod: a virtual Environment has no volume.");
31
+ errors.push("storage is for kind pod: a virtual Agent has no volume.");
29
32
  for (const field of ["setup", "files"])
30
33
  if (request[field] !== undefined && request[field].length > 0 && !pod)
31
34
  errors.push(`${field} is for kind pod: it runs when a pod's disk is made or reset.`);
@@ -34,7 +37,7 @@ export function resolveRecipe(kind, request, config) {
34
37
  if (!withinCeiling(host, limits.network))
35
38
  errors.push(`network.allowedHosts includes ${host}, which this Tenant does not allow.`);
36
39
  else if (host.startsWith("*.") && !pod)
37
- errors.push(`network.allowedHosts includes ${host}; a virtual Environment allows exact host names only, such as ${host.slice(2)}.`);
40
+ errors.push(`network.allowedHosts includes ${host}; a virtual Agent allows exact host names only, such as ${host.slice(2)}.`);
38
41
  }
39
42
  const cpus = request.resources?.cpus ?? limits.defaultResources.cpus;
40
43
  const memory = request.resources?.memory === undefined
@@ -57,10 +60,17 @@ export function resolveRecipe(kind, request, config) {
57
60
  lifecycle: { ...lifecycle },
58
61
  ...(pod && request.setup?.length ? { setup: request.setup.map((command) => ({ ...command })) } : {}),
59
62
  ...(pod && request.files?.length ? { files: request.files.map((file) => ({ ...file })) } : {}),
63
+ ...limitsOf(request),
60
64
  ...slotsOf(request),
61
65
  },
62
66
  };
63
67
  }
68
+ /** The request's limits, when it sets any. */
69
+ function limitsOf(request) {
70
+ return request.limits !== undefined && Object.keys(request.limits).length > 0
71
+ ? { limits: structuredClone(request.limits) }
72
+ : {};
73
+ }
64
74
  /** The request's secret slots, when it declares any. */
65
75
  function slotsOf(request) {
66
76
  return request.secrets !== undefined && Object.keys(request.secrets).length > 0
@@ -68,8 +78,8 @@ function slotsOf(request) {
68
78
  : {};
69
79
  }
70
80
  /**
71
- * The sandbox spec of a recipe: what a session's `runtime` block pins and the virtual
72
- * workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
81
+ * The sandbox spec of a recipe: what a session pins when it opens and the virtual
82
+ * workspace runs. Idle is the Agent's `lifecycle.idle`, else the Tenant's.
73
83
  */
74
84
  export function sandboxSpecOf(recipe, tenantIdle) {
75
85
  const allow = recipe.network?.allowedHosts ?? [];
@@ -6,6 +6,8 @@ export { SETUP_FAILED };
6
6
  export interface SetupPlan {
7
7
  /** The applied recipe's version: written into the volume's marker. */
8
8
  readonly recipeVersion: number;
9
+ /** The sha256 of the commands and files: a marker with another runs the setup again (T3). */
10
+ readonly setupHash: string;
9
11
  readonly commands: readonly {
10
12
  readonly command: string;
11
13
  readonly cwd: string;
@@ -33,8 +35,10 @@ export interface SetupReport {
33
35
  export declare function parseSetupReport(value: unknown): SetupReport | undefined;
34
36
  /**
35
37
  * Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
36
- * the pod is `failed` with a reason that starts `setup:`, and `environment.failed` says which
37
- * command failed and how. A report from an older host, or a success, changes nothing.
38
+ * the pod is `failed` with a reason that starts `setup:`, and `agent.failed` says which
39
+ * command failed and how, with the end of its output (the Tenant's `setupOutputChars`; the reason
40
+ * quotes `setupReasonChars` of it and `setupCommandExcerptChars` of the command). A report
41
+ * from an older host, or a success, changes nothing.
38
42
  */
39
43
  export declare function recordSetup(ctx: TenantContext, id: string, epoch: number, report: SetupReport): Promise<void>;
40
44
  /**