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.
- package/.env.example +29 -2
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +524 -0
- package/DOCUMENTATION.md +178 -43
- package/ONBOARDING.md +339 -0
- package/QUICKSTART.md +15 -2
- package/README.md +6 -2
- 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 +7 -7
- package/config/agents/examples/critic.yaml +24 -24
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +37 -35
- 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 +859 -13
- package/config/agents/syndicateSchema.yaml +122 -73
- 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 +64 -62
- 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 +24 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +27 -0
- package/dist/lib/a2a/app.js +64 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +43 -5
- package/dist/lib/a2a/oauthSetup.d.ts +106 -0
- package/dist/lib/a2a/oauthSetup.js +259 -0
- package/dist/lib/agentDialect.d.ts +76 -0
- package/dist/lib/agentDialect.js +151 -0
- package/dist/lib/compile.d.ts +42 -6
- package/dist/lib/compile.js +97 -45
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +20 -1
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +110 -1
- package/dist/lib/doctor.js +205 -9
- package/dist/lib/loadSyndicate.d.ts +54 -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 +25 -1
- package/dist/lib/models/capabilities.js +79 -1
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +2 -1
- package/dist/lib/models/gateway.js +4 -2
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/onboarding.d.ts +134 -0
- package/dist/lib/onboarding.js +539 -0
- package/dist/lib/runtime/approvals.d.ts +8 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +23 -3
- package/dist/lib/runtime/native/agentLoop.js +107 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
- package/dist/lib/runtime/native/checkpoint.js +359 -0
- package/dist/lib/runtime/native/delegate.d.ts +222 -18
- package/dist/lib/runtime/native/delegate.js +390 -39
- package/dist/lib/runtime/native/interrupts.d.ts +184 -2
- package/dist/lib/runtime/native/interrupts.js +391 -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 +11 -3
- package/dist/lib/runtime/syndicateTurn.js +353 -68
- package/dist/lib/runtime/turnControl.d.ts +56 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +12 -2
- package/dist/lib/storage/postgres/index.js +8 -3
- package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
- package/dist/lib/storage/postgres/taskQueue.js +29 -5
- 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 +316 -1
- package/dist/lib/syndicateSchema.js +203 -29
- package/dist/lib/tools/auth.d.ts +20 -6
- package/dist/lib/tools/auth.js +21 -5
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/credentialUses.d.ts +92 -0
- package/dist/lib/tools/credentialUses.js +112 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthConsent.js +8 -1
- package/dist/lib/tools/oauthHosts.d.ts +73 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +242 -0
- package/dist/lib/tools/oauthTools.js +447 -0
- package/dist/lib/tools/openapiTools.d.ts +11 -6
- package/dist/lib/tools/openapiTools.js +88 -38
- package/dist/lib/tools/taskTools.d.ts +61 -6
- package/dist/lib/tools/taskTools.js +145 -21
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +14 -7
- package/dist/lib/workflow.js +14 -7
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +131 -1
- package/dist/scripts/assistant_worker.d.ts +12 -0
- package/dist/scripts/assistant_worker.js +81 -22
- package/dist/scripts/doctor.d.ts +7 -2
- package/dist/scripts/doctor.js +9 -4
- package/dist/scripts/setup.d.ts +23 -0
- package/dist/scripts/setup.js +154 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/yaml_codemod.d.ts +62 -0
- package/dist/scripts/yaml_codemod.js +652 -0
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +7 -4
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/briefs/skills-readme.md +3 -1
- 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 +11 -4
- package/skills/melchizedek-onboard/SKILL.md +49 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -72,6 +72,17 @@
|
|
|
72
72
|
*
|
|
73
73
|
* DELEGATION: a call to a subagent tool runs the subagent as its own child
|
|
74
74
|
* loop, as ADK's AgentTool runs it (lib/runtime/native/delegate.ts, ADR 0074).
|
|
75
|
+
* A step's delegated calls run at once through the step's DelegationGate,
|
|
76
|
+
* at most the agent's `maxConcurrency` (the syndicate's `max_concurrency`)
|
|
77
|
+
* at a time, entered in call order, their responses stored in call order
|
|
78
|
+
* (ADR 0116).
|
|
79
|
+
* A child that ends paused leaves the call open (ADR 0110): no response is
|
|
80
|
+
* stored for it, the step's other responses are, and the run ends paused on
|
|
81
|
+
* the call's id. Before each step, after the approval resume, a delegated
|
|
82
|
+
* call whose pause the latest user message answers is resumed with that
|
|
83
|
+
* answer (interrupts.ts resumedDelegations, delegate.ts resumeSubagent) and
|
|
84
|
+
* its response stored; while another open call still waits, the run ends
|
|
85
|
+
* paused again on it without a model step.
|
|
75
86
|
*
|
|
76
87
|
* SELF-CORRECTION (ADR 0034, ADR 0075) is ADK's reflect-and-retry plugins,
|
|
77
88
|
* ported to lib/runtime/native/selfCorrection.ts: the step declares the
|
|
@@ -102,6 +113,8 @@
|
|
|
102
113
|
* Before each step, a credential request the latest user message answers
|
|
103
114
|
* runs its paused call again (interrupts.ts grantedCalls, ADK's auth
|
|
104
115
|
* preprocessor), ahead of the approval resume.
|
|
116
|
+
* Inside a delegated subagent the same pause leaves the caller's call open,
|
|
117
|
+
* and the grant's answer comes down through it (ADR 0118).
|
|
105
118
|
*
|
|
106
119
|
* NOT HERE (later tickets): transfer_to_agent (no compiled syndicate sets
|
|
107
120
|
* subAgents). The run's spans (agent.invoke, model.call,
|
|
@@ -158,10 +171,17 @@ export interface AgentLoopContext extends Omit<ModelStepOptions, 'agent' | 'adap
|
|
|
158
171
|
/**
|
|
159
172
|
* The consent step (ADR 0085): a call whose provider the user has not
|
|
160
173
|
* granted asks for it (`requestCredential`, or `accessToken` by itself),
|
|
161
|
-
* and the run pauses on ADK's `adk_request_credential` call.
|
|
162
|
-
*
|
|
174
|
+
* and the run pauses on ADK's `adk_request_credential` call. A delegated
|
|
175
|
+
* subagent's loop gets it too, its flows bound to the caller's app
|
|
176
|
+
* (delegate.ts consentPinnedTo, ADR 0118): its pause leaves the call open.
|
|
163
177
|
*/
|
|
164
178
|
consent?: Pick<OAuthConsent, 'has' | 'begin'>;
|
|
179
|
+
/**
|
|
180
|
+
* The run is a delegated subagent's, on its own child session filed under
|
|
181
|
+
* its agent path (lib/runtime/native/delegate.ts childAppName, ADR 0111):
|
|
182
|
+
* its own delegations are filed below that path. Set by runChild only.
|
|
183
|
+
*/
|
|
184
|
+
delegated?: boolean;
|
|
165
185
|
}
|
|
166
186
|
/** How the run ended. */
|
|
167
187
|
export type AgentLoopEndReason =
|
|
@@ -181,7 +201,7 @@ export interface AgentLoopEnd {
|
|
|
181
201
|
steps: number;
|
|
182
202
|
/** The last event stored. */
|
|
183
203
|
lastEvent?: TurnEvent;
|
|
184
|
-
/** For `paused`: the ids waiting on a person (the ask_user call ids,
|
|
204
|
+
/** For `paused`: the ids waiting on a person (the ask_user call ids, the adk_request_confirmation call ids, or the delegated calls whose child waits; ADR 0110). */
|
|
185
205
|
pending?: string[];
|
|
186
206
|
/** For `stopped`: the turn's code and message. */
|
|
187
207
|
stop?: StepStop;
|
|
@@ -72,6 +72,17 @@
|
|
|
72
72
|
*
|
|
73
73
|
* DELEGATION: a call to a subagent tool runs the subagent as its own child
|
|
74
74
|
* loop, as ADK's AgentTool runs it (lib/runtime/native/delegate.ts, ADR 0074).
|
|
75
|
+
* A step's delegated calls run at once through the step's DelegationGate,
|
|
76
|
+
* at most the agent's `maxConcurrency` (the syndicate's `max_concurrency`)
|
|
77
|
+
* at a time, entered in call order, their responses stored in call order
|
|
78
|
+
* (ADR 0116).
|
|
79
|
+
* A child that ends paused leaves the call open (ADR 0110): no response is
|
|
80
|
+
* stored for it, the step's other responses are, and the run ends paused on
|
|
81
|
+
* the call's id. Before each step, after the approval resume, a delegated
|
|
82
|
+
* call whose pause the latest user message answers is resumed with that
|
|
83
|
+
* answer (interrupts.ts resumedDelegations, delegate.ts resumeSubagent) and
|
|
84
|
+
* its response stored; while another open call still waits, the run ends
|
|
85
|
+
* paused again on it without a model step.
|
|
75
86
|
*
|
|
76
87
|
* SELF-CORRECTION (ADR 0034, ADR 0075) is ADK's reflect-and-retry plugins,
|
|
77
88
|
* ported to lib/runtime/native/selfCorrection.ts: the step declares the
|
|
@@ -102,6 +113,8 @@
|
|
|
102
113
|
* Before each step, a credential request the latest user message answers
|
|
103
114
|
* runs its paused call again (interrupts.ts grantedCalls, ADK's auth
|
|
104
115
|
* preprocessor), ahead of the approval resume.
|
|
116
|
+
* Inside a delegated subagent the same pause leaves the caller's call open,
|
|
117
|
+
* and the grant's answer comes down through it (ADR 0118).
|
|
105
118
|
*
|
|
106
119
|
* NOT HERE (later tickets): transfer_to_agent (no compiled syndicate sets
|
|
107
120
|
* subAgents). The run's spans (agent.invoke, model.call,
|
|
@@ -125,8 +138,8 @@ import { MAX_VALUE_DEPTH, nestedDeeperThan } from '../valueDepth.js';
|
|
|
125
138
|
import { currentTurnSignal } from '../turnControl.js';
|
|
126
139
|
import { compactBeforeStep } from './compaction.js';
|
|
127
140
|
import { ADK_CALL_ID_PREFIX } from './history.js';
|
|
128
|
-
import { runSubagent, runWorkflowSubagent, subagentOf, workflowSubagentOf } from './delegate.js';
|
|
129
|
-
import { approvedCalls, grantedCalls } from './interrupts.js';
|
|
141
|
+
import { DEFAULT_MAX_CONCURRENCY, DelegationGate, SubagentPause, resumeSubagent, resumeWorkflowSubagent, runSubagent, runWorkflowSubagent, subagentOf, workflowSubagentOf } from './delegate.js';
|
|
142
|
+
import { approvedCalls, grantedCalls, resumedDelegations } from './interrupts.js';
|
|
130
143
|
import { SelfCorrection } from './selfCorrection.js';
|
|
131
144
|
import { eitherSignal, runModelStep, stopOf } from './step.js';
|
|
132
145
|
import { taskNodeRun } from './taskMode.js';
|
|
@@ -342,7 +355,7 @@ async function runOwnTool(tool, args, context) {
|
|
|
342
355
|
throw new Error(`Error in tool '${tool.name}': ${message}`);
|
|
343
356
|
}
|
|
344
357
|
}
|
|
345
|
-
async function runCall(scope, call, tools, correction, confirmation) {
|
|
358
|
+
async function runCall(scope, call, tools, correction, confirmation, resume, ticket) {
|
|
346
359
|
const context = callContext(scope, call.id || undefined, confirmation);
|
|
347
360
|
const name = call.name ?? '';
|
|
348
361
|
const tool = name && tools.has(name) ? tools.get(name) : undefined;
|
|
@@ -363,17 +376,26 @@ async function runCall(scope, call, tools, correction, confirmation) {
|
|
|
363
376
|
// 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).
|
|
364
377
|
const subagent = subagentOf(tool);
|
|
365
378
|
const workflow = subagent ? undefined : workflowSubagentOf(tool);
|
|
366
|
-
const delegation = { ctx: scope.ctx, stateBase: scope.stateBase, signal: scope.signal, runLoop: runAgentLoop
|
|
379
|
+
const delegation = { ctx: scope.ctx, caller: scope.agent.name, stateBase: scope.stateBase, signal: scope.signal, runLoop: runAgentLoop };
|
|
367
380
|
let response;
|
|
368
381
|
let failure;
|
|
369
382
|
try {
|
|
383
|
+
// ADR 0116: a delegated call waits for its turn in the step's gate, and frees it as soon as the child's run ends.
|
|
384
|
+
if (ticket && (subagent || workflow))
|
|
385
|
+
await ticket.ready;
|
|
370
386
|
response = subagent
|
|
371
|
-
?
|
|
387
|
+
? resume
|
|
388
|
+
? await resumeSubagent(subagent, resume, context, delegation)
|
|
389
|
+
: await runSubagent(subagent, args, context, delegation)
|
|
372
390
|
: workflow
|
|
373
|
-
?
|
|
391
|
+
? resume
|
|
392
|
+
? await resumeWorkflowSubagent(workflow, resume, context, delegation)
|
|
393
|
+
: await runWorkflowSubagent(workflow, args, context, delegation)
|
|
374
394
|
: await runOwnTool(tool, args, context);
|
|
395
|
+
ticket?.release();
|
|
375
396
|
}
|
|
376
397
|
catch (e) {
|
|
398
|
+
ticket?.release();
|
|
377
399
|
failure = e instanceof Error ? e.message : e;
|
|
378
400
|
// Self-correction answers a thrown Error with reflection guidance in its place; a call waiting on a grant is not a failure.
|
|
379
401
|
const guided = isEmptyRecord(context.actions.requestedAuthConfigs) ? await correction?.failed(toolName, args, e) : undefined;
|
|
@@ -386,6 +408,12 @@ async function runCall(scope, call, tools, correction, confirmation) {
|
|
|
386
408
|
const provider = String(asked[0]?.credentialKey ?? '');
|
|
387
409
|
return { part: { functionResponse: { id: context.functionCallId, name: toolName, response: { result: CONSENT_TEXTS.pending(provider) } } }, actions: context.actions };
|
|
388
410
|
}
|
|
411
|
+
// A child that ended paused (ADR 0110): the call stays open, with no response, and the run ends paused on it.
|
|
412
|
+
if (response instanceof SubagentPause) {
|
|
413
|
+
if (context.functionCallId)
|
|
414
|
+
(scope.paused ??= []).push(context.functionCallId);
|
|
415
|
+
return isDefaultActions(context.actions) ? undefined : { actions: context.actions };
|
|
416
|
+
}
|
|
389
417
|
if (failure === undefined)
|
|
390
418
|
await correction?.answered(toolName, response);
|
|
391
419
|
// As ADK: a long-running call with no response answers nothing, even when it threw.
|
|
@@ -401,10 +429,33 @@ async function runCall(scope, call, tools, correction, confirmation) {
|
|
|
401
429
|
* The calls run, in parallel, as the one response event ADK stores for them
|
|
402
430
|
* (not yet stored); undefined when none answered. `confirmations` holds the
|
|
403
431
|
* person's answer for a pinned call an approval resumes, by call id.
|
|
432
|
+
*
|
|
433
|
+
* Delegated calls (a subagent, a nested workflow) run through the step's
|
|
434
|
+
* DelegationGate (ADR 0116): at most the agent's `maxConcurrency` at once,
|
|
435
|
+
* entered in call order, two calls to one subagent one after the other.
|
|
436
|
+
* Other calls start at once, as before. The event is built once every call
|
|
437
|
+
* has answered, its parts in call order, and the open calls a paused child
|
|
438
|
+
* left (scope.paused) are listed in call order too: neither depends on
|
|
439
|
+
* which call finished first.
|
|
404
440
|
*/
|
|
405
|
-
async function runCalls(scope, calls, tools, confirmations) {
|
|
441
|
+
async function runCalls(scope, calls, tools, confirmations, resumes) {
|
|
406
442
|
const order = scope.selfCorrection?.forCalls(scope.ctx.invocationId, calls.length);
|
|
407
|
-
const
|
|
443
|
+
const gate = new DelegationGate(scope.agent.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY);
|
|
444
|
+
const outcomes = (await Promise.all(calls.map((call, i) => {
|
|
445
|
+
const tool = tools.get(call.name ?? '');
|
|
446
|
+
// Entered here, synchronously and in call order, before any call runs (ADR 0116).
|
|
447
|
+
const delegated = subagentOf(tool) ?? workflowSubagentOf(tool);
|
|
448
|
+
const ticket = delegated ? gate.enter(delegated.name) : undefined;
|
|
449
|
+
return traceToolCall(call, tool, () => runCall(scope, call, tools, order?.call(i), call.id ? confirmations?.get(call.id) : undefined, call.id ? resumes?.get(call.id) : undefined, ticket)).finally(() => {
|
|
450
|
+
ticket?.release();
|
|
451
|
+
order?.release(i);
|
|
452
|
+
});
|
|
453
|
+
}))).filter((o) => !!o);
|
|
454
|
+
// The open calls, in call order rather than the order their children paused in.
|
|
455
|
+
if (scope.paused && scope.paused.length > 1) {
|
|
456
|
+
const at = (id) => calls.findIndex((c) => c.id === id);
|
|
457
|
+
scope.paused.sort((a, b) => at(a) - at(b));
|
|
458
|
+
}
|
|
408
459
|
if (outcomes.length === 0)
|
|
409
460
|
return undefined;
|
|
410
461
|
const base = { invocationId: scope.ctx.invocationId, author: scope.agent.name };
|
|
@@ -503,6 +554,32 @@ async function resumeGrants(agent, ctx, stateBase, selfCorrection) {
|
|
|
503
554
|
const auth = authEvent(scope, response);
|
|
504
555
|
return { response, ...(auth ? { auth } : {}) };
|
|
505
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Delegated calls the latest user message resumes (ADR 0110), before a step
|
|
559
|
+
* and after the approval resume: each open call whose child waits on what
|
|
560
|
+
* the message answers runs the child on with that answer, and their
|
|
561
|
+
* response is returned to be stored, with the open calls still paused:
|
|
562
|
+
* those the child paused again, and those the message did not answer.
|
|
563
|
+
* Undefined when the message answers none; 'stopped' when the turn stopped
|
|
564
|
+
* while they ran (nothing is stored).
|
|
565
|
+
*/
|
|
566
|
+
async function resumeDelegations(agent, ctx, stateBase, selfCorrection) {
|
|
567
|
+
const resumed = await resumedDelegations(agent, ctx, ctx.sessions);
|
|
568
|
+
if (!resumed)
|
|
569
|
+
return undefined;
|
|
570
|
+
const calls = resumed.calls.filter((c) => {
|
|
571
|
+
const tool = resumed.tools.get(c.name ?? '');
|
|
572
|
+
return subagentOf(tool) || workflowSubagentOf(tool);
|
|
573
|
+
});
|
|
574
|
+
if (calls.length === 0)
|
|
575
|
+
return undefined;
|
|
576
|
+
const signal = eitherSignal(ctx.signal, currentTurnSignal());
|
|
577
|
+
const scope = { agent, ctx, stateBase, selfCorrection, ...(signal ? { signal } : {}) };
|
|
578
|
+
const response = await runCalls(scope, calls, resumed.tools, undefined, resumed.answers);
|
|
579
|
+
if (signal?.aborted)
|
|
580
|
+
return 'stopped';
|
|
581
|
+
return { ...(response ? { response } : {}), paused: [...(scope.paused ?? []), ...resumed.waiting] };
|
|
582
|
+
}
|
|
506
583
|
// ── The model step, with partials and the fallback ───────────────────────────
|
|
507
584
|
/** Runs a step, yielding its partial events as they arrive; returns the step's result. */
|
|
508
585
|
async function* withPartials(start) {
|
|
@@ -640,6 +717,18 @@ async function* agentLoop(agent, ctx) {
|
|
|
640
717
|
lastEvent = await store(resumed);
|
|
641
718
|
yield lastEvent;
|
|
642
719
|
}
|
|
720
|
+
// Delegation hook (WS6-2a, ADR 0110): an answer to a pause inside a delegated call resumes that call before the step.
|
|
721
|
+
const redelegated = await resumeDelegations(agent, ctx, withStateOverlay(session.state, runTemp.values()), selfCorrection);
|
|
722
|
+
if (redelegated === 'stopped')
|
|
723
|
+
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
724
|
+
if (redelegated) {
|
|
725
|
+
if (redelegated.response) {
|
|
726
|
+
lastEvent = await store(redelegated.response);
|
|
727
|
+
yield lastEvent;
|
|
728
|
+
}
|
|
729
|
+
if (redelegated.paused.length > 0)
|
|
730
|
+
return { reason: 'paused', steps, lastEvent, pending: redelegated.paused };
|
|
731
|
+
}
|
|
643
732
|
// Compaction hook (WS2-9, lib/runtime/native/compaction.ts): with `context:`, the summary is stored before the step reads the history,
|
|
644
733
|
// before the step budget, as ADK's request processors run before its call count.
|
|
645
734
|
const compacted = await compactBeforeStep(agent, {
|
|
@@ -679,6 +768,8 @@ async function* agentLoop(agent, ctx) {
|
|
|
679
768
|
// As ADK: a turn that stopped while the calls ran (a long subagent run, say) stores no response.
|
|
680
769
|
if (scope.signal?.aborted)
|
|
681
770
|
return { reason: 'stopped', steps, lastEvent, stop: stopOf() };
|
|
771
|
+
// Delegated calls whose child ended paused (ADR 0110): they stay open, and the run ends paused on them once the step's responses are stored.
|
|
772
|
+
const delegated = scope.paused ?? [];
|
|
682
773
|
if (response) {
|
|
683
774
|
const auth = authEvent(scope, response);
|
|
684
775
|
if (auth) {
|
|
@@ -690,17 +781,22 @@ async function* agentLoop(agent, ctx) {
|
|
|
690
781
|
if (confirmation) {
|
|
691
782
|
lastEvent = await store(confirmation);
|
|
692
783
|
yield lastEvent;
|
|
693
|
-
return { reason: 'paused', steps, lastEvent, pending: [...(lastEvent.longRunningToolIds ?? [])] };
|
|
784
|
+
return { reason: 'paused', steps, lastEvent, pending: [...(lastEvent.longRunningToolIds ?? []), ...delegated] };
|
|
694
785
|
}
|
|
695
786
|
lastEvent = await store(response);
|
|
696
787
|
yield lastEvent;
|
|
697
788
|
// As ADK: the response asking for auth is final, and the run waits on its credential request (ADR 0085).
|
|
698
789
|
if (authIds.length > 0)
|
|
699
|
-
return { reason: 'paused', steps, lastEvent, pending: authIds };
|
|
790
|
+
return { reason: 'paused', steps, lastEvent, pending: [...authIds, ...delegated] };
|
|
700
791
|
stepEnd = lastEvent;
|
|
701
|
-
if (task?.finished)
|
|
792
|
+
if (task?.finished && delegated.length === 0)
|
|
702
793
|
return { reason: 'final', steps, lastEvent, output: task.output };
|
|
703
794
|
}
|
|
795
|
+
if (delegated.length > 0) {
|
|
796
|
+
const answered = new Set(getFunctionResponses(stepEnd).map((r) => r.id));
|
|
797
|
+
const longRunning = (modelEvent.longRunningToolIds ?? []).filter((id) => !answered.has(id));
|
|
798
|
+
return { reason: 'paused', steps, lastEvent, pending: [...longRunning, ...delegated] };
|
|
799
|
+
}
|
|
704
800
|
}
|
|
705
801
|
// ADK's runAsyncImpl: stop on a final event, unless it is an empty metadata event after tool calls.
|
|
706
802
|
const emptyMetadata = stepEnd.author === agent.name && !stepEnd.partial && !hasParts(stepEnd) && isDefaultActions(stepEnd.actions);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/native/checkpoint.ts — durable long runs: the run's sessions
|
|
3
|
+
* snapshotted at every step boundary, and a run restored from the last
|
|
4
|
+
* snapshot taking the next step (ADR 0113, ADR 0015).
|
|
5
|
+
*
|
|
6
|
+
* WHY this file exists:
|
|
7
|
+
* A background job (ADR 0015) runs as one turn on an in-process session
|
|
8
|
+
* store. A worker that stopped mid-job lost everything the job had done,
|
|
9
|
+
* and the next claim started again from the first model call. The turn
|
|
10
|
+
* runner's signature is pinned by every surface and the boundary suite
|
|
11
|
+
* (ADR 0024), so durability lives at the one seam the runner already
|
|
12
|
+
* takes: the SessionService. checkpointingSessions wraps the in-process
|
|
13
|
+
* store a run writes, hands a snapshot of every session to a sink at each
|
|
14
|
+
* step boundary, and restores a snapshot into a fresh store so that the
|
|
15
|
+
* runner, unchanged, continues where the snapshot stood. runDurableTurn
|
|
16
|
+
* is runSyndicateTurn with that store and a sink.
|
|
17
|
+
*
|
|
18
|
+
* A STEP BOUNDARY is an append that carries function responses after which
|
|
19
|
+
* every function call any session of the run holds has its response (calls
|
|
20
|
+
* listed in their event's longRunningToolIds, which wait on a person, aside).
|
|
21
|
+
* So a parent waiting on a delegated subagent is never at a boundary, the
|
|
22
|
+
* subagent's own steps are not either, and a final answer never is: a
|
|
23
|
+
* resumed run redoes the step that was in flight, and the last one.
|
|
24
|
+
*
|
|
25
|
+
* WHAT IT GUARANTEES:
|
|
26
|
+
* - A resumed run's stored events equal an uninterrupted run's, ids and
|
|
27
|
+
* times of the steps run after the resume aside: restored events keep
|
|
28
|
+
* their ids, times and invocation ids, and their state deltas are
|
|
29
|
+
* applied again in order (InProcessSessionService.append, applyEvent).
|
|
30
|
+
* - The opening message is stored once. A session whose first event is
|
|
31
|
+
* the run's own message (the run's session, a workflow route's child)
|
|
32
|
+
* is held back on restore: it does not exist until the runner creates
|
|
33
|
+
* it, so `resumedSession` reads as on a fresh run, and the runner's
|
|
34
|
+
* append of the message is answered with the stored events, the
|
|
35
|
+
* opening one returned in its place, onto the caller's own session
|
|
36
|
+
* object. Every other session (a delegated subagent's) is restored at
|
|
37
|
+
* once, before the runner starts.
|
|
38
|
+
* - A checkpoint is used only by the run that wrote it, for the message
|
|
39
|
+
* it was written for: its runId and the sha256 of the message's parts
|
|
40
|
+
* must match, and its events must parse (parseTurnEvents). Otherwise
|
|
41
|
+
* the run starts fresh.
|
|
42
|
+
* - The sink's save resolving false means the run no longer holds its
|
|
43
|
+
* claim (the job was cancelled or leased elsewhere): runDurableTurn
|
|
44
|
+
* aborts the run and reports lostClaim. A save that throws is reported
|
|
45
|
+
* to onSaveError and the run goes on; a later attempt resumes from the
|
|
46
|
+
* older checkpoint.
|
|
47
|
+
*
|
|
48
|
+
* LIMITS:
|
|
49
|
+
* - A dispatch syndicate's route classifier runs in a throwaway store, so
|
|
50
|
+
* its model call is not checkpointed and runs again on resume.
|
|
51
|
+
* - A dispatch route that is an agent answers through the transcript
|
|
52
|
+
* projection (ProjectedSessionService, lib/session/transcript.ts), which
|
|
53
|
+
* copies the session when the route opens it. The held opening session
|
|
54
|
+
* is empty at that open, so the copy is too; the route's append of the
|
|
55
|
+
* message is answered with the stored events, and the projection takes
|
|
56
|
+
* every event the real session gained, unprojected (the current turn),
|
|
57
|
+
* so the route resumes like a single agent. A workflow route writes its
|
|
58
|
+
* own child session and resumes. Checkpoints saved before this, which
|
|
59
|
+
* 1.1.0 set aside, restore the same way.
|
|
60
|
+
* - A task backend stores a checkpoint only up to its cap
|
|
61
|
+
* (TaskBackendOptions.checkpointMaxBytes, lib/tools/taskTools.ts,
|
|
62
|
+
* default 5 MiB of JSON); a save above it keeps the previous checkpoint
|
|
63
|
+
* and still answers whether the claim holds, so a huge run resumes from
|
|
64
|
+
* an older boundary.
|
|
65
|
+
* - Each resumed attempt has its own step budget (max_steps, the loop's
|
|
66
|
+
* call ceiling); attempts are bounded by the queue's MAX_ATTEMPTS.
|
|
67
|
+
* - `temp:` state lasts one invocation and is never stored, so it does not
|
|
68
|
+
* survive a resume.
|
|
69
|
+
* - A workflow walk resumes through its own rules (ADR 0094); its
|
|
70
|
+
* checkpoint is the sessions it walks.
|
|
71
|
+
* - A checkpoint holds the run's sessions, so it carries the user's text,
|
|
72
|
+
* model output and tool results: it lives only where the run's record
|
|
73
|
+
* lives, under the same owner, and is deleted with it. This module never
|
|
74
|
+
* clears one; the caller does when the run finishes.
|
|
75
|
+
*/
|
|
76
|
+
import type { TurnEvent } from '../events.ts';
|
|
77
|
+
import { InProcessSessionService } from '../sessions.ts';
|
|
78
|
+
import type { SessionService } from '../sessions.ts';
|
|
79
|
+
import type { MessagePart, SyndicateTurnOptions, SyndicateTurnResult } from '../syndicateTurn.ts';
|
|
80
|
+
export declare const CHECKPOINT_VERSION = 1;
|
|
81
|
+
/** One session of a run, as it stood at a step boundary. */
|
|
82
|
+
export interface CheckpointSession {
|
|
83
|
+
appName: string;
|
|
84
|
+
userId: string;
|
|
85
|
+
id: string;
|
|
86
|
+
/** The state given to create(), before any event. */
|
|
87
|
+
createdState: Record<string, unknown>;
|
|
88
|
+
events: TurnEvent[];
|
|
89
|
+
/** The first event is the run's own opening user message: restore holds the session back until the runner stores that message. */
|
|
90
|
+
opening: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** A run's sessions at its latest step boundary. */
|
|
93
|
+
export interface RunCheckpoint {
|
|
94
|
+
version: 1;
|
|
95
|
+
runId: string;
|
|
96
|
+
/** The run's own session id: a resumed run uses it again. */
|
|
97
|
+
sessionId: string;
|
|
98
|
+
/** sha256 hex of JSON.stringify(parts) of the run's message (strings as `{ text }`). */
|
|
99
|
+
openingHash: string;
|
|
100
|
+
/** Step boundaries recorded so far, across every attempt. */
|
|
101
|
+
steps: number;
|
|
102
|
+
/** ISO time of the save. */
|
|
103
|
+
savedAt: string;
|
|
104
|
+
sessions: CheckpointSession[];
|
|
105
|
+
}
|
|
106
|
+
/** Where a run's checkpoints go. save resolves false when the run no longer holds its claim (cancelled, or re-leased): the run must stop. */
|
|
107
|
+
export interface CheckpointSink {
|
|
108
|
+
load(): Promise<RunCheckpoint | null>;
|
|
109
|
+
save(checkpoint: RunCheckpoint): Promise<boolean>;
|
|
110
|
+
}
|
|
111
|
+
export interface CheckpointingOptions {
|
|
112
|
+
runId: string;
|
|
113
|
+
/** The run's own session id (the one runSyndicateTurn is given). */
|
|
114
|
+
sessionId: string;
|
|
115
|
+
/** The run's message. */
|
|
116
|
+
parts: MessagePart[];
|
|
117
|
+
/** The checkpoint to restore; ignored unless its runId and openingHash match and it parses. */
|
|
118
|
+
from?: RunCheckpoint | null;
|
|
119
|
+
save: (checkpoint: RunCheckpoint) => Promise<boolean>;
|
|
120
|
+
/** Called once, when a save resolves false. */
|
|
121
|
+
onLost?: () => void;
|
|
122
|
+
onSaveError?: (err: unknown) => void;
|
|
123
|
+
/** The store wrapped. Default a fresh InProcessSessionService. */
|
|
124
|
+
inner?: InProcessSessionService;
|
|
125
|
+
}
|
|
126
|
+
export type CheckpointingSessionService = SessionService & {
|
|
127
|
+
/** The checkpoint given was restored. */
|
|
128
|
+
readonly restored: boolean;
|
|
129
|
+
/** Step boundaries recorded, the restored checkpoint's included. */
|
|
130
|
+
readonly steps: number;
|
|
131
|
+
};
|
|
132
|
+
/** sha256 hex of the message's parts, as a checkpoint names its run's message. */
|
|
133
|
+
export declare function openingHashOf(parts: readonly MessagePart[]): string;
|
|
134
|
+
/**
|
|
135
|
+
* The checkpoint, checked, when it belongs to this run and this message;
|
|
136
|
+
* null otherwise. A stored checkpoint is data read back from a table or a
|
|
137
|
+
* file, so every field the restore reads is checked before it is trusted.
|
|
138
|
+
*/
|
|
139
|
+
export declare function usableCheckpoint(cp: unknown, runId: string, parts: readonly MessagePart[]): RunCheckpoint | null;
|
|
140
|
+
/**
|
|
141
|
+
* A session store that checkpoints a run: the in-process store `inner`,
|
|
142
|
+
* restored from `from` when it is this run's checkpoint for this message,
|
|
143
|
+
* saving a snapshot of every session at each step boundary (see the header).
|
|
144
|
+
*/
|
|
145
|
+
export declare function checkpointingSessions(opts: CheckpointingOptions): CheckpointingSessionService;
|
|
146
|
+
export type DurableTurnOptions = Omit<SyndicateTurnOptions, 'sessionService' | 'sessionId'> & {
|
|
147
|
+
/** Names the run: a checkpoint is restored only by the run that wrote it. */
|
|
148
|
+
runId: string;
|
|
149
|
+
checkpoints: CheckpointSink;
|
|
150
|
+
/** Called once when a save resolves false; the run is then aborted. */
|
|
151
|
+
onLost?: () => void;
|
|
152
|
+
onSaveError?: (err: unknown) => void;
|
|
153
|
+
/** The in-process store the run writes. Default a fresh one; a test passes its own to read the sessions afterwards. */
|
|
154
|
+
sessions?: InProcessSessionService;
|
|
155
|
+
};
|
|
156
|
+
export type DurableTurnResult = SyndicateTurnResult & {
|
|
157
|
+
/** The step boundaries the restored checkpoint had recorded; 0 on a fresh run. */
|
|
158
|
+
resumedFromStep: number;
|
|
159
|
+
/** A save resolved false: the run no longer held its claim and was aborted. */
|
|
160
|
+
lostClaim: boolean;
|
|
161
|
+
/** The run's own session id: the checkpoint's on a resume, a new UUID otherwise. */
|
|
162
|
+
sessionId: string;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* runSyndicateTurn, durable: the run's sessions live in a fresh in-process
|
|
166
|
+
* store, restored from the sink's checkpoint when it is this run's for this
|
|
167
|
+
* message, and checkpointed at every step boundary. A save that resolves
|
|
168
|
+
* false aborts the run (`lostClaim`). The caller clears the checkpoint when
|
|
169
|
+
* the run finishes; this never does.
|
|
170
|
+
*/
|
|
171
|
+
export declare function runDurableTurn(opts: DurableTurnOptions): Promise<DurableTurnResult>;
|