@loopingai/core 0.5.0 → 0.6.0

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 (304) hide show
  1. package/README.md +6 -4
  2. package/dist/a2a/agent-stub.d.ts +0 -1
  3. package/dist/a2a/agent-stub.js +0 -1
  4. package/dist/a2a/caller.d.ts +0 -1
  5. package/dist/a2a/caller.js +0 -1
  6. package/dist/a2a/card.d.ts +0 -1
  7. package/dist/a2a/card.js +0 -1
  8. package/dist/a2a/context.d.ts +0 -1
  9. package/dist/a2a/context.js +0 -1
  10. package/dist/a2a/executor.d.ts +0 -1
  11. package/dist/a2a/executor.js +0 -1
  12. package/dist/a2a/index.d.ts +0 -1
  13. package/dist/a2a/index.js +0 -1
  14. package/dist/a2a/notify.d.ts +4 -4
  15. package/dist/a2a/notify.js +4 -4
  16. package/dist/a2a/parts.d.ts +0 -1
  17. package/dist/a2a/parts.js +0 -1
  18. package/dist/a2a/push.d.ts +0 -1
  19. package/dist/a2a/push.js +0 -1
  20. package/dist/a2a/task-store.d.ts +0 -1
  21. package/dist/a2a/task-store.js +0 -1
  22. package/dist/a2a/task.d.ts +0 -1
  23. package/dist/a2a/task.js +0 -1
  24. package/dist/a2a/verify.d.ts +0 -1
  25. package/dist/a2a/verify.js +0 -1
  26. package/dist/agent/anthropic/index.d.ts +15 -0
  27. package/dist/agent/anthropic/index.js +19 -0
  28. package/dist/agent/anthropic/language-model.d.ts +59 -0
  29. package/dist/agent/anthropic/language-model.js +442 -0
  30. package/dist/agent/anthropic/prompt.d.ts +84 -0
  31. package/dist/agent/anthropic/prompt.js +541 -0
  32. package/dist/agent/anthropic/runtime.d.ts +79 -0
  33. package/dist/agent/anthropic/runtime.js +130 -0
  34. package/dist/agent/budget.d.ts +0 -1
  35. package/dist/agent/budget.js +0 -1
  36. package/dist/agent/control.d.ts +0 -1
  37. package/dist/agent/control.js +10 -10
  38. package/dist/agent/errors.d.ts +85 -0
  39. package/dist/agent/errors.js +64 -0
  40. package/dist/agent/final-reply.d.ts +14 -14
  41. package/dist/agent/final-reply.js +28 -12
  42. package/dist/agent/history.d.ts +3 -4
  43. package/dist/agent/history.js +2 -3
  44. package/dist/agent/index.d.ts +4 -3
  45. package/dist/agent/index.js +4 -3
  46. package/dist/agent/inference.d.ts +58 -2
  47. package/dist/agent/inference.js +44 -1
  48. package/dist/agent/model.d.ts +42 -26
  49. package/dist/agent/model.js +1 -49
  50. package/dist/agent/session.d.ts +6 -8
  51. package/dist/agent/session.js +3 -4
  52. package/dist/agent/workers-ai/index.d.ts +23 -0
  53. package/dist/agent/workers-ai/index.js +23 -0
  54. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  55. package/dist/agent/workers-ai/runtime.js +63 -0
  56. package/dist/config.d.ts +49 -16
  57. package/dist/config.js +30 -2
  58. package/dist/contract/index.d.ts +0 -1
  59. package/dist/contract/index.js +0 -1
  60. package/dist/contract/plugin.d.ts +63 -4
  61. package/dist/contract/plugin.js +76 -1
  62. package/dist/contract/recipe.d.ts +16 -18
  63. package/dist/contract/recipe.js +0 -1
  64. package/dist/contract/validation.d.ts +0 -1
  65. package/dist/contract/validation.js +0 -1
  66. package/dist/db/db.d.ts +0 -2
  67. package/dist/db/db.js +0 -1
  68. package/dist/db/index.d.ts +0 -1
  69. package/dist/db/index.js +0 -1
  70. package/dist/db/migrations/index.d.ts +0 -1
  71. package/dist/db/migrations/index.js +8 -2
  72. package/dist/db/models/subtasks.d.ts +24 -26
  73. package/dist/db/models/subtasks.js +33 -77
  74. package/dist/db/models/tasks.d.ts +0 -1
  75. package/dist/db/models/tasks.js +0 -1
  76. package/dist/db/schema.d.ts +2 -22
  77. package/dist/db/schema.js +2 -5
  78. package/dist/env.d.ts +0 -1
  79. package/dist/env.js +0 -1
  80. package/dist/host/agent.d.ts +58 -5
  81. package/dist/host/agent.js +63 -10
  82. package/dist/host/index.d.ts +0 -1
  83. package/dist/host/index.js +0 -1
  84. package/dist/host/plugin-host.d.ts +0 -1
  85. package/dist/host/plugin-host.js +0 -1
  86. package/dist/index.d.ts +2 -3
  87. package/dist/index.js +2 -3
  88. package/dist/platform.d.ts +74 -12
  89. package/dist/platform.js +76 -14
  90. package/dist/round/agent.d.ts +36 -32
  91. package/dist/round/agent.js +61 -90
  92. package/dist/round/index.d.ts +3 -3
  93. package/dist/round/index.js +2 -3
  94. package/dist/round/policy.d.ts +2 -3
  95. package/dist/round/policy.js +0 -1
  96. package/dist/round/subagent.d.ts +19 -2
  97. package/dist/round/subagent.js +22 -6
  98. package/dist/round/turn.d.ts +32 -14
  99. package/dist/round/turn.js +83 -17
  100. package/dist/round/workflow.d.ts +30 -9
  101. package/dist/round/workflow.js +146 -78
  102. package/dist/runtime/index.d.ts +4 -3
  103. package/dist/runtime/index.js +6 -1
  104. package/dist/runtime/tool-families.d.ts +0 -1
  105. package/dist/runtime/tool-families.js +0 -1
  106. package/dist/subagent/fingerprint.d.ts +2 -3
  107. package/dist/subagent/fingerprint.js +8 -18
  108. package/dist/subagent/index.d.ts +6 -5
  109. package/dist/subagent/index.js +8 -7
  110. package/dist/subagent/prompt.d.ts +4 -6
  111. package/dist/subagent/prompt.js +0 -9
  112. package/dist/subagent/run.d.ts +8 -2
  113. package/dist/subagent/run.js +59 -10
  114. package/dist/subagent/workspace.d.ts +0 -1
  115. package/dist/subagent/workspace.js +0 -1
  116. package/dist/subtasks/catalog.d.ts +1 -2
  117. package/dist/subtasks/catalog.js +1 -2
  118. package/dist/subtasks/decomposition.d.ts +16 -21
  119. package/dist/subtasks/decomposition.js +27 -76
  120. package/dist/subtasks/delegate.d.ts +20 -2
  121. package/dist/subtasks/delegate.js +21 -17
  122. package/dist/subtasks/index.d.ts +1 -3
  123. package/dist/subtasks/index.js +1 -3
  124. package/dist/subtasks/subtask-types.d.ts +0 -9
  125. package/dist/subtasks/subtask-types.js +0 -8
  126. package/dist/subtasks/types.d.ts +45 -71
  127. package/dist/subtasks/types.js +0 -1
  128. package/dist/testing/auth.d.ts +0 -1
  129. package/dist/testing/auth.js +0 -1
  130. package/dist/testing/do.d.ts +0 -1
  131. package/dist/testing/do.js +0 -1
  132. package/dist/testing/fake-session.d.ts +0 -1
  133. package/dist/testing/fake-session.js +0 -1
  134. package/dist/testing/fixtures.d.ts +0 -1
  135. package/dist/testing/fixtures.js +0 -1
  136. package/dist/testing/harness.d.ts +0 -1
  137. package/dist/testing/harness.js +0 -1
  138. package/dist/testing/index.d.ts +0 -1
  139. package/dist/testing/index.js +0 -1
  140. package/dist/testing/mock-model.d.ts +35 -1
  141. package/dist/testing/mock-model.js +75 -1
  142. package/dist/testing/node.d.ts +0 -1
  143. package/dist/testing/node.js +0 -1
  144. package/dist/testing/vcr-global-setup.d.ts +1 -4
  145. package/dist/testing/vcr-global-setup.js +1 -4
  146. package/dist/testing/vcr-shared.d.ts +0 -1
  147. package/dist/testing/vcr-shared.js +0 -1
  148. package/dist/testing/vcr-spec.d.ts +0 -1
  149. package/dist/testing/vcr-spec.js +0 -1
  150. package/dist/testing/vcr-store.d.ts +0 -1
  151. package/dist/testing/vcr-store.js +0 -1
  152. package/dist/testing/vcr.d.ts +0 -1
  153. package/dist/testing/vcr.js +0 -1
  154. package/dist/worker/define-agent.d.ts +0 -1
  155. package/dist/worker/define-agent.js +0 -1
  156. package/dist/worker/index.d.ts +5 -13
  157. package/dist/worker/index.js +5 -13
  158. package/package.json +20 -2
  159. package/dist/a2a/agent-stub.d.ts.map +0 -1
  160. package/dist/a2a/agent-stub.js.map +0 -1
  161. package/dist/a2a/caller.d.ts.map +0 -1
  162. package/dist/a2a/caller.js.map +0 -1
  163. package/dist/a2a/card.d.ts.map +0 -1
  164. package/dist/a2a/card.js.map +0 -1
  165. package/dist/a2a/context.d.ts.map +0 -1
  166. package/dist/a2a/context.js.map +0 -1
  167. package/dist/a2a/executor.d.ts.map +0 -1
  168. package/dist/a2a/executor.js.map +0 -1
  169. package/dist/a2a/index.d.ts.map +0 -1
  170. package/dist/a2a/index.js.map +0 -1
  171. package/dist/a2a/notify.d.ts.map +0 -1
  172. package/dist/a2a/notify.js.map +0 -1
  173. package/dist/a2a/parts.d.ts.map +0 -1
  174. package/dist/a2a/parts.js.map +0 -1
  175. package/dist/a2a/push.d.ts.map +0 -1
  176. package/dist/a2a/push.js.map +0 -1
  177. package/dist/a2a/task-store.d.ts.map +0 -1
  178. package/dist/a2a/task-store.js.map +0 -1
  179. package/dist/a2a/task.d.ts.map +0 -1
  180. package/dist/a2a/task.js.map +0 -1
  181. package/dist/a2a/verify.d.ts.map +0 -1
  182. package/dist/a2a/verify.js.map +0 -1
  183. package/dist/agent/budget.d.ts.map +0 -1
  184. package/dist/agent/budget.js.map +0 -1
  185. package/dist/agent/control.d.ts.map +0 -1
  186. package/dist/agent/control.js.map +0 -1
  187. package/dist/agent/final-reply.d.ts.map +0 -1
  188. package/dist/agent/final-reply.js.map +0 -1
  189. package/dist/agent/history.d.ts.map +0 -1
  190. package/dist/agent/history.js.map +0 -1
  191. package/dist/agent/index.d.ts.map +0 -1
  192. package/dist/agent/index.js.map +0 -1
  193. package/dist/agent/inference.d.ts.map +0 -1
  194. package/dist/agent/inference.js.map +0 -1
  195. package/dist/agent/model.d.ts.map +0 -1
  196. package/dist/agent/model.js.map +0 -1
  197. package/dist/agent/session.d.ts.map +0 -1
  198. package/dist/agent/session.js.map +0 -1
  199. package/dist/config.d.ts.map +0 -1
  200. package/dist/config.js.map +0 -1
  201. package/dist/contract/index.d.ts.map +0 -1
  202. package/dist/contract/index.js.map +0 -1
  203. package/dist/contract/plugin.d.ts.map +0 -1
  204. package/dist/contract/plugin.js.map +0 -1
  205. package/dist/contract/recipe.d.ts.map +0 -1
  206. package/dist/contract/recipe.js.map +0 -1
  207. package/dist/contract/validation.d.ts.map +0 -1
  208. package/dist/contract/validation.js.map +0 -1
  209. package/dist/db/db.d.ts.map +0 -1
  210. package/dist/db/db.js.map +0 -1
  211. package/dist/db/index.d.ts.map +0 -1
  212. package/dist/db/index.js.map +0 -1
  213. package/dist/db/migrations/index.d.ts.map +0 -1
  214. package/dist/db/migrations/index.js.map +0 -1
  215. package/dist/db/models/subtasks.d.ts.map +0 -1
  216. package/dist/db/models/subtasks.js.map +0 -1
  217. package/dist/db/models/tasks.d.ts.map +0 -1
  218. package/dist/db/models/tasks.js.map +0 -1
  219. package/dist/db/schema.d.ts.map +0 -1
  220. package/dist/db/schema.js.map +0 -1
  221. package/dist/env.d.ts.map +0 -1
  222. package/dist/env.js.map +0 -1
  223. package/dist/host/agent.d.ts.map +0 -1
  224. package/dist/host/agent.js.map +0 -1
  225. package/dist/host/index.d.ts.map +0 -1
  226. package/dist/host/index.js.map +0 -1
  227. package/dist/host/plugin-host.d.ts.map +0 -1
  228. package/dist/host/plugin-host.js.map +0 -1
  229. package/dist/index.d.ts.map +0 -1
  230. package/dist/index.js.map +0 -1
  231. package/dist/platform.d.ts.map +0 -1
  232. package/dist/platform.js.map +0 -1
  233. package/dist/round/agent.d.ts.map +0 -1
  234. package/dist/round/agent.js.map +0 -1
  235. package/dist/round/index.d.ts.map +0 -1
  236. package/dist/round/index.js.map +0 -1
  237. package/dist/round/policy.d.ts.map +0 -1
  238. package/dist/round/policy.js.map +0 -1
  239. package/dist/round/subagent.d.ts.map +0 -1
  240. package/dist/round/subagent.js.map +0 -1
  241. package/dist/round/turn.d.ts.map +0 -1
  242. package/dist/round/turn.js.map +0 -1
  243. package/dist/round/workflow.d.ts.map +0 -1
  244. package/dist/round/workflow.js.map +0 -1
  245. package/dist/runtime/index.d.ts.map +0 -1
  246. package/dist/runtime/index.js.map +0 -1
  247. package/dist/runtime/tool-families.d.ts.map +0 -1
  248. package/dist/runtime/tool-families.js.map +0 -1
  249. package/dist/subagent/fingerprint.d.ts.map +0 -1
  250. package/dist/subagent/fingerprint.js.map +0 -1
  251. package/dist/subagent/index.d.ts.map +0 -1
  252. package/dist/subagent/index.js.map +0 -1
  253. package/dist/subagent/prompt.d.ts.map +0 -1
  254. package/dist/subagent/prompt.js.map +0 -1
  255. package/dist/subagent/run.d.ts.map +0 -1
  256. package/dist/subagent/run.js.map +0 -1
  257. package/dist/subagent/workspace.d.ts.map +0 -1
  258. package/dist/subagent/workspace.js.map +0 -1
  259. package/dist/subtasks/catalog.d.ts.map +0 -1
  260. package/dist/subtasks/catalog.js.map +0 -1
  261. package/dist/subtasks/decomposition.d.ts.map +0 -1
  262. package/dist/subtasks/decomposition.js.map +0 -1
  263. package/dist/subtasks/delegate.d.ts.map +0 -1
  264. package/dist/subtasks/delegate.js.map +0 -1
  265. package/dist/subtasks/index.d.ts.map +0 -1
  266. package/dist/subtasks/index.js.map +0 -1
  267. package/dist/subtasks/scheduler.d.ts +0 -49
  268. package/dist/subtasks/scheduler.d.ts.map +0 -1
  269. package/dist/subtasks/scheduler.js +0 -48
  270. package/dist/subtasks/scheduler.js.map +0 -1
  271. package/dist/subtasks/subtask-types.d.ts.map +0 -1
  272. package/dist/subtasks/subtask-types.js.map +0 -1
  273. package/dist/subtasks/types.d.ts.map +0 -1
  274. package/dist/subtasks/types.js.map +0 -1
  275. package/dist/testing/auth.d.ts.map +0 -1
  276. package/dist/testing/auth.js.map +0 -1
  277. package/dist/testing/do.d.ts.map +0 -1
  278. package/dist/testing/do.js.map +0 -1
  279. package/dist/testing/fake-session.d.ts.map +0 -1
  280. package/dist/testing/fake-session.js.map +0 -1
  281. package/dist/testing/fixtures.d.ts.map +0 -1
  282. package/dist/testing/fixtures.js.map +0 -1
  283. package/dist/testing/harness.d.ts.map +0 -1
  284. package/dist/testing/harness.js.map +0 -1
  285. package/dist/testing/index.d.ts.map +0 -1
  286. package/dist/testing/index.js.map +0 -1
  287. package/dist/testing/mock-model.d.ts.map +0 -1
  288. package/dist/testing/mock-model.js.map +0 -1
  289. package/dist/testing/node.d.ts.map +0 -1
  290. package/dist/testing/node.js.map +0 -1
  291. package/dist/testing/vcr-global-setup.d.ts.map +0 -1
  292. package/dist/testing/vcr-global-setup.js.map +0 -1
  293. package/dist/testing/vcr-shared.d.ts.map +0 -1
  294. package/dist/testing/vcr-shared.js.map +0 -1
  295. package/dist/testing/vcr-spec.d.ts.map +0 -1
  296. package/dist/testing/vcr-spec.js.map +0 -1
  297. package/dist/testing/vcr-store.d.ts.map +0 -1
  298. package/dist/testing/vcr-store.js.map +0 -1
  299. package/dist/testing/vcr.d.ts.map +0 -1
  300. package/dist/testing/vcr.js.map +0 -1
  301. package/dist/worker/define-agent.d.ts.map +0 -1
  302. package/dist/worker/define-agent.js.map +0 -1
  303. package/dist/worker/index.d.ts.map +0 -1
  304. package/dist/worker/index.js.map +0 -1
