melchizedek-agents 1.0.2 → 1.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 (122) hide show
  1. package/.env.example +19 -2
  2. package/CHANGELOG.md +333 -0
  3. package/DOCUMENTATION.md +99 -39
  4. package/README.md +1 -0
  5. package/config/agents/README.md +2 -2
  6. package/config/agents/examples/ares.yaml +6 -10
  7. package/config/agents/examples/assistant.yaml +6 -6
  8. package/config/agents/examples/augustin.yaml +9 -15
  9. package/config/agents/examples/cartographers.yaml +6 -6
  10. package/config/agents/examples/council.yaml +6 -6
  11. package/config/agents/examples/critic.yaml +16 -16
  12. package/config/agents/examples/harness.yaml +4 -4
  13. package/config/agents/examples/image_production.yaml +33 -32
  14. package/config/agents/examples/librarian.yaml +4 -4
  15. package/config/agents/examples/patient_advocate.yaml +6 -10
  16. package/config/agents/examples/pipeline.yaml +20 -19
  17. package/config/agents/examples/research.yaml +36 -43
  18. package/config/agents/examples/scribe.yaml +31 -32
  19. package/config/agents/examples/scriptorium.yaml +6 -6
  20. package/config/agents/examples/structured_critic.yaml +82 -0
  21. package/config/agents/examples/style_council.yaml +9 -11
  22. package/config/agents/examples/syndicate.yaml +3 -5
  23. package/config/agents/examples/tutor.yaml +2 -2
  24. package/config/agents/examples/weather.yaml +2 -2
  25. package/config/agents/syndicate.schema.json +1979 -0
  26. package/config/agents/syndicateSchema.yaml +109 -72
  27. package/config/agents/templates/README.md +1 -1
  28. package/config/agents/templates/account_memory.yaml +4 -4
  29. package/config/agents/templates/case_desk.yaml +6 -6
  30. package/config/agents/templates/conversational.yaml +2 -2
  31. package/config/agents/templates/draft_review.yaml +6 -6
  32. package/config/agents/templates/intake_extractor.yaml +61 -60
  33. package/config/agents/templates/research_brief.yaml +6 -6
  34. package/config/agents/templates/research_desk.yaml +6 -6
  35. package/config/agents/templates/review_panel.yaml +8 -8
  36. package/config/agents/templates/support_triage.yaml +30 -29
  37. package/config/agents/templates/systems_operator.yaml +21 -5
  38. package/db/migrations/0014_durable_runs.sql +31 -0
  39. package/dist/lib/a2a/app.d.ts +15 -0
  40. package/dist/lib/a2a/app.js +53 -5
  41. package/dist/lib/a2a/executor.d.ts +7 -1
  42. package/dist/lib/a2a/executor.js +34 -4
  43. package/dist/lib/a2a/oauthSetup.d.ts +90 -0
  44. package/dist/lib/a2a/oauthSetup.js +205 -0
  45. package/dist/lib/agentDialect.d.ts +66 -0
  46. package/dist/lib/agentDialect.js +134 -0
  47. package/dist/lib/compile.d.ts +10 -5
  48. package/dist/lib/compile.js +69 -37
  49. package/dist/lib/compileNative.js +2 -0
  50. package/dist/lib/dispatch.d.ts +10 -5
  51. package/dist/lib/dispatch.js +10 -5
  52. package/dist/lib/doctor.d.ts +34 -1
  53. package/dist/lib/doctor.js +47 -3
  54. package/dist/lib/loadSyndicate.d.ts +45 -2
  55. package/dist/lib/loadSyndicate.js +22 -2
  56. package/dist/lib/memory/namespace.d.ts +6 -5
  57. package/dist/lib/memory/namespace.js +6 -5
  58. package/dist/lib/memory/providers.js +3 -4
  59. package/dist/lib/models/capabilities.d.ts +16 -1
  60. package/dist/lib/models/capabilities.js +55 -1
  61. package/dist/lib/observability/tracer.d.ts +8 -1
  62. package/dist/lib/observability/tracer.js +33 -5
  63. package/dist/lib/runtime/approvals.d.ts +6 -0
  64. package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
  65. package/dist/lib/runtime/native/agentLoop.js +105 -11
  66. package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
  67. package/dist/lib/runtime/native/checkpoint.js +365 -0
  68. package/dist/lib/runtime/native/delegate.d.ts +141 -18
  69. package/dist/lib/runtime/native/delegate.js +235 -39
  70. package/dist/lib/runtime/native/interrupts.d.ts +101 -2
  71. package/dist/lib/runtime/native/interrupts.js +201 -1
  72. package/dist/lib/runtime/native/request.d.ts +13 -3
  73. package/dist/lib/runtime/native/request.js +28 -4
  74. package/dist/lib/runtime/native/telemetry.js +10 -15
  75. package/dist/lib/runtime/questions.d.ts +7 -4
  76. package/dist/lib/runtime/sessions.d.ts +1 -1
  77. package/dist/lib/runtime/syndicateTurn.d.ts +7 -1
  78. package/dist/lib/runtime/syndicateTurn.js +74 -22
  79. package/dist/lib/runtime/turnControl.d.ts +15 -0
  80. package/dist/lib/runtime/turnControl.js +10 -0
  81. package/dist/lib/storage/postgres/index.d.ts +6 -1
  82. package/dist/lib/storage/postgres/index.js +7 -2
  83. package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
  84. package/dist/lib/storage/postgres/taskQueue.js +20 -3
  85. package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
  86. package/dist/lib/storage/postgres/taskStore.js +74 -8
  87. package/dist/lib/syndicateSchema.d.ts +278 -0
  88. package/dist/lib/syndicateSchema.js +182 -15
  89. package/dist/lib/tools/auth.d.ts +7 -5
  90. package/dist/lib/tools/auth.js +12 -4
  91. package/dist/lib/tools/credentialEnv.d.ts +28 -0
  92. package/dist/lib/tools/credentialEnv.js +52 -0
  93. package/dist/lib/tools/credentialStore.js +9 -2
  94. package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
  95. package/dist/lib/tools/mcpToolFactory.js +179 -21
  96. package/dist/lib/tools/oauthHosts.d.ts +71 -0
  97. package/dist/lib/tools/oauthHosts.js +154 -0
  98. package/dist/lib/tools/oauthTools.d.ts +233 -0
  99. package/dist/lib/tools/oauthTools.js +421 -0
  100. package/dist/lib/tools/openapiTools.d.ts +4 -6
  101. package/dist/lib/tools/openapiTools.js +40 -36
  102. package/dist/lib/tools/taskTools.d.ts +30 -5
  103. package/dist/lib/tools/taskTools.js +74 -10
  104. package/dist/lib/workflow.d.ts +8 -4
  105. package/dist/lib/workflow.js +8 -4
  106. package/dist/lib/workflowConfig.d.ts +6 -0
  107. package/dist/scripts/a2a_server.d.ts +36 -0
  108. package/dist/scripts/a2a_server.js +122 -1
  109. package/dist/scripts/assistant_worker.d.ts +10 -0
  110. package/dist/scripts/assistant_worker.js +69 -19
  111. package/dist/scripts/doctor.d.ts +4 -1
  112. package/dist/scripts/doctor.js +5 -2
  113. package/dist/scripts/yaml_codemod.d.ts +52 -0
  114. package/dist/scripts/yaml_codemod.js +570 -0
  115. package/package.json +11 -7
  116. package/skills/briefs/melchizedek-author.md +7 -5
  117. package/skills/briefs/melchizedek-models.md +4 -3
  118. package/skills/briefs/melchizedek.md +1 -0
  119. package/skills/melchizedek/SKILL.md +1 -0
  120. package/skills/melchizedek-author/SKILL.md +7 -4
  121. package/skills/melchizedek-author/assets/minimal.yaml +13 -13
  122. package/skills/melchizedek-models/SKILL.md +6 -3
