melchizedek-agents 0.19.0 → 0.20.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 (106) hide show
  1. package/CHANGELOG.md +252 -76
  2. package/DOCUMENTATION.md +44 -15
  3. package/QUICKSTART.md +9 -6
  4. package/README.md +6 -9
  5. package/config/agents/syndicateSchema.yaml +3 -0
  6. package/dist/lib/adkPeer.d.ts +63 -0
  7. package/dist/lib/adkPeer.js +240 -0
  8. package/dist/lib/compile.d.ts +40 -2
  9. package/dist/lib/compile.js +80 -12
  10. package/dist/lib/compileAdk.d.ts +7 -4
  11. package/dist/lib/compileAdk.js +22 -12
  12. package/dist/lib/compileNative.d.ts +28 -2
  13. package/dist/lib/compileNative.js +60 -4
  14. package/dist/lib/doctor.d.ts +28 -1
  15. package/dist/lib/doctor.js +63 -2
  16. package/dist/lib/index.d.ts +4 -0
  17. package/dist/lib/index.js +5 -0
  18. package/dist/lib/models/adapterResolver.d.ts +13 -11
  19. package/dist/lib/models/adapterResolver.js +11 -10
  20. package/dist/lib/models/adkGeminiAdapter.js +2 -0
  21. package/dist/lib/models/adkShim.d.ts +1 -1
  22. package/dist/lib/models/adkShim.js +1 -1
  23. package/dist/lib/models/capabilities.js +13 -10
  24. package/dist/lib/models/claudeLlm.js +2 -2
  25. package/dist/lib/models/fallback.d.ts +1 -1
  26. package/dist/lib/models/fallback.js +1 -1
  27. package/dist/lib/models/gatewayLlm.js +2 -2
  28. package/dist/lib/models/geminiAdapter.d.ts +6 -4
  29. package/dist/lib/models/geminiAdapter.js +28 -5
  30. package/dist/lib/models/geminiState.d.ts +21 -0
  31. package/dist/lib/models/geminiState.js +26 -0
  32. package/dist/lib/models/genaiMapping.d.ts +24 -4
  33. package/dist/lib/models/genaiMapping.js +109 -6
  34. package/dist/lib/models/gptLlm.js +2 -2
  35. package/dist/lib/models/grokLlm.js +2 -2
  36. package/dist/lib/models/kimiLlm.js +2 -2
  37. package/dist/lib/models/ollamaLlm.js +2 -2
  38. package/dist/lib/models/registry.d.ts +5 -1
  39. package/dist/lib/models/registry.js +17 -12
  40. package/dist/lib/models/tracedGemini.d.ts +1 -1
  41. package/dist/lib/models/tracedGemini.js +1 -1
  42. package/dist/lib/runtime/adkSessionBridge.d.ts +1 -1
  43. package/dist/lib/runtime/adkSessionBridge.js +1 -1
  44. package/dist/lib/runtime/approvals.d.ts +5 -0
  45. package/dist/lib/runtime/approvals.js +39 -8
  46. package/dist/lib/runtime/credentials.d.ts +3 -1
  47. package/dist/lib/runtime/credentials.js +6 -1
  48. package/dist/lib/runtime/native/agentLoop.d.ts +7 -1
  49. package/dist/lib/runtime/native/agentLoop.js +31 -13
  50. package/dist/lib/runtime/native/delegate.d.ts +53 -0
  51. package/dist/lib/runtime/native/delegate.js +91 -8
  52. package/dist/lib/runtime/native/interrupts.d.ts +6 -3
  53. package/dist/lib/runtime/native/interrupts.js +23 -4
  54. package/dist/lib/runtime/native/request.d.ts +1 -1
  55. package/dist/lib/runtime/native/request.js +1 -1
  56. package/dist/lib/runtime/native/selfCorrection.d.ts +64 -6
  57. package/dist/lib/runtime/native/selfCorrection.js +82 -8
  58. package/dist/lib/runtime/native/step.d.ts +14 -6
  59. package/dist/lib/runtime/native/step.js +34 -10
  60. package/dist/lib/runtime/runtimeFlag.d.ts +13 -5
  61. package/dist/lib/runtime/runtimeFlag.js +14 -6
  62. package/dist/lib/runtime/syndicateTurn.d.ts +15 -10
  63. package/dist/lib/runtime/syndicateTurn.js +59 -32
  64. package/dist/lib/runtime/valueDepth.d.ts +23 -0
  65. package/dist/lib/runtime/valueDepth.js +41 -0
  66. package/dist/lib/session/supabaseSessionService.d.ts +1 -1
  67. package/dist/lib/session/supabaseSessionService.js +1 -1
  68. package/dist/lib/session/transcript.d.ts +1 -1
  69. package/dist/lib/session/transcript.js +5 -3
  70. package/dist/lib/storage/postgres/sessionService.d.ts +1 -1
  71. package/dist/lib/storage/postgres/sessionService.js +1 -1
  72. package/dist/lib/syndicateSchema.d.ts +2 -0
  73. package/dist/lib/syndicateSchema.js +27 -6
  74. package/dist/lib/toolRegistry.d.ts +12 -1
  75. package/dist/lib/toolRegistry.js +42 -2
  76. package/dist/lib/tools/adkTool.d.ts +1 -1
  77. package/dist/lib/tools/adkTool.js +1 -1
  78. package/dist/lib/tools/collectionsSearchTool.d.ts +1 -1
  79. package/dist/lib/tools/collectionsSearchTool.js +1 -1
  80. package/dist/lib/tools/generateImageTool.d.ts +1 -1
  81. package/dist/lib/tools/inspectImageTool.d.ts +1 -1
  82. package/dist/lib/tools/urlContextTool.d.ts +1 -1
  83. package/dist/lib/tools/urlContextTool.js +1 -1
  84. package/dist/lib/tools/webExtractTool.d.ts +1 -1
  85. package/dist/lib/tools/webSearchTool.d.ts +1 -1
  86. package/dist/lib/tools/webSearchTool.js +1 -1
  87. package/dist/lib/tools/xApiSearchTool.d.ts +1 -1
  88. package/dist/lib/tools/xSearchTool.d.ts +1 -1
  89. package/dist/lib/tools/xSearchTool.js +1 -1
  90. package/dist/lib/wiki/agentRun.d.ts +1 -1
  91. package/dist/lib/wiki/agentRun.js +2 -1
  92. package/dist/lib/workflow/agentNode.d.ts +21 -4
  93. package/dist/lib/workflow/agentNode.js +96 -5
  94. package/dist/lib/workflow/graph.d.ts +5 -4
  95. package/dist/lib/workflow/graph.js +14 -4
  96. package/dist/lib/workflow/resume.d.ts +10 -9
  97. package/dist/lib/workflow/resume.js +13 -12
  98. package/dist/lib/workflow/scheduler.d.ts +10 -7
  99. package/dist/lib/workflow/scheduler.js +15 -2
  100. package/dist/lib/workflow.d.ts +33 -11
  101. package/dist/lib/workflow.js +49 -27
  102. package/dist/lib/workflowConfig.d.ts +4 -0
  103. package/dist/lib/workflowConfig.js +4 -0
  104. package/dist/scripts/doctor.d.ts +3 -1
  105. package/dist/scripts/doctor.js +4 -2
  106. package/package.json +6 -3