@@ -164,8 +164,8 @@ export declare const notifyTasks: import("drizzle-orm/sqlite-core").SQLiteTableW
164
164
  *
165
165
  * The integer primary key assigns a caller-local, monotonically increasing
166
166
  * `SubtaskId` (autoincrement, so ids are never reused after cleanup deletes
167
- * rows). References, dependency edges, and result parts are stored as JSON text
168
- * and parsed back into the `Subtask` contract by `models/subtasks.ts`.
167
+ * rows). References and result parts are stored as JSON text and parsed back
168
+ * into the `Subtask` contract by `models/subtasks.ts`.
169
169
  * `recipe_id`/`recipe_version` are null until execution starts, then record the
170
170
  * resolved recipe after the fact.
171
171
  */
@@ -336,25 +336,6 @@ export declare const subtasks: import("drizzle-orm/sqlite-core").SQLiteTableWith
336
336
  }, {}, {
337
337
  length: number | undefined;
338
338
  }>;
339
- dependsOnJson: import("drizzle-orm/sqlite-core").SQLiteColumn<{
340
- name: "depends_on_json";
341
- tableName: "subtasks";
342
- dataType: "string";
343
- columnType: "SQLiteText";
344
- data: string;
345
- driverParam: string;
346
- notNull: true;
347
- hasDefault: false;
348
- isPrimaryKey: false;
349
- isAutoincrement: false;
350
- hasRuntimeDefault: false;
351
- enumValues: [string, ...string[]];
352
- baseColumn: never;
353
- identity: undefined;
354
- generated: undefined;
355
- }, {}, {
356
- length: number | undefined;
357
- }>;
358
339
  paramsJson: import("drizzle-orm/sqlite-core").SQLiteColumn<{
359
340
  name: "params_json";
360
341
  tableName: "subtasks";
@@ -485,4 +466,3 @@ export declare const subtasks: import("drizzle-orm/sqlite-core").SQLiteTableWith
485
466
  };
