@loopingai/core 0.1.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 (238) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +279 -0
  3. package/dist/a2a/agent-stub.d.ts +61 -0
  4. package/dist/a2a/agent-stub.d.ts.map +1 -0
  5. package/dist/a2a/agent-stub.js +2 -0
  6. package/dist/a2a/agent-stub.js.map +1 -0
  7. package/dist/a2a/card.d.ts +119 -0
  8. package/dist/a2a/card.d.ts.map +1 -0
  9. package/dist/a2a/card.js +115 -0
  10. package/dist/a2a/card.js.map +1 -0
  11. package/dist/a2a/context.d.ts +14 -0
  12. package/dist/a2a/context.d.ts.map +1 -0
  13. package/dist/a2a/context.js +55 -0
  14. package/dist/a2a/context.js.map +1 -0
  15. package/dist/a2a/executor.d.ts +85 -0
  16. package/dist/a2a/executor.d.ts.map +1 -0
  17. package/dist/a2a/executor.js +107 -0
  18. package/dist/a2a/executor.js.map +1 -0
  19. package/dist/a2a/index.d.ts +18 -0
  20. package/dist/a2a/index.d.ts.map +1 -0
  21. package/dist/a2a/index.js +17 -0
  22. package/dist/a2a/index.js.map +1 -0
  23. package/dist/a2a/notify.d.ts +75 -0
  24. package/dist/a2a/notify.d.ts.map +1 -0
  25. package/dist/a2a/notify.js +161 -0
  26. package/dist/a2a/notify.js.map +1 -0
  27. package/dist/a2a/parts.d.ts +46 -0
  28. package/dist/a2a/parts.d.ts.map +1 -0
  29. package/dist/a2a/parts.js +95 -0
  30. package/dist/a2a/parts.js.map +1 -0
  31. package/dist/a2a/task-store.d.ts +45 -0
  32. package/dist/a2a/task-store.d.ts.map +1 -0
  33. package/dist/a2a/task-store.js +100 -0
  34. package/dist/a2a/task-store.js.map +1 -0
  35. package/dist/a2a/task.d.ts +86 -0
  36. package/dist/a2a/task.d.ts.map +1 -0
  37. package/dist/a2a/task.js +16 -0
  38. package/dist/a2a/task.js.map +1 -0
  39. package/dist/a2a/verify.d.ts +64 -0
  40. package/dist/a2a/verify.d.ts.map +1 -0
  41. package/dist/a2a/verify.js +122 -0
  42. package/dist/a2a/verify.js.map +1 -0
  43. package/dist/agent/budget.d.ts +47 -0
  44. package/dist/agent/budget.d.ts.map +1 -0
  45. package/dist/agent/budget.js +43 -0
  46. package/dist/agent/budget.js.map +1 -0
  47. package/dist/agent/control.d.ts +110 -0
  48. package/dist/agent/control.d.ts.map +1 -0
  49. package/dist/agent/control.js +115 -0
  50. package/dist/agent/control.js.map +1 -0
  51. package/dist/agent/final-reply.d.ts +49 -0
  52. package/dist/agent/final-reply.d.ts.map +1 -0
  53. package/dist/agent/final-reply.js +52 -0
  54. package/dist/agent/final-reply.js.map +1 -0
  55. package/dist/agent/history.d.ts +83 -0
  56. package/dist/agent/history.d.ts.map +1 -0
  57. package/dist/agent/history.js +117 -0
  58. package/dist/agent/history.js.map +1 -0
  59. package/dist/agent/index.d.ts +17 -0
  60. package/dist/agent/index.d.ts.map +1 -0
  61. package/dist/agent/index.js +17 -0
  62. package/dist/agent/index.js.map +1 -0
  63. package/dist/agent/inference.d.ts +52 -0
  64. package/dist/agent/inference.d.ts.map +1 -0
  65. package/dist/agent/inference.js +79 -0
  66. package/dist/agent/inference.js.map +1 -0
  67. package/dist/agent/model.d.ts +75 -0
  68. package/dist/agent/model.d.ts.map +1 -0
  69. package/dist/agent/model.js +62 -0
  70. package/dist/agent/model.js.map +1 -0
  71. package/dist/agent/session.d.ts +92 -0
  72. package/dist/agent/session.d.ts.map +1 -0
  73. package/dist/agent/session.js +80 -0
  74. package/dist/agent/session.js.map +1 -0
  75. package/dist/config.d.ts +159 -0
  76. package/dist/config.d.ts.map +1 -0
  77. package/dist/config.js +92 -0
  78. package/dist/config.js.map +1 -0
  79. package/dist/contract/index.d.ts +10 -0
  80. package/dist/contract/index.d.ts.map +1 -0
  81. package/dist/contract/index.js +9 -0
  82. package/dist/contract/index.js.map +1 -0
  83. package/dist/contract/plugin.d.ts +163 -0
  84. package/dist/contract/plugin.d.ts.map +1 -0
  85. package/dist/contract/plugin.js +39 -0
  86. package/dist/contract/plugin.js.map +1 -0
  87. package/dist/contract/recipe.d.ts +170 -0
  88. package/dist/contract/recipe.d.ts.map +1 -0
  89. package/dist/contract/recipe.js +2 -0
  90. package/dist/contract/recipe.js.map +1 -0
  91. package/dist/contract/validation.d.ts +86 -0
  92. package/dist/contract/validation.d.ts.map +1 -0
  93. package/dist/contract/validation.js +76 -0
  94. package/dist/contract/validation.js.map +1 -0
  95. package/dist/db/db.d.ts +131 -0
  96. package/dist/db/db.d.ts.map +1 -0
  97. package/dist/db/db.js +91 -0
  98. package/dist/db/db.js.map +1 -0
  99. package/dist/db/index.d.ts +9 -0
  100. package/dist/db/index.d.ts.map +1 -0
  101. package/dist/db/index.js +9 -0
  102. package/dist/db/index.js.map +1 -0
  103. package/dist/db/migrations/index.d.ts +21 -0
  104. package/dist/db/migrations/index.d.ts.map +1 -0
  105. package/dist/db/migrations/index.js +54 -0
  106. package/dist/db/migrations/index.js.map +1 -0
  107. package/dist/db/models/subtasks.d.ts +102 -0
  108. package/dist/db/models/subtasks.d.ts.map +1 -0
  109. package/dist/db/models/subtasks.js +285 -0
  110. package/dist/db/models/subtasks.js.map +1 -0
  111. package/dist/db/models/tasks.d.ts +92 -0
  112. package/dist/db/models/tasks.d.ts.map +1 -0
  113. package/dist/db/models/tasks.js +212 -0
  114. package/dist/db/models/tasks.js.map +1 -0
  115. package/dist/db/schema.d.ts +488 -0
  116. package/dist/db/schema.d.ts.map +1 -0
  117. package/dist/db/schema.js +91 -0
  118. package/dist/db/schema.js.map +1 -0
  119. package/dist/env.d.ts +46 -0
  120. package/dist/env.d.ts.map +1 -0
  121. package/dist/env.js +40 -0
  122. package/dist/env.js.map +1 -0
  123. package/dist/index.d.ts +20 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +18 -0
  126. package/dist/index.js.map +1 -0
  127. package/dist/platform.d.ts +47 -0
  128. package/dist/platform.d.ts.map +1 -0
  129. package/dist/platform.js +47 -0
  130. package/dist/platform.js.map +1 -0
  131. package/dist/runtime/index.d.ts +86 -0
  132. package/dist/runtime/index.d.ts.map +1 -0
  133. package/dist/runtime/index.js +123 -0
  134. package/dist/runtime/index.js.map +1 -0
  135. package/dist/runtime/tool-families.d.ts +33 -0
  136. package/dist/runtime/tool-families.d.ts.map +1 -0
  137. package/dist/runtime/tool-families.js +62 -0
  138. package/dist/runtime/tool-families.js.map +1 -0
  139. package/dist/subagent/fingerprint.d.ts +38 -0
  140. package/dist/subagent/fingerprint.d.ts.map +1 -0
  141. package/dist/subagent/fingerprint.js +97 -0
  142. package/dist/subagent/fingerprint.js.map +1 -0
  143. package/dist/subagent/index.d.ts +147 -0
  144. package/dist/subagent/index.d.ts.map +1 -0
  145. package/dist/subagent/index.js +299 -0
  146. package/dist/subagent/index.js.map +1 -0
  147. package/dist/subagent/prompt.d.ts +34 -0
  148. package/dist/subagent/prompt.d.ts.map +1 -0
  149. package/dist/subagent/prompt.js +46 -0
  150. package/dist/subagent/prompt.js.map +1 -0
  151. package/dist/subagent/run.d.ts +151 -0
  152. package/dist/subagent/run.d.ts.map +1 -0
  153. package/dist/subagent/run.js +491 -0
  154. package/dist/subagent/run.js.map +1 -0
  155. package/dist/subagent/workspace.d.ts +65 -0
  156. package/dist/subagent/workspace.d.ts.map +1 -0
  157. package/dist/subagent/workspace.js +53 -0
  158. package/dist/subagent/workspace.js.map +1 -0
  159. package/dist/subtasks/catalog.d.ts +40 -0
  160. package/dist/subtasks/catalog.d.ts.map +1 -0
  161. package/dist/subtasks/catalog.js +36 -0
  162. package/dist/subtasks/catalog.js.map +1 -0
  163. package/dist/subtasks/decomposition.d.ts +90 -0
  164. package/dist/subtasks/decomposition.d.ts.map +1 -0
  165. package/dist/subtasks/decomposition.js +205 -0
  166. package/dist/subtasks/decomposition.js.map +1 -0
  167. package/dist/subtasks/delegate.d.ts +77 -0
  168. package/dist/subtasks/delegate.d.ts.map +1 -0
  169. package/dist/subtasks/delegate.js +102 -0
  170. package/dist/subtasks/delegate.js.map +1 -0
  171. package/dist/subtasks/index.d.ts +11 -0
  172. package/dist/subtasks/index.d.ts.map +1 -0
  173. package/dist/subtasks/index.js +11 -0
  174. package/dist/subtasks/index.js.map +1 -0
  175. package/dist/subtasks/scheduler.d.ts +49 -0
  176. package/dist/subtasks/scheduler.d.ts.map +1 -0
  177. package/dist/subtasks/scheduler.js +48 -0
  178. package/dist/subtasks/scheduler.js.map +1 -0
  179. package/dist/subtasks/subtask-types.d.ts +100 -0
  180. package/dist/subtasks/subtask-types.d.ts.map +1 -0
  181. package/dist/subtasks/subtask-types.js +111 -0
  182. package/dist/subtasks/subtask-types.js.map +1 -0
  183. package/dist/subtasks/types.d.ts +321 -0
  184. package/dist/subtasks/types.d.ts.map +1 -0
  185. package/dist/subtasks/types.js +16 -0
  186. package/dist/subtasks/types.js.map +1 -0
  187. package/dist/testing/auth.d.ts +12 -0
  188. package/dist/testing/auth.d.ts.map +1 -0
  189. package/dist/testing/auth.js +25 -0
  190. package/dist/testing/auth.js.map +1 -0
  191. package/dist/testing/do.d.ts +30 -0
  192. package/dist/testing/do.d.ts.map +1 -0
  193. package/dist/testing/do.js +26 -0
  194. package/dist/testing/do.js.map +1 -0
  195. package/dist/testing/fake-session.d.ts +27 -0
  196. package/dist/testing/fake-session.d.ts.map +1 -0
  197. package/dist/testing/fake-session.js +38 -0
  198. package/dist/testing/fake-session.js.map +1 -0
  199. package/dist/testing/fixtures.d.ts +27 -0
  200. package/dist/testing/fixtures.d.ts.map +1 -0
  201. package/dist/testing/fixtures.js +67 -0
  202. package/dist/testing/fixtures.js.map +1 -0
  203. package/dist/testing/index.d.ts +31 -0
  204. package/dist/testing/index.d.ts.map +1 -0
  205. package/dist/testing/index.js +31 -0
  206. package/dist/testing/index.js.map +1 -0
  207. package/dist/testing/mock-model.d.ts +43 -0
  208. package/dist/testing/mock-model.d.ts.map +1 -0
  209. package/dist/testing/mock-model.js +62 -0
  210. package/dist/testing/mock-model.js.map +1 -0
  211. package/dist/testing/node.d.ts +24 -0
  212. package/dist/testing/node.d.ts.map +1 -0
  213. package/dist/testing/node.js +24 -0
  214. package/dist/testing/node.js.map +1 -0
  215. package/dist/testing/vcr-global-setup.d.ts +3 -0
  216. package/dist/testing/vcr-global-setup.d.ts.map +1 -0
  217. package/dist/testing/vcr-global-setup.js +9 -0
  218. package/dist/testing/vcr-global-setup.js.map +1 -0
  219. package/dist/testing/vcr-shared.d.ts +21 -0
  220. package/dist/testing/vcr-shared.d.ts.map +1 -0
  221. package/dist/testing/vcr-shared.js +21 -0
  222. package/dist/testing/vcr-shared.js.map +1 -0
  223. package/dist/testing/vcr-spec.d.ts +17 -0
  224. package/dist/testing/vcr-spec.d.ts.map +1 -0
  225. package/dist/testing/vcr-spec.js +72 -0
  226. package/dist/testing/vcr-spec.js.map +1 -0
  227. package/dist/testing/vcr.d.ts +81 -0
  228. package/dist/testing/vcr.d.ts.map +1 -0
  229. package/dist/testing/vcr.js +211 -0
  230. package/dist/testing/vcr.js.map +1 -0
  231. package/dist/worker/index.d.ts +62 -0
  232. package/dist/worker/index.d.ts.map +1 -0
  233. package/dist/worker/index.js +194 -0
  234. package/dist/worker/index.js.map +1 -0
  235. package/eslint-rules/index.js +31 -0
  236. package/eslint-rules/no-deprecated-object-properties.js +81 -0
  237. package/package.json +147 -0
  238. package/scripts/generate-keys.mjs +53 -0