@@ -36,7 +36,11 @@
36
36
  * - a long-running tool (ask_user) that returns nothing answers
37
37
  * nothing; its actions, when it set any, make an event of their own;
38
38
  * - a result that is not an object is wrapped `{ result }`, an array
39
- * `{ results }`, as ADK wraps them.
39
+ * `{ results }`, as ADK wraps them;
40
+ * - a result nested deeper than MAX_VALUE_DEPTH levels answers
41
+ * `{ error: TOO_DEEP_RESULT }` instead: every later reader of the
42
+ * session would overflow its stack on it (lib/runtime/valueDepth.ts,
43
+ * ADR 0101). ADK keeps it.
40
44
  * One call's response is stored as its own event; several are merged
41
45
  * into one, parts in call order and actions merged (ADK's
42
46
  * mergeParallelFunctionResponseEvents). ADK runs the calls one after
@@ -115,10 +119,11 @@ import { z } from 'zod';
115
119
  import { ToolCredentialError, toolAccessToken } from '../../tools/auth.js';
116
120
  import { APPROVAL_TEXTS, CONSENT_TEXTS, isTool } from '../../tools/tool.js';
117
121
  import { createEventActions, createTurnEvent, getFunctionCalls, getFunctionResponses, isFinal } from '../events.js';
122
+ import { MAX_VALUE_DEPTH, nestedDeeperThan } from '../valueDepth.js';
118
123
  import { currentTurnSignal } from '../turnControl.js';
119
124
  import { compactBeforeStep } from './compaction.js';
120
125
  import { ADK_CALL_ID_PREFIX } from './history.js';
121
- import { runSubagent, subagentOf } from './delegate.js';
126
+ import { runSubagent, runWorkflowSubagent, subagentOf, workflowSubagentOf } from './delegate.js';
122
127
  import { approvedCalls, grantedCalls } from './interrupts.js';
123
128
  import { SelfCorrection } from './selfCorrection.js';
124
129
  import { eitherSignal, runModelStep, stopOf } from './step.js';
@@ -145,19 +150,23 @@ function isDefaultActions(a) {
145
150
  a.transferToAgent === undefined &&
146
151
  a.escalate === undefined);
147
152
  }
153
+ /** Copies `from`'s own keys onto `to` as own keys: Object.assign would re-parent `to` for a key `__proto__` (a model-chosen call id). */
154
+ function assignOwn(to, from) {
155
+ for (const [k, v] of Object.entries(from))
156
+ setOwn(to, k, v);
157
+ }
148
158
  /** ADK's mergeEventActions: dictionaries merged in order, the later flag winning. */
