melchizedek-agents 1.0.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/.env.example +29 -2
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +524 -0
  4. package/DOCUMENTATION.md +178 -43
  5. package/ONBOARDING.md +339 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +6 -2
  8. package/config/agents/README.md +2 -2
  9. package/config/agents/examples/ares.yaml +6 -10
  10. package/config/agents/examples/assistant.yaml +6 -6
  11. package/config/agents/examples/augustin.yaml +9 -15
  12. package/config/agents/examples/cartographers.yaml +6 -6
  13. package/config/agents/examples/council.yaml +7 -7
  14. package/config/agents/examples/critic.yaml +24 -24
  15. package/config/agents/examples/harness.yaml +4 -4
  16. package/config/agents/examples/image_production.yaml +37 -35
  17. package/config/agents/examples/librarian.yaml +4 -4
  18. package/config/agents/examples/patient_advocate.yaml +6 -10
  19. package/config/agents/examples/pipeline.yaml +20 -19
  20. package/config/agents/examples/research.yaml +36 -43
  21. package/config/agents/examples/scribe.yaml +31 -32
  22. package/config/agents/examples/scriptorium.yaml +6 -6
  23. package/config/agents/examples/structured_critic.yaml +82 -0
  24. package/config/agents/examples/style_council.yaml +9 -11
  25. package/config/agents/examples/syndicate.yaml +3 -5
  26. package/config/agents/examples/tutor.yaml +2 -2
  27. package/config/agents/examples/weather.yaml +2 -2
  28. package/config/agents/syndicate.schema.json +859 -13
  29. package/config/agents/syndicateSchema.yaml +122 -73
  30. package/config/agents/templates/README.md +1 -1
  31. package/config/agents/templates/account_memory.yaml +4 -4
  32. package/config/agents/templates/case_desk.yaml +6 -6
  33. package/config/agents/templates/conversational.yaml +2 -2
  34. package/config/agents/templates/draft_review.yaml +6 -6
  35. package/config/agents/templates/intake_extractor.yaml +64 -62
  36. package/config/agents/templates/research_brief.yaml +6 -6
  37. package/config/agents/templates/research_desk.yaml +6 -6
  38. package/config/agents/templates/review_panel.yaml +8 -8
  39. package/config/agents/templates/support_triage.yaml +30 -29
  40. package/config/agents/templates/systems_operator.yaml +24 -5
  41. package/db/migrations/0014_durable_runs.sql +31 -0
  42. package/dist/lib/a2a/app.d.ts +27 -0
  43. package/dist/lib/a2a/app.js +64 -5
  44. package/dist/lib/a2a/executor.d.ts +7 -1
  45. package/dist/lib/a2a/executor.js +43 -5
  46. package/dist/lib/a2a/oauthSetup.d.ts +106 -0
  47. package/dist/lib/a2a/oauthSetup.js +259 -0
  48. package/dist/lib/agentDialect.d.ts +76 -0
  49. package/dist/lib/agentDialect.js +151 -0
  50. package/dist/lib/compile.d.ts +42 -6
  51. package/dist/lib/compile.js +97 -45
  52. package/dist/lib/compileNative.d.ts +11 -3
  53. package/dist/lib/compileNative.js +20 -1
  54. package/dist/lib/dispatch.d.ts +10 -5
  55. package/dist/lib/dispatch.js +10 -5
  56. package/dist/lib/doctor.d.ts +110 -1
  57. package/dist/lib/doctor.js +205 -9
  58. package/dist/lib/loadSyndicate.d.ts +54 -2
  59. package/dist/lib/loadSyndicate.js +22 -2
  60. package/dist/lib/memory/namespace.d.ts +6 -5
  61. package/dist/lib/memory/namespace.js +6 -5
  62. package/dist/lib/memory/providers.js +3 -4
  63. package/dist/lib/models/capabilities.d.ts +25 -1
  64. package/dist/lib/models/capabilities.js +79 -1
  65. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  66. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  67. package/dist/lib/models/claudeAdapter.js +4 -0
  68. package/dist/lib/models/contract.d.ts +5 -2
  69. package/dist/lib/models/gateway.d.ts +2 -1
  70. package/dist/lib/models/gateway.js +4 -2
  71. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  72. package/dist/lib/models/gatewayAdapter.js +8 -0
  73. package/dist/lib/models/geminiAdapter.js +8 -2
  74. package/dist/lib/models/genaiMapping.d.ts +8 -4
  75. package/dist/lib/models/genaiMapping.js +23 -9
  76. package/dist/lib/models/gptAdapter.d.ts +4 -2
  77. package/dist/lib/models/gptAdapter.js +11 -4
  78. package/dist/lib/models/grokAdapter.d.ts +3 -1
  79. package/dist/lib/models/grokAdapter.js +11 -3
  80. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  81. package/dist/lib/models/kimiAdapter.js +3 -3
  82. package/dist/lib/models/reasoning.d.ts +30 -2
  83. package/dist/lib/models/reasoning.js +56 -5
  84. package/dist/lib/observability/tracer.d.ts +8 -1
  85. package/dist/lib/observability/tracer.js +33 -5
  86. package/dist/lib/onboarding.d.ts +134 -0
  87. package/dist/lib/onboarding.js +539 -0
  88. package/dist/lib/runtime/approvals.d.ts +8 -0
  89. package/dist/lib/runtime/approvals.js +4 -3
  90. package/dist/lib/runtime/credentials.d.ts +6 -0
  91. package/dist/lib/runtime/native/agentLoop.d.ts +23 -3
  92. package/dist/lib/runtime/native/agentLoop.js +107 -11
  93. package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
  94. package/dist/lib/runtime/native/checkpoint.js +359 -0
  95. package/dist/lib/runtime/native/delegate.d.ts +222 -18
  96. package/dist/lib/runtime/native/delegate.js +390 -39
  97. package/dist/lib/runtime/native/interrupts.d.ts +184 -2
  98. package/dist/lib/runtime/native/interrupts.js +391 -1
  99. package/dist/lib/runtime/native/request.d.ts +13 -3
  100. package/dist/lib/runtime/native/request.js +28 -4
  101. package/dist/lib/runtime/native/telemetry.js +10 -15
  102. package/dist/lib/runtime/questions.d.ts +7 -4
  103. package/dist/lib/runtime/sessions.d.ts +1 -1
  104. package/dist/lib/runtime/syndicateTurn.d.ts +11 -3
  105. package/dist/lib/runtime/syndicateTurn.js +353 -68
  106. package/dist/lib/runtime/turnControl.d.ts +56 -0
  107. package/dist/lib/runtime/turnControl.js +10 -0
  108. package/dist/lib/session/transcript.d.ts +9 -0
  109. package/dist/lib/session/transcript.js +20 -1
  110. package/dist/lib/skills.d.ts +18 -0
  111. package/dist/lib/skills.js +73 -1
  112. package/dist/lib/storage/postgres/index.d.ts +12 -2
  113. package/dist/lib/storage/postgres/index.js +8 -3
  114. package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
  115. package/dist/lib/storage/postgres/taskQueue.js +29 -5
  116. package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
  117. package/dist/lib/storage/postgres/taskStore.js +74 -8
  118. package/dist/lib/syndicateSchema.d.ts +316 -1
  119. package/dist/lib/syndicateSchema.js +203 -29
  120. package/dist/lib/tools/auth.d.ts +20 -6
  121. package/dist/lib/tools/auth.js +21 -5
  122. package/dist/lib/tools/credentialEnv.d.ts +28 -0
  123. package/dist/lib/tools/credentialEnv.js +52 -0
  124. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  125. package/dist/lib/tools/credentialHosts.js +165 -0
  126. package/dist/lib/tools/credentialStore.js +9 -2
  127. package/dist/lib/tools/credentialUses.d.ts +92 -0
  128. package/dist/lib/tools/credentialUses.js +112 -0
  129. package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
  130. package/dist/lib/tools/mcpToolFactory.js +179 -21
  131. package/dist/lib/tools/oauthConsent.js +8 -1
  132. package/dist/lib/tools/oauthHosts.d.ts +73 -0
  133. package/dist/lib/tools/oauthHosts.js +154 -0
  134. package/dist/lib/tools/oauthTools.d.ts +242 -0
  135. package/dist/lib/tools/oauthTools.js +447 -0
  136. package/dist/lib/tools/openapiTools.d.ts +11 -6
  137. package/dist/lib/tools/openapiTools.js +88 -38
  138. package/dist/lib/tools/taskTools.d.ts +61 -6
  139. package/dist/lib/tools/taskTools.js +145 -21
  140. package/dist/lib/workflow/turn.d.ts +22 -3
  141. package/dist/lib/workflow/turn.js +151 -31
  142. package/dist/lib/workflow.d.ts +14 -7
  143. package/dist/lib/workflow.js +14 -7
  144. package/dist/lib/workflowConfig.d.ts +6 -0
  145. package/dist/scripts/a2a_server.d.ts +36 -0
  146. package/dist/scripts/a2a_server.js +131 -1
  147. package/dist/scripts/assistant_worker.d.ts +12 -0
  148. package/dist/scripts/assistant_worker.js +81 -22
  149. package/dist/scripts/doctor.d.ts +7 -2
  150. package/dist/scripts/doctor.js +9 -4
  151. package/dist/scripts/setup.d.ts +23 -0
  152. package/dist/scripts/setup.js +154 -0
  153. package/dist/scripts/skills_install.d.ts +3 -1
  154. package/dist/scripts/skills_install.js +17 -2
  155. package/dist/scripts/yaml_codemod.d.ts +62 -0
  156. package/dist/scripts/yaml_codemod.js +652 -0
  157. package/package.json +8 -2
  158. package/skills/README.md +11 -2
  159. package/skills/briefs/_shared.md +1 -1
  160. package/skills/briefs/melchizedek-author.md +7 -5
  161. package/skills/briefs/melchizedek-models.md +7 -4
  162. package/skills/briefs/melchizedek.md +1 -0
  163. package/skills/briefs/skills-readme.md +3 -1
  164. package/skills/melchizedek/SKILL.md +1 -0
  165. package/skills/melchizedek-author/SKILL.md +7 -4
  166. package/skills/melchizedek-author/assets/minimal.yaml +13 -13
  167. package/skills/melchizedek-models/SKILL.md +11 -4
  168. package/skills/melchizedek-onboard/SKILL.md +49 -0
  169. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  170. package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
  171. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  172. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -40,6 +40,7 @@ import { isWorkflowSyndicate, nodeKind } from './workflowConfig.js';