486
467
  dialect: "sqlite";
487
468
  }>;
488
- //# sourceMappingURL=schema.d.ts.map
package/dist/db/schema.js CHANGED
@@ -51,8 +51,8 @@ export const notifyTasks = sqliteTable("notify_tasks", {
51
51
  *
52
52
  * The integer primary key assigns a caller-local, monotonically increasing
53
53
  * `SubtaskId` (autoincrement, so ids are never reused after cleanup deletes
54
- * rows). References, dependency edges, and result parts are stored as JSON text
55
- * and parsed back into the `Subtask` contract by `models/subtasks.ts`.
54
+ * rows). References and result parts are stored as JSON text and parsed back
55
+ * into the `Subtask` contract by `models/subtasks.ts`.
56
56
  * `recipe_id`/`recipe_version` are null until execution starts, then record the
57
57
  * resolved recipe after the fact.
58
58
  */
@@ -71,8 +71,6 @@ export const subtasks = sqliteTable("subtasks", {
71
71
  prompt: text("prompt").notNull(),
72
72
  /** JSON `SubtaskReference[]` — verbatim role+text snapshots from decomposition. */
73
73
  referencesJson: text("references_json").notNull(),
74
- /** JSON `SubtaskId[]` — resolved dependency edges. */
75
- dependsOnJson: text("depends_on_json").notNull(),
76
74
  /** JSON `SubtaskParams` — the type's required inputs, validated at delegation. */
77
75
  paramsJson: text("params_json").notNull().default("{}"),
78
76
  status: text("status").notNull(),
@@ -88,4 +86,3 @@ export const subtasks = sqliteTable("subtasks", {
88
86
  index("idx_subtasks_status").on(table.status),
89
87
  index("idx_subtasks_created_at").on(table.createdAt)
90
88
  ]);
91
- //# sourceMappingURL=schema.js.map
package/dist/env.d.ts CHANGED
@@ -51,4 +51,3 @@ export type CoreEnv = AiEnv & A2ASecretsEnv;
51
51
  * rather than as an empty allowlist that rejects every caller identically.
52
52
  */
53
53
  export declare function parseGatewayOrigins(raw: string): string[];
54
- //# sourceMappingURL=env.d.ts.map
package/dist/env.js CHANGED
@@ -45,4 +45,3 @@ export function parseGatewayOrigins(raw) {
45
45
  throw new Error("GATEWAY_ORIGINS is empty");
46
46
  return parsed;
47
47
  }
48
- //# sourceMappingURL=env.js.map
@@ -2,7 +2,7 @@ import { Agent, type Schedule } from "agents";
2
2
  import { type Task } from "@a2a-js/sdk";
3
3
  import { type AgentRuntime } from "../runtime/index.js";
4
4
  import type { AgentPlugin } from "../contract/plugin.js";
5
- import { type CoreConfig, type CoreConfigOverrides } from "../config.js";
5
+ import { type CoreConfig, type CoreConfigOverrides, type ModelConfig } from "../config.js";
6
6
  import type { A2ASecretsEnv, AiEnv } from "../env.js";
7
7
  import { AgentDB } from "../db/index.js";
8
8
  import type { GatewayIdentity } from "../a2a/verify.js";
@@ -10,7 +10,7 @@ import type { PlainTask } from "../a2a/task.js";
10
10
  import type { TaskListQuery } from "../a2a/agent-stub.js";
11
11
  import { type PushChannel, type TurnPushContext } from "../a2a/push.js";
12
12
  import { type SessionLike } from "../agent/session.js";
13
- import { type GatewayMetadata, type ModelPair, type ModelRuntime } from "../agent/model.js";
13
+ import type { GatewayMetadata, ModelPair, ModelRuntime } from "../agent/model.js";
14
14
  import type { PluginHost } from "./plugin-host.js";
15
15
  /**
16
16
  * The Durable Object body every Looping agent has, whatever loop it runs.
@@ -64,6 +64,9 @@ export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv &
64
64
  * `onStart` runs before any request, so it is not known when `agentPlugins()`
65
65
  * is built — which is why anything per-caller takes a thunk. The DO is keyed
66
66
  * 1:1 by this value, so it is constant once set.
67
+ *
68
+ * In-memory, and deliberately so — see {@link requireIdentityKey}, which does
69
+ * not depend on it surviving.
67
70
  */
68
71
  private identityKey?;
69
72
  /**
@@ -95,7 +98,30 @@ export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv &
95
98
  protected get config(): CoreConfig;
96
99
  /** The agent's database (drizzle + migrations), built once per DO instance. */
97
100
  protected get db(): AgentDB;
98
- /** The model runtime for this instance, built lazily over the `AI` binding. */
101
+ /**
102
+ * Which provider this agent's loops run on. Defaults to Workers AI; override
103
+ * to run on something else.
104
+ *
105
+ * The seam is here rather than in `models` because `models` memoizes, and a
106
+ * subclass overriding a memoized getter has to remember to keep the caching —
107
+ * a trap that only shows up as a performance bug. This is called once.
108
+ *
109
+ * `ModelRuntime` is the whole contract: return anything satisfying it and
110
+ * every loop in core keeps working unchanged. Core ships two implementations,
111
+ * one directory each — {@link file://../agent/workers-ai/index.ts
112
+ * `agent/workers-ai`} (the default below) and `@loopingai/core/anthropic` —
113
+ * and a third provider is a third directory exporting one
114
+ * {@link file://../agent/model.ts ModelRuntimeFactory}, not a change to
115
+ * anything on this path.
116
+ *
117
+ * Takes the resolved {@link ModelConfig} rather than reading `this.config`, so
118
+ * that this signature matches
119
+ * {@link file://../round/subagent.ts RecipeSubagentHost.modelRuntime} — an
120
+ * agent and its facet **must** run the same provider, and identical seams are
121
+ * what let one factory serve both instead of two hand-copied bodies.
122
+ */
123
+ protected modelRuntime(model: ModelConfig): ModelRuntime;
124
+ /** The model runtime for this instance, built lazily and memoized. */
99
125
  protected get models(): ModelRuntime;
100
126
  /**
101
127
  * What this agent's plugins are handed. Built from
@@ -141,7 +167,35 @@ export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv &
141
167
  * announces the loss, and the runtime fans it out to every plugin that asked.
142
168
  */
143
169
  getSession(identity: GatewayIdentity): SessionLike;
144
- /** The caller key, which is present on every path that can reach a plugin. */
170
+ /**
171
+ * The caller key, which is present on every path that can reach a plugin.
172
+ *
173
+ * ## Why this does not just read the field
174
+ *
175
+ * `identityKey` is set on the first turn and lives in the isolate. An isolate
176
+ * does not live as long as the work does: it can be evicted between two rounds
177
+ * of the same task, and it can be reset outright — "Durable Object connection
178
+ * closed because the object was reset" — while a Workflow step is mid-flight.
179
+ * The next call arrives on a fresh instance where the field is empty, and
180
+ * every per-caller thunk built off it throws.
181
+ *
182
+ * That failure is disproportionate to its cause. A plugin asking which caller
183
+ * it is serving gets an exception, mid-task, on an object whose entire purpose
184
+ * is to be that caller's — and because the throw happens inside a tool or a
185
+ * runtime resolution rather than at the edge, it surfaces as a failed branch
186
+ * rather than as anything an operator can read.
187
+ *
188
+ * So the object answers from itself. `define-agent` routes with
189
+ * `ns.get(ns.idFromName(identity.key))`, which means the caller key *is* this
190
+ * object's name and the platform hands it back on `ctx.id.name` — durable, free
191
+ * and correct by construction: an object cannot disagree with the name it was
192
+ * addressed by.
193
+ *
194
+ * The field still wins when it is set. `id.name` is undefined for an object
195
+ * addressed by `newUniqueId()` or a raw id string, so it is a fallback rather
196
+ * than the source of truth, and the throw is kept for the case where neither
197
+ * exists.
198
+ */
145
199
  protected requireIdentityKey(): string;
146
200
  /** The gateway callback channel for one turn. See {@link PushChannel}. */
147
201
  protected push(context: TurnPushContext): PushChannel;
@@ -214,4 +268,3 @@ export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv &
214
268
  */
215
269
  protected onTaskCanceled(_taskId: string): Promise<void>;
216
270
  }
217
- //# sourceMappingURL=agent.d.ts.map
@@ -6,7 +6,7 @@ import { AgentDB, stateOf } from "../db/index.js";
6
6
  import { callerContext } from "../a2a/caller.js";
7
7
  import { createPushChannel } from "../a2a/push.js";
8
8
  import { buildAgentSession } from "../agent/session.js";
9
- import { createModelRuntime } from "../agent/model.js";
9
+ import { workersAIModels } from "../agent/workers-ai/index.js";
10
10
  /**
11
11
  * The Durable Object body every Looping agent has, whatever loop it runs.
12
12
  *
@@ -59,6 +59,9 @@ export class LoopingAgent extends Agent {
59
59
  * `onStart` runs before any request, so it is not known when `agentPlugins()`
60
60
  * is built — which is why anything per-caller takes a thunk. The DO is keyed
61
61
  * 1:1 by this value, so it is constant once set.
62
+ *
63
+ * In-memory, and deliberately so — see {@link requireIdentityKey}, which does
64
+ * not depend on it surviving.
62
65
  */
63
66
  identityKey;
64
67
  /**
@@ -101,12 +104,34 @@ export class LoopingAgent extends Agent {
101
104
  stores: this.runtime.stores
102
105
  }));
103
106
  }
104
- /** The model runtime for this instance, built lazily over the `AI` binding. */
107
+ /**
108
+ * Which provider this agent's loops run on. Defaults to Workers AI; override
109
+ * to run on something else.
110
+ *
111
+ * The seam is here rather than in `models` because `models` memoizes, and a
112
+ * subclass overriding a memoized getter has to remember to keep the caching —
113
+ * a trap that only shows up as a performance bug. This is called once.
114
+ *
115
+ * `ModelRuntime` is the whole contract: return anything satisfying it and
116
+ * every loop in core keeps working unchanged. Core ships two implementations,
117
+ * one directory each — {@link file://../agent/workers-ai/index.ts
118
+ * `agent/workers-ai`} (the default below) and `@loopingai/core/anthropic` —
119
+ * and a third provider is a third directory exporting one
120
+ * {@link file://../agent/model.ts ModelRuntimeFactory}, not a change to
121
+ * anything on this path.
122
+ *
123
+ * Takes the resolved {@link ModelConfig} rather than reading `this.config`, so
124
+ * that this signature matches
125
+ * {@link file://../round/subagent.ts RecipeSubagentHost.modelRuntime} — an
126
+ * agent and its facet **must** run the same provider, and identical seams are
127
+ * what let one factory serve both instead of two hand-copied bodies.
128
+ */
129
+ modelRuntime(model) {
130
+ return workersAIModels(this.env, model);
131
+ }
132
+ /** The model runtime for this instance, built lazily and memoized. */
105
133
  get models() {
106
- return (this._models ??= createModelRuntime({
107
- ai: this.env.AI,
108
- config: this.config.model
109
- }));
134
+ return (this._models ??= this.modelRuntime(this.config.model));
110
135
  }
111
136
  /**
112
137
  * What this agent's plugins are handed. Built from
@@ -193,12 +218,41 @@ export class LoopingAgent extends Agent {
193
218
  onMessagesDisplaced: this.runtime.onMessagesDisplaced
194
219
  }));
195
220
  }
196
- /** The caller key, which is present on every path that can reach a plugin. */
221
+ /**
222
+ * The caller key, which is present on every path that can reach a plugin.
223
+ *
224
+ * ## Why this does not just read the field
225
+ *
226
+ * `identityKey` is set on the first turn and lives in the isolate. An isolate
227
+ * does not live as long as the work does: it can be evicted between two rounds
228
+ * of the same task, and it can be reset outright — "Durable Object connection
229
+ * closed because the object was reset" — while a Workflow step is mid-flight.
230
+ * The next call arrives on a fresh instance where the field is empty, and
231
+ * every per-caller thunk built off it throws.
232
+ *
233
+ * That failure is disproportionate to its cause. A plugin asking which caller
234
+ * it is serving gets an exception, mid-task, on an object whose entire purpose
235
+ * is to be that caller's — and because the throw happens inside a tool or a
236
+ * runtime resolution rather than at the edge, it surfaces as a failed branch
237
+ * rather than as anything an operator can read.
238
+ *
239
+ * So the object answers from itself. `define-agent` routes with
240
+ * `ns.get(ns.idFromName(identity.key))`, which means the caller key *is* this
241
+ * object's name and the platform hands it back on `ctx.id.name` — durable, free
242
+ * and correct by construction: an object cannot disagree with the name it was
243
+ * addressed by.
244
+ *
245
+ * The field still wins when it is set. `id.name` is undefined for an object
246
+ * addressed by `newUniqueId()` or a raw id string, so it is a fallback rather
247
+ * than the source of truth, and the throw is kept for the case where neither
248
+ * exists.
249
+ */
197
250
  requireIdentityKey() {
198
- if (!this.identityKey) {
251
+ const key = this.identityKey ?? this.ctx.id.name;
252
+ if (!key) {
199
253
  throw new Error("identity.key is required for per-caller isolation");
200
254
  }
201
- return this.identityKey;
255
+ return (this.identityKey = key);
202
256
  }
203
257
  /** The gateway callback channel for one turn. See {@link PushChannel}. */
204
258
  push(context) {
@@ -301,4 +355,3 @@ export class LoopingAgent extends Agent {
301
355
  */
302
356
  async onTaskCanceled(_taskId) { }
303
357
  }
304
- //# sourceMappingURL=agent.js.map
@@ -18,4 +18,3 @@
18
18
  */
19
19
  export { LoopingAgent } from "./agent.js";
20
20
  export type { PluginHost } from "./plugin-host.js";
21
- //# sourceMappingURL=index.d.ts.map
@@ -17,4 +17,3 @@
17
17
  * round loop, which is opt-in and takes its prompt copy from the agent.
18
18
  */
19
19
  export { LoopingAgent } from "./agent.js";
20
- //# sourceMappingURL=index.js.map
@@ -40,4 +40,3 @@ export interface PluginHost<TEnv extends object = object> {
40
40
  /** `config.model.aiGatewayId`, already resolved over core's baseline. */
41
41
  aiGatewayId: string;
42
42
  }
43
- //# sourceMappingURL=plugin-host.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=plugin-host.js.map
package/dist/index.d.ts CHANGED
@@ -9,12 +9,11 @@
9
9
  * production bundle.
10
10
  */
11
11
  export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies, type AgentRuntime, type CreateAgentRuntimeOptions } from "./runtime/index.js";
12
- export { PLUGIN_CONTRACT_VERSION, definePlugin, type AgentPlugin, type EmitProgress, type EnrichResultContext, type MainAgentToolContext, type PluginRequirements, type RecipeToolSet, type ResolveRuntimeContext, type ToolFamilyBuilder, type ToolFamilyContext, type TurnGateContext } from "./contract/plugin.js";
12
+ export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools, type AgentPlugin, type RestrictMainAgentToolsOptions, type EmitProgress, type EnrichResultContext, type MainAgentToolContext, type PluginRequirements, type RecipeToolSet, type ResolveRuntimeContext, type ToolFamilyBuilder, type ToolFamilyContext, type TurnGateContext } from "./contract/plugin.js";
13
13
  export type { DelegationNames, RecipeLimits, ResolvedRecipe, SubtaskParams, SubtaskParamsSchema, SubtaskParamsShape, SubtaskTypeSpec, ValidatedRecipe } from "./contract/recipe.js";
14
14
  export { RecipeValidationError, resolveLimits, validateRecipe, type RecipePolicy } from "./contract/validation.js";
15
15
  export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig, type AgentLimits, type CoreConfig, type CoreConfigOverrides, type ModelConfig, type SessionConfig } from "./config.js";
16
16
  export { parseGatewayOrigins, type A2ASecretsEnv, type AiEnv, type CoreEnv } from "./env.js";
17
- export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
17
+ export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
18
18
  export type { PluginStore } from "./db/db.js";
19
19
  export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES, type WorkspaceBacking, type WorkspaceEntry, type WorkspaceHandle } from "./subagent/workspace.js";
