@nylorun/runtime 0.26.0-beta → 0.28.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +18 -14
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.d.ts +4 -3
  12. package/dist/api/a2a/card.js +4 -4
  13. package/dist/api/a2a/endpoint.js +3 -4
  14. package/dist/api/a2a/protocol.js +4 -0
  15. package/dist/api/a2a/routes.d.ts +2 -6
  16. package/dist/api/a2a/routes.js +30 -25
  17. package/dist/api/ag-ui/endpoint.js +2 -3
  18. package/dist/api/ag-ui/routes.js +8 -11
  19. package/dist/api/components.d.ts +6789 -3405
  20. package/dist/api/components.js +43 -30
  21. package/dist/api/gate/keys.js +7 -4
  22. package/dist/api/gate/routes.d.ts +6 -2
  23. package/dist/api/gate/routes.js +15 -11
  24. package/dist/api/http/app.js +9 -4
  25. package/dist/api/http/body.d.ts +17 -3
  26. package/dist/api/http/body.js +35 -7
  27. package/dist/api/http/respond.js +6 -2
  28. package/dist/api/http/routes/access.js +5 -4
  29. package/dist/api/http/routes/agent-manifests.d.ts +10 -0
  30. package/dist/api/http/routes/agent-manifests.js +135 -0
  31. package/dist/api/http/routes/agents.d.ts +11 -0
  32. package/dist/api/http/routes/agents.js +418 -0
  33. package/dist/api/http/routes/artifacts.js +21 -9
  34. package/dist/api/http/routes/coverage.d.ts +4 -4
  35. package/dist/api/http/routes/coverage.js +13 -7
  36. package/dist/api/http/routes/files.d.ts +2 -1
  37. package/dist/api/http/routes/files.js +31 -5
  38. package/dist/api/http/routes/mcp.js +3 -3
  39. package/dist/api/http/routes/me.d.ts +1 -1
  40. package/dist/api/http/routes/me.js +1 -2
  41. package/dist/api/http/routes/memories.d.ts +9 -0
  42. package/dist/api/http/routes/memories.js +152 -0
  43. package/dist/api/http/routes/reads.d.ts +10 -111
  44. package/dist/api/http/routes/reads.js +51 -29
  45. package/dist/api/http/routes/sessions.js +26 -54
  46. package/dist/api/http/routes/tenant.d.ts +2 -2
  47. package/dist/api/http/routes/tenant.js +77 -24
  48. package/dist/api/http/routes/triggers.d.ts +10 -0
  49. package/dist/api/http/routes/triggers.js +180 -0
  50. package/dist/api/inbound-files.d.ts +3 -9
  51. package/dist/api/inbound-files.js +18 -24
  52. package/dist/api/openapi.js +73 -47
  53. package/dist/artifacts/disk.d.ts +10 -14
  54. package/dist/artifacts/disk.js +99 -95
  55. package/dist/artifacts/files.d.ts +13 -4
  56. package/dist/artifacts/files.js +27 -9
  57. package/dist/artifacts/folder-upload.d.ts +75 -0
  58. package/dist/artifacts/folder-upload.js +263 -0
  59. package/dist/artifacts/folders.d.ts +14 -8
  60. package/dist/artifacts/folders.js +19 -11
  61. package/dist/artifacts/links.js +7 -6
  62. package/dist/artifacts/parts.d.ts +9 -14
  63. package/dist/artifacts/parts.js +11 -9
  64. package/dist/artifacts/pipe.d.ts +15 -0
  65. package/dist/artifacts/pipe.js +71 -0
  66. package/dist/artifacts/quota.d.ts +36 -0
  67. package/dist/artifacts/quota.js +44 -0
  68. package/dist/artifacts/service.d.ts +25 -1
  69. package/dist/artifacts/service.js +72 -27
  70. package/dist/blob/fs.js +2 -1
  71. package/dist/blob/s3.d.ts +6 -2
  72. package/dist/blob/s3.js +9 -7
  73. package/dist/blob/types.d.ts +1 -1
  74. package/dist/blob/types.js +1 -1
  75. package/dist/core/flow-host.d.ts +0 -1
  76. package/dist/core/limits.d.ts +11 -35
  77. package/dist/core/limits.js +5 -73
  78. package/dist/core/sandbox-routes.js +0 -1
  79. package/dist/core/turn-manifest.d.ts +1 -2
  80. package/dist/core/turn-manifest.js +4 -28
  81. package/dist/execution/memory.d.ts +8 -5
  82. package/dist/execution/memory.js +8 -6
  83. package/dist/execution/types.d.ts +1 -6
  84. package/dist/execution/types.js +0 -5
  85. package/dist/gates/contract.d.ts +3 -2
  86. package/dist/gates/contract.js +1 -1
  87. package/dist/gates/egress-credentials.js +5 -4
  88. package/dist/gates/egress.d.ts +13 -13
  89. package/dist/gates/egress.js +19 -21
  90. package/dist/gates/fixture-model.js +13 -2
  91. package/dist/gates/handler.d.ts +1 -3
  92. package/dist/gates/handler.js +4 -3
  93. package/dist/gates/http-client.d.ts +4 -3
  94. package/dist/gates/http-client.js +5 -7
  95. package/dist/gates/http-tool.d.ts +24 -4
  96. package/dist/gates/http-tool.js +41 -13
  97. package/dist/gates/in-process.d.ts +2 -1
  98. package/dist/gates/in-process.js +17 -9
  99. package/dist/gates/inflight.d.ts +2 -2
  100. package/dist/gates/inflight.js +3 -2
  101. package/dist/gates/journal.js +5 -7
  102. package/dist/gates/mcp-handler.d.ts +4 -3
  103. package/dist/gates/mcp-handler.js +15 -5
  104. package/dist/gates/meter.js +6 -5
  105. package/dist/gates/model-gate.d.ts +11 -2
  106. package/dist/gates/policy.d.ts +34 -6
  107. package/dist/gates/policy.js +66 -18
  108. package/dist/gates/provider-gate.d.ts +22 -0
  109. package/dist/gates/provider-gate.js +30 -0
  110. package/dist/gates/tool-calls.d.ts +1 -3
  111. package/dist/gates/tool-calls.js +2 -3
  112. package/dist/gates/tool-client.d.ts +1 -1
  113. package/dist/gates/tool-client.js +5 -6
  114. package/dist/gates/tool-contract.d.ts +8 -5
  115. package/dist/gates/tool-contract.js +1 -1
  116. package/dist/gates/tool-gate.d.ts +18 -7
  117. package/dist/gates/tool-gate.js +49 -7
  118. package/dist/harness/calls.d.ts +27 -25
  119. package/dist/harness/calls.js +40 -46
  120. package/dist/harness/executors.d.ts +10 -3
  121. package/dist/harness/executors.js +53 -23
  122. package/dist/harness/main.js +2 -2
  123. package/dist/harness/pod.d.ts +2 -2
  124. package/dist/harness/pod.js +7 -6
  125. package/dist/harness/run-tokens.d.ts +4 -1
  126. package/dist/harness/run-tokens.js +6 -2
  127. package/dist/harness/service.d.ts +17 -14
  128. package/dist/harness/service.js +21 -35
  129. package/dist/harness/setup.d.ts +0 -2
  130. package/dist/harness/setup.js +21 -15
  131. package/dist/harness/ws-client.d.ts +2 -2
  132. package/dist/harness/ws-client.js +7 -5
  133. package/dist/harness/ws-port.d.ts +3 -4
  134. package/dist/harness/ws-port.js +3 -4
  135. package/dist/harness-api/artifacts.d.ts +20 -0
  136. package/dist/harness-api/artifacts.js +478 -0
  137. package/dist/harness-api/in-process.d.ts +2 -2
  138. package/dist/harness-api/in-process.js +18 -16
  139. package/dist/harness-api/record.d.ts +10 -9
  140. package/dist/harness-api/record.js +113 -157
  141. package/dist/harness-api/renew.d.ts +1 -1
  142. package/dist/harness-api/renew.js +2 -2
  143. package/dist/harness-api/server.d.ts +1 -16
  144. package/dist/harness-api/server.js +86 -73
  145. package/dist/harness-api/start.d.ts +27 -5
  146. package/dist/harness-api/start.js +102 -19
  147. package/dist/harness-api/tool-journal.d.ts +15 -10
  148. package/dist/harness-api/tool-journal.js +26 -24
  149. package/dist/harness-api/workspace.d.ts +0 -4
  150. package/dist/harness-api/workspace.js +4 -47
  151. package/dist/harness-api/ws-server.js +10 -10
  152. package/dist/host/create-host.js +2 -3
  153. package/dist/host/execution.js +2 -3
  154. package/dist/host/gates.d.ts +2 -9
  155. package/dist/host/gates.js +15 -12
  156. package/dist/host/http.d.ts +2 -4
  157. package/dist/host/http.js +5 -29
  158. package/dist/host/main.js +10 -3
  159. package/dist/host/operate.js +12 -3
  160. package/dist/host/stack-config.d.ts +11 -0
  161. package/dist/host/stack-config.js +21 -1
  162. package/dist/index.d.ts +0 -1
  163. package/dist/index.js +0 -1
  164. package/dist/infra/index.js +2 -1
  165. package/dist/infra/readiness.d.ts +0 -2
  166. package/dist/infra/readiness.js +4 -3
  167. package/dist/keys/client.d.ts +1 -0
  168. package/dist/keys/client.js +2 -3
  169. package/dist/keys/contract.d.ts +0 -2
  170. package/dist/keys/contract.js +0 -2
  171. package/dist/keys/egress-ca.d.ts +0 -2
  172. package/dist/keys/egress-ca.js +2 -3
  173. package/dist/keys/keys.d.ts +15 -4
  174. package/dist/keys/keys.js +22 -1
  175. package/dist/keys/webhooks.d.ts +25 -0
  176. package/dist/keys/webhooks.js +62 -0
  177. package/dist/keys/x509.d.ts +8 -4
  178. package/dist/keys/x509.js +14 -5
  179. package/dist/management-openapi.json +3043 -1954
  180. package/dist/mcp/connect.d.ts +35 -26
  181. package/dist/mcp/connect.js +112 -141
  182. package/dist/mcp/pool.d.ts +27 -22
  183. package/dist/mcp/pool.js +34 -48
  184. package/dist/mcp/preview.d.ts +12 -5
  185. package/dist/mcp/preview.js +43 -26
  186. package/dist/mcp/snapshot.d.ts +35 -86
  187. package/dist/mcp/snapshot.js +93 -135
  188. package/dist/memory/service.d.ts +97 -0
  189. package/dist/memory/service.js +335 -0
  190. package/dist/memory/tool.d.ts +36 -0
  191. package/dist/memory/tool.js +84 -0
  192. package/dist/model/auth-store.js +5 -3
  193. package/dist/model/capabilities.d.ts +1 -1
  194. package/dist/model/catalog.d.ts +9 -36
  195. package/dist/model/catalog.js +6 -33
  196. package/dist/model/descriptor.d.ts +51 -0
  197. package/dist/model/descriptor.js +83 -0
  198. package/dist/model/models.d.ts +0 -3
  199. package/dist/model/models.js +3 -5
  200. package/dist/model/native-blocks.js +1 -1
  201. package/dist/model/pi-model.d.ts +16 -5
  202. package/dist/model/pi-model.js +86 -105
  203. package/dist/openapi.json +13653 -9986
  204. package/dist/reads/cursor.js +3 -1
  205. package/dist/reads/types.d.ts +3 -3
  206. package/dist/record/agent.d.ts +27 -0
  207. package/dist/record/agent.js +46 -0
  208. package/dist/record/append.d.ts +34 -2
  209. package/dist/record/append.js +50 -1
  210. package/dist/record/index.d.ts +2 -2
  211. package/dist/record/index.js +2 -2
  212. package/dist/redact.d.ts +1 -1
  213. package/dist/redact.js +3 -2
  214. package/dist/sandbox/egress-token.d.ts +3 -3
  215. package/dist/sandbox/egress-token.js +3 -3
  216. package/dist/sandbox/join.js +8 -7
  217. package/dist/sandbox/manager.d.ts +41 -41
  218. package/dist/sandbox/manager.js +112 -44
  219. package/dist/sandbox/placement.d.ts +2 -2
  220. package/dist/sandbox/placement.js +7 -10
  221. package/dist/sandbox/pods/client.d.ts +1 -10
  222. package/dist/sandbox/pods/client.js +8 -4
  223. package/dist/sandbox/pods/lifecycle.d.ts +25 -17
  224. package/dist/sandbox/pods/lifecycle.js +51 -29
  225. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  226. package/dist/sandbox/pods/network-gate.js +7 -7
  227. package/dist/sandbox/pods/reconcile.js +14 -3
  228. package/dist/sandbox/pods/spec.js +8 -5
  229. package/dist/sandbox/policy.d.ts +7 -1
  230. package/dist/sandbox/policy.js +10 -3
  231. package/dist/sandbox/resolve.d.ts +11 -6
  232. package/dist/sandbox/resolve.js +19 -9
  233. package/dist/sandbox/setup.d.ts +6 -2
  234. package/dist/sandbox/setup.js +47 -14
  235. package/dist/sandbox/share.d.ts +5 -14
  236. package/dist/sandbox/share.js +5 -34
  237. package/dist/sandbox/skills.d.ts +7 -13
  238. package/dist/sandbox/skills.js +16 -31
  239. package/dist/sandbox/tenant-config.d.ts +2 -2
  240. package/dist/sandbox/tenant-config.js +4 -3
  241. package/dist/sandbox/tools.d.ts +7 -1
  242. package/dist/sandbox/tools.js +26 -26
  243. package/dist/sandbox/types.d.ts +10 -3
  244. package/dist/sandbox/types.js +9 -0
  245. package/dist/store/postgres/connect.d.ts +6 -5
  246. package/dist/store/postgres/connect.js +14 -11
  247. package/dist/store/postgres/control.js +13 -13
  248. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  249. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  250. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  251. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  252. package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
  253. package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
  254. package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
  255. package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
  256. package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
  257. package/dist/store/postgres/drizzle/meta/_journal.json +63 -0
  258. package/dist/store/postgres/reads.js +17 -82
  259. package/dist/store/postgres/record-writer.d.ts +4 -4
  260. package/dist/store/postgres/record-writer.js +9 -9
  261. package/dist/store/postgres/schema.d.ts +1382 -151
  262. package/dist/store/postgres/schema.js +191 -37
  263. package/dist/store/postgres/store.js +384 -105
  264. package/dist/store/postgres/tenant.js +1 -2
  265. package/dist/store/types.d.ts +172 -57
  266. package/dist/store/types.js +0 -1
  267. package/dist/streams/relay/core.d.ts +3 -3
  268. package/dist/streams/relay/core.js +9 -10
  269. package/dist/tenant/advance.js +41 -39
  270. package/dist/tenant/agent-manifests.d.ts +72 -0
  271. package/dist/tenant/agent-manifests.js +359 -0
  272. package/dist/tenant/agent-versions.d.ts +126 -0
  273. package/dist/tenant/agent-versions.js +389 -0
  274. package/dist/tenant/agents.d.ts +230 -0
  275. package/dist/tenant/{environments.js → agents.js} +493 -328
  276. package/dist/tenant/auth.d.ts +1 -1
  277. package/dist/tenant/auth.js +3 -3
  278. package/dist/tenant/commands.d.ts +63 -2
  279. package/dist/tenant/commands.js +240 -90
  280. package/dist/tenant/context.d.ts +57 -33
  281. package/dist/tenant/context.js +6 -2
  282. package/dist/tenant/definition-files.d.ts +25 -4
  283. package/dist/tenant/definition-files.js +85 -47
  284. package/dist/tenant/effects.js +15 -51
  285. package/dist/tenant/ephemeral.js +2 -1
  286. package/dist/tenant/history.d.ts +1 -1
  287. package/dist/tenant/history.js +1 -1
  288. package/dist/tenant/host-token.d.ts +3 -3
  289. package/dist/tenant/host-token.js +11 -10
  290. package/dist/tenant/http.d.ts +5 -8
  291. package/dist/tenant/http.js +9 -0
  292. package/dist/tenant/identity-file.d.ts +29 -6
  293. package/dist/tenant/identity-file.js +45 -15
  294. package/dist/tenant/issuers.d.ts +5 -5
  295. package/dist/tenant/issuers.js +34 -26
  296. package/dist/tenant/jwt.d.ts +8 -2
  297. package/dist/tenant/jwt.js +10 -2
  298. package/dist/tenant/limits.d.ts +87 -0
  299. package/dist/tenant/limits.js +172 -0
  300. package/dist/tenant/memories.d.ts +38 -0
  301. package/dist/tenant/memories.js +119 -0
  302. package/dist/tenant/module.d.ts +4 -1
  303. package/dist/tenant/module.js +9 -8
  304. package/dist/tenant/outbound.d.ts +11 -14
  305. package/dist/tenant/outbound.js +10 -15
  306. package/dist/tenant/reset.js +2 -2
  307. package/dist/tenant/run-grants.d.ts +6 -0
  308. package/dist/tenant/run-grants.js +7 -2
  309. package/dist/tenant/run-token.d.ts +0 -4
  310. package/dist/tenant/run-token.js +6 -11
  311. package/dist/tenant/runtime-limits.d.ts +24 -0
  312. package/dist/tenant/runtime-limits.js +27 -0
  313. package/dist/tenant/runtime.d.ts +1 -1
  314. package/dist/tenant/runtime.js +38 -43
  315. package/dist/tenant/session-streams.js +7 -6
  316. package/dist/tenant/session.d.ts +1 -1
  317. package/dist/tenant/session.js +1 -1
  318. package/dist/tenant/sessions.d.ts +28 -45
  319. package/dist/tenant/sessions.js +89 -209
  320. package/dist/tenant/signing-keys.d.ts +1 -1
  321. package/dist/tenant/signing-keys.js +5 -6
  322. package/dist/tenant/status.js +10 -10
  323. package/dist/tenant/streams.js +18 -20
  324. package/dist/tenant/sweep.d.ts +1 -21
  325. package/dist/tenant/sweep.js +51 -51
  326. package/dist/tenant/transcript.d.ts +70 -23
  327. package/dist/tenant/transcript.js +28 -22
  328. package/dist/tenant/triggers.d.ts +143 -0
  329. package/dist/tenant/triggers.js +715 -0
  330. package/dist/tenant/turn-timing.d.ts +11 -1
  331. package/dist/tenant/turn-timing.js +3 -1
  332. package/dist/tenant/types.d.ts +9 -14
  333. package/dist/tenant/worker.d.ts +4 -10
  334. package/dist/tenant/worker.js +10 -14
  335. package/dist/traces/ids.js +5 -1
  336. package/dist/traces/map.d.ts +10 -7
  337. package/dist/traces/map.js +16 -12
  338. package/dist/traces/otlp.d.ts +3 -3
  339. package/dist/traces/otlp.js +5 -3
  340. package/dist/traces/projector.d.ts +9 -4
  341. package/dist/traces/projector.js +13 -7
  342. package/dist/traces/read.js +6 -1
  343. package/dist/tuning.d.ts +390 -0
  344. package/dist/tuning.js +677 -0
  345. package/dist/vault/headers.js +4 -2
  346. package/dist/vault/service.d.ts +13 -8
  347. package/dist/vault/service.js +61 -17
  348. package/dist/version.d.ts +1 -1
  349. package/dist/version.js +1 -1
  350. package/package.json +13 -12
  351. package/dist/api/http/routes/environment-manifests.d.ts +0 -9
  352. package/dist/api/http/routes/environment-manifests.js +0 -118
  353. package/dist/api/http/routes/environments.d.ts +0 -11
  354. package/dist/api/http/routes/environments.js +0 -332
  355. package/dist/artifacts/export.d.ts +0 -8
  356. package/dist/artifacts/export.js +0 -204
  357. package/dist/artifacts/media-types.d.ts +0 -30
  358. package/dist/artifacts/media-types.js +0 -137
  359. package/dist/artifacts/workspace.d.ts +0 -60
  360. package/dist/artifacts/workspace.js +0 -45
  361. package/dist/media.d.ts +0 -10
  362. package/dist/media.js +0 -58
  363. package/dist/record/environment.d.ts +0 -25
  364. package/dist/record/environment.js +0 -44
  365. package/dist/sandbox/runtime-tools.d.ts +0 -9
  366. package/dist/sandbox/runtime-tools.js +0 -16
  367. package/dist/sandbox/session-sandbox.d.ts +0 -16
  368. package/dist/sandbox/session-sandbox.js +0 -77
  369. package/dist/tenant/artifact-tool.d.ts +0 -13
  370. package/dist/tenant/artifact-tool.js +0 -243
  371. package/dist/tenant/environment-manifests.d.ts +0 -57
  372. package/dist/tenant/environment-manifests.js +0 -200
  373. package/dist/tenant/environments.d.ts +0 -174
  374. package/dist/tenant/skill-tool.d.ts +0 -20
  375. package/dist/tenant/skill-tool.js +0 -67
  376. package/dist/tenant/tool-results.d.ts +0 -57
  377. package/dist/tenant/tool-results.js +0 -298
  378. package/dist/tenant/tool-search.d.ts +0 -39
  379. package/dist/tenant/tool-search.js +0 -103
