@nylorun/runtime 0.23.0-beta → 0.25.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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -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/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
package/CHANGELOG.md CHANGED
@@ -1,5 +1,257 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.25.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 3a32a57: **Files by hash and images by digest (Q6, Q14).** An Environment's `files` entry may name files by content hash, and a pod Environment runs its image by digest once a pod ran it.
8
+
9
+ - **Files by hash.** `{ path, files: { "<relative path>": "sha256:<hex>" } }` copies each definition file the Tenant holds (`PUT /v1/files/sha256:<hex>`, at most 10 MiB each, at most 1000 per entry) under `path`, before `setup`. A file the Tenant does not hold is refused when the manifest is saved or the Environment made. The engine reads them as a folder through `host/files/{index}`. `@nylorun/agents`: `environmentFiles(path, contents)` hashes them, and `manifests.save(manifest, { files })` and `plan()` upload those the Runtime lacks.
10
+ - **Images by digest.** The sandboxes service reports the workload container's `imageID`; the first time a pod is ready the Runtime records it (`pod.imageDigest` in the view) and every later apply runs that digest, so a stop, relaunch or reset never pulls another image under the same tag. A fork pulls afresh. Migration 0027.
11
+ - **Studio** lists files by hash on the Environment page.
12
+
13
+ - af90d1e: **Forks: how an Environment gets a new manifest version (Q15, Q17).** `POST /v1/environments/{id}/fork` makes a new Environment `id` from a manifest version (default the source's manifest at its latest), its disk started from a version of the source's (`disk`: `latest`, the default, snapshots first; a version; or `empty`) and the secrets the new manifest declares copied inside the Runtime (`secrets: "none"` copies none): each value is unsealed and sealed again under the new vault, with the slot's binding. The source keeps what it runs, its disk and its sessions, and gets `environment.forked`; the new Environment's view has `forkedFrom`. `dryRun: true` answers the plan: the disk, the secrets copied and left behind, required slots still missing, the changes and the sessions that stay. Migration 0026.
14
+
15
+ - **Setup on a forked disk.** A pod fork onto other setup runs the new version's setup on the copied disk before its first turn: the engine connects first (the join answer's `afterSeed`), core writes the disk version through it, then the engine runs the setup, and turns wait until it reports it done. A fork onto the same setup, a restore and a lost-volume recovery skip it, as before.
16
+ - `@nylorun/core`: `ForkEnvironmentRequestSchema`, `EnvironmentForkPlanSchema`, `ForkEnvironmentResponseSchema`, `EnvironmentView.forkedFrom`, the `environment.forked` event and `forkedFrom` on `environment.created`.
17
+ - `@nylorun/agents`: `client.environments.fork(id, { id, manifest?, disk?, secrets?, labels?, ownerUserId? })` and `planFork()`.
18
+
19
+ - 104f24f: **Environment manifests, and Environments that never change (protocol 14, D63 to D65).** An Environment's definition can now be saved like an agent: a named, versioned environment manifest, written as a JSON file with a published schema and checked with a plan before it is saved. An Environment is made from one version and keeps it for life; a save changes no Environment. The Runtime speaks protocol 14 only; sessions, Environments and vaults carry over. See MIGRATION.md.
20
+
21
+ - **Environment manifests.** `PUT /v1/environment-manifests/{name}` saves the next version, or answers the latest `unchanged`; `If-Match: <version>` refuses a save over a newer one (`412 manifest_changed`), and `?dryRun=true` answers the plan (changes, errors, warnings, the Environments behind) and saves nothing. Each version records `savedBy`. `GET` (latest), `GET /v1/environment-manifests`, `.../versions`, `.../versions/{n}` and `DELETE` (refused while an Environment names it, `409 manifest_in_use`). Migration 0025.
22
+ - **Pinned Environments.** `PUT /v1/environments/{id}` takes `manifest: "name"` (the latest at creation) or `"name@n"`. On an existing Environment only `labels` and `ownerUserId` may change; fields that match are accepted, and anything else is `409 environment_immutable` with `details.fields`. The view's `recipe` is now `manifest`, with `manifestName`, `manifestVersion`, `manifestHash` and `slots`; `version` and `appliedVersion` no longer move. `environment.created` names the manifest.
23
+ - **Secret slots.** A manifest's (or inline) `secrets` declares each secret's name, type, binding, `required` and `description`, never a value. On such an Environment a secret write sends only `{ value }` (or `{ headers }`), the binding comes from the slot, and an undeclared name is `409 secret_not_in_manifest`.
24
+ - **`requires`.** Agent manifest v7 takes `requires: { sandbox?: "none" | "any" | "pod", secrets? }` (`.requires()` in `@nylorun/core/define`), checked when a session opens (`409 environment_unsuitable`, `details.missing`). Turn variants keep it.
25
+ - **Dry runs.** `PUT /v1/agents/{id}?dryRun=true` checks a definition and saves nothing (`dryRun: true` in the answer).
26
+ - `@nylorun/core`: `PROTOCOL_VERSION` 14, `HOST_PROTOCOL` `{ min: 14, max: 14 }`, required feature `environment-manifests`; `EnvironmentManifestSchema`, `EnvironmentSecretSlotSchema`, `parseEnvironmentManifestRef` and the view, plan and save schemas; JSON Schemas at `@nylorun/core/schemas/environment-manifest.json` and `@nylorun/core/schemas/agent-manifest.json` (`$id` `https://docs.nylorun.com/schemas/environment-manifest/v1.json` and `.../agent-manifest/v7.json`).
27
+ - `@nylorun/agents`: `client.environments.manifests` (`save`, `plan`, `get`, `list`, `versions`, `delete`), `environmentManifest()`, `EnvironmentInput` (was `EnvironmentRecipeInput`), value-only `secrets.put`, and `saveAgent(agent, { dryRun })`.
28
+ - **Studio** edits only an Environment's labels and owner guard, shows the manifest it was made from, and its setup and files are read-only.
29
+ - `nylorun environments put` help says what it runs never changes.
30
+
31
+ - c96b9a8: **An agent manifest is flat: manifest v7, a `runtime` block the Runtime writes, and tools known by name (protocol 13, Harness API 3).** A manifest listed capabilities, each with its own instructions, tools, skills and servers, and the Runtime added its own as more capabilities. Now the agent's parts sit at the top level, a plugin's skills and servers say which plugin brought them, and what the Runtime adds to a session's agent is one `runtime` block. A fresh start: the Runtime speaks protocol 13 only, so save your agents again; sessions from protocol 12 do not carry over. See MIGRATION.md.
32
+
33
+ - **Manifest v7.** `{ manifestSchemaVersion: 7, id, name?, description?, metadata?, outputSchema?, instructions?, tools?, skills?, mcpServers?, runtime? }`. Versions 3 to 6 and a `capabilities` field are refused with a message to rebuild. `SkillManifest.plugin` and `McpServerManifest.plugin` are `{ name, version? }`, set by `.plugin(dir)` from `plugin.json`.
34
+ - **The `runtime` block.** `{ sandbox?, tools?, instructions? }`, written only by the Runtime when a session pins the agent: the sandbox spec and tools, `save_artifact`, `read_artifact`, `load_skill` and `read_skill_resource`, the skill catalog and the notes that go with them. A definition or a turn variant that sets it is refused. `runtimeManifestFor` in `@nylorun/core/define` replaces `sandboxCapabilityManifest`, `artifactsCapabilityManifest` and `toolsCapabilityManifest`. `.skills()` and `.plugin()` no longer add the skill tools or the catalog.
35
+ - **Names.** An agent's tool may not take a Runtime tool's name (`RUNTIME_TOOL_NAMES`) or contain `__`, and names are unique across tools, skills and servers.
36
+ - **Turn variants** may remove tools, tighten MCP servers and change instructions, name, description and metadata. Skills are fixed, and a variant never sets `runtime` or changes `outputSchema`.
37
+ - **Tools by name.** `capabilityId` is gone from `tool.completed`, `mcp.discovered`, tool call rows, `EffectIntent`, gate requests, checkpoints and `SessionToolRef`. An HTTP tool's `server` is its URL's host. `Implementations` is `{ tools }`, keyed by tool name (`Agent.from(json, { tools })`), `AgentBinding.declarations` is `parts`, and observe events say `partId`.
38
+ - **Harness API 3.** `hello.capabilities` is `features`, and the `harness` pseudo-capability is gone: a native tool is known by its name, and one whose name is taken fails the turn at its start. `createHarness({ features })` in `@nylorun/harness`. `examples/harness-minimal` speaks it.
39
+ - **Studio.** The Agent tab shows instructions, tools (HTTP and agent badges), skills and MCP servers, each skill and server with its plugin's badge and a filter by plugin, and a session's Runtime section: its sandbox, tools and instructions.
40
+ - `@nylorun/core`: `PROTOCOL_VERSION` 13, `HOST_PROTOCOL` `{ min: 13, max: 13 }`, required feature `flat-manifest`, `HARNESS_API_VERSION` 3; `PluginLabel`, `RuntimeManifest`, `RUNTIME_TOOL_NAMES`, `isRuntimeToolName`, `runtimeToolOf`, `isSkillTool`, `SKILLS_USAGE` and `formatSkillCatalog` in `/define`.
41
+ - `@nylorun/agents`, `@nylorun/admin`, `@nylorun/studio` and `nylorun` send protocol 13.
42
+
43
+ - 94bf329: **Each model says what it reads, a file it does not read is refused before a turn starts, and a file is an artifact the model reads again after its turn (protocol 12).** A message could name any file, and a model that could not read it failed the turn at its model call; every later turn sent the file's bytes again. Now the Runtime knows what the Tenant's model reads, checks a message's files when it is accepted, and sends a file's bytes only in the turn that brought it. Protocol 12 only adds to 11: the Runtime serves both.
44
+
45
+ - **Model inputs.** A model reads `text`, and `image`, `pdf`, `office`, `audio` or `video` when its catalog says so or the operator adds it where its API carries it natively (images everywhere; PDFs on the Anthropic, OpenAI, Azure and Google APIs; Office documents on the OpenAI Responses APIs; audio and video on Google's APIs and OpenAI-compatible completions). `GET /v1/tenant/models` gives each model `inputs` and `addableInputs`, `GET /v1/tenant/providers` each provider's `inputs`, and **`PUT /v1/tenant/models/inputs`** (the Management API; `admin.models.setInputs({ provider, model, inputs })`) sets the added ones, kept in the Tenant setting `model.inputs`. The Model Gate gives the provider adapter a model with an added image, so it is sent.
46
+ - **A custom endpoint reads text only by default.** It used to be sent images. Set `settings.inputs` on `PUT /v1/tenant/model` (`image`, `pdf`, `audio`, `video`) for what yours reads; `office` is refused there. See MIGRATION.md.
47
+ - **The accept check.** A message naming a file of a type the Tenant's model does not read is `422 attachment_unsupported`, with `details.parts` (`artifactId`, `name`, `mediaType`, `reason`), and no turn starts. Only the type is checked, never the size. Without a model in the vault nothing is refused.
48
+ - **Files as artifacts.** After its turn a file is a reference line, `[Uploaded artifact "name" · <artifactId> v<n> · read_artifact to view]` (a tool's: `[Tool <name> returned artifact …]`). Our harness sends the turn's own files once more, as media, in one user message after the conversation, so a message reads the same in every later call. At the gate, an image the model reads is an image, a text file a `<file>` block, and any other file, or one that cannot be read, a note: no file fails a call. PDFs, Office documents, audio and video are notes until the gate sends them natively.
49
+ - **`read_artifact` for every agent.** Every agent's session has it (an agent that declares its own keeps it), so every agent session pins its definition with it added. It takes a `version`, reads the Tenant-wide artifacts the session's messages named, and shows any file the Tenant's model reads; one it does not read fails `artifact.unsupported`.
50
+ - **Compaction** keeps the summarized part's reference lines (`files` on the compaction entry), and counts the turn's files by type and size in its token estimate.
51
+ - `@nylorun/core`: `PROTOCOL_VERSION` 12, required feature `model-inputs`, `HOST_PROTOCOL` `{ min: 11, max: 12 }`, error code `attachment_unsupported`; `MODEL_INPUTS`, `ModelInputSchema`, `PutModelInputsRequestSchema`, `ModelInputsViewSchema`, `CustomModelSettings.inputs`; `mediaReferenceLine` and `mediaReferenceOf` in `/define`; `TranscriptCompactionEntry.files`.
52
+ - `@nylorun/agents`, `@nylorun/studio` and `nylorun` send protocol 12.
53
+
54
+ - bd80e14: **PDFs, Office documents, audio and video reach the model, and files in AG-UI and A2A messages.**
55
+
56
+ - **Native blocks.** A file the Tenant's model reads is sent in its API's own block, no longer as a note: a PDF as an Anthropic `document`, an OpenAI Responses `input_file`, an OpenAI-compatible completions `file` part or Google `inlineData`; Office documents as an OpenAI Responses `input_file`; audio as completions `input_audio` (WAV and MP3 only) or Google `inlineData`; video as completions `video_url` or Google `inlineData`. The Runtime converts and resizes nothing. A file the request cannot carry (audio other than WAV or MP3 on completions, say) fails the model call with `invalid_request` before anything is sent, and the call is not retried. The bytes are only in the provider request, never in the reported call or the transcript.
57
+ - **Files in AG-UI messages.** A user message's image, audio, video and document parts are files: bytes sent inline (a `data` source) are stored as an artifact of the thread's session, and a `file` source with provider `nylorun` names an artifact the caller may read. A thread's messages give a file back as a media part whose source is `{ type: "file", provider: "nylorun", value: <artifact id> }`.
58
+ - **Files in A2A messages.** A message may carry `raw` file parts beside its text, stored the same way in the context's session. The Agent Card's `defaultInputModes` add the media types the Tenant's model reads (`image/png`, `image/jpeg`, `image/webp`, `image/gif`, `application/pdf`, the three Office Open XML types, `audio/*`, `video/*`).
59
+ - Each file holds at most 20 MiB, and the two endpoints take bodies up to 28 MiB. The message then goes through the session's usual command path as text and file parts, so a file the model does not read is refused like any other (`attachment_unsupported`): a `RUN_ERROR` in the AG-UI stream, `ContentTypeNotSupportedError` over A2A, and the files that message stored are deleted. A retried message (the same AG-UI message id or A2A `messageId`) reuses the files it stored the first time. A file by URL is refused (AG-UI `400`, A2A `ContentTypeNotSupportedError`): the Runtime fetches nothing.
60
+
61
+ - aff9bb8: **Sessions on one Environment run their sandbox commands at the same time, and each version of a skill has its own folder (D66).** The SandboxManager ran every call on a workspace one at a time, whichever session sent it, so parallel turns would still have queued at every command. Now each session's calls run in the order it sends them and different sessions' calls overlap; opening, stopping and removing a workspace run alone, after the calls running.
62
+
63
+ - **Skill folders.** A skill's files are mounted at `/skills/<name>@<hash>/` (`skillFolderOf` in `@nylorun/core/define`, the hash from its canonical file map), written once and never replaced, so agents with different versions of one skill share an Environment. An agent used as a tool gets its own version too. The instructions, `load_skill`'s `sandboxPath` and skill attribution on `tool.completed` follow the folder.
64
+
65
+ - f44c018: **Sessions on one Environment run their turns at the same time, and egress is recorded on the Environment (protocol 14, D66, D67).** A second session's turn on an Environment was refused with `409 environment_busy` until the first ended. Now any session on it may start a turn while others run; one turn per session stays. Which sessions work on one disk, and when, is the app's call. See MIGRATION.md, "Parallel turns, and egress on the Environment".
66
+
67
+ - **Turns.** `checkEnvironmentTurn` no longer refuses a turn because another session's turn runs, and the flow-tree exception is gone. `environment_busy` still answers stop, reset, snapshot and restore while any turn runs, and a turn while a disk copy runs.
68
+ - **Egress.** Every connection through egress-gate, allowed or refused, is `environment.egress` on the Environment's stream, with `runningTurns: [{ sessionId, turnId }]`, the turns running when it opened (at most 16). `egress.connection` leaves the session event catalog, and turn traces carry no `CONNECT` span.
69
+ - **Reading it.** `GET /v1/environments/{id}/events` takes `types` (comma-separated), `since` and `until` (ISO times); `client.environments.events(id, { types, since, until })` in `@nylorun/agents`, and `nylorun environments events <id> --types --since --until`.
70
+ - `@nylorun/core`: `EgressRunningTurnSchema`, `EgressRunningTurn`; `EgressConnectionPayloadSchema` gains `runningTurns`.
71
+
72
+ - c8e640b: **A tool call is traced from the log alone to its skill or MCP server and the plugin that brought it.** No protocol change beyond 13. See MIGRATION.md.
73
+
74
+ - **`tool.completed`** gains `skill` and `plugin` (`{ name, version? }`), and tool call rows (`GET /v1/sessions/{id}/calls/tool`, `session.toolCalls()`) gain `skill` and `plugin`, `null` when there is none. An MCP call gets its server's plugin. `load_skill` and `read_skill_resource` get the skill they name, and a sandbox tool whose input names `/skills/<name>/` gets that skill; both get the skill's plugin.
75
+ - **`kind: "harness"`** (`TOOL_CALL_KINDS`): a tool the turn's harness declared in `hello.features.tools`. Such a call said `platform` before.
76
+ - **`mcp.discovered`** names each server's `plugin`.
77
+ - `@nylorun/core`: `skillOfToolCall`, `pluginOf` and `ToolCallTrace` in `/define`.
78
+ - **Studio** shows each tool call's kind, server or skill, and a plugin badge, in the chat and the event details and summaries, and the plugin of each server `mcp.discovered` lists.
79
+
80
+ ### Patch Changes
81
+
82
+ - 912336a: **A `500 internal_error` is logged.** The Tenant API, the gates listener and the keys service log an unexpected error through the Tenant's or the gate's logger as `request_failed` (`keys_request_failed` for a keys operation), with the method, the route's pattern and the error's message and stack, never a request body, header or argument. The answers are unchanged.
83
+ - a4c431a: **One command, `nylorun`, grouped by the API each command uses.** `nylo` became part of `nylorun`, and the commands that are not the installation's lifecycle moved into groups: `environments` (the Runtime API, an application key), `tenant` (the Management API, a management key) and `local` (Docker and kubectl on this machine). See MIGRATION.md.
84
+
85
+ - **`nylorun`:** `up`, `down`, `start`, `stop`, `status`, `studio` and `doctor` stay at the top. `tenant reset|keys|signing-keys|models set|mcp inspect`; `environments list|delete`; `local ls|delete|logs|reset|sandbox|telemetry`; `doctor sandbox`. The old spellings (`nylorun logs`, `reset`, `key`, `mcp`, `sandbox`, `configure`, …) exit 2 naming their replacement.
86
+ - **Remote installations:** without `--tenant`, `NYLORUN_RUNTIME_URL` with `NYLORUN_MANAGEMENT_KEY` points `status`, `doctor sandbox` and `tenant` at a remote installation, and with `NYLORUN_SERVER_KEY` points `environments` there. `tenant keys` then manages application keys through the Management API; `tenant mcp inspect` and `environments` no longer need a local Tenant.
87
+ - **`nylo`:** for one release, each command exits 2 naming the `nylorun` command that replaced it. `nylo env` has no replacement: the SDKs read the Project link themselves.
88
+ - **`@nylorun/cli`:** its `nylo` still runs nylorun's, which now names the replacement.
89
+ - **`@nylorun/runtime`:** messages that named `nylo configure`, `nylorun sandbox enable|status` or `nylorun key put` name `nylorun tenant models set`, `nylorun local sandbox enable|status` and `nylorun tenant keys put`.
90
+
91
+ - 1a31e47: **A pod's new volume is seeded through its new engine, never the old one.** A restore or a lost volume moves the pod's host epoch, and the old engine's connection closes only when that reaches every process. A seed that started in between could write the version into the old pod, and the new volume came up without the restored files. A seed now waits for an engine that joined at the Environment's epoch or later, and writes through it.
92
+ - Pin core to the tested release.
93
+ - Pin harness to the tested release.
94
+ - Updated dependencies [3a32a57]
95
+ - Updated dependencies [af90d1e]
96
+ - Updated dependencies [104f24f]
97
+ - Updated dependencies [c96b9a8]
98
+ - Updated dependencies [94bf329]
99
+ - Updated dependencies [b5ea2b5]
100
+ - Updated dependencies [aff9bb8]
101
+ - Updated dependencies [f44c018]
102
+ - Updated dependencies [c8e640b]
103
+ - Updated dependencies
104
+ - @nylorun/core@0.21.0-beta
105
+ - @nylorun/harness@0.27.0-beta
106
+
107
+ ## 0.24.0-beta
108
+
109
+ ### Minor Changes
110
+
111
+ - e917bd6: **Bring your own harness: harness ids, harness-owned state, and who wrote each event (R4.1, R4.2, R4.4, R4.9).**
112
+
113
+ - **Harness ids and routing.** An Environment's `harness` takes any harness id (`^[a-z][a-z0-9-]{1,62}$`). It is no longer only `nylorun`.
114
+ - **The pin.** A session pins its harness when it opens: its Environment's harness, else `nylorun`. A flow always runs `nylorun`. `GET /v1/sessions/{id}` returns the pin as `harness`.
115
+ - **Routing.** Only a connection whose `hello.name` is that id takes the session's runs.
116
+ - **Our harness's name.** It now says `nylorun` in `hello`. A harness that says `@nylorun/harness` is refused with a message naming the new id, so an older harness image needs this Runtime's.
117
+ - **Placement.** It is keyed by the real harness id, and `"*": {hosts: []}` refuses any harness the map does not name.
118
+ - **Drift.** A turn on an Environment that now runs another harness is `409 harness_mismatch`; open a new session. For a pod Environment, a changed `harness` applies at the pod's next wake, like its image.
119
+ - **Per-harness tokens.** `nylorun-operate harness token put <id>` makes a custom harness container's own Harness API credential, bound to its id; `list` and `rm` manage them. `NYLORUN_HARNESS_TOKEN` stays ours, bound to `nylorun`.
120
+ - **A custom harness as a pod's image.** A pod Environment whose harness is not ours runs its `image` as the harness: the image's own entrypoint (`entrypoint: "image"` to the sandboxes service), with nothing copied in. It needs `image`.
121
+ - **Harness-owned state.** Core stores a harness's turn state as sent and never reads it. Our engine now aligns its state with the turn's manifest itself.
122
+ - **Limits for a custom harness.** Its state is at most 256 KiB, or its turn fails. It cannot pause for approvals in Harness API 2.
123
+ - **Native tools.** It may declare the tools it runs itself in `hello` (`capabilities.tools`). Their calls (capability `harness`) are journaled like built-ins, and the log records a real `tool.completed` instead of `tool.unavailable`.
124
+ - **Who wrote each event.** The writer sets an event's `source` and `evidence`; the event's type no longer decides them. Events written before this release still say `loop` or `api`.
125
+ - **`client`**: a command, with a new `principal` taken from its credential.
126
+ - **`harness`**: what a harness reported, with its harness id and `runId`. Evidence `claimed`.
127
+ - **`core`**: core's own logic. Evidence `observed`.
128
+ - **`effectId` on model calls.** `GET /v1/sessions/{id}/calls/model` returns each call's `effectId`, so a metered call joins the effect journal.
129
+ - **The published contract grows.** It adds `HarnessCheckpointSchema` (the checkpoint fields a harness reads), `NativeToolSchema`, the harness id helpers, `HARNESS_TOOLS_CAPABILITY`, `MAX_HARNESS_STATE_BYTES`, and the gate error codes `key_required` and `effect_not_journaled`. It also documents the effect context keys, the model request hash rule and how a turn's status maps to its output method.
130
+
131
+ - 5165451: **A pod's disk can start prepared, and every Environment's disk has versions (R2d steps 2 and 3).** Before, a new pod volume was empty and a disk could only be kept or reset. Now:
132
+
133
+ - **Setup and input files** (pods, Host feature `environment-setup`): `PUT /v1/environments/{id}` takes `files` (versions of Tenant-wide artifacts, copied in: a file at its `path`, a folder under it) and `setup` (commands run in order with `/bin/sh -c`, `cwd` default `/workspace`, `timeout` default 10m and at most 30m, at most 16 of each). They run once per new disk, after the pod's engine joins and before its first turn, with the Environment's variables, secrets (as the sentinel) and egress. A failed command fails the pod (`observed: failed`, reason `setup: command N …`, an `environment.failed` event with the exit code and the end of the output) and the turn waiting for it; the next turn start runs the setup again. A virtual Environment refuses both with a `400`.
134
+ - **Disk versions** (Host feature `environment-disk-versions`): `POST /v1/environments/{id}/snapshot` keeps a version of the disk between turns (`/workspace`, and a pod's `/harness` but the engine's own files) as a version of a folder artifact the Environment owns, with modes, modification times, links and empty directories (folder manifest `nylorun.folder.v2`); each file's bytes are stored once, and an unchanged disk makes no version. `POST .../restore` puts a version back: a virtual Environment at once, a pod on a new volume seeded before its next turn (`disk.operation` in the view meanwhile; turns wait). `PUT` with `from: { environment, version }` creates an Environment from another's version. `GET .../versions` and `DELETE .../versions/{n}` list and delete them; their trees, files, diffs and zips read through `/v1/artifacts`. A lost pod volume with a version is recovered from the latest one instead of refusing turns until a reset. Snapshot and restore during a turn are `409 environment_busy`. `EnvironmentView.disk` gives the latest version and the copy in progress. Migration `0021_disk_versions`.
135
+ - `@nylorun/agents`: `client.environments.snapshot`, `restore`, `versions` and `deleteVersion`; `put` takes `setup`, `files` and `from`.
136
+
137
+ - 35e29b7: **Environments replace sandboxes and vaults (protocol 11). This is a fresh start: upgrading deletes your sandboxes, their disks and every vault except the model's, and closes every open session (its history stays readable).** List what you have before upgrading; see "Environments replace sandboxes and vaults" in MIGRATION.md.
138
+
139
+ An Environment is where sessions run: one sandbox (kind `virtual`, `pod` or `none`) with a recipe that can change, and one vault of secrets for its shell, MCP servers and HTTP tools. The app decides who uses which Environment; the Runtime keeps it by id and holds its lifecycle.
140
+
141
+ - `@nylorun/core`: protocol 11 (the Host accepts 11 only; older clients get `426`), required feature `environments`. `PutEnvironmentRequestSchema`, `EnvironmentViewSchema` (`recipe`, `version`, `appliedVersion`, `ownerUserId`, `lastUsedAt`), `PutEnvironmentSecretRequestSchema`, `EnvironmentSecretViewSchema`, `ENVIRONMENT_EVENT_TYPES`, `TenantEnvironmentsConfigSchema`, `environmentGranted`. `PutSessionRequest.environment` replaces `sandbox`, `vaultIds` and `credentialSelections`; `SessionView.environment` replaces the session's sandbox and vault fields. Error codes `environment_busy`, `environment_unavailable`, `environment_lost`, `environment_not_granted`, `environment_owner_mismatch` and `session_closed` (the `sandbox_*` codes and `sandbox_expired` are gone); session events `environment.state`, `environment.exec` and `environment.attached`; scope `environments:write`. Credential coverage takes `environmentId` and answers `covered` or `missing`; the MCP preview takes `environmentId`. The vault Management schemas are removed.
142
+ - `@nylorun/runtime`: `/v1/environments/{id}` replaces `/v1/sandboxes/{id}`: `PUT` creates, finds or changes one (`kind` and `storage` fixed; a recipe change bumps `version`; a pod gets a new image or resources at its next start from stopped, a new allowlist within seconds), with list by label, events (`nylorun.environment-event/1`), stop, reset and delete. Each Environment owns one vault: `PUT|GET|DELETE /v1/environments/{id}/secrets/{name}` on the Runtime API, write-only, with a second credential for a host or URL refused at the write (`409 credential_conflict`). An optional owner guard (`ownerUserId`) is checked when a session opens and at every turn start. `lifecycle.deleteAfter` deletes an Environment unused that long; TTL and `onExpiry` are gone. Kind `none` holds secrets for MCP-only agents. `/v1/tenant/vaults`, user and installation vaults, the Tenant's default sandbox, inline sandboxes and `{ session }` sharing are removed. The Tenant setting is `environments.config` (`limits.environments`). Identity files grant `environments:` (an old `sandboxes:` key, or `sandboxes:write`, stops the Runtime with a message naming the new key). Migration `0020_environments`.
143
+ - `@nylorun/harness`: the Harness API claims `environment.state` and `environment.exec`, and its workspace fields name `environmentId`.
144
+ - `@nylorun/agents`: `client.environments` (`put`, `get`, `list`, `page`, `stop`, `reset`, `delete`, `events`, `secrets.put|list|get|delete`, `forSession`) replaces `client.sandboxes`; `createSession({ environment })`; `sessions.page({ environmentId })` filters by Environment. The AG-UI handler's session options take `environment`.
145
+ - `@nylorun/admin`: `admin.coverage({ agentId, environmentId })` replaces `admin.vaults`; `admin.mcp.preview({ url, environmentId })`.
146
+ - `@nylorun/studio`: Environments replace Sandboxes, with a create form, stop, reset, delete and their secrets; the Credentials forms write an Environment's secrets; New session picks an Environment and shows its coverage.
147
+ - `nylorun`: `nylorun sandbox ls|rm` list and delete Environments; `nylorun mcp inspect --environment <id>` replaces `--vault`; `nylo reset --environments` replaces `--sandboxes`; `nylo doctor sandbox` reports the Environment limits.
148
+
149
+ - e917bd6: **The Harness API is a published contract (R4.5).** Everything a harness sends or reads now has a schema in `@nylorun/core/harness-api`, and the Runtime validates with those same schemas:
150
+ - the transport and the pod join (`HARNESS_API_PATH`, `HARNESS_API_HEADER`, `HOST_JOIN_PATH`, `HostJoinRequestSchema`, `HostJoinAnswerSchema`);
151
+ - the Model and Tool Gate bodies (`ModelCallBodySchema`, `ToolCallBodySchema`, `HttpCallBodySchema`, the MCP bodies, `GATE_ERROR_CODES`);
152
+ - the MCP snapshot (`McpSnapshotSchema`);
153
+ - transcript entries (`TranscriptEntrySchema`);
154
+ - the effect id rule (`effectIdOf`, `isEffectIdOf`).
155
+
156
+ The build also writes them as one JSON Schema document, `@nylorun/core/harness-api/schema.json`, for harnesses written in other languages.
157
+
158
+ Other changes:
159
+ - `TurnStart` gains an optional `trace` (W3C `traceparent`) for the turn's trace context. Nothing fills it yet.
160
+ - Core refuses a `hello` whose `api` is not `2`.
161
+ - **The fixture model answers at the Model Gate (R4.3).** With a Tenant's `fixtureModel` seeded, the gate answers model calls itself and meters each one, like any model: a `model_usage` row with provider `fixture` and cost 0, readable at `GET /v1/sessions/{id}/calls/model`. Our harness no longer answers it locally.
162
+ - The fixture model also answers scripted markers: `fixture:text`, `fixture:tool <name> <json>`, `fixture:budget` and `fixture:slow <seconds>`.
163
+ - **The sandboxes service can run a harness image (R4.4, Go).** `PUT /v1/pods/{name}` accepts `entrypoint: "image"`: the workload image's own `ENTRYPOINT` and `CMD` run, with no engine copied in and no `harnessImage`. The default, `entrypoint: "engine"`, is unchanged. The Runtime starts sending it with the harness pin in a later change.
164
+
165
+ - 3013501: **Tool calls are delivered at least once, and every one is on the record (R5 step 1).** A gateway restart used to park the sessions it interrupted as `uncertain` until someone cancelled them, a harness could call tools with no record, and nothing listed a session's tool calls. Now every tool call ends in the log with its outcome, a crash sends it again with the same key, and the gateway runs only calls the effect journal recorded.
166
+
167
+ - **Every outcome in the log.** Each tool call ends in one `tool.completed` with `status` (`completed`: the tool answered; `failed`: the Runtime ended it, `tool.disabled`, `tool.unavailable` or `tool.interrupted`; `cancelled`), `kind` (`mcp`, `http`, `sandbox`, `platform`), `server`, `attempts` and `durationMs`. Refused and cancelled calls are in the log for the first time.
168
+ - **`GET /v1/sessions/{id}/calls/tool`** pages a session's tool calls from the effect journal, in the order they were first sent: tool, input, status, sends, error and timing. Same credentials and paging as `/calls/model`; `@nylorun/agents` adds `session.toolCalls()`. Host feature `tool-calls`.
169
+ - **At least once, with the same key.** An MCP or HTTP tool call that a crash, a shutdown or a lost gateway hop interrupted is sent again with its effect id as the key, up to 3 sends; then it fails to the model as `tool.interrupted` ("may have run") and the session continues. A gateway that restarts mid call runs the call again under its key instead of answering `uncertain`. HTTP tools get the key as `Idempotency-Key`, as before; MCP servers now get it in `_meta["nylorun/idempotency-key"]` on `tools/call`. A tool that honors the key acts once; `attempts` above 1 marks where one may have acted twice.
170
+ - **Sandbox commands are never run twice.** A sandbox or core-served call a crash interrupted has no key to dedupe on, so it fails to the model as `tool.interrupted` at once, and the model decides whether to run it again.
171
+ - **No tool call parks a session.** The `uncertain` state for tool calls, its cancel-only exit and R2b's read-only/idempotent `mcp.lost` exception are gone: a lost MCP answer is `mcp.lost` to the model for every tool. Model calls keep their own rules.
172
+ - **The gateway runs only journaled calls.** Under a run token, `/nylorun/v1/tool-calls` and `/nylorun/v1/http-calls` need the call's effect id as `Idempotency-Key` (`400 key_required`), and the effect journal must hold it running, for the run's turn, the same tool and the same input (`409 effect_not_journaled`). A harness that skips the journal gets nothing run.
173
+ - `@nylorun/harness`: `HarnessExecutors.resends(effect, error, run)` says a gate hop lost a call's answer; the harness then asks core again with the same intent and sends the call again.
174
+ - **Approval** stays in the manifest and our harness still asks for it. Only the Runtime's own harness asks: one built from the Harness API can skip it until tool policy moves to the gateway (R5 step 2).
175
+
176
+ - 6061a3a: **Tool approvals are the Tenant's tool policy (R5 step 2).** An agent's manifest used to say which tool calls wait for a person, and only the Runtime's own harness asked; a harness built from the Harness API could skip it. Now the operator sets one tool policy for every agent and every harness, and the Runtime enforces it before anything is sent.
177
+
178
+ - **`GET`/`PUT /v1/tenant/tool-policy`** (the Management API; `admin.toolPolicy.get()`/`.put({ rules })`): rules `{ effect: "allow" | "ask" | "deny", tool?, server?, kind?, agent?, when?, reason? }`. A rule matches a call when every selector it sets matches (`tool` and `server` take `*` globs; `when` tests input fields by dotted path with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule decides; a call no rule matches runs.
179
+ - **Enforced for every harness.** Core decides an MCP or HTTP tool call as it journals it. `deny` fails it to the model as `tool.denied`, unsent. `ask` holds the call (`held` at `/calls/tool`) and pauses the turn with one `approval` interaction per held call (`turn.paused`, with the tool, its input and the reason). `approve` sends that exact call, or fails it as `tool.denied`. The Tool Gate runs only calls the journal holds running, so a held or refused call cannot run. Nothing holds a process while it waits: an approval survives a restart. A flow's HTTP stages are decided the same way.
180
+ - **`approval` leaves the manifest.** `PUT /v1/agents/:id` and `saveAgent()` refuse a definition that sets `approval` on an HTTP tool, an MCP server or an MCP tool's settings; `http({ approval })` and `.mcp({ … approval })` throw; a turn manifest can no longer add one. The engine never asks for an HTTP or MCP tool's approval itself. A session pinned to a manifest saved with `approval: "always"` keeps it, as an ask.
181
+ - `@nylorun/studio`: a paused session shows an approval card for each held call, with its tool, why it is held and its input, and **Approve** / **Deny**.
182
+ - Migration: move each `approval: "always"` to an `ask` rule (see MIGRATION.md).
183
+
184
+ - 7d496e4: **Every connection a pod Environment makes to the internet is on the record (R7, egress events).** egress-gate used to keep only log lines, so a session's log could not say which hosts its turn reached or what the gate refused. Now each connection through egress-gate is one event, written by the gate itself.
185
+
186
+ - **`egress.connection`** in the session log of the turn that held the Environment when the connection opened, with that turn's id even if the turn has ended by the time it closes. Source `egress-gate`, evidence `observed`.
187
+ - **`environment.egress`**, the same payload, on the Environment's own stream (`GET /v1/environments/{id}/events`) for traffic while no turn holds the Environment.
188
+ - **The payload:** `environmentId`, `host`, `port`, `outcome` (`allowed` or `refused`), `reason` and `status` for a refusal, `mode` (`tunnel` or `credential`), `credentialBound`, `bytesUp` and `bytesDown`, `startedAt` (the gate's clock at CONNECT) and `durationMs`. A host a shell credential is bound to adds `requests` and the `credentialIds` the gate set. It never carries a header value, a URL path or a body.
189
+ - **One event per connection:** written when it closes, or at once when the gate refuses it (a host not in `network.allowedHosts`, a port other than 443 or 80, a private address, too many tunnels, a name that does not resolve). A refusal before the Environment is known (no token, an expired or stale one) stays a log line only.
190
+ - **Never in the way:** a write happens after the connection closes or is answered, and a failed write is logged as `egress_record_failed` without touching the tunnel. The gateway's `egress_refused` and `egress_terminated` log lines stay.
191
+
192
+ - 81c783e: **Every row that ends a call or a turn carries its own timing (R7.1).** The session log said what happened but not how long it took: a model call's outcome had no time at all, a tool call's only core's window, a flow's HTTP stage had no outcome row, and a turn's end had neither its duration nor its cost. Now each one is a span on its own, ready for the traces that follow, with no protocol change.
193
+
194
+ - **Model calls.** `message.assistant`, `model.failed` and `context.compacted` carry the call's `effectId` and `durationMs` (from the intent to the outcome). When the Model Gate metered the call they also carry the gate's own `gate: { startedAt, endedAt }`, `firstTokenMs` (to the provider's first stream event) and `meteredUsage` (`inputTokens`, `outputTokens`, `totalTokens`, and `cachedTokens` and `costUsd` when not zero), beside the `usage` the harness reported. The usage ledger keeps the gate's times (migration 0022: `model_usage.started_at`, `ended_at`, `first_token_ms`).
195
+ - **One row per model call.** A step that said nothing (no text, no tool calls, as a structured answer) now writes `message.assistant` with `text: ""`, and each partial summary of a long compaction writes `context.compacted` with `partial: true`. The AG-UI and A2A histories skip both; `isEmptyAssistantMessage` in `@nylorun/core/contracts` tells your own UI which steps said nothing.
196
+ - **Tool calls.** `tool.completed` carries the call's `effectId`, the Tool Gate's own `gate: { startedAt, endedAt }` for an MCP or HTTP call it ran (absent for sandbox and core-served tools), and `policy: { decision: "ask" | "deny", reason, approvedBy? }` when the Tenant's tool policy held or refused the call.
197
+ - **Flow HTTP stages.** A new event type, `node.completed` (`path`, `kind`, `key`, `iterations`, `effectId`, `status`, `durationMs`, `gate`), ends each HTTP stage and HTTP verifier that `node.started` began, and a tool stage the Runtime refused to run. Clients ignore event types they do not know.
198
+ - **Turns.** `turn.completed`, `turn.failed` and `turn.cancelled` carry `durationMs` (from the turn's start to its end, waits for a person included), `pausedMs` (how much of that it waited paused), `agentId` and `totals: { steps, tokens, costUsd }`, the same totals `GET /v1/tenant/usage?scope=turn` returns. A turn started before this release has no `durationMs`.
199
+
200
+ - 7800358: **Each turn is one OpenTelemetry trace, projected from the session log (R7).** An operator could not see what a turn did or where its time went without grepping container logs. Now every turn is a trace with a span for each model call, tool call and egress tunnel, exported over OTLP to any collector, readable per turn from the Runtime, and joined to the trace of the app that started it. No protocol change.
201
+
202
+ - **Traces from the log.** A turn's end is its root span, `invoke_agent <agent>`; each model call is a `chat <model>` span, each tool call and flow HTTP stage an `execute_tool <tool>` span, each egress-gate tunnel a `CONNECT <host>` span, and each pause and answer a marker. A model or gate tool span is the gate's own start and end; usage and cost are the gate's (`nylorun.usage.mismatch` when the harness reported other numbers). Attribute names follow the OTel GenAI conventions at one pinned commit, recorded as `nylorun.semconv.genai` on the resource. Ids are derived from the log under a key the Tenant keeps (setting `trace.key`), so every export and read of a turn gives the same ids.
203
+ - **OTLP export, off until you set an endpoint.** With `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) the Runtime posts OTLP/HTTP JSON from a replication slot of its own, `nylorun_trace_export`, in batches of up to 512 spans or every 2 s; `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` work as usual. No turn waits for it. A collector that is down costs only its spans: after three retries a batch is dropped and logged as `trace_export_dropped`, and the slot moves on, so Postgres keeps no WAL for it. `nylorun start` passes these variables to the runtime container.
204
+ - **References by default.** Spans point at log events (`nylorun.event.cursor`) and carry no content unless you opt in per kind with `NYLORUN_TRACE_CONTENT` (`user`, `outputs`, `tool-results`, `errors`), or all of them with `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`, each cut at 8 KiB. A span never carries a credential value, a prompt, a person's id, an identity header or an idempotency key.
205
+ - **`GET /v1/sessions/{id}/turns/{turnId}/trace`** answers a turn's spans as OTLP JSON, the same spans the exporter posts: look at a turn without a collector, or post it to one to backfill. Application keys, as the `/calls` reads. Host feature `traces`; `@nylorun/agents` adds `session.trace(turnId)`, and `@nylorun/core/contracts` `TurnTraceSchema`.
206
+ - **Trace context in and out.** A `traceparent` (and `tracestate`) on `POST /v1/sessions/{id}/commands`, an AG-UI run or an A2A message puts the turn in the caller's trace; `command.message` records it as `trace`. The turn hands its context on: to the harness in `TurnStart.trace`, to HTTP tools as `traceparent` beside `Idempotency-Key`, to MCP servers as `traceparent` on the call's requests, and to sandbox commands as `TRACEPARENT`.
207
+ - **Harness API** (additive): the `effect.intent` answer `execute` may carry the tool call's `traceparent`. `@nylorun/harness`: `HarnessExecutors.tool` takes it as a fourth argument (`ToolCallContext`).
208
+
209
+ ### Patch Changes
210
+
211
+ - 17a97e9: **An artifact names the Environment whose disk it holds.** `ArtifactView` (and so `ArtifactListItem` in artifact pages) gains optional `environmentId`: set on the folder artifact that holds an Environment's disk versions (R2d step 3), absent on a session's or the Tenant's own artifacts. Before, such an artifact read as a Tenant-owned one with no way to tell whose disk it was.
212
+ - b6214d9: Add opt-in artifact metadata pagination to `GET /v1/artifacts?limit=…`, shared
213
+ `ArtifactListItem`/`ArtifactPage` contracts and SDK `artifacts.page()` (default 50,
214
+ maximum 200). Hosts advertise `artifact-reads`. Pages order by creation time and
215
+ ID descending, filter by session, kind and labels, bind cursors to the Tenant and
216
+ filters, and reapply ownership and agent grants on each read. Legacy unpaged
217
+ responses remain unchanged. A Drizzle migration adds the creation-order index;
218
+ pages reuse the existing bounded read-only pool. Studio and every other Runtime
219
+ client can consume this public API without database access.
220
+ - a708b27: **The example harness, on harness ids (R4.8).**
221
+
222
+ - **A new fixture marker.** `fixture:unoffered <name> <json>` makes the fixture model call a tool the turn does not offer, so a test can see that such a call runs nothing and the turn goes on. The other markers are unchanged.
223
+ - **The example harness.** `examples/harness-minimal` says `minimal` in `hello`, connects with its own token from `nylorun-operate harness token put minimal`, and runs beside ours: sessions on an Environment whose `harness` is `minimal` go to it. As a pod Environment's `image` it is the pod's harness.
224
+ - **A session's workspace is its own harness's.** Off a pod, a session's sandbox routes, `save_artifact` and turn-end export go only to a connection of the session's harness that serves workspaces. Before, a custom harness's session was served from our harness's workspace, which is not that harness's files. Without such a connection the request is refused. Sweeps and removes still go to ours.
225
+ - **The Harness API reference and guide.** `guides/HARNESS_API.md` covers every section a harness implements, with a walkthrough for building one.
226
+
227
+ - dec813c: **A gateway shutdown no longer leaves an unhandled rejection.** When a keyed MCP or HTTP tool call was re-sent and the re-send's client went away before the gateway joined the running call, closing the gateway mid call rejected that join with nobody listening ("The gateway is shutting down"), which Node reports as an unhandled rejection. The gateway now handles it: the call still stops on shutdown and the next gateway sends it again under its key, as before.
228
+ - 11c3b2a: **The Runtime's own tokens never reach a session's record from what a harness reports.** A harness's commands hold the egress token in their proxy variables, and a command that prints its environment, or a model that repeats it, would put that token into a tool result. Core now replaces every Nylorun-signed token before recording it:
229
+ - where it looks: tool and model outcomes, error messages, claimed sandbox events, and a turn's output, state and transcript edits;
230
+ - what a token is: a compact JWT whose `typ` starts with `nylorun-`, such as egress, run or host tokens;
231
+ - what replaces it: `[nylorun token redacted]`.
232
+
233
+ This applies to every harness, ours included. The leak showed when the example harness ran as a sandbox pod's image. Other values are unchanged.
234
+
235
+ - 02c2d24: **The runtime image keeps the repository's new layout.** The Runtime's code moved to `runtime/server/` and the harness to `runtime/harness/` in the repository, and the image follows it: the entry point is `/app/runtime/server/dist/host/main.js` (was `/app/runtime/dist/host/main.js`). The image's `ENTRYPOINT`, `nylorun-operate` and the sandboxes service (`ghcr.io/nylorun/sandboxes`, released with the Runtime) use the new path; anything that runs a file under `/app/runtime/dist/` directly needs the new one.
236
+ - a0cbca1: **Clearer wording when a definition runs your code.** The refusal for a code tool or a flow's tool stage, the `hooks were removed` error and `nylo endpoints` now say that the Runtime runs agents from their manifests alone, instead of that it runs no code of yours during a session. Only the message text changed; anything matching the old wording (for example `runs no code of yours during a session`) needs the new text.
237
+ - Pin core to the tested release.
238
+ - Pin harness to the tested release.
239
+ - Updated dependencies [17a97e9]
240
+ - Updated dependencies [b6214d9]
241
+ - Updated dependencies [e917bd6]
242
+ - Updated dependencies [5165451]
243
+ - Updated dependencies [35e29b7]
244
+ - Updated dependencies [e917bd6]
245
+ - Updated dependencies [a0cbca1]
246
+ - Updated dependencies [3013501]
247
+ - Updated dependencies [6061a3a]
248
+ - Updated dependencies [7d496e4]
249
+ - Updated dependencies [81c783e]
250
+ - Updated dependencies [7800358]
251
+ - Updated dependencies
252
+ - @nylorun/core@0.20.0-beta
253
+ - @nylorun/harness@0.26.0-beta
254
+
3
255
  ## 0.23.0-beta
4
256
 
5
257
  ### Minor Changes
package/README.md CHANGED
@@ -4,7 +4,7 @@ The independent OSS **Runtime Host** consumes `@nylorun/harness/run` and
4
4
  `@nylorun/core/contracts`. Cloud installs published `@nylorun/harness` from npm
5
5
  and does not import this package. Every Tenant serves two APIs on one URL: the
6
6
  **Runtime API** for developers (agents, sessions with AG-UI and
7
- A2A, sandboxes, artifacts), whose client is `@nylorun/agents`, and the
7
+ A2A, Environments and their secrets, artifacts), whose client is `@nylorun/agents`, and the
8
8
  **Management API** for operators (`/v1/tenant/*`), whose client is `@nylorun/admin`.
9
9
  Vocabulary: [src/CONTEXT.md](./src/CONTEXT.md).
10
10
 
@@ -24,7 +24,7 @@ Restate and S2: `nylorun start` runs them, as a local Tenant, on a developer mac
24
24
  package is a library with no bin; its Host entry is `@nylorun/runtime/server`
25
25
  (`dist/host/main.js`), which requires `NYLORUN_DATABASE_URL`. For tests and
26
26
  ephemeral embeds, use `startEphemeralRuntime()` from `@nylorun/runtime/core`.
27
- See [MIGRATION.md](../MIGRATION.md).
27
+ See [MIGRATION.md](../../guides/MIGRATION.md).
28
28
 
29
29
  A Runtime serves one **Tenant**: an installation is one Runtime with its own
30
30
  Postgres database, Restate and S2 basin, and two Tenants are two installations. No
@@ -45,7 +45,7 @@ database (`tenant_<id>` schemas), or by a pre-release build of one Tenant per da
45
45
  (`schema_version` tables), is refused: this release starts fresh on a new database. Restate runs one advance of a session at a time, delivers its wakes and holds
46
46
  its timers and the Tenant's sweep (Durable Session Execution), and journals no effect:
47
47
  checkpoints, effects, events and the outbox are the Session Store's, and a wake or a pod
48
- sandbox's reconcile is written to the outbox in the transaction that causes it, so a commit
48
+ Environment's reconcile is written to the outbox in the transaction that causes it, so a commit
49
49
  never loses it ([src/CONTEXT.md](src/CONTEXT.md)); every session's events are relayed from the record to
50
50
  its own S2 stream, which history and SSE read (Durable Streams). One image runs every **service**,
51
51
  and `--service` picks what a process runs: `core` serves the Runtime API, the
@@ -66,7 +66,7 @@ core's process. A `harness` process needs `NYLORUN_HARNESS_URL`,
66
66
  `/harness`), answers `/health` on `127.0.0.1:4300`, and refuses to start with a
67
67
  database, Restate, keys or gates credential in its environment. A local Tenant
68
68
  runs it in the `harness` container, on a network with only the runtime and the
69
- gateway ([DEPLOYMENT.md](../DEPLOYMENT.md#the-harness-agent-turns-mcp-servers-and-workspaces)).
69
+ gateway ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#the-harness-agent-turns-mcp-servers-and-workspaces)).
70
70
 
71
71
  The container is configured by its environment, which a local Tenant's Compose file
72
72
  sets: `NYLORUN_DATABASE_URL` (required), `NYLORUN_RESTATE_INGRESS_URL`,
@@ -91,7 +91,7 @@ Host) the registration replaces the deployment at that URL.
91
91
  `NYLORUN_IDENTITY_FILE` names the identity
92
92
  file, a YAML list of the trusted issuers whose JWTs the Runtime API accepts
93
93
  (Host feature `trusted-issuers`), read once at boot; a malformed file stops the
94
- boot ([DEPLOYMENT.md](../DEPLOYMENT.md#trusted-issuers)). A process that runs `loop` sends its model
94
+ boot ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#trusted-issuers)). A process that runs `loop` sends its model
95
95
  calls to the gate at `NYLORUN_GATES_URL`; in a container it refuses to start
96
96
  without it and `NYLORUN_GATES_TOKEN`. That token is core's credential: the gate
97
97
  accepts only it for vault writes and token signing. Model, HTTP tool and
@@ -103,16 +103,16 @@ is refused with `409 run_stale`. A `gates` process needs only
103
103
  (`NYLORUN_GATES_LISTEN_HOST`, `NYLORUN_GATES_LISTEN_PORT`, default 4100, and
104
104
  `NYLORUN_GATES_ALLOWED_HOSTS`) and the Host's `tenant/` directory, which it
105
105
  never writes; it serves the database's one Tenant. A session's MCP servers and
106
- HTTP tools get their credentials from its attached vaults only: a `bearer` token
106
+ HTTP tools get their credentials from the vault of its Environment only: a `bearer` token
107
107
  or a `headers` map per URL, sent to an MCP gateway instead when it has `via`,
108
108
  with the session owner in its identity header
109
- ([DEPLOYMENT.md](../DEPLOYMENT.md#mcp-servers-and-http-tools)); the credential resolver is gone
109
+ ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)); the credential resolver is gone
110
110
  (protocol 10), and a process that still sets a `NYLORUN_RESOLVER_*` variable logs
111
111
  `resolver_removed` and ignores it. With `egress`
112
112
  (`--service gates,keys,egress`, for pod sandboxes) the same process runs
113
113
  egress-gate on `NYLORUN_EGRESS_LISTEN_HOST`:`NYLORUN_EGRESS_LISTEN_PORT`
114
114
  (default `0.0.0.0:4200`): a CONNECT proxy that admits a pod's egress token and
115
- tunnels only to the hosts its sandbox spec allows, on 443 or 80, never to a
115
+ tunnels only to the hosts its Environment allows (`network.allowedHosts`), on 443 or 80, never to a
116
116
  private address. `NYLORUN_PACKING` (`combined` or `split`) is logged at startup.
117
117
 
118
118
  The Host has one listener, which serves both APIs. Host work runs on the machine
@@ -160,6 +160,7 @@ The Host root is `NYLORUN_HOME` or `~/.nylorun` (for a local Tenant,
160
160
  | `/v1/*` Runtime API routes (all but `/v1/tenant/*`) | application key or trusted issuer's token | Require `Nylorun-Protocol`; nothing names the Tenant. A management key is `403 key_role_mismatch`, except on `/v1/me` and the public `GET /v1/access/jwks` |
161
161
  | `/v1/tenant/*` Management API routes | management key | Require `Nylorun-Protocol`. An application key, alone or acting for a subject, is `403 key_role_mismatch`; a management key with `Nylorun-Subject` or `Nylorun-Scopes` is `403 subject_invalid` |
162
162
  | `PUT /v1/tenant/keys/{keyId}` | management key | Creates application key `keyId` or rotates it, and returns it once; `GET /v1/tenant/keys` lists every key (id, role, when issued, never the keys) and `DELETE` deletes one. `studio`, `bootstrap` and management keys are refused |
163
+ | `PUT /v1/tenant/models/inputs` | management key | What a catalog model reads beyond its catalog's inputs (protocol 12): only inputs in its `addableInputs`; an empty list removes them. `GET /v1/tenant/models` gives each model `inputs` and `addableInputs`. A custom endpoint sets `settings.inputs` on `PUT /v1/tenant/model` (text only by default) |
163
164
  | `GET /v1/artifact-links/{token}` | the link itself | A capability link to one artifact version (protocol 6): no credential, no `Nylorun-Protocol`, Range supported; a folder's link opens its zip, or one of its files |
164
165
 
165
166
  Every route checks `Host` first (`421 host_rejected`) and rejects non-JSON bodies
@@ -169,9 +170,17 @@ with `415 unsupported_media_type`, except an artifact upload (`POST /v1/artifact
169
170
  presents a trusted issuer's token; keys (application and management)
170
171
  are refused from browsers before they are looked up. The Runtime sends no CORS headers, and answers
171
172
  `OPTIONS` with `204` and `Allow` only: the operator's reverse proxy answers preflights
172
- ([DEPLOYMENT.md](../DEPLOYMENT.md#calling-the-runtime-from-browsers-and-apps)). Missing or
173
+ ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#calling-the-runtime-from-browsers-and-apps)). Missing or
173
174
  unsupported protocol → `426` before authentication, unless the request sends neither
174
- `Nylorun-Protocol` nor `Authorization` (the Host serves protocols 4 to 10; protocol 10 removes
175
+ `Nylorun-Protocol` nor `Authorization` (the Host serves protocol 13 only; protocol 13 makes the
176
+ manifest flat, manifest 7: no `capabilities`, tools known by name, the Runtime's own tools in the
177
+ pinned manifest's `runtime` block and no `capabilityId` on events, tool call rows or the Harness
178
+ API, now 3; it closes every session opened before it; protocol 12 adds
179
+ model inputs, the `422 attachment_unsupported` accept check and `read_artifact` for every agent,
180
+ and changes nothing a protocol 11 client sends; protocol 11 replaces
181
+ sandbox resources, the Tenant's default sandbox, inline sandboxes and user and installation
182
+ vaults with Environments, each one sandbox and one vault, under `/v1/environments`, and
183
+ removes the Management API's vault routes, so a protocol 10 client gets `426`; protocol 10 removes
175
184
  the MCP OAuth connect, the vault's `oauth` credentials and the credential resolver, and adds
176
185
  `headers` credentials, `via` and the identity header, manifest v6 and
177
186
  `POST /v1/tenant/mcp/preview`; protocol 9 makes the Runtime API an OAuth 2.1 resource server: a missing or rejected credential is `401
@@ -182,8 +191,7 @@ the Admin API, and those old paths answer `404` with no alias; protocol 7 remove
182
191
  derived principals, whose routes answer `404`; protocol 6 adds file and folder artifacts and message `parts`; at each turn's end the
183
192
  Runtime exports `/workspace/outputs` of the session's sandbox as a version of its
184
193
  `outputs` folder, read at `/v1/artifacts/{id}/versions/{n|latest}/tree`, `/files/{path}`,
185
- `/diff?from=` and `/zip`). Protocol 5 and later clients name no Tenant. A protocol 4 client's `Nylorun-Tenant` naming
186
- another Tenant (or malformed) and a Tenant that
194
+ `/diff?from=` and `/zip`). Clients name no Tenant (since protocol 5). A Tenant that
187
195
  could not be opened → opaque `404` with identical body. A path or method no route serves is `404 Route not found`
188
196
  once the caller is known.
189
197
 
@@ -207,13 +215,13 @@ https://cdn.jsdelivr.net/npm/@nylorun/runtime@<version>/dist/management-openapi.
207
215
 
208
216
  Each document has described tags, in the order a developer uses them. The Runtime API's are
209
217
  grouped (`x-tagGroups`): Agents (Agents, Definition files), Sessions (Sessions API, AG-UI,
210
- A2A), Sandboxes, Artifacts and Service (health, readiness, `/v1/me`, the JWKS and the
218
+ A2A), Environments, Artifacts and Service (health, readiness, `/v1/me`, the JWKS and the
211
219
  document; the `/openapi.json` alias is served but not listed). The Management API's are
212
- Tenant, Application keys, Models, Vaults, Signing keys and Settings. Each operation's
220
+ Tenant, Application keys, Models, Credential coverage, MCP servers, Signing keys and Settings. Each operation's
213
221
  `security` says which credentials it takes (application key and trusted issuer's token in
214
222
  the Runtime API; management key in the Management API), and its
215
223
  `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields say
216
- who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/openapi/` holds the committed snapshots: a change to a route changes
224
+ who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/server/openapi/` holds the committed snapshots: a change to a route changes
217
225
  them (`node scripts/build-openapi.mjs --write`), and `check-package` fails until they are
218
226
  updated.
219
227
 
@@ -251,18 +259,22 @@ const runtime = await startEphemeralRuntime({
251
259
 
252
260
  The package's own tests run every Session Store on Postgres: `npm test` gives each
253
261
  test file a database on the Docker test stack (`test/stack/compose.yaml`) and starts
254
- the stack when it is down; see [CONTRIBUTING.md](../CONTRIBUTING.md).
262
+ the stack when it is down; see [CONTRIBUTING.md](../../CONTRIBUTING.md).
255
263
 
256
264
  Save agents with `PUT /v1/agents/{agentId}` using the application principal (`saveAgent`
257
265
  in `@nylorun/agents`). The Runtime runs the agent from its manifest and never calls your
258
266
  code during a session: a definition with a tool that would (`tool({ run })`, a flow's tool
259
267
  stage) is refused. Your services are reached as tools: HTTP tools, POSTed from the Runtime,
260
- so their URLs must be reachable from it, and remote MCP servers. The operator gives
261
- each URL a vault credential, previews an MCP server's tools (`nylorun mcp inspect`),
262
- and the manifest says which tools are enabled, need approval or are deferred
263
- ([DEPLOYMENT.md](../DEPLOYMENT.md#mcp-servers-and-http-tools)). Model gateway
264
- and sandbox backend are Tenant configuration (vault / seed), not Host process
265
- env.
268
+ so their URLs must be reachable from it, and remote MCP servers. Each URL gets a
269
+ credential in the vault of the Environment a session names (`PUT
270
+ /v1/environments/{id}/secrets/{name}`), the operator previews an MCP server's tools
271
+ (`nylorun mcp inspect --environment <id>`),
272
+ and the manifest says which tools are enabled or deferred
273
+ ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)). Which calls wait for a
274
+ person, or are refused, is the Tenant's tool policy
275
+ ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#tool-policy)). Model gateway
276
+ and sandbox backend are Tenant configuration (the Tenant's own vault, Tenant
277
+ settings and seed), not Host process env.
266
278
 
267
279
  ## Session behaviour
268
280
 
@@ -273,11 +285,13 @@ definition/session APIs.
273
285
  Postgres transactions persist session checkpoints, command receipts, individual
274
286
  effects, waits and the record of session events — in the Tenant's database; history is
275
287
  read from Durable Streams (S2). An advance owns its session through a lease with
276
- an epoch; a Worker that takes over after a crash marks in-flight effects
277
- `uncertain`. Vault ciphertext needs that Tenant's own KEK.
288
+ an epoch; a Worker that takes over after a crash marks in-flight model effects
289
+ `uncertain`, and sends an interrupted MCP or HTTP tool call again with the same key (R5). Vault ciphertext needs that Tenant's own KEK.
278
290
 
279
- Agents that declare `.use(sandbox())` get Runtime-executed sandbox tools. The
280
- Tenant owns each sandbox; backend names are prefixed `nylorun-<tenant-id>-`.
291
+ A session that names an Environment of kind `virtual` or `pod` gets Runtime-executed
292
+ sandbox tools, pinned in its manifest's `runtime` block with `save_artifact`; the agent declares
293
+ nothing, and none of its own tools may take a Runtime tool's name. The Tenant owns each Environment; backend
294
+ names are prefixed `nylorun-<tenant-id>-`.
281
295
 
282
296
  ## Local Project workflow
283
297
 
@@ -297,6 +311,7 @@ the Project link (outside a project it runs the Tenant `default`); the project's
297
311
  | `database-layout-old` | The database holds `tenant_<id>` schemas of an older Runtime, or the `schema_version` tables of a pre-release one: point the Runtime at a new database (locally, a new Tenant: `nylorun start --tenant <new name>`); the old one is left as it is |
298
312
  | `426 protocol_unsupported` | Upgrade clients or Host to a compatible set |
299
313
  | `421 host_rejected` / `403 origin_rejected` | In a container, list the `Host` in `NYLORUN_ALLOWED_HOSTS`. From a browser, send a trusted issuer's token, never a key |
314
+ | `422 attachment_unsupported` | A message names a file the Tenant's model does not read (`details.parts` says which and why): choose a model that reads it, add the input with `PUT /v1/tenant/models/inputs` (or a custom endpoint's `settings.inputs`), or send it as another type |
300
315
  | `403 key_role_mismatch` | The key belongs to the other API: use a management key on `/v1/tenant/*` (`@nylorun/admin`) and an application key everywhere else (`@nylorun/agents`) |
301
316
  | `503` for a Tenant | Postgres or Restate is unreachable; `GET /ready` names which |
302
317
  | Port in use | Change `NYLORUN_PORT` in `~/.nylorun/tenants/<name>/docker/.env` and run `nylorun start` |
@@ -307,9 +322,53 @@ Definitions have no `agent.run()`; applications use `@nylorun/agents`.
307
322
  ## Session reads
308
323
 
309
324
  Hosts advertising `session-reads` serve pinned session manifests, recorded usage, model-call
310
- pages and opt-in session/history/sandbox pagination. `calls-export` adds the Management API's
325
+ pages and opt-in session/history/Environment pagination. `calls-export` adds the Management API's
311
326
  unfiltered, resumable model ledger export (`GET /v1/tenant/calls/model`, a management key) in
312
- safe transaction order. Existing list/history
327
+ safe transaction order. `tool-calls` adds `GET /v1/sessions/{id}/calls/tool`: each tool call of
328
+ the session from the effect journal, with its input, status and sends. Existing list/history
313
329
  requests without `limit` retain their response shapes. These are public APIs for Studio,
314
330
  CLI and custom clients; clients never need database access. See the
315
331
  [API contracts, authorization, SDK examples and Studio handoff](https://github.com/nylorun/agents/blob/main/runtime/docs/session-reads.md).
332
+
333
+ ## Artifact pages
334
+
335
+ Hosts advertising `artifact-reads` accept `GET /v1/artifacts?limit=50`, returning
336
+ `{ artifacts: ArtifactListItem[], nextCursor: string | null }`. An explicit `limit`
337
+ of 1–200 opts into paging; without it the existing `{ artifacts: ArtifactView[] }`
338
+ list remains oldest first. Pages order by `(createdAt DESC, artifactId DESC)` and
339
+ contain metadata only: id, kind, name, current content type, latest version number,
340
+ optional session and labels, and creation/update timestamps. Use the existing
341
+ artifact detail, numbered version and download APIs for history and bytes.
342
+
343
+ Paged reads accept equality filters `sessionId`, `kind=file|folder` and repeated
344
+ `label=key=value` (all labels must match). `sessionId` includes only that session's
345
+ artifacts, excluding Tenant-owned artifacts. An application key acting as itself
346
+ sees every artifact; subjects and issuer tokens see only artifacts of their owned
347
+ sessions and permitted agents. Missing or inaccessible session filters return 404.
348
+ Management keys cannot use this Runtime API route.
349
+
350
+ Send `nextCursor` as `cursor` with the same normalized filters until it is null.
351
+ Cursors are bound to the Tenant, route and filters; invalid or mismatched cursors
352
+ return 400 (`cursor_invalid` or `cursor_mismatch`). Each page reapplies current
353
+ authorization and is an independent read: it is not a frozen snapshot. Newer
354
+ inserts require a fresh traversal; deleted rows disappear and versions/metadata
355
+ may change between pages. Creation time and ID remain the paging position.
356
+
357
+ These pages reuse the separate bounded, read-only Drizzle pool (four connections,
358
+ two-second statement timeout). Migration `0023_artifact_pages` adds only the
359
+ creation-order index; the existing session index also serves session pages in
360
+ reverse order. No data backfill, protocol bump or direct client database access is
361
+ required. Studio can discover `artifact-reads` through `/health` before enabling
362
+ a tenant-wide table; `@nylorun/agents` exposes the same API as `artifacts.page()`.
363
+
364
+ [Recorded query plans](docs/artifact-pages-evidence.json) use a disposable local
365
+ Postgres 17 fixture: the public SDK drained 10,006 artifacts in 201 pages of at
366
+ most 50 rows, without gaps or duplicates, in 796 ms. First/deep page queries used
367
+ backward index scans with a tuple index condition on the deep page (0.014/0.028 ms).
368
+ These are fixture timings, not production throughput estimates. Reproduce after
369
+ building dependencies with:
370
+
371
+ ```sh
372
+ ARTIFACT_READS_EVIDENCE="$PWD/runtime/server/docs/artifact-pages-evidence.json" \
373
+ npm test --workspace @nylorun/runtime -- test/http/artifact-pages.test.ts
374
+ ```
@@ -314,7 +314,7 @@ export class RestateExecution {
314
314
  if ((await ctx.get(`at:${input.timer}`)) !== input.at)
315
315
  return;
316
316
  ctx.clear(`at:${input.timer}`);
317
- await runSandbox(ctx, input.timer === "retry" ? "reconcile" : input.timer);
317
+ await runSandbox(ctx, input.timer === "idle" ? "idle" : "reconcile");
318
318
  },
319
319
  },
320
320
  options: serviceOptions,
@@ -13,6 +13,8 @@ export interface PgoutputSourceOptions {
13
13
  publication?: string;
14
14
  /** The schema of the record tables. Default `nylorun_streams`. */
15
15
  schema?: string;
16
+ /** The connections' `application_name`. Default `nylorun-stream-relay`. */
17
+ applicationName?: string;
16
18
  /** Wait between attempts to take the slot. Default 5 s. */
17
19
  retryMs?: number;
18
20
  log?: (message: string, fields?: Record<string, unknown>) => void;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * The stream relay's change source on Postgres logical replication (Durable Streams §7.1,
3
3
  * §7.4, §7.6): a persistent `pgoutput` slot on the `nylorun_stream_relay` publication, read
4
- * with `pg-logical-replication`. The only file importing it (with `pg`, which it needs).
4
+ * with `pg-logical-replication`. The only file importing it (with `pg`, which it needs). The
5
+ * trace projector (R7.2) reads the same publication from a slot of its own
6
+ * (`traces/projector.ts`); for it a fresh slot means nothing to reconcile.
5
7
  *
6
8
  * - **One reader per slot.** Postgres lets one connection hold a slot, so every Runtime
7
9
  * process can run a source and exactly one is active; the others retry every few seconds
@@ -25,12 +27,13 @@ export function createPgoutputSource(options) {
25
27
  const publication = options.publication ?? "nylorun_stream_relay";
26
28
  const schema = options.schema ?? "nylorun_streams";
27
29
  const retryMs = options.retryMs ?? 5000;
30
+ const applicationName = options.applicationName ?? "nylorun-stream-relay";
28
31
  const { tenantId } = options;
29
32
  const log = options.log ?? (() => { });
30
33
  const pool = new pg.Pool({
31
34
  connectionString: options.connectionString,
32
35
  max: 2,
33
- application_name: "nylorun-stream-relay",
36
+ application_name: applicationName,
34
37
  });
35
38
  let service;
36
39
  let stopped = false;
@@ -72,7 +75,7 @@ export function createPgoutputSource(options) {
72
75
  // `stop()` during `prepareSlot` had no service to stop: do not start one it cannot.
73
76
  if (stopped)
74
77
  return;
75
- const current = new LogicalReplicationService({ connectionString: options.connectionString, application_name: "nylorun-stream-relay" }, { acknowledge: { auto: false, timeoutSeconds: 0 }, flowControl: { enabled: false } });
78
+ const current = new LogicalReplicationService({ connectionString: options.connectionString, application_name: applicationName }, { acknowledge: { auto: false, timeoutSeconds: 0 }, flowControl: { enabled: false } });
76
79
  service = current;
77
80
  let decoding;
78
81
  current.on("start", () => handlers.onActive({ fresh }));