melchizedek-agents 1.0.3 → 1.2.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 (172) hide show
  1. package/.env.example +29 -2
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +524 -0
  4. package/DOCUMENTATION.md +178 -43
  5. package/ONBOARDING.md +339 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +6 -2
  8. package/config/agents/README.md +2 -2
  9. package/config/agents/examples/ares.yaml +6 -10
  10. package/config/agents/examples/assistant.yaml +6 -6
  11. package/config/agents/examples/augustin.yaml +9 -15
  12. package/config/agents/examples/cartographers.yaml +6 -6
  13. package/config/agents/examples/council.yaml +7 -7
  14. package/config/agents/examples/critic.yaml +24 -24
  15. package/config/agents/examples/harness.yaml +4 -4
  16. package/config/agents/examples/image_production.yaml +37 -35
  17. package/config/agents/examples/librarian.yaml +4 -4
  18. package/config/agents/examples/patient_advocate.yaml +6 -10
  19. package/config/agents/examples/pipeline.yaml +20 -19
  20. package/config/agents/examples/research.yaml +36 -43
  21. package/config/agents/examples/scribe.yaml +31 -32
  22. package/config/agents/examples/scriptorium.yaml +6 -6
  23. package/config/agents/examples/structured_critic.yaml +82 -0
  24. package/config/agents/examples/style_council.yaml +9 -11
  25. package/config/agents/examples/syndicate.yaml +3 -5
  26. package/config/agents/examples/tutor.yaml +2 -2
  27. package/config/agents/examples/weather.yaml +2 -2
  28. package/config/agents/syndicate.schema.json +859 -13
  29. package/config/agents/syndicateSchema.yaml +122 -73
  30. package/config/agents/templates/README.md +1 -1
  31. package/config/agents/templates/account_memory.yaml +4 -4
  32. package/config/agents/templates/case_desk.yaml +6 -6
  33. package/config/agents/templates/conversational.yaml +2 -2
  34. package/config/agents/templates/draft_review.yaml +6 -6
  35. package/config/agents/templates/intake_extractor.yaml +64 -62
  36. package/config/agents/templates/research_brief.yaml +6 -6
  37. package/config/agents/templates/research_desk.yaml +6 -6
  38. package/config/agents/templates/review_panel.yaml +8 -8
  39. package/config/agents/templates/support_triage.yaml +30 -29
  40. package/config/agents/templates/systems_operator.yaml +24 -5
  41. package/db/migrations/0014_durable_runs.sql +31 -0
  42. package/dist/lib/a2a/app.d.ts +27 -0
  43. package/dist/lib/a2a/app.js +64 -5
  44. package/dist/lib/a2a/executor.d.ts +7 -1
  45. package/dist/lib/a2a/executor.js +43 -5
  46. package/dist/lib/a2a/oauthSetup.d.ts +106 -0
  47. package/dist/lib/a2a/oauthSetup.js +259 -0
  48. package/dist/lib/agentDialect.d.ts +76 -0
  49. package/dist/lib/agentDialect.js +151 -0
  50. package/dist/lib/compile.d.ts +42 -6
  51. package/dist/lib/compile.js +97 -45
  52. package/dist/lib/compileNative.d.ts +11 -3
  53. package/dist/lib/compileNative.js +20 -1
  54. package/dist/lib/dispatch.d.ts +10 -5
  55. package/dist/lib/dispatch.js +10 -5
  56. package/dist/lib/doctor.d.ts +110 -1
  57. package/dist/lib/doctor.js +205 -9
  58. package/dist/lib/loadSyndicate.d.ts +54 -2
  59. package/dist/lib/loadSyndicate.js +22 -2
  60. package/dist/lib/memory/namespace.d.ts +6 -5
  61. package/dist/lib/memory/namespace.js +6 -5
  62. package/dist/lib/memory/providers.js +3 -4
  63. package/dist/lib/models/capabilities.d.ts +25 -1
  64. package/dist/lib/models/capabilities.js +79 -1
  65. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  66. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  67. package/dist/lib/models/claudeAdapter.js +4 -0
  68. package/dist/lib/models/contract.d.ts +5 -2
  69. package/dist/lib/models/gateway.d.ts +2 -1
  70. package/dist/lib/models/gateway.js +4 -2
  71. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  72. package/dist/lib/models/gatewayAdapter.js +8 -0
  73. package/dist/lib/models/geminiAdapter.js +8 -2
  74. package/dist/lib/models/genaiMapping.d.ts +8 -4
  75. package/dist/lib/models/genaiMapping.js +23 -9
  76. package/dist/lib/models/gptAdapter.d.ts +4 -2
  77. package/dist/lib/models/gptAdapter.js +11 -4
  78. package/dist/lib/models/grokAdapter.d.ts +3 -1
  79. package/dist/lib/models/grokAdapter.js +11 -3
  80. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  81. package/dist/lib/models/kimiAdapter.js +3 -3
  82. package/dist/lib/models/reasoning.d.ts +30 -2
  83. package/dist/lib/models/reasoning.js +56 -5
  84. package/dist/lib/observability/tracer.d.ts +8 -1
  85. package/dist/lib/observability/tracer.js +33 -5
  86. package/dist/lib/onboarding.d.ts +134 -0
  87. package/dist/lib/onboarding.js +539 -0
  88. package/dist/lib/runtime/approvals.d.ts +8 -0
  89. package/dist/lib/runtime/approvals.js +4 -3
  90. package/dist/lib/runtime/credentials.d.ts +6 -0
  91. package/dist/lib/runtime/native/agentLoop.d.ts +23 -3
  92. package/dist/lib/runtime/native/agentLoop.js +107 -11
  93. package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
  94. package/dist/lib/runtime/native/checkpoint.js +359 -0
  95. package/dist/lib/runtime/native/delegate.d.ts +222 -18
  96. package/dist/lib/runtime/native/delegate.js +390 -39
  97. package/dist/lib/runtime/native/interrupts.d.ts +184 -2
  98. package/dist/lib/runtime/native/interrupts.js +391 -1
  99. package/dist/lib/runtime/native/request.d.ts +13 -3
  100. package/dist/lib/runtime/native/request.js +28 -4
  101. package/dist/lib/runtime/native/telemetry.js +10 -15
  102. package/dist/lib/runtime/questions.d.ts +7 -4
  103. package/dist/lib/runtime/sessions.d.ts +1 -1
  104. package/dist/lib/runtime/syndicateTurn.d.ts +11 -3
  105. package/dist/lib/runtime/syndicateTurn.js +353 -68
  106. package/dist/lib/runtime/turnControl.d.ts +56 -0
  107. package/dist/lib/runtime/turnControl.js +10 -0
  108. package/dist/lib/session/transcript.d.ts +9 -0
  109. package/dist/lib/session/transcript.js +20 -1
  110. package/dist/lib/skills.d.ts +18 -0
  111. package/dist/lib/skills.js +73 -1
  112. package/dist/lib/storage/postgres/index.d.ts +12 -2
  113. package/dist/lib/storage/postgres/index.js +8 -3
  114. package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
  115. package/dist/lib/storage/postgres/taskQueue.js +29 -5
  116. package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
  117. package/dist/lib/storage/postgres/taskStore.js +74 -8
  118. package/dist/lib/syndicateSchema.d.ts +316 -1
  119. package/dist/lib/syndicateSchema.js +203 -29
  120. package/dist/lib/tools/auth.d.ts +20 -6
  121. package/dist/lib/tools/auth.js +21 -5
  122. package/dist/lib/tools/credentialEnv.d.ts +28 -0
  123. package/dist/lib/tools/credentialEnv.js +52 -0
  124. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  125. package/dist/lib/tools/credentialHosts.js +165 -0
  126. package/dist/lib/tools/credentialStore.js +9 -2
  127. package/dist/lib/tools/credentialUses.d.ts +92 -0
  128. package/dist/lib/tools/credentialUses.js +112 -0
  129. package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
  130. package/dist/lib/tools/mcpToolFactory.js +179 -21
  131. package/dist/lib/tools/oauthConsent.js +8 -1
  132. package/dist/lib/tools/oauthHosts.d.ts +73 -0
  133. package/dist/lib/tools/oauthHosts.js +154 -0
  134. package/dist/lib/tools/oauthTools.d.ts +242 -0
  135. package/dist/lib/tools/oauthTools.js +447 -0
  136. package/dist/lib/tools/openapiTools.d.ts +11 -6
  137. package/dist/lib/tools/openapiTools.js +88 -38
  138. package/dist/lib/tools/taskTools.d.ts +61 -6
  139. package/dist/lib/tools/taskTools.js +145 -21
  140. package/dist/lib/workflow/turn.d.ts +22 -3
  141. package/dist/lib/workflow/turn.js +151 -31
  142. package/dist/lib/workflow.d.ts +14 -7
  143. package/dist/lib/workflow.js +14 -7
  144. package/dist/lib/workflowConfig.d.ts +6 -0
  145. package/dist/scripts/a2a_server.d.ts +36 -0
  146. package/dist/scripts/a2a_server.js +131 -1
  147. package/dist/scripts/assistant_worker.d.ts +12 -0
  148. package/dist/scripts/assistant_worker.js +81 -22
  149. package/dist/scripts/doctor.d.ts +7 -2
  150. package/dist/scripts/doctor.js +9 -4
  151. package/dist/scripts/setup.d.ts +23 -0
  152. package/dist/scripts/setup.js +154 -0
  153. package/dist/scripts/skills_install.d.ts +3 -1
  154. package/dist/scripts/skills_install.js +17 -2
  155. package/dist/scripts/yaml_codemod.d.ts +62 -0
  156. package/dist/scripts/yaml_codemod.js +652 -0
  157. package/package.json +8 -2
  158. package/skills/README.md +11 -2
  159. package/skills/briefs/_shared.md +1 -1
  160. package/skills/briefs/melchizedek-author.md +7 -5
  161. package/skills/briefs/melchizedek-models.md +7 -4
  162. package/skills/briefs/melchizedek.md +1 -0
  163. package/skills/briefs/skills-readme.md +3 -1
  164. package/skills/melchizedek/SKILL.md +1 -0
  165. package/skills/melchizedek-author/SKILL.md +7 -4
  166. package/skills/melchizedek-author/assets/minimal.yaml +13 -13
  167. package/skills/melchizedek-models/SKILL.md +11 -4
  168. package/skills/melchizedek-onboard/SKILL.md +49 -0
  169. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  170. package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
  171. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  172. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -80,12 +80,72 @@
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
+ *
107
+ * NESTED WORKFLOWS AS A ROUTE OR A NODE (ADR 0119). A workflow node that is
108
+ * a nested workflow raises its walk's open requests again on its caller's
109
+ * walk, on one event of its own (lib/workflow/turn.ts), so the caller's
110
+ * walk pauses on the same ids; deepestPause follows such a request down,
111
+ * by id, through each child session filed under entryAppName (its kind in
112
+ * the key: `route:`, `node:`, ADR 0120; else an older key, legacyEntry) to
113
+ * the node that asked. A dispatch route that is one leaves its pause
114
+ * record as the conversation's last event (routePause). A delegated nested
115
+ * workflow's node that is one is followed the same way.
116
+ *
117
+ * NESTED DISPATCH SYNDICATES (ADR 0120). One runs as a turn of its own on
118
+ * its own conversation (the child session of a delegated call, a route or
119
+ * a node), its routes answering there as a turn's routes answer in the
120
+ * turn's conversation. conversationPause finds what it waits on as the
121
+ * turn finds it at the top: a route's pause record, a route's open
122
+ * request, question or consent request, else a pause inside a call a route
123
+ * left open (filed `<conversation>/<route>/<subagent>`). pauseBelow falls
124
+ * to it when the subagent's own checks find nothing; nestedWorkflowPause
125
+ * when the child session is not a walk. The answer goes down the open
126
+ * call, the route's pause record or the node's raised request, and the
127
+ * nested turn reads it as a message at the top.
128
+ *
129
+ * CONSENT AND SKILL SCRIPTS INSIDE DELEGATED SUBAGENTS (ADR 0118). A skill
130
+ * script run asks for approval through the same `adk_request_confirmation`
131
+ * call a gated tool stores, so the walk and the resume above carry it with
132
+ * nothing of its own. An OAuth consent request (`adk_request_credential`)
133
+ * the subagent left open in its child session is a pause too: the walk
134
+ * reports it with the path (pendingConsent), the turn runner stores the
135
+ * grant's answer once the callback has stored the grant, and the open
136
+ * call carries that answer down as it carries an approval decision; the
137
+ * child's own grantedCalls binds it and runs its paused call again.
138
+ *
83
139
  * ADK stays out of this file: an ADK tool an agent still lists is asked