149
159
  function mergeActions(sources) {
150
160
  const out = createEventActions();
151
161
  for (const s of sources) {
152
162
  if (s.stateDelta)
153
- for (const [k, v] of Object.entries(s.stateDelta))
154
- setOwn(out.stateDelta, k, v);
163
+ assignOwn(out.stateDelta, s.stateDelta);
155
164
  if (s.artifactDelta)
156
- Object.assign(out.artifactDelta, s.artifactDelta);
165
+ assignOwn(out.artifactDelta, s.artifactDelta);
157
166
  if (s.requestedAuthConfigs)
158
- Object.assign(out.requestedAuthConfigs, s.requestedAuthConfigs);
167
+ assignOwn(out.requestedAuthConfigs, s.requestedAuthConfigs);
159
168
  if (s.requestedToolConfirmations)
160
- Object.assign(out.requestedToolConfirmations, s.requestedToolConfirmations);
169
+ assignOwn(out.requestedToolConfirmations, s.requestedToolConfirmations);
161
170
  if (s.skipSummarization !== undefined)
162
171
  out.skipSummarization = s.skipSummarization;
163
172
  if (s.transferToAgent !== undefined)
@@ -235,7 +244,8 @@ function callContext(scope, functionCallId, confirmation) {
235
244
  const requestCredential = consent && functionCallId
236
245
  ? async (provider) => {
237
246
  const request = await consent.begin({ appName: session.appName, userId: session.userId, sessionId: session.id, functionCallId, provider });
238
- actions.requestedAuthConfigs[functionCallId] = request.authConfig;
247
+ // An own key, as every delta: a call id such as `__proto__` names a request, never a prototype.
248
+ setOwn(actions.requestedAuthConfigs, functionCallId, request.authConfig);
239
249
  }
240
250
  : undefined;
241
251
  const credentials = ctx.credentials;
@@ -270,7 +280,7 @@ function callContext(scope, functionCallId, confirmation) {
270
280
  requestConfirmation: ({ hint, payload } = {}) => {
271
281
  if (!functionCallId)
272
282
  throw new Error('functionCallId is not set.');
273
- actions.requestedToolConfirmations[functionCallId] = { hint: hint ?? '', confirmed: false, payload };
283
+ setOwn(actions.requestedToolConfirmations, functionCallId, { hint: hint ?? '', confirmed: false, payload });
274
284
  },
275
285
  // The person's answer, when this call is the pinned call an approval resumes (interrupts.ts).
276
286
  confirmation,
@@ -292,6 +302,8 @@ function callContext(scope, functionCallId, confirmation) {
292
302
  },
293
303
  };
294
304
  }
305
+ /** What a tool's result becomes when it nests past MAX_VALUE_DEPTH (ADR 0101). */
306
+ export const TOO_DEEP_RESULT = (toolName) => `The result of tool '${toolName}' nested deeper than ${MAX_VALUE_DEPTH} levels and was not kept.`;
295
307
  /** ADK's normalization of a tool's result into a function response. */
296
308
  function asResponse(value) {
297
309
  if (typeof value !== 'object' || value === null)
@@ -339,14 +351,18 @@ async function runCall(scope, call, tools, correction, confirmation) {
339
351
  const args = call.args ?? {};
340
352
  const toolName = tool.name;
341
353
  const longRunning = isTool(tool) ? tool.longRunning === true : tool.isLongRunning === true;
342
- // Delegation (WS2-6): a subagent runs as its own child loop, before the generic path (delegate.ts).
354
+ // Delegation (WS2-6): a subagent runs as its own child loop, a nested workflow as its own walk (ADR 0098), before the generic path (delegate.ts).
343
355
  const subagent = subagentOf(tool);
356
+ const workflow = subagent ? undefined : workflowSubagentOf(tool);
357
+ const delegation = { ctx: scope.ctx, stateBase: scope.stateBase, signal: scope.signal, runLoop: runAgentLoop, queue: scope };
344
358
  let response;
345
359
  let failure;
346
360
  try {
347
361
  response = subagent
348
- ? await runSubagent(subagent, args, context, { ctx: scope.ctx, stateBase: scope.stateBase, signal: scope.signal, runLoop: runAgentLoop, queue: scope })
349
- : isTool(tool) ? await runOwnTool(tool, args, context) : await tool.runAsync({ args, toolContext: context });
362
+ ? await runSubagent(subagent, args, context, delegation)
363
+ : workflow
364
+ ? await runWorkflowSubagent(workflow, args, context, delegation)
365
+ : isTool(tool) ? await runOwnTool(tool, args, context) : await tool.runAsync({ args, toolContext: context });
350
366
  }
351
367
  catch (e) {
352
368
  failure = e instanceof Error ? e.message : e;
@@ -368,7 +384,9 @@ async function runCall(scope, call, tools, correction, confirmation) {
368
384
  return isDefaultActions(context.actions) ? undefined : { actions: context.actions };
369
385
  }
370
386
  const answer = failure ? { error: failure } : response === null || response === undefined ? { result: response } : asResponse(response);
371
- return { part: { functionResponse: { id: context.functionCallId, name: toolName, response: answer } }, actions: context.actions };
387
+ // A result nested past MAX_VALUE_DEPTH would overflow every later reader of the session (lib/runtime/valueDepth.ts, ADR 0101): a note stands in for it.
388
+ const kept = nestedDeeperThan(answer) ? { error: TOO_DEEP_RESULT(toolName) } : answer;
389
+ return { part: { functionResponse: { id: context.functionCallId, name: toolName, response: kept } }, actions: context.actions };
372
390
  }
373
391
  /**
374
392
  * The calls run, in parallel, as the one response event ADK stores for them
@@ -47,6 +47,17 @@
47
47
  * here like any subagent, and its own delegations open sessions under their
48
48
  * own names, as on ADK.
49
49
  *
50
+ * A NESTED WORKFLOW (ADR 0098): a `yaml_reference` to a workflow syndicate
51
+ * is a workflowSubagentTool, holding the whole graph as a WorkflowSubagent.
52
+ * A call runs as steps 1 to 6 say, with the walk in place of the loop: the
53
+ * graph's walk (lib/workflow/turn.ts, runNativeWorkflow, which
54
+ * lib/compileNative.ts hands over) stores the message itself and yields the
55
+ * events ADK's Runner yields for a Workflow root, node input turns aside;
56
+ * the answer is the last of them's text, as ADK's AgentTool reads it from a
57
+ * Workflow (never parsed: a Workflow has no output schema). A node that
58
+ * gave up fails the call with its error. The walk is not imported here, so
59
+ * this module stays below lib/workflow/ in the import graph.
60
+ *
50
61
  * The DELEGATE relay fallback (an orchestrator that echoes a tool name or
51
62
  * says nothing) stays in lib/runtime/syndicateTurn.ts: it reads the drained
52
63
  * run, whichever runtime produced it.
@@ -54,12 +65,17 @@
54
65
  * ADK stays out of this file: an ADK AgentTool is recognized by ADK's global
55
66
  * symbol only to refuse it, with a message naming what to list instead.
56
67
  */
68
+ import type { ModelAdapter } from '../../models/contract.ts';
57
69
  import type { Tool, ToolContext } from '../../tools/tool.ts';
58
70
  import type { TurnEvent } from '../events.ts';
71
+ import type { SessionService } from '../sessions.ts';
59
72
  import type { AgentLoopContext, AgentLoopEnd } from './agentLoop.ts';
60
73
  import type { NativeAgent } from './request.ts';
74
+ import { SelfCorrection } from './selfCorrection.ts';
61
75
  /** Where a subagent tool keeps the agent it runs. A global symbol, so a second copy of this module still finds it. */
62
76
  export declare const SUBAGENT: unique symbol;
77
+ /** Where a workflow subagent tool keeps the graph it runs. */
78
+ export declare const WORKFLOW_SUBAGENT: unique symbol;
63
79
  /** A subagent listed on its caller as a tool: the declaration ADK's AgentTool gives it, and the agent the call runs. */
64
80
  export interface SubagentTool extends Tool {
65
81
  readonly [SUBAGENT]: NativeAgent;
@@ -70,6 +86,36 @@ export interface SubagentTool extends Tool {
70
86
  * one string `request`. Only the native loop runs it.
71
87
  */
72
88
  export declare function subagentTool(agent: NativeAgent): SubagentTool;
89
+ /** One call's walk of a nested workflow, on the child session (WorkflowSubagent.walk). */
90
+ export interface WorkflowSubagentRun {
91
+ sessions: SessionService;
92
+ appName: string;
93
+ userId: string;
94
+ sessionId: string;
95
+ /** The message's parts: the call's request as one text part. */
96
+ userParts: unknown[];
97
+ signal?: AbortSignal;
98
+ adapterFor: (model: string) => ModelAdapter;
99
+ memory?: AgentLoopContext['memory'];
100
+ log?: (message: string) => void;
101
+ selfCorrection: SelfCorrection;
102
+ credentials?: AgentLoopContext['credentials'];
103
+ }
104
+ /** A workflow syndicate delegated to as a subagent (ADR 0098): its name and description, and the walk one call runs. */
105
+ export interface WorkflowSubagent {
106
+ name: string;
107
+ description?: string;
108
+ /** Stores the message on the child session and walks the whole graph, yielding each event ADK's Runner yields. Throws what a node gave up with. */
109
+ walk(run: WorkflowSubagentRun): AsyncGenerator<TurnEvent, unknown>;
110
+ }
111
+ /** A nested workflow listed on its caller as a tool: the declaration ADK's AgentTool gives a Workflow, and the graph the call walks. */
112
+ export interface WorkflowSubagentTool extends Tool {
113
+ readonly [WORKFLOW_SUBAGENT]: WorkflowSubagent;
114
+ }
115
+ /** `workflow` as a tool its caller lists, as lib/compileAdk.ts lists a nested Workflow as an AgentTool: one string `request`. Only the native loop runs it. */
116
+ export declare function workflowSubagentTool(workflow: WorkflowSubagent): WorkflowSubagentTool;
117
+ /** The nested workflow a delegated call walks: the one a workflow subagent tool holds, else undefined. */
118
+ export declare function workflowSubagentOf(tool: unknown): WorkflowSubagent | undefined;
73
119
  /**
74
120
  * The agent a delegated call runs: the one a subagent tool (or anything
75
121
  * carrying SUBAGENT) holds, else undefined. Throws for an ADK AgentTool that
@@ -96,3 +142,10 @@ export interface DelegationScope {
96
142
  * throws, and for an output-schema answer that does not parse.
97
143
  */
98
144
  export declare function runSubagent(agent: NativeAgent, args: Record<string, unknown>, context: ToolContext, scope: DelegationScope): Promise<unknown>;
145
+ /**
146
+ * Runs one delegated call to a nested workflow (ADR 0098): the whole graph
147
+ * walked on the child session with the call's `request` as its message,
148
+ * the walk's state writes recorded in `context`. Resolves to the last
149
+ * yielded event's text, '' for none. Throws what the walk throws.
150
+ */
151
+ export declare function runWorkflowSubagent(workflow: WorkflowSubagent, args: Record<string, unknown>, context: ToolContext, scope: DelegationScope): Promise<unknown>;
@@ -47,6 +47,17 @@
47
47
  * here like any subagent, and its own delegations open sessions under their
48
48
  * own names, as on ADK.
49
49
  *
50
+ * A NESTED WORKFLOW (ADR 0098): a `yaml_reference` to a workflow syndicate
51
+ * is a workflowSubagentTool, holding the whole graph as a WorkflowSubagent.
52
+ * A call runs as steps 1 to 6 say, with the walk in place of the loop: the
53
+ * graph's walk (lib/workflow/turn.ts, runNativeWorkflow, which
54
+ * lib/compileNative.ts hands over) stores the message itself and yields the
55
+ * events ADK's Runner yields for a Workflow root, node input turns aside;
56
+ * the answer is the last of them's text, as ADK's AgentTool reads it from a
57
+ * Workflow (never parsed: a Workflow has no output schema). A node that
58
+ * gave up fails the call with its error. The walk is not imported here, so
59
+ * this module stays below lib/workflow/ in the import graph.
60
+ *
50
61
  * The DELEGATE relay fallback (an orchestrator that echoes a tool name or
51
62
  * says nothing) stays in lib/runtime/syndicateTurn.ts: it reads the drained
52
63
  * run, whichever runtime produced it.
@@ -55,6 +66,7 @@
55
66
  * symbol only to refuse it, with a message naming what to list instead.
56
67
  */
57
68
  import { randomUUID } from 'node:crypto';
69
+ import { resolveAdapter } from '../../models/registry.js';
58
70
  import { createTurnEvent } from '../events.js';
59
71
  import { TEMP_STATE_PREFIX } from '../sessions.js';
60
72
  import { SelfCorrection } from './selfCorrection.js';
@@ -63,6 +75,8 @@ import { SelfCorrection } from './selfCorrection.js';
63
75
  export const SUBAGENT = Symbol.for('melchizedek.subagent');
64
76
  /** ADK's own mark on an AgentTool (agent_tool.js). */
65
77
  const ADK_AGENT_TOOL = Symbol.for('google.adk.agentTool');
78
+ /** Where a workflow subagent tool keeps the graph it runs. */
79
+ export const WORKFLOW_SUBAGENT = Symbol.for('melchizedek.workflowSubagent');
66
80
  /**
67
81
  * `agent` as a tool its caller lists, as lib/compile.ts lists a subagent as
68
82
  * an AgentTool: named for the agent, described by its description, taking
@@ -82,6 +96,28 @@ export function subagentTool(agent) {
82
96
  },
83
97
  };
84
98
  }
99
+ /** `workflow` as a tool its caller lists, as lib/compileAdk.ts lists a nested Workflow as an AgentTool: one string `request`. Only the native loop runs it. */
100
+ export function workflowSubagentTool(workflow) {
101
+ return {
102
+ name: workflow.name,
103
+ [WORKFLOW_SUBAGENT]: workflow,
104
+ declaration: () => ({
105
+ name: workflow.name,
106
+ description: workflow.description ?? '',
107
+ parameters: { type: 'object', properties: { request: { type: 'string' } }, required: ['request'] },
108
+ }),
109
+ execute: async () => {
110
+ throw new Error(`${workflow.name}: a workflow subagent tool runs only on the native loop (lib/runtime/native/agentLoop.ts)`);
111
+ },
112
+ };
113
+ }
114
+ /** The nested workflow a delegated call walks: the one a workflow subagent tool holds, else undefined. */
115
+ export function workflowSubagentOf(tool) {
116
+ if (!tool || typeof tool !== 'object')
117
+ return undefined;
118
+ const workflow = tool[WORKFLOW_SUBAGENT];
119
+ return workflow && typeof workflow === 'object' ? workflow : undefined;
120
+ }
85
121
  /**
86
122
  * The agent a delegated call runs: the one a subagent tool (or anything
87
123
  * carrying SUBAGENT) holds, else undefined. Throws for an ADK AgentTool that
@@ -107,7 +143,7 @@ function inTurn(key, run) {
107
143
  queues.set(key, mine.then(() => undefined, () => undefined));
108
144
  return mine;
109
145
  }
110
- /** ADK's AgentTool answer: the last event's non-thought text, joined by a newline. */
146
+ /** ADK's AgentTool answer: the last event's non-thought text, joined by a newline; parsed under an output schema. */
111
147
  function answerOf(agent, last) {
112
148
  const parts = last?.content?.parts;
113
149
  if (!parts?.length)
@@ -119,6 +155,19 @@ function answerOf(agent, last) {
119
155
  .join('\n');
120
156
  return agent.outputSchema ? JSON.parse(text) : text;
121
157
  }
158
+ /** 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. */
159
+ async function childSession(name, context, scope) {
160
+ const { sessions, session: parent } = scope.ctx;
161
+ const key = { appName: name, userId: parent.userId, sessionId: parent.id };
162
+ return (await sessions.get(key)) ?? (await sessions.create({ ...key, state: { ...scope.stateBase, ...context.stateDelta } }));
163
+ }
164
+ /** Step 5: an event's state writes, `temp:` keys aside, into the call's state delta. */
165
+ function recordState(event, context) {
166
+ for (const [k, v] of Object.entries(event.actions?.stateDelta ?? {})) {
167
+ if (!k.startsWith(TEMP_STATE_PREFIX))
168
+ context.state.set(k, v);
169
+ }
170
+ }
122
171
  /**
123
172
  * Runs one delegated call: `agent` on its own session with the call's
124
173
  * `request` as its message, the child's state writes recorded in `context`.
@@ -131,10 +180,8 @@ export function runSubagent(agent, args, context, scope) {
131
180
  async function runChild(agent, args, context, scope) {
132
181
  const { ctx, signal } = scope;
133
182
  const { sessions } = ctx;
134
- const parent = ctx.session;
135
183
  const content = { role: 'user', parts: [{ text: args.request }] };
136
- const key = { appName: agent.name, userId: parent.userId, sessionId: parent.id };
137
- const session = (await sessions.get(key)) ?? (await sessions.create({ ...key, state: { ...scope.stateBase, ...context.stateDelta } }));
184
+ const session = await childSession(agent.name, context, scope);
138
185
  if (signal?.aborted)
139
186
  return '';
140
187
  const invocationId = `e-${randomUUID()}`;
@@ -163,11 +210,47 @@ async function runChild(agent, args, context, scope) {
163
210
  // ADK's Runner stops yielding once the turn stopped: the answer is the last event it yielded.
164
211
  if (signal?.aborted)
165
212
  break;
166
- for (const [k, v] of Object.entries(event.actions?.stateDelta ?? {})) {
167
- if (!k.startsWith(TEMP_STATE_PREFIX))
168
- context.state.set(k, v);
169
- }
213
+ recordState(event, context);
170
214
  last = event;
171
215
  }
172
216
  return answerOf(agent, last);
173
217
  }
218
+ /**
219
+ * Runs one delegated call to a nested workflow (ADR 0098): the whole graph
220
+ * walked on the child session with the call's `request` as its message,
221
+ * the walk's state writes recorded in `context`. Resolves to the last
222
+ * yielded event's text, '' for none. Throws what the walk throws.
223
+ */
224
+ export function runWorkflowSubagent(workflow, args, context, scope) {
225
+ return inTurn(scope.queue, () => walkChild(workflow, args, context, scope));
226
+ }
227
+ async function walkChild(workflow, args, context, scope) {
228
+ const { ctx, signal } = scope;
229
+ const session = await childSession(workflow.name, context, scope);
230
+ if (signal?.aborted)
231
+ return '';
232
+ const walk = workflow.walk({
233
+ sessions: ctx.sessions,
234
+ appName: session.appName,
235
+ userId: session.userId,
236
+ sessionId: session.id,
237
+ userParts: [{ text: args.request }],
238
+ adapterFor: ctx.adapterFor ?? resolveAdapter,
239
+ // As runChild: ADK's AgentTool runs its sub-runner without the reflect-and-retry plugins (ADR 0075).
240
+ selfCorrection: new SelfCorrection({ model_errors: 0, tool_errors: 0 }),
241
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
242
+ ...(ctx.memory ? { memory: ctx.memory } : {}),
243
+ ...(ctx.log ? { log: ctx.log } : {}),
244
+ ...(ctx.credentials ? { credentials: ctx.credentials } : {}),
245
+ });
246
+ let last;
247
+ for await (const event of walk) {
248
+ if (signal?.aborted)
249
+ break;
250
+ if (event.partial)
251
+ continue;
252
+ recordState(event, context);
253
+ last = event;
254
+ }
255
+ return answerOf({}, last);
256
+ }
@@ -62,7 +62,9 @@
62
62
  * nothing happens.
63
63
  * 2. The requests they answer: `adk_request_credential` calls this agent
64
64
  * made, by id. An answer naming no such request is ignored, as ADK
65
- * ignores it.
65
+ * ignores it. So is an answer to a request an earlier grant already
66
+ * bound (step 3): a replayed grant runs nothing, where ADK would run
67
+ * the paused call again (WS5-5).
66
68
  * 3. The binding. ADK's answer carries the authorization response (a code
67
69
  * or the redirect URL), which ADK exchanges in-process with the client
68
70
  * secret its request event stored. Here the server's callback route has
@@ -71,8 +73,9 @@
71
73
  * `{ credentialKey, granted: true }`, bound to the request by its
72
74
  * credentialKey. An answer that does not bind is ignored.
73
75
  * 4. The paused calls the bound requests name (`function_call_id`, ADK's
74
- * toolset requests aside) run again, from the latest event that made
75
- * them, through the loop's own call path. The tool now reads its grant
76
+ * toolset requests aside) run again, from the latest event this agent
77
+ * authored that made them (ADK reads any author's; a call forged into
78
+ * a user event never runs), through the loop's own call path. The tool now reads its grant
76
79
  * through ctx.accessToken. The response is stored before the step
77
80
  * builds its request. A later step finds the agent's own events last,
78
81
  * so the call runs once.
@@ -62,7 +62,9 @@
62
62
  * nothing happens.
63
63
  * 2. The requests they answer: `adk_request_credential` calls this agent
64
64
  * made, by id. An answer naming no such request is ignored, as ADK
65
- * ignores it.
65
+ * ignores it. So is an answer to a request an earlier grant already
66
+ * bound (step 3): a replayed grant runs nothing, where ADK would run
67
+ * the paused call again (WS5-5).
66
68
  * 3. The binding. ADK's answer carries the authorization response (a code
67
69
  * or the redirect URL), which ADK exchanges in-process with the client
68
70
  * secret its request event stored. Here the server's callback route has
@@ -71,8 +73,9 @@
71
73
  * `{ credentialKey, granted: true }`, bound to the request by its
72
74
  * credentialKey. An answer that does not bind is ignored.
73
75
  * 4. The paused calls the bound requests name (`function_call_id`, ADK's
74
- * toolset requests aside) run again, from the latest event that made
75
- * them, through the loop's own call path. The tool now reads its grant
76
+ * toolset requests aside) run again, from the latest event this agent
77
+ * authored that made them (ADK reads any author's; a call forged into
78
+ * a user event never runs), through the loop's own call path. The tool now reads its grant
76
79
  * through ctx.accessToken. The response is stored before the step
77
80
  * builds its request. A later step finds the agent's own events last,
78
81
  * so the call runs once.
@@ -256,6 +259,16 @@ export async function grantedCalls(agent, scope) {
256
259
  answers.set(r.id, r.response);
257
260
  if (answers.size === 0)
258
261
  return undefined;
262
+ // The answers earlier events gave the same requests: one that bound closed its request (below).
263
+ const earlier = new Map();
264
+ for (const event of events) {
265
+ if (event === last)
266
+ break;
267
+ for (const r of getFunctionResponses(event)) {
268
+ if (r.name === REQUEST_CREDENTIAL_CALL && r.id && answers.has(r.id))
269
+ earlier.set(r.id, [...(earlier.get(r.id) ?? []), r.response]);
270
+ }
271
+ }
259
272
  // The requests this agent made, by id.
260
273
  const requests = new Map();
261
274
  for (const event of events) {
@@ -274,14 +287,20 @@ export async function grantedCalls(agent, scope) {
274
287
  const request = requests.get(id);
275
288
  if (!request || !bindsGrant(request.config, response))
276
289
  continue;
290
+ // A request a grant already bound is closed (WS5-5): a replayed grant runs nothing, as a replayed approval runs nothing. ADK resumes it again.
291
+ if ((earlier.get(id) ?? []).some((before) => bindsGrant(request.config, before)))
292
+ continue;
277
293
  const callId = request.args.function_call_id ?? request.args.functionCallId;
278
294
  if (typeof callId === 'string' && callId && !callId.startsWith(TOOLSET_AUTH_CREDENTIAL_ID_PREFIX))
279
295
  resume.add(callId);
280
296
  }
281
297
  if (resume.size === 0)
282
298
  return undefined;
283
- // As ADK: the latest event before the answer that made any of the calls, those calls only.
299
+ // As ADK: the latest event before the answer that made any of the calls, those calls only. Only one this agent authored (WS5-5):
300
+ // ADK takes any author's, so a call forged into a user event with the paused call's id would run with the forged arguments.
284
301
  for (let i = events.length - 2; i >= 0; i--) {
302
+ if (events[i]?.author !== agent.name)
303
+ continue;
285
304
  const calls = getFunctionCalls(events[i]);
286
305
  if (!calls.some((c) => c.id && resume.has(c.id)))
287
306
  continue;
@@ -43,7 +43,7 @@
43
43
  * xAI); `code_execution` first among Gemini's own tools; and the
44
44
  * set_model_response tool when (2) asked for it, then the caller's
45
45
  * `extraTools` (self-correction's reflection tool, as ADK's plugin adds
46
- * it to the toolsDict last). In `mode: task`, finish_task takes
46
+ * it to the toolsDict last, when the step declares it: ADR 0097). In `mode: task`, finish_task takes
47
47
  * set_model_response's place and the output schema is never the
48
48
  * response schema (lib/runtime/native/taskMode.ts).
49
49
  * 5. Tool choice, the output schema or JSON mode, reasoning and sampling,
@@ -43,7 +43,7 @@
43
43
  * xAI); `code_execution` first among Gemini's own tools; and the
44
44
  * set_model_response tool when (2) asked for it, then the caller's
45
45
  * `extraTools` (self-correction's reflection tool, as ADK's plugin adds
46
- * it to the toolsDict last). In `mode: task`, finish_task takes
46
+ * it to the toolsDict last, when the step declares it: ADR 0097). In `mode: task`, finish_task takes
47
47
  * set_model_response's place and the output schema is never the
48
48
  * response schema (lib/runtime/native/taskMode.ts).
49
49
  * 5. Tool choice, the output schema or JSON mode, reasoning and sampling,
@@ -12,9 +12,14 @@
12
12
  * read differently on the other:
13
13
  *
14
14
  * THE MODEL (`retries.model_errors`, default 2; 0 off):
15
- * - Every request declares the reflection tool, `adk_handle_model_error`,
16
- * after the agent's own tools (the plugin's beforeModelCallback adds it
17
- * to the toolsDict last).
15
+ * - The reflection tool, `adk_handle_model_error`, is one of every step's
16
+ * tools, after the agent's own: the plugin's beforeModelCallback adds it
17
+ * to the request's toolsDict last, and never to its config. Whether the
18
+ * model is told of it depends on the model class ADK hands the request
19
+ * to (ADR 0097): the ADK shim and the engine's own ADK classes declare
20
+ * the toolsDict, so the tool is declared; ADK's own Gemini sends the
21
+ * config alone, so a Gemini model is never told of it. The step asks
22
+ * `declaresReflectionTool` and runs the tool either way.
18
23
  * - Each response is checked (afterModelCallback), partials included. A