20
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -9,10 +9,9 @@
9
9
  * production bundle.
10
10
  */
11
11
  export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies } from "./runtime/index.js";
12
- export { PLUGIN_CONTRACT_VERSION, definePlugin } from "./contract/plugin.js";
12
+ export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools } from "./contract/plugin.js";
13
13
  export { RecipeValidationError, resolveLimits, validateRecipe } from "./contract/validation.js";
14
14
  export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig } from "./config.js";
15
15
  export { parseGatewayOrigins } from "./env.js";
16
- export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
16
+ export { CHUNK_SOFT_MS, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
17
17
  export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES } from "./subagent/workspace.js";
18
- //# sourceMappingURL=index.js.map
@@ -4,20 +4,38 @@
4
4
  * for those, and note that no Recipe can reach these. They change when the
5
5
  * platform changes, and for no other reason.
6
6
  *
7
- * The distinction is worth keeping sharp, because collapsing it is what produced
8
- * the bug this file was extracted during: a *turn count* was used to keep a step
9
- * under the step timeout, which only works if you can predict how long a turn
10
- * takes. You cannot. Time bounds time here; turns bound cost, over in `config.ts`.
7
+ * The distinction is worth keeping sharp, because collapsing it produces a
8
+ * specific bug: using a *turn count* to keep a step under the step timeout only
9
+ * works if you can predict how long a turn takes. You cannot. Time bounds time
10
+ * here; turns bound cost, over in `config.ts`.
11
11
  */