84
140
  * whether it gates through its own checkRequireConfirmation, by shape.
85
141
  */
86
142
  import type { ToolConfirmation } from '../../tools/tool.ts';
87
- import type { TurnFunctionCall } from '../events.ts';
88
- import type { Session } from '../sessions.ts';
143
+ import type { PendingInput } from '../../workflowConfig.ts';
144
+ import type { PendingApproval } from '../approvals.ts';
145
+ import type { PendingConsent } from '../credentials.ts';
146
+ import type { TurnEvent, TurnFunctionCall, TurnPart } from '../events.ts';
147
+ import type { Session, SessionService } from '../sessions.ts';
148
+ import type { EntryKind } from './delegate.ts';
89
149
  import type { NativeAgent } from './request.ts';
90
150
  /** Why a confirmation does not bind to the call it pins: ADK's IntentMismatchError reasons. */
91
151
  export type IntentMismatchReason = 'untrusted_request' | 'malformed_request' | 'unknown_original_call' | 'unregistered_tool' | 'tool_name_mismatch' | 'arguments_mismatch' | 'confirmation_not_required';
@@ -106,6 +166,8 @@ interface Scope {
106
166
  session: Session;
107
167
  invocationId: string;
108
168
  branch?: string;
169
+ /** The session is a delegated subagent's, filed under its agent path (delegate.ts childAppName). */
170
+ delegated?: boolean;
109
171
  }
