@nylorun/runtime 0.23.0-beta → 0.24.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 (252) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +79 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/endpoint.js +5 -1
  10. package/dist/api/a2a/routes.d.ts +2 -0
  11. package/dist/api/a2a/routes.js +2 -2
  12. package/dist/api/a2a/tasks.js +2 -1
  13. package/dist/api/ag-ui/endpoint.js +4 -2
  14. package/dist/api/ag-ui/history.js +4 -1
  15. package/dist/api/ag-ui/routes.d.ts +4 -1
  16. package/dist/api/ag-ui/routes.js +11 -10
  17. package/dist/api/ag-ui/translate.js +3 -2
  18. package/dist/api/components.d.ts +4953 -1204
  19. package/dist/api/components.js +25 -16
  20. package/dist/api/gate/keys.js +7 -1
  21. package/dist/api/gate/routes.d.ts +6 -0
  22. package/dist/api/gate/routes.js +39 -3
  23. package/dist/api/http/app.js +4 -4
  24. package/dist/api/http/define.d.ts +6 -0
  25. package/dist/api/http/define.js +13 -1
  26. package/dist/api/http/respond.js +1 -1
  27. package/dist/api/http/routes/artifacts.js +35 -8
  28. package/dist/api/http/routes/coverage.d.ts +9 -0
  29. package/dist/api/http/routes/coverage.js +34 -0
  30. package/dist/api/http/routes/environments.d.ts +11 -0
  31. package/dist/api/http/routes/environments.js +283 -0
  32. package/dist/api/http/routes/mcp.d.ts +1 -1
  33. package/dist/api/http/routes/mcp.js +2 -2
  34. package/dist/api/http/routes/me.js +1 -1
  35. package/dist/api/http/routes/reads.d.ts +45 -9
  36. package/dist/api/http/routes/reads.js +54 -5
  37. package/dist/api/http/routes/sessions.js +11 -6
  38. package/dist/api/http/routes/tenant.js +32 -10
  39. package/dist/api/openapi.js +31 -26
  40. package/dist/artifacts/disk.d.ts +59 -0
  41. package/dist/artifacts/disk.js +563 -0
  42. package/dist/artifacts/export.d.ts +1 -1
  43. package/dist/artifacts/export.js +2 -3
  44. package/dist/artifacts/folders.d.ts +6 -3
  45. package/dist/artifacts/folders.js +17 -12
  46. package/dist/artifacts/service.d.ts +2 -1
  47. package/dist/artifacts/service.js +6 -1
  48. package/dist/artifacts/workspace.d.ts +1 -1
  49. package/dist/artifacts/workspace.js +1 -1
  50. package/dist/core/flow-host.d.ts +2 -3
  51. package/dist/core/flow-host.js +3 -0
  52. package/dist/core/provider.d.ts +3 -2
  53. package/dist/core/provider.js +5 -37
  54. package/dist/core/sandbox-routes.d.ts +2 -21
  55. package/dist/core/sandbox-routes.js +10 -43
  56. package/dist/execution/types.d.ts +1 -1
  57. package/dist/gates/contract.d.ts +3 -24
  58. package/dist/gates/contract.js +1 -36
  59. package/dist/gates/egress-credentials.d.ts +9 -0
  60. package/dist/gates/egress-credentials.js +11 -4
  61. package/dist/gates/egress.d.ts +28 -1
  62. package/dist/gates/egress.js +115 -10
  63. package/dist/gates/fixture-model.d.ts +28 -0
  64. package/dist/gates/fixture-model.js +92 -0
  65. package/dist/gates/handler.js +4 -4
  66. package/dist/gates/http-tool.d.ts +6 -2
  67. package/dist/gates/http-tool.js +3 -5
  68. package/dist/gates/in-process.d.ts +5 -0
  69. package/dist/gates/in-process.js +12 -1
  70. package/dist/gates/journal.d.ts +30 -0
  71. package/dist/gates/journal.js +52 -0
  72. package/dist/gates/mcp-handler.d.ts +4 -0
  73. package/dist/gates/mcp-handler.js +5 -1
  74. package/dist/gates/meter.d.ts +10 -2
  75. package/dist/gates/meter.js +20 -5
  76. package/dist/gates/policy.d.ts +49 -0
  77. package/dist/gates/policy.js +134 -0
  78. package/dist/gates/tenant-vaults.d.ts +7 -7
  79. package/dist/gates/tenant-vaults.js +5 -5
  80. package/dist/gates/tool-calls.d.ts +10 -5
  81. package/dist/gates/tool-calls.js +28 -10
  82. package/dist/gates/tool-client.d.ts +8 -0
  83. package/dist/gates/tool-client.js +27 -11
  84. package/dist/gates/tool-contract.d.ts +10 -68
  85. package/dist/gates/tool-contract.js +1 -85
  86. package/dist/gates/tool-gate.d.ts +1 -1
  87. package/dist/harness/calls.d.ts +12 -5
  88. package/dist/harness/calls.js +21 -12
  89. package/dist/harness/executors.js +12 -9
  90. package/dist/harness/main.js +18 -5
  91. package/dist/harness/pod.d.ts +34 -0
  92. package/dist/harness/pod.js +41 -0
  93. package/dist/harness/service.d.ts +0 -1
  94. package/dist/harness/service.js +13 -5
  95. package/dist/harness/setup.d.ts +27 -0
  96. package/dist/harness/setup.js +150 -0
  97. package/dist/harness/ws-client.js +3 -2
  98. package/dist/harness/ws-port.d.ts +1 -5
  99. package/dist/harness/ws-port.js +1 -5
  100. package/dist/harness-api/in-process.js +3 -3
  101. package/dist/harness-api/record.d.ts +15 -1
  102. package/dist/harness-api/record.js +174 -44
  103. package/dist/harness-api/redact.d.ts +3 -0
  104. package/dist/harness-api/redact.js +45 -0
  105. package/dist/harness-api/server.d.ts +8 -0
  106. package/dist/harness-api/server.js +93 -13
  107. package/dist/harness-api/start.d.ts +10 -3
  108. package/dist/harness-api/start.js +40 -10
  109. package/dist/harness-api/tool-journal.d.ts +96 -0
  110. package/dist/harness-api/tool-journal.js +102 -0
  111. package/dist/harness-api/tool-status.d.ts +13 -0
  112. package/dist/harness-api/tool-status.js +26 -0
  113. package/dist/harness-api/workspace.d.ts +6 -3
  114. package/dist/harness-api/workspace.js +27 -11
  115. package/dist/harness-api/ws-server.d.ts +11 -3
  116. package/dist/harness-api/ws-server.js +94 -15
  117. package/dist/host/gates.d.ts +2 -1
  118. package/dist/host/gates.js +10 -2
  119. package/dist/host/main.js +54 -5
  120. package/dist/host/operate.js +67 -5
  121. package/dist/host/stack-config.d.ts +64 -0
  122. package/dist/host/stack-config.js +61 -1
  123. package/dist/keys/client.js +4 -1
  124. package/dist/keys/keys.d.ts +5 -5
  125. package/dist/keys/keys.js +5 -7
  126. package/dist/management-openapi.json +999 -2211
  127. package/dist/mcp/connect.d.ts +25 -20
  128. package/dist/mcp/connect.js +29 -30
  129. package/dist/mcp/pool.d.ts +2 -2
  130. package/dist/mcp/pool.js +1 -1
  131. package/dist/mcp/preview.d.ts +3 -3
  132. package/dist/mcp/preview.js +10 -20
  133. package/dist/mcp/snapshot.d.ts +5 -11
  134. package/dist/mcp/snapshot.js +6 -7
  135. package/dist/model/pi-model.js +8 -0
  136. package/dist/openapi.json +6955 -2526
  137. package/dist/reads/types.d.ts +16 -3
  138. package/dist/record/append.d.ts +3 -1
  139. package/dist/record/append.js +1 -0
  140. package/dist/record/envelope.d.ts +5 -1
  141. package/dist/record/envelope.js +7 -3
  142. package/dist/record/environment.d.ts +25 -0
  143. package/dist/record/environment.js +44 -0
  144. package/dist/record/index.d.ts +1 -1
  145. package/dist/record/index.js +1 -1
  146. package/dist/sandbox/disk-fs.d.ts +95 -0
  147. package/dist/sandbox/disk-fs.js +206 -0
  148. package/dist/sandbox/join.d.ts +17 -18
  149. package/dist/sandbox/join.js +42 -15
  150. package/dist/sandbox/manager.d.ts +16 -8
  151. package/dist/sandbox/manager.js +33 -17
  152. package/dist/sandbox/placement.d.ts +13 -12
  153. package/dist/sandbox/placement.js +29 -12
  154. package/dist/sandbox/pods/client.d.ts +6 -1
  155. package/dist/sandbox/pods/lifecycle.d.ts +28 -27
  156. package/dist/sandbox/pods/lifecycle.js +14 -26
  157. package/dist/sandbox/pods/reconcile.js +18 -12
  158. package/dist/sandbox/pods/spec.d.ts +15 -13
  159. package/dist/sandbox/pods/spec.js +16 -20
  160. package/dist/sandbox/records.d.ts +2 -2
  161. package/dist/sandbox/records.js +3 -3
  162. package/dist/sandbox/resolve.d.ts +35 -27
  163. package/dist/sandbox/resolve.js +66 -41
  164. package/dist/sandbox/session-sandbox.js +1 -1
  165. package/dist/sandbox/setup.d.ts +61 -0
  166. package/dist/sandbox/setup.js +123 -0
  167. package/dist/sandbox/share.d.ts +11 -21
  168. package/dist/sandbox/share.js +12 -41
  169. package/dist/sandbox/tenant-config.d.ts +12 -14
  170. package/dist/sandbox/tenant-config.js +12 -17
  171. package/dist/sandbox/types.d.ts +6 -0
  172. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  173. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  174. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  175. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  176. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  177. package/dist/store/postgres/reads.js +117 -17
  178. package/dist/store/postgres/record-writer.d.ts +4 -4
  179. package/dist/store/postgres/record-writer.js +9 -9
  180. package/dist/store/postgres/record.d.ts +7 -0
  181. package/dist/store/postgres/record.js +23 -2
  182. package/dist/store/postgres/schema.d.ts +934 -761
  183. package/dist/store/postgres/schema.js +102 -73
  184. package/dist/store/postgres/store.js +119 -66
  185. package/dist/store/types.d.ts +109 -48
  186. package/dist/tenant/advance.d.ts +7 -6
  187. package/dist/tenant/advance.js +115 -42
  188. package/dist/tenant/artifact-tool.js +3 -4
  189. package/dist/tenant/commands.d.ts +15 -2
  190. package/dist/tenant/commands.js +93 -12
  191. package/dist/tenant/context.d.ts +48 -22
  192. package/dist/tenant/context.js +4 -15
  193. package/dist/tenant/effects.d.ts +1 -1
  194. package/dist/tenant/effects.js +37 -20
  195. package/dist/tenant/environments.d.ts +167 -0
  196. package/dist/tenant/environments.js +823 -0
  197. package/dist/tenant/harness-tokens.d.ts +21 -0
  198. package/dist/tenant/harness-tokens.js +61 -0
  199. package/dist/tenant/host-token.d.ts +1 -1
  200. package/dist/tenant/host-token.js +2 -4
  201. package/dist/tenant/identity-file.d.ts +4 -4
  202. package/dist/tenant/identity-file.js +17 -8
  203. package/dist/tenant/issuers.js +3 -3
  204. package/dist/tenant/principals.js +1 -1
  205. package/dist/tenant/reset.js +3 -3
  206. package/dist/tenant/runtime.d.ts +6 -1
  207. package/dist/tenant/runtime.js +7 -1
  208. package/dist/tenant/session.d.ts +3 -3
  209. package/dist/tenant/session.js +0 -14
  210. package/dist/tenant/sessions.d.ts +13 -13
  211. package/dist/tenant/sessions.js +61 -138
  212. package/dist/tenant/signing-keys.d.ts +5 -1
  213. package/dist/tenant/status.js +3 -3
  214. package/dist/tenant/sweep.js +12 -6
  215. package/dist/tenant/transcript.d.ts +210 -20
  216. package/dist/tenant/transcript.js +62 -11
  217. package/dist/tenant/turn-timing.d.ts +43 -0
  218. package/dist/tenant/turn-timing.js +39 -0
  219. package/dist/tenant/types.d.ts +2 -0
  220. package/dist/traces/ids.d.ts +42 -0
  221. package/dist/traces/ids.js +85 -0
  222. package/dist/traces/key.d.ts +6 -0
  223. package/dist/traces/key.js +37 -0
  224. package/dist/traces/map.d.ts +113 -0
  225. package/dist/traces/map.js +317 -0
  226. package/dist/traces/options.d.ts +7 -0
  227. package/dist/traces/options.js +1 -0
  228. package/dist/traces/otlp.d.ts +42 -0
  229. package/dist/traces/otlp.js +84 -0
  230. package/dist/traces/projector.d.ts +58 -0
  231. package/dist/traces/projector.js +200 -0
  232. package/dist/traces/read.d.ts +23 -0
  233. package/dist/traces/read.js +16 -0
  234. package/dist/vault/coverage.d.ts +26 -0
  235. package/dist/vault/coverage.js +64 -0
  236. package/dist/vault/error.d.ts +11 -1
  237. package/dist/vault/error.js +5 -1
  238. package/dist/vault/service.d.ts +47 -48
  239. package/dist/vault/service.js +321 -382
  240. package/dist/vault/sources.d.ts +5 -16
  241. package/dist/vault/sources.js +6 -8
  242. package/dist/version.d.ts +1 -1
  243. package/dist/version.js +2 -2
  244. package/package.json +13 -13
  245. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  246. package/dist/api/http/routes/sandboxes.js +0 -153
  247. package/dist/api/http/routes/vaults.d.ts +0 -10
  248. package/dist/api/http/routes/vaults.js +0 -139
  249. package/dist/record/sandbox.d.ts +0 -25
  250. package/dist/record/sandbox.js +0 -43
  251. package/dist/tenant/sandboxes.d.ts +0 -123
  252. package/dist/tenant/sandboxes.js +0 -566