19
24
  * response that calls the reflection tool itself, or whose finish reason
20
25
  * is MALFORMED_FUNCTION_CALL, is replaced by a call to the reflection
@@ -22,6 +27,13 @@
22
27
  * the retry's number counted per agent in the run. The loop runs that
23
28
  * call like any other, and the tool answers with reflection guidance.
24
29
  * Any other response resets the agent's count.
30
+ * - ON A GEMINI MODEL the stored reflection call is signed (ADR 0103), where
31
+ * ADK's plugin stores it unsigned: it carries the replaced response's
32
+ * thoughtSignature, or on Gemini 3 Gemini's documented placeholder when
33
+ * the response had none (a MALFORMED_FUNCTION_CALL has no parts). Gemini 3
34
+ * rejects the next request with an unsigned current-turn call (400,
35
+ * "missing a thought_signature"); ADK's runtime still does. This is the
36
+ * one place the native loop stores what ADK would not (reflectionSigning).
25
37
  * - Past the limit the step ends on ADK's error event for a callback that
26
38
  * threw: UNKNOWN_ERROR, and the plugin manager's message.
27
39
  *
@@ -56,6 +68,48 @@ export declare const DEFAULT_MODEL_ERROR_RETRIES = 2;
56
68
  export declare const DEFAULT_TOOL_ERROR_RETRIES = 3;