40
40
  import { loadSyndicate, nestedLoader } from './loadSyndicate.js';
41
41
  import { REASONING_OLDER_SPELLING } from './syndicateSchema.js';
42
42
  import { reasoningConfig } from './models/reasoning.js';
43
+ import { instructionFor, toEngineAgent } from './agentDialect.js';
43
44
  import { resolveTools as resolveNamedTools } from './toolRegistry.js';
44
45
  import { createMcpTools } from './tools/mcpToolFactory.js';
45
46
  import { requireApproval, toolOf } from './tools/tool.js';
@@ -144,7 +145,7 @@ export function examplesTool(examples) {
144
145
  const tool = examplesInstructionTool(examples);
145
146
  return tool ? [tool] : [];
146
147
  }
147
- async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, examples, mcpAllowed) {
148
+ async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, examples, mcpAllowed, mcpAuth) {
148
149
  const tools = [...resolveNamedTools(toolNames, opts.onUnknownTool), ...examplesTool(examples)];
149
150
  // OpenAPI operations become tools here, so require_approval can name them.
150
151
  for (const entry of openapi ?? []) {
@@ -158,7 +159,8 @@ async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, example
158
159
  }
159
160
  if (mcpServerUrl) {
160
161
  opts.log?.(`Loading MCP tools: ${mcpServerUrl}`);
161
- const offered = await createMcpTools(mcpServerUrl);
162
+ // An authorization_code server lists nothing at startup: its tools are the mcp_tools names, each on its user's own connection (ADR 0112).
163
+ const offered = await createMcpTools(mcpServerUrl, { ...(mcpAllowed ? { tools: mcpAllowed } : {}), ...(mcpAuth?.oauth2 ? { oauth2: mcpAuth.oauth2 } : {}) });
162
164
  // mcp_tools: only the named tools are exposed (a server's list is its
163
165
  // own to change); a name the server does not offer is reported, and a
164
166
  // gate on it fails the compile in gateTools.
@@ -201,9 +203,11 @@ async function withSkills(instruction, tools, skills, agentName, opts) {
201
203
  export function agentGates(agent) {
202
204
  return !!agent?.require_approval?.length || agent?.skills?.scripts === 'local';
203
205
  }
204
- function specOf(yaml, name, description, instruction, tools, opts) {
205
- const resolvedModel = (opts.resolveModel ?? ((m) => m))(yaml.model);
206
- const modelId = modelIdOf(yaml.model, resolvedModel);
206
+ function resolveAgentModel(model, opts) {
207
+ const resolvedModel = (opts.resolveModel ?? ((m) => m))(model);
208
+ return { resolvedModel, modelId: modelIdOf(model, resolvedModel) };
209
+ }
210
+ function specOf(yaml, name, description, instruction, tools, { resolvedModel, modelId }) {
207
211
  const spec = {
208
212
  name,
209
213
  instruction,
@@ -240,12 +244,6 @@ function specOf(yaml, name, description, instruction, tools, opts) {
240
244
  return spec;
241
245
  }
242
246
  const asTools = (tools) => tools.map((tool) => ({ kind: 'tool', tool }));
243
- /**
244
- * A nested `yaml_reference:` syndicate, loaded. A nested syndicate runs
245
- * inside a tool call (or as a route whose own subagents do): a pause there
246
- * cannot reach the caller (ADR 0028), so one that declares approval gates is
247
- * refused.
248
- */
249
247
  /** Where the yaml_reference chain a compile is inside is kept: an own symbol on the options, so a spread copy keeps it. */
250
248
  const NESTING = Symbol('melchizedek.compile.nesting');
251
249
  /** The deepest a yaml_reference chain may go: well past any syndicate the engine ships, short of the stack. */
@@ -265,13 +263,45 @@ function nestedOptions(ref, opts) {
265
263
  throw new Error(`${ref}: nested syndicates go deeper than ${MAX_NESTING_DEPTH} levels (${[...chain, ref].join(' → ')}).`);
266
264
  return { ...opts, [NESTING]: [...chain, ref] };
267
265
  }
266
+ /**
267
+ * A nested `yaml_reference:` syndicate, loaded. Every pause inside one
268
+ * reaches the turn wherever it runs (ADR 0110, ADR 0111, ADR 0119,
269
+ * ADR 0120): a nested delegate syndicate's through the open call, a nested
270
+ * workflow's through its walk, a nested dispatch syndicate's through its
271
+ * own turn, whose routes run as they run at the top. Nothing is refused
272
+ * here any more.
273
+ */
268
274
  function loadNestedSyndicate(ref, opts) {
269
275
  opts.log?.(`Loading nested syndicate: ${ref}`);
270
- const nested = (opts.loadNested ?? loadSyndicate)(ref);
271
- if (declaresApprovals(nested)) {
272
- throw new Error(`${ref}: approval gates (require_approval) are not supported inside a nested syndicate.`);
276
+ return (opts.loadNested ?? loadSyndicate)(ref);
277
+ }
278
+ /**
279
+ * A nested dispatch syndicate as its entry runs it (ADR 0120). Its routes
280
+ * compile when its turn routes to one, as at the top, so a route that
281
+ * cannot compile fails that turn; the references below it are followed
282
+ * here (loaded, not compiled: no tool is resolved and no MCP server is
283
+ * reached), so a reference cycle or a depth past the limit is refused at
284
+ * load, by name, before any model call. A route may not carry the entry's
285
+ * name: the pause walk tells the nested syndicate from its routes by name.
286
+ */
287
+ function compileDispatchEntry(subCfg, nested, nestedOpts) {
288
+ const ref = subCfg.yaml_reference;
289
+ for (const route of nested.subagents ?? []) {
290
+ if (route.name === subCfg.name) {
291
+ throw new Error(`${ref}: the route '${route.name}' has the name its caller gives the nested syndicate; rename the route or the entry.`);
292
+ }
293
+ }
294
+ followReferences(nested, nestedOpts);
295
+ return { name: subCfg.name, description: subCfg.description ?? '', config: nested, ref, compile: nestedOpts };
296
+ }
297
+ /** Loads every `yaml_reference` below `config`, depth first, through nestedOptions: a cycle or a chain past the limit throws by name. */
298
+ function followReferences(config, opts) {
299
+ for (const entry of config.subagents ?? []) {
300
+ if (!entry.yaml_reference || entry.a2a_agent_url)
301
+ continue;
302
+ const below = nestedOptions(entry.yaml_reference, opts);
303
+ followReferences(loadNestedSyndicate(entry.yaml_reference, below), below);
273
304
  }
274
- return nested;
275
305
  }
276
306
  /**
277
307
  * The spec of a workflow syndicate: every agent through compileSubagentSpec
@@ -279,34 +309,34 @@ function loadNestedSyndicate(ref, opts) {
279
309
  * lookup for its tool nodes. `name` and `description` name a nested one
280
310
  * after its subagent entry (ADR 0098); `ref` is the file it came from.
281
311
  *
282
- * A nested workflow runs inside its caller's tool call, so a pause inside it
283
- * could not reach the caller (ADR 0028, as for any nested syndicate): an
284
- * `ask_user` node is refused by name here.
312
+ * A nested workflow's pauses (an `ask_user` node, a gate) reach the turn
313
+ * wherever it runs (ADR 0111, ADR 0119). `delegated` is accepted for the
314
+ * callers that pass it and no longer changes what compiles.
285
315
  */
286
- export async function compileWorkflowSpec(config, opts = {}, name, description, ref) {
316
+ export async function compileWorkflowSpec(config, opts = {}, name, description, ref, delegated = false) {
287
317
  if (!isWorkflowSyndicate(config))
288
318
  throw new Error(`${config.syndicate_name}: no workflow block`);
289
- if (name) {
290
- const asking = Object.entries(config.workflow.nodes ?? {}).find(([, node]) => nodeKind(node) === 'ask_user');
291
- if (asking) {
292
- throw new Error(`${ref ?? config.syndicate_name}: the ask_user node '${asking[0]}' pauses for a person, which a workflow nested in another syndicate (${name}) cannot carry to its caller; run the workflow as its own syndicate, or remove the node.`);
293
- }
294
- }
295
319
  const workflowName = name || config.syndicate_name;
296
320
  const agents = [];
297
321
  const workflows = [];
322
+ const dispatches = [];
298
323
  for (const yaml of [{ description: '', ...config.orchestrator }, ...(config.subagents ?? [])]) {
299
324
  const entry = await compileEntrySpec(yaml, opts);
300
325
  if (entry.kind === 'workflow')
301
326
  workflows.push({ yaml, workflow: entry.workflow });
327
+ else if (entry.kind === 'dispatch')
328
+ dispatches.push({ yaml, dispatch: entry.dispatch });
302
329
  else
303
330
  agents.push({ yaml, spec: entry.spec });
304
331
  }
305
- // A map runs one agent per item; a nested graph is not an agent a map item can be (ADR 0106).
332
+ // A map runs one agent per item; a nested graph or a nested dispatch syndicate is not an agent a map item can be (ADR 0106, ADR 0120).
306
333
  for (const [node, entry] of Object.entries(config.workflow.nodes ?? {})) {
307
- const mapped = nodeKind(entry) === 'map' ? workflows.find((w) => w.yaml.name === entry.map) : undefined;
334
+ if (nodeKind(entry) !== 'map')
335
+ continue;
336
+ const mapped = workflows.find((w) => w.yaml.name === entry.map) ?? dispatches.find((d) => d.yaml.name === entry.map);
308
337
  if (mapped) {
309
- throw new Error(`${ref ?? config.syndicate_name}: the map node '${node}' runs '${mapped.yaml.name}', a workflow syndicate (${mapped.yaml.yaml_reference}); a map runs one agent per item, so make the workflow a node of its own.`);
338
+ const what = 'workflow' in mapped ? 'a workflow syndicate' : 'a dispatch syndicate';
339
+ throw new Error(`${ref ?? config.syndicate_name}: the map node '${node}' runs '${mapped.yaml.name}', ${what} (${mapped.yaml.yaml_reference}); a map runs one agent per item, so make it a node of its own.`);
310
340
  }
311
341
  }
312
342
  return {
@@ -315,6 +345,7 @@ export async function compileWorkflowSpec(config, opts = {}, name, description,
315
345
  config: { ...config, syndicate_name: workflowName },
316
346
  agents,
317
347
  workflows,
348
+ dispatches,
318
349
  resolveTool: (tool) => resolveNamedTools([tool], opts.onUnknownTool)[0],
319
350
  };
320
351
  }
@@ -328,14 +359,20 @@ export function workflowAgentSpecs(spec) {
328
359
  * as its whole graph under the entry's name and description. A delegated
329
360
  * subagent, a dispatch route and a workflow node all compile through here,
330
361
  * so the graph a file describes is the graph that runs (ADR 0106).
362
+ * A nested workflow pauses wherever it runs (ADR 0111, ADR 0119);
363
+ * `delegated` is accepted for the callers that pass it and no longer
364
+ * changes what compiles.
331
365
  */
332
- export async function compileEntrySpec(subCfg, opts = {}) {
366
+ export async function compileEntrySpec(subCfg, opts = {}, delegated = false) {
333
367
  if (subCfg.yaml_reference && !subCfg.a2a_agent_url) {
334
368
  const nestedOpts = nestedOptions(subCfg.yaml_reference, opts);
335
369
  const nested = loadNestedSyndicate(subCfg.yaml_reference, nestedOpts);
336
370
  if (isWorkflowSyndicate(nested)) {
337
- return { kind: 'workflow', workflow: await compileWorkflowSpec(nested, nestedOpts, subCfg.name, subCfg.description, subCfg.yaml_reference) };
371
+ return { kind: 'workflow', workflow: await compileWorkflowSpec(nested, nestedOpts, subCfg.name, subCfg.description, subCfg.yaml_reference, delegated) };
338
372
  }
373
+ // A nested dispatch syndicate classifies and routes as at the top (ADR 0120), wherever it runs.
374
+ if (isDispatchSyndicate(nested))
375
+ return { kind: 'dispatch', dispatch: compileDispatchEntry(subCfg, nested, nestedOpts) };
339
376
  return { kind: 'agent', spec: await compileSpec(nested, nestedOpts, subCfg.name, subCfg.description) };
340
377
  }
341
378
  return { kind: 'agent', spec: await compileSubagentSpec(subCfg, opts) };
@@ -369,12 +406,19 @@ export async function compileSubagentSpec(subCfg, opts = {}) {
369
406
  if (isWorkflowSyndicate(nested)) {
370
407
  throw new Error(`${subCfg.yaml_reference}: '${subCfg.name}' is a workflow syndicate, which runs as its whole graph, not as one agent; compile the entry with compileEntrySpec (ADR 0106).`);
371
408
  }
409
+ if (isDispatchSyndicate(nested)) {
410
+ throw new Error(`${subCfg.yaml_reference}: '${subCfg.name}' is a dispatch syndicate, which runs as its own turn (its classifier, then a route), not as one agent; compile the entry with compileEntrySpec (ADR 0120).`);
411
+ }
372
412
  return compileSpec(nested, nestedOpts, subCfg.name, subCfg.description);
373
413
  }
374
- const gated = gateTools(await resolveAgentTools(subCfg.tools, subCfg.mcp_server_url, opts, subCfg.openapi, subCfg.examples, subCfg.mcp_tools), subCfg.require_approval, subCfg.name);
375
- const { instruction, tools } = await withSkills(subCfg.instruction ?? '', gated, subCfg.skills, subCfg.name, opts);
376
- logCapabilities(opts, subCfg.name, subCfg.model, subCfg.tools);
377
- return specOf(subCfg, subCfg.name, subCfg.description, instruction, asTools(tools), opts);
414
+ // YAML v2 (ADR 0115): the engine form, for a config built in code (a loaded one already is).
415
+ const agent = toEngineAgent(subCfg);
416
+ const gated = gateTools(await resolveAgentTools(agent.tools, agent.mcp_server_url, opts, agent.openapi, agent.examples, agent.mcp_tools, agent.mcp_auth), agent.require_approval, agent.name);
417
+ const model = resolveAgentModel(agent.model, opts);
418
+ // model_overrides for the provider the agent runs on, before the skills index is appended.
419
+ const { instruction, tools } = await withSkills(instructionFor(agent, model.modelId), gated, agent.skills, agent.name, opts);
420
+ logCapabilities(opts, agent.name, agent.model, agent.tools);
421
+ return specOf(agent, agent.name, agent.description, instruction, asTools(tools), model);
378
422
  }
379
423
  /**
380
424
  * The spec of a syndicate's orchestrator. In DELEGATE mode its subagents
@@ -401,16 +445,24 @@ export async function compileSpec(config, opts = {}, overrideName, overrideDescr
401
445
  opts.log?.(`Remote A2A agent: ${subCfg.name} → ${subCfg.a2a_agent_url}`);
402
446
  return { kind: 'remote', name: subCfg.name, description: subCfg.description, url: subCfg.a2a_agent_url };
403
447
  }
404
- const entry = await compileEntrySpec(subCfg, opts);
405
- return entry.kind === 'workflow' ? entry : { kind: 'agent', agent: entry.spec };
448
+ // A delegated subagent: a pause inside it, a nested workflow's included, reaches the caller (ADR 0110, ADR 0111).
449
+ const entry = await compileEntrySpec(subCfg, opts, true);
450
+ return entry.kind === 'agent' ? { kind: 'agent', agent: entry.spec } : entry;
406
451
  }));
407
- const name = overrideName || config.orchestrator.name;
408
- // Orchestrator tools are registry names only — no entrypoint has ever
409
- // attached an MCP server to an orchestrator, and this compiler preserves
410
- // that exactly rather than widening the contract in passing.
411
- const own = gateTools(await resolveAgentTools(config.orchestrator.tools, undefined, opts, config.orchestrator.openapi, config.orchestrator.examples), config.orchestrator.require_approval, name);
412
- // The skills toolset goes last, after the delegations and the agent's own tools.
413
- const { instruction, tools } = await withSkills(config.orchestrator.instruction, own, config.orchestrator.skills, name, opts);
414
- logCapabilities(opts, name, config.orchestrator.model, config.orchestrator.tools);
415
- return specOf(config.orchestrator, name, overrideDescription || config.orchestrator.description, instruction, [...delegated, ...asTools(tools)], opts);
452
+ const orchestrator = toEngineAgent(config.orchestrator); // YAML v2 (ADR 0115), for a config built in code
453
+ const name = overrideName || orchestrator.name;
454
+ // The orchestrator's own tools resolve as a subagent's do: registry
455
+ // names, OpenAPI operations, and the MCP server's tools (narrowed by
456
+ // mcp_tools, under its mcp_auth grant), so a one-agent syndicate can reach an MCP server.
457
+ const own = gateTools(await resolveAgentTools(orchestrator.tools, orchestrator.mcp_server_url, opts, orchestrator.openapi, orchestrator.examples, orchestrator.mcp_tools, orchestrator.mcp_auth), orchestrator.require_approval, name);
458
+ const model = resolveAgentModel(orchestrator.model, opts);
459
+ // The skills toolset goes last, after the delegations and the agent's own tools; the
460
+ // skills index goes after the instruction with its model_overrides entry applied.
461
+ const { instruction, tools } = await withSkills(instructionFor(orchestrator, model.modelId), own, orchestrator.skills, name, opts);
462
+ logCapabilities(opts, name, orchestrator.model, orchestrator.tools);
463
+ const spec = specOf(orchestrator, name, overrideDescription || orchestrator.description, instruction, [...delegated, ...asTools(tools)], model);
464
+ // The syndicate's own key: a nested syndicate's orchestrator runs its delegations under its own file's (ADR 0116).
465
+ if (config.max_concurrency !== undefined)
466
+ spec.maxConcurrency = config.max_concurrency;
467
+ return spec;
416
468
  }
@@ -32,13 +32,14 @@
32
32
  * caller's. ADK classes are told apart by ADK's own Symbol.for marks, so
33
33
  * this file imports nothing from ADK.
34
34
  */
35
- import type { AgentSpec, CompileOptions, WorkflowSpec } from './compile.ts';
35
+ import type { AgentSpec, CompileOptions, DispatchSpec, WorkflowSpec } from './compile.ts';
36
36
  import type { SubagentYamlConfig, SyndicateYamlConfig } from './loadSyndicate.ts';
37
37
  import type { ModelAdapter } from './models/contract.ts';
38
38
  import type { WorkflowSubagent } from './runtime/native/delegate.ts';
39
39
  import type { NativeAgent } from './runtime/native/request.ts';
40
40
  export { UnsupportedOnRuntimeError, unsupportedOnNative } from './runtime/runtimeFlag.ts';
41
41
  import type { WorkflowGraph } from './workflow/graph.ts';
42
+ import type { NestedRun } from './workflow/turn.ts';
42
43
  /**
43
44
  * The class name of an ADK model the runtime cannot run, else undefined:
44
45
  * any ADK BaseLlm (ADR 0107; the ADK runtime that ran one left in 1.0.0).
@@ -67,8 +68,8 @@ export interface NativeWorkflow {
67
68
  graph: WorkflowGraph;
68
69
  agents: Map<string, NativeAgent>;
69
70
  resolveTool: (name: string) => unknown;
70
- /** The nodes that are a nested workflow syndicate, each compiled the same way, by YAML name (ADR 0106). */
71
- workflows: Map<string, NativeWorkflow>;
71
+ /** The nodes that are a nested workflow syndicate, each compiled the same way (ADR 0106), or a nested dispatch syndicate, its own turn (ADR 0120), by YAML name. */
72
+ workflows: Map<string, NativeWorkflow | NestedRun>;
72
73
  }
73
74
  /**
74
75
  * A workflow spec for the walk: the graph, every agent compiled from its
@@ -82,6 +83,13 @@ export declare function compileNativeWorkflow(spec: WorkflowSpec): NativeWorkflo
82
83
  * session lib/runtime/native/delegate.ts opened.
83
84
  */
84
85
  export declare function workflowSubagentOf(workflow: NativeWorkflow): WorkflowSubagent;
86
+ /**
87
+ * A nested dispatch syndicate as the delegated subagent the native loop
88
+ * calls, or the node a walk runs (ADR 0120): one call runs it as its own
89
+ * turn (nestedDispatchWalk) on the child session lib/runtime/native/delegate.ts
90
+ * (or lib/workflow/turn.ts) opened.
91
+ */
92
+ export declare function dispatchSubagentOf(dispatch: DispatchSpec): WorkflowSubagent & NestedRun;
85
93
  /** One subagent entry (a dispatch route) for the native loop: compileSubagentSpec, then compileNative. */
86
94
  export declare function compileNativeSubagent(subCfg: SubagentYamlConfig, opts?: CompileOptions): Promise<NativeAgent>;
87
95
  /** What nativeAdapterFor reads from a spec: its model resolution, the other ids it calls, and its delegated subagents. */
@@ -35,7 +35,7 @@
35
35
  import { remoteAgentOwnTool } from './a2a/remoteAgent.js';
36
36
  import { compileSpec, compileSubagentSpec, workflowAgentSpecs } from './compile.js';
37
37
  import { resolveAdapter } from './models/registry.js';
38
- import { subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
38
+ import { nestedDispatchWalk, subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
39
39
  import { unsupportedOnNative } from './runtime/runtimeFlag.js';
40
40
  export { UnsupportedOnRuntimeError, unsupportedOnNative } from './runtime/runtimeFlag.js';
41
41
  import { instructionToolOf, toolOf, toolsetOf } from './tools/tool.js';
@@ -108,6 +108,8 @@ export function compileNative(spec) {
108
108
  tools.push(subagentTool(compileNative(entry.agent)));
109
109
  else if (entry.kind === 'workflow')
110
110
  tools.push(workflowSubagentTool(workflowSubagentOf(compileNativeWorkflow(entry.workflow))));
111
+ else if (entry.kind === 'dispatch')
112
+ tools.push(workflowSubagentTool(dispatchSubagentOf(entry.dispatch)));
111
113
  else if (entry.kind === 'remote')
112
114
  tools.push(remoteAgentOwnTool({ name: entry.name, description: entry.description, url: entry.url }));
113
115
  else
@@ -145,6 +147,8 @@ export function compileNative(spec) {
145
147
  agent.fallbackModel = spec.fallbackModel;
146
148
  if (spec.context)
147
149
  agent.context = spec.context;
150
+ if (spec.maxConcurrency !== undefined)
151
+ agent.maxConcurrency = spec.maxConcurrency;
148
152
  return agent;
149
153
  }
150
154
  /**
@@ -180,6 +184,8 @@ export function compileNativeWorkflow(spec) {
180
184
  const workflows = new Map();
181
185
  for (const { yaml, workflow } of spec.workflows)
182
186
  workflows.set(yaml.name, compileNativeWorkflow(workflow));
187
+ for (const { yaml, dispatch } of spec.dispatches ?? [])
188
+ workflows.set(yaml.name, dispatchSubagentOf(dispatch));
183
189
  refuseUnrunnableNodes(graph, spec.resolveTool);
184
190
  return { name: spec.name, description: spec.description, graph, agents, resolveTool: spec.resolveTool, workflows };
185
191
  }
@@ -212,6 +218,19 @@ export function workflowSubagentOf(workflow) {
212
218
  }),
213
219
  };
214
220
  }
221
+ /**
222
+ * A nested dispatch syndicate as the delegated subagent the native loop
223
+ * calls, or the node a walk runs (ADR 0120): one call runs it as its own
224
+ * turn (nestedDispatchWalk) on the child session lib/runtime/native/delegate.ts
225
+ * (or lib/workflow/turn.ts) opened.
226
+ */
227
+ export function dispatchSubagentOf(dispatch) {
228
+ return {
229
+ name: dispatch.name,
230
+ ...(dispatch.description ? { description: dispatch.description } : {}),
231
+ walk: (run) => nestedDispatchWalk({ name: dispatch.name, config: dispatch.config, compile: dispatch.compile }, run),
232
+ };
233
+ }
215
234
  /** One subagent entry (a dispatch route) for the native loop: compileSubagentSpec, then compileNative. */
216
235
  export async function compileNativeSubagent(subCfg, opts = {}) {
217
236
  return compileNative(await compileSubagentSpec(subCfg, opts));
@@ -26,11 +26,16 @@
26
26
  * be logged, traced, and streamed to a waiting user as progress. In DELEGATE
27
27
  * mode the choice is only ever implicit in a tool call.
28
28
  *
29
- * ── Why the router MUST be tool-less ──────────────────────────────────────
30
- * The classifier holds an `outputSchema`, and an agent holding one answers
31
- * with that JSON and nothing else (see config/agents/examples/critic.yaml; ADK
32
- * refused the combination outright). So the classifier is a leaf with no
33
- * subagent tools, and dispatch happens in code where it belongs.
29
+ * ── Why the router is tool-less ────────────────────────────────────────────
30
+ * The classifier holds an `outputSchema`, and an agent holding one ends its
31
+ * turn on that JSON. Its JSON names a route for code to run, so it holds no
32
+ * subagent tools: dispatch happens in code where it belongs. This is a choice
33
+ * of method, not a constraint of the engine: an orchestrator may hold an
34
+ * `outputSchema` beside its subagent tools, delegate, and answer in that
35
+ * schema itself (ADR 0109; ADK refused the combination, the engine does not).
36
+ * Plan-dispatch is for a turn whose answer IS the specialist's; a schema on a
37
+ * delegating orchestrator is for a turn whose answer is the orchestrator's
38
+ * own structured judgment of what its team returned.
34
39
  *
35
40
  * ── Fail-static ───────────────────────────────────────────────────────────
36
41
  * Every failure mode here resolves to `default_route` and answers the user.
@@ -26,11 +26,16 @@
26
26
  * be logged, traced, and streamed to a waiting user as progress. In DELEGATE
27
27
  * mode the choice is only ever implicit in a tool call.
28
28
  *
29
- * ── Why the router MUST be tool-less ──────────────────────────────────────
30
- * The classifier holds an `outputSchema`, and an agent holding one answers
31
- * with that JSON and nothing else (see config/agents/examples/critic.yaml; ADK
32
- * refused the combination outright). So the classifier is a leaf with no
33
- * subagent tools, and dispatch happens in code where it belongs.
29
+ * ── Why the router is tool-less ────────────────────────────────────────────
30
+ * The classifier holds an `outputSchema`, and an agent holding one ends its
31
+ * turn on that JSON. Its JSON names a route for code to run, so it holds no
32
+ * subagent tools: dispatch happens in code where it belongs. This is a choice
33
+ * of method, not a constraint of the engine: an orchestrator may hold an
34
+ * `outputSchema` beside its subagent tools, delegate, and answer in that
35
+ * schema itself (ADR 0109; ADK refused the combination, the engine does not).
36
+ * Plan-dispatch is for a turn whose answer IS the specialist's; a schema on a
37
+ * delegating orchestrator is for a turn whose answer is the orchestrator's
38
+ * own structured judgment of what its team returned.
34
39
  *
35
40
  * ── Fail-static ───────────────────────────────────────────────────────────
36
41
  * Every failure mode here resolves to `default_route` and answers the user.
@@ -7,7 +7,8 @@
7
7
  * from the YAMLs and the environment: every syndicate the loader can see,
8
8
  * every agent's model, the provider that id routes to, whether that path
9
9
  * is funded (direct key, gateway, or local), and which declared
10
- * server-side tools the path will drop. Then it says exactly what to set,
10
+ * server-side tools the path will drop, and which tools need an OAuth
11
+ * grant before they work (ADR 0112). Then it says exactly what to set,
11
12
  * grouped by how much each variable unlocks.
12
13
  *
13
14
  * WHAT IT NEVER DOES:
@@ -46,6 +47,49 @@ export interface DoctorRow {
46
47
  gaps: CapabilityGap[];
47
48
  }
48
49
  export type VerdictState = 'ready' | 'ready-local' | 'via-gateway' | 'blocked';
50
+ /**
51
+ * A tool that sends an OAuth token (ADR 0112): which agent holds it, the
52
+ * provider, and the grant. An authorization_code grant needs each user's
53
+ * consent before the tool works for them; a client_credentials grant needs
54
+ * its variables set. Variable names only, never a value.
55
+ */
56
+ export interface DoctorGrant {
57
+ agent: string;
58
+ /** `openapi <spec>` or `mcp <url>`. */
59
+ tools: string;
60
+ provider: string;
61
+ grant: 'authorization_code' | 'client_credentials';
62
+ scopes: string[];
63
+ /** The variables the grant reads that are not set here. */
64
+ missingEnv: string[];
65
+ /** Why the operator's OAuth host allowlist refuses this grant here (ADR 0114); empty when it is permitted. */
66
+ hostProblems: string[];
67
+ }
68
+ /**
69
+ * The server's OAuth setup under this environment (ADR 0114): which of its
70
+ * variables are set, and what is wrong, names only. Present when a
71
+ * syndicate declares a grant or any of the variables is set.
72
+ */
73
+ export interface DoctorOAuth {
74
+ /** Which of MELCHIZEDEK_CREDENTIAL_KEY, OAUTH_REDIRECT_URI and MELCHIZEDEK_OAUTH_HOSTS are set. */
75
+ set: Record<string, boolean>;
76
+ problems: string[];
77
+ }
78
+ /**
79
+ * The static credential variables the syndicates send, under the operator's
80
+ * credential host allowlist (ADR 0122). Present when any syndicate sends one
81
+ * or MELCHIZEDEK_CREDENTIAL_HOSTS is set. Names and hosts only.
82
+ */
83
+ export interface DoctorCredentials {
84
+ /** Whether MELCHIZEDEK_CREDENTIAL_HOSTS is set. */
85
+ set: boolean;
86
+ /** Every variable the syndicates send (bearer_env, api_key.env, client_secret_env), sorted. */
87
+ envs: string[];
88
+ /** With no allowlist: the variables sent wherever the YAML says, named. Not a --check failure. */
89
+ warning?: string;
90
+ /** A malformed allowlist, and each use it refuses (`file: agent · tools · role: why`). A --check failure. */
91
+ problems: string[];
92
+ }
49
93
  export interface DoctorSyndicate {
50
94
  /** Path relative to the agents dir, e.g. "examples/council.yaml". */
51
95
  file: string;
@@ -75,6 +119,13 @@ export interface DoctorSyndicate {
75
119
  declared: boolean;
76
120
  issue?: string;
77
121
  };
122
+ /** The tools that need an OAuth grant (ADR 0112), when any do. */
123
+ grants?: DoctorGrant[];
124
+ /** The static credential variables its tools send (ADR 0122), and what the allowlist refuses; when it sends any. */
125
+ credentials?: {
126
+ envs: string[];
127
+ problems: string[];
128
+ };
78
129
  }
79
130
  export interface Unlock {
80
131
  env: string;
@@ -108,6 +159,47 @@ export interface DoctorResult {
108
159
  /** Every provider not on its default endpoint: a cloud platform or a proxy (ADR 0023). */
109
160
  endpoints: DoctorEndpoint[];
110
161
  counts: Record<VerdictState, number>;
162
+ /** The OAuth setup, when any syndicate declares a grant or its variables are set. */
163
+ oauth?: DoctorOAuth;
164
+ /** The credential host allowlist (ADR 0122), when any syndicate sends a static credential or it is set. */
165
+ credentials?: DoctorCredentials;
166
+ /** How each provider would be reached here: the providers line, as data. */
167
+ providers: ProviderPath[];
168
+ /** How the A2A server would authenticate and bill, when any of its variables is set. */
169
+ serving?: DoctorServing;
170
+ }
171
+ /**
172
+ * How one provider would be reached under this environment (the doctor's
173
+ * providers line): funded or not, directly or through the gateway, and on
174
+ * which platform. `melchizedek-setup --auto` reads its levels from this.
175
+ */
176
+ export interface ProviderPath {
177
+ provider: ProviderId;
178
+ label: string;
179
+ funded: boolean;
180
+ transport: 'direct' | 'gateway';
181
+ /** The gateway id when transport is 'gateway'. */
182
+ gateway?: string;
183
+ /** `local` for Ollama; a cloud platform (vertex, bedrock, azure) or `direct` otherwise. */
184
+ platform: Platform | 'local';
185
+ keyEnv: string | null;
186
+ }
187
+ /**
188
+ * The A2A server's identity and billing settings (scripts/a2a_server.ts),
189
+ * names and presence only: which `A2A_AUTH` mode, whether the secret and the
190
+ * mode's own variables are set, and who pays (`A2A_KEY_MODE`).
191
+ */
192
+ export interface DoctorServing {
193
+ /** A2A_AUTH as set, or `secret` (the server's default). */
194
+ auth: string;
195
+ /** True when A2A_AUTH is set explicitly. */
196
+ authDeclared: boolean;
197
+ /** A2A_KEY_MODE as set, or `server`. */
198
+ keyMode: string;
199
+ /** Which of the serving variables are set (never their values). */
200
+ set: Record<string, boolean>;
201
+ /** What the server would refuse to start on, by variable name. */
202
+ problems: string[];
111
203
  }
112
204
  export interface DoctorEndpoint {
113
205
  provider: ProviderId;
@@ -133,6 +225,16 @@ export interface DoctorEndpoint {
133
225
  export declare function runtimeReport(env?: NodeJS.ProcessEnv): DoctorRuntime;
134
226
  /** One row per provider that is not on its vendor's public API with an env key. */
135
227
  export declare function endpointRows(): DoctorEndpoint[];
228
+ /** One status per provider, probed with an id of its own prefix (the providers line). */
229
+ export declare function providerPaths(): ProviderPath[];
230
+ /** The serving variables the doctor reports by presence. */
231
+ export declare const SERVING_ENV: readonly ['A2A_AUTH', 'A2A_SERVER_SECRET', 'A2A_CALLERS', 'A2A_JWT_ISSUER', 'A2A_JWT_AUDIENCE', 'A2A_JWT_JWKS_URL', 'A2A_JWT_SECRET', 'A2A_TRUSTED_USER_HEADER', 'A2A_KEY_MODE', 'PUBLIC_URL'];
232
+ /**
233
+ * How the A2A server would authenticate callers and who would pay, from the
234
+ * environment; undefined when none of its variables is set. The checks are
235
+ * the ones scripts/a2a_server.ts stops on, reported by name.
236
+ */
237
+ export declare function servingReport(env?: NodeJS.ProcessEnv): DoctorServing | undefined;
136
238
  export declare const CONSOLE_URLS: Record<ProviderId, string>;
137
239
  /**
138
240
  * Every syndicate file the loader can see: the root (this deployment's
@@ -161,6 +263,13 @@ export declare function runDoctor(options?: {
161
263
  agentsDir?: string;
162
264
  scripts?: Record<string, string>;
163
265
  }): DoctorResult;
266
+ /**
267
+ * Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
268
+ * it passes. A blocked syndicate, a runtime problem, the OAuth setup's and
269
+ * the credential allowlist's problems, and each grant whose hosts the OAuth
270
+ * host allowlist refuses (ADR 0114). Names and hosts only, never a value.
271
+ */
272
+ export declare function checkProblems(result: DoctorResult): string[];
164
273
  export declare function renderDoctor(result: DoctorResult, opts?: {
165
274
  color?: boolean;
166
275
  }): string;