@@ -1,26 +1,25 @@
1
1
  /**
2
- * Session and definition resources: `PUT/GET /v1/agents`, `PUT /v1/sessions/:id` (create or
3
- * re-attach vaults), the session list and the session view. Reads use the store's typed
4
- * queries; `PUT /v1/sessions/:id` runs in one transaction with the vault attachment checks.
2
+ * Session and definition resources: `PUT/GET /v1/agents`, `PUT /v1/sessions/:id` (create, or
3
+ * find an identical one), the session list and the session view. Reads use the store's typed
4
+ * queries; `PUT /v1/sessions/:id` runs in one transaction with the Environment's checks.
5
5
  */
6
6
  import { DefinitionDocumentSchema, definitionFilesOf, } from "@nylorun/core/contracts";
7
7
  import { hashManifest } from "@nylorun/core/compatibility";
8
- import { codeToolRefusal } from "@nylorun/core/define";
8
+ import { approvalRefusal, codeToolRefusal } from "@nylorun/core/define";
9
9
  import { aggregateWaits, isWorkflowManifest } from "../core/flow-host.js";
10
10
  import { canonical } from "../store/canonical.js";
11
- import { validateSandboxAttach } from "../core/sandbox-routes.js";
12
- import { resolveSandbox } from "../sandbox/resolve.js";
11
+ import { sandboxSpecOf } from "../sandbox/resolve.js";
13
12
  import { withPlatformTools, withSandboxCapability } from "../sandbox/session-sandbox.js";