110
172
  /**
111
173
  * The pinned calls the latest user message approves or refuses, bound to
@@ -126,4 +188,124 @@ export interface GrantedCalls {
126
188
  * AuthPreprocessor, the exchange left to the callback route).
127
189
  */
128
190
  export declare function grantedCalls(agent: NativeAgent, scope: Scope): Promise<GrantedCalls | undefined>;
191
+ /**
192
+ * An approval request or a question waiting inside a delegated call: the
193
+ * open call that leads to it from the session it was found from, and on
194
+ * down, to the agent that asked.
195
+ */
196
+ export interface DelegatedPause {
197
+ /** The agents from the one that made the open call down to the one that asked: `[caller, subagent, …, asker]`. */
198
+ path: string[];
199
+ /** The open subagent calls on that path, by id, the outermost first. */
200
+ callIds: string[];
201
+ /** The approval request, when the asker waits on one; its `path` is set. */
202
+ approval?: PendingApproval;
203
+ /** The question, when the asker waits on one; its `path` is set. */
204
+ question?: PendingInput;
205
+ /** The OAuth consent request, when the asker waits for a grant (ADR 0118); its `path` is set. */
206
+ consent?: PendingConsent;
207
+ }
208
+ /** Where a session's delegated calls opened their child sessions: the store, and the caller's user and session ids. */
209
+ export interface DelegationKey {
210
+ sessions: Pick<SessionService, 'get'>;
211
+ userId: string;
212
+ sessionId: string;
213
+ /**
214
+ * The app name of the session whose events are walked: its children are
215
+ * filed below it (delegate.ts childAppName, ADR 0111). Absent, only the
216
+ * children a conversation stored before ADR 0111 holds are found.
217
+ */
218
+ appName?: string;
219
+ /** The session walked is a delegated subagent's, already filed under its agent path. */
220
+ delegated?: boolean;
221
+ }
222
+ /**
223
+ * The calls an agent made that are still open: no later event answers it,
224
+ * and the person has not written since. Only an agent's calls count (a
225
+ * call in a user-authored event is never one), and the engine's own
226
+ * framework calls are left out. `author`, when given, keeps one agent's
227
+ * calls. Latest event first, calls in call order.
228
+ */
229
+ export declare function openCalls(events: readonly TurnEvent[], author?: string): Array<{
230
+ author: string;
231
+ call: TurnFunctionCall;
232
+ }>;
233
+ /** A pause a nested workflow's walk holds: the path from the workflow down to the node that asked, and what it asks. */
234
+ export interface NestedWorkflowPause {
235
+ /** From the workflow (the route, the node, the subagent) down to the node that asked: `[workflow, …, node]`. */
236
+ path: string[];
237
+ /** The approval request, its `agent` the node that asked and its `path` set. */
238
+ approval?: PendingApproval;
239
+ /** The question, its `node` the node that asked and its `path` set. */
240
+ question?: PendingInput;
241
+ /** The OAuth consent request, inside a nested dispatch syndicate's route (ADR 0120); its `path` set. */
242
+ consent?: PendingConsent;
243
+ }
244
+ /**
245
+ * The pause a nested workflow run as a dispatch route or a workflow node
246
+ * holds (ADR 0119): `name`'s child session below the session filed under
247
+ * `key.appName` (delegate.ts entryAppName), else the one filed under its
248
+ * name alone (legacyChild), and the paused walk there, followed down to the
249
+ * node that asked. With `id`, only that interrupt counts: a request a node
250
+ * raised again on its caller's walk names the one its own walk waits on.
251
+ */
252
+ export declare function nestedWorkflowPause(key: DelegationKey & {
253
+ appName: string;
254
+ }, name: string, kind: EntryKind, id?: string, depth?: number): Promise<NestedWorkflowPause | undefined>;
255
+ /**
256
+ * A dispatch route that is a nested workflow, paused (ADR 0119): the
257
+ * conversation's last event is the route's pause record (authored by the
258
+ * route at its own path, no content, the walk's open interrupts in
259
+ * `longRunningToolIds`; lib/runtime/syndicateTurn.ts stores it), and the
260
+ * route's child session holds the paused walk under one of those ids.
261
+ * Anything stored after the record closes it. `routes`, when given, are the
262
+ * names a record may carry.
263
+ */
264
+ export declare function routePause(key: DelegationKey & {
265
+ appName: string;
266
+ }, events: readonly TurnEvent[], routes?: ReadonlySet<string>, depth?: number): Promise<(NestedWorkflowPause & {
267
+ route: string;
268
+ }) | undefined>;
269
+ /**
270
+ * An approval request or question found in a workflow's own session, raised
271
+ * by a node that is a nested workflow (lib/workflow/turn.ts raises its
272
+ * walk's requests again on the node's event, ADR 0119): the one its walk
273
+ * waits on, with the path from the node down to the one that asked. Any
274
+ * other request comes back as it is.
275
+ */
276
+ export declare function deepestPause<T extends PendingApproval | PendingInput>(key: DelegationKey & {
277
+ appName: string;
278
+ }, pending: T): Promise<T>;
279
+ /**
280
+ * Every pause waiting inside a delegated call still open in `events` (one
281
+ * session's events), latest call first: each open call whose child session
282
+ * holds an open approval request or question, at any depth. `author`, when
283
+ * given, keeps one agent's calls. A call the person has written past is no
284
+ * longer open, and neither is anything waiting below it.
285
+ */
286
+ export declare function delegatedPauses(key: DelegationKey, events: readonly TurnEvent[], author?: string, depth?: number, seen?: ReadonlySet<string>): Promise<DelegatedPause[]>;
287
+ /** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
288
+ export declare function interruptIdOf(pause: DelegatedPause): string | undefined;
289
+ /** The open delegated calls the latest user message resumes, and the ones still waiting after it. */
290
+ export interface ResumedDelegations {
291
+ /** The open calls whose pause the message answers, as the agent made them. */
292
+ calls: TurnFunctionCall[];
293
+ /** By call id: the message's parts that answer what waits below it, to be the child's next message. */
294
+ answers: Map<string, TurnPart[]>;
295
+ /** The open calls whose pause the message does not answer, by id. */
296
+ waiting: string[];
297
+ /** The agent's tools by name, as the loop runs calls against them. */
298
+ tools: Map<string, unknown>;
299
+ }
300
+ /**
301
+ * The delegated calls `agent` left open that the latest user message
302
+ * answers (ADR 0110): the message's approval decisions and question
303
+ * answers (function responses to `adk_request_confirmation` or `ask_user`)
304
+ * and grants (to `adk_request_credential`, ADR 0118) matched, by interrupt
305
+ * id, to the pause below each open call. Undefined when the message answers
306
+ * none. The child binds the answer itself: an approval is checked against
307
+ * its pinned call there (approvedCalls), a grant against its request
308
+ * (grantedCalls), as at the top.
309
+ */
310
+ export declare function resumedDelegations(agent: NativeAgent, scope: Scope, sessions: Pick<SessionService, 'get'>): Promise<ResumedDelegations | undefined>;
129
311
  export {};
