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.
- package/.env.example +19 -2
- package/CHANGELOG.md +333 -0
- package/DOCUMENTATION.md +99 -39
- package/README.md +1 -0
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +6 -6
- package/config/agents/examples/critic.yaml +16 -16
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +33 -32
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +1979 -0
- package/config/agents/syndicateSchema.yaml +109 -72
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +61 -60
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +21 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +15 -0
- package/dist/lib/a2a/app.js +53 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +34 -4
- package/dist/lib/a2a/oauthSetup.d.ts +90 -0
- package/dist/lib/a2a/oauthSetup.js +205 -0
- package/dist/lib/agentDialect.d.ts +66 -0
- package/dist/lib/agentDialect.js +134 -0
- package/dist/lib/compile.d.ts +10 -5
- package/dist/lib/compile.js +69 -37
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +34 -1
- package/dist/lib/doctor.js +47 -3
- package/dist/lib/loadSyndicate.d.ts +45 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +16 -1
- package/dist/lib/models/capabilities.js +55 -1
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/runtime/approvals.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
- package/dist/lib/runtime/native/agentLoop.js +105 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
- package/dist/lib/runtime/native/checkpoint.js +365 -0
- package/dist/lib/runtime/native/delegate.d.ts +141 -18
- package/dist/lib/runtime/native/delegate.js +235 -39
- package/dist/lib/runtime/native/interrupts.d.ts +101 -2
- package/dist/lib/runtime/native/interrupts.js +201 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +7 -1
- package/dist/lib/runtime/syndicateTurn.js +74 -22
- package/dist/lib/runtime/turnControl.d.ts +15 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +7 -2
- package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
- package/dist/lib/storage/postgres/taskQueue.js +20 -3
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +278 -0
- package/dist/lib/syndicateSchema.js +182 -15
- package/dist/lib/tools/auth.d.ts +7 -5
- package/dist/lib/tools/auth.js +12 -4
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthHosts.d.ts +71 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +233 -0
- package/dist/lib/tools/oauthTools.js +421 -0
- package/dist/lib/tools/openapiTools.d.ts +4 -6
- package/dist/lib/tools/openapiTools.js +40 -36
- package/dist/lib/tools/taskTools.d.ts +30 -5
- package/dist/lib/tools/taskTools.js +74 -10
- package/dist/lib/workflow.d.ts +8 -4
- package/dist/lib/workflow.js +8 -4
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +122 -1
- package/dist/scripts/assistant_worker.d.ts +10 -0
- package/dist/scripts/assistant_worker.js +69 -19
- package/dist/scripts/doctor.d.ts +4 -1
- package/dist/scripts/doctor.js +5 -2
- package/dist/scripts/yaml_codemod.d.ts +52 -0
- package/dist/scripts/yaml_codemod.js +570 -0
- package/package.json +11 -7
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +4 -3
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- 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:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
-
*
|
|
43
|
-
*
|
|
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
|
-
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
/**
|
|
152
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
333
|
+
async function runChild(agent, content, context, scope, resuming) {
|
|
174
334
|
const { ctx, signal } = scope;
|
|
175
335
|
const { sessions } = ctx;
|
|
176
|
-
const
|
|
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:
|
|
195
|
-
//
|
|
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
|
|
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
|
-
|
|
207
|
-
|
|
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
|
|
388
|
+
return walkChild(workflow, [{ text: args.request }], context, scope, false);
|
|
219
389
|
}
|
|
220
|
-
|
|
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
|
|
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
|
|
241
|
-
|
|
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 {
|
|
88
|
-
import type {
|
|
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 {};
|