@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,400 @@
1
+ import { Agent } from "agents";
2
+ import { TaskState } from "@a2a-js/sdk";
3
+ import { createAgentRuntime } from "../runtime/index.js";
4
+ import { resolveConfig } from "../config.js";
5
+ import { AgentDB, stateOf } from "../db/index.js";
6
+ import { callerContext } from "../a2a/caller.js";
7
+ import { createPushChannel } from "../a2a/push.js";
8
+ import { SelfOrigin } from "../a2a/self-origin.js";
9
+ import { buildAgentSession } from "../agent/session.js";
10
+ import { workersAIModels } from "../agent/workers-ai/index.js";
11
+ /**
12
+ * The Durable Object body every Dynamic Agents agent has, whatever loop it runs.
13
+ *
14
+ * ## Why this is core's and not the app's
15
+ *
16
+ * It was the app's, in the starter, and it was written twice — once for the
17
+ * delegating round agent, once for the single-turn proactive one. The two copies
18
+ * were identical for ~180 lines: the memoized runtime/db/models getters, the
19
+ * `onStart` that must await migrations before the SDK dispatches any RPC, the
20
+ * cron registration guard, the session with its displacement fan-out, the
21
+ * `identityKey` timing, and the task RPC surface.
22
+ *
23
+ * They did not stay identical. The second copy dropped `markWorking`'s
24
+ * cancellation verdict on the floor and probed with a separate `getTask` before
25
+ * writing a terminal Task — so a canceled task still burned a model call, and the
26
+ * gatekeeper could still receive a `completed` callback for a task the caller had
27
+ * abandoned. Both are lifecycle invariants, both were documented in the first
28
+ * copy, and neither is visible to a type checker or a linter.
29
+ *
30
+ * That is the argument for this class. **None of it is policy.** How a turn is
31
+ * shaped, what ends it, what the model is told — all of that stays with the
32
+ * agent, and an agent that wants a different loop simply does not extend
33
+ * {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
34
+ * being different is only ever a bug.
35
+ *
36
+ * ## The three seams
37
+ *
38
+ * ```ts
39
+ * export class MyAgent extends DynamicAgent<Env> {
40
+ * protected agentConfig() { return MY_CONFIG; }
41
+ * protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
42
+ * protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
43
+ * }
44
+ * ```
45
+ *
46
+ * One Durable Object instance per verified caller (keyed by the gatekeeper JWT's
47
+ * `identity.key`), each owning **one continuous Session** — durable history plus
48
+ * a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
49
+ * any channel or thread, accumulate into that one conversation.
50
+ */
51
+ export class DynamicAgent extends Agent {
52
+ session;
53
+ _runtime;
54
+ _models;
55
+ _pair;
56
+ _db;
57
+ /**
58
+ * The verified caller this instance belongs to, set on the first turn.
59
+ *
60
+ * `onStart` runs before any request, so it is not known when `agentPlugins()`
61
+ * is built — which is why anything per-caller takes a thunk. The DO is keyed
62
+ * 1:1 by this value, so it is constant once set.
63
+ *
64
+ * In-memory, and deliberately so — see {@link requireIdentityKey}, which does
65
+ * not depend on it surviving.
66
+ */
67
+ identityKey;
68
+ /**
69
+ * This deployment's own public origin, learned from the `jku` every turn
70
+ * carries and **pinned on the first one** this instance serves.
71
+ *
72
+ * Unlike {@link identityKey} this is shared by concurrent turns — the object
73
+ * is keyed by caller, not by origin — so it is pinned rather than
74
+ * last-write-wins: an immutable field cannot change under a credential thunk
75
+ * that reads it while a turn awaits a model call. See {@link SelfOrigin} for
76
+ * the full argument, and for why nothing is persisted.
77
+ */
78
+ selfOriginMemo = new SelfOrigin();
79
+ /**
80
+ * Test-only model injection. A **field**, not a constructor argument or an RPC
81
+ * parameter, so it never appears on the generated DO stub: production callers
82
+ * cannot reach it, and no model configuration crosses the RPC boundary.
83
+ */
84
+ modelsOverride;
85
+ // --- assembly -------------------------------------------------------------
86
+ /**
87
+ * Everything that would otherwise be a module-level constant, resolved once
88
+ * per DO instance from this agent's config and its installed plugins.
89
+ *
90
+ * Resolving a registry at *import* time is the one thing the package split
91
+ * exists to prevent: it freezes the registry before `env` exists (which on
92
+ * Workers is always), defeats tree-shaking, and makes per-agent plugin
93
+ * selection impossible.
94
+ */
95
+ get runtime() {
96
+ return (this._runtime ??= createAgentRuntime({
97
+ config: this.agentConfig(),
98
+ plugins: this.agentPlugins(this.pluginHost()),
99
+ // Opt in to verifying every plugin's declared bindings exist. Fails at DO
100
+ // start with a sentence naming the plugin, rather than at the first tool
101
+ // call inside a request someone is waiting on.
102
+ env: this.env
103
+ }));
104
+ }
105
+ /** The resolved config. */
106
+ get config() {
107
+ return this.runtime.config;
108
+ }
109
+ /** The agent's database (drizzle + migrations), built once per DO instance. */
110
+ get db() {
111
+ return (this._db ??= new AgentDB(this.ctx.storage, {
112
+ maxSubtasks: this.config.maxSubtasks,
113
+ // Plugin-owned tables, applied after core's own migrations. A store that
114
+ // throws fails DO start rather than being skipped — a plugin whose tables
115
+ // are missing would otherwise fail at its first tool call.
116
+ stores: this.runtime.stores
117
+ }));
118
+ }
119
+ /**
120
+ * Which provider this agent's loops run on. Defaults to Workers AI; override
121
+ * to run on something else.
122
+ *
123
+ * The seam is here rather than in `models` because `models` memoizes, and a
124
+ * subclass overriding a memoized getter has to remember to keep the caching —
125
+ * a trap that only shows up as a performance bug. This is called once.
126
+ *
127
+ * `ModelRuntime` is the whole contract: return anything satisfying it and
128
+ * every loop in core keeps working unchanged. Core ships one implementation,
129
+ * {@link file://../agent/workers-ai/index.ts `agent/workers-ai`} (the default
130
+ * below); a second provider is one more
131
+ * {@link file://../agent/model.ts ModelRuntimeFactory}, defined here or in the
132
+ * consumer, not a change to anything on this path.
133
+ *
134
+ * Takes the resolved {@link ModelConfig} rather than reading `this.config`, so
135
+ * that this signature matches
136
+ * {@link file://../round/subagent.ts RecipeSubagentHost.modelRuntime} — an
137
+ * agent and its facet **must** run the same provider, and identical seams are
138
+ * what let one factory serve both instead of two hand-copied bodies.
139
+ */
140
+ modelRuntime(model) {
141
+ return workersAIModels(this.env, model);
142
+ }
143
+ /** The model runtime for this instance, built lazily and memoized. */
144
+ get models() {
145
+ return (this._models ??= this.modelRuntime(this.config.model));
146
+ }
147
+ /**
148
+ * What this agent's plugins are handed. Built from
149
+ * {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
150
+ * building the runtime is what needs these.
151
+ */
152
+ pluginHost() {
153
+ const model = this.resolvedModelIds();
154
+ return {
155
+ env: this.env,
156
+ storage: this.ctx.storage,
157
+ // A thunk, not a value — see `identityKey`.
158
+ callerKey: () => this.requireIdentityKey(),
159
+ aiGatewayId: model.aiGatewayId
160
+ };
161
+ }
162
+ /**
163
+ * The model settings a locally-declared recipe runs on, resolved *before* the
164
+ * runtime exists.
165
+ *
166
+ * Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
167
+ * cheap and pure and fills in core's baseline, so this is the same result the
168
+ * runtime lands on; that matters because every recipe runs on exactly this
169
+ * pair — `RecipePolicy` carries it and `validateRecipe` stamps it on.
170
+ */
171
+ resolvedModelIds() {
172
+ return resolveConfig(this.agentConfig()).model;
173
+ }
174
+ async onStart() {
175
+ // Await migrations before the SDK dispatches any RPC — eliminates the race
176
+ // between schema creation and first query on cold start / hibernation wake-up.
177
+ await this.db.ensureReady();
178
+ // Register the weekly cleanup cron once per DO instance (idempotent guard).
179
+ const existing = await this.listSchedules({ type: "cron" });
180
+ if (!existing.some((s) => s.callback === "cleanupOldTasks")) {
181
+ await this.schedule("0 1 * * 0", "cleanupOldTasks", {});
182
+ }
183
+ }
184
+ /**
185
+ * Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
186
+ *
187
+ * A plugin's own tables are its business — core's journal does not reach them,
188
+ * and neither does this sweep. A subclass with more durable state of its own
189
+ * overrides {@link cleanupAgentState}.
190
+ */
191
+ async cleanupOldTasks(_payload, _schedule) {
192
+ this.db.tasks.cleanup();
193
+ this.cleanupAgentState();
194
+ }
195
+ /** Extra durable state to age out alongside the task rows. Default: none. */
196
+ cleanupAgentState() { }
197
+ /**
198
+ * The main agent's primary/fallback pair. With `metadata` it builds a fresh
199
+ * pair carrying that AI Gateway correlation tag (so an AI Gateway log ties the call
200
+ * to its task and round); without it — the Session's own compaction model — it
201
+ * reuses a memoized default. A test `modelsOverride` always wins.
202
+ */
203
+ modelPair(metadata) {
204
+ if (this.modelsOverride)
205
+ return this.modelsOverride;
206
+ if (!metadata)
207
+ return (this._pair ??= this.models.createModelPair());
208
+ return this.models.createModelPair({ metadata });
209
+ }
210
+ /**
211
+ * The one continuous Session for this caller (rebuilt from `this.sql` after
212
+ * eviction). Memoized — `identity` is constant for the DO's life, since the DO
213
+ * is keyed 1:1 by `identity.key`.
214
+ *
215
+ * `onMessagesDisplaced` is the whole integration for anything that wants the
216
+ * messages a compaction folds away: core performs the compaction, so core
217
+ * announces the loss, and the runtime fans it out to every plugin that asked.
218
+ */
219
+ getSession(identity) {
220
+ this.identityKey ??= identity.key ?? undefined;
221
+ const { session, model } = this.config;
222
+ return (this.session ??= buildAgentSession(this, this.modelPair().primary(), {
223
+ soul: () => this.agentSoul(this.runtime.renderCapabilities()),
224
+ memoryDescription: session.memoryDescription,
225
+ memoryMaxTokens: session.memoryMaxTokens,
226
+ compactAfterTokens: session.compactAfterTokens,
227
+ compactTailTokens: session.compactTailTokens,
228
+ maxOutputTokens: model.maxOutputTokens,
229
+ onMessagesDisplaced: this.runtime.onMessagesDisplaced
230
+ }));
231
+ }
232
+ /**
233
+ * The caller key, which is present on every path that can reach a plugin.
234
+ *
235
+ * ## Why this does not just read the field
236
+ *
237
+ * `identityKey` is set on the first turn and lives in the isolate. An isolate
238
+ * does not live as long as the work does: it can be evicted between two rounds
239
+ * of the same task, and it can be reset outright — "Durable Object connection
240
+ * closed because the object was reset" — while a Workflow step is mid-flight.
241
+ * The next call arrives on a fresh instance where the field is empty, and
242
+ * every per-caller thunk built off it throws.
243
+ *
244
+ * That failure is disproportionate to its cause. A plugin asking which caller
245
+ * it is serving gets an exception, mid-task, on an object whose entire purpose
246
+ * is to be that caller's — and because the throw happens inside a tool or a
247
+ * runtime resolution rather than at the edge, it surfaces as a failed branch
248
+ * rather than as anything an operator can read.
249
+ *
250
+ * So the object answers from itself. `define-agent` routes with
251
+ * `ns.get(ns.idFromName(identity.key))`, which means the caller key *is* this
252
+ * object's name and the platform hands it back on `ctx.id.name` — durable, free
253
+ * and correct by construction: an object cannot disagree with the name it was
254
+ * addressed by.
255
+ *
256
+ * The field still wins when it is set. `id.name` is undefined for an object
257
+ * addressed by `newUniqueId()` or a raw id string, so it is a fallback rather
258
+ * than the source of truth, and the throw is kept for the case where neither
259
+ * exists.
260
+ */
261
+ requireIdentityKey() {
262
+ const key = this.identityKey ?? this.ctx.id.name;
263
+ if (!key) {
264
+ throw new Error("identity.key is required for per-caller isolation");
265
+ }
266
+ return (this.identityKey = key);
267
+ }
268
+ /**
269
+ * Offer this deployment's own origin from a value that carries it. The first
270
+ * usable one is kept for the life of the instance.
271
+ *
272
+ * Called wherever a {@link TurnPushContext} arrives — here for every agent
273
+ * shape, and at the entry of `RoundAgentBase`'s two RPCs, where the origin is
274
+ * needed *before* this channel would be built. All three matter because any of
275
+ * them can be the call that wakes a fresh isolate. Cheap and unfailing: past
276
+ * the first turn it is one truthiness check, and an unusable value is ignored
277
+ * rather than thrown, because a turn must not fail over this.
278
+ */
279
+ noteSelfOrigin(url) {
280
+ this.selfOriginMemo.note(url);
281
+ }
282
+ /**
283
+ * This deployment's own public origin, if a turn has carried it to this
284
+ * instance yet. Constant once set, so it reads the same from any turn running
285
+ * on this object. See {@link SelfOrigin}.
286
+ */
287
+ selfOrigin() {
288
+ return this.selfOriginMemo.peek();
289
+ }
290
+ /**
291
+ * The same, for a caller that cannot proceed without it — signing a caller
292
+ * token with {@link file://../a2a/caller-token.ts signCallerToken} above all,
293
+ * whose `iss` this is. Throws naming the timing rather than producing a token
294
+ * with a nonsense issuer.
295
+ */
296
+ requireSelfOrigin() {
297
+ return this.selfOriginMemo.require();
298
+ }
299
+ /** The gatekeeper callback channel for one turn. See {@link PushChannel}. */
300
+ push(context) {
301
+ this.noteSelfOrigin(context.jku);
302
+ return createPushChannel(this.env.A2A_SIGNING_KEY, context);
303
+ }
304
+ /**
305
+ * The per-request system-prompt suffix describing the verified caller.
306
+ *
307
+ * A rendering of a protocol fact rather than prompt copy, so core supplies one
308
+ * — see {@link callerContext}. Override it to name what a workspace id means in
309
+ * your deployment; do not use it to say who the *user* is, which this is not.
310
+ */
311
+ callerContext(identity) {
312
+ return callerContext(identity);
313
+ }
314
+ // --- Async task state (accept + notify) ----------------------------------
315
+ //
316
+ // A thin RPC surface over `AgentDB`'s `tasks` table. Native RPC methods — the
317
+ // DO is never a network-reachable server — called by the Workflow, which
318
+ // cannot touch this SQLite directly.
319
+ //
320
+ // The Task-returning methods return `PlainTask`: the SDK `Task` narrowed to
321
+ // what survives Cloudflare's RPC types. Returning the raw SDK `Task` breaks
322
+ // the generated DO-stub types (under v1.0 it blows past TypeScript's
323
+ // instantiation-depth limit).
324
+ async beginTask(input) {
325
+ return this.db.tasks.begin(input);
326
+ }
327
+ async getTask(taskId) {
328
+ return this.db.tasks.get(taskId);
329
+ }
330
+ async listTasks(query) {
331
+ return this.db.tasks.list(query);
332
+ }
333
+ /**
334
+ * Persist a Task, returning **whether the guarded write applied**.
335
+ *
336
+ * That boolean is the cancellation check, and a caller must key its callback on
337
+ * it: `AgentDB` refuses to write a terminal state over a `canceled` row and
338
+ * does that read and write in one synchronous pass inside the DO. Probing with
339
+ * {@link getTask} first and saving second leaves a window — between the two
340
+ * calls, and again between the save and the notify — in which a cancel lands
341
+ * and the gatekeeper still receives a `completed` callback.
342
+ *
343
+ * A `canceled` state routes to {@link markCanceled} instead of a plain write,
344
+ * so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
345
+ * through {@link cancelTask} converge on the same interruption path.
346
+ */
347
+ async saveTask(task) {
348
+ if (stateOf(task) === TaskState.TASK_STATE_CANCELED) {
349
+ return (await this.markCanceled(task.id, task)) !== null;
350
+ }
351
+ return this.db.tasks.save(task);
352
+ }
353
+ /**
354
+ * Move the Task to `working`. Returns `"canceled"` when the caller cancelled
355
+ * first — read it and stop, rather than probing with a separate
356
+ * {@link getTask}, which reopens the gap between asking and acting.
357
+ *
358
+ * Anything else is `"ok"`, including an unknown row and a row already `working`
359
+ * (a replayed step): only an actual cancellation stops the pipeline.
360
+ */
361
+ async markWorking(taskId) {
362
+ return this.db.tasks.markWorking(taskId);
363
+ }
364
+ async cancelTask(taskId) {
365
+ return this.markCanceled(taskId);
366
+ }
367
+ /**
368
+ * The one place a Task becomes canceled: flip the row — terminal, so every
369
+ * non-canceled write is refused afterwards — then interrupt whatever is still
370
+ * running for it.
371
+ *
372
+ * `task` is supplied when the caller already built the canceled Task (the
373
+ * a2a-js cancel branch attaches its own status message); otherwise the row's
374
+ * own guarded flip produces it. Both paths are guarded against the same race:
375
+ * a task that already reached `completed`/`failed` refuses the write, and its
376
+ * verdict — not a `get` read straight after, which would return that
377
+ * unchanged terminal row and be mistaken for a successful cancellation — is
378
+ * what decides whether {@link onTaskCanceled} runs at all.
379
+ */
380
+ async markCanceled(taskId, task) {
381
+ const canceled = task
382
+ ? this.db.tasks.save(task) && this.db.tasks.get(taskId)
383
+ : this.db.tasks.cancel(taskId);
384
+ if (!canceled)
385
+ return null;
386
+ await this.onTaskCanceled(taskId);
387
+ return canceled;
388
+ }
389
+ /**
390
+ * Interrupt work still in flight for a task that has just been canceled.
391
+ *
392
+ * Default: nothing, which is right for an agent whose turn is a single
393
+ * inference — the row is terminal and the next guarded write refuses. An agent
394
+ * with children overrides this to abort them.
395
+ *
396
+ * **Must be best-effort.** Cancellation has already been recorded by the time
397
+ * this runs, and it must not fail because cleanup did.
398
+ */
399
+ async onTaskCanceled(_taskId) { }
400
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@dynamicagents/core/host` — the Durable Object half of an agent.
3
+ *
4
+ * {@link DynamicAgent} is the DO body every Dynamic Agents agent has whatever loop it
5
+ * runs: the runtime and database built once per instance, the one continuous
6
+ * Session per verified caller, the gatekeeper callback channel, and the task
7
+ * lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
8
+ * the plugins.
9
+ *
10
+ * Its own subpath rather than part of `/agent` on purpose. `/agent` is the
11
+ * primitives a *loop* is built from — session, model pair, budget, control tools
12
+ * — and a loop module should be able to import those without pulling a Durable
13
+ * Object base class and drizzle into its graph.
14
+ *
15
+ * Core still ships **no loop and no prompt copy**. Nothing here decides how a
16
+ * turn is shaped or what ends it; see `@dynamicagents/core/round` for the delegating
17
+ * round loop, which is opt-in and takes its prompt copy from the agent.
18
+ */
19
+ export { DynamicAgent } from "./agent.js";
20
+ export type { PluginHost } from "./plugin-host.js";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@dynamicagents/core/host` — the Durable Object half of an agent.
3
+ *
4
+ * {@link DynamicAgent} is the DO body every Dynamic Agents agent has whatever loop it
5
+ * runs: the runtime and database built once per instance, the one continuous
6
+ * Session per verified caller, the gatekeeper callback channel, and the task
7
+ * lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
8
+ * the plugins.
9
+ *
10
+ * Its own subpath rather than part of `/agent` on purpose. `/agent` is the
11
+ * primitives a *loop* is built from — session, model pair, budget, control tools
12
+ * — and a loop module should be able to import those without pulling a Durable
13
+ * Object base class and drizzle into its graph.
14
+ *
15
+ * Core still ships **no loop and no prompt copy**. Nothing here decides how a
16
+ * turn is shaped or what ends it; see `@dynamicagents/core/round` for the delegating
17
+ * round loop, which is opt-in and takes its prompt copy from the agent.
18
+ */
19
+ export { DynamicAgent } from "./agent.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Everything a plugin may need from its host, resolved once per Durable Object
3
+ * instance.
4
+ *
5
+ * This is **contract, not application code**, which is why it lives in core. A
6
+ * published plugin's config is written against this shape — `arcAgi` takes
7
+ * `storage`, `recall` takes `callerKey` — so a plugin that could not name the
8
+ * type was writing its signature against a structural guess at an interface
9
+ * declared in an app it has never seen.
10
+ *
11
+ * Not just `env`, and each field is load-bearing:
12
+ *
13
+ * - `env` — bindings and secrets. Typed `object` for the same reason
14
+ * {@link file://../runtime/index.ts CreateAgentRuntimeOptions.env} is: `Env` is
15
+ * the ambient interface `wrangler types` generates into a consumer's
16
+ * `worker-configuration.d.ts`, it has no index signature, and requiring a cast
17
+ * to pass one's own `this.env` is how a seam goes unused. A host narrows it.
18
+ * - `storage` — a plugin that owns tables needs the DO's storage to build a query
19
+ * handle over. `this.ctx.storage`.
20
+ * - `callerKey` — **a thunk, deliberately.** It derives from the verified
21
+ * caller's identity, which does not exist yet when `plugins()` runs at DO
22
+ * start. The DO is keyed 1:1 by that caller, so the value is constant once
23
+ * known; a thunk is what lets the host supply it late while every hook reads
24
+ * the same one. On a subagent facet there is no caller at all, and the honest
25
+ * encoding is a thunk that throws.
26
+ * - `aiGatewayId` — the **resolved** AI Gateway slug, so a plugin making its own
27
+ * model calls is correlated with the agent's. Resolved, not read off the
28
+ * overrides object: reaching into `MY_CONFIG.model?.aiGatewayId` at a call site
29
+ * silently yields `undefined` the moment that override is dropped in favour of
30
+ * core's baseline, and the plugin's calls quietly stop being correlated.
31
+ *
32
+ * Deliberately **not** here: the agent's model pair. See the note on the
33
+ * interface below.
34
+ */
35
+ export interface PluginHost<TEnv extends object = object> {
36
+ env: TEnv;
37
+ storage: DurableObjectStorage;
38
+ /** The verified caller. A thunk — it does not exist when `onStart` runs. */
39
+ callerKey: () => string;
40
+ /** `config.model.aiGatewayId`, already resolved over core's baseline. */
41
+ aiGatewayId: string;
42
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@dynamicagents/core` — the mandatory foundation for a Dynamic Agents agent.
3
+ *
4
+ * This root entry carries only what nearly every consumer touches: the plugin
5
+ * contract, the config shape, the runtime factory, and the platform facts.
6
+ * Everything else lives behind a subpath (`/a2a`, `/agent`, `/db`, `/subtasks`,
7
+ * `/subagent`, `/worker`, `/testing`), so importing the delegation layer does not
8
+ * pull in the A2A adapter and importing the test harness never reaches a
9
+ * production bundle.
10
+ */
11
+ export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies, type AgentRuntime, type CreateAgentRuntimeOptions } from "./runtime/index.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
+ export type { DelegationNames, RecipeLimits, ResolvedRecipe, SubtaskParams, SubtaskParamsSchema, SubtaskParamsShape, SubtaskTypeSpec, ValidatedRecipe } from "./contract/recipe.js";
14
+ export { RecipeValidationError, resolveLimits, validateRecipe, type RecipePolicy } from "./contract/validation.js";
15
+ export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig, type AgentLimits, type CoreConfig, type CoreConfigOverrides, type ModelConfig, type SessionConfig } from "./config.js";
16
+ export { parseGatekeeperOrigins, type A2ASecretsEnv, type AiEnv, type CoreEnv } from "./env.js";
17
+ export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
18
+ export type { PluginStore } from "./db/db.js";
19
+ export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES, type WorkspaceBacking, type WorkspaceEntry, type WorkspaceHandle } from "./subagent/workspace.js";
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@dynamicagents/core` — the mandatory foundation for a Dynamic Agents agent.
3
+ *
4
+ * This root entry carries only what nearly every consumer touches: the plugin
5
+ * contract, the config shape, the runtime factory, and the platform facts.
6
+ * Everything else lives behind a subpath (`/a2a`, `/agent`, `/db`, `/subtasks`,
7
+ * `/subagent`, `/worker`, `/testing`), so importing the delegation layer does not
8
+ * pull in the A2A adapter and importing the test harness never reaches a
9
+ * production bundle.
10
+ */
11
+ export { createAgentRuntime, RuntimeSetupError, buildRecipeTools, collectToolFamilies } from "./runtime/index.js";
12
+ export { PLUGIN_CONTRACT_VERSION, definePlugin, restrictMainAgentTools } from "./contract/plugin.js";
13
+ export { RecipeValidationError, resolveLimits, validateRecipe } from "./contract/validation.js";
14
+ export { ConfigError, DEFAULT_CORE_CONFIG, resolveConfig } from "./config.js";
15
+ export { parseGatekeeperOrigins } from "./env.js";
16
+ export { CHUNK_SOFT_MS, CHUNK_STEP, MAX_CHUNKS_PER_BRANCH, MAX_TOOL_CALL_MS, STEP_TIMEOUT_MS, STEPS_PER_INSTANCE } from "./platform.js";
17
+ export { makeWorkspaceHandle, memoryWorkspaceBacking, WorkspaceLimitError, WORKSPACE_MAX_FILES, WORKSPACE_MAX_FILE_BYTES } from "./subagent/workspace.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@dynamicagents/core/job` — a long job a Durable Object owns through its alarm.
3
+ *
4
+ * **The sibling of `@dynamicagents/core/alarm`, and the pairing is the point.**
5
+ * `WakeMap` owns *when* an object wakes; this owns *what a job owes on waking*.
6
+ * Neither depends on the other's reason for existing, and both are useful to a
7
+ * plain `DurableObject` rather than only to a `DynamicAgent` — which is why they
8
+ * are subpaths and not part of the agent machinery.
9
+ *
10
+ * **Mechanism only.** Nothing here knows what a job *does*: no command, no
11
+ * container, no filesystem, no vendor library. A consumer supplies the handle
12
+ * and the meaning; this supplies the four rules that are wrong in the same way
13
+ * every time — arming before the work starts, one job at a time, a drain that
14
+ * can outlive its job, and a job nobody is draining. See {@link JobLifecycle}.
15
+ *
16
+ * Deliberately **not** called `task`. Core already has a `Task` — the A2A one,
17
+ * with its own lifecycle, its own guarded writes and its own table — and two
18
+ * unrelated meanings in one namespace is a cost paid forever by every reader.
19
+ */
20
+ export { isRearmable, isRunning, type DoneJob, type FailedJob, type IdleJob, type JobState, type RunningJob, type SkippedJob } from "./state.js";
21
+ export { JobLifecycle, type JobContext, type JobHandle, type JobLifecycleOptions, type JobResult } from "./lifecycle.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@dynamicagents/core/job` — a long job a Durable Object owns through its alarm.
3
+ *
4
+ * **The sibling of `@dynamicagents/core/alarm`, and the pairing is the point.**
5
+ * `WakeMap` owns *when* an object wakes; this owns *what a job owes on waking*.
6
+ * Neither depends on the other's reason for existing, and both are useful to a
7
+ * plain `DurableObject` rather than only to a `DynamicAgent` — which is why they
8
+ * are subpaths and not part of the agent machinery.
9
+ *
10
+ * **Mechanism only.** Nothing here knows what a job *does*: no command, no
11
+ * container, no filesystem, no vendor library. A consumer supplies the handle
12
+ * and the meaning; this supplies the four rules that are wrong in the same way
13
+ * every time — arming before the work starts, one job at a time, a drain that
14
+ * can outlive its job, and a job nobody is draining. See {@link JobLifecycle}.
15
+ *
16
+ * Deliberately **not** called `task`. Core already has a `Task` — the A2A one,
17
+ * with its own lifecycle, its own guarded writes and its own table — and two
18
+ * unrelated meanings in one namespace is a cost paid forever by every reader.
19
+ */
20
+ export { isRearmable, isRunning } from "./state.js";
21
+ export { JobLifecycle } from "./lifecycle.js";