@@ -13,12 +13,21 @@
13
13
  *
14
14
  * ONE CALL, AS ADK'S AgentTool RAN IT:
15
15
  * 1. The message: `{ role: 'user', parts: [{ text: args.request }] }`.
16
- * 2. The subagent's session: the one under the subagent's own name as the
17
- * app name, with the caller's user id and session id. It is created on
18
- * the first call, with the caller's state as it stands (the session's,
19
- * then this call's writes; `temp:` keys dropped), and kept: a later
20
- * call, in this turn or another, continues it. This is not a branch of
21
- * the caller's session; ADK's AgentTool runs a Runner of its own.
16
+ * 2. The subagent's session: filed under the agent path (childAppName,
17
+ * ADR 0111), with the caller's user id and session id: the caller's
18
+ * app name, the caller's name and the subagent's at the top
19
+ * (`<app>/<caller>/<subagent>`), the caller's app name and the
20
+ * subagent's below it (`<app>/<caller>/<subagent>/<inner>`). It is
21
+ * created on the first call, with the caller's state as it stands (the
22
+ * session's, then this call's writes; `temp:` keys dropped), and kept:
23
+ * a later call, in this turn or another, continues it. This is not a
24
+ * branch of the caller's session; ADK's AgentTool runs a Runner of its
25
+ * own. ADK filed it under the subagent's name alone, so two syndicates
26
+ * with a same-named subagent on one conversation shared it. A session
27
+ * stored under that old key is still read (legacyChild): when no
28
+ * session exists under the path and the caller has called the
29
+ * subagent before (or is resuming a pause found there), the old one is
30
+ * continued, so a stored conversation resumes where it stopped.
22
31
  * 3. A turn already stopped answers an empty text, before or after the