@@ -80,14 +80,75 @@
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
+ *
107
+ * NESTED WORKFLOWS AS A ROUTE OR A NODE (ADR 0119). A workflow node that is
108
+ * a nested workflow raises its walk's open requests again on its caller's
109
+ * walk, on one event of its own (lib/workflow/turn.ts), so the caller's
110
+ * walk pauses on the same ids; deepestPause follows such a request down,
111
+ * by id, through each child session filed under entryAppName (its kind in
112
+ * the key: `route:`, `node:`, ADR 0120; else an older key, legacyEntry) to
113
+ * the node that asked. A dispatch route that is one leaves its pause
114
+ * record as the conversation's last event (routePause). A delegated nested
115
+ * workflow's node that is one is followed the same way.
116
+ *
117
+ * NESTED DISPATCH SYNDICATES (ADR 0120). One runs as a turn of its own on
118
+ * its own conversation (the child session of a delegated call, a route or
119
+ * a node), its routes answering there as a turn's routes answer in the
120
+ * turn's conversation. conversationPause finds what it waits on as the
121
+ * turn finds it at the top: a route's pause record, a route's open
122
+ * request, question or consent request, else a pause inside a call a route
123
+ * left open (filed `<conversation>/<route>/<subagent>`). pauseBelow falls
124
+ * to it when the subagent's own checks find nothing; nestedWorkflowPause
125
+ * when the child session is not a walk. The answer goes down the open
126
+ * call, the route's pause record or the node's raised request, and the
127
+ * nested turn reads it as a message at the top.
128
+ *
129
+ * CONSENT AND SKILL SCRIPTS INSIDE DELEGATED SUBAGENTS (ADR 0118). A skill
130
+ * script run asks for approval through the same `adk_request_confirmation`
131
+ * call a gated tool stores, so the walk and the resume above carry it with
132
+ * nothing of its own. An OAuth consent request (`adk_request_credential`)
133
+ * the subagent left open in its child session is a pause too: the walk
134
+ * reports it with the path (pendingConsent), the turn runner stores the
135
+ * grant's answer once the callback has stored the grant, and the open
136
+ * call carries that answer down as it carries an approval decision; the
137
+ * child's own grantedCalls binds it and runs its paused call again.
138
+ *
83
139
  * ADK stays out of this file: an ADK tool an agent still lists is asked
