@nylorun/runtime 0.26.0-beta → 0.27.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/CHANGELOG.md +130 -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.js +1 -1
  12. package/dist/api/a2a/endpoint.js +3 -4
  13. package/dist/api/a2a/protocol.js +4 -0
  14. package/dist/api/a2a/routes.d.ts +2 -6
  15. package/dist/api/a2a/routes.js +12 -18
  16. package/dist/api/ag-ui/endpoint.js +2 -3
  17. package/dist/api/ag-ui/routes.js +4 -4
  18. package/dist/api/components.d.ts +326 -45
  19. package/dist/api/components.js +5 -1
  20. package/dist/api/gate/keys.js +7 -4
  21. package/dist/api/gate/routes.d.ts +6 -2
  22. package/dist/api/gate/routes.js +15 -11
  23. package/dist/api/http/body.d.ts +15 -3
  24. package/dist/api/http/body.js +15 -7
  25. package/dist/api/http/respond.js +6 -2
  26. package/dist/api/http/routes/access.js +5 -4
  27. package/dist/api/http/routes/artifacts.js +19 -7
  28. package/dist/api/http/routes/coverage.js +1 -1
  29. package/dist/api/http/routes/environment-manifests.js +1 -1
  30. package/dist/api/http/routes/environments.js +6 -6
  31. package/dist/api/http/routes/files.d.ts +2 -1
  32. package/dist/api/http/routes/files.js +31 -5
  33. package/dist/api/http/routes/mcp.js +1 -1
  34. package/dist/api/http/routes/reads.d.ts +35 -0
  35. package/dist/api/http/routes/reads.js +50 -22
  36. package/dist/api/http/routes/sessions.js +11 -10
  37. package/dist/api/http/routes/tenant.d.ts +2 -2
  38. package/dist/api/http/routes/tenant.js +69 -16
  39. package/dist/api/inbound-files.d.ts +3 -9
  40. package/dist/api/inbound-files.js +18 -24
  41. package/dist/api/openapi.js +6 -2
  42. package/dist/artifacts/disk.d.ts +0 -4
  43. package/dist/artifacts/disk.js +77 -73
  44. package/dist/artifacts/files.d.ts +13 -4
  45. package/dist/artifacts/files.js +27 -9
  46. package/dist/artifacts/folder-upload.d.ts +75 -0
  47. package/dist/artifacts/folder-upload.js +263 -0
  48. package/dist/artifacts/folders.d.ts +14 -8
  49. package/dist/artifacts/folders.js +19 -11
  50. package/dist/artifacts/links.js +7 -6
  51. package/dist/artifacts/parts.d.ts +9 -14
  52. package/dist/artifacts/parts.js +11 -9
  53. package/dist/artifacts/pipe.d.ts +15 -0
  54. package/dist/artifacts/pipe.js +71 -0
  55. package/dist/artifacts/quota.d.ts +36 -0
  56. package/dist/artifacts/quota.js +44 -0
  57. package/dist/artifacts/service.d.ts +25 -1
  58. package/dist/artifacts/service.js +70 -25
  59. package/dist/blob/fs.js +2 -1
  60. package/dist/blob/s3.d.ts +6 -2
  61. package/dist/blob/s3.js +9 -7
  62. package/dist/blob/types.d.ts +1 -1
  63. package/dist/blob/types.js +1 -1
  64. package/dist/core/limits.d.ts +11 -35
  65. package/dist/core/limits.js +5 -73
  66. package/dist/core/sandbox-routes.js +0 -1
  67. package/dist/core/turn-manifest.d.ts +1 -2
  68. package/dist/core/turn-manifest.js +4 -28
  69. package/dist/execution/memory.d.ts +8 -5
  70. package/dist/execution/memory.js +8 -6
  71. package/dist/execution/types.d.ts +1 -6
  72. package/dist/execution/types.js +0 -5
  73. package/dist/gates/contract.d.ts +3 -2
  74. package/dist/gates/contract.js +1 -1
  75. package/dist/gates/egress-credentials.js +4 -3
  76. package/dist/gates/egress.d.ts +9 -9
  77. package/dist/gates/egress.js +14 -16
  78. package/dist/gates/fixture-model.js +13 -2
  79. package/dist/gates/handler.d.ts +1 -3
  80. package/dist/gates/handler.js +4 -3
  81. package/dist/gates/http-client.d.ts +4 -3
  82. package/dist/gates/http-client.js +5 -7
  83. package/dist/gates/http-tool.d.ts +24 -4
  84. package/dist/gates/http-tool.js +40 -12
  85. package/dist/gates/in-process.d.ts +2 -1
  86. package/dist/gates/in-process.js +17 -9
  87. package/dist/gates/inflight.d.ts +2 -2
  88. package/dist/gates/inflight.js +3 -2
  89. package/dist/gates/journal.js +5 -7
  90. package/dist/gates/mcp-handler.d.ts +4 -3
  91. package/dist/gates/mcp-handler.js +15 -5
  92. package/dist/gates/model-gate.d.ts +11 -2
  93. package/dist/gates/policy.d.ts +28 -5
  94. package/dist/gates/policy.js +60 -17
  95. package/dist/gates/provider-gate.d.ts +22 -0
  96. package/dist/gates/provider-gate.js +30 -0
  97. package/dist/gates/tool-calls.d.ts +1 -3
  98. package/dist/gates/tool-calls.js +2 -3
  99. package/dist/gates/tool-client.d.ts +1 -1
  100. package/dist/gates/tool-client.js +5 -6
  101. package/dist/gates/tool-contract.d.ts +8 -5
  102. package/dist/gates/tool-contract.js +1 -1
  103. package/dist/gates/tool-gate.d.ts +18 -7
  104. package/dist/gates/tool-gate.js +49 -7
  105. package/dist/harness/calls.d.ts +27 -25
  106. package/dist/harness/calls.js +40 -46
  107. package/dist/harness/executors.d.ts +10 -3
  108. package/dist/harness/executors.js +53 -23
  109. package/dist/harness/main.js +2 -2
  110. package/dist/harness/pod.d.ts +0 -2
  111. package/dist/harness/pod.js +7 -6
  112. package/dist/harness/run-tokens.d.ts +4 -1
  113. package/dist/harness/run-tokens.js +6 -2
  114. package/dist/harness/service.d.ts +17 -14
  115. package/dist/harness/service.js +21 -35
  116. package/dist/harness/setup.d.ts +0 -2
  117. package/dist/harness/setup.js +4 -9
  118. package/dist/harness/ws-client.d.ts +2 -2
  119. package/dist/harness/ws-client.js +7 -5
  120. package/dist/harness/ws-port.d.ts +3 -4
  121. package/dist/harness/ws-port.js +3 -4
  122. package/dist/harness-api/artifacts.d.ts +20 -0
  123. package/dist/harness-api/artifacts.js +478 -0
  124. package/dist/harness-api/in-process.d.ts +2 -2
  125. package/dist/harness-api/in-process.js +18 -16
  126. package/dist/harness-api/record.d.ts +10 -9
  127. package/dist/harness-api/record.js +102 -157
  128. package/dist/harness-api/renew.d.ts +1 -1
  129. package/dist/harness-api/renew.js +2 -2
  130. package/dist/harness-api/server.d.ts +1 -16
  131. package/dist/harness-api/server.js +82 -73
  132. package/dist/harness-api/start.d.ts +20 -2
  133. package/dist/harness-api/start.js +57 -18
  134. package/dist/harness-api/tool-journal.d.ts +15 -10
  135. package/dist/harness-api/tool-journal.js +26 -24
  136. package/dist/harness-api/workspace.d.ts +0 -4
  137. package/dist/harness-api/workspace.js +2 -45
  138. package/dist/harness-api/ws-server.js +10 -10
  139. package/dist/host/create-host.js +2 -3
  140. package/dist/host/execution.js +2 -3
  141. package/dist/host/gates.d.ts +2 -9
  142. package/dist/host/gates.js +15 -12
  143. package/dist/host/http.d.ts +0 -2
  144. package/dist/host/http.js +0 -27
  145. package/dist/host/main.js +10 -3
  146. package/dist/host/operate.js +12 -3
  147. package/dist/host/stack-config.d.ts +11 -0
  148. package/dist/host/stack-config.js +21 -1
  149. package/dist/index.d.ts +0 -1
  150. package/dist/index.js +0 -1
  151. package/dist/infra/index.js +2 -1
  152. package/dist/infra/readiness.d.ts +0 -2
  153. package/dist/infra/readiness.js +4 -3
  154. package/dist/keys/client.d.ts +1 -0
  155. package/dist/keys/client.js +2 -3
  156. package/dist/keys/contract.d.ts +0 -2
  157. package/dist/keys/contract.js +0 -2
  158. package/dist/keys/egress-ca.d.ts +0 -2
  159. package/dist/keys/egress-ca.js +2 -3
  160. package/dist/keys/keys.js +6 -1
  161. package/dist/keys/x509.d.ts +8 -4
  162. package/dist/keys/x509.js +14 -5
  163. package/dist/management-openapi.json +1961 -994
  164. package/dist/mcp/connect.d.ts +35 -26
  165. package/dist/mcp/connect.js +112 -141
  166. package/dist/mcp/pool.d.ts +27 -22
  167. package/dist/mcp/pool.js +34 -48
  168. package/dist/mcp/preview.d.ts +12 -5
  169. package/dist/mcp/preview.js +36 -19
  170. package/dist/mcp/snapshot.d.ts +35 -86
  171. package/dist/mcp/snapshot.js +93 -135
  172. package/dist/model/auth-store.js +5 -3
  173. package/dist/model/capabilities.d.ts +1 -1
  174. package/dist/model/catalog.d.ts +9 -36
  175. package/dist/model/catalog.js +6 -33
  176. package/dist/model/descriptor.d.ts +51 -0
  177. package/dist/model/descriptor.js +83 -0
  178. package/dist/model/models.d.ts +0 -3
  179. package/dist/model/models.js +3 -5
  180. package/dist/model/native-blocks.js +1 -1
  181. package/dist/model/pi-model.d.ts +16 -5
  182. package/dist/model/pi-model.js +86 -105
  183. package/dist/openapi.json +838 -1218
  184. package/dist/reads/cursor.js +3 -1
  185. package/dist/record/append.d.ts +34 -2
  186. package/dist/record/append.js +50 -1
  187. package/dist/record/environment.d.ts +2 -0
  188. package/dist/record/environment.js +2 -0
  189. package/dist/record/index.d.ts +1 -1
  190. package/dist/record/index.js +1 -1
  191. package/dist/redact.d.ts +1 -1
  192. package/dist/redact.js +3 -2
  193. package/dist/sandbox/egress-token.d.ts +3 -3
  194. package/dist/sandbox/egress-token.js +3 -3
  195. package/dist/sandbox/join.js +3 -2
  196. package/dist/sandbox/manager.d.ts +41 -41
  197. package/dist/sandbox/manager.js +112 -44
  198. package/dist/sandbox/placement.d.ts +1 -1
  199. package/dist/sandbox/placement.js +5 -8
  200. package/dist/sandbox/pods/client.d.ts +1 -10
  201. package/dist/sandbox/pods/client.js +8 -4
  202. package/dist/sandbox/pods/lifecycle.d.ts +19 -11
  203. package/dist/sandbox/pods/lifecycle.js +45 -23
  204. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  205. package/dist/sandbox/pods/network-gate.js +7 -7
  206. package/dist/sandbox/pods/reconcile.js +4 -1
  207. package/dist/sandbox/pods/spec.js +8 -5
  208. package/dist/sandbox/policy.d.ts +7 -1
  209. package/dist/sandbox/policy.js +10 -3
  210. package/dist/sandbox/resolve.d.ts +8 -3
  211. package/dist/sandbox/resolve.js +12 -2
  212. package/dist/sandbox/setup.d.ts +3 -1
  213. package/dist/sandbox/setup.js +34 -11
  214. package/dist/sandbox/share.d.ts +5 -14
  215. package/dist/sandbox/share.js +5 -34
  216. package/dist/sandbox/skills.d.ts +7 -13
  217. package/dist/sandbox/skills.js +16 -31
  218. package/dist/sandbox/tenant-config.d.ts +2 -2
  219. package/dist/sandbox/tenant-config.js +4 -3
  220. package/dist/sandbox/tools.d.ts +7 -1
  221. package/dist/sandbox/tools.js +26 -26
  222. package/dist/sandbox/types.d.ts +10 -3
  223. package/dist/sandbox/types.js +9 -0
  224. package/dist/store/postgres/connect.d.ts +6 -5
  225. package/dist/store/postgres/connect.js +14 -11
  226. package/dist/store/postgres/control.js +13 -13
  227. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  228. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  229. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  230. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  231. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  232. package/dist/store/postgres/reads.js +10 -2
  233. package/dist/store/postgres/schema.d.ts +19 -0
  234. package/dist/store/postgres/schema.js +7 -0
  235. package/dist/store/postgres/store.js +55 -26
  236. package/dist/store/types.d.ts +17 -7
  237. package/dist/streams/relay/core.d.ts +3 -3
  238. package/dist/streams/relay/core.js +9 -10
  239. package/dist/tenant/advance.js +21 -31
  240. package/dist/tenant/commands.js +9 -16
  241. package/dist/tenant/context.d.ts +14 -17
  242. package/dist/tenant/definition-files.d.ts +25 -4
  243. package/dist/tenant/definition-files.js +85 -47
  244. package/dist/tenant/effects.js +9 -36
  245. package/dist/tenant/environment-manifests.js +9 -1
  246. package/dist/tenant/environments.d.ts +0 -2
  247. package/dist/tenant/environments.js +49 -28
  248. package/dist/tenant/ephemeral.js +2 -1
  249. package/dist/tenant/history.d.ts +1 -1
  250. package/dist/tenant/history.js +1 -1
  251. package/dist/tenant/host-token.d.ts +3 -3
  252. package/dist/tenant/host-token.js +11 -10
  253. package/dist/tenant/http.d.ts +5 -8
  254. package/dist/tenant/http.js +9 -0
  255. package/dist/tenant/identity-file.d.ts +23 -0
  256. package/dist/tenant/identity-file.js +34 -6
  257. package/dist/tenant/issuers.d.ts +5 -5
  258. package/dist/tenant/issuers.js +31 -23
  259. package/dist/tenant/jwt.d.ts +8 -2
  260. package/dist/tenant/jwt.js +10 -2
  261. package/dist/tenant/limits.d.ts +90 -0
  262. package/dist/tenant/limits.js +175 -0
  263. package/dist/tenant/module.d.ts +4 -1
  264. package/dist/tenant/module.js +9 -8
  265. package/dist/tenant/outbound.d.ts +11 -14
  266. package/dist/tenant/outbound.js +10 -15
  267. package/dist/tenant/run-grants.js +3 -2
  268. package/dist/tenant/run-token.d.ts +0 -4
  269. package/dist/tenant/run-token.js +6 -11
  270. package/dist/tenant/runtime-limits.d.ts +24 -0
  271. package/dist/tenant/runtime-limits.js +27 -0
  272. package/dist/tenant/runtime.js +30 -43
  273. package/dist/tenant/session-streams.js +7 -6
  274. package/dist/tenant/session.d.ts +1 -1
  275. package/dist/tenant/session.js +1 -1
  276. package/dist/tenant/sessions.js +32 -29
  277. package/dist/tenant/signing-keys.d.ts +1 -1
  278. package/dist/tenant/signing-keys.js +5 -6
  279. package/dist/tenant/status.js +4 -3
  280. package/dist/tenant/streams.js +18 -20
  281. package/dist/tenant/sweep.d.ts +1 -21
  282. package/dist/tenant/sweep.js +39 -48
  283. package/dist/tenant/transcript.d.ts +70 -23
  284. package/dist/tenant/transcript.js +28 -22
  285. package/dist/tenant/types.d.ts +9 -14
  286. package/dist/tenant/worker.d.ts +2 -10
  287. package/dist/tenant/worker.js +5 -14
  288. package/dist/traces/ids.js +5 -1
  289. package/dist/traces/map.d.ts +9 -6
  290. package/dist/traces/map.js +9 -11
  291. package/dist/traces/otlp.d.ts +3 -3
  292. package/dist/traces/otlp.js +5 -3
  293. package/dist/traces/projector.d.ts +9 -4
  294. package/dist/traces/projector.js +13 -7
  295. package/dist/traces/read.js +6 -1
  296. package/dist/tuning.d.ts +383 -0
  297. package/dist/tuning.js +658 -0
  298. package/dist/vault/headers.js +4 -2
  299. package/dist/version.d.ts +1 -1
  300. package/dist/version.js +1 -1
  301. package/package.json +3 -3
  302. package/dist/artifacts/export.d.ts +0 -8
  303. package/dist/artifacts/export.js +0 -204
  304. package/dist/artifacts/media-types.d.ts +0 -30
  305. package/dist/artifacts/media-types.js +0 -137
  306. package/dist/artifacts/workspace.d.ts +0 -60
  307. package/dist/artifacts/workspace.js +0 -45
  308. package/dist/media.d.ts +0 -10
  309. package/dist/media.js +0 -58
  310. package/dist/sandbox/runtime-tools.d.ts +0 -9
  311. package/dist/sandbox/runtime-tools.js +0 -16
  312. package/dist/sandbox/session-sandbox.d.ts +0 -16
  313. package/dist/sandbox/session-sandbox.js +0 -77
  314. package/dist/tenant/artifact-tool.d.ts +0 -13
  315. package/dist/tenant/artifact-tool.js +0 -243
  316. package/dist/tenant/skill-tool.d.ts +0 -20
  317. package/dist/tenant/skill-tool.js +0 -67
  318. package/dist/tenant/tool-results.d.ts +0 -57
  319. package/dist/tenant/tool-results.js +0 -298
  320. package/dist/tenant/tool-search.d.ts +0 -39
  321. package/dist/tenant/tool-search.js +0 -103