23
32
  * message is stored.
24
33
  * 4. The message is stored as a user event under a fresh invocation id
@@ -34,13 +43,30 @@
34
43
  * schema it is parsed as JSON; text that does not parse fails the call
35
44
  * with the parser's message, as on ADK.
36
45
  *
37
- * A PAUSE INSIDE A SUBAGENT (an ask_user call, an approval request) cannot
38
- * reach the caller, exactly as under ADK (ADR 0028): the child run ends
39
- * paused, its last event carries no text, the call answers '', and the gated
40
- * tool never runs. WS6-2a lifts this.
46
+ * A PAUSE INSIDE A SUBAGENT (an ask_user call, an approval request, or a
47
+ * pause inside its own delegated call) reaches the caller (ADR 0110, which
48
+ * lifts ADR 0028's refusal). The child run ends paused; the call resolves to
49
+ * a SubagentPause naming the ids it waits on, and the caller stores no
50
+ * response to it: the call stays open, and the caller's run ends paused too
51
+ * (agentLoop.ts). The turn finds the pause by walking down the open calls
52
+ * (interrupts.ts delegatedPauses) and reports it with the agent path. The
53
+ * answer comes back as the caller's next user message; the caller resumes
54
+ * the open call (resumeSubagent), which stores the same answer parts as
55
+ * the child's next message, under a fresh invocation id, and runs the
56
+ * child's loop on: it reads the answer from its own session as a top-level
57
+ * agent reads it (an approval runs or refuses its pinned call, a question's
58
+ * answer is the call's response), finishes, and its answer is the open
59
+ * call's response. A child that pauses again leaves the call open again.
41
60
  *
42
- * Calls to subagents in one step run one after another, in call order, as
43
- * ADK runs them; running them concurrently is WS6.
61
+ * CALLS TO SUBAGENTS IN ONE STEP RUN CONCURRENTLY (ADR 0116), under the
62
+ * caller's `max_concurrency` (the syndicate's root key, default
63
+ * DEFAULT_MAX_CONCURRENCY): agentLoop.ts runs each delegated call through
64
+ * the step's DelegationGate, which starts them in call order as slots free
65
+ * up, and runs two calls to the same subagent (one child session) one after
66
+ * the other. The step's responses are stored once every call has answered,
67
+ * in call order, whatever order the children finished in; a child that
68
+ * pauses leaves its call open while the others finish and are stored. ADK
69
+ * ran them one after another; `max_concurrency: 1` keeps that order.
44
70
  *
45
71
  * NESTED SYNDICATES: a `yaml_reference` subagent is the nested syndicate's
46
72
  * orchestrator, named as the entry, listing its own subagent tools. It runs
@@ -49,6 +75,13 @@
49
75
  *
50
76
  * A NESTED WORKFLOW (ADR 0098): a `yaml_reference` to a workflow syndicate
51
77
  * is a workflowSubagentTool, holding the whole graph as a WorkflowSubagent.
78
+ * A walk that ends paused (an ask_user node's question, an agent node's
79
+ * approval request) leaves the call open as a child loop's pause does
80
+ * (ADR 0111): the call resolves to a SubagentPause naming the walk's open
81
+ * interrupts, and resumeWorkflowSubagent walks the graph again on the
82
+ * answer, which the walk's own resume (lib/workflow/resume.ts) reads as it
83
+ * reads a top-level workflow's. An `ask_user` answer reaches the walk as
84
+ * ADK's explicit reply to its `adk_request_input` call.
52
85
  * A call runs as steps 1 to 6 say, with the walk in place of the loop: the
53
86
  * graph's walk (lib/workflow/turn.ts, runNativeWorkflow, which
54
87
  * lib/compileNative.ts hands over) stores the message itself and yields the