@@ -47,15 +47,16 @@ import { endAdvance } from "./scheduler.js";
47
47
  import { harnessOf } from "./context.js";
48
48
  import { HARNESS_ID } from "../sandbox/placement.js";
49
49
  import { recoversModelCalls } from "./effects.js";
50
+ import { contextCompacted } from "./transcript.js";
50
51
  import { slimModelEffects } from "./slim.js";
51
52
  import { applyUpdates, leanState, transcriptShadow, withTranscript, } from "./history.js";
52
- import { command } from "./commands.js";
53
- import { exportOutputs } from "../artifacts/export.js";
54
- import { dropRunGrant, grantRun } from "./run-grants.js";
53
+ import { command, startQueuedTurn } from "./commands.js";
54
+ import { dropRunGrant, grantRun, rootAgentOf } from "./run-grants.js";
55
55
  import { startHeartbeat } from "../harness-api/renew.js";
56
56
  import { buildTurnStart, startSegment } from "../harness-api/start.js";
57
- import { touchEnvironment } from "./environments.js";
57
+ import { touchAgent } from "./agents.js";
58
58
  import { endTurn, pauseTurn } from "./turn-timing.js";
59
+ import { hostTuning } from "../tuning.js";
59
60
  import { AdvanceAbort, AdvanceDeadlineError, abortKind, } from "./worker.js";