@@ -16,6 +16,9 @@
16
16
  * workspace request it is serving acts for; a `sandbox.state` claim's record becomes the
17
17
  * workspace's row in the `sandboxes` table.
18
18
  *
19
+ * Protocol 15: a harness reads and writes its run's session's artifacts over the artifact seam
20
+ * (`artifact.read`, `artifact.write`, `./artifacts.ts`); a run's open uploads end with it.
21
+ *
19
22
  * F7.2: a pod sandbox's engine connects as that sandbox's *host* (`HarnessPeer.host`, from its
20
23
  * host token): it leases only runs whose session is attached to its sandbox (`RunOffer.pod`),
21
24
  * serves only that sandbox's workspace (`workspace(..., { pod })`), and claims events only for
@@ -25,20 +28,23 @@
25
28
  * `connection.lost`.
26
29
  */
27
30
  import { randomUUID } from "node:crypto";
28
- import { DEFAULT_HARNESS, HARNESS_API_VERSION, HARNESS_CLAIMS, MAX_HARNESS_STATE_BYTES, isHarnessId, HarnessApiError, } from "@nylorun/core/harness-api";
29
- import { definitionFilesOf } from "@nylorun/core/contracts";
30
- import { TOOL_CALL_TOOL, TOOL_SEARCH_TOOL, delegatesOf, } from "@nylorun/core/define";
31
+ import { DEFAULT_HARNESS, HARNESS_API_VERSION, HARNESS_CLAIMS, isHarnessId, HarnessApiError, } from "@nylorun/core/harness-api";
32
+ import { definitionFilesOf, limitExceededMessage } from "@nylorun/core/contracts";
31
33
  import { isOwnershipLost } from "../store/ownership.js";