12
12
  /**
13
- * Platform fact: a `step.do(...)` callback is killed at ~10 minutes (Workflows'
14
- * default step timeout). {@link CHUNK_SOFT_MS} is sized against this.
13
+ * The step timeout **we configure**, passed as `WorkflowStepConfig.timeout` on
14
+ * every step that can hold a model call or a container command.
15
+ *
16
+ * This is the one value in this file that is **not** a platform fact, and
17
+ * treating it as one is the mistake to avoid: ten minutes is Workflows'
18
+ * *default* step timeout, not its ceiling, and a step that never passes a config
19
+ * inherits it silently. Sizing {@link CHUNK_SOFT_MS} against that inherited
20
+ * default is what once ran a production task as ten four-minute slices, one of
21
+ * which blew the ten minutes anyway and was retried whole.
22
+ *
23
+ * The genuine platform facts are narrower and do not bind us: wall-clock time per
24
+ * step is effectively unlimited, and a step is bounded by **CPU** time. Measured
25
+ * over a 59-minute task, the chunk steps used ~100 ms of CPU against 3,146 s of
26
+ * wall — this ceiling is nowhere near the real one.
27
+ *
28
+ * It is still a ceiling worth having, because it is what turns a hung container
29
+ * into a retry rather than a task that never ends. {@link CHUNK_SOFT_MS} is sized
30
+ * against it, and that relationship is asserted in `platform.spec.ts`.
15
31
  */
