@nylorun/runtime 0.25.0-beta → 0.27.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 (326) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +26 -16
  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.js +1 -1
  12. package/dist/api/a2a/endpoint.js +3 -4
  13. package/dist/api/a2a/protocol.js +4 -0
  14. package/dist/api/a2a/routes.d.ts +2 -6
  15. package/dist/api/a2a/routes.js +12 -18
  16. package/dist/api/ag-ui/endpoint.js +2 -3
  17. package/dist/api/ag-ui/routes.js +4 -4
  18. package/dist/api/components.d.ts +326 -45
  19. package/dist/api/components.js +5 -1
  20. package/dist/api/gate/keys.js +7 -4
  21. package/dist/api/gate/routes.d.ts +6 -2
  22. package/dist/api/gate/routes.js +15 -11
  23. package/dist/api/http/app.d.ts +2 -2
  24. package/dist/api/http/app.js +2 -2
  25. package/dist/api/http/body.d.ts +15 -3
  26. package/dist/api/http/body.js +15 -7
  27. package/dist/api/http/define.d.ts +1 -1
  28. package/dist/api/http/respond.js +7 -3
  29. package/dist/api/http/routes/access.js +5 -4
  30. package/dist/api/http/routes/artifacts.js +19 -7
  31. package/dist/api/http/routes/coverage.js +1 -1
  32. package/dist/api/http/routes/environment-manifests.js +1 -1
  33. package/dist/api/http/routes/environments.js +6 -6
  34. package/dist/api/http/routes/files.d.ts +2 -1
  35. package/dist/api/http/routes/files.js +31 -5
  36. package/dist/api/http/routes/mcp.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +35 -0
  38. package/dist/api/http/routes/reads.js +50 -22
  39. package/dist/api/http/routes/sessions.js +12 -11
  40. package/dist/api/http/routes/tenant.d.ts +2 -2
  41. package/dist/api/http/routes/tenant.js +69 -16
  42. package/dist/api/inbound-files.d.ts +3 -9
  43. package/dist/api/inbound-files.js +18 -24
  44. package/dist/api/openapi.js +6 -2
  45. package/dist/artifacts/disk.d.ts +0 -4
  46. package/dist/artifacts/disk.js +77 -73
  47. package/dist/artifacts/files.d.ts +13 -4
  48. package/dist/artifacts/files.js +27 -9
  49. package/dist/artifacts/folder-upload.d.ts +75 -0
  50. package/dist/artifacts/folder-upload.js +263 -0
  51. package/dist/artifacts/folders.d.ts +14 -8
  52. package/dist/artifacts/folders.js +19 -11
  53. package/dist/artifacts/links.js +7 -6
  54. package/dist/artifacts/parts.d.ts +9 -14
  55. package/dist/artifacts/parts.js +11 -9
  56. package/dist/artifacts/pipe.d.ts +15 -0
  57. package/dist/artifacts/pipe.js +71 -0
  58. package/dist/artifacts/quota.d.ts +36 -0
  59. package/dist/artifacts/quota.js +44 -0
  60. package/dist/artifacts/service.d.ts +25 -1
  61. package/dist/artifacts/service.js +70 -25
  62. package/dist/blob/fs.js +2 -1
  63. package/dist/blob/s3.d.ts +6 -2
  64. package/dist/blob/s3.js +9 -7
  65. package/dist/blob/types.d.ts +1 -1
  66. package/dist/blob/types.js +1 -1
  67. package/dist/core/limits.d.ts +11 -35
  68. package/dist/core/limits.js +5 -73
  69. package/dist/core/sandbox-routes.js +0 -1
  70. package/dist/core/turn-manifest.d.ts +1 -2
  71. package/dist/core/turn-manifest.js +4 -28
  72. package/dist/execution/memory.d.ts +8 -5
  73. package/dist/execution/memory.js +8 -6
  74. package/dist/execution/types.d.ts +1 -6
  75. package/dist/execution/types.js +0 -5
  76. package/dist/gates/contract.d.ts +3 -2
  77. package/dist/gates/contract.js +1 -1
  78. package/dist/gates/egress-credentials.js +4 -3
  79. package/dist/gates/egress.d.ts +9 -9
  80. package/dist/gates/egress.js +19 -19
  81. package/dist/gates/fixture-model.js +13 -2
  82. package/dist/gates/handler.d.ts +1 -3
  83. package/dist/gates/handler.js +4 -3
  84. package/dist/gates/http-client.d.ts +4 -3
  85. package/dist/gates/http-client.js +5 -7
  86. package/dist/gates/http-tool.d.ts +24 -4
  87. package/dist/gates/http-tool.js +53 -17
  88. package/dist/gates/in-process.d.ts +2 -1
  89. package/dist/gates/in-process.js +17 -9
  90. package/dist/gates/inflight.d.ts +2 -2
  91. package/dist/gates/inflight.js +3 -2
  92. package/dist/gates/journal.js +5 -7
  93. package/dist/gates/mcp-handler.d.ts +4 -3
  94. package/dist/gates/mcp-handler.js +15 -5
  95. package/dist/gates/model-gate.d.ts +11 -2
  96. package/dist/gates/policy.d.ts +28 -5
  97. package/dist/gates/policy.js +60 -17
  98. package/dist/gates/provider-gate.d.ts +22 -0
  99. package/dist/gates/provider-gate.js +30 -0
  100. package/dist/gates/tool-calls.d.ts +1 -3
  101. package/dist/gates/tool-calls.js +2 -3
  102. package/dist/gates/tool-client.d.ts +1 -1
  103. package/dist/gates/tool-client.js +5 -6
  104. package/dist/gates/tool-contract.d.ts +8 -5
  105. package/dist/gates/tool-contract.js +1 -1
  106. package/dist/gates/tool-gate.d.ts +18 -7
  107. package/dist/gates/tool-gate.js +49 -7
  108. package/dist/harness/calls.d.ts +27 -25
  109. package/dist/harness/calls.js +40 -46
  110. package/dist/harness/executors.d.ts +10 -3
  111. package/dist/harness/executors.js +53 -23
  112. package/dist/harness/main.js +2 -2
  113. package/dist/harness/pod.d.ts +0 -2
  114. package/dist/harness/pod.js +7 -6
  115. package/dist/harness/run-tokens.d.ts +4 -1
  116. package/dist/harness/run-tokens.js +6 -2
  117. package/dist/harness/service.d.ts +17 -14
  118. package/dist/harness/service.js +21 -35
  119. package/dist/harness/setup.d.ts +0 -2
  120. package/dist/harness/setup.js +4 -9
  121. package/dist/harness/ws-client.d.ts +2 -2
  122. package/dist/harness/ws-client.js +7 -5
  123. package/dist/harness/ws-port.d.ts +3 -4
  124. package/dist/harness/ws-port.js +3 -4
  125. package/dist/harness-api/artifacts.d.ts +20 -0
  126. package/dist/harness-api/artifacts.js +478 -0
  127. package/dist/harness-api/in-process.d.ts +2 -2
  128. package/dist/harness-api/in-process.js +18 -16
  129. package/dist/harness-api/record.d.ts +10 -9
  130. package/dist/harness-api/record.js +102 -157
  131. package/dist/harness-api/renew.d.ts +1 -1
  132. package/dist/harness-api/renew.js +2 -2
  133. package/dist/harness-api/server.d.ts +1 -16
  134. package/dist/harness-api/server.js +82 -73
  135. package/dist/harness-api/start.d.ts +20 -2
  136. package/dist/harness-api/start.js +57 -18
  137. package/dist/harness-api/tool-journal.d.ts +15 -10
  138. package/dist/harness-api/tool-journal.js +26 -24
  139. package/dist/harness-api/workspace.d.ts +0 -4
  140. package/dist/harness-api/workspace.js +2 -45
  141. package/dist/harness-api/ws-server.js +10 -10
  142. package/dist/host/create-host.js +2 -3
  143. package/dist/host/execution.js +2 -3
  144. package/dist/host/gates.d.ts +2 -9
  145. package/dist/host/gates.js +15 -12
  146. package/dist/host/http.d.ts +0 -2
  147. package/dist/host/http.js +0 -27
  148. package/dist/host/main.js +10 -3
  149. package/dist/host/operate.js +12 -3
  150. package/dist/host/stack-config.d.ts +11 -0
  151. package/dist/host/stack-config.js +21 -1
  152. package/dist/index.d.ts +0 -1
  153. package/dist/index.js +0 -1
  154. package/dist/infra/index.js +2 -1
  155. package/dist/infra/readiness.d.ts +0 -2
  156. package/dist/infra/readiness.js +4 -3
  157. package/dist/keys/client.d.ts +1 -0
  158. package/dist/keys/client.js +2 -3
  159. package/dist/keys/contract.d.ts +1 -3
  160. package/dist/keys/contract.js +1 -3
  161. package/dist/keys/egress-ca.d.ts +0 -2
  162. package/dist/keys/egress-ca.js +2 -3
  163. package/dist/keys/keys.js +6 -1
  164. package/dist/keys/x509.d.ts +8 -4
  165. package/dist/keys/x509.js +14 -5
  166. package/dist/management-openapi.json +1961 -994
  167. package/dist/mcp/connect.d.ts +35 -26
  168. package/dist/mcp/connect.js +112 -141
  169. package/dist/mcp/pool.d.ts +27 -22
  170. package/dist/mcp/pool.js +34 -48
  171. package/dist/mcp/preview.d.ts +13 -6
  172. package/dist/mcp/preview.js +37 -20
  173. package/dist/mcp/snapshot.d.ts +35 -86
  174. package/dist/mcp/snapshot.js +93 -135
  175. package/dist/model/auth-store.js +5 -3
  176. package/dist/model/capabilities.d.ts +1 -1
  177. package/dist/model/catalog.d.ts +9 -36
  178. package/dist/model/catalog.js +6 -33
  179. package/dist/model/descriptor.d.ts +51 -0
  180. package/dist/model/descriptor.js +83 -0
  181. package/dist/model/models.d.ts +0 -3
  182. package/dist/model/models.js +3 -5
  183. package/dist/model/native-blocks.js +1 -1
  184. package/dist/model/pi-model.d.ts +16 -5
  185. package/dist/model/pi-model.js +86 -105
  186. package/dist/openapi.json +866 -1218
  187. package/dist/reads/cursor.js +3 -1
  188. package/dist/record/append.d.ts +34 -2
  189. package/dist/record/append.js +50 -1
  190. package/dist/record/environment.d.ts +2 -0
  191. package/dist/record/environment.js +2 -0
  192. package/dist/record/index.d.ts +1 -1
  193. package/dist/record/index.js +1 -1
  194. package/dist/redact.d.ts +1 -1
  195. package/dist/redact.js +3 -2
  196. package/dist/sandbox/egress-token.d.ts +3 -3
  197. package/dist/sandbox/egress-token.js +3 -3
  198. package/dist/sandbox/join.js +3 -2
  199. package/dist/sandbox/manager.d.ts +41 -41
  200. package/dist/sandbox/manager.js +112 -44
  201. package/dist/sandbox/placement.d.ts +1 -1
  202. package/dist/sandbox/placement.js +5 -8
  203. package/dist/sandbox/pods/client.d.ts +1 -10
  204. package/dist/sandbox/pods/client.js +8 -4
  205. package/dist/sandbox/pods/lifecycle.d.ts +19 -11
  206. package/dist/sandbox/pods/lifecycle.js +45 -23
  207. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  208. package/dist/sandbox/pods/network-gate.js +7 -7
  209. package/dist/sandbox/pods/reconcile.js +4 -1
  210. package/dist/sandbox/pods/spec.js +8 -5
  211. package/dist/sandbox/policy.d.ts +7 -1
  212. package/dist/sandbox/policy.js +10 -3
  213. package/dist/sandbox/resolve.d.ts +8 -3
  214. package/dist/sandbox/resolve.js +12 -2
  215. package/dist/sandbox/setup.d.ts +3 -1
  216. package/dist/sandbox/setup.js +34 -11
  217. package/dist/sandbox/share.d.ts +5 -14
  218. package/dist/sandbox/share.js +5 -34
  219. package/dist/sandbox/skills.d.ts +7 -13
  220. package/dist/sandbox/skills.js +16 -31
  221. package/dist/sandbox/tenant-config.d.ts +2 -2
  222. package/dist/sandbox/tenant-config.js +4 -3
  223. package/dist/sandbox/tools.d.ts +7 -1
  224. package/dist/sandbox/tools.js +26 -26
  225. package/dist/sandbox/types.d.ts +10 -3
  226. package/dist/sandbox/types.js +9 -0
  227. package/dist/store/postgres/connect.d.ts +6 -5
  228. package/dist/store/postgres/connect.js +14 -11
  229. package/dist/store/postgres/control.js +13 -13
  230. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  231. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  232. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  233. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  234. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  235. package/dist/store/postgres/migrate.js +1 -1
  236. package/dist/store/postgres/reads.js +10 -2
  237. package/dist/store/postgres/schema.d.ts +19 -0
  238. package/dist/store/postgres/schema.js +7 -0
  239. package/dist/store/postgres/store.js +55 -26
  240. package/dist/store/types.d.ts +17 -7
  241. package/dist/streams/relay/core.d.ts +3 -3
  242. package/dist/streams/relay/core.js +9 -10
  243. package/dist/tenant/advance.js +21 -31
  244. package/dist/tenant/commands.js +9 -16
  245. package/dist/tenant/context.d.ts +14 -17
  246. package/dist/tenant/definition-files.d.ts +25 -4
  247. package/dist/tenant/definition-files.js +85 -47
  248. package/dist/tenant/effects.js +9 -36
  249. package/dist/tenant/environment-manifests.js +9 -1
  250. package/dist/tenant/environments.d.ts +0 -2
  251. package/dist/tenant/environments.js +54 -30
  252. package/dist/tenant/ephemeral.d.ts +1 -1
  253. package/dist/tenant/ephemeral.js +2 -1
  254. package/dist/tenant/history.d.ts +1 -1
  255. package/dist/tenant/history.js +1 -1
  256. package/dist/tenant/host-token.d.ts +3 -3
  257. package/dist/tenant/host-token.js +12 -11
  258. package/dist/tenant/http.d.ts +5 -8
  259. package/dist/tenant/http.js +9 -0
  260. package/dist/tenant/identity-file.d.ts +24 -1
  261. package/dist/tenant/identity-file.js +35 -7
  262. package/dist/tenant/issuers.d.ts +5 -5
  263. package/dist/tenant/issuers.js +31 -23
  264. package/dist/tenant/jwt.d.ts +8 -2
  265. package/dist/tenant/jwt.js +10 -2
  266. package/dist/tenant/limits.d.ts +90 -0
  267. package/dist/tenant/limits.js +175 -0
  268. package/dist/tenant/module.d.ts +4 -1
  269. package/dist/tenant/module.js +9 -8
  270. package/dist/tenant/outbound.d.ts +17 -19
  271. package/dist/tenant/outbound.js +18 -21
  272. package/dist/tenant/run-grants.js +3 -2
  273. package/dist/tenant/run-token.d.ts +0 -4
  274. package/dist/tenant/run-token.js +6 -11
  275. package/dist/tenant/runtime-limits.d.ts +24 -0
  276. package/dist/tenant/runtime-limits.js +27 -0
  277. package/dist/tenant/runtime.js +30 -43
  278. package/dist/tenant/session-streams.js +7 -6
  279. package/dist/tenant/session.d.ts +1 -1
  280. package/dist/tenant/session.js +1 -1
  281. package/dist/tenant/sessions.js +32 -29
  282. package/dist/tenant/signing-keys.d.ts +1 -1
  283. package/dist/tenant/signing-keys.js +5 -6
  284. package/dist/tenant/status.js +4 -3
  285. package/dist/tenant/streams.js +18 -20
  286. package/dist/tenant/sweep.d.ts +1 -21
  287. package/dist/tenant/sweep.js +39 -48
  288. package/dist/tenant/transcript.d.ts +70 -23
  289. package/dist/tenant/transcript.js +28 -22
  290. package/dist/tenant/types.d.ts +11 -16
  291. package/dist/tenant/worker.d.ts +2 -10
  292. package/dist/tenant/worker.js +5 -14
  293. package/dist/traces/ids.js +5 -1
  294. package/dist/traces/map.d.ts +9 -6
  295. package/dist/traces/map.js +9 -11
  296. package/dist/traces/otlp.d.ts +3 -3
  297. package/dist/traces/otlp.js +5 -3
  298. package/dist/traces/projector.d.ts +9 -4
  299. package/dist/traces/projector.js +13 -7
  300. package/dist/traces/read.js +6 -1
  301. package/dist/tuning.d.ts +383 -0
  302. package/dist/tuning.js +658 -0
  303. package/dist/vault/headers.js +4 -2
  304. package/dist/version.d.ts +1 -1
  305. package/dist/version.js +1 -1
  306. package/package.json +3 -3
  307. package/dist/artifacts/export.d.ts +0 -8
  308. package/dist/artifacts/export.js +0 -204
  309. package/dist/artifacts/media-types.d.ts +0 -30
  310. package/dist/artifacts/media-types.js +0 -137
  311. package/dist/artifacts/workspace.d.ts +0 -60
  312. package/dist/artifacts/workspace.js +0 -45
  313. package/dist/media.d.ts +0 -10
  314. package/dist/media.js +0 -58
  315. package/dist/sandbox/runtime-tools.d.ts +0 -9
  316. package/dist/sandbox/runtime-tools.js +0 -16
  317. package/dist/sandbox/session-sandbox.d.ts +0 -16
  318. package/dist/sandbox/session-sandbox.js +0 -77
  319. package/dist/tenant/artifact-tool.d.ts +0 -13
  320. package/dist/tenant/artifact-tool.js +0 -243
  321. package/dist/tenant/skill-tool.d.ts +0 -20
  322. package/dist/tenant/skill-tool.js +0 -67
  323. package/dist/tenant/tool-results.d.ts +0 -57
  324. package/dist/tenant/tool-results.js +0 -298
  325. package/dist/tenant/tool-search.d.ts +0 -39
  326. package/dist/tenant/tool-search.js +0 -103