32
34
  import { harnessOf, ownedSession } from "../tenant/context.js";
33
- import { mcpDiscovered, pinDeferral, sessionToolsOf, } from "../mcp/snapshot.js";
34
- import { contextWindowOf } from "../model/catalog.js";
35
+ import { fitSnapshot, isSnapshot, mcpDiscovered, mcpFactsOf, } from "../mcp/snapshot.js";
35
36
  import { turnManifestOf } from "../tenant/session.js";
37
+ import { readRuntimeLimits } from "../tenant/runtime-limits.js";
36
38
  import { sandboxWorkspaceKey, workspacePrefix } from "../sandbox/records.js";
37
39
  import { abortKind } from "../tenant/worker.js";
38
- import { readDefinitionFile } from "../tenant/definition-files.js";
40
+ import { BlobRangeError } from "../blob/index.js";
41
+ import { openDefinitionFile, readDefinitionFile } from "../tenant/definition-files.js";
39
42
  import { recordIntent, recordOutcome } from "./record.js";
43
+ import { createArtifactSeam } from "./artifacts.js";
44
+ import { RecordEntryTooLargeError } from "../record/index.js";
40
45
  import { redactTokens } from "./redact.js";
41
46
  import { beat, renewEveryMs, tokenOf } from "./renew.js";
47
+ import { hostTuning } from "../tuning.js";
42
48
  /** No attached harness serves workspaces (or, with `pod`, that pod sandbox's). */