57
69
  export declare const ADK_HANDLE_MODEL_ERROR = "adk_handle_model_error";
58
70
  export declare const REFLECT_AND_RETRY_RESPONSE_TYPE = "ERROR_HANDLED_BY_REFLECT_AND_RETRY_PLUGIN";
71
+ /**
72
+ * Marks `adapter` as one the ADK runtime would call through the ADK shim
73
+ * (a resolver's shim or contract adapter, lib/compileNative.ts), not through
74
+ * ADK's own Gemini. Returns it.
75
+ */
76
+ export declare function servedThroughShim<T extends object>(adapter: T): T;
77
+ /**
78
+ * Whether the ADK runtime would tell this adapter's model of the reflection
79
+ * tool (ADR 0097). The plugin puts the tool in the toolsDict alone. Every
80
+ * model class the engine hands ADK declares the toolsDict, but ADK's own
81
+ * Gemini (and TracedGemini over it) sends only the request's config, which
82
+ * never holds it. A Gemini adapter stands for ADK's Gemini unless a caller
83
+ * handed it over behind the shim (servedThroughShim).
84
+ */
85
+ export declare function declaresReflectionTool(adapter: {
86
+ readonly provider: string;
87
+ }): boolean;
88
+ /**
89
+ * Whether this adapter stands, on the native runtime, for ADK's own Gemini
90
+ * (TracedGemini) on the ADK runtime: a Gemini adapter no caller handed over
91
+ * behind the shim. The step declares no reflection tool to it (ADR 0097) and
92
+ * stores no `turnComplete` on its events, as ADK's Gemini writes none
93
+ * (ADR 0100).
94
+ */
95
+ export declare function standsForAdkGemini(adapter: {
96
+ readonly provider: string;
97
+ }): boolean;
98
+ /** How the reflection call stored in a model's place is signed. */
99
+ export interface ReflectionSigning {
100
+ /** The signature for a reflection call whose replaced response carried none; absent: left unsigned. */
101
+ placeholder?: string;
102
+ }
103
+ /**
104
+ * How self-correction signs the reflection call it stores in place of this
105
+ * adapter's response, or undefined to store it unsigned, as ADK's plugin
106
+ * does. A Gemini adapter's call carries the replaced response's signature;
107
+ * on a Gemini 3 model, whose API rejects an unsigned current-turn call, the
108
+ * placeholder stands in when there was none.
109
+ */
110
+ export declare function reflectionSigning(adapter: {
111
+ readonly provider: string;
112
+ }, model: string | undefined): ReflectionSigning | undefined;
59
113
  /** What afterModel leaves of a response: the response to record, or the error event that ends the step. */
60
114
  export type CorrectedResponse = {
61
115
  response: LlmResponse;
@@ -68,10 +122,14 @@ export type CorrectedResponse = {
68
122
  };
69
123
  /** The model side, for one agent's steps in one run (lib/runtime/native/step.ts). */
70
124
  export interface ModelCorrection {
71
- /** Declared after the agent's own tools: the reflection tool. */
125
+ /** After the agent's own tools: the reflection tool. Declared to the model only where ADK declares it (declaresReflectionTool). */
72
126
  readonly tools: readonly Tool[];
73
- /** ADK's afterModelCallback, on every response the step reads. */
74
- afterModel(response: LlmResponse): CorrectedResponse;
127
+ /**
128
+ * ADK's afterModelCallback, on every response the step reads. With
129
+ * `signing` (reflectionSigning), a reflection call put in the response's
130
+ * place is signed.
131
+ */
132
+ afterModel(response: LlmResponse, signing?: ReflectionSigning): CorrectedResponse;
75
133
  }
76
134
  /** The tool side, for one call (lib/runtime/native/agentLoop.ts). Each call makes exactly one of these. */
77
135
  export interface CallCorrection {