@@ -0,0 +1,147 @@
1
+ import { Agent } from "agents";
2
+ import type { ModelPair, ModelRuntime } from "../agent/model.js";
3
+ import type { ToolFamilyBuilder } from "../contract/plugin.js";
4
+ import { type RecipePolicy } from "../contract/validation.js";
5
+ import type { RecipeChunkResult, RecipeExecutionRequest, SubtaskId, SubtaskRuntime } from "../subtasks/types.js";
6
+ import { type SubtaskTypeRegistry } from "../subtasks/subtask-types.js";
7
+ import { type WorkspaceBacking } from "./workspace.js";
8
+ /**
9
+ * Everything the facet needs that comes from the *host*: resolved config, the
10
+ * capability policy, the installed subtask types and tool families, the model
11
+ * runtime, and a workspace backend.
12
+ *
13
+ * A Durable Object class is constructed by the runtime, so it cannot take
14
+ * constructor arguments — which is exactly the problem a library version of this
15
+ * facet has to solve. {@link RecipeSubagentBase} takes it from an abstract
16
+ * method instead: the consumer subclasses once, and everything host-specific
17
+ * arrives through that one seam.
18
+ */
19
+ export interface SubagentRuntime {
20
+ policy: RecipePolicy;
21
+ types: SubtaskTypeRegistry;
22
+ models: ModelRuntime;
23
+ toolFamilies: ReadonlyMap<string, ToolFamilyBuilder>;
24
+ /** `CoreConfig.toolOutputWindow`. */
25
+ toolOutputWindow: number;
26
+ /** `CoreConfig.model.maxOutputTokens`. */
27
+ maxOutputTokens: number;
28
+ /**
29
+ * Build the durable file store over this facet's own SQLite.
30
+ *
31
+ * Supplied by the host because the backend is a plugin's: core declares the
32
+ * {@link WorkspaceBacking} shape and the caps, `@loopingai/plugins/workspace`
33
+ * supplies the `@cloudflare/shell` implementation, and an agent that never
34
+ * delegates file work installs neither.
35
+ */
36
+ workspaceBacking(sql: SqlStorage, name: () => string | undefined): WorkspaceBacking;
37
+ }
38
+ /**
39
+ * Message prefix of the error thrown when a child that already holds a cached
40
+ * terminal result receives a *different* request. Custom error classes don't
41
+ * survive DO RPC, so this prefix is the cross-boundary contract: it signals a
42
+ * parent lifecycle bug — stale children must be deleted before a genuinely new
43
+ * execution — and a Workflow retry after the parent's cleanup will succeed.
44
+ */
45
+ export declare const FINGERPRINT_MISMATCH = "recipe-subagent: request fingerprint mismatch";
46
+ /** Deterministic managed-child name for one Subtask execution (shared with C3). */
47
+ export declare function subagentName(taskId: string, subtaskId: SubtaskId): string;
48
+ /**
49
+ * `RecipeSubagent` — the isolated, stateless managed child that executes one
50
+ * Subtask under a resolved Recipe. Created as an Agents SDK sub-agent (facet)
51
+ * beneath the caller's `ReactiveAgent`, so it needs no wrangler Durable Object
52
+ * binding and no `new_sqlite_classes` entry; it must only be exported from the
53
+ * worker entry (`src/index.ts`) so `ctx.exports` can resolve it by class name.
54
+ *
55
+ * It never constructs a Session, never reads parent history beyond the
56
+ * references supplied on its request, never uses recall or durable memory, and
57
+ * never resolves a Recipe itself — it defensively re-validates the resolved
58
+ * Recipe the parent sends and accepts no configuration beyond it.
59
+ *
60
+ * Retry safety: the child persists at most one terminal result in its own
61
+ * SQLite, keyed by the deterministic request fingerprint, plus the rolling
62
+ * `run_state` of an in-progress multi-chunk run. A retry with the same
63
+ * fingerprint replays the terminal result or resumes the run without repeating
64
+ * completed work; a different request for the same child name is rejected
65
+ * ({@link FINGERPRINT_MISMATCH}). Transient platform faults throw and cache
66
+ * nothing, so the enclosing Workflow step can retry. The parent deletes the child
67
+ * (`deleteSubAgent`) only after its durable copy of the result succeeds, which
68
+ * wipes this storage — the workspace and run state included.
69
+ *
70
+ * Not "stateless" like the single-shot original: it owns per-execution durable
71
+ * state (the workspace and the run checkpoint), scoped to one execution and swept
72
+ * with the child.
73
+ */
74
+ export declare abstract class RecipeSubagentBase<TEnv extends Cloudflare.Env = Cloudflare.Env> extends Agent<TEnv> {
75
+ /**
76
+ * Supply the host runtime. Called per RPC, not memoized here — an
77
+ * implementation that builds something expensive should memoize its own.
78
+ *
79
+ * ```ts
80
+ * export class RecipeSubagent extends RecipeSubagentBase<Env> {
81
+ * protected subagentRuntime() {
82
+ * return (this._rt ??= buildSubagentRuntime(this.env));
83
+ * }
84
+ * }
85
+ * ```
86
+ */
87
+ protected abstract subagentRuntime(): SubagentRuntime;
88
+ /**
89
+ * Test-only `ModelPair` injection (a field, so never on the RPC stub).
90
+ * A whole pair — rather than model instances — so error-path tests can throw
91
+ * synchronously from the pair's factories, the repo convention (a rejecting
92
+ * `doGenerate` inside `generateText` leaks an unhandled rejection through
93
+ * the AI SDK telemetry span that workerd flags as a failure).
94
+ */
95
+ modelsOverride?: ModelPair;
96
+ private _workspace?;
97
+ /**
98
+ * The chunk currently executing here, if any. In memory only — it exists to be
99
+ * interrupted mid-call, and an isolate that lost it has no in-flight call left
100
+ * to interrupt. See {@link abortRun}.
101
+ */
102
+ private inflight?;
103
+ onStart(): Promise<void>;
104
+ /**
105
+ * Idempotent schema bootstrap. Also called lazily from the RPCs so
106
+ * `runInDurableObject`-style tests reach ready tables without RPC dispatch
107
+ * (mirroring how `AgentDB` migrates on construction).
108
+ */
109
+ private ensureTables;
110
+ /** The recipe's durable workspace, backed by this facet's own SQLite storage. */
111
+ private workspace;
112
+ /**
113
+ * Execute one durable chunk of a Subtask under the parent's resolved Recipe.
114
+ *
115
+ * A terminal outcome (completed / failed) is cached and replayed on retry. A
116
+ * mid-run chunk persists its rolling state to `run_state` and returns a
117
+ * `done: false` yield for the Workflow to run another chunk. `chunk` is a
118
+ * separate argument — never part of `request` — so every chunk fingerprints
119
+ * identically and the cache/resume keys line up. Only transient platform faults
120
+ * throw (nothing cached), so a Workflow retry resumes from the last checkpoint.
121
+ */
122
+ executeChunk(request: RecipeExecutionRequest, _chunk: number, runtime?: SubtaskRuntime): Promise<RecipeChunkResult>;
123
+ /**
124
+ * Interrupt the chunk running here right now, so a cancellation lands on the
125
+ * current model call instead of at the next chunk boundary (up to `chunkSoftMs`
126
+ * later — minutes, for a long recipe). Returns whether there was one to stop.
127
+ *
128
+ * Distinct from {@link abortExecution}, which releases *external* state after
129
+ * the fact; this only stops local work. An aborted run yields rather than
130
+ * producing a terminal result, so nothing is cached and the parent resolves the
131
+ * row itself. Reaching a facet mid-`executeChunk` works because it is awaiting
132
+ * a model `fetch` at the time, which does not hold the input gate closed.
133
+ */
134
+ abortRun(): Promise<boolean>;
135
+ /**
136
+ * Best-effort cleanup on cancellation: rebuild the recipe's tool families and
137
+ * run their `abort` hooks (e.g. release an external resource recorded in the
138
+ * workspace session file). Reconstructible from the workspace, so it is safe on a fresh
139
+ * isolate. The parent supplies the validated tool families it resolved.
140
+ */
141
+ abortExecution(toolFamilies: string[]): Promise<void>;
142
+ /** Persist a terminal result to the cache and return it as a done chunk. */
143
+ private cacheTerminal;
144
+ /** Persist the rolling run state (called after every model turn). */
145
+ private saveRunState;
146
+ }
147
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/subagent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAG/B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAEV,iBAAiB,EACjB,sBAAsB,EAEtB,SAAS,EACT,cAAc,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAI5E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACrD,qCAAqC;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,gBAAgB,CACd,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,MAAM,MAAM,GAAG,SAAS,GAC7B,gBAAgB,CAAC;CACrB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,kDACgB,CAAC;AAElD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,CAEzE;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,CAC5C,SAAQ,KAAK,CAAC,IAAI,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,IAAI,eAAe;IAErD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,SAAS,CAAC;IAE3B,OAAO,CAAC,UAAU,CAAC,CAAmB;IAEtC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,CAAkB;IAE7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAmBpB,iFAAiF;IACjF,OAAO,CAAC,SAAS;IAOjB;;;;;;;;;OASG;IACG,YAAY,CAChB,OAAO,EAAE,sBAAsB,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,iBAAiB,CAAC;IAmH7B;;;;;;;;;;OAUG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAMlC;;;;;OAKG;IACG,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3D,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAgBrB,qEAAqE;IACrE,OAAO,CAAC,YAAY;CAUrB"}
@@ -0,0 +1,299 @@
1
+ import { Agent } from "agents";
2
+ import { z } from "zod";
3
+ import { CHUNK_SOFT_MS } from "../platform.js";
4
+ import { buildRecipeTools } from "../runtime/tool-families.js";
5
+ import { RecipeValidationError, validateRecipe } from "../contract/validation.js";
6
+ import { SubtaskParamsError } from "../subtasks/subtask-types.js";
7
+ import { renderSubagentPrompt } from "./prompt.js";
8
+ import { makeWorkspaceHandle } from "./workspace.js";
9
+ import { fingerprintRequest } from "./fingerprint.js";
10
+ import { runResumableChunk } from "./run.js";
11
+ /**
12
+ * Message prefix of the error thrown when a child that already holds a cached
13
+ * terminal result receives a *different* request. Custom error classes don't
14
+ * survive DO RPC, so this prefix is the cross-boundary contract: it signals a
15
+ * parent lifecycle bug — stale children must be deleted before a genuinely new
16
+ * execution — and a Workflow retry after the parent's cleanup will succeed.
17
+ */
18
+ export const FINGERPRINT_MISMATCH = "recipe-subagent: request fingerprint mismatch";
19
+ /** Deterministic managed-child name for one Subtask execution (shared with C3). */
20
+ export function subagentName(taskId, subtaskId) {
21
+ return `subtask:${taskId}:${subtaskId}`;
22
+ }
23
+ /** Zod mirror of {@link RecipeExecutionResult} for parsing the cached JSON. */
24
+ const cachedResultSchema = z.discriminatedUnion("status", [
25
+ z.object({
26
+ status: z.literal("completed"),
27
+ resultParts: z
28
+ .array(z.object({ kind: z.literal("text"), text: z.string() }))
29
+ .min(1),
30
+ modelId: z.string()
31
+ }),
32
+ z.object({
33
+ status: z.literal("failed"),
34
+ error: z.string(),
35
+ modelId: z.string().nullable()
36
+ })
37
+ ]);
38
+ /**
39
+ * `RecipeSubagent` — the isolated, stateless managed child that executes one
40
+ * Subtask under a resolved Recipe. Created as an Agents SDK sub-agent (facet)
41
+ * beneath the caller's `ReactiveAgent`, so it needs no wrangler Durable Object
42
+ * binding and no `new_sqlite_classes` entry; it must only be exported from the
43
+ * worker entry (`src/index.ts`) so `ctx.exports` can resolve it by class name.
44
+ *
45
+ * It never constructs a Session, never reads parent history beyond the
46
+ * references supplied on its request, never uses recall or durable memory, and
47
+ * never resolves a Recipe itself — it defensively re-validates the resolved
48
+ * Recipe the parent sends and accepts no configuration beyond it.
49
+ *
50
+ * Retry safety: the child persists at most one terminal result in its own
51
+ * SQLite, keyed by the deterministic request fingerprint, plus the rolling
52
+ * `run_state` of an in-progress multi-chunk run. A retry with the same
53
+ * fingerprint replays the terminal result or resumes the run without repeating
54
+ * completed work; a different request for the same child name is rejected
55
+ * ({@link FINGERPRINT_MISMATCH}). Transient platform faults throw and cache
56
+ * nothing, so the enclosing Workflow step can retry. The parent deletes the child
57
+ * (`deleteSubAgent`) only after its durable copy of the result succeeds, which
58
+ * wipes this storage — the workspace and run state included.
59
+ *
60
+ * Not "stateless" like the single-shot original: it owns per-execution durable
61
+ * state (the workspace and the run checkpoint), scoped to one execution and swept
62
+ * with the child.
63
+ */
64
+ export class RecipeSubagentBase extends Agent {
65
+ /**
66
+ * Test-only `ModelPair` injection (a field, so never on the RPC stub).
67
+ * A whole pair — rather than model instances — so error-path tests can throw
68
+ * synchronously from the pair's factories, the repo convention (a rejecting
69
+ * `doGenerate` inside `generateText` leaks an unhandled rejection through
70
+ * the AI SDK telemetry span that workerd flags as a failure).
71
+ */
72
+ modelsOverride;
73
+ _workspace;
74
+ /**
75
+ * The chunk currently executing here, if any. In memory only — it exists to be
76
+ * interrupted mid-call, and an isolate that lost it has no in-flight call left
77
+ * to interrupt. See {@link abortRun}.
78
+ */
79
+ inflight;
80
+ async onStart() {
81
+ this.ensureTables();
82
+ }
83
+ /**
84
+ * Idempotent schema bootstrap. Also called lazily from the RPCs so
85
+ * `runInDurableObject`-style tests reach ready tables without RPC dispatch
86
+ * (mirroring how `AgentDB` migrates on construction).
87
+ */
88
+ ensureTables() {
89
+ this.sql `
90
+ CREATE TABLE IF NOT EXISTS execution_cache (
91
+ slot INTEGER PRIMARY KEY CHECK (slot = 1),
92
+ fingerprint TEXT NOT NULL,
93
+ result_json TEXT NOT NULL,
94
+ created_at INTEGER NOT NULL
95
+ )
96
+ `;
97
+ this.sql `
98
+ CREATE TABLE IF NOT EXISTS run_state (
99
+ slot INTEGER PRIMARY KEY CHECK (slot = 1),
100
+ fingerprint TEXT NOT NULL,
101
+ state_json TEXT NOT NULL,
102
+ updated_at INTEGER NOT NULL
103
+ )
104
+ `;
105
+ }
106
+ /** The recipe's durable workspace, backed by this facet's own SQLite storage. */
107
+ workspace() {
108
+ return (this._workspace ??= this.subagentRuntime().workspaceBacking(this.ctx.storage.sql, () => this.name));
109
+ }
110
+ /**
111
+ * Execute one durable chunk of a Subtask under the parent's resolved Recipe.
112
+ *
113
+ * A terminal outcome (completed / failed) is cached and replayed on retry. A
114
+ * mid-run chunk persists its rolling state to `run_state` and returns a
115
+ * `done: false` yield for the Workflow to run another chunk. `chunk` is a
116
+ * separate argument — never part of `request` — so every chunk fingerprints
117
+ * identically and the cache/resume keys line up. Only transient platform faults
118
+ * throw (nothing cached), so a Workflow retry resumes from the last checkpoint.
119
+ */
120
+ async executeChunk(request, _chunk, runtime = {}) {
121
+ this.ensureTables();
122
+ const rt = this.subagentRuntime();
123
+ const fingerprint = await fingerprintRequest(request);
124
+ // A terminal result already exists → replay it (idempotent retry).
125
+ const cached = this.sql `
126
+ SELECT fingerprint, result_json FROM execution_cache WHERE slot = 1
127
+ `[0];
128
+ if (cached) {
129
+ if (cached.fingerprint !== fingerprint)
130
+ throw mismatch("terminal");
131
+ return {
132
+ done: true,
133
+ result: cachedResultSchema.parse(JSON.parse(cached.result_json)),
134
+ progress: []
135
+ };
136
+ }
137
+ // Validate the recipe up front; an unusable recipe (disabled, or with no
138
+ // soul) and an empty prompt are deterministic, cacheable terminal failures
139
+ // with no model call.
140
+ let recipe;
141
+ try {
142
+ recipe = validateRecipe(request.recipe, rt.policy);
143
+ }
144
+ catch (error) {
145
+ if (!(error instanceof RecipeValidationError))
146
+ throw error;
147
+ return this.cacheTerminal(fingerprint, {
148
+ status: "failed",
149
+ error: error.message,
150
+ modelId: null
151
+ });
152
+ }
153
+ if (request.prompt.trim() === "") {
154
+ return this.cacheTerminal(fingerprint, {
155
+ status: "failed",
156
+ error: "empty subtask prompt",
157
+ modelId: null
158
+ });
159
+ }
160
+ // Re-check the type's param contract, the same defensive posture as
161
+ // `validateRecipe`: a play with no scorecard cannot succeed, and failing
162
+ // here costs no model call and gives the parent a real diagnostic.
163
+ try {
164
+ rt.types.validateParams(request.type, request.params);
165
+ }
166
+ catch (error) {
167
+ if (!(error instanceof SubtaskParamsError))
168
+ throw error;
169
+ return this.cacheTerminal(fingerprint, {
170
+ status: "failed",
171
+ error: error.message,
172
+ modelId: null
173
+ });
174
+ }
175
+ // Resume an in-progress run, guarding against a stale child holding a
176
+ // *different* run (the same reuse hazard the terminal cache guards).
177
+ const saved = this.sql `
178
+ SELECT fingerprint, state_json FROM run_state WHERE slot = 1
179
+ `[0];
180
+ if (saved && saved.fingerprint !== fingerprint)
181
+ throw mismatch("in-progress");
182
+ const prev = saved
183
+ ? JSON.parse(saved.state_json)
184
+ : null;
185
+ const models = this.modelsOverride ??
186
+ rt.models.createModelPair({
187
+ primaryModelId: recipe.primaryModelId,
188
+ fallbackModelId: recipe.fallbackModelId,
189
+ // AI Gateway correlation: tie this child's model calls to its Subtask.
190
+ metadata: { taskId: request.taskId, subtaskId: request.subtaskId }
191
+ });
192
+ const workspace = makeWorkspaceHandle(this.workspace());
193
+ const progress = [];
194
+ const { tools } = buildRecipeTools(recipe.toolFamilies, rt.toolFamilies, {
195
+ workspace,
196
+ emitProgress: (event) => progress.push(event),
197
+ params: request.params,
198
+ runtime
199
+ });
200
+ const { system, prompt } = renderSubagentPrompt({ ...request, recipe });
201
+ const controller = new AbortController();
202
+ this.inflight = controller;
203
+ let outcome, state;
204
+ try {
205
+ ({ outcome, state } = await runResumableChunk(prev, {
206
+ system,
207
+ seedPrompt: prompt,
208
+ models,
209
+ tools,
210
+ limits: recipe.limits,
211
+ chunkSoftMs: CHUNK_SOFT_MS,
212
+ historyWindow: recipe.historyWindow,
213
+ toolOutputWindow: rt.toolOutputWindow,
214
+ reportMetrics: recipe.reportMetrics,
215
+ maxOutputTokens: rt.maxOutputTokens,
216
+ now: () => Date.now(),
217
+ progress,
218
+ checkpoint: (s) => this.saveRunState(fingerprint, s),
219
+ abortSignal: controller.signal
220
+ }));
221
+ }
222
+ finally {
223
+ this.inflight = undefined;
224
+ }
225
+ // The per-step checkpoint already ran; persist the final state too so a chunk
226
+ // that yielded without a completed step still advances durably.
227
+ this.saveRunState(fingerprint, state);
228
+ if (outcome.done) {
229
+ return this.cacheTerminal(fingerprint, outcome.result, outcome.progress);
230
+ }
231
+ return { done: false, progress: outcome.progress };
232
+ }
233
+ /**
234
+ * Interrupt the chunk running here right now, so a cancellation lands on the
235
+ * current model call instead of at the next chunk boundary (up to `chunkSoftMs`
236
+ * later — minutes, for a long recipe). Returns whether there was one to stop.
237
+ *
238
+ * Distinct from {@link abortExecution}, which releases *external* state after
239
+ * the fact; this only stops local work. An aborted run yields rather than
240
+ * producing a terminal result, so nothing is cached and the parent resolves the
241
+ * row itself. Reaching a facet mid-`executeChunk` works because it is awaiting
242
+ * a model `fetch` at the time, which does not hold the input gate closed.
243
+ */
244
+ async abortRun() {
245
+ if (!this.inflight)
246
+ return false;
247
+ this.inflight.abort();
248
+ return true;
249
+ }
250
+ /**
251
+ * Best-effort cleanup on cancellation: rebuild the recipe's tool families and
252
+ * run their `abort` hooks (e.g. release an external resource recorded in the
253
+ * workspace session file). Reconstructible from the workspace, so it is safe on a fresh
254
+ * isolate. The parent supplies the validated tool families it resolved.
255
+ */
256
+ async abortExecution(toolFamilies) {
257
+ this.ensureTables();
258
+ const rt = this.subagentRuntime();
259
+ const ctx = {
260
+ workspace: makeWorkspaceHandle(this.workspace()),
261
+ emitProgress: () => { },
262
+ params: {},
263
+ runtime: {}
264
+ };
265
+ const { abort } = buildRecipeTools(toolFamilies, rt.toolFamilies, ctx);
266
+ if (abort)
267
+ await abort(ctx);
268
+ }
269
+ /** Persist a terminal result to the cache and return it as a done chunk. */
270
+ cacheTerminal(fingerprint, result, progress = []) {
271
+ this.sql `
272
+ INSERT INTO execution_cache (slot, fingerprint, result_json, created_at)
273
+ VALUES (1, ${fingerprint}, ${JSON.stringify(result)}, ${Date.now()})
274
+ ON CONFLICT (slot) DO UPDATE SET
275
+ fingerprint = excluded.fingerprint,
276
+ result_json = excluded.result_json,
277
+ created_at = excluded.created_at
278
+ `;
279
+ return { done: true, result, progress };
280
+ }
281
+ /** Persist the rolling run state (called after every model turn). */
282
+ saveRunState(fingerprint, state) {
283
+ this.sql `
284
+ INSERT INTO run_state (slot, fingerprint, state_json, updated_at)
285
+ VALUES (1, ${fingerprint}, ${JSON.stringify(state)}, ${Date.now()})
286
+ ON CONFLICT (slot) DO UPDATE SET
287
+ fingerprint = excluded.fingerprint,
288
+ state_json = excluded.state_json,
289
+ updated_at = excluded.updated_at
290
+ `;
291
+ }
292
+ }
293
+ /** The cross-RPC stale-child error (see {@link FINGERPRINT_MISMATCH}). */
294
+ function mismatch(phase) {
295
+ return new Error(`${FINGERPRINT_MISMATCH}: this child already holds a ${phase} state for a ` +
296
+ "different request; the parent must delete a stale child before starting a " +
297
+ "genuinely new execution");
298
+ }
299
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/subagent/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { RecipeExecutionRequest } from "../subtasks/types.js";
2
+ import type { ValidatedRecipe } from "../contract/recipe.js";
3
+ /**
4
+ * Deterministic rendering of one subagent invocation. Pure — no model, no
5
+ * Session, no lookups: everything comes verbatim from the request. The four
6
+ * sections stay clearly separated and labeled so tests (and the model) can tell
7
+ * them apart: the execution's budget, the main-agent instruction, the verbatim
8
+ * conversation reference snapshots, and generated dependency output — which is
9
+ * explicitly marked as generated and never presented as conversation evidence.
10
+ */
11
+ /**
12
+ * A request whose Recipe has been through `validateRecipe`, so its limits are
13
+ * merged rather than partial. Required rather than convenient: the budget line
14
+ * below states a number to the model, and stating "up to undefined turns" — or
15
+ * quietly rendering a Recipe's override as the whole budget — is worse than not
16
+ * telling it at all.
17
+ */
18
+ export type RenderableExecution = RecipeExecutionRequest & {
19
+ recipe: ValidatedRecipe;
20
+ };
21
+ export interface RenderedInvocation {
22
+ /** The validated Recipe soul, verbatim — the invocation's system prompt. */
23
+ system: string;
24
+ /** The sectioned user message (instruction, references, dependency results). */
25
+ prompt: string;
26
+ }
27
+ /**
28
+ * Render the sectioned user message for one execution. Reference snapshots are
29
+ * emitted exactly as captured at decomposition — `[ref N]` labels with the
30
+ * message role, no summarizing, rewriting, or interpolation. Sections with no
31
+ * content are omitted entirely.
32
+ */
33
+ export declare function renderSubagentPrompt(request: RenderableExecution): RenderedInvocation;
34
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/subagent/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG;IACzD,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAsBF,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CA8BpB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The budget, in the terms the runner actually enforces.
3
+ *
4
+ * Worth its own section because a subagent that does not know its budget invents
5
+ * one, and a *main* agent writing the prompt invents one for it: in one logged
6
+ * play the instruction said "you have up to 20 actions total" — 20 was the turn
7
+ * budget — and the subagent stopped after eleven game moves reasoning that it
8
+ * must be out. Turns and actions are not the same currency and this is the line
9
+ * that says so.
10
+ */
11
+ function renderBudget(limits) {
12
+ const minutes = Math.round(limits.maxWallMs / 60_000);
13
+ return (`# Budget\nUp to ${limits.maxTurns} turns and about ${minutes} minutes. ` +
14
+ "One turn is one tool call, however much that call does — anything a tool " +
15
+ "counts internally is its own budget, not this one. Reaching either ceiling " +
16
+ "does not drop your work: you get one last turn, with no tools, to write up " +
17
+ "what you have.");
18
+ }
19
+ /**
20
+ * Render the sectioned user message for one execution. Reference snapshots are
21
+ * emitted exactly as captured at decomposition — `[ref N]` labels with the
22
+ * message role, no summarizing, rewriting, or interpolation. Sections with no
23
+ * content are omitted entirely.
24
+ */
25
+ export function renderSubagentPrompt(request) {
26
+ const sections = [renderBudget(request.recipe.limits)];
27
+ const prompt = request.prompt.trim();
28
+ if (prompt !== "") {
29
+ sections.push(`# Task\n${prompt}`);
30
+ }
31
+ if (request.references.length > 0) {
32
+ const refs = request.references.map((ref, i) => `[ref ${i + 1}] (${ref.role}): ${ref.text}`);
33
+ sections.push("# Conversation references (verbatim snapshots of the caller's conversation)\n" +
34
+ refs.join("\n"));
35
+ }
36
+ if (request.dependencyResults.length > 0) {
37
+ const deps = request.dependencyResults.map((dep) => {
38
+ const text = dep.resultParts.map((part) => part.text).join("\n");
39
+ return `[dependency ${dep.subtaskId}] (${dep.type}): ${text}`;
40
+ });
41
+ sections.push("# Dependency results (generated output from prerequisite subtasks — not conversation evidence)\n" +
42
+ deps.join("\n"));
43
+ }
44
+ return { system: request.recipe.soul, prompt: sections.join("\n\n") };
45
+ }
46
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/subagent/prompt.ts"],"names":[],"mappings":"AAuBA;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,MAAiC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;IACtD,OAAO,CACL,mBAAmB,MAAM,CAAC,QAAQ,oBAAoB,OAAO,YAAY;QACzE,2EAA2E;QAC3E,6EAA6E;QAC7E,6EAA6E;QAC7E,gBAAgB,CACjB,CAAC;AACJ,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA4B;IAE5B,MAAM,QAAQ,GAAa,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,QAAQ,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CACjC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,CACxD,CAAC;QACF,QAAQ,CAAC,IAAI,CACX,+EAA+E;YAC7E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAClB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACjD,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,OAAO,eAAe,GAAG,CAAC,SAAS,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI,EAAE,CAAC;QAChE,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CACX,kGAAkG;YAChG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAClB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;AACxE,CAAC"}