@@ -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,14 @@
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";
24
26
  export async function reconcileSandbox(ctx, sandboxId, trigger) {
25
27
  const pods = ctx.pods;
26
28
  if (!pods || ctx.closed)
@@ -52,6 +54,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
52
54
  const lifecycle = podLifecycleConfig(config, row.recipe);
53
55
  const decision = decide({
54
56
  row,
57
+ image: row.applied.image ?? DEFAULT_SANDBOX_IMAGE,
55
58
  config: lifecycle,
56
59
  status: await status(row.pod.k8sName),
57
60
  ...(row.pod.retiring ? { retiring: await status(row.pod.retiring) } : {}),
@@ -116,7 +119,7 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
116
119
  message: error instanceof Error ? error.message : String(error),
117
120
  });
118
121
  // Nothing of the decision is written: the next reconcile decides again.
119
- return { retryAfterMs: decision.retryAfterMs ?? 5_000 };
122
+ return { retryAfterMs: decision.retryAfterMs ?? hostTuning().pods.reconcileRetryMs };
120
123
  }
121
124
  }
122
125
  const written = await ctx.store.tx(async (t) => {
@@ -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
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
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";
@@ -36,7 +41,7 @@ export type RecipeResolution = {
36
41
  /** The recipe `request` asks for an Environment 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
44
+ * The sandbox spec of a recipe: what a session pins when it opens and the virtual
40
45
  * workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
41
46
  */
42
47
  export declare function sandboxSpecOf(recipe: EnvironmentRecipe, tenantIdle: string): SandboxManifest;
@@ -20,7 +20,10 @@ export function resolveRecipe(kind, request, config) {
20
20
  errors.push(`${field} is for an Environment 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
29
  errors.push("image is for kind pod: a virtual Environment runs no images.");
@@ -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,7 +78,7 @@ 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
81
+ * The sandbox spec of a recipe: what a session pins when it opens and the virtual
72
82
  * workspace runs. Idle is the Environment's `lifecycle.idle`, else the Tenant's.
73
83
  */
74
84
  export function sandboxSpecOf(recipe, tenantIdle) {
@@ -34,7 +34,9 @@ export declare function parseSetupReport(value: unknown): SetupReport | undefine
34
34
  /**
35
35
  * Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
36
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.
37
+ * command failed and how, with the end of its output (the Tenant's `setupOutputChars`; the reason
38
+ * quotes `setupReasonChars` of it and `setupCommandExcerptChars` of the command). A report
39
+ * from an older host, or a success, changes nothing.
38
40
  */
39
41
  export declare function recordSetup(ctx: TenantContext, id: string, epoch: number, report: SetupReport): Promise<void>;
40
42
  /**
@@ -1,24 +1,43 @@
1
+ /**
2
+ * Setup and input files (R2d step 2, Host feature `environment-setup`): what a pod Environment's
3
+ * engine runs and copies when its disk is made or reset, before it takes any turn. The plan is
4
+ * the applied recipe's `setup` and `files`, handed to the engine with its host token (the join
5
+ * answer, and `GET host/setup` while it waits to retry, or, on a fork onto other setup, for its
6
+ * disk version to be written: Q17); the engine keeps a marker on the volume
7
+ * (`/harness/setup.json`), so a resume never runs it again and a reset (a new volume) does.
8
+ *
9
+ * Input files come only through the routes here, indexed into the applied recipe's `files`:
10
+ * a pod reads the artifact versions and the definition files by hash (Q6) its own recipe names,
11
+ * and nothing else of the Object store.
12
+ *
13
+ * A failed command fails the pod (`observed: failed`, a reason that starts `setup:`, and
14
+ * `environment.failed` with the command, its exit code and the end of its output): the turn that
15
+ * waits for it fails with that reason (`podUnusable`, `tenant/advance.ts`), the failure stays
16
+ * until a turn start asks the pod to run again (`wantRunning`), and the engine, polling, runs the
17
+ * setup again then.
18
+ */
19
+ import { ENVIRONMENT_SETUP_TIMEOUT_DEFAULT, } from "@nylorun/core/contracts";
1
20
  import { parseSandboxDuration } from "@nylorun/core/define";
2
21
  import { contentKey, readManifest } from "../artifacts/folders.js";
3
- import { contentTypeFor } from "../artifacts/media-types.js";
22
+ import { contentTypeFor } from "@nylorun/core/contracts";
4
23
  import { definitionFileKey } from "../tenant/definition-files.js";
5
24
  import { SETUP_FAILED } from "./pods/lifecycle.js";
25
+ import { readRuntimeLimits } from "../tenant/runtime-limits.js";
6
26
  export { SETUP_FAILED };
7
- /** The most of a failed command's output kept in its event and reason. */
8
- const OUTPUT_TAIL = 4 * 1024;
9
- /** A command's timeout when the recipe gives none. */
10
- const DEFAULT_TIMEOUT_MS = 10 * 60_000;
11
27
  /** The setup of `recipe`, or undefined when it has neither commands nor files. */
12
28
  export async function setupPlanOf(ctx, t, environment) {
13
29
  const recipe = environment.applied;
14
30
  if (!recipe.setup?.length && !recipe.files?.length)
15
31
  return undefined;
32
+ // A command's own timeout, else the default, within the Tenant's `setupCommandMs` as it is now.
33
+ const { setupCommandMs } = await readRuntimeLimits(t);
34
+ const timeoutOf = (timeout) => Math.min(parseSandboxDuration(timeout ?? ENVIRONMENT_SETUP_TIMEOUT_DEFAULT), setupCommandMs);
16
35
  return {
17
36
  recipeVersion: environment.appliedVersion,
18
37
  commands: (recipe.setup ?? []).map((command) => ({
19
38
  command: command.command,
20
39
  cwd: command.cwd ?? "/workspace",
21
- timeoutMs: (command.timeout && parseSandboxDuration(command.timeout)) || DEFAULT_TIMEOUT_MS,
40
+ timeoutMs: timeoutOf(command.timeout),
22
41
  })),
23
42
  files: (recipe.files ?? []).map((file, index) => ({ index, path: file.path })),
24
43
  environment: await ctx.vault.environmentVariables(t, environment.id),
@@ -32,7 +51,7 @@ export function parseSetupReport(value) {
32
51
  return undefined;
33
52
  const index = typeof report.index === "number" && Number.isInteger(report.index) ? report.index : undefined;
34
53
  const exitCode = typeof report.exitCode === "number" && Number.isInteger(report.exitCode) ? report.exitCode : undefined;
35
- const output = typeof report.output === "string" ? report.output.slice(-OUTPUT_TAIL) : undefined;
54
+ const output = typeof report.output === "string" ? report.output : undefined;
36
55
  return {
37
56
  status: report.status,
38
57
  ...(index === undefined ? {} : { index }),
@@ -43,7 +62,9 @@ export function parseSetupReport(value) {
43
62
  /**
44
63
  * Records a failed setup on Environment `id` held at host epoch `epoch` (the reporting engine's):
45
64
  * the pod is `failed` with a reason that starts `setup:`, and `environment.failed` says which
46
- * command failed and how. A report from an older host, or a success, changes nothing.
65
+ * command failed and how, with the end of its output (the Tenant's `setupOutputChars`; the reason
66
+ * quotes `setupReasonChars` of it and `setupCommandExcerptChars` of the command). A report
67
+ * from an older host, or a success, changes nothing.
47
68
  */
48
69
  export async function recordSetup(ctx, id, epoch, report) {
49
70
  await ctx.store.tx(async (t) => {
@@ -57,9 +78,11 @@ export async function recordSetup(ctx, id, epoch, report) {
57
78
  await t.updateEnvironment(id, { diskSeed: null }, new Date().toISOString());
58
79
  return;
59
80
  }
81
+ const { setupOutputChars, setupReasonChars, setupCommandExcerptChars } = await readRuntimeLimits(t);
82
+ const output = (report.output ?? "").slice(-setupOutputChars);
60
83
  const command = report.index === undefined ? undefined : environment.applied.setup?.[report.index];
61
- const what = command === undefined ? "copying the input files failed" : `command ${report.index + 1} (${command.command.slice(0, 80)}) exited ${report.exitCode ?? "abnormally"}`;
62
- const tail = (report.output ?? "").trim().slice(-500);
84
+ const what = command === undefined ? "copying the input files failed" : `command ${report.index + 1} (${command.command.slice(0, setupCommandExcerptChars)}) exited ${report.exitCode ?? "abnormally"}`;
85
+ const tail = output.trim().slice(-setupReasonChars);
63
86
  const reason = `${SETUP_FAILED} ${what}${tail ? `: ${tail}` : ""}`;
64
87
  await t.updateEnvironmentPod(id, { observed: "failed", reason }, new Date().toISOString());
65
88
  await t.environmentEvent(id, "environment.failed", {
@@ -67,7 +90,7 @@ export async function recordSetup(ctx, id, epoch, report) {
67
90
  setup: {
68
91
  ...(report.index === undefined ? {} : { index: report.index }),
69
92
  ...(report.exitCode === undefined ? {} : { exitCode: report.exitCode }),
70
- output: report.output ?? "",
93
+ output,
71
94
  },
72
95
  });
73
96
  });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A session's sandbox: the one of the Environment it names (protocol 11), whose id keys the
3
- * workspace, with the spec pinned when the session was opened (`Session.sandbox`). Definitions
4
- * no longer declare one (Sandboxes v3); `sandboxSpecOf` still reads a pinned manifest's.
3
+ * workspace, with the spec pinned when the session was opened (`Session.sandbox`, the one place
4
+ * it is kept since protocol 15). Definitions declare none (Sandboxes v3).
5
5
  */
6
6
  import { type AgentManifest, type SandboxManifest, type WorkflowManifest } from "@nylorun/core/define";
7
7
  export type SessionSandboxRef = {
@@ -13,23 +13,14 @@ export type SessionSandboxRef = {
13
13
  /** The Environment's sandbox spec, pinned when the session was opened. */
14
14
  readonly sandbox?: SandboxManifest;
15
15
  };
16
- /** The session's sandbox spec: the one pinned at open, else the one its definition declares. */
17
- export declare function sessionSandboxSpec(session: SessionSandboxRef): SandboxManifest | undefined;
18
- /** The sandbox spec of a pinned agent manifest (its `runtime` block) or a workflow manifest. */
19
- export declare function sandboxSpecOf(manifest: AgentManifest | WorkflowManifest | Record<string, unknown> | undefined): SandboxManifest | undefined;
16
+ /** The session's sandbox spec, pinned when it was opened (or inherited from its tree's owner). */
17
+ export declare function sessionSandboxSpec(session: Pick<SessionSandboxRef, "sandbox">): SandboxManifest | undefined;
20
18
  /**
21
19
  * Where a definition document declares a sandbox: a workflow's `sandbox`, and the agents embedded
22
20
  * in a flow agent. Definitions no longer declare one (Sandboxes v3), so registration refuses any
23
- * of these. An agent manifest cannot: its sandbox is in the `runtime` block, which the schema
24
- * refuses at registration.
21
+ * of these. An agent manifest has no field for one.
25
22
  */
26
23
  export declare function declaredSandboxes(manifest: unknown, into?: string[]): string[];
27
- /**
28
- * The agents of a definition document that set `runtime`: the document itself, the agents it
29
- * uses as tools and those embedded in a flow agent. Only the Runtime writes that block, when it
30
- * pins a session's manifest, so registration refuses any of these.
31
- */
32
- export declare function authoredRuntimeBlocks(manifest: unknown, into?: string[]): string[];
33
24
  /**
34
25
  * The workspace a session's sandbox tools run in: the session, whose log records the
35
26
  * workspace's events, and its Environment, whose id keys the workspace.
@@ -1,26 +1,17 @@
1
1
  /**
2
2
  * A session's sandbox: the one of the Environment it names (protocol 11), whose id keys the
3
- * workspace, with the spec pinned when the session was opened (`Session.sandbox`). Definitions
4
- * no longer declare one (Sandboxes v3); `sandboxSpecOf` still reads a pinned manifest's.
3
+ * workspace, with the spec pinned when the session was opened (`Session.sandbox`, the one place
4
+ * it is kept since protocol 15). Definitions declare none (Sandboxes v3).
5
5
  */
6
6
  import {} from "@nylorun/core/define";
7
- /** The session's sandbox spec: the one pinned at open, else the one its definition declares. */
7
+ /** The session's sandbox spec, pinned when it was opened (or inherited from its tree's owner). */
8
8
  export function sessionSandboxSpec(session) {
9
- return session.sandbox ?? sandboxSpecOf(session.manifest);
10
- }
11
- /** The sandbox spec of a pinned agent manifest (its `runtime` block) or a workflow manifest. */
12
- export function sandboxSpecOf(manifest) {
13
- if (!manifest || typeof manifest !== "object")
14
- return undefined;
15
- if (manifest.kind === "workflow")
16
- return manifest.sandbox;
17
- return manifest.runtime?.sandbox;
9
+ return session.sandbox;
18
10
  }
19
11
  /**
20
12
  * Where a definition document declares a sandbox: a workflow's `sandbox`, and the agents embedded
21
13
  * in a flow agent. Definitions no longer declare one (Sandboxes v3), so registration refuses any
22
- * of these. An agent manifest cannot: its sandbox is in the `runtime` block, which the schema
23
- * refuses at registration.
14
+ * of these. An agent manifest has no field for one.
24
15
  */
25
16
  export function declaredSandboxes(manifest, into = []) {
26
17
  if (!manifest || typeof manifest !== "object")
@@ -37,26 +28,6 @@ export function declaredSandboxes(manifest, into = []) {
37
28
  declaredSandboxes(tool.agent, into);
38
29
  return into;
39
30
  }
40
- /**
41
- * The agents of a definition document that set `runtime`: the document itself, the agents it
42
- * uses as tools and those embedded in a flow agent. Only the Runtime writes that block, when it
43
- * pins a session's manifest, so registration refuses any of these.
44
- */
45
- export function authoredRuntimeBlocks(manifest, into = []) {
46
- if (!manifest || typeof manifest !== "object")
47
- return into;
48
- const document = manifest;
49
- if (document.kind === "workflow") {
50
- for (const agent of Object.values(document.agents ?? {}))
51
- authoredRuntimeBlocks(agent, into);
52
- return into;
53
- }
54
- if (document.runtime !== undefined)
55
- into.push(`'${document.id}'`);
56
- for (const tool of document.tools ?? [])
57
- authoredRuntimeBlocks(tool.agent, into);
58
- return into;
59
- }
60
31
  /**
61
32
  * The workspace a session's sandbox tools run in: the session, whose log records the
62
33
  * workspace's events, and its Environment, whose id keys the workspace.
@@ -1,15 +1,9 @@
1
- import { type AgentManifest } from "@nylorun/core/define";
2
- import type { SandboxHandle } from "./types.js";
3
- /** A skill's files, by path in its folder, as `sha256:<hex>`. */
4
- export type SkillFiles = Readonly<Record<string, string>>;
1
+ import type { SandboxFileBytes, SandboxHandle } from "./types.js";
2
+ /** A folder's files, by path in it, as `sha256:<hex>`. */
3
+ export type FolderFiles = Readonly<Record<string, string>>;
5
4
  /**
6
- * The skill versions of the agent and the agents it uses as tools, by folder
7
- * (`/skills/<name>@<hash>`): each distinct version once.
5
+ * Writes one folder's files to `root`, replacing a partial copy a failed mount left, and makes
6
+ * them read-only. `read` gives a file's bytes by `sha256:<hex>`, whole or as chunks, which are
7
+ * written as they come.
8
8
  */
9
- export declare function sandboxSkillsOf(manifest: AgentManifest | undefined, into?: Map<string, Readonly<Record<string, string>>>): Map<string, SkillFiles>;
10
- /**
11
- * Writes one skill version's files to its folder `root` (`/skills/<name>@<hash>`), replacing a
12
- * partial copy a failed mount left, and makes them read-only. `read` gives a file's bytes by
13
- * `sha256:<hex>`.
14
- */
15
- export declare function mountSkill(handle: SandboxHandle, root: string, files: SkillFiles, read: (sha256: string) => Promise<Uint8Array>, signal: AbortSignal): Promise<void>;
9
+ export declare function mountFolderFiles(handle: SandboxHandle, root: string, files: FolderFiles, read: (sha256: string) => Promise<SandboxFileBytes>, signal: AbortSignal): Promise<void>;
@@ -1,41 +1,26 @@
1
1
  /**
2
- * A sandbox's skills (track R2 M4, D66): each version of a skill of the session's agent, and of
3
- * the agents it uses as tools (they share its sandbox), gets its files in its own folder,
4
- * `/skills/<name>@<hash>/` (`skillFolderOf`), read-only, so the model can read them and run their
5
- * scripts with bash. A version's folder is written once and never replaced: sessions whose agents
6
- * carry different versions of one skill share an Environment, and run turns at once, without
7
- * changing each other's files. The SandboxManager mounts the folders a session needs before the
8
- * first call that needs them; the bytes come from the Tenant's definition files. Folders live
9
- * outside the persisted disk, so a stop clears them and the next open mounts what is in use.
2
+ * Folders a harness mounts in a sandbox (protocol 15): our engine's skill folders,
3
+ * `/skills/<name>@<hash>/` (`skillFolderOf`, track R2 M4, D66), each one version's files,
4
+ * read-only, so the model can read them and run their scripts with bash. Our engine decides which
5
+ * folders and when (before each sandbox call); the SandboxManager writes them once per open
6
+ * handle (`mountFolder`). A version's folder is written once and never replaced: sessions whose
7
+ * agents carry different versions of one skill share an Environment, and run turns at once,
8
+ * without changing each other's files. Folders live outside the persisted disk, so a stop clears
9
+ * them and the next call mounts them again.
10
10
  */
11
11
  import { posix } from "node:path";
12
- import { skillFolderOf } from "@nylorun/core/define";
13
- import { quote } from "./tools.js";
12
+ import { helperCommandTimeoutMs, quote } from "./tools.js";
13
+ import { hostTuning } from "../tuning.js";
14
14
  /**
15
- * The skill versions of the agent and the agents it uses as tools, by folder
16
- * (`/skills/<name>@<hash>`): each distinct version once.
15
+ * Writes one folder's files to `root`, replacing a partial copy a failed mount left, and makes
16
+ * them read-only. `read` gives a file's bytes by `sha256:<hex>`, whole or as chunks, which are
17
+ * written as they come.
17
18
  */
18
- export function sandboxSkillsOf(manifest, into = new Map()) {
19
- for (const skill of Object.values(manifest?.skills ?? {})) {
20
- const folder = skillFolderOf(skill);
21
- if (!into.has(folder))
22
- into.set(folder, skill.files);
23
- }
24
- for (const tool of manifest?.tools ?? [])
25
- if (tool.agent !== undefined && !("kind" in tool.agent))
26
- sandboxSkillsOf(tool.agent, into);
27
- return into;
28
- }
29
- /**
30
- * Writes one skill version's files to its folder `root` (`/skills/<name>@<hash>`), replacing a
31
- * partial copy a failed mount left, and makes them read-only. `read` gives a file's bytes by
32
- * `sha256:<hex>`.
33
- */
34
- export async function mountSkill(handle, root, files, read, signal) {
19
+ export async function mountFolderFiles(handle, root, files, read, signal) {
35
20
  const exec = async (command) => {
36
- const result = await handle.exec({ command, cwd: "/", timeoutMs: 60_000 }, signal);
21
+ const result = await handle.exec({ command, cwd: "/", timeoutMs: helperCommandTimeoutMs() }, signal);
37
22
  if (result.exitCode !== 0)
38
- throw new Error(`${command.slice(0, 120)} failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
23
+ throw new Error(`${command.slice(0, hostTuning().harness.helperCommandExcerptChars)} failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
39
24
  };
40
25
  const directories = new Set([root]);
41
26
  for (const path of Object.keys(files))
@@ -4,13 +4,14 @@
4
4
  * fields take the defaults below.
5
5
  */
6
6
  import { type PlacementHost, type TenantEnvironmentsConfig } from "@nylorun/core/contracts";
7
+ import { DEFAULT_SANDBOX_IDLE } from "./policy.js";
8
+ export { DEFAULT_SANDBOX_IDLE };
7
9
  export declare const ENVIRONMENTS_CONFIG_SETTING = "environments.config";
8
10
  /** The Tenant's settings, as stored (`environments.config`). */
9
11
  export type TenantSandboxConfig = TenantEnvironmentsConfig;
10
12
  /** The most an Environment may ask for when the Tenant sets no maximum. */
11
13
  export declare const DEFAULT_MAX_CPUS = 4;
12
14
  export declare const DEFAULT_MAX_MEMORY_MIB = 8192;
13
- export declare const DEFAULT_SANDBOX_IDLE = "15m";
14
15
  /** The most Environments a Tenant holds when it sets no limit. */
15
16
  export declare const DEFAULT_MAX_ENVIRONMENTS = 100;
16
17
  export interface SandboxResourcesLimit {
@@ -47,4 +48,3 @@ export declare function effectiveSandboxConfig(config: TenantSandboxConfig): Eff
47
48
  /** The stored configuration, or `{}` when the Tenant has none. */
48
49
  export declare function readSandboxConfig(t: SettingsReader): Promise<TenantSandboxConfig>;
49
50
  export declare function writeSandboxConfig(t: SettingsWriter, config: TenantSandboxConfig): Promise<void>;
50
- export {};
@@ -5,17 +5,18 @@
5
5
  */
6
6
  import { TenantEnvironmentsConfigSchema, } from "@nylorun/core/contracts";
7
7
  import { parseSandboxSize } from "@nylorun/core/define";
8
- import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_MEMORY_MIB, DEV_PRESET_HOSTS, } from "./policy.js";
8
+ import { DEFAULT_SANDBOX_CPUS, DEFAULT_SANDBOX_IDLE, DEFAULT_SANDBOX_MEMORY_MIB, DEV_PRESET_HOSTS, } from "./policy.js";
9
+ import { hostTuning } from "../tuning.js";
10
+ export { DEFAULT_SANDBOX_IDLE };
9
11
  export const ENVIRONMENTS_CONFIG_SETTING = "environments.config";
10
12
  /** The most an Environment may ask for when the Tenant sets no maximum. */
11
13
  export const DEFAULT_MAX_CPUS = 4;
12
14
  export const DEFAULT_MAX_MEMORY_MIB = 8192;
13
- export const DEFAULT_SANDBOX_IDLE = "15m";
14
15
  /** The most Environments a Tenant holds when it sets no limit. */
15
16
  export const DEFAULT_MAX_ENVIRONMENTS = 100;
16
17
  export function memoryMiB(value) {
17
18
  const bytes = parseSandboxSize(value);
18
- return bytes === undefined ? 0 : Math.max(128, Math.ceil(bytes / 1024 ** 2));
19
+ return bytes === undefined ? 0 : Math.max(hostTuning().pods.memoryFloorMiB, Math.ceil(bytes / 1024 ** 2));
19
20
  }
20
21
  function resources(value, fallback) {
21
22
  return {
@@ -1,5 +1,6 @@
1
1
  import type { SandboxToolOutcome } from "@nylorun/core/contracts";
2
2
  import { type SandboxToolName } from "@nylorun/core/define";
3
+ import { type SandboxToolLimits } from "@nylorun/harness";
3
4
  import type { SandboxHandle } from "./types.js";
4
5
  export type { SandboxToolOutcome };
5
6
  /** What the Runtime reports about a tool call on the session stream. */
@@ -9,6 +10,11 @@ export interface SandboxToolReport {
9
10
  readonly exitCode?: number;
10
11
  readonly killed?: boolean;
11
12
  }
13
+ /**
14
+ * How long a command the Runtime runs for itself in a sandbox may take (making a file's folder,
15
+ * mounting a skill, a tool's own helper command): `harness.helperCommandTimeoutMs`.
16
+ */
17
+ export declare const helperCommandTimeoutMs: () => number;
12
18
  export declare function quote(value: string): string;
13
19
  /**
14
20
  * An absolute path inside the sandbox. Relative paths are relative to the workspace; on a backend
@@ -20,6 +26,6 @@ export declare function truncateMiddle(value: string, limit?: number): {
20
26
  text: string;
21
27
  truncated: boolean;
22
28
  };
23
- export declare function runSandboxTool(handle: SandboxHandle, name: SandboxToolName, input: Record<string, any>, signal: AbortSignal, report: (value: SandboxToolReport) => void,
29
+ export declare function runSandboxTool(handle: SandboxHandle, name: SandboxToolName, input: Record<string, any>, signal: AbortSignal, report: (value: SandboxToolReport) => void, limits?: SandboxToolLimits,
24
30
  /** Variables every command gets (R2c): secret names set to the sentinel, plain values. */
25
31
  environment?: Readonly<Record<string, string>>): Promise<SandboxToolOutcome>;