16
32
  export declare const STEP_TIMEOUT_MS: number;
17
33
  /**
18
- * Platform fact: a single Workflow instance may run ~10,000 steps (paid plan).
19
- * {@link MAX_CHUNKS_PER_BRANCH} is sized against thissee the worst-case
20
- * product asserted in `platform.spec.ts`.
34
+ * Platform fact: a single Workflow instance may run 10,000 steps by default on the
35
+ * paid plan. Cloudflare will raise it to 25,000 on request worth knowing, and
36
+ * worth not relying on: {@link MAX_CHUNKS_PER_BRANCH} is sized against the default
37
+ * so nothing here needs an account-level exception to be correct. See the
38
+ * worst-case product asserted in `platform.spec.ts`.
21
39
  */
22
40
  export declare const STEPS_PER_INSTANCE = 10000;
23
41
  /**
@@ -28,8 +46,52 @@ export declare const STEPS_PER_INSTANCE = 10000;
28
46
  * This is the *only* thing keeping a step under the timeout. A subagent otherwise
29
47
  * runs until its turn or wall-clock budget is spent, however many turns that takes
30
48
  * — which is the point: the runner no longer guesses at turn duration.
49
+ *
50
+ * ## Why this is 15 minutes and not 4
51
+ *
52
+ * A chunk boundary is not free. It checkpoints, returns through two RPC hops,
53
+ * starts a fresh step, and re-hydrates the subagent — and for a coding agent it
54
+ * also means the container connection is re-established. Four minutes bought a
55
+ * boundary roughly every third tool call: a task that edited one README line spent
56
+ * 59 minutes across **ten** chunks, and the model was idle for most of each one,
57
+ * blocked on a single `sb_exec` running the project's test gate.
58
+ *
59
+ * ## Why it is not larger, which is the part that bit us
60
+ *
61
+ * This is a **soft** deadline, checked between turns (`stopWhen` in
62
+ * `subagent/run.ts`). A turn that starts one millisecond before it trips still runs
63
+ * to completion, so the real worst case is:
64
+ *
65
+ * chunk wall ≤ CHUNK_SOFT_MS + one whole turn
66
+ *
67
+ * and one turn is a model call plus a tool call. The old pair ignored that: four
68
+ * minutes soft under a ten-minute timeout looked like six minutes of headroom, but
69
+ * a single `sb_exec` may run for {@link MAX_TOOL_CALL_MS}, so a turn could add ten.
70
+ * That is not a hypothetical — it is the `WorkflowTimeoutError` that cost a
71
+ * production task ten minutes and a full chunk replay.
72
+ *
73
+ * So the headroom is sized against a whole turn, not against a guess:
74
+ * `STEP_TIMEOUT_MS - CHUNK_SOFT_MS` is 15 minutes, covering
75
+ * {@link MAX_TOOL_CALL_MS} of tool call plus five minutes for the model call and
76
+ * its provider retries. Asserted in `platform.spec.ts` — raise the step timeout
77
+ * before raising this.
31
78
  */