84
140
  * whether it gates through its own checkRequireConfirmation, by shape.
85
141
  */
86
142
  import { isDeepStrictEqual } from 'node:util';
87
143
  import { nativeToolOf } from '../../models/schemaNormalize.js';
88
144
  import { instructionToolOf, isTool, toolOf } from '../../tools/tool.js';
145
+ import { inputRequestFrom } from '../../workflowConfig.js';
146
+ import { approvalRequestOf, pendingApproval } from '../approvals.js';
147
+ import { pendingConsent } from '../credentials.js';
89
148
  import { getFunctionCalls, getFunctionResponses } from '../events.js';
90
- import { REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, isSegmentPrefix } from './history.js';
149
+ import { ASK_USER, pendingQuestion } from '../questions.js';
150
+ import { childAppName, entryAppName, legacyChild, legacyEntry } from './delegate.js';
151
+ import { REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, REQUEST_INPUT_CALL, isSegmentPrefix } from './history.js';
91
152
  import { isToolset } from './request.js';
92
153
  /** A confirmation that does not bind to its pinned call: ADK's IntentMismatchError, by name and text. */
93
154
  export class IntentMismatchError extends Error {
@@ -308,3 +369,332 @@ export async function grantedCalls(agent, scope) {
308
369
  }
309
370
  return undefined;
310
371
  }