@@ -69,6 +102,7 @@ import { randomUUID } from 'node:crypto';
69
102
  import { resolveAdapter } from '../../models/registry.js';
70
103
  import { createTurnEvent } from '../events.js';
71
104
  import { TEMP_STATE_PREFIX } from '../sessions.js';
105
+ import { REQUEST_INPUT_CALL } from './history.js';
72
106
  import { SelfCorrection } from './selfCorrection.js';
73
107
  // ── The subagent tool ────────────────────────────────────────────────────────
74
108
  /** Where a subagent tool keeps the agent it runs. A global symbol, so a second copy of this module still finds it. */
@@ -128,13 +162,91 @@ export function subagentOf(tool) {
128
162
  return agent;
129
163
  return undefined;
130
164
  }
131
- const queues = new WeakMap();
132
- /** Runs `run` after every earlier call queued under `key`, whether it succeeded or not. */
133
- function inTurn(key, run) {
134
- const before = queues.get(key) ?? Promise.resolve();
135
- const mine = before.then(run, run);
136
- queues.set(key, mine.then(() => undefined, () => undefined));
137
- return mine;
165
+ // ── Running one call ─────────────────────────────────────────────────────────
166
+ /**
167
+ * A delegated call whose child run ended paused (ADR 0110): the ids the
168
+ * child waits on. The caller stores no response to the call and ends its
169
+ * own run paused.
170
+ */
171
+ export class SubagentPause {
172
+ pending;
173
+ constructor(pending) {
174
+ this.pending = pending;
175
+ }
176
+ }
177
+ /** Delegated calls one step runs at once when the syndicate sets no `max_concurrency` (ADR 0116). */
178
+ export const DEFAULT_MAX_CONCURRENCY = 4;
179
+ /**
180
+ * The delegated calls of one step (ADR 0116): at most `limit` run at once,
181
+ * started in the order they entered (agentLoop.ts enters them in call
182
+ * order, synchronously, before any of them runs), and calls sharing a key
183
+ * (the subagent's name, so one child session) run one after another in
184
+ * that order. A slot is held from the call's start to its release, which
185
+ * comes as soon as the child's run ends: what the step does after (self-
186
+ * correction's bookkeeping, which waits on earlier calls) holds no slot. A
187
+ * call waiting on an earlier same-key call holds its slot meanwhile: the
188
+ * earlier call already holds one, so the wait always ends.
189
+ */
190
+ export class DelegationGate {
191
+ limit;
192
+ active = 0;
193
+ waiting = [];
194
+ lanes = new Map();
195
+ constructor(limit = DEFAULT_MAX_CONCURRENCY) {
196
+ this.limit = Number.isInteger(limit) && limit > 0 ? limit : DEFAULT_MAX_CONCURRENCY;
197
+ }
198
+ /** Enters one call under `key`, behind every call entered before it. */
199
+ enter(key) {
200
+ let granted = false;
201
+ let released = false;
202
+ let freeLane;
203
+ const laneDone = new Promise((resolve) => (freeLane = resolve));
204
+ const slot = new Promise((resolve) => {
205
+ this.waiting.push(() => {
206
+ granted = true;
207
+ resolve();
208
+ });
209
+ this.pump();
210
+ });
211
+ const before = this.lanes.get(key);
212
+ this.lanes.set(key, laneDone);
213
+ const ready = Promise.all([slot, before]).then(() => undefined);
214
+ const release = () => {
215
+ if (released)
216
+ return;
217
+ released = true;
218
+ freeLane();
219
+ if (granted) {
220
+ this.active -= 1;
221
+ this.pump();
222
+ }
223
+ else {
224
+ // Never started (a step that failed before reaching the child): the slot it would have taken is not held, and it never will be.
225
+ void slot.then(() => {
226
+ this.active -= 1;
227
+ this.pump();
228
+ });
229
+ }
230
+ };
231
+ return { ready, release };
232
+ }
233
+ /** Runs `run` under a ticket for `key`: once ready, the ticket released when it settles. */
234
+ async run(key, run) {
235
+ const ticket = this.enter(key);
236
+ try {
237
+ await ticket.ready;
238
+ return await run();
239
+ }
240
+ finally {
241
+ ticket.release();
242
+ }
243
+ }
244
+ pump() {
245
+ while (this.active < this.limit && this.waiting.length > 0) {
246
+ this.active += 1;
247
+ this.waiting.shift()();
248
+ }
249
+ }
138
250
  }
139
251
  /** ADK's AgentTool answer: the last event's non-thought text, joined by a newline; parsed under an output schema. */
140
252
  function answerOf(agent, last) {
@@ -148,11 +260,49 @@ function answerOf(agent, last) {
148
260
  .join('\n');
149
261
  return agent.outputSchema ? JSON.parse(text) : text;
150
262
  }
151
- /** The child's session, as ADK's AgentTool gets or creates it: under the subagent's name, from the caller's state the first time. */
152
- async function childSession(name, context, scope) {
263
+ /**
264
+ * The app name a delegated call's child session is filed under (ADR 0111):
265
+ * the agent path. Below a top-level agent, its app name, its name and the
266
+ * subagent's (`<app>/<caller>/<subagent>`); below a delegated subagent,
267
+ * whose own session is already filed under its path, that app name and the
268
+ * subagent's. Agent names are identifiers (lib/syndicateSchema.ts), so
269
+ * never hold a `/`.
270
+ */
271
+ export function childAppName(parent, caller, name) {
272
+ return parent.delegated ? `${parent.appName}/${name}` : `${parent.appName}/${caller}/${name}`;
273
+ }
274
+ /**
275
+ * The session a delegated call ran in before ADR 0111, filed under the
276
+ * subagent's name alone, when it is the one to continue (`continues`): the
277
+ * caller of a stored conversation called the subagent there before, or a
278
+ * pause was found waiting in it. Read only when no session exists under the
279
+ * agent path.
280
+ */
281
+ export async function legacyChild(sessions, key, name, continues) {
282
+ if (!continues)
283
+ return undefined;
284
+ return sessions.get({ appName: name, userId: key.userId, sessionId: key.sessionId });
285
+ }
286
+ /** Whether `caller` called `name` in `events` in a call other than `callId`: the subagent ran for it before. */
287
+ function calledBefore(events, caller, name, callId) {
288
+ return events.some((e) => e.author === caller && (e.content?.parts ?? []).some((p) => p.functionCall?.name === name && p.functionCall.id !== callId));
289
+ }
290
+ /**
291
+ * The child's session, as ADK's AgentTool gets or creates it: filed under
292
+ * the agent path (childAppName), from the caller's state the first time. A
293
+ * session under the old key is continued when the caller called the
294
+ * subagent before, or when `resuming` a pause found there (legacyChild):
295
+ * a syndicate that never called it, another syndicate's caller included,
296
+ * starts its own.
297
+ */
298
+ async function childSession(name, context, scope, resuming) {
153
299
  const { sessions, session: parent } = scope.ctx;
154
- const key = { appName: name, userId: parent.userId, sessionId: parent.id };
155
- return (await sessions.get(key)) ?? (await sessions.create({ ...key, state: { ...scope.stateBase, ...context.stateDelta } }));
300
+ const key = { appName: childAppName({ appName: parent.appName, delegated: scope.ctx.delegated === true }, scope.caller, name), userId: parent.userId, sessionId: parent.id };
301
+ const own = await sessions.get(key);
302
+ if (own)
303
+ return own;
304
+ const legacy = await legacyChild(sessions, key, name, resuming || calledBefore(parent.events, scope.caller, name, context.functionCallId));
305
+ return legacy ?? (await sessions.create({ ...key, state: { ...scope.stateBase, ...context.stateDelta } }));
156
306
  }
157
307
  /** Step 5: an event's state writes, `temp:` keys aside, into the call's state delta. */
158
308
  function recordState(event, context) {
@@ -168,13 +318,22 @@ function recordState(event, context) {
168
318
  * throws, and for an output-schema answer that does not parse.
169
319
  */
170
320
  export function runSubagent(agent, args, context, scope) {
171
- return inTurn(scope.queue, () => runChild(agent, args, context, scope));
321
+ return runChild(agent, { role: 'user', parts: [{ text: args.request }] }, context, scope, false);
322
+ }
323
+ /**
324
+ * Resumes a delegated call the child left paused (ADR 0110): `answer` (the
325
+ * caller's user message parts that answer what waits below the call) is
326
+ * stored as the child's next message and the child's loop runs on, as
327
+ * runSubagent runs it. Resolves as runSubagent does, a SubagentPause again
328
+ * when the child pauses again.
329
+ */
330
+ export function resumeSubagent(agent, answer, context, scope) {
331
+ return runChild(agent, { role: 'user', parts: answer }, context, scope, true);
172
332
  }
173
- async function runChild(agent, args, context, scope) {
333
+ async function runChild(agent, content, context, scope, resuming) {
174
334
  const { ctx, signal } = scope;
175
335
  const { sessions } = ctx;
176
- const content = { role: 'user', parts: [{ text: args.request }] };
177
- const session = await childSession(agent.name, context, scope);
336
+ const session = await childSession(agent.name, context, scope, resuming);
178
337
  if (signal?.aborted)
179
338
  return '';
180
339
  const invocationId = `e-${randomUUID()}`;
@@ -187,24 +346,35 @@ async function runChild(agent, args, context, scope) {
187
346
  invocationId,
188
347
  userContent: content,
189
348
  stream: false,
349
+ // Its own delegations are filed below its path (childAppName, ADR 0111).
350
+ delegated: true,
190
351
  // ADK's AgentTool builds its sub-runner without the reflect-and-retry
191
352
  // plugins, so a subagent's own errors are not retried (ADR 0075).
192
353
  selfCorrection: new SelfCorrection({ model_errors: 0, tool_errors: 0 }),
193
354
  ...(ctx.memory ? { memory: ctx.memory } : {}),
194
- // The parent's grants, pinned to the root's app (ADR 0072). Not its consent step: a subagent's
195
- // pause would end inside the call, as ADK's AgentTool swallows it (ADR 0085).
355
+ // The parent's grants, pinned to the root's app (ADR 0072). Not its consent step: an OAuth
356
+ // consent inside a subagent is not carried to the caller (ADR 0085, ADR 0110).
196
357
  ...(ctx.credentials ? { credentials: ctx.credentials } : {}),
197
358
  ...(ctx.signal ? { signal: ctx.signal } : {}),
198
359
  ...(ctx.adapterFor ? { adapterFor: ctx.adapterFor } : {}),
199
360
  ...(ctx.log ? { log: ctx.log } : {}),
200
361
  });
201
362
  let last;
202
- for await (const event of loop) {
363
+ for (;;) {
364
+ const next = await loop.next();
365
+ if (next.done) {
366
+ // A child that ended waiting on a person leaves the call open (ADR 0110).
367
+ if (next.value.reason === 'paused' && !signal?.aborted)
368
+ return new SubagentPause(next.value.pending ?? []);
369
+ break;
370
+ }
203
371
  // ADK's Runner stops yielding once the turn stopped: the answer is the last event it yielded.
204
- if (signal?.aborted)
372
+ if (signal?.aborted) {
373
+ await loop.return(undefined);
205
374
  break;
206
- recordState(event, context);
207
- last = event;
375
+ }
376
+ recordState(next.value, context);
377
+ last = next.value;
208
378
  }
209
379
  return answerOf(agent, last);
210
380
  }
@@ -215,11 +385,26 @@ async function runChild(agent, args, context, scope) {
215
385
  * yielded event's text, '' for none. Throws what the walk throws.
216
386
  */
217
387
  export function runWorkflowSubagent(workflow, args, context, scope) {
218
- return inTurn(scope.queue, () => walkChild(workflow, args, context, scope));
388
+ return walkChild(workflow, [{ text: args.request }], context, scope, false);
219
389
  }
220
- async function walkChild(workflow, args, context, scope) {
390
+ /** The engine's ask_user tool name (lib/runtime/questions.ts ASK_USER). */
391
+ const ASK_USER_CALL = 'ask_user';
392
+ /**
393
+ * Resumes a delegated call whose nested workflow ended paused (ADR 0111):
394
+ * the graph is walked again on `answer` (the caller's user message parts
395
+ * that answer the walk's open interrupt), which the walk's resume reads as
396
+ * it reads a top-level workflow's. An approval decision goes as it came; an
397
+ * `ask_user` answer goes as ADK's explicit reply to the `adk_request_input`
398
+ * call it answers, under that call's name. Resolves as runWorkflowSubagent
399
+ * does, a SubagentPause again when the walk pauses again.
400
+ */
401
+ export function resumeWorkflowSubagent(workflow, answer, context, scope) {
402
+ const parts = answer.map((p) => (p.functionResponse?.name === ASK_USER_CALL ? { functionResponse: { ...p.functionResponse, name: REQUEST_INPUT_CALL } } : p));
403
+ return walkChild(workflow, parts, context, scope, true);
404
+ }
405
+ async function walkChild(workflow, userParts, context, scope, resuming) {
221
406
  const { ctx, signal } = scope;
222
- const session = await childSession(workflow.name, context, scope);
407
+ const session = await childSession(workflow.name, context, scope, resuming);
223
408
  if (signal?.aborted)
224
409
  return '';
225
410
  const walk = workflow.walk({
@@ -227,7 +412,7 @@ async function walkChild(workflow, args, context, scope) {
227
412
  appName: session.appName,
228
413
  userId: session.userId,
229
414
  sessionId: session.id,
230
- userParts: [{ text: args.request }],
415
+ userParts,
231
416
  adapterFor: ctx.adapterFor ?? resolveAdapter,
232
417
  // As runChild: ADK's AgentTool runs its sub-runner without the reflect-and-retry plugins (ADR 0075).
233
418
  selfCorrection: new SelfCorrection({ model_errors: 0, tool_errors: 0 }),
@@ -237,9 +422,20 @@ async function walkChild(workflow, args, context, scope) {
237
422
  ...(ctx.credentials ? { credentials: ctx.credentials } : {}),
238
423
  });
239
424
  let last;
240
- for await (const event of walk) {
241
- if (signal?.aborted)
425
+ for (;;) {
426
+ const next = await walk.next();
427
+ if (next.done) {
428
+ // A walk that ended waiting on a person leaves the call open (ADR 0111).
429
+ const paused = next.value?.run?.interruptIds ?? [];
430
+ if (paused.length > 0 && !signal?.aborted)
431
+ return new SubagentPause(paused);
432
+ break;
433
+ }
434
+ if (signal?.aborted) {
435
+ await walk.return(undefined);
242
436
  break;
437
+ }
438
+ const event = next.value;
243
439
  if (event.partial)
244
440
  continue;
245
441
  recordState(event, context);
@@ -80,12 +80,38 @@
80
80
  * builds its request. A later step finds the agent's own events last,
81
81
  * so the call runs once.
82
82
  *
83
+ * PAUSES INSIDE DELEGATED SUBAGENTS (WS6-2a, ADR 0110). A subagent runs in
84
+ * its own child session (delegate.ts); an approval request or question it
85
+ * leaves open ends its run paused, and its caller leaves the delegated
86
+ * call open (no response stored) and ends paused too, up to the turn.
87
+ * delegatedPauses finds such a pause from any session by walking down:
88
+ * each call its agent left open (openCalls) leads to the child session
89
+ * under the call's name, whose own open request or question is the pause,
90
+ * or whose own open call leads one level further. The answer travels back
91
+ * down the same way: before each step, the loop resumes the open call
92
+ * whose pause the latest user message answers (agentLoop.ts), and the
93
+ * child reads the same answer from its own session, as it would at the top.
94
+ *
95
+ * NESTED SYNDICATES (WS6-2b, ADR 0111). The child session of a call is the
96
+ * one runSubagent files it under: the agent path (delegate.ts childAppName),
97
+ * else, for a conversation stored before ADR 0111, the subagent's name
98
+ * alone (legacyChild). A nested delegate syndicate is its orchestrator
99
+ * there, so the walk goes on through its open calls. A nested workflow
100
+ * syndicate holds a paused walk there instead: its own pause record (the
101
+ * workflow's event naming every interrupt it left open, authored by the
102
+ * workflow under the call's name) makes an approval request an agent node
103
+ * raised, or an ask_user node's question, the pause, with the node that
104
+ * asked at the end of the path. The walk stops there: a pause inside a
105
+ * workflow node's own delegation does not run (lib/workflow/agentNode.ts).
106
+ *
83
107
  * ADK stays out of this file: an ADK tool an agent still lists is asked
84
108
  * whether it gates through its own checkRequireConfirmation, by shape.
85
109
  */
86
110
  import type { ToolConfirmation } from '../../tools/tool.ts';
87
- import type { TurnFunctionCall } from '../events.ts';
88
- import type { Session } from '../sessions.ts';
111
+ import type { PendingInput } from '../../workflowConfig.ts';
112
+ import type { PendingApproval } from '../approvals.ts';
113
+ import type { TurnEvent, TurnFunctionCall, TurnPart } from '../events.ts';
114
+ import type { Session, SessionService } from '../sessions.ts';
89
115
  import type { NativeAgent } from './request.ts';
90
116
  /** Why a confirmation does not bind to the call it pins: ADK's IntentMismatchError reasons. */
91
117
  export type IntentMismatchReason = 'untrusted_request' | 'malformed_request' | 'unknown_original_call' | 'unregistered_tool' | 'tool_name_mismatch' | 'arguments_mismatch' | 'confirmation_not_required';
@@ -106,6 +132,8 @@ interface Scope {
106
132
  session: Session;
107
133
  invocationId: string;
108
134
  branch?: string;
135
+ /** The session is a delegated subagent's, filed under its agent path (delegate.ts childAppName). */
136
+ delegated?: boolean;
109
137
  }
110
138
  /**
111
139
  * The pinned calls the latest user message approves or refuses, bound to
@@ -126,4 +154,75 @@ export interface GrantedCalls {
126
154
  * AuthPreprocessor, the exchange left to the callback route).
127
155
  */
128
156
  export declare function grantedCalls(agent: NativeAgent, scope: Scope): Promise<GrantedCalls | undefined>;
157
+ /**
158
+ * An approval request or a question waiting inside a delegated call: the
159
+ * open call that leads to it from the session it was found from, and on
160
+ * down, to the agent that asked.
161
+ */
162
+ export interface DelegatedPause {
163
+ /** The agents from the one that made the open call down to the one that asked: `[caller, subagent, …, asker]`. */
164
+ path: string[];
165
+ /** The open subagent calls on that path, by id, the outermost first. */
166
+ callIds: string[];
167
+ /** The approval request, when the asker waits on one; its `path` is set. */
168
+ approval?: PendingApproval;
169
+ /** The question, when the asker waits on one; its `path` is set. */
170
+ question?: PendingInput;
171
+ }
172
+ /** Where a session's delegated calls opened their child sessions: the store, and the caller's user and session ids. */
173
+ export interface DelegationKey {
174
+ sessions: Pick<SessionService, 'get'>;
175
+ userId: string;
176
+ sessionId: string;
177
+ /**
178
+ * The app name of the session whose events are walked: its children are
179
+ * filed below it (delegate.ts childAppName, ADR 0111). Absent, only the
180
+ * children a conversation stored before ADR 0111 holds are found.
181
+ */
182
+ appName?: string;
183
+ /** The session walked is a delegated subagent's, already filed under its agent path. */
184
+ delegated?: boolean;
185
+ }
186
+ /**
187
+ * The calls an agent made that are still open: no later event answers it,
188
+ * and the person has not written since. Only an agent's calls count (a
189
+ * call in a user-authored event is never one), and the engine's own
190
+ * framework calls are left out. `author`, when given, keeps one agent's
191
+ * calls. Latest event first, calls in call order.
192
+ */
193
+ export declare function openCalls(events: readonly TurnEvent[], author?: string): Array<{
194
+ author: string;
195
+ call: TurnFunctionCall;
196
+ }>;
197
+ /**
198
+ * Every pause waiting inside a delegated call still open in `events` (one
199
+ * session's events), latest call first: each open call whose child session
200
+ * holds an open approval request or question, at any depth. `author`, when
201
+ * given, keeps one agent's calls. A call the person has written past is no
202
+ * longer open, and neither is anything waiting below it.
203
+ */
204
+ export declare function delegatedPauses(key: DelegationKey, events: readonly TurnEvent[], author?: string, depth?: number, seen?: ReadonlySet<string>): Promise<DelegatedPause[]>;
205
+ /** The interrupt a delegated pause waits on: the approval request's id, or the question's. */
206
+ export declare function interruptIdOf(pause: DelegatedPause): string | undefined;
207
+ /** The open delegated calls the latest user message resumes, and the ones still waiting after it. */
208
+ export interface ResumedDelegations {
209
+ /** The open calls whose pause the message answers, as the agent made them. */
210
+ calls: TurnFunctionCall[];
211
+ /** By call id: the message's parts that answer what waits below it, to be the child's next message. */
212
+ answers: Map<string, TurnPart[]>;
213
+ /** The open calls whose pause the message does not answer, by id. */
214
+ waiting: string[];
215
+ /** The agent's tools by name, as the loop runs calls against them. */
216
+ tools: Map<string, unknown>;
217
+ }
218
+ /**
219
+ * The delegated calls `agent` left open that the latest user message
220
+ * answers (ADR 0110): the message's approval decisions and question
221
+ * answers (function responses to `adk_request_confirmation` or `ask_user`)
222
+ * matched, by interrupt id, to the pause below each open call. Undefined
223
+ * when the message answers none. The child binds the answer itself: an
224
+ * approval is checked against its pinned call there (approvedCalls), as at
225
+ * the top.
226
+ */
227
+ export declare function resumedDelegations(agent: NativeAgent, scope: Scope, sessions: Pick<SessionService, 'get'>): Promise<ResumedDelegations | undefined>;
129
228
  export {};