14
- import { declaredSandboxes, sandboxSpecOf, sessionSandboxSpec, } from "../sandbox/share.js";
13
+ import { declaredSandboxes } from "../sandbox/share.js";
15
14
  import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
16
- import { checkPlacement, requirePods } from "../sandbox/placement.js";
17
- import { attachSandbox, recordAttachment } from "./sandboxes.js";
18
- import { sandboxLookup, } from "./context.js";
15
+ import { HARNESS_ID, checkPlacement, requirePods } from "../sandbox/placement.js";
16
+ import { openEnvironment, recordAttachment } from "./environments.js";
17
+ import { harnessOf, } from "./context.js";
19
18
  import { fail } from "./http.js";
20
19
  import { missingDefinitionFiles } from "./definition-files.js";
21
- /** A session's creation identity: everything but the request id and vault attachments. */
20
+ /** A session's creation identity: everything but the request id. */
22
21
  const sessionIdentity = (value) => {
23
- const { requestId: _requestId, vaultIds: _vaultIds, credentialSelections: _selections, ...body } = value ?? {};
22
+ const { requestId: _requestId, ...body } = value ?? {};
24
23
  return canonical(body);
25
24
  };
26
25
  export async function listDefinitions(ctx) {
@@ -56,13 +55,17 @@ export async function putDefinition(ctx, agentId, body) {
56
55
  if (body.manifest.id !== agentId)
57
56
  fail(400, "Agent id mismatch");
58
57
  DefinitionDocumentSchema.parse(body.manifest);
59
- // The Runtime runs no code of yours during a session (track R2): no tool may need it.
58
+ // The Runtime runs agents from their manifests alone (track R2): no tool may need it.
60
59
  const code = codeToolRefusal(body.manifest);
61
60
  if (code)
62
61
  fail(400, code);
62
+ // Tool approvals are the Tenant's tool policy, decided at the Tool Gate (R5 step 2).
63
+ const approval = approvalRefusal(body.manifest);
64
+ if (approval)
65
+ fail(400, approval);
63
66
  const declared = declaredSandboxes(body.manifest);
64
67
  if (declared.length > 0)
65
- fail(400, `${declared.join(", ")} declares a sandbox. Agents no longer declare one: remove .sandbox() and open the session with it, createSession({ sandbox: { image, network: { allow }, resources } }), or set the Tenant's default sandbox. See MIGRATION.md.`);
68
+ fail(400, `${declared.join(", ")} declares a sandbox. Agents no longer declare one: remove .sandbox(), create an Environment with the sandbox (PUT /v1/environments/{id}) and open the session on it, createSession({ environment }). See MIGRATION.md.`);
66
69
  const definition = {
67
70
  ...body,
68
71
  manifestHash: hashManifest(body.manifest),
@@ -104,18 +107,16 @@ export async function listSessions(ctx, agentId, access) {
104
107
  };
105
108
  }
106
109
  /**
107
- * Create a session from its definition, or re-attach vaults to an identical one. `owner`, when
108
- * the request acts for a subject, must be the session's owner: another owner's session id is
109
- * the 404 of a missing one (not the 409 of a mismatch), and so are another owner's vaults and
110
- * sandboxes.
110
+ * Create a session from its definition, or find an identical one. `access`, when the request
111
+ * acts for a subject, must be the session's owner: another owner's session id is the 404 of a
112
+ * missing one (not the 409 of a mismatch), and so is an Environment whose owner guard names
113
+ * another owner.
111
114
  *
112
- * The session's sandbox is decided here and fixed for its life (§5 of Sandboxes v3): shared
113
- * with `{ session }`, none with `false`, inline, or the Tenant default when omitted. A definition
114
- * that declares its own sandbox (`.sandbox()`) keeps it and takes no sandbox request.
115
+ * The session's Environment is fixed for its life: its sandbox (kinds `virtual` and `pod`) is
116
+ * the session's, with the spec of the Environment's applied recipe pinned now, and its vault
117
+ * holds the session's credentials. Without one the session has no sandbox and no credentials.
115
118
  */
116
119
  export async function putSession(ctx, id, body, access, options = {}) {
117
- const vaultIds = body.vaultIds ?? [];
118
- const credentialSelections = body.credentialSelections ?? [];
119
120
  const opaque = access !== undefined;
120
121
  if (opaque && body.ownerUserId !== access.owner)
121
122
  fail(403, "ownerUserId must be the subject");
@@ -135,35 +136,34 @@ export async function putSession(ctx, id, body, access, options = {}) {
135
136
  fail(404, "Session not found");
136
137
  return prior;
137
138
  }
138
- await ctx.vault.assertAttachment(t, body.ownerUserId, vaultIds, credentialSelections, { opaque });
139
- const environment = await ctx.vault.sessionEnvironment(t, vaultIds);
140
- if (environment.conflict)
141
- fail(409, environment.conflict, { code: "credential_conflict" });
142
139
  if (prior) {
143
- if (prior.sandboxId !== undefined)
144
- await assertSandboxSecrets(ctx, t, prior.sandboxId, id, body.ownerUserId, environment.secretHosts);
145
140
  if (sessionIdentity(prior.creation) !== sessionIdentity(body))
146
141
  fail(409, "Session already exists with different creation parameters");
147
- prior.vaultIds = vaultIds;
148
- prior.credentialSelections = credentialSelections;
149
- prior.creation = body;
150
- await t.put("sessions", id, prior);
151
- await ctx.vault.recordAttachment(t, id, vaultIds);
152
142
  return prior;
153
143
  }
154
144
  const definition = (await t.get("definitions", body.agentId)) ??
155
145
  fail(404, "Definition not found");
156
- const sandbox = await sessionSandbox(t, body, definition, {
157
- opaque,
158
- ...(options.sandboxGrants === undefined ? {} : { grants: options.sandboxGrants }),
159
- });
160
- // Placement (D38): where this session's harness may run, decided now and kept.
161
- checkPlacement(await readSandboxConfig(t), sandbox.kind);
162
- if (sandbox.kind === "pod")
163
- await requirePods(ctx.pods, `Sandbox ${sandbox.sandboxId}`);
164
- if (sandbox.sandboxId !== undefined)
165
- await assertSandboxSecrets(ctx, t, sandbox.sandboxId, id, body.ownerUserId, environment.secretHosts);
166
- const pinned = sandbox.manifest === undefined ? platformTools(definition) : sandbox;
146
+ const environment = body.environment === undefined
147
+ ? undefined
148
+ : await openEnvironment(t, body.environment, {
149
+ ...(options.environmentGrants === undefined ? {} : { grants: options.environmentGrants }),
150
+ ownerUserId: body.ownerUserId,
151
+ opaque,
152
+ });
153
+ const config = await readSandboxConfig(t);
154
+ // The harness the session runs for its life (R4.1): its Environment's, else ours. A flow
155
+ // runs in core and our engine, whatever its Environment names.
156
+ const harness = isWorkflowManifest(definition.manifest) || environment === undefined
157
+ ? HARNESS_ID
158
+ : environment.applied.harness;
159
+ // Placement (D38, D52): where this session's harness may run, decided now and kept.
160
+ checkPlacement(config, environment?.kind, harness);
161
+ if (environment?.kind === "pod")
162
+ await requirePods(ctx.pods, `Environment ${environment.id}`);
163
+ const spec = environment === undefined || environment.kind === "none"
164
+ ? undefined
165
+ : sandboxSpecOf(environment.applied, effectiveSandboxConfig(config).limits.idle);
166
+ const pinned = spec === undefined ? platformTools(definition) : pin(definition, spec);
167
167
  const created = {
168
168
  id,
169
169
  agentId: body.agentId,
@@ -176,90 +176,18 @@ export async function putSession(ctx, id, body, access, options = {}) {
176
176
  status: "idle",
177
177
  activeTurnId: null,
178
178
  creation: body,
179
- vaultIds,
180
- credentialSelections,
181
- ...(sandbox.sandboxOwnerId !== undefined
182
- ? { sandboxOwnerId: sandbox.sandboxOwnerId }
183
- : {}),
184
- ...(sandbox.sandboxId !== undefined ? { sandboxId: sandbox.sandboxId } : {}),
185
- ...(sandbox.spec !== undefined
186
- ? { sandbox: sandbox.spec, sandboxSource: sandbox.source }
187
- : {}),
179
+ harness,
180
+ ...(environment === undefined
181
+ ? {}
182
+ : { environmentId: environment.id, environmentVersion: environment.version }),
183
+ ...(spec === undefined ? {} : { sandbox: spec }),
188
184
  };
189
185
  await t.put("sessions", id, created);
190
- await ctx.vault.recordAttachment(t, id, vaultIds);
191
- if (sandbox.sandboxId !== undefined)
192
- await recordAttachment(t, id, sandbox.sandboxId);
186
+ if (environment !== undefined)
187
+ await recordAttachment(t, id, environment.id);
193
188
  return created;
194
189
  });
195
190
  }
196
- /**
197
- * A sandbox's shell secrets must be unambiguous (R2c, D50): egress-gate sets the header from the
198
- * one `environment_secret` bound to a host among the sessions using the sandbox. So while any of
199
- * them has secrets, they all have one owner, and no host is bound to two credentials.
200
- */
201
- async function assertSandboxSecrets(ctx, t, sandboxId, sessionId, ownerUserId, secretHosts) {
202
- const conflict = (message) => fail(409, message, { code: "credential_conflict" });
203
- const mine = Object.keys(secretHosts).length > 0;
204
- for (const other of await t.sessionsOnSandbox(sandboxId)) {
205
- if (other.id === sessionId)
206
- continue;
207
- const theirs = (await ctx.vault.sessionEnvironment(t, other.vaultIds ?? [])).secretHosts;
208
- if (other.ownerUserId !== ownerUserId && (mine || Object.keys(theirs).length > 0))
209
- conflict(`Sandbox ${sandboxId} serves environment_secret credentials of one owner at a time`);
210
- for (const [host, credentialId] of Object.entries(secretHosts))
211
- if (theirs[host] !== undefined && theirs[host] !== credentialId)
212
- conflict(`Two environment_secret credentials would be bound to ${host} in sandbox ${sandboxId}`);
213
- }
214
- }
215
- function isShare(value) {
216
- return typeof value === "object" && value !== null && "session" in value;
217
- }
218
- function isAttach(value) {
219
- return typeof value === "object" && value !== null && "id" in value;
220
- }
221
- /** Decide a new session's sandbox and, for an agent, the manifest that carries its tools. */
222
- async function sessionSandbox(t, body, definition, options) {
223
- const request = body.sandbox;
224
- const declared = sandboxSpecOf(definition.manifest) !== undefined;
225
- if (isAttach(request)) {
226
- if (declared)
227
- fail(400, `'${body.agentId}' declares its own sandbox with .sandbox(). Remove it from the agent to attach a sandbox.`);
228
- const sandbox = await attachSandbox(t, request.id, options.grants);
229
- // A pod's storage and lifecycle are the resource's, not the agent's sandbox manifest.
230
- const { storage: _storage, lifecycle: _lifecycle, ...spec } = sandbox.spec;
231
- return { ...(await pin(definition, spec, "sandbox")), sandboxId: sandbox.id, kind: sandbox.kind };
232
- }
233
- if (isShare(request)) {
234
- const owner = await t.get("sessions", request.session);
235
- if (owner?.sandboxId !== undefined && (!options.opaque || owner.ownerUserId === body.ownerUserId))
236
- fail(400, `Session ${request.session} is attached to sandbox ${owner.sandboxId}: open this session with sandbox: { id: "${owner.sandboxId}" }.`);
237
- const lookup = await sandboxLookup(t, request.session);
238
- const sandboxOwnerId = validateSandboxAttach(body, definition.manifest, lookup, {
239
- opaque: options.opaque,
240
- inherit: !declared,
241
- });
242
- if (declared || sandboxOwnerId === undefined)
243
- return { sandboxOwnerId };
244
- const spec = sessionSandboxSpec(lookup(request.session));
245
- return { ...(await pin(definition, spec, "shared")), sandboxOwnerId };
246
- }
247
- if (declared) {
248
- if (request !== undefined)
249
- fail(400, `'${body.agentId}' declares its own sandbox with .sandbox(). Remove it from the agent to choose the sandbox when opening the session, or omit sandbox here.`);
250
- return {};
251
- }
252
- const resolved = resolveSandbox({
253
- request,
254
- config: effectiveSandboxConfig(await readSandboxConfig(t)),
255
- actingForSubject: options.opaque,
256
- });
257
- if (resolved.kind === "none")
258
- return {};
259
- if (resolved.kind === "error")
260
- return fail(resolved.status, resolved.errors.join(" "));
261
- return pin(definition, resolved.spec, resolved.source);
262
- }
263
191
  /**
264
192
  * The manifest a session without a sandbox pins: its agents' `read_artifact` (R2b C11), when
265
193
  * one has a remote MCP server or an HTTP tool; nothing otherwise, and for a workflow.
@@ -274,19 +202,15 @@ function platformTools(definition) {
274
202
  return fail(400, pinned.message);
275
203
  return { manifest: pinned.manifest, manifestHash: pinned.manifestHash };
276
204
  }
277
- async function pin(definition, spec, source) {
205
+ /** The manifest a session with a sandbox pins: the definition with the sandbox capability. */
206
+ function pin(definition, spec) {
278
207
  // A workflow's manifest stays as registered: its agents inherit the sandbox when they start.
279
208
  if (isWorkflowManifest(definition.manifest))
280
- return { spec, source };
209
+ return {};
281
210
  const sandboxed = withSandboxCapability(definition.manifest, spec);
282
211
  if (!sandboxed.ok)
283
212
  return fail(400, sandboxed.message);
284
- return {
285
- spec,
286
- source,
287
- manifest: sandboxed.manifest,
288
- manifestHash: sandboxed.manifestHash,
289
- };
213
+ return { manifest: sandboxed.manifest, manifestHash: sandboxed.manifestHash };
290
214
  }
291
215
  /** The session resource returned by `GET`/`PUT /v1/sessions/:id`. */
292
216
  export async function sessionView(t, s) {
@@ -315,12 +239,11 @@ export async function sessionView(t, s) {
315
239
  implementationVersion: s.implementationVersion,
316
240
  status: s.status,
317
241
  activeTurnId: s.activeTurnId,
318
- vaultIds: s.vaultIds ?? [],
319
- credentialSelections: s.credentialSelections ?? [],
320
- sandboxOwnerId: s.sandboxOwnerId ?? null,
321
- ...(s.sandboxId === undefined ? {} : { sandboxId: s.sandboxId }),
322
- sandbox: s.sandbox ?? null,
323
- ...(s.sandboxSource ? { sandboxSource: s.sandboxSource } : {}),
242
+ environment: s.environmentId === undefined
243
+ ? null
244
+ : { id: s.environmentId, version: s.environmentVersion ?? 1 },
245
+ harness: harnessOf(s),
246
+ ...(s.closedReason === undefined ? {} : { closedReason: s.closedReason }),
324
247
  mcpSnapshot: s.mcpSnapshot ?? null,
325
248
  mcpDiagnostics: s.mcpDiagnostics ?? [],
326
249
  waits,
@@ -19,7 +19,11 @@ import type { SigningKeyRow, Tx } from "../store/types.js";
19
19
  type KeyLike = Awaited<ReturnType<typeof importJWK>>;
20
20
  export interface SigningKeysOptions {
21
21
  tenantId: string;
22
- /** The vault KEK, created on first use. Resolve it before opening a transaction. */
22
+ /**
23
+ * The vault KEK. The gateway's keys service only reads the file `nylorun start` wrote
24
+ * (`gates/tenant-vaults.ts`); an in-process Runtime may create it on first use. Resolve it
25
+ * before opening a transaction.
26
+ */
23
27
  kek(): Buffer;
24
28
  }
25
29
  export interface CurrentKey {
@@ -1,5 +1,5 @@
1
1
  import { FIXTURE_MODEL_SETTING, seedFixtureModel } from "./model-setting.js";
2
- import { SANDBOX_CONFIG_SETTING, writeSandboxConfig } from "../sandbox/tenant-config.js";
2
+ import { ENVIRONMENTS_CONFIG_SETTING, writeSandboxConfig } from "../sandbox/tenant-config.js";
3
3
  /** The Tenant's harnesses, for its status and the Host's. */
4
4
  export function harnessStatusOf(ctx) {
5
5
  return { mode: ctx.mcp ? "in-process" : "remote", ...ctx.harness.status() };
@@ -108,12 +108,12 @@ export async function seedTenantConfig(ctx, body) {
108
108
  const sandboxConfig = body.sandbox?.config;
109
109
  if (sandboxConfig) {
110
110
  const inserted = await ctx.store.tx(async (t) => {
111
- if ((await t.getSetting(SANDBOX_CONFIG_SETTING)) !== undefined)
111
+ if ((await t.getSetting(ENVIRONMENTS_CONFIG_SETTING)) !== undefined)
112
112
  return false;
113
113
  await writeSandboxConfig(t, sandboxConfig);
114
114
  return true;
115
115
  });
116
- (inserted ? applied : kept).push(SANDBOX_CONFIG_SETTING);
116
+ (inserted ? applied : kept).push(ENVIRONMENTS_CONFIG_SETTING);
117
117
  }
118
118
  if (body.fixtureModel) {
119
119
  const inserted = await ctx.store.tx((t) => seedFixtureModel(t));
@@ -28,10 +28,12 @@
28
28
  * for a Tenant not open on its Worker, an invocation an operator killed, Restate's state
29
29
  * wiped (§17.11), or a parked outbox row.
30
30
  * 4. **Sandboxes.** Idle sandboxes are stopped, records of compute no longer held are marked
31
- * stopped, and sandboxes whose session or sandbox resource is gone are removed, by the
31
+ * stopped, and sandboxes whose session or Environment is gone are removed, by the
32
32
  * workspace capability (`harness-api/workspace.ts`: here, or in the harness that serves
33
33
  * workspaces). Idle MCP connections of the in-process harness are closed. As often as the
34
- * orphan scan, pod sandboxes whose change stalled get a reconcile (`reconcileStalePods`).
34
+ * orphan scan, pod Environments whose change stalled get a reconcile (`reconcileStalePods`).
35
+ * Environments unused for their `lifecycle.deleteAfter` are deleted
36
+ * (`sweepUnusedEnvironments`).
35
37
  * 5. **Control signals.** Signals on the control bus older than `SIGNAL_RETENTION_MS` are
36
38
  * deleted: followers read back only the last two minutes (`store/postgres/control.ts`).
37
39
  * 6. **Hooks.** Callbacks registered with `ctx.onSweep`.
@@ -41,6 +43,8 @@
41
43
  * step does not stop the others; the first error is rethrown at the end so the execution
42
44
  * reports it.
43
45
  */
46
+ import { sweepUnusedEnvironments } from "./environments.js";
47
+ import { sweepPendingSeeds } from "../artifacts/disk.js";
44
48
  import { pendingAgentEffects, reconcilePendingAgentEffect, } from "../core/flow-host.js";
45
49
  const BATCH = 100;
46
50
  /** How long a control signal is kept after it was written. */
@@ -101,7 +105,7 @@ export async function sweep(ctx) {
101
105
  () => ctx.sandbox.sweep({
102
106
  now: now.getTime(),
103
107
  sessionExists: async (id) => !!(await ctx.store.tx((t) => t.get("sessions", id))),
104
- sandboxExists: async (id) => (await ctx.store.tx((t) => t.sandboxResource(id))) !== undefined,
108
+ sandboxExists: async (id) => (await ctx.store.tx((t) => t.environment(id))) !== undefined,
105
109
  }),
106
110
  ],
107
111
  ...(checkPods
@@ -115,6 +119,10 @@ export async function sweep(ctx) {
115
119
  ],
116
120
  ]
117
121
  : []),
122
+ // Environments unused for their `lifecycle.deleteAfter`: one query when none is due.
123
+ ["environments", () => sweepUnusedEnvironments(ctx, now.getTime())],
124
+ // Seeds of new volumes whose engine is connected and no process copies (R2d step 3).
125
+ ["seeds", () => sweepPendingSeeds(ctx)],
118
126
  // A harness elsewhere closes its own idle MCP connections.
119
127
  ["mcp", async () => ctx.mcp?.sweep()],
120
128
  [
@@ -257,16 +265,14 @@ const STALE_POD_MS = 60_000;
257
265
  async function reconcileStalePods(ctx, now) {
258
266
  if (!ctx.pods)
259
267
  return;
260
- const stale = await ctx.store.tx(async (t) => (await t.listSandboxResources()).filter((row) => {
268
+ const stale = await ctx.store.tx(async (t) => (await t.listEnvironments()).filter((row) => {
261
269
  const pod = row.pod;
262
270
  if (!pod || now.getTime() - Date.parse(row.updatedAt) < STALE_POD_MS)
263
271
  return false;
264
- const expiring = pod.expiresAt !== undefined && Date.parse(pod.expiresAt) <= now.getTime() && pod.observed !== "expired";
265
272
  return (pod.desired === "deleted" ||
266
273
  pod.retiring !== undefined ||
267
274
  pod.observed === "creating" ||
268
275
  pod.observed === "deleting" ||
269
- expiring ||
270
276
  (pod.desired === "suspended" && pod.observed !== "suspended") ||
271
277
  // Running, but its engine is not connected: the pod may be gone, or its volume.
272
278
  (pod.desired === "running" && pod.observed === "running" && !ctx.harness.hosting(row.id)));