372
+ /** The deepest a chain of delegated pauses is followed: nested syndicates stop at 16 levels (lib/compile.ts). */
373
+ const MAX_DELEGATION_DEPTH = 16;
374
+ /** The engine's own long-running calls: never a delegation. */
375
+ const FRAMEWORK_CALLS = new Set([REQUEST_CONFIRMATION_CALL, REQUEST_CREDENTIAL_CALL, ASK_USER]);
376
+ const hasUserText = (e) => e.author === 'user' && (e.content?.parts ?? []).some((p) => typeof p.text === 'string' && p.text.trim() !== '' && !p.thought);
377
+ /**
378
+ * The calls an agent made that are still open: no later event answers it,
379
+ * and the person has not written since. Only an agent's calls count (a
380
+ * call in a user-authored event is never one), and the engine's own
381
+ * framework calls are left out. `author`, when given, keeps one agent's
382
+ * calls. Latest event first, calls in call order.
383
+ */
384
+ export function openCalls(events, author) {
385
+ const answered = new Set();
386
+ const open = [];
387
+ for (let i = events.length - 1; i >= 0; i--) {
388
+ const event = events[i];
389
+ if (hasUserText(event))
390
+ break;
391
+ for (const r of getFunctionResponses(event))
392
+ if (r.id)
393
+ answered.add(r.id);
394
+ if (!event.author || event.author === 'user' || (author !== undefined && event.author !== author))
395
+ continue;
396
+ for (const call of getFunctionCalls(event)) {
397
+ if (!call.id || !call.name || FRAMEWORK_CALLS.has(call.name) || answered.has(call.id))
398
+ continue;
399
+ open.push({ author: event.author, call });
400
+ }
401
+ }
402
+ return open;
403
+ }
404
+ /**
405
+ * What waits inside the delegated call `call` that `caller` left open: the
406
+ * child session under the call's name (where runSubagent runs it), and its
407
+ * own open approval request or question, else a pause inside a call it left
408
+ * open, one level down. Only the subagent's own requests count: a request
409
+ * or question authored by anyone else is none.
410
+ */
411
+ async function pauseBelow(key, caller, call, depth, seen) {
412
+ const name = call.name;
413
+ const id = call.id;
414
+ if (depth >= MAX_DELEGATION_DEPTH || seen.has(name))
415
+ return undefined;
416
+ // Where runSubagent filed the call (ADR 0111), else where it filed it before.
417
+ const own = key.appName === undefined ? undefined : await key.sessions.get({ appName: childAppName({ appName: key.appName, delegated: key.delegated === true }, caller, name), userId: key.userId, sessionId: key.sessionId });
418
+ const child = own ?? (await legacyChild(key.sessions, key, name, true));
419
+ if (!child)
420
+ return undefined;
421
+ const here = [caller, name];
422
+ const walked = await workflowPauseIn(key, child, name);
423
+ if (walked) {
424
+ const path = [caller, ...walked.path];
425
+ return {
426
+ path,
427
+ callIds: [id],
428
+ ...(walked.approval ? { approval: { ...walked.approval, path } } : {}),
429
+ ...(walked.question ? { question: { ...walked.question, path } } : {}),
430
+ };
431
+ }
432
+ const approval = pendingApproval(child.events);
433
+ if (approval && approval.agent === name)
434
+ return { path: here, callIds: [id], approval: { ...approval, path: here } };
435
+ const question = pendingQuestion(child.events);
436
+ if (question && question.node === name)
437
+ return { path: here, callIds: [id], question: { ...question, path: here } };
438
+ // Only the subagent's own request counts (pendingConsent already skips a user-authored one).
439
+ const consent = pendingConsent(child.events);
440
+ if (consent && consent.agent === name)
441
+ return { path: here, callIds: [id], consent: { ...consent, path: here } };
442
+ const [first] = await delegatedPauses({ ...key, appName: child.appName, delegated: true }, child.events, name, depth + 1, new Set([...seen, name]));
443
+ if (first) {
444
+ const path = [caller, ...first.path];
445
+ return {
446
+ path,
447
+ callIds: [id, ...first.callIds],
448
+ ...(first.approval ? { approval: { ...first.approval, path } } : {}),
449
+ ...(first.question ? { question: { ...first.question, path } } : {}),
450
+ ...(first.consent ? { consent: { ...first.consent, path } } : {}),
451
+ };
452
+ }
453
+ // A nested dispatch syndicate delegated to (ADR 0120): its routes answer in the child session, as routes answer in a turn's.
454
+ const routed = await conversationPause(key, child, name, undefined, depth + 1, new Set([...seen, name]));
455
+ if (!routed)
456
+ return undefined;
457
+ const path = [caller, ...routed.path];
458
+ return {
459
+ path,
460
+ callIds: [id],
461
+ ...(routed.approval ? { approval: { ...routed.approval, path } } : {}),
462
+ ...(routed.question ? { question: { ...routed.question, path } } : {}),
463
+ ...(routed.consent ? { consent: { ...routed.consent, path } } : {}),
464
+ };
465
+ }
466
+ /** The id a pause found below waits on. */
467
+ const idOf = (pause) => pause.approval?.id ?? pause.question?.id ?? pause.consent?.id;
468
+ /**
469
+ * What a nested dispatch syndicate's conversation (`child`, filed for the
470
+ * entry `name`) waits on (ADR 0120): what its own turn would report at the
471
+ * top. Its routes answer there as a turn's routes answer in the turn's
472
+ * conversation, so it is found as the turn finds it: a route's pause record
473
+ * (a nested workflow or a nested dispatch syndicate run as a route, followed
474
+ * down), a route's open approval request, question or consent request, else
475
+ * a pause inside a call a route left open, its child filed under the
476
+ * route's name (`<child>/<route>/<subagent>`, as a turn's route files its
477
+ * own). The path runs from `name` down to the agent that asked. With `id`,
478
+ * only that request counts. Calls `name` made itself are not a route's.
479
+ */
480
+ async function conversationPause(key, child, name, id, depth, seen = new Set()) {
481
+ if (depth >= MAX_DELEGATION_DEPTH)
482
+ return undefined;
483
+ const at = { ...key, appName: child.appName, delegated: false };
484
+ const wanted = (found) => !!found && (id === undefined || found === id);
485
+ const down = (path) => [name, ...path];
486
+ const routed = await routePause(at, child.events, undefined, depth + 1);
487
+ if (routed && wanted(idOf(routed))) {
488
+ const path = down(routed.path);
489
+ return {
490
+ path,
491
+ ...(routed.approval ? { approval: { ...routed.approval, path } } : {}),
492
+ ...(routed.question ? { question: { ...routed.question, path } } : {}),
493
+ ...(routed.consent ? { consent: { ...routed.consent, path } } : {}),
494
+ };
495
+ }
496
+ const approval = pendingApproval(child.events);
497
+ if (approval && approval.agent && approval.agent !== 'user' && approval.agent !== name && wanted(approval.id)) {
498
+ const path = down([approval.agent]);
499
+ return { path, approval: { ...approval, path } };
500
+ }
501
+ const question = pendingQuestion(child.events);
502
+ if (question && question.node && question.node !== 'user' && question.node !== name && wanted(question.id)) {
503
+ const path = down([question.node]);
504
+ return { path, question: { ...question, path } };
505
+ }
506
+ const consent = pendingConsent(child.events);
507
+ if (consent && consent.agent && consent.agent !== name && wanted(consent.id)) {
508
+ const path = down([consent.agent]);
509
+ return { path, consent: { ...consent, path } };
510
+ }
511
+ const below = await delegatedPauses(at, child.events, undefined, depth + 1, seen);
512
+ const first = below.find((p) => p.path[0] !== name && wanted(interruptIdOf(p)));
513
+ if (!first)
514
+ return undefined;
515
+ const path = down(first.path);
516
+ return {
517
+ path,
518
+ ...(first.approval ? { approval: { ...first.approval, path } } : {}),
519
+ ...(first.question ? { question: { ...first.question, path } } : {}),
520
+ ...(first.consent ? { consent: { ...first.consent, path } } : {}),
521
+ };
522
+ }
523
+ /** A walk's session: its node events carry a dotted node path (`<workflow>.<node>`). */
524
+ const isWalk = (session) => session.events.some((e) => typeof e.nodeInfo?.path === 'string' && e.nodeInfo.path.includes('.'));
525
+ /**
526
+ * The pause a nested workflow's walk holds in its child session (ADR 0111):
527
+ * the workflow's own pause record, the last event, authored by the
528
+ * workflow (`name`) at its own path with no content, names the open
529
+ * interrupts; the approval request or ask_user question among them is the
530
+ * pause, and the node that raised it the asker. With `id`, only that
531
+ * interrupt counts. Undefined when the session holds no paused walk of
532
+ * `name`'s. A node that is itself a nested workflow raised its walk's
533
+ * requests again on its own event (lib/workflow/turn.ts, ADR 0119), so it
534
+ * is the asker here, and workflowPauseIn goes on down.
535
+ */
536
+ function workflowPause(events, name, id) {
537
+ const record = events[events.length - 1];
538
+ if (!record || record.author !== name || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== name)
539
+ return undefined;
540
+ const open = new Set((record.longRunningToolIds ?? []).filter((x) => id === undefined || x === id));
541
+ if (open.size === 0)
542
+ return undefined;
543
+ // The latest open request the record names, an approval first: the walk stores node inputs as user turns, so a later one may follow it.
544
+ for (const kind of [REQUEST_CONFIRMATION_CALL, REQUEST_INPUT_CALL]) {
545
+ for (let i = events.length - 2; i >= 0; i--) {
546
+ const event = events[i];
547
+ if (!event.author || event.author === 'user')
548
+ continue;
549
+ for (const call of getFunctionCalls(event)) {
550
+ if (call.name !== kind || !call.id || !open.has(call.id))
551
+ continue;
552
+ if (kind === REQUEST_CONFIRMATION_CALL)
553
+ return { asker: event.author, approval: approvalRequestOf(event, call) };
554
+ const question = inputRequestFrom(event.author, call);
555
+ if (question && question.id === call.id)
556
+ return { asker: event.author, question };
557
+ }
558
+ }
559
+ }
560
+ return undefined;
561
+ }
562
+ /**
563
+ * The pause the walk of `name` holds in `child` (its child session), with
564
+ * `id` the only interrupt that counts when given: the node that asked, or,
565
+ * when that node is itself a nested workflow (ADR 0119), the pause its own
566
+ * child session holds under the same id, one level further down.
567
+ */
568
+ async function workflowPauseIn(key, child, name, id, depth = 0) {
569
+ const walked = workflowPause(child.events, name, id);
570
+ if (!walked)
571
+ return undefined;
572
+ const found = (walked.approval?.id ?? walked.question?.id);
573
+ const deeper = await nestedWorkflowPause({ ...key, appName: child.appName }, walked.asker, 'node', found, depth + 1);
574
+ const path = deeper ? [name, ...deeper.path] : [name, walked.asker];
575
+ const approval = deeper ? deeper.approval : walked.approval;
576
+ const question = deeper ? deeper.question : walked.question;
577
+ return { path, ...(approval ? { approval: { ...approval, path } } : {}), ...(question ? { question: { ...question, path } } : {}) };
578
+ }
579
+ /**
580
+ * The pause a nested workflow run as a dispatch route or a workflow node
581
+ * holds (ADR 0119): `name`'s child session below the session filed under
582
+ * `key.appName` (delegate.ts entryAppName), else the one filed under its
583
+ * name alone (legacyChild), and the paused walk there, followed down to the
584
+ * node that asked. With `id`, only that interrupt counts: a request a node
585
+ * raised again on its caller's walk names the one its own walk waits on.
586
+ */
587
+ export async function nestedWorkflowPause(key, name, kind, id, depth = 0) {
588
+ if (depth >= MAX_DELEGATION_DEPTH)
589
+ return undefined;
590
+ const own = await key.sessions.get({ appName: entryAppName(key.appName, name, kind), userId: key.userId, sessionId: key.sessionId });
591
+ const child = own ?? (await legacyEntry(key.sessions, key, name, true));
592
+ if (!child)
593
+ return undefined;
594
+ const walked = await workflowPauseIn(key, child, name, id, depth);
595
+ if (walked || isWalk(child))
596
+ return walked;
597
+ // A nested dispatch syndicate run as the route or the node (ADR 0120): its own conversation, as its turn reads it.
598
+ return conversationPause(key, child, name, id, depth + 1);
599
+ }
600
+ /**
601
+ * A dispatch route that is a nested workflow, paused (ADR 0119): the
602
+ * conversation's last event is the route's pause record (authored by the
603
+ * route at its own path, no content, the walk's open interrupts in
604
+ * `longRunningToolIds`; lib/runtime/syndicateTurn.ts stores it), and the
605
+ * route's child session holds the paused walk under one of those ids.
606
+ * Anything stored after the record closes it. `routes`, when given, are the
607
+ * names a record may carry.
608
+ */
609
+ export async function routePause(key, events, routes, depth = 0) {
610
+ const record = events[events.length - 1];
611
+ const route = record?.author;
612
+ if (!record || !route || route === 'user' || (record.content?.parts ?? []).length > 0 || record.nodeInfo?.path !== route)
613
+ return undefined;
614
+ if (routes && !routes.has(route))
615
+ return undefined;
616
+ for (const id of record.longRunningToolIds ?? []) {
617
+ const pause = await nestedWorkflowPause(key, route, 'route', id, depth);
618
+ if (pause)
619
+ return { ...pause, route };
620
+ }
621
+ return undefined;
622
+ }
623
+ /**
624
+ * An approval request or question found in a workflow's own session, raised
625
+ * by a node that is a nested workflow (lib/workflow/turn.ts raises its
626
+ * walk's requests again on the node's event, ADR 0119): the one its walk
627
+ * waits on, with the path from the node down to the one that asked. Any
628
+ * other request comes back as it is.
629
+ */
630
+ export async function deepestPause(key, pending) {
631
+ const raisedBy = 'agent' in pending ? pending.agent : pending.node;
632
+ if (!raisedBy || pending.path)
633
+ return pending;
634
+ const below = await nestedWorkflowPause(key, raisedBy, 'node', pending.id);
635
+ const found = 'agent' in pending ? below?.approval : below?.question;
636
+ return found ?? pending;
637
+ }
638
+ /**
639
+ * Every pause waiting inside a delegated call still open in `events` (one
640
+ * session's events), latest call first: each open call whose child session
641
+ * holds an open approval request or question, at any depth. `author`, when
642
+ * given, keeps one agent's calls. A call the person has written past is no
643
+ * longer open, and neither is anything waiting below it.
644
+ */
645
+ export async function delegatedPauses(key, events, author, depth = 0, seen = new Set()) {
646
+ const out = [];
647
+ for (const { author: caller, call } of openCalls(events, author)) {
648
+ const pause = await pauseBelow(key, caller, call, depth, seen);
649
+ if (pause)
650
+ out.push(pause);
651
+ }
652
+ return out;
653
+ }
654
+ /** The interrupt a delegated pause waits on: the approval request's id, the question's, or the consent request's. */
655
+ export function interruptIdOf(pause) {
656
+ return pause.approval?.id ?? pause.question?.id ?? pause.consent?.id;
657
+ }
658
+ /**
659
+ * The delegated calls `agent` left open that the latest user message
660
+ * answers (ADR 0110): the message's approval decisions and question
661
+ * answers (function responses to `adk_request_confirmation` or `ask_user`)
662
+ * and grants (to `adk_request_credential`, ADR 0118) matched, by interrupt
663
+ * id, to the pause below each open call. Undefined when the message answers
664
+ * none. The child binds the answer itself: an approval is checked against
665
+ * its pinned call there (approvedCalls), a grant against its request
666
+ * (grantedCalls), as at the top.
667
+ */
668
+ export async function resumedDelegations(agent, scope, sessions) {
669
+ const events = scope.session.events;
670
+ let latest;
671
+ for (let i = events.length - 1; i >= 0 && !latest; i--)
672
+ if (events[i]?.author === 'user')
673
+ latest = events[i];
674
+ if (!latest)
675
+ return undefined;
676
+ const answerParts = (latest.content?.parts ?? []).filter((p) => {
677
+ const r = p.functionResponse;
678
+ return !!r?.id && (r.name === REQUEST_CONFIRMATION_CALL || r.name === ASK_USER || r.name === REQUEST_CREDENTIAL_CALL);
679
+ });
680
+ if (answerParts.length === 0)
681
+ return undefined;
682
+ const pauses = await delegatedPauses({ sessions, userId: scope.session.userId, sessionId: scope.session.id, appName: scope.session.appName, delegated: scope.delegated === true }, events, agent.name);
683
+ if (pauses.length === 0)
684
+ return undefined;
685
+ const open = new Map(openCalls(events, agent.name).map(({ call }) => [call.id, call]));
686
+ const answers = new Map();
687
+ const waiting = [];
688
+ for (const pause of pauses) {
689
+ const callId = pause.callIds[0];
690
+ const parts = answerParts.filter((p) => p.functionResponse?.id === interruptIdOf(pause));
691
+ if (parts.length)
692
+ answers.set(callId, [...(answers.get(callId) ?? []), ...parts]);
693
+ else
694
+ waiting.push(callId);
695
+ }
696
+ if (answers.size === 0)
697
+ return undefined;
698
+ const calls = [...answers.keys()].map((id) => open.get(id)).filter((c) => !!c);
699
+ return { calls, answers, waiting: waiting.filter((id) => !answers.has(id)), tools: await toolsOf(agent, scope) };
700
+ }
@@ -13,8 +13,12 @@
13
13
  *