32
79
  export declare const CHUNK_SOFT_MS: number;
80
+ /**
81
+ * The longest a **single tool call** may run, and a contract rather than a
82
+ * mechanism: core has no way to enforce it, because core installs no tools.
83
+ *
84
+ * It exists because {@link CHUNK_SOFT_MS} cannot be reasoned about without it. The
85
+ * soft deadline is checked between turns, so a host that lets one tool block for
86
+ * longer than the headroom under {@link STEP_TIMEOUT_MS} reintroduces exactly the
87
+ * step-timeout kill this pair is sized to prevent — and it reintroduces it
88
+ * invisibly, in a plugin, a long way from this file.
89
+ *
90
+ * A host installing a tool that can block (a shell, a container command, a fetch
91
+ * with no ceiling of its own) must bound it at or below this. See the `timeoutMs`
92
+ * passed to `@loopingai/plugins/computer` in looping-starter.
93
+ */
94
+ export declare const MAX_TOOL_CALL_MS: number;
33
95
  /**
34
96
  * Hard ceiling on durable chunk steps for one Subtask branch. A backstop, not a
35
97
  * budget: the Workflow *fails* a branch that reaches it, so reaching it is a bug.
@@ -39,9 +101,9 @@ export declare const CHUNK_SOFT_MS: number;
39
101
  * 1. It exceeds every Recipe's `maxTurns`. A chunk that yields always advanced at
40
102
  * least one turn, so a run takes at most `maxTurns` chunks however short they
41
103
  * are — and they do get short, because `CHUNK_SOFT_MS` and progress events both
42
- * end one early. That bound survives both; a nominal `maxTurns / turnsPerChunk`
43
- * estimate survived neither, which is why no such estimate exists any more.
104
+ * end one early. Counting turns is what makes the bound survive that; any
105
+ * estimate of turns-per-chunk would not, since neither of those two is
106
+ * predictable.
44
107
  * 2. The worst-case step product stays under {@link STEPS_PER_INSTANCE}.
45
108
  */
46
109
  export declare const MAX_CHUNKS_PER_BRANCH = 40;
47
- //# sourceMappingURL=platform.d.ts.map
package/dist/platform.js CHANGED
@@ -4,20 +4,38 @@
4
4
  * for those, and note that no Recipe can reach these. They change when the
5
5
  * platform changes, and for no other reason.
6
6
  *
7
- * The distinction is worth keeping sharp, because collapsing it is what produced
8
- * the bug this file was extracted during: a *turn count* was used to keep a step
9
- * under the step timeout, which only works if you can predict how long a turn
10
- * takes. You cannot. Time bounds time here; turns bound cost, over in `config.ts`.
7
+ * The distinction is worth keeping sharp, because collapsing it produces a
8
+ * specific bug: using a *turn count* to keep a step under the step timeout only
9
+ * works if you can predict how long a turn takes. You cannot. Time bounds time
10
+ * here; turns bound cost, over in `config.ts`.
11
11
  */
