@dynamicagents/core 0.9.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 (168) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +522 -0
  3. package/dist/a2a/agent-stub.d.ts +60 -0
  4. package/dist/a2a/agent-stub.js +1 -0
  5. package/dist/a2a/caller-token.d.ts +44 -0
  6. package/dist/a2a/caller-token.js +61 -0
  7. package/dist/a2a/caller.d.ts +23 -0
  8. package/dist/a2a/caller.js +33 -0
  9. package/dist/a2a/card.d.ts +158 -0
  10. package/dist/a2a/card.js +163 -0
  11. package/dist/a2a/context.d.ts +19 -0
  12. package/dist/a2a/context.js +61 -0
  13. package/dist/a2a/deliver.d.ts +159 -0
  14. package/dist/a2a/deliver.js +185 -0
  15. package/dist/a2a/executor.d.ts +84 -0
  16. package/dist/a2a/executor.js +106 -0
  17. package/dist/a2a/index.d.ts +35 -0
  18. package/dist/a2a/index.js +34 -0
  19. package/dist/a2a/notify.d.ts +106 -0
  20. package/dist/a2a/notify.js +180 -0
  21. package/dist/a2a/parts.d.ts +45 -0
  22. package/dist/a2a/parts.js +94 -0
  23. package/dist/a2a/push.d.ts +70 -0
  24. package/dist/a2a/push.js +53 -0
  25. package/dist/a2a/self-origin.d.ts +91 -0
  26. package/dist/a2a/self-origin.js +114 -0
  27. package/dist/a2a/task-store.d.ts +44 -0
  28. package/dist/a2a/task-store.js +99 -0
  29. package/dist/a2a/task.d.ts +85 -0
  30. package/dist/a2a/task.js +15 -0
  31. package/dist/a2a/verify.d.ts +80 -0
  32. package/dist/a2a/verify.js +143 -0
  33. package/dist/agent/budget.d.ts +46 -0
  34. package/dist/agent/budget.js +42 -0
  35. package/dist/agent/control.d.ts +109 -0
  36. package/dist/agent/control.js +115 -0
  37. package/dist/agent/errors.d.ts +85 -0
  38. package/dist/agent/errors.js +64 -0
  39. package/dist/agent/final-reply.d.ts +49 -0
  40. package/dist/agent/final-reply.js +68 -0
  41. package/dist/agent/history.d.ts +97 -0
  42. package/dist/agent/history.js +133 -0
  43. package/dist/agent/index.d.ts +29 -0
  44. package/dist/agent/index.js +29 -0
  45. package/dist/agent/inference.d.ts +110 -0
  46. package/dist/agent/inference.js +120 -0
  47. package/dist/agent/model.d.ts +90 -0
  48. package/dist/agent/model.js +1 -0
  49. package/dist/agent/session.d.ts +100 -0
  50. package/dist/agent/session.js +82 -0
  51. package/dist/agent/workers-ai/index.d.ts +23 -0
  52. package/dist/agent/workers-ai/index.js +23 -0
  53. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  54. package/dist/agent/workers-ai/runtime.js +63 -0
  55. package/dist/alarm/index.d.ts +77 -0
  56. package/dist/alarm/index.js +116 -0
  57. package/dist/config.d.ts +202 -0
  58. package/dist/config.js +135 -0
  59. package/dist/contract/index.d.ts +9 -0
  60. package/dist/contract/index.js +8 -0
  61. package/dist/contract/plugin.d.ts +324 -0
  62. package/dist/contract/plugin.js +114 -0
  63. package/dist/contract/recipe.d.ts +180 -0
  64. package/dist/contract/recipe.js +1 -0
  65. package/dist/contract/validation.d.ts +91 -0
  66. package/dist/contract/validation.js +84 -0
  67. package/dist/db/db.d.ts +147 -0
  68. package/dist/db/db.js +90 -0
  69. package/dist/db/index.d.ts +8 -0
  70. package/dist/db/index.js +8 -0
  71. package/dist/db/migrations/index.d.ts +20 -0
  72. package/dist/db/migrations/index.js +60 -0
  73. package/dist/db/models/subtasks.d.ts +100 -0
  74. package/dist/db/models/subtasks.js +241 -0
  75. package/dist/db/models/tasks.d.ts +118 -0
  76. package/dist/db/models/tasks.js +274 -0
  77. package/dist/db/schema.d.ts +468 -0
  78. package/dist/db/schema.js +88 -0
  79. package/dist/env.d.ts +53 -0
  80. package/dist/env.js +47 -0
  81. package/dist/host/agent.d.ts +305 -0
  82. package/dist/host/agent.js +400 -0
  83. package/dist/host/index.d.ts +20 -0
  84. package/dist/host/index.js +19 -0
  85. package/dist/host/plugin-host.d.ts +42 -0
  86. package/dist/host/plugin-host.js +1 -0
  87. package/dist/index.d.ts +19 -0
  88. package/dist/index.js +17 -0
  89. package/dist/job/index.d.ts +21 -0
  90. package/dist/job/index.js +21 -0
  91. package/dist/job/lifecycle.d.ts +176 -0
  92. package/dist/job/lifecycle.js +230 -0
  93. package/dist/job/state.d.ts +92 -0
  94. package/dist/job/state.js +40 -0
  95. package/dist/platform.d.ts +138 -0
  96. package/dist/platform.js +140 -0
  97. package/dist/round/agent.d.ts +271 -0
  98. package/dist/round/agent.js +678 -0
  99. package/dist/round/index.d.ts +25 -0
  100. package/dist/round/index.js +23 -0
  101. package/dist/round/policy.d.ts +98 -0
  102. package/dist/round/policy.js +1 -0
  103. package/dist/round/subagent.d.ts +87 -0
  104. package/dist/round/subagent.js +108 -0
  105. package/dist/round/turn.d.ts +249 -0
  106. package/dist/round/turn.js +564 -0
  107. package/dist/round/workflow.d.ts +147 -0
  108. package/dist/round/workflow.js +387 -0
  109. package/dist/runtime/index.d.ts +127 -0
  110. package/dist/runtime/index.js +186 -0
  111. package/dist/runtime/tool-families.d.ts +32 -0
  112. package/dist/runtime/tool-families.js +61 -0
  113. package/dist/subagent/fingerprint.d.ts +37 -0
  114. package/dist/subagent/fingerprint.js +92 -0
  115. package/dist/subagent/index.d.ts +169 -0
  116. package/dist/subagent/index.js +330 -0
  117. package/dist/subagent/prompt.d.ts +32 -0
  118. package/dist/subagent/prompt.js +37 -0
  119. package/dist/subagent/run.d.ts +157 -0
  120. package/dist/subagent/run.js +540 -0
  121. package/dist/subagent/workspace.d.ts +85 -0
  122. package/dist/subagent/workspace.js +127 -0
  123. package/dist/subtasks/catalog.d.ts +40 -0
  124. package/dist/subtasks/catalog.js +36 -0
  125. package/dist/subtasks/decomposition.d.ts +85 -0
  126. package/dist/subtasks/decomposition.js +156 -0
  127. package/dist/subtasks/delegate.d.ts +120 -0
  128. package/dist/subtasks/delegate.js +131 -0
  129. package/dist/subtasks/index.d.ts +9 -0
  130. package/dist/subtasks/index.js +9 -0
  131. package/dist/subtasks/subtask-types.d.ts +91 -0
  132. package/dist/subtasks/subtask-types.js +103 -0
  133. package/dist/subtasks/types.d.ts +295 -0
  134. package/dist/subtasks/types.js +15 -0
  135. package/dist/testing/auth.d.ts +34 -0
  136. package/dist/testing/auth.js +35 -0
  137. package/dist/testing/do.d.ts +29 -0
  138. package/dist/testing/do.js +25 -0
  139. package/dist/testing/fake-session.d.ts +26 -0
  140. package/dist/testing/fake-session.js +37 -0
  141. package/dist/testing/fixtures.d.ts +64 -0
  142. package/dist/testing/fixtures.js +104 -0
  143. package/dist/testing/harness.d.ts +97 -0
  144. package/dist/testing/harness.js +138 -0
  145. package/dist/testing/index.d.ts +31 -0
  146. package/dist/testing/index.js +35 -0
  147. package/dist/testing/mock-model.d.ts +77 -0
  148. package/dist/testing/mock-model.js +136 -0
  149. package/dist/testing/node.d.ts +56 -0
  150. package/dist/testing/node.js +56 -0
  151. package/dist/testing/vcr-global-setup.d.ts +12 -0
  152. package/dist/testing/vcr-global-setup.js +15 -0
  153. package/dist/testing/vcr-shared.d.ts +38 -0
  154. package/dist/testing/vcr-shared.js +33 -0
  155. package/dist/testing/vcr-spec.d.ts +25 -0
  156. package/dist/testing/vcr-spec.js +124 -0
  157. package/dist/testing/vcr-store.d.ts +86 -0
  158. package/dist/testing/vcr-store.js +191 -0
  159. package/dist/testing/vcr.d.ts +117 -0
  160. package/dist/testing/vcr.js +275 -0
  161. package/dist/worker/define-agent.d.ts +123 -0
  162. package/dist/worker/define-agent.js +20 -0
  163. package/dist/worker/index.d.ts +218 -0
  164. package/dist/worker/index.js +369 -0
  165. package/eslint-rules/index.js +31 -0
  166. package/eslint-rules/no-deprecated-object-properties.js +81 -0
  167. package/package.json +178 -0
  168. package/scripts/generate-keys.mjs +48 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@dynamicagents/core/round` — the delegating round loop.
3
+ *
4
+ * A round agent answers the user or hands work to isolated subagents, waits for
5
+ * their durable results, and decides again. This subpath ships the whole
6
+ * mechanism of that: the round loop and its primary→fallback→repair ladder
7
+ * ({@link runTurn}), the durable Workflow orchestration that runs a round's
8
+ * subtasks concurrently ({@link runHandleTask}), the Durable Object body ({@link
9
+ * RoundAgentBase}), and the subagent facet host ({@link RecipeSubagentHost}).
10
+ *
11
+ * **Opt-in, and its own subpath on purpose.** An agent whose turn is a single
12
+ * inference — one that never delegates — imports none of this and carries none of
13
+ * it in its bundle. Core's root barrel does not re-export it.
14
+ *
15
+ * **Core still ships no prompt copy.** Everything the model and the user read
16
+ * comes from the {@link RoundPolicy} an agent supplies: the round contract, the
17
+ * budget-spent note, and the three user-facing strings. That is the line — core
18
+ * owns the machine, you own the words.
19
+ */
20
+ export type { RoundPolicy } from "./policy.js";
21
+ export { RoundAgentBase } from "./agent.js";
22
+ export { RecipeSubagentHost, type SubagentClass } from "./subagent.js";
23
+ export { runHandleTask, type HandleTaskDeps, type HandleTaskParams } from "./workflow.js";
24
+ export type { NonRecoverableKind, RoundFailureKind } from "../agent/inference.js";
25
+ export { buildTurnInstructions, joinSuccessfulBranches, renderTurnMessages, runTurn, type RoundMode, type RunTurnArgs, type RunTurnOutcome, type TurnInstructions } from "./turn.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@dynamicagents/core/round` — the delegating round loop.
3
+ *
4
+ * A round agent answers the user or hands work to isolated subagents, waits for
5
+ * their durable results, and decides again. This subpath ships the whole
6
+ * mechanism of that: the round loop and its primary→fallback→repair ladder
7
+ * ({@link runTurn}), the durable Workflow orchestration that runs a round's
8
+ * subtasks concurrently ({@link runHandleTask}), the Durable Object body ({@link
9
+ * RoundAgentBase}), and the subagent facet host ({@link RecipeSubagentHost}).
10
+ *
11
+ * **Opt-in, and its own subpath on purpose.** An agent whose turn is a single
12
+ * inference — one that never delegates — imports none of this and carries none of
13
+ * it in its bundle. Core's root barrel does not re-export it.
14
+ *
15
+ * **Core still ships no prompt copy.** Everything the model and the user read
16
+ * comes from the {@link RoundPolicy} an agent supplies: the round contract, the
17
+ * budget-spent note, and the three user-facing strings. That is the line — core
18
+ * owns the machine, you own the words.
19
+ */
20
+ export { RoundAgentBase } from "./agent.js";
21
+ export { RecipeSubagentHost } from "./subagent.js";
22
+ export { runHandleTask } from "./workflow.js";
23
+ export { buildTurnInstructions, joinSuccessfulBranches, renderTurnMessages, runTurn } from "./turn.js";
@@ -0,0 +1,98 @@
1
+ import type { AgentLimits } from "../config.js";
2
+ /**
3
+ * Everything about a round loop that is **yours**, not core's.
4
+ *
5
+ * The round loop in this subpath is mechanism: concurrent subtask execution,
6
+ * chunked subagent runs, cancellation ordering, idempotent recovery, the
7
+ * primary→fallback→repair ladder. None of it varies between agents, and every
8
+ * place it *did* vary between two agents in one repo turned out to be a bug.
9
+ *
10
+ * What genuinely varies is what the model is told and what the user reads — and
11
+ * core ships no prompt copy, deliberately, so that no run ever executes under an
12
+ * identity nobody chose. This interface is that boundary, made explicit: supply
13
+ * the words, get the machine.
14
+ *
15
+ * ```ts
16
+ * export const policy: RoundPolicy = {
17
+ * roundContract: ({ typeKeys, maxSubtasks }) => `…`,
18
+ * finalRoundNote: (limits) => `…`,
19
+ * copy: {
20
+ * taskFailed: "Sorry — something went wrong handling that request.",
21
+ * recoveredReply: "Working on your request.",
22
+ * partialNote: "Some parts of this request could not be completed…"
23
+ * }
24
+ * };
25
+ * ```
26
+ *
27
+ * Nothing here is optional and nothing has a default. A stub that returned an
28
+ * empty contract would produce a round the model has no way to end correctly,
29
+ * and a lending default would be exactly the house prompt copy core refuses to
30
+ * have.
31
+ *
32
+ * ## Each prompt string owns its own leading separator
33
+ *
34
+ * {@link roundContract} and {@link finalRoundNote} are concatenated directly
35
+ * onto text that came from somewhere else — the soul, the caller context, and
36
+ * for `finalRoundNote` the open contract itself. The composition adds nothing
37
+ * between them, so **start each with a blank line** (`\n\n`, or a template
38
+ * literal opening on an empty line, which is what the starter does):
39
+ *
40
+ * ```ts
41
+ * roundContract: () => `
42
+ *
43
+ * # Answering this request
44
+ * …`
45
+ * ```
46
+ *
47
+ * Return `"# Answering this request…"` with no leading newline and the model
48
+ * reads `Calling workspace: 1.# Answering this request` — a run-together line
49
+ * that costs nothing to produce and is invisible in every test that does not
50
+ * assert on the rendered prompt.
51
+ *
52
+ * Core does not insert the separator for you, because these are *your* sections
53
+ * and a section that cannot control its own spacing cannot control its shape —
54
+ * a policy that legitimately wants a single newline, or continues the previous
55
+ * paragraph, has no way to say so once core has decided.
56
+ */
57
+ export interface RoundPolicy {
58
+ /**
59
+ * The round contract: how a round ends, and what `delegate` takes. Appended to
60
+ * the soul and the caller context on every round.
61
+ *
62
+ * `typeKeys` is the installed subtask types — the only values `delegate` may
63
+ * name. Per-type guidance is *not* written here: each type declares its own
64
+ * via `SubtaskTypeSpec.delegationGuidance`, and the loop appends it, so this
65
+ * text names no domain.
66
+ */
67
+ roundContract(ctx: {
68
+ typeKeys: readonly string[];
69
+ maxSubtasks: number;
70
+ }): string;
71
+ /**
72
+ * Appended when the task has spent its budget. That round is handed no work
73
+ * tools and no `delegate`, so this explains a constraint the model can already
74
+ * see rather than imposing one.
75
+ */
76
+ finalRoundNote(limits: AgentLimits): string;
77
+ /** The strings a user can actually read. */
78
+ copy: {
79
+ /**
80
+ * User-facing text on a failed Task. The diagnostic is logged separately —
81
+ * this is what the person sees.
82
+ */
83
+ taskFailed: string;
84
+ /**
85
+ * Stand-in acknowledgement for the unreachable case where a round's subtasks
86
+ * are durable but its acknowledgement is not in the Session. Neutral by
87
+ * design: the work is valid and running, so the user gets an honest
88
+ * acknowledgement rather than a failed Task.
89
+ */
90
+ recoveredReply: string;
91
+ /**
92
+ * Appended when a deterministic join has to disclose gaps — both models
93
+ * failed, but earlier branches succeeded and their results are worth
94
+ * delivering.
95
+ */
96
+ partialNote: string;
97
+ };
98
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,87 @@
1
+ import type { AgentPlugin } from "../contract/plugin.js";
2
+ import type { CoreConfigOverrides, ModelConfig } from "../config.js";
3
+ import type { AiEnv } from "../env.js";
4
+ import type { ModelRuntime } from "../agent/model.js";
5
+ import { RecipeSubagentBase, type SubagentRuntime } from "../subagent/index.js";
6
+ import type { PluginHost } from "../host/plugin-host.js";
7
+ /**
8
+ * The host half of a subagent facet: the same plugins as its parent, assembled
9
+ * into the {@link SubagentRuntime} the base class executes recipes with.
10
+ *
11
+ * `RecipeSubagentBase` already ships the whole body — resumable chunks, the
12
+ * terminal-result cache keyed by request fingerprint, workspace wiring,
13
+ * cancellation, the fingerprint-mismatch contract. All that was ever missing is
14
+ * this assembly, and a Durable Object class is constructed by the runtime, so it
15
+ * cannot take constructor arguments. The two abstract methods are that seam.
16
+ *
17
+ * ## Why the facet builds its own runtime instead of asking its parent
18
+ *
19
+ * The obvious move is to read it off the parent, which already has one. It does
20
+ * not work: the SDK reaches a parent through `this.parentAgent(Cls)`, which is an
21
+ * **RPC stub**, and a `SubagentRuntime` is mostly functions — `models`, the
22
+ * `toolFamilies` builder map, `workspaceBacking`. None of that survives
23
+ * serialization, so the call would return a shape that type-checks and is inert.
24
+ *
25
+ * So each agent gets its own facet class over this base, building the same
26
+ * runtime from the same `plugins.ts` its parent uses:
27
+ *
28
+ * ```ts
29
+ * export class MySubagent extends RecipeSubagentHost<Env> {
30
+ * protected agentConfig() { return MY_CONFIG; }
31
+ * protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
32
+ * }
33
+ * ```
34
+ *
35
+ * It must be a **named, exported** class, not one produced by a factory: the
36
+ * framework resolves a facet through `ctx.exports[this.constructor.name]`, so an
37
+ * anonymous class breaks the lookup, and so does a bundler that minifies class
38
+ * names.
39
+ *
40
+ * ## Not a bound Durable Object
41
+ *
42
+ * A facet is created beneath its calling agent, so it needs no wrangler binding
43
+ * and no `new_sqlite_classes` entry — only an export from the Worker entry so
44
+ * `ctx.exports` can resolve it. It *does* need a test-only binding, because the
45
+ * Vitest pool only marks bound classes as DO classes.
46
+ */
47
+ export declare abstract class RecipeSubagentHost<TEnv extends Cloudflare.Env & AiEnv = Cloudflare.Env & AiEnv> extends RecipeSubagentBase<TEnv> {
48
+ private _rt?;
49
+ /** This facet's agent config — the same object its parent DO passes. */
50
+ protected abstract agentConfig(): CoreConfigOverrides;
51
+ /** This facet's plugins — the same list its parent DO installs. */
52
+ protected abstract agentPlugins(host: PluginHost<TEnv>): AgentPlugin[];
53
+ /**
54
+ * Built once per facet instance. The base calls this per RPC and documents
55
+ * that an implementation building anything expensive should memoize its own.
56
+ */
57
+ protected subagentRuntime(): SubagentRuntime;
58
+ /**
59
+ * Which provider this facet's chunks run on. Mirrors
60
+ * {@link file://../host/agent.ts DynamicAgent.modelRuntime}, and **must be
61
+ * overridden to match it** — a facet that keeps the Workers AI default while
62
+ * its parent runs on another provider would silently execute every subtask on
63
+ * a different model than the round that delegated it.
64
+ *
65
+ * The two seams take the same arguments precisely so that keeping them in step
66
+ * needs no discipline: write the provider once as a
67
+ * {@link file://../agent/model.ts ModelRuntimeFactory} and have both return
68
+ * it. Two hand-copied runtime-construction bodies is what this shape exists to
69
+ * stop, because nothing type-checks their agreement.
70
+ *
71
+ * Note the cheapest way to satisfy this is to override *neither* seam, which
72
+ * is what an agent on core's default does.
73
+ *
74
+ * Takes the model config rather than reading `this.config`, because the facet
75
+ * resolves its config inside `buildRuntime` and this is called from there.
76
+ */
77
+ protected modelRuntime(model: ModelConfig): ModelRuntime;
78
+ private buildRuntime;
79
+ }
80
+ /**
81
+ * A **concrete** facet constructor, as `subAgent()` requires one.
82
+ *
83
+ * {@link RecipeSubagentHost} is abstract, and `subAgent()` rightly refuses an
84
+ * abstract class — it is what constructs one. So the seam on the agent side is
85
+ * typed as this: any named subclass below it.
86
+ */
87
+ export type SubagentClass = new (...args: ConstructorParameters<typeof RecipeSubagentBase>) => RecipeSubagentBase;
@@ -0,0 +1,108 @@
1
+ import { createAgentRuntime } from "../runtime/index.js";
2
+ import { workersAIModels } from "../agent/workers-ai/index.js";
3
+ import { RecipeSubagentBase } from "../subagent/index.js";
4
+ /**
5
+ * The host half of a subagent facet: the same plugins as its parent, assembled
6
+ * into the {@link SubagentRuntime} the base class executes recipes with.
7
+ *
8
+ * `RecipeSubagentBase` already ships the whole body — resumable chunks, the
9
+ * terminal-result cache keyed by request fingerprint, workspace wiring,
10
+ * cancellation, the fingerprint-mismatch contract. All that was ever missing is
11
+ * this assembly, and a Durable Object class is constructed by the runtime, so it
12
+ * cannot take constructor arguments. The two abstract methods are that seam.
13
+ *
14
+ * ## Why the facet builds its own runtime instead of asking its parent
15
+ *
16
+ * The obvious move is to read it off the parent, which already has one. It does
17
+ * not work: the SDK reaches a parent through `this.parentAgent(Cls)`, which is an
18
+ * **RPC stub**, and a `SubagentRuntime` is mostly functions — `models`, the
19
+ * `toolFamilies` builder map, `workspaceBacking`. None of that survives
20
+ * serialization, so the call would return a shape that type-checks and is inert.
21
+ *
22
+ * So each agent gets its own facet class over this base, building the same
23
+ * runtime from the same `plugins.ts` its parent uses:
24
+ *
25
+ * ```ts
26
+ * export class MySubagent extends RecipeSubagentHost<Env> {
27
+ * protected agentConfig() { return MY_CONFIG; }
28
+ * protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
29
+ * }
30
+ * ```
31
+ *
32
+ * It must be a **named, exported** class, not one produced by a factory: the
33
+ * framework resolves a facet through `ctx.exports[this.constructor.name]`, so an
34
+ * anonymous class breaks the lookup, and so does a bundler that minifies class
35
+ * names.
36
+ *
37
+ * ## Not a bound Durable Object
38
+ *
39
+ * A facet is created beneath its calling agent, so it needs no wrangler binding
40
+ * and no `new_sqlite_classes` entry — only an export from the Worker entry so
41
+ * `ctx.exports` can resolve it. It *does* need a test-only binding, because the
42
+ * Vitest pool only marks bound classes as DO classes.
43
+ */
44
+ export class RecipeSubagentHost extends RecipeSubagentBase {
45
+ _rt;
46
+ /**
47
+ * Built once per facet instance. The base calls this per RPC and documents
48
+ * that an implementation building anything expensive should memoize its own.
49
+ */
50
+ subagentRuntime() {
51
+ return (this._rt ??= this.buildRuntime());
52
+ }
53
+ /**
54
+ * Which provider this facet's chunks run on. Mirrors
55
+ * {@link file://../host/agent.ts DynamicAgent.modelRuntime}, and **must be
56
+ * overridden to match it** — a facet that keeps the Workers AI default while
57
+ * its parent runs on another provider would silently execute every subtask on
58
+ * a different model than the round that delegated it.
59
+ *
60
+ * The two seams take the same arguments precisely so that keeping them in step
61
+ * needs no discipline: write the provider once as a
62
+ * {@link file://../agent/model.ts ModelRuntimeFactory} and have both return
63
+ * it. Two hand-copied runtime-construction bodies is what this shape exists to
64
+ * stop, because nothing type-checks their agreement.
65
+ *
66
+ * Note the cheapest way to satisfy this is to override *neither* seam, which
67
+ * is what an agent on core's default does.
68
+ *
69
+ * Takes the model config rather than reading `this.config`, because the facet
70
+ * resolves its config inside `buildRuntime` and this is called from there.
71
+ */
72
+ modelRuntime(model) {
73
+ return workersAIModels(this.env, model);
74
+ }
75
+ buildRuntime() {
76
+ const config = this.agentConfig();
77
+ const runtime = createAgentRuntime({
78
+ config,
79
+ plugins: this.agentPlugins({
80
+ env: this.env,
81
+ storage: this.ctx.storage,
82
+ // No caller identity exists down here, and nothing reads one: the
83
+ // per-caller hooks are the parent's surface, never a subagent's. A
84
+ // throwing thunk is the honest encoding — it can only fire if a plugin
85
+ // starts reading caller state on the execution path, which is a design
86
+ // question, not a missing value.
87
+ callerKey: () => {
88
+ throw new Error("a subagent execution has no caller identity — this plugin reads " +
89
+ "per-caller state on a path where none exists");
90
+ },
91
+ aiGatewayId: config.model?.aiGatewayId ?? ""
92
+ }),
93
+ env: this.env
94
+ });
95
+ return {
96
+ policy: runtime.policy,
97
+ types: runtime.types,
98
+ models: this.modelRuntime(runtime.config.model),
99
+ toolFamilies: runtime.toolFamilies,
100
+ toolOutputWindow: runtime.config.toolOutputWindow,
101
+ maxOutputTokens: runtime.config.model.maxOutputTokens,
102
+ maxRetries: runtime.config.model.maxRetries,
103
+ // Always defined: the plugin that declared a backend, or core's in-memory
104
+ // fallback. So this needs no null check.
105
+ workspaceBacking: runtime.workspaceBacking
106
+ };
107
+ }
108
+ }
@@ -0,0 +1,249 @@
1
+ import type { ModelMessage, ToolSet } from "ai";
2
+ import type { SessionMessage } from "agents/experimental/memory/session";
3
+ import type { AgentLimits } from "../config.js";
4
+ import type { SubtaskTypeRegistry } from "../subtasks/subtask-types.js";
5
+ import { type SessionLike } from "../agent/session.js";
6
+ import { type OnContent, type RoundFailureKind } from "../agent/inference.js";
7
+ import { type TurnBudget } from "../agent/budget.js";
8
+ import type { ModelPair } from "../agent/model.js";
9
+ import { type ReferenceCatalogEntry } from "../subtasks/catalog.js";
10
+ import type { CompositionBranch, SubtaskDraft } from "../subtasks/types.js";
11
+ import type { RoundPolicy } from "./policy.js";
12
+ /**
13
+ * One **round** of the main agent: a single inference over the agent's continuous
14
+ * Session that ends in one of two decisions — answer the user, or delegate.
15
+ *
16
+ * This is the whole task pipeline's control point. The Workflow runs rounds in a
17
+ * loop: a round that delegates gets its Subtasks executed and is followed by
18
+ * another round; a round that answers ends the Task. So "compose" is not a
19
+ * separate phase with its own rules — it is simply the round in which the model
20
+ * decides it has enough to answer.
21
+ *
22
+ * Two layers of tools, and the difference is the design:
23
+ *
24
+ * - **Work tools** (whatever the installed plugins offer the main agent, plus the
25
+ * Session's own `set_context`) carry an `execute` and run *inside* the round's
26
+ * tool loop. They never end a round; the model keeps reasoning over their
27
+ * results. Every round gets them except the one the budget forced — looking
28
+ * something up before answering is ordinary work, not a special phase, right up
29
+ * until there is nothing left to spend on it (see {@link RoundMode}).
30
+ * - **Control tools** — `delegate` and `final_reply` — have no `execute`. The call
31
+ * *is* the round's output: the loop halts on it, and for `delegate` the Workflow
32
+ * performs it durably. Because the loop halts, the SDK never validates their
33
+ * input either, so each one checks its own and the round repairs what it rejects
34
+ * — see `agent/control.ts`. A future `escalate` (ask the human) is the same
35
+ * shape: another entry there, another variant of {@link TurnDecision}, another
36
+ * `case` in the Workflow's switch.
37
+ *
38
+ * Nothing forces the *choice*, and that is deliberate. An earlier design pinned
39
+ * `toolChoice` to a specific tool to force delegation in one phase and forbid it in
40
+ * another, which meant a request the main agent was best placed to answer got
41
+ * shipped to a memoryless subagent, and material that came back could only ever be
42
+ * turned into prose. That is still rejected: the model picks its own ending, and
43
+ * delegating twice is allowed.
44
+ *
45
+ * What *is* forced is that the round end in a control call at all —
46
+ * `toolChoice: "required"`, with both endings declared as tools. Prose is not an
47
+ * outcome: see {@link file://../agent/final-reply.ts final-reply.ts} for why not.
48
+ *
49
+ * Narration survived that fix by moving house. A round whose results have just come
50
+ * back can still announce its next step *inside* a `final_reply` — "now sending the
51
+ * second one" — and that ends the Task as surely as prose did, having done nothing,
52
+ * while telling the user the opposite. No mechanism can catch it: `final_reply` is a
53
+ * legitimate ending for exactly this round, and only the model knows whether the
54
+ * request is finished. So it is the **round contract** that has to close it — which
55
+ * is why that text is a {@link RoundPolicy} the agent writes, not something core
56
+ * ships.
57
+ *
58
+ * The model reasons over the whole conversation but references it by **catalog
59
+ * index only** — see {@link renderTurnMessages}.
60
+ */
61
+ /**
62
+ * The prompt copy for one configured agent, built once per Durable Object
63
+ * instance.
64
+ *
65
+ * A builder rather than a module-level `const`, because the type enum is a
66
+ * function of the plugins this agent installed, which is a function of `env`,
67
+ * which does not exist at module scope on Workers. The DO memoizes the result.
68
+ */
69
+ export interface TurnInstructions {
70
+ /** Contract + per-type delegation guidance. Appended to soul + caller context. */
71
+ open: string;
72
+ /** Further appended when the budget is spent. */
73
+ final: string;
74
+ }
75
+ /**
76
+ * Build both prompt suffixes for one configured agent.
77
+ *
78
+ * The agent's contract, then whatever the delegable types have to say about being
79
+ * delegated — each declared by the type that owns it (`SubtaskTypeSpec`) and
80
+ * collected by the runtime's registry, so no domain is named by the policy. That
81
+ * is the rule the two prompt fields on a subtask type exist to hold: everything
82
+ * the main agent is told about a domain is declared by the plugin that owns it,
83
+ * never written inside the loop.
84
+ */
85
+ export declare function buildTurnInstructions(policy: RoundPolicy, types: SubtaskTypeRegistry, maxSubtasks: number, limits: AgentLimits): TurnInstructions;
86
+ /**
87
+ * Render the model's view for one round: the conversation, with every earlier
88
+ * round's delegation restored as the call-and-result it actually was, and every
89
+ * referenceable turn marked with the catalog index the model selects it by.
90
+ *
91
+ * One pass, two jobs, because they have to agree. The `[ref N]` markers use
92
+ * `isCatalogEligible` — the same predicate the catalog is numbered with — so a
93
+ * marker and its entry can never drift: compaction summaries (`assistant` role,
94
+ * generated) stay in the messages unmarked, readable for context but structurally
95
+ * uncitable as conversation evidence, which is exactly the intent.
96
+ *
97
+ * A round's acknowledgment is stored as plain assistant text (history is
98
+ * text-only, and stays that way — `sessionText`, the catalog, compaction, and
99
+ * recall all read text parts). So its `delegate` call is re-attached to that
100
+ * message here, and the result appended after it, for this one inference call. The
101
+ * pair is emitted together, anchored on the ack's deterministic id, so a `tool`
102
+ * message can never be orphaned from its call — and an ack that has been compacted
103
+ * away still gets its pair, appended at the end minus the acknowledgment text: a
104
+ * result the model cannot place beats a malformed history.
105
+ *
106
+ * Acks are deliberately **not** catalog-eligible: they are the agent's own
107
+ * scaffolding, and a subtask referencing "I'm on it" as verbatim conversation
108
+ * evidence would be noise. That holds for every ack in the Session, not only the
109
+ * ones this render can pair with branches — see `parseRoundAckMessageId`.
110
+ *
111
+ * Everything here is ephemeral — scaffolding for this call only. Reference text is
112
+ * snapshotted from the catalog, so no `[ref N]` prefix ever reaches a Subtask, and
113
+ * the Session never sees any of this markup.
114
+ */
115
+ export declare function renderTurnMessages(history: SessionMessage[], taskId: string, branches: CompositionBranch[]): {
116
+ messages: ModelMessage[];
117
+ catalog: ReferenceCatalogEntry[];
118
+ };
119
+ /**
120
+ * Deterministic fallback reply: the successful branches' text in ordinal order,
121
+ * plus the policy's short note when some branches did not succeed.
122
+ *
123
+ * Used when a round's inference is unavailable but its predecessors' work is
124
+ * durable. Failing the whole Task because the answering model is down would throw
125
+ * away good results the user asked for.
126
+ */
127
+ export declare function joinSuccessfulBranches(branches: CompositionBranch[], partialNote: string): string;
128
+ /**
129
+ * How much rope this round gets, decided by the Workflow from the Task's spent
130
+ * budget.
131
+ *
132
+ * - `open` — the normal round: `delegate`, `final_reply`, and every work tool.
133
+ * It may spend whatever is left of the turn budget.
134
+ * - `final` — the Task has spent its turns or its wall clock. **No work tools and
135
+ * no `delegate`**: the only thing on the table is the answer. This is not a
136
+ * punishment but the shape of the ceiling — a budget that ends in a forced
137
+ * answer returns the work, where one that simply stopped would discard it.
138
+ * Costs one turn, or two if the primary model fails and the fallback has to
139
+ * produce the answer instead; a fallback with no step to spend could not answer
140
+ * at all.
141
+ */
142
+ export type RoundMode = "open" | "final";
143
+ export interface RunTurnArgs {
144
+ /** The DO's one continuous Session. */
145
+ session: SessionLike;
146
+ /** Parent Task id — derives the deterministic Session message ids. */
147
+ taskId: string;
148
+ /** 0-based round within this Task. */
149
+ round: number;
150
+ /** The inbound user text (keeps its `<turn>` provenance wrapper verbatim). Appended on round 0 only. */
151
+ text: string;
152
+ /** What this round may do — see {@link RoundMode}. */
153
+ mode: RoundMode;
154
+ /**
155
+ * The Task's unspent turns, and the tally this round writes back into them.
156
+ * Mutated in place as the model works, so the primary and the fallback draw on
157
+ * one allowance rather than one each — see `TurnBudget`.
158
+ *
159
+ * There is no per-round allowance beyond what the Task has left: an early round
160
+ * that dithers spends what a later one would have had, and is then handed a
161
+ * `final` round to answer in. The caller reads `spent` when the round returns.
162
+ */
163
+ budget: TurnBudget;
164
+ /** Per-request system-prompt suffix (verified caller context). */
165
+ systemSuffix: string;
166
+ /** The main agent's gated **work** tools, merged over the session's own tools. */
167
+ tools: ToolSet;
168
+ /** Primary + fallback model pair. */
169
+ models: ModelPair;
170
+ /** Every earlier round's branches, all rounds, in stable ordinal order. */
171
+ branches: CompositionBranch[];
172
+ /** The installed subtask types — what `delegate` may name. */
173
+ types: SubtaskTypeRegistry;
174
+ /** `CoreConfig.maxSubtasks`, the per-round fan-out bound. */
175
+ maxSubtasks: number;
176
+ /** `CoreConfig.model.maxOutputTokens`. */
177
+ maxOutputTokens: number;
178
+ /** `CoreConfig.model.maxRetries` — retries on *this* model before the fallback. */
179
+ maxRetries: number;
180
+ /** The prompt suffixes, memoized by the DO. See {@link buildTurnInstructions}. */
181
+ instructions: TurnInstructions;
182
+ /** The note a deterministic join appends when it has to disclose gaps. */
183
+ partialNote: string;
184
+ /** Streams intermediate content while the model reasons. Best-effort. */
185
+ onContent?: OnContent;
186
+ }
187
+ /**
188
+ * Terminal outcome of one round. `failed` means the round produced no answer and
189
+ * there was no durable work to fall back on — the parent Task fails rather than
190
+ * running a synthesized subtask nobody asked for. Transient faults throw instead
191
+ * (the Workflow step retries).
192
+ *
193
+ * `kind` is *why*, not a second outcome. The round is over either way and the
194
+ * Task it delivers has the same shape; what the kind decides is what a human is
195
+ * told — `exhausted` is "the models could not do it", a credential kind is "a
196
+ * human must fix the deployment". Both were once separate statuses, and every
197
+ * consumer promptly bundled them back together to do the same three things. See
198
+ * {@link RoundFailureKind}.
199
+ *
200
+ * What the round cost is not here: it is in the caller's `TurnBudget`, which every
201
+ * exit has already charged — including the ones that failed. A round that burned
202
+ * the primary and recovered on the fallback spent both, and there is no variant
203
+ * that could quietly forgive the expensive half of a bad round.
204
+ */
205
+ export type RunTurnOutcome = {
206
+ status: "replied";
207
+ reply: string;
208
+ } | {
209
+ status: "delegated";
210
+ reply: string;
211
+ drafts: SubtaskDraft[];
212
+ } | {
213
+ status: "failed";
214
+ kind: RoundFailureKind;
215
+ error: string;
216
+ };
217
+ /**
218
+ * Run one round against the continuous Session: append the user turn (round 0),
219
+ * let the model decide over the indexed history, validate any delegation against
220
+ * this round's catalog, and persist what the user will see.
221
+ *
222
+ * Every append uses a deterministic id, so a Workflow-step re-run neither
223
+ * duplicates the turn nor changes an already-delivered reply.
224
+ *
225
+ * Two nested recoveries, and they answer different failures. Within a slot, a
226
+ * decomposition the catalog rejects is handed back to the *same* model as a failed
227
+ * tool result, up to {@link MAX_REPAIR_ATTEMPTS} times — a shape error is the one
228
+ * thing a model can fix once it sees it. Across slots, an attempt that produced no
229
+ * decision at all moves to the fallback model, which is what that slot is for.
230
+ *
231
+ * Throws only on a transient platform fault (for the Workflow step to retry).
232
+ * A deterministic failure that outlasts every repair on both slots, with durable
233
+ * work behind it, degrades to {@link joinSuccessfulBranches} rather than discarding
234
+ * completed branches; with nothing behind it, it resolves to
235
+ * `{ status: "failed", kind: "exhausted" }`.
236
+ *
237
+ * The third failure is neither, and it short-circuits the *model* recoveries
238
+ * above: a {@link nonRecoverableKind} error ends the round from wherever it
239
+ * happens, carrying that kind — without repairing and **without reaching the
240
+ * fallback slot**, both of which would only present the same dead credential
241
+ * again. See that function for why the transient/deterministic split cannot
242
+ * express it.
243
+ *
244
+ * It does **not** skip the deterministic join. That path needs no credential —
245
+ * it is string concatenation over rows that are already durable — so completed
246
+ * branches are still delivered, and the credential fault reaches the operator
247
+ * through the log rather than by throwing away finished work.
248
+ */
249
+ export declare function runTurn(args: RunTurnArgs): Promise<RunTurnOutcome>;