60
61
  /**
61
62
  * A segment stopped by a `shutdown` or `ownership.lost` abort before it settled. Never
@@ -95,15 +96,6 @@ class HarnessUnavailable extends Error {
95
96
  }
96
97
  }
97
98
  const DONE = { status: "done" };
98
- /** How soon an advance whose segment no harness took is tried again. */
99
- const UNAVAILABLE_RETRY_MS = 1000;
100
- /**
101
- * How long a pod sandbox's segment waits for its engine (F7.2): a pod that is starting, or
102
- * resuming, takes longer than a harness that is already connected.
103
- */
104
- const POD_OFFER_WAIT_MS = 60_000;
105
- /** Shortest `busy` retry, so a lease about to lapse is not polled in a tight loop. */
106
- const MIN_RETRY_MS = 25;
107
99
  /**
108
100
  * Advance session `id` if it has a runnable checkpoint (`WorkerHandlers.advance`). Returns
109
101
  * `busy` when another advance holds a live lease, `done` otherwise.
@@ -115,7 +107,10 @@ export async function advance(ctx, id, signal) {
115
107
  if (taken.status === "busy")
116
108
  return {
117
109
  status: "busy",
118
- retryAfterMs: Math.min(Math.max(Date.parse(taken.ownerExpiresAt) - Date.now(), MIN_RETRY_MS), ctx.ownerLeaseMs),
110
+ retryAfterMs: Math.min(
111
+ // The shortest `busy` retry (`tenant.busyRetryMinMs`), so a lease about to lapse is not
112
+ // polled in a tight loop.
113
+ Math.max(Date.parse(taken.ownerExpiresAt) - Date.now(), hostTuning().tenant.busyRetryMinMs), ctx.ownerLeaseMs),
119
114
  };
120
115
  if (taken.status === "idle")
121
116
  return DONE;
@@ -129,7 +124,10 @@ export async function advance(ctx, id, signal) {
129
124
  ctx.work.running.set(id, controller);
130
125
  ctx.work.runningTurns.set(id, taken.session.activeTurnId ?? null);
131
126
  // A harness renews the lease once it holds the run; until then, and without one, core does.
132
- const run = { agentId: taken.session.agentId, activeTurnId: taken.session.activeTurnId };
127
+ // The run token names the root agent as the engine does: the loop manifest's id, i.e. the
128
+ // agent manifest's name (protocol 16), never the Agent the session runs on. The model gate's
129
+ // usage rows and `manifest` budgets key on it.
130
+ const run = { agentId: rootAgentOf(taken.session), activeTurnId: taken.session.activeTurnId };
133
131
  const heartbeat = startHeartbeat(ctx, lease, controller, run);
134
132
  let result = DONE;
135
133
  let release = true;
@@ -152,7 +150,7 @@ export async function advance(ctx, id, signal) {
152
150
  sessionId: id,
153
151
  epoch: lease.epoch,
154
152
  });
155
- result = { status: "busy", retryAfterMs: UNAVAILABLE_RETRY_MS };
153
+ result = { status: "busy", retryAfterMs: hostTuning().tenant.unavailableRetryMs };
156
154
  }
157
155
  else if (error instanceof SegmentStopped && error.kind === "shutdown") {
158
156
  // Left for the next advance, which resumes from the checkpoint once this one releases.
@@ -186,7 +184,7 @@ export async function advance(ctx, id, signal) {
186
184
  // The Environment was used: `deleteAfter`, and a pod's idle time (D34), count from the end
187
185
  // of each segment.
188
186
  if (placed.environment !== undefined)
189
- await touchEnvironment(ctx, placed.environment);
187
+ await touchAgent(ctx, placed.environment);
190
188
  }
191
189
  return result;
192
190
  }
@@ -199,11 +197,11 @@ function podUnusable(ctx, segment) {
199
197
  return `Environment ${pod.id} is a pod Environment, and this Runtime has no sandbox pods`;
200
198
  const state = pod.pod;
201
199
  if (state.desired === "deleted")
202
- return `Environment ${pod.id} was deleted`;
200
+ return `Agent ${pod.id} was deleted`;
203
201
  if (state.observed === "lost")
204
- return `Environment ${pod.id} was lost: ${state.reason ?? "its volume is gone"}`;
202
+ return `Agent ${pod.id} was lost: ${state.reason ?? "its volume is gone"}`;
205
203
  if (state.observed === "failed")
206
- return `Environment ${pod.id} failed to start: ${state.reason ?? "its pod was not ready"}`;
204
+ return `Agent ${pod.id} failed to start: ${state.reason ?? "its pod was not ready"}`;
207
205
  return undefined;
208
206
  }
209
207
  /**
@@ -290,6 +288,10 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
290
288
  // A new volume being seeded from a disk version takes no turn until it holds it (R2d step 3).
291
289
  if (segment.pod?.seeding)
292
290
  throw new HarnessUnavailable();
291
+ // A pod starting on a new version takes no turn until its harness joins (track T3): a start
292
+ // that fails goes back to the version it ran, and the turn's first segment re-pins to it.
293
+ if (segment.pod?.starting)
294
+ throw new HarnessUnavailable();
293
295
  const pod = segment.pod?.id;
294
296
  if (started.environmentId !== undefined)
295
297
  placed.environment = started.environmentId;
@@ -302,7 +304,8 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
302
304
  onTaken,
303
305
  // Only a connection of the session's harness takes its runs (R4.1).
304
306
  harness: harnessOf(segment.current),
305
- ...(pod === undefined ? {} : { pod, waitMs: POD_OFFER_WAIT_MS }),
307
+ // A pod that is starting, or resuming, takes longer than a harness already connected.
308
+ ...(pod === undefined ? {} : { pod, waitMs: hostTuning().harnessApi.podOfferWaitMs }),
306
309
  });
307
310
  if (end.kind === "unavailable") {
308
311
  // A pod stopped, or not yet started: make sure it is asked to run.
@@ -313,9 +316,6 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
313
316
  // Aborted before a harness took it: settled as an abort of the engine would be.
314
317
  if (end.kind === "aborted")
315
318
  throw signal.reason;
316
- // The harness's own tools take names of this turn's tools (Harness API 3): the turn fails.
317
- if (end.kind === "refused")
318
- throw new Error(end.message);
319
319
  // Checked before the abort: whatever core's signal says, the run's effects are unaccounted for.
320
320
  if (end.kind === "released" && end.reason === "connection.lost")
321
321
  throw new RunLost();
@@ -333,7 +333,7 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
333
333
  await settleFailure(ctx, lease, started, signal.reason);
334
334
  else {
335
335
  const recorded = result.status === "yielded" || result.status === "completed" || result.status === "paused";
336
- cursor = await settleTurn(ctx, lease, started, result, recorded ? output.transcript ?? [] : [], signal, segment.cursor,
336
+ cursor = await settle(ctx, lease, started, result, recorded ? output.transcript ?? [] : [], segment.cursor,
337
337
  // The turn's end and its transcript are what the harness reported (R4.9).
338
338
  { source: { kind: "harness", id: harnessOf(started) }, evidence: "claimed", runId: output.runId });
339
339
  }
@@ -419,17 +419,6 @@ async function wakeWorkflowOf(ctx, t, agent, turnId, type, payload) {
419
419
  error: "Agent turn was cancelled",
420
420
  });
421
421
  }
422
- /**
423
- * `settle`, then, for a completed turn, the turn-end export of its sandbox outputs (F8.2): after
424
- * the turn has committed and while the advance still holds the lease. The harness hears back
425
- * after the export. The export never throws.
426
- */
427
- async function settleTurn(ctx, lease, s, result, updates, signal, cursor, writer) {
428
- const kept = await settle(ctx, lease, s, result, updates, cursor, writer);
429
- if (result.status === "completed")
430
- await exportOutputs(ctx, lease.sessionId, s.activeTurnId, signal);
431
- return kept;
432
- }
433
422
  /**
434
423
  * Record a segment result, then cancel failed siblings and wake a linked workflow. Returns the
435
424
  * cursor of the transcript it recorded (`cursor` when it recorded no edit), or undefined when
@@ -580,6 +569,9 @@ writer) {
580
569
  await t.event(id, s.activeTurnId, type, payload, writer);
581
570
  await t.put("sessions", id, current);
582
571
  await wakeWorkflowOf(ctx, t, current, s.activeTurnId, type, payload);
572
+ // Messages that waited for this turn start the next one (protocol 17).
573
+ if (await startQueuedTurn(ctx, t, current, id))
574
+ await t.put("sessions", id, current);
583
575
  return siblings;
584
576
  });
585
577
  for (const agentId of siblingCancelIds) {
@@ -613,6 +605,10 @@ async function heldCalls(t, effectIds) {
613
605
  const request = row.request;
614
606
  const context = (request.context ?? {});
615
607
  const toolName = request.toolName ?? request.key ?? effectId;
608
+ // A person approves the tool by its identity (protocol 15): an MCP tool by server and tool.
609
+ const what = row.server !== undefined && row.tool !== undefined && row.toolKind === "mcp"
610
+ ? `${row.server} / ${row.tool}`
611
+ : (row.tool ?? toolName);
616
612
  const reason = row.hold?.reason ?? "The Tenant's tool policy asks a person about this call";
617
613
  held.push({
618
614
  invocationId: typeof context.invocationId === "string" ? context.invocationId : effectId,
@@ -620,9 +616,10 @@ async function heldCalls(t, effectIds) {
620
616
  interaction: {
621
617
  id: effectId,
622
618
  kind: "approval",
623
- prompt: `Approve ${toolName}?`,
619
+ prompt: `Approve ${what}?`,
624
620
  reason,
625
621
  ...(row.server === undefined ? {} : { server: row.server }),
622
+ ...(row.tool === undefined ? {} : { tool: row.tool }),
626
623
  input: request.input ?? null,
627
624
  },
628
625
  status: "interaction",
@@ -649,6 +646,8 @@ async function settleFailure(ctx, lease, s, error) {
649
646
  current.activeTurnId = null;
650
647
  await t.put("sessions", id, current);
651
648
  await wakeWorkflowOf(ctx, t, current, s.activeTurnId, "turn.failed", payload);
649
+ if (await startQueuedTurn(ctx, t, current, id))
650
+ await t.put("sessions", id, current);
652
651
  });
653
652
  }
654
653
  /** Whether the session runs our harness, whose state core may store lean (R4.2). */