12
12
  /**
13
- * Platform fact: a `step.do(...)` callback is killed at ~10 minutes (Workflows'
14
- * default step timeout). {@link CHUNK_SOFT_MS} is sized against this.
13
+ * The step timeout **we configure**, passed as `WorkflowStepConfig.timeout` on
14
+ * every step that can hold a model call or a container command.
15
+ *
16
+ * This is the one value in this file that is **not** a platform fact, and
17
+ * treating it as one is the mistake to avoid: ten minutes is Workflows'
18
+ * *default* step timeout, not its ceiling, and a step that never passes a config
19
+ * inherits it silently. Sizing {@link CHUNK_SOFT_MS} against that inherited
20
+ * default is what once ran a production task as ten four-minute slices, one of
21
+ * which blew the ten minutes anyway and was retried whole.
22
+ *
23
+ * The genuine platform facts are narrower and do not bind us: wall-clock time per
24
+ * step is effectively unlimited, and a step is bounded by **CPU** time. Measured
25
+ * over a 59-minute task, the chunk steps used ~100 ms of CPU against 3,146 s of
26
+ * wall — this ceiling is nowhere near the real one.
27
+ *
28
+ * It is still a ceiling worth having, because it is what turns a hung container
29
+ * into a retry rather than a task that never ends. {@link CHUNK_SOFT_MS} is sized
30
+ * against it, and that relationship is asserted in `platform.spec.ts`.
15
31
  */
16
- export const STEP_TIMEOUT_MS = 10 * 60_000;
32
+ export const STEP_TIMEOUT_MS = 30 * 60_000;
17
33
  /**
18
- * Platform fact: a single Workflow instance may run ~10,000 steps (paid plan).
19
- * {@link MAX_CHUNKS_PER_BRANCH} is sized against thissee the worst-case
20
- * product asserted in `platform.spec.ts`.
34
+ * Platform fact: a single Workflow instance may run 10,000 steps by default on the
35
+ * paid plan. Cloudflare will raise it to 25,000 on request worth knowing, and
36
+ * worth not relying on: {@link MAX_CHUNKS_PER_BRANCH} is sized against the default
37
+ * so nothing here needs an account-level exception to be correct. See the
38
+ * worst-case product asserted in `platform.spec.ts`.
21
39
  */
22
40
  export const STEPS_PER_INSTANCE = 10_000;
23
41
  /**
@@ -28,8 +46,52 @@ export const STEPS_PER_INSTANCE = 10_000;
28
46
  * This is the *only* thing keeping a step under the timeout. A subagent otherwise
29
47
  * runs until its turn or wall-clock budget is spent, however many turns that takes
30
48
  * — which is the point: the runner no longer guesses at turn duration.
49
+ *
50
+ * ## Why this is 15 minutes and not 4
51
+ *
52
+ * A chunk boundary is not free. It checkpoints, returns through two RPC hops,
53
+ * starts a fresh step, and re-hydrates the subagent — and for a coding agent it
54
+ * also means the container connection is re-established. Four minutes bought a
55
+ * boundary roughly every third tool call: a task that edited one README line spent
56
+ * 59 minutes across **ten** chunks, and the model was idle for most of each one,
57
+ * blocked on a single `sb_exec` running the project's test gate.
58
+ *
59
+ * ## Why it is not larger, which is the part that bit us
60
+ *
61
+ * This is a **soft** deadline, checked between turns (`stopWhen` in
62
+ * `subagent/run.ts`). A turn that starts one millisecond before it trips still runs
63
+ * to completion, so the real worst case is:
64
+ *
65
+ * chunk wall ≤ CHUNK_SOFT_MS + one whole turn
66
+ *
67
+ * and one turn is a model call plus a tool call. The old pair ignored that: four
68
+ * minutes soft under a ten-minute timeout looked like six minutes of headroom, but
69
+ * a single `sb_exec` may run for {@link MAX_TOOL_CALL_MS}, so a turn could add ten.
70
+ * That is not a hypothetical — it is the `WorkflowTimeoutError` that cost a
71
+ * production task ten minutes and a full chunk replay.
72
+ *
73
+ * So the headroom is sized against a whole turn, not against a guess:
74
+ * `STEP_TIMEOUT_MS - CHUNK_SOFT_MS` is 15 minutes, covering
75
+ * {@link MAX_TOOL_CALL_MS} of tool call plus five minutes for the model call and
76
+ * its provider retries. Asserted in `platform.spec.ts` — raise the step timeout
77
+ * before raising this.
78
+ */
79
+ export const CHUNK_SOFT_MS = 15 * 60_000;
80
+ /**
81
+ * The longest a **single tool call** may run, and a contract rather than a
82
+ * mechanism: core has no way to enforce it, because core installs no tools.
83
+ *
84
+ * It exists because {@link CHUNK_SOFT_MS} cannot be reasoned about without it. The
85
+ * soft deadline is checked between turns, so a host that lets one tool block for
86
+ * longer than the headroom under {@link STEP_TIMEOUT_MS} reintroduces exactly the
87
+ * step-timeout kill this pair is sized to prevent — and it reintroduces it
88
+ * invisibly, in a plugin, a long way from this file.
89
+ *
90
+ * A host installing a tool that can block (a shell, a container command, a fetch
91
+ * with no ceiling of its own) must bound it at or below this. See the `timeoutMs`
92
+ * passed to `@loopingai/plugins/computer` in looping-starter.
31
93
  */
32
- export const CHUNK_SOFT_MS = 4 * 60_000;
94
+ export const MAX_TOOL_CALL_MS = 10 * 60_000;
33
95
  /**
34
96
  * Hard ceiling on durable chunk steps for one Subtask branch. A backstop, not a
35
97
  * budget: the Workflow *fails* a branch that reaches it, so reaching it is a bug.
@@ -39,9 +101,9 @@ export const CHUNK_SOFT_MS = 4 * 60_000;
39
101
  * 1. It exceeds every Recipe's `maxTurns`. A chunk that yields always advanced at
40
102
  * least one turn, so a run takes at most `maxTurns` chunks however short they
41
103
  * are — and they do get short, because `CHUNK_SOFT_MS` and progress events both
42
- * end one early. That bound survives both; a nominal `maxTurns / turnsPerChunk`
43
- * estimate survived neither, which is why no such estimate exists any more.
104
+ * end one early. Counting turns is what makes the bound survive that; any
105
+ * estimate of turns-per-chunk would not, since neither of those two is
106
+ * predictable.
44
107
  * 2. The worst-case step product stays under {@link STEPS_PER_INSTANCE}.
45
108
  */
46
109
  export const MAX_CHUNKS_PER_BRANCH = 40;
47
- //# sourceMappingURL=platform.js.map