43
49
  export class NoWorkspaceHarness extends Error {
44
50
  pod;
@@ -60,11 +66,12 @@ const OUTPUTS = [
60
66
  "checkpoint",
61
67
  ];
62
68
  export function createHarnessApiServer(ctx, options = {}) {
63
- const offerWaitMs = options.offerWaitMs ?? 10_000;
69
+ const offerWaitMs = options.offerWaitMs ?? hostTuning().harnessApi.offerWaitMs;
64
70
  const connections = new Set();
65
71
  const waiting = [];
66
72
  const queued = [];
67
73
  const runs = new Map();
74
+ const artifacts = createArtifactSeam(ctx);
68
75
  let closed = false;
69
76
  /**
70
77
  * A harness may take a run of its own harness id (R4.1): a pod sandbox's host its sandbox's
@@ -81,15 +88,6 @@ export function createHarnessApiServer(ctx, options = {}) {
81
88
  continue;
82
89
  }
83
90
  queued.splice(i, 1);
84
- const clash = nativeToolClashes(waiting[at].connection.tools, run.offer.start);
85
- if (clash.length > 0) {
86
- // The harness keeps waiting for a run its tools fit; this turn fails (Harness API 3).
87
- run.end({
88
- kind: "refused",
89
- message: `Harness '${waiting[at].connection.harness}' runs tools named like tools of this turn's agents: ${clash.join(", ")}. A tool the harness runs itself needs a name no other tool of the session has.`,
90
- });
91
- continue;
92
- }
93
91
  const lease = waiting.splice(at, 1)[0];
94
92
  run.connection = lease.connection;
95
93
  // The run token the advance holds now (F5): the gate calls of this run present it.
@@ -111,11 +109,18 @@ export function createHarnessApiServer(ctx, options = {}) {
111
109
  evidence: "claimed",
112
110
  ...(runId === undefined ? {} : { runId }),
113
111
  });
112
+ /** What the artifact seam serves a run: its session and turn, its limits, its signal. */
113
+ const artifactScope = (run) => ({
114
+ runId: run.grant.runId,
115
+ sessionId: run.grant.sessionId,
116
+ turnId: run.grant.turnId,
117
+ limits: run.offer.start.limits,
118
+ runSignal: run.offer.controller.signal,
119
+ });
114
120
  const scope = (run) => ({
115
121
  ctx,
116
122
  lease: run.offer.lease,
117
123
  signal: run.offer.controller.signal,
118
- ...(run.connection?.tools ? { nativeTools: run.connection.tools } : {}),
119
124
  ...(run.connection ? { writer: writerOf(run.connection, run.grant.runId) } : {}),
120
125
  });
121
126
  const serve = (connection) => async (method, params, signal) => {
@@ -127,8 +132,6 @@ export function createHarnessApiServer(ctx, options = {}) {
127
132
  throw new HarnessApiError("invalid", `unsupported Harness API version ${api}: this Runtime speaks ${HARNESS_API_VERSION}`);
128
133
  connection.harness = await harnessIdOf(connection, name);
129
134
  connection.workspace = features.workspace !== undefined;
130
- if (features.tools?.length)
131
- connection.tools = new Set(features.tools.map((tool) => tool.name));
132
135
  return {
133
136
  api: HARNESS_API_VERSION,
134
137
  tenantId: ctx.config.tenantId,
@@ -181,7 +184,11 @@ export function createHarnessApiServer(ctx, options = {}) {
181
184
  case "session.mcp":
182
185
  return recordMcp(run, p);
183
186
  case "definition.file":
184
- return definitionFile(run, p.sha256, signal);
187
+ return definitionFile(run, p, signal);
188
+ case "artifact.read":
189
+ return artifacts.read(artifactScope(run), p, signal);
190
+ case "artifact.write":
191
+ return artifacts.write(artifactScope(run), p, signal);
185
192
  default:
186
193
  throw new HarnessApiError("invalid", `Unknown request ${method}`);
187
194
  }
@@ -211,7 +218,8 @@ export function createHarnessApiServer(ctx, options = {}) {
211
218
  * A `sandbox.*` event the harness claims: for a run it holds (the run's session, or the
212
219
  * session owning its sandbox), or a session one of its workspace requests acts for. A
213
220
  * `sandbox.state` record becomes the workspace's `sandboxes` row. Claims are best effort, as
214
- * the events of core's own SandboxManager are: a session gone since is skipped.
221
+ * the events of core's own SandboxManager are: a session gone since is skipped. One past the
222
+ * Tenant's `recordEntryBytes` is refused (`limit_exceeded`).
215
223
  */
216
224
  const claim = async (connection, event) => {
217
225
  if (!HARNESS_CLAIMS.includes(event.type))
@@ -240,6 +248,9 @@ export function createHarnessApiServer(ctx, options = {}) {
240
248
  });
241
249
  }
242
250
  catch (error) {
251
+ // Past the record entry limit is the harness's to hear (`limit_exceeded`), not a lost claim.
252
+ if (error instanceof RecordEntryTooLargeError)
253
+ throw error;
243
254
  ctx.config.logger.warn("harness event claim not recorded", {
244
255
  sessionId: event.sessionId,
245
256
  type: event.type,
@@ -250,22 +261,23 @@ export function createHarnessApiServer(ctx, options = {}) {
250
261
  };
251
262
  /**
252
263
  * What a harness found readying the run's MCP servers: the first snapshot recorded for the
253
- * session wins, with `mcp.discovered` in its log; diagnostics replace those of the same server.
254
- * Answers the session's snapshot and the tools the engine advertises.
264
+ * session wins, pinned as it is within the Tenant's Runtime limit `mcpSnapshotBytes` (a server
265
+ * past it is left out, with a diagnostic: `fitSnapshot`), with `mcp.discovered` in its log;
266
+ * diagnostics replace those of the same server. Answers the session's facts as the run's turn
267
+ * sees them (protocol 15).
255
268
  */
256
269
  const recordMcp = async (run, params) => {
257
270
  const snapshot = params.snapshot;
258
271
  if (snapshot !== undefined && !isSnapshot(snapshot))
259
272
  throw new HarnessApiError("invalid", "The MCP snapshot is malformed");
260
- // Which MCP tools the model reaches through tool_search, decided once for the session from
261
- // the Tenant's model's context window (R2b C10, Q21).
262
- const contextWindow = snapshot ? contextWindowOf(await ctx.vault.getHostModel()) : 0;
263
273
  return ctx.store.tx(async (t) => {
264
274
  const { lease } = run.offer;
265
275
  const current = await ownedSession(t, lease, lease.sessionId);
266
- const diagnostics = params.diagnostics;
276
+ let diagnostics = params.diagnostics;
267
277
  if (snapshot && !current.mcpSnapshot) {
268
- current.mcpSnapshot = pinDeferral(snapshot, current.manifest, contextWindow);
278
+ const fitted = fitSnapshot(snapshot, diagnostics, (await readRuntimeLimits(t)).mcpSnapshotBytes);
279
+ diagnostics = fitted.diagnostics;
280
+ current.mcpSnapshot = fitted.snapshot;
269
281
  current.mcpDiagnostics = diagnostics;
270
282
  await t.put("sessions", current.id, current);
271
283
  await t.event(current.id, run.grant.turnId, "mcp.discovered", mcpDiscovered(current.mcpSnapshot, diagnostics, current.manifest), run.connection ? writerOf(run.connection, run.grant.runId) : undefined);
@@ -282,27 +294,48 @@ export function createHarnessApiServer(ctx, options = {}) {
282
294
  current.mcpDiagnostics = prior;
283
295
  await t.put("sessions", current.id, current);
284
296
  }
285
- return {
286
- ...(current.mcpSnapshot ? { snapshot: current.mcpSnapshot } : {}),
287
- // The turn's manifest: a variant may have disabled tools or required their approval.
288
- sessionTools: [...(sessionToolsOf(current.mcpSnapshot, turnManifestOf(current)) ?? [])],
289
- };
297
+ // The turn's manifest: a variant may have disabled tools.
298
+ return current.mcpSnapshot ? { mcp: mcpFactsOf(current.mcpSnapshot, turnManifestOf(current)) } : {};
290
299
  });
291
300
  };
292
- /** A definition file the run's definition names (its skills', for its sandbox), base64. */
293
- const definitionFile = async (run, sha256, signal) => {
301
+ /**
302
+ * A definition file the run's definition names (its skills', for its sandbox), base64: whole,
303
+ * or the range `offset` and `length` name (none past the end), at most the run's
304
+ * `limits.chunkBytes`, which is also the length when the harness names none.
305
+ */
306
+ const definitionFile = async (run, { sha256, offset, length }, signal) => {
294
307
  if (!definitionFilesOf(run.offer.start.routing.rootManifest).has(sha256))
295
308
  throw new HarnessApiError("invalid", `The run's definition names no file ${sha256}`);
296
- const bytes = await readDefinitionFile(ctx.blobs, sha256, signal);
297
- if (!bytes)
298
- throw new HarnessApiError("unavailable", `The Object store has no bytes for ${sha256}`);
299
- return { base64: Buffer.from(bytes).toString("base64") };
309
+ const missing = () => new HarnessApiError("unavailable", `The Object store has no bytes for ${sha256}`);
310
+ if (offset === undefined && length === undefined) {
311
+ const bytes = await readDefinitionFile(ctx.blobs, sha256, signal);
312
+ if (!bytes)
313
+ throw missing();
314
+ return { base64: Buffer.from(bytes).toString("base64") };
315
+ }
316
+ const { chunkBytes } = run.offer.start.limits;
317
+ if (length !== undefined && length > chunkBytes)
318
+ throw new HarnessApiError("limit_exceeded", limitExceededMessage("The range asked for", length, "chunkBytes", chunkBytes));
319
+ const start = offset ?? 0;
320
+ const end = start + (length ?? chunkBytes) - 1;
321
+ let body;
322
+ try {
323
+ body = await openDefinitionFile(ctx.blobs, sha256, { range: { start, end }, signal });
324
+ }
325
+ catch (error) {
326
+ if (error instanceof BlobRangeError)
327
+ return { base64: "" };
328
+ throw error;
329
+ }
330
+ if (!body)
331
+ throw missing();
332
+ return { base64: Buffer.from(await new Response(body).arrayBuffer()).toString("base64") };
300
333
  };
301
334
  /**
302
335
  * A custom harness's output (R4.2): its state is its own, stored as sent and never read, so it
303
- * must be JSON and at most `MAX_HARNESS_STATE_BYTES`; a larger one fails the turn, as an engine
304
- * that threw would. It cannot pause for approvals in v2 (Q14): approvals read our engine's plan
305
- * until R5 makes them core holds.
336
+ * must be JSON and at most the run's `limits.harnessStateBytes`; a larger one fails the turn
337
+ * (`limit_exceeded`), as an engine that threw would. It cannot pause for approvals in v2 (Q14):
338
+ * approvals read our engine's plan until R5 makes them core holds.
306
339
  */
307
340
  const checkCustomOutput = (run, method, params) => {
308
341
  if (method === "turn.paused")
@@ -310,14 +343,16 @@ export function createHarnessApiServer(ctx, options = {}) {
310
343
  if (params.state === undefined || run.outputs.has(method))
311
344
  return;
312
345
  const bytes = Buffer.byteLength(JSON.stringify(params.state) ?? "");
313
- if (bytes <= MAX_HARNESS_STATE_BYTES)
346
+ const { harnessStateBytes } = run.offer.start.limits;
347
+ if (bytes <= harnessStateBytes)
314
348
  return;
349
+ const message = limitExceededMessage("The harness's state", bytes, "harnessStateBytes", harnessStateBytes);
315
350
  output(run, "turn.failed", {
316
351
  runId: run.grant.runId,
317
352
  status: "failed",
318
- thrown: { code: "invalid", message: `The harness's state is ${bytes} bytes, over ${MAX_HARNESS_STATE_BYTES}` },
353
+ thrown: { code: "limit_exceeded", message },
319
354
  }).catch(() => undefined);
320
- throw new HarnessApiError("invalid", `state over 256 KiB (${bytes} bytes): the turn failed`);
355
+ throw new HarnessApiError("limit_exceeded", `${message} The turn failed.`);
321
356
  };
322
357
  const output = (run, method, params) => {
323
358
  const known = run.outputs.get(method);
@@ -455,6 +490,8 @@ export function createHarnessApiServer(ctx, options = {}) {
455
490
  return;
456
491
  run.ended = true;
457
492
  clearTimeout(timer);
493
+ // An upload the run left open commits nothing.
494
+ artifacts.endRun(run.grant.runId);
458
495
  controller.signal.removeEventListener("abort", onAbort);
459
496
  // An output's run stays held until core answers it, so a repeat gets the same answer.
460
497
  if (end.kind === "output") {
@@ -501,31 +538,3 @@ export function createHarnessApiServer(ctx, options = {}) {
501
538
  },
502
539
  };
503
540
  }
504
- function isSnapshot(value) {
505
- const snapshot = value;
506
- return (typeof snapshot === "object" &&
507
- snapshot !== null &&
508
- snapshot.snapshotSchemaVersion === 1 &&
509
- typeof snapshot.manifestHash === "string" &&
510
- Array.isArray(snapshot.mcpTools));
511
- }
512
- /**
513
- * The tools a harness runs itself (`features.tools`) whose names the turn already uses: a tool of
514
- * the turn's agents (their own and the Runtime's `runtime` tools), a session tool, `tool_search` or
515
- * `tool_call` (session tools a later step may get). Tools are known by name alone, so a native
516
- * tool must not take one (Harness API 3); `hello` already refuses a name with `__`, which names an
517
- * MCP server's tools. A flow turn runs no native tools.
518
- */
519
- export function nativeToolClashes(tools, start) {
520
- if (!tools?.size || start.engine !== "agent")
521
- return [];
522
- const manifest = start.manifest;
523
- const taken = new Set([TOOL_SEARCH_TOOL, TOOL_CALL_TOOL]);
524
- for (const agent of [manifest, ...delegatesOf(manifest).map((delegate) => delegate.manifest)])
525
- for (const tool of [...(agent.tools ?? []), ...(agent.runtime?.tools ?? [])])
526
- taken.add(tool.name);
527
- for (const tool of (start.sessionTools ?? []))
528
- if (typeof tool?.name === "string")
529
- taken.add(tool.name);
530
- return [...tools].filter((name) => taken.has(name)).sort();
531
- }
@@ -1,4 +1,5 @@
1
- import type { RecordedOutcome, TurnStart } from "@nylorun/core/harness-api";
1
+ import type { ModelDescriptor, RecordedOutcome, RuntimeLimits, TurnEnvironment, TurnStart } from "@nylorun/core/harness-api";
2
+ import { type SandboxManifest } from "@nylorun/core/define";
2
3
  import type { SandboxPodState } from "../store/types.js";
3
4
  import { type Lease, type Session, type TenantContext } from "../tenant/context.js";
4
5
  import { type TraceContext } from "../traces/ids.js";
@@ -6,6 +7,8 @@ import { type TraceContext } from "../traces/ids.js";
6
7
  export interface SegmentStart {
7
8
  readonly current: Session;
8
9
  readonly fixtureModel: boolean;
10
+ /** The Tenant's Runtime limits as the segment starts: what `turn.start.limits` hands the harness. */
11
+ readonly limits: RuntimeLimits;
9
12
  /** The transcript the engine resumes from (agents with state; empty otherwise). */
10
13
  readonly transcript: unknown[];
11
14
  /** The record position of `transcript` (`foldTranscriptWithCursor`). */
@@ -17,7 +20,14 @@ export interface SegmentStart {
17
20
  readonly environmentId?: string;
18
21
  /** Variables every sandbox command gets (R2c): secret names set to the sentinel, plain values. */
19
22
  readonly environment?: Readonly<Record<string, string>>;
23
+ /** The sandbox spec the session pinned when it opened; absent without a sandbox. */
24
+ readonly spec?: SandboxManifest;
20
25
  };
26
+ /**
27
+ * The Environment the session runs in, as `TurnStart.environment` states it (protocol 15): its
28
+ * id and kind, and its sandbox's workspace when the session has a sandbox.
29
+ */
30
+ readonly environment?: TurnEnvironment;
21
31
  /**
22
32
  * The Environment is a pod (F7.2): its engine runs the segment. Its lifecycle state, and
23
33
  * whether its new volume is still being seeded from a disk version (R2d step 3).
@@ -29,6 +39,11 @@ export interface SegmentStart {
29
39
  };
30
40
  /** The turn's span context (R7.4): what `TurnStart.trace` hands the harness. */
31
41
  readonly trace?: TraceContext;
42
+ /**
43
+ * The model the turn's calls use (protocol 15), as the segment starts: what `TurnStart.model`
44
+ * hands an agent's harness. Absent for a flow, and when the Tenant has no model.
45
+ */
46
+ readonly model?: ModelDescriptor;
32
47
  }
33
48
  /** The segment's starting transaction and fold. */
34
49
  export declare function startSegment(ctx: TenantContext, lease: Lease,
@@ -38,7 +53,10 @@ options?: {
38
53
  }): Promise<SegmentStart>;
39
54
  /** The run's `turn.start`. */
40
55
  export declare function buildTurnStart(ctx: TenantContext, segment: SegmentStart): TurnStart;
41
- /** The engine's yield budget for a segment. */
56
+ /**
57
+ * The engine's yield budget for a segment: the Tenant's `rollover`, else the Host tuning's
58
+ * (`harnessApi.rolloverSteps`, `rolloverMs`), well inside the advance deadline.
59
+ */
42
60
  export declare function yieldAfterOf(ctx: TenantContext): {
43
61
  steps: number;
44
62
  ms: number;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * What a segment starts from (§10.5): in one transaction the session's state is rebased onto
3
+ * the turn's manifest, a transcript still on the row is recorded, and the segment's completed
4
+ * outcomes are read; then the transcript is folded from the record (P0.3). `buildTurnStart`
5
+ * turns that into the run's `turn.start`: the checkpoint goes without its transcript, which the
6
+ * harness holds at the fold's cursor or reads once.
7
+ */
8
+ import { harnessLimitsOf } from "@nylorun/core/contracts";
9
+ import { SANDBOX_WORKSPACE } from "@nylorun/core/define";
1
10
  import { isWorkflowManifest } from "../core/flow-host.js";
2
11
  import { sandboxWorkspaceOf } from "../sandbox/share.js";
3
12
  import { ownedTx } from "../store/ownership.js";
@@ -5,13 +14,15 @@ import { harnessOf } from "../tenant/context.js";
5
14
  import { HARNESS_ID } from "../sandbox/placement.js";
6
15
  import { checkParity, foldTranscriptWithCursor, leanState, transcriptOf, transcriptUpdates, withTranscript, } from "../tenant/history.js";
7
16
  import { usesFixtureModel } from "../tenant/model-setting.js";
8
- import { sessionToolsOf, turnManifestOf } from "../tenant/session.js";
17
+ import { currentModelDescriptor } from "../model/descriptor.js";
18
+ import { readRuntimeLimits } from "../tenant/runtime-limits.js";
19
+ import { flowLimitsOf } from "../core/limits.js";
20
+ import { turnManifestOf } from "../tenant/session.js";
21
+ import { mcpFactsOf } from "../mcp/snapshot.js";
9
22
  import { bulkOutcomes } from "./record.js";
10
23
  import { traceKey } from "../traces/key.js";
24
+ import { hostTuning } from "../tuning.js";
11
25
  import { traceparentOf, turnSpanOf, turnTraceIdOf, } from "../traces/ids.js";
12
- /** Segment rollover defaults (Model Calls §10), well inside the advance deadline. */
13
- const ROLLOVER_STEPS = 50;
14
- const ROLLOVER_MS = 20 * 60_000;
15
26
  /** The segment's starting transaction and fold. */
16
27
  export async function startSegment(ctx, lease,
17
28
  /** For a harness: also the segment's outcomes and the sandbox's owner. */
@@ -19,34 +30,46 @@ options = {}) {
19
30
  const id = lease.sessionId;
20
31
  // Read before the transaction: the first read of the Tenant may create the key.
21
32
  const key = options.harness ? await traceKey(ctx.store) : undefined;
22
- const started = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
33
+ const opened = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
23
34
  // A transcript left on our engine's state by an older Runtime is recorded once; another
24
35
  // harness's state is its own (R4.2).
36
+ const limits = await readRuntimeLimits(t);
25
37
  if (!isWorkflowManifest(current.manifest) && current.checkpoint && harnessOf(current) === HARNESS_ID) {
26
- await adoptStoredTranscript(t, current);
38
+ await adoptStoredTranscript(t, current, limits.recordEntryBytes);
27
39
  const cp = current.checkpoint;
28
40
  current.checkpoint = { ...cp, state: current.state };
29
41
  await t.put("sessions", id, current);
30
42
  }
31
43
  const fixtureModel = await usesFixtureModel(t);
32
44
  if (!options.harness)
33
- return { current, fixtureModel, outcomes: [], sandbox: { ownerId: id } };
45
+ return { current, fixtureModel, limits, outcomes: [], sandbox: { ownerId: id } };
34
46
  const workspace = sandboxWorkspaceOf(current);
35
47
  const resource = workspace.environmentId === undefined ? undefined : await t.environment(workspace.environmentId);
36
48
  const environment = await ctx.vault.environmentVariables(t, current.environmentId);
37
- const sandbox = Object.keys(environment).length > 0 ? { ...workspace, environment } : workspace;
49
+ const sandbox = {
50
+ ...workspace,
51
+ ...(Object.keys(environment).length > 0 ? { environment } : {}),
52
+ ...(current.sandbox === undefined ? {} : { spec: current.sandbox }),
53
+ };
38
54
  const turnId = current.activeTurnId;
39
55
  return {
40
56
  current,
41
57
  fixtureModel,
58
+ limits,
42
59
  outcomes: current.checkpoint ? await bulkOutcomes(t, current, current.checkpoint) : [],
43
60
  sandbox,
61
+ ...(resource ? { environment: environmentOf(resource, current.sandbox) } : {}),
44
62
  ...(key && turnId ? { trace: turnContextOf(key, ctx.store.tenantId, current, turnId) } : {}),
45
63
  ...(resource?.pod
46
64
  ? { pod: { id: resource.id, pod: resource.pod, ...(resource.diskSeed ? { seeding: true } : {}) } }
47
65
  : {}),
48
66
  };
49
67
  });
68
+ // The model an agent's harness decides files by, read outside the session's transaction.
69
+ const model = options.harness && !isWorkflowManifest(opened.current.manifest)
70
+ ? await currentModelDescriptor(ctx)
71
+ : undefined;
72
+ const started = model ? { ...opened, model } : opened;
50
73
  // One history (P0.3): the engine resumes from the transcript folded from the record.
51
74
  const { current } = started;
52
75
  const cp = current.checkpoint;
@@ -76,22 +99,35 @@ export function buildTurnStart(ctx, segment) {
76
99
  checkpoint: !flow && cp.state && harnessOf(current) === HARNESS_ID
77
100
  ? { ...cp, state: withTranscript(cp.state, []) }
78
101
  : cp,
79
- // The turn's manifest: a variant may have disabled MCP tools or required their approval.
80
- ...(flow ? {} : { sessionTools: sessionToolsOf(current.mcpSnapshot, turnManifestOf(current)) ?? [] }),
102
+ // The session's MCP discovery as the turn's manifest sees it: a variant may disable tools.
103
+ ...(!flow && current.mcpSnapshot ? { mcp: mcpFactsOf(current.mcpSnapshot, turnManifestOf(current)) } : {}),
81
104
  outcomes: segment.outcomes,
82
105
  transcript: { cursor: segment.cursor },
83
106
  options: {
84
107
  ...(flow
85
- ? { flowLimits: ctx.flowLimits }
108
+ ? { flowLimits: flowLimitsOf(segment.limits) }
86
109
  : { yieldAfter: yieldAfterOf(ctx) }),
87
110
  fixtureModel: segment.fixtureModel,
88
111
  },
89
112
  routing: {
90
113
  rootManifest: current.manifest,
91
- ...(current.mcpSnapshot ? { mcpSnapshot: current.mcpSnapshot } : {}),
92
114
  sandbox: segment.sandbox,
93
115
  },
94
116
  ...(segment.trace ? { trace: segment.trace } : {}),
117
+ limits: harnessLimitsOf(segment.limits),
118
+ ...(segment.model && !flow ? { model: segment.model } : {}),
119
+ ...(segment.environment && !flow ? { environment: segment.environment } : {}),
120
+ };
121
+ }
122
+ /**
123
+ * The facts of the Environment a session runs in (protocol 15): its id and kind, and, with the
124
+ * session's sandbox, the workspace its commands start in. The harness words them.
125
+ */
126
+ function environmentOf(resource, spec) {
127
+ return {
128
+ id: resource.id,
129
+ kind: resource.kind,
130
+ ...(spec === undefined ? {} : { sandbox: { workspace: SANDBOX_WORKSPACE } }),
95
131
  };
96
132
  }
97
133
  /**
@@ -106,18 +142,21 @@ function turnContextOf(key, tenantId, s, turnId) {
106
142
  ...(s.turnTrace?.tracestate ? { tracestate: s.turnTrace.tracestate } : {}),
107
143
  };
108
144
  }
109
- /** The engine's yield budget for a segment. */
145
+ /**
146
+ * The engine's yield budget for a segment: the Tenant's `rollover`, else the Host tuning's
147
+ * (`harnessApi.rolloverSteps`, `rolloverMs`), well inside the advance deadline.
148
+ */
110
149
  export function yieldAfterOf(ctx) {
111
150
  return {
112
- steps: ctx.config.rollover?.steps ?? ROLLOVER_STEPS,
113
- ms: ctx.config.rollover?.ms ?? ROLLOVER_MS,
151
+ steps: ctx.config.rollover?.steps ?? hostTuning().harnessApi.rolloverSteps,
152
+ ms: ctx.config.rollover?.ms ?? hostTuning().harnessApi.rolloverMs,
114
153
  };
115
154
  }
116
155
  /**
117
156
  * A session written before transcripts were recorded keeps its transcript on its row: record
118
157
  * it once (the turn's starting transcript, then the turn so far), and store the row lean.
119
158
  */
120
- async function adoptStoredTranscript(t, s) {
159
+ async function adoptStoredTranscript(t, s, recordEntryBytes) {
121
160
  if (s.history)
122
161
  return;
123
162
  const now = transcriptOf(s.state);
@@ -131,9 +170,9 @@ async function adoptStoredTranscript(t, s) {
131
170
  first ??= event.seq;
132
171
  }
133
172
  };
134
- await write(null, transcriptUpdates([], start));
173
+ await write(null, transcriptUpdates([], start, recordEntryBytes));
135
174
  if (s.activeTurnId)
136
- await write(s.activeTurnId, transcriptUpdates(start, now));
175
+ await write(s.activeTurnId, transcriptUpdates(start, now, recordEntryBytes));
137
176
  s.history = { from: first ?? 0, snapshot: first ?? 0 };
138
177
  s.state = leanState(s.state);
139
178
  s.turnStartState = leanState(s.turnStartState);
@@ -5,12 +5,13 @@
5
5
  * Delivery (R5.3, R5.4). A tool call is sent at least once, and every send carries its effect
6
6
  * id as the key. After a crash, a shutdown or a lost gate hop, a row still `invoking` is sent
7
7
  * again: an MCP or HTTP tool call (it crosses the Tool Gate, which hands the tool the key) up to
8
- * `MAX_TOOL_ATTEMPTS` sends, then it fails to the model as `tool.interrupted`. A sandbox or
8
+ * the Tenant's Runtime limit `toolAttempts` sends, then it fails to the model as
9
+ * `tool.interrupted`. A sandbox or
9
10
  * core-served call has no key to dedupe on, so a crash fails it to the model at once, and
10
11
  * nothing runs it again (Q2). No tool call leaves its session `uncertain`.
11
12
  *
12
- * The row body keeps the journal's facts: `toolKind`, `server`, `attempts`, `startedAt` and
13
- * `settledAt`. A row written before R5 has none of them.
13
+ * The row body keeps the journal's facts: `toolKind`, `server`, `tool` (the identity, protocol
14
+ * 15), `attempts`, `startedAt` and `settledAt`. A row written before R5 has none of them.
14
15
  *
15
16
  * Spans (R7.1). `tool.completed` also carries the call's effect id, the Tool Gate's own start
16
17
  * and end of its last send (`gate`, from its `tool_crossings` row: MCP and HTTP calls through
@@ -23,8 +24,6 @@ import type { HostEffect } from "@nylorun/harness/run";
23
24
  import type { Session } from "../tenant/context.js";
24
25
  import { type GateTimes, type ToolCallFacts, type ToolCallKind, type ToolCallStatus, type ToolPolicyFact } from "../tenant/transcript.js";
25
26
  import type { EffectDoc, Tx } from "../store/types.js";
26
- /** How many times a tool call is sent before it fails to the model as `tool.interrupted`. */
27
- export declare const MAX_TOOL_ATTEMPTS = 3;
28
27
  /** A tool row's journal facts (R5), kept in its body. */
29
28
  export interface ToolRowFacts {
30
29
  /** Why the Tool Gate held the call for approval, and since when (R5 step 2). */
@@ -40,6 +39,8 @@ export interface ToolRowFacts {
40
39
  };
41
40
  toolKind?: ToolCallKind;
42
41
  server?: string;
42
+ /** The tool's identity: an MCP tool's name on `server`, an HTTP tool's declared name. */
43
+ tool?: string;
43
44
  /** The skill the call ran, and the plugin that brought its skill or MCP server. */
44
45
  skill?: string;
45
46
  plugin?: PluginLabel;
@@ -62,18 +63,22 @@ export type ToolRow = EffectDoc & ToolRowFacts & {
62
63
  };
63
64
  /** Where a tool call runs, and what it traces to (Q7, Q11). */
64
65
  export interface ToolCallPlace {
65
- toolKind: ToolCallKind;
66
+ /** Its target's kind; absent for a call that names none (an author's code tool). */
67
+ toolKind?: ToolCallKind;
66
68
  /** Its MCP server or, for an agent's HTTP tool, its URL's host. */
67
69
  server?: string;
70
+ /** Its identity: an MCP tool's own name on the server, an HTTP tool's declared name. */
71
+ tool?: string;
68
72
  skill?: string;
69
73
  plugin?: PluginLabel;
70
74
  }
71
75
  /**
72
- * Where a tool call runs, and what it traces to: an MCP call names its server and the plugin
73
- * that brought it; a skill tool, or a sandbox tool whose input names `/skills/<name>@<hash>/`, names the
74
- * skill and its plugin. A tool the turn's harness declared (`nativeTools`) runs in the harness.
76
+ * Where a tool call runs, and what it traces to (protocol 15): its target's kind; an MCP call
77
+ * names its identity (server and tool) and the plugin that brought the server, an HTTP call its
78
+ * declared name and its URL's host. The skill the effect names (`skill`: a skill tool's, or a
79
+ * sandbox call in a skill's folder) is kept when the agent has it, with the plugin that brought it.
75
80
  */
76
- export declare function toolKindOf(s: Pick<Session, "manifest" | "mcpSnapshot">, agentManifest: AgentManifest | undefined, request: HostEffect, nativeTools?: ReadonlySet<string>): ToolCallPlace;
81
+ export declare function toolKindOf(s: Pick<Session, "manifest">, agentManifest: AgentManifest | undefined, request: HostEffect): ToolCallPlace;
77
82
  /** The facts a new tool row starts with: its first send, now. */
78
83
  export declare function firstSend(kind: ToolCallPlace, now?: Date): ToolRowFacts;
79
84
  /** A failed outcome the Runtime gives a call a crash interrupted: it may have run. */
@@ -1,38 +1,39 @@
1
- import { pluginOf, skillOfToolCall } from "@nylorun/core/define";
1
+ import { pluginOf } from "@nylorun/core/define";
2
2
  import { isFlowToolEffect } from "../core/flow-host.js";
3
- import { declaredHttpTool, isHttpToolCall } from "../gates/http-tool.js";
4
- import { mcpToolOf } from "../mcp/snapshot.js";
5
- import { sandboxToolSpec } from "../sandbox/runtime-tools.js";
3
+ import { declaredHttpTool } from "../gates/http-tool.js";
6
4
  import { toolCompleted, } from "../tenant/transcript.js";
7
5
  import { toolCallDuration, toolCallStatus } from "./tool-status.js";
8
- /** How many times a tool call is sent before it fails to the model as `tool.interrupted`. */
9
- export const MAX_TOOL_ATTEMPTS = 3;
10
6
  /**
11
- * Where a tool call runs, and what it traces to: an MCP call names its server and the plugin
12
- * that brought it; a skill tool, or a sandbox tool whose input names `/skills/<name>@<hash>/`, names the
13
- * skill and its plugin. A tool the turn's harness declared (`nativeTools`) runs in the harness.
7
+ * Where a tool call runs, and what it traces to (protocol 15): its target's kind; an MCP call
8
+ * names its identity (server and tool) and the plugin that brought the server, an HTTP call its
9
+ * declared name and its URL's host. The skill the effect names (`skill`: a skill tool's, or a
10
+ * sandbox call in a skill's folder) is kept when the agent has it, with the plugin that brought it.
14
11
  */
15
- export function toolKindOf(s, agentManifest, request, nativeTools) {
16
- const mcp = mcpToolOf(s.mcpSnapshot, request);
17
- if (mcp) {
18
- const plugin = pluginOf(agentManifest, { server: mcp.serverName });
19
- return { toolKind: "mcp", server: mcp.serverName, ...(plugin === undefined ? {} : { plugin }) };
12
+ export function toolKindOf(s, agentManifest, request) {
13
+ const target = request.target;
14
+ const kind = target?.kind;
15
+ if (target?.kind === "mcp") {
16
+ const plugin = pluginOf(agentManifest, { server: target.server });
17
+ return {
18
+ toolKind: "mcp",
19
+ server: target.server,
20
+ tool: target.tool,
21
+ ...(plugin === undefined ? {} : { plugin }),
22
+ };
20
23
  }
21
- if (isHttpToolCall(s.manifest, request)) {
22
- const host = httpHostOf(declaredHttpTool(s.manifest, request.agent?.id, request.toolName)?.http.url);
23
- return { toolKind: "http", ...(host === undefined ? {} : { server: host }) };
24
+ if (target?.kind === "http") {
25
+ const host = httpHostOf(declaredHttpTool(s.manifest, request.agent?.id, target.tool)?.http.url);
26
+ return { toolKind: "http", ...(host === undefined ? {} : { server: host }), tool: target.tool };
24
27
  }
25
- if (request.toolName !== undefined && nativeTools?.has(request.toolName))
26
- return { toolKind: "harness" };
27
- const skill = skillOfToolCall(agentManifest, request.toolName, request.input);
28
+ const skill = request.skill !== undefined && agentManifest?.skills?.[request.skill] !== undefined
29
+ ? request.skill
30
+ : undefined;
28
31
  const plugin = skill === undefined ? undefined : pluginOf(agentManifest, { skill });
29
- const traced = {
32
+ return {
33
+ ...(kind === undefined ? {} : { toolKind: kind }),
30
34
  ...(skill === undefined ? {} : { skill }),
31
35
  ...(plugin === undefined ? {} : { plugin }),
32
36
  };
33
- if (sandboxToolSpec(agentManifest, request.toolName))
34
- return { toolKind: "sandbox", ...traced };
35
- return { toolKind: "platform", ...traced };
36
37
  }
37
38
  /** The host (and port, when it names one) of an HTTP tool's URL. */
38
39
  function httpHostOf(url) {
@@ -61,6 +62,7 @@ export function factsOf(row, status, gate) {
61
62
  status,
62
63
  ...(row.toolKind === undefined ? {} : { kind: row.toolKind }),
63
64
  ...(row.server === undefined ? {} : { server: row.server }),
65
+ ...(row.tool === undefined ? {} : { tool: row.tool }),
64
66
  ...(row.skill === undefined ? {} : { skill: row.skill }),
65
67
  ...(row.plugin === undefined ? {} : { plugin: row.plugin }),
66
68
  ...(row.attempts === undefined ? {} : { attempts: row.attempts }),