@@ -668,13 +667,16 @@ function leanCheckpoint(session, checkpoint) {
668
667
  return { ...checkpoint, state: leanOf(session, state) };
669
668
  }
670
669
  /**
671
- * Appends a segment's transcript edits; a snapshot moves `history.snapshot`. Returns the seq of
672
- * the last edit, if any.
670
+ * Appends a segment's transcript edits; a snapshot moves `history.snapshot`. An edit the harness
671
+ * marks as a compaction (`kind: "compaction"`, Q4) also writes `context.compacted`, from its facts
672
+ * and the summary calls it names. Returns the seq of the last edit, if any.
673
673
  */
674
674
  async function recordTranscript(t, id, turnId, s, updates, writer) {
675
675
  let last;
676
676
  for (const update of updates) {
677
677
  const event = await t.event(id, turnId, "transcript.updated", update, writer);
678
+ if ("compaction" in update)
679
+ await t.event(id, turnId, "context.compacted", await contextCompacted(t, id, turnId, update.compaction), writer);
678
680
  if (update.keep === 0)
679
681
  s.history = { from: s.history?.from ?? event.seq, snapshot: event.seq };
680
682
  last = event.seq;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Agent manifests (protocol 16, track T2): `/v1/agent-manifests/{name}`, named and versioned
3
+ * documents with what runs inside an agent's loop (`harness`) and what the Runtime provides
4
+ * around it (`runtime`). They replace agent definitions (`PUT /v1/agents/:id` before protocol
5
+ * 16) and environment manifests.
6
+ *
7
+ * - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged; any
8
+ * other writes the next version. A version is never changed or deleted alone.
9
+ * - **Tracking Agents move.** Saving a version moves every Agent that tracks the manifest to it
10
+ * (track T3, `moveAgent`), in the save's transaction: at once, at its pod's next start, or once
11
+ * its required secrets are set. A pinned Agent stays, and so does one the version cannot move
12
+ * (a fixed field) or whose start failed on it. The plan counts both.
13
+ * - **Both sections at once.** The Runtime sees both sections, so it checks them against each
14
+ * other (`sectionGaps`): every gap is named in one `422 manifest_inconsistent`, which is what
15
+ * `requires` checked when a session opened before protocol 16. Each section's own checks run
16
+ * too: the harness's (no code tools, no approvals, HTTP tool timeouts, the skill files) and the
17
+ * runtime's (the Tenant's limits, its files).
18
+ * - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
19
+ * changes from the latest by section, every reason the Runtime would refuse it, warnings, the
20
+ * Agents it moves and how many stay on older versions. `If-Match: <version>` refuses
21
+ * (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
22
+ * for a manifest that does not exist yet.
23
+ * - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
24
+ */
25
+ import { type AgentManifestChange, type AgentManifestPlan, type AgentManifestV8, type AgentManifestVersions, type AgentManifestView, type PutAgentManifestRequest, type SaveAgentManifestResponse } from "@nylorun/core/contracts";
26
+ import { type RecipeRequest } from "../sandbox/resolve.js";
27
+ import type { AuthScope, TenantContext } from "./context.js";
28
+ /** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
29
+ export declare function manifestRecipeRequest(manifest: Pick<AgentManifestV8, "harness" | "runtime">): RecipeRequest;
30
+ /** The hash a manifest's version records: equal manifests hash the same. */
31
+ export declare function agentManifestHash(manifest: AgentManifestV8): string;
32
+ /** The owner a version's definition files are recorded under, for the sweep. */
33
+ export declare function manifestFileOwner(name: string): string;
34
+ /** Each field that differs between `before` and `after`, by path; arrays compare whole. */
35
+ export declare function manifestChanges(before: unknown, after: unknown, path?: string): {
36
+ path: string;
37
+ from?: unknown;
38
+ to?: unknown;
39
+ }[];
40
+ /**
41
+ * The changes from `before` to `after` by section: `manifest` for the top-level fields, else
42
+ * `harness` or `runtime` with the path inside it. A section added or removed whole is one change.
43
+ */
44
+ export declare function sectionChanges(before: Partial<AgentManifestV8> | undefined, after: AgentManifestV8): AgentManifestChange[];
45
+ /**
46
+ * `PUT /v1/agent-manifests/{name}`: saves `body` as manifest `name`'s next version, or answers
47
+ * the latest unchanged; with `dryRun`, the plan of that save and nothing saved.
48
+ */
49
+ export declare function saveAgentManifest(ctx: TenantContext, name: string, body: PutAgentManifestRequest, scope: AuthScope, options: {
50
+ dryRun: true;
51
+ ifMatch?: number;
52
+ }): Promise<AgentManifestPlan>;
53
+ export declare function saveAgentManifest(ctx: TenantContext, name: string, body: PutAgentManifestRequest, scope: AuthScope, options?: {
54
+ dryRun?: false;
55
+ ifMatch?: number;
56
+ }): Promise<SaveAgentManifestResponse>;
57
+ /** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
58
+ export declare function getAgentManifest(ctx: TenantContext, name: string, version?: number): Promise<AgentManifestView>;
59
+ /** `GET /v1/agent-manifests`: each manifest's latest version. */
60
+ export declare function listAgentManifests(ctx: TenantContext): Promise<{
61
+ manifests: AgentManifestView[];
62
+ }>;
63
+ /** `GET /v1/agent-manifests/{name}/versions`: every version, newest first. */
64
+ export declare function agentManifestVersions(ctx: TenantContext, name: string): Promise<AgentManifestVersions>;
65
+ /**
66
+ * `DELETE /v1/agent-manifests/{name}`: deletes every version, refused while any Agent was made
67
+ * from one (`409 manifest_in_use`): the Agent names the version it runs.
68
+ */
69
+ export declare function deleteAgentManifest(ctx: TenantContext, name: string): Promise<{
70
+ name: string;
71
+ deleted: boolean;
72
+ }>;
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Agent manifests (protocol 16, track T2): `/v1/agent-manifests/{name}`, named and versioned
3
+ * documents with what runs inside an agent's loop (`harness`) and what the Runtime provides
4
+ * around it (`runtime`). They replace agent definitions (`PUT /v1/agents/:id` before protocol
5
+ * 16) and environment manifests.
6
+ *
7
+ * - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged; any
8
+ * other writes the next version. A version is never changed or deleted alone.
9
+ * - **Tracking Agents move.** Saving a version moves every Agent that tracks the manifest to it
10
+ * (track T3, `moveAgent`), in the save's transaction: at once, at its pod's next start, or once
11
+ * its required secrets are set. A pinned Agent stays, and so does one the version cannot move
12
+ * (a fixed field) or whose start failed on it. The plan counts both.
13
+ * - **Both sections at once.** The Runtime sees both sections, so it checks them against each
14
+ * other (`sectionGaps`): every gap is named in one `422 manifest_inconsistent`, which is what
15
+ * `requires` checked when a session opened before protocol 16. Each section's own checks run
16
+ * too: the harness's (no code tools, no approvals, HTTP tool timeouts, the skill files) and the
17
+ * runtime's (the Tenant's limits, its files).
18
+ * - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
19
+ * changes from the latest by section, every reason the Runtime would refuse it, warnings, the
20
+ * Agents it moves and how many stay on older versions. `If-Match: <version>` refuses
21
+ * (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
22
+ * for a manifest that does not exist yet.
23
+ * - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
24
+ */
25
+ import { definitionFilesOf, } from "@nylorun/core/contracts";
26
+ import { hashManifest } from "@nylorun/core/compatibility";
27
+ import { approvalRefusal, codeToolRefusal, harnessIdOf, loopManifestOf, runtimeOf, sectionGaps, } from "@nylorun/core/define";
28
+ import { canonical } from "../store/canonical.js";
29
+ import { resolveRecipe } from "../sandbox/resolve.js";
30
+ import { declaredSandboxes } from "../sandbox/share.js";
31
+ import { httpToolTimeoutIssues } from "../gates/http-tool.js";
32
+ import { limitIssues, limitsInForce, manifestShapeIssues, readTenantLimits } from "./limits.js";
33
+ import { triggerIssues } from "./triggers.js";
34
+ import { readRuntimeLimits } from "./runtime-limits.js";
35
+ import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
36
+ import { actorOf, fileErrors } from "./agents.js";
37
+ import { moveAgent, planMove } from "./agent-versions.js";
38
+ import { definitionFileIssues } from "./definition-files.js";
39
+ import { fail } from "./http.js";
40
+ /** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
41
+ export function manifestRecipeRequest(manifest) {
42
+ const runtime = runtimeOf(manifest);
43
+ return {
44
+ ...(runtime.image === undefined ? {} : { image: runtime.image }),
45
+ ...(runtime.resources === undefined ? {} : { resources: runtime.resources }),
46
+ ...(runtime.storage === undefined ? {} : { storage: runtime.storage }),
47
+ ...(runtime.network === undefined ? {} : { network: runtime.network }),
48
+ harness: harnessIdOf(manifest.harness),
49
+ ...(runtime.lifecycle === undefined ? {} : { lifecycle: runtime.lifecycle }),
50
+ ...(runtime.setup === undefined ? {} : { setup: runtime.setup }),
51
+ ...(runtime.files === undefined ? {} : { files: runtime.files }),
52
+ ...(runtime.limits === undefined ? {} : { limits: runtime.limits }),
53
+ ...(runtime.secrets === undefined ? {} : { secrets: runtime.secrets }),
54
+ };
55
+ }
56
+ /** The hash a manifest's version records: equal manifests hash the same. */
57
+ export function agentManifestHash(manifest) {
58
+ return hashManifest(manifest);
59
+ }
60
+ /** The owner a version's definition files are recorded under, for the sweep. */
61
+ export function manifestFileOwner(name) {
62
+ return `agent-manifest:${name}`;
63
+ }
64
+ const notFound = (name, version) => fail(404, version === undefined ? `No agent manifest is named ${name}.` : `Agent manifest ${name} has no version ${version}.`, {
65
+ code: "manifest_not_found",
66
+ });
67
+ function viewOf(row, counts) {
68
+ return {
69
+ name: row.name,
70
+ version: row.version,
71
+ hash: row.hash,
72
+ manifest: row.body,
73
+ savedBy: row.savedBy,
74
+ createdAt: row.createdAt,
75
+ agents: counts.get(row.version) ?? 0,
76
+ };
77
+ }
78
+ /** Each field that differs between `before` and `after`, by path; arrays compare whole. */
79
+ export function manifestChanges(before, after, path = "") {
80
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
81
+ if (object(before) && object(after)) {
82
+ const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
83
+ return keys.flatMap((key) => manifestChanges(before[key], after[key], path ? `${path}.${key}` : key));
84
+ }
85
+ if (canonical(before) === canonical(after))
86
+ return [];
87
+ return [
88
+ {
89
+ path,
90
+ ...(before === undefined ? {} : { from: before }),
91
+ ...(after === undefined ? {} : { to: after }),
92
+ },
93
+ ];
94
+ }
95
+ /**
96
+ * The changes from `before` to `after` by section: `manifest` for the top-level fields, else
97
+ * `harness` or `runtime` with the path inside it. A section added or removed whole is one change.
98
+ */
99
+ export function sectionChanges(before, after) {
100
+ const op = (from, to) => from === undefined ? "add" : to === undefined ? "remove" : "replace";
101
+ const changes = [];
102
+ const { harness: beforeHarness, runtime: beforeRuntime, ...beforeTop } = before ?? {};
103
+ const { harness, runtime, ...top } = after;
104
+ for (const change of manifestChanges(beforeTop, top))
105
+ changes.push({ section: "manifest", ...change, op: op(change.from, change.to) });
106
+ for (const [section, from, to] of [
107
+ ["harness", beforeHarness, harness],
108
+ ["runtime", beforeRuntime, runtime],
109
+ ]) {
110
+ if (from === undefined || to === undefined) {
111
+ if (from !== to)
112
+ changes.push({
113
+ section,
114
+ path: "",
115
+ op: op(from, to),
116
+ ...(from === undefined ? {} : { from }),
117
+ ...(to === undefined ? {} : { to }),
118
+ });
119
+ continue;
120
+ }
121
+ for (const change of manifestChanges(from, to))
122
+ changes.push({ section, ...change, op: op(change.from, change.to) });
123
+ }
124
+ return changes;
125
+ }
126
+ /**
127
+ * Each live Agent of manifest `name` on another version than `to`, and whether a save of `to`
128
+ * moves it: it tracks the manifest, is not moving to `to` already, and `to` did not fail to start on it.
129
+ */
130
+ async function agentsBehind(t, name, to) {
131
+ return (await t.listEnvironments())
132
+ .filter((agent) => agent.manifestName === name && agent.pod?.desired !== "deleted" && agent.manifestVersion !== to)
133
+ .map((agent) => ({
134
+ agent,
135
+ moves: agent.manifestRef === "tracking" && agent.targetVersion !== to && agent.failedVersion !== to,
136
+ }));
137
+ }
138
+ /**
139
+ * Plans (or with `apply`, makes) the move of each Agent that tracks manifest `version.name` to
140
+ * `version` (track T3): each under its row lock, in the save's transaction.
141
+ */
142
+ async function moveTracking(t, version, apply) {
143
+ const moves = { now: 0, nextStart: 0, secrets: 0, behind: 0 };
144
+ const now = new Date().toISOString();
145
+ for (const { agent: listed, moves: tracks } of await agentsBehind(t, version.name, version.version)) {
146
+ const agent = tracks && apply ? await t.environment(listed.id, { lock: true }) : listed;
147
+ if (!agent || !tracks) {
148
+ if (agent && agent.targetVersion !== version.version)
149
+ moves.behind++;
150
+ continue;
151
+ }
152
+ const move = await planMove(t, agent, version, "tracking");
153
+ if (move.plan.applies === "refused") {
154
+ moves.behind++;
155
+ continue;
156
+ }
157
+ const applies = apply ? await moveAgent(t, agent, move, now) : move.plan.applies;
158
+ if (applies === "now")
159
+ moves.now++;
160
+ else if (applies === "next_start")
161
+ moves.nextStart++;
162
+ else if (applies === "secrets")
163
+ moves.secrets++;
164
+ }
165
+ return moves;
166
+ }
167
+ /** What a save of `manifest` should tell its saver, though it would be saved. */
168
+ function warningsOf(ctx, manifest, to, moves) {
169
+ const warnings = [];
170
+ const runtime = runtimeOf(manifest);
171
+ if (runtime.image !== undefined && !runtime.image.includes("@sha256:"))
172
+ warnings.push({
173
+ code: "image_tag",
174
+ message: `runtime.image ${runtime.image} is a tag, not a digest: each Agent pins what its first pod pulls, so two Tenants may run different bytes.`,
175
+ });
176
+ if (runtime.kind === "pod" && !ctx.pods)
177
+ warnings.push({
178
+ code: "pods_unavailable",
179
+ message: "runtime.kind pod needs sandbox pods, which this Tenant does not have: an Agent made from it is refused until `nylorun local sandbox enable`.",
180
+ });
181
+ if (moves === undefined)
182
+ return warnings;
183
+ const moved = moves.now + moves.nextStart + moves.secrets;
184
+ if (moved > 0) {
185
+ const parts = [
186
+ ...(moves.now > 0 ? [`${moves.now} at once`] : []),
187
+ ...(moves.nextStart > 0 ? [`${moves.nextStart} at their pod's next start`] : []),
188
+ ...(moves.secrets > 0 ? [`${moves.secrets} once their required secrets are set`] : []),
189
+ ];
190
+ warnings.push({
191
+ code: "agents_move",
192
+ message: `${moved} Agent${moved === 1 ? " tracks" : "s track"} ${manifest.name} and move${moved === 1 ? "s" : ""} to version ${to}: ${parts.join(", ")}.`,
193
+ });
194
+ }
195
+ if (moves.behind > 0)
196
+ warnings.push({
197
+ code: "agents_behind",
198
+ message: `${moves.behind} Agent${moves.behind === 1 ? " stays" : "s stay"} on an older version of ${manifest.name}: pinned, or ${moves.behind === 1 ? "it" : "they"} cannot move to version ${to} (move or fork ${moves.behind === 1 ? "it" : "them"}).`,
199
+ });
200
+ return warnings;
201
+ }
202
+ /**
203
+ * Every reason the Runtime would refuse `manifest` now, apart from the gaps between its sections:
204
+ * its harness section's own checks and its runtime section's (the Tenant's limits, its files).
205
+ */
206
+ async function errorsOf(t, manifest) {
207
+ const loop = loopManifestOf(manifest);
208
+ const errors = [];
209
+ // The Runtime runs agents from their manifests alone (track R2): no tool may need it.
210
+ const code = codeToolRefusal(loop);
211
+ if (code)
212
+ errors.push(code);
213
+ // Tool approvals are the Tenant's tool policy, decided at the Tool Gate (R5 step 2).
214
+ const approval = approvalRefusal(loop);
215
+ if (approval)
216
+ errors.push(approval);
217
+ const declared = declaredSandboxes(loop);
218
+ if (declared.length > 0)
219
+ errors.push(`${declared.join(", ")} declares a sandbox: what an agent runs on is the manifest's runtime section.`);
220
+ const runtimeLimits = await readRuntimeLimits(t);
221
+ errors.push(...httpToolTimeoutIssues(loop, runtimeLimits.httpToolTimeoutMs));
222
+ const runtime = runtimeOf(manifest);
223
+ const config = effectiveSandboxConfig(await readSandboxConfig(t));
224
+ const resolved = resolveRecipe(runtime.kind, manifestRecipeRequest(manifest), config);
225
+ const tenantLimits = await readTenantLimits(t);
226
+ errors.push(...(resolved.kind === "error" ? resolved.errors : []), ...limitIssues(runtime.limits, tenantLimits), ...manifestShapeIssues({ ...runtime, ...(manifest.description === undefined ? {} : { description: manifest.description }) }, runtimeLimits), ...(await fileErrors(t, runtime.files)), ...triggerIssues(runtime.triggers, runtimeLimits));
227
+ // A skill's files count against the manifest's own skill limit, never above the Tenant's.
228
+ const limitBytes = limitsInForce(tenantLimits, runtime.limits).skills.totalBytes;
229
+ const { missing, oversized } = await definitionFileIssues(t, loop, limitBytes);
230
+ if (missing.length > 0)
231
+ errors.push(`The manifest names ${missing.length} file(s) the Runtime does not hold; upload each with PUT /v1/files/<sha256> first: ${missing.join(", ")}`);
232
+ if (oversized.length > 0)
233
+ errors.push(`A skill's files may hold at most ${limitBytes} bytes together: ${oversized.map((skill) => `'${skill.name}' holds ${skill.bytes}`).join(", ")}`);
234
+ return { errors, missing, oversized, limitBytes };
235
+ }
236
+ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
237
+ const { requestId: _, ...manifest } = body;
238
+ if (manifest.name !== name)
239
+ fail(400, `The manifest is named ${manifest.name}, and the path names ${name}: they must match.`);
240
+ const hash = agentManifestHash(manifest);
241
+ const gaps = sectionGaps(manifest);
242
+ return ctx.store.tx(async (t) => {
243
+ await t.lockAgentManifest(name);
244
+ const latest = await t.agentManifest(name);
245
+ if (options.ifMatch !== undefined && (latest?.version ?? 0) !== options.ifMatch)
246
+ fail(412, latest === undefined
247
+ ? `Agent manifest ${name} does not exist, and If-Match named version ${options.ifMatch}.`
248
+ : `Agent manifest ${name} is at version ${latest.version}, not ${options.ifMatch}: someone saved it since you read it. Read it again, or save without If-Match to replace it.`, { code: "manifest_changed", details: { latest: latest?.version ?? 0 } });
249
+ const unchanged = latest?.hash === hash;
250
+ const to = unchanged ? latest.version : (latest?.version ?? 0) + 1;
251
+ const counts = await t.agentsOfManifest(name);
252
+ const checked = await errorsOf(t, manifest);
253
+ // The version as it would be saved, for the plan of the Agents it moves.
254
+ const row = unchanged
255
+ ? latest
256
+ : { name, version: to, body: manifest, hash, savedBy: { actor: actorOf(scope) }, createdAt: new Date().toISOString() };
257
+ if (options.dryRun) {
258
+ const errors = [...gaps, ...checked.errors];
259
+ // A save with errors moves nothing; one that changes nothing moves no Agent either.
260
+ const moves = errors.length > 0 || unchanged ? undefined : await moveTracking(t, row, false);
261
+ return {
262
+ name,
263
+ from: latest?.version ?? null,
264
+ to,
265
+ unchanged,
266
+ changes: unchanged ? [] : sectionChanges(latest?.body, manifest),
267
+ warnings: warningsOf(ctx, manifest, to, moves),
268
+ errors,
269
+ behind: moves?.behind ?? (await agentsBehind(t, name, to)).length,
270
+ moves: { now: moves?.now ?? 0, nextStart: moves?.nextStart ?? 0, secrets: moves?.secrets ?? 0 },
271
+ };
272
+ }
273
+ if (gaps.length > 0)
274
+ fail(422, `Agent manifest ${name}'s harness and runtime sections do not fit: ${gaps.join("; ")}.`, {
275
+ code: "manifest_inconsistent",
276
+ details: { gaps },
277
+ });
278
+ if (checked.missing.length > 0)
279
+ fail(400, checked.errors.find((error) => error.startsWith("The manifest names")), {
280
+ code: "definition_files_missing",
281
+ details: { missing: checked.missing },
282
+ });
283
+ if (checked.oversized.length > 0)
284
+ fail(413, `This manifest allows a skill's files at most ${checked.limitBytes} bytes together: ${checked.oversized.map((skill) => `'${skill.name}' holds ${skill.bytes}`).join(", ")}`, {
285
+ code: "limit_exceeded",
286
+ details: { limitBytes: checked.limitBytes, skills: checked.oversized },
287
+ });
288
+ if (checked.errors.length > 0)
289
+ fail(400, checked.errors.join(" "));
290
+ if (unchanged)
291
+ return { manifest: viewOf(latest, counts), unchanged: true, warnings: warningsOf(ctx, manifest, to, undefined) };
292
+ await t.insertAgentManifest(row);
293
+ // Its files by hash, skills' and runtime files' alike, for the sweep that deletes unused ones.
294
+ const shas = new Set(definitionFilesOf(loopManifestOf(manifest)));
295
+ for (const file of runtimeOf(manifest).files ?? [])
296
+ if ("files" in file)
297
+ for (const sha of Object.values(file.files))
298
+ shas.add(sha);
299
+ if (shas.size > 0)
300
+ await t.insertDefinitionFileUses(manifestFileOwner(name), hash, [...shas]);
301
+ // Every Agent that tracks the manifest moves to the new version (track T3).
302
+ const moves = await moveTracking(t, row, true);
303
+ return { manifest: viewOf(row, await t.agentsOfManifest(name)), unchanged: false, warnings: warningsOf(ctx, manifest, to, moves) };
304
+ });
305
+ }
306
+ /** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
307
+ export async function getAgentManifest(ctx, name, version) {
308
+ return ctx.store.tx(async (t) => {
309
+ const row = (await t.agentManifest(name, version)) ?? notFound(name, version);
310
+ return viewOf(row, await t.agentsOfManifest(name));
311
+ });
312
+ }
313
+ /** `GET /v1/agent-manifests`: each manifest's latest version. */
314
+ export async function listAgentManifests(ctx) {
315
+ return ctx.store.tx(async (t) => {
316
+ const manifests = [];
317
+ for (const row of await t.listAgentManifests())
318
+ manifests.push(viewOf(row, await t.agentsOfManifest(row.name)));
319
+ return { manifests };
320
+ });
321
+ }
322
+ /** `GET /v1/agent-manifests/{name}/versions`: every version, newest first. */
323
+ export async function agentManifestVersions(ctx, name) {
324
+ return ctx.store.tx(async (t) => {
325
+ const rows = await t.agentManifestVersions(name);
326
+ if (rows.length === 0)
327
+ notFound(name);
328
+ const counts = await t.agentsOfManifest(name);
329
+ return {
330
+ name,
331
+ latest: rows[0].version,
332
+ versions: rows.map((row) => ({
333
+ version: row.version,
334
+ hash: row.hash,
335
+ savedBy: row.savedBy,
336
+ createdAt: row.createdAt,
337
+ agents: counts.get(row.version) ?? 0,
338
+ })),
339
+ };
340
+ });
341
+ }
342
+ /**
343
+ * `DELETE /v1/agent-manifests/{name}`: deletes every version, refused while any Agent was made
344
+ * from one (`409 manifest_in_use`): the Agent names the version it runs.
345
+ */
346
+ export async function deleteAgentManifest(ctx, name) {
347
+ return ctx.store.tx(async (t) => {
348
+ await t.lockAgentManifest(name);
349
+ let made = 0;
350
+ for (const n of (await t.agentsOfManifest(name)).values())
351
+ made += n;
352
+ if (made > 0)
353
+ fail(409, `${made} Agent(s) were made from agent manifest ${name}: delete them first, or keep the manifest.`, {
354
+ code: "manifest_in_use",
355
+ details: { agents: made },
356
+ });
357
+ return { name, deleted: (await t.deleteAgentManifest(name)) > 0 };
358
+ });
359
+ }