14
14
  * THE REQUEST, in the order ADK built it:
15
15
  * 1. The agent's generateContentConfig, and its output schema when it has
16
- * no tools (or the model takes a schema beside tools: Gemini 2 and
17
- * later on Vertex AI).
16
+ * no tools, or when every model the step may call (its own and its
17
+ * fallback_model) takes a schema beside tools in one request: Claude
18
+ * from Opus 4.8, Sonnet 5 and Haiku 5.5 on, OpenAI, Gemini 2 and later
19
+ * on Vertex AI (outputSchemaBesideTools, lib/models/capabilities.ts,
20
+ * ADR 0109). That is how an orchestrator that delegates answers in a
21
+ * schema without a set_model_response call.
18
22
  * 2. The system prompt, each piece joined to the last by a blank line:
19
23
  * - the identity lines ("You are an agent. Your internal name is …"),
20
24
  * unless the agent may transfer to no one (an output schema rules
@@ -112,6 +116,8 @@ export interface NativeAgent {
112
116
  fallbackModel?: string;
113
117
  /** `context:` (ADR 0033): compact the history into a summary past a token threshold. The loop runs it before each step (compaction.ts). */
114
118
  context?: ContextConfig;
119
+ /** The syndicate's `max_concurrency` (ADR 0116): delegated calls one step runs at once. Default DEFAULT_MAX_CONCURRENCY (delegate.ts). */
120
+ maxConcurrency?: number;
115
121
  }
116
122
  /** What an instruction function reads. */
117
123
  export interface InstructionContext {
@@ -166,7 +172,11 @@ export interface BuiltRequest {
166
172
  export declare const SET_MODEL_RESPONSE = "set_model_response";
167
173
  /** set_model_response as an own Tool: its answer is the arguments, as JSON text, and it ends the step. */
168
174
  export declare function setModelResponseTool(outputSchema: Record<string, unknown>): Tool;
169
- /** Whether the model takes an output schema beside tools in one request (ADK: Vertex AI, Gemini 2 and later). */
175
+ /**
176
+ * ADK's rule for whether the model takes an output schema beside tools in one
177
+ * request (Vertex AI, Gemini 2 and later). `mode: task` keeps it as it is,
178
+ * for the instruction line ADK's processor writes there.
179
+ */
170
180
  export declare function outputSchemaWithTools(model: string): boolean;
171
181
  /**
172
182
  * What ADK's runLlmAgentAsNode puts in a node agent's invocation context