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
@@ -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:
@@ -33,6 +34,13 @@ import { credentialSource, endpointFromEnv, endpointLabel, endpointProblems, PLA
33
34
  import { GATEWAY_ENV, GATEWAY_KEY_ENV, GATEWAYS, gatewayConfig, gatewayProblem, gatewayUsable, } from './models/gateway.js';
34
35
  import { PROVIDERS } from './models/providerMap.js';
35
36
  import { describeRuntime } from './runtime/runtimeFlag.js';
37
+ import { agentOAuthGrants, oauthEnvNames } from './tools/oauthTools.js';
38
+ import { grantHostProblems, OAUTH_REDIRECT_URI_ENV, oauthEnvProblems } from './a2a/oauthSetup.js';
39
+ import { CREDENTIAL_KEY_ENV } from './tools/credentialCipher.js';
40
+ import { OAUTH_HOSTS_ENV } from './tools/oauthHosts.js';
41
+ import { CREDENTIAL_HOSTS_ENV, credentialHosts } from './tools/credentialHosts.js';
42
+ import { syndicateCredentialUses } from './tools/credentialUses.js';
43
+ import { credentialHostEnvProblems, unboundCredentialWarning } from './a2a/oauthSetup.js';
36
44
  function resolvable(module) {
37
45
  try {
38
46
  createRequire(import.meta.url).resolve(module);
@@ -95,6 +103,69 @@ export function endpointRows() {
95
103
  }
96
104
  return rows;
97
105
  }
106
+ /** One status per provider, probed with an id of its own prefix (the providers line). */
107
+ export function providerPaths() {
108
+ return Object.keys(PROVIDERS).map((p) => {
109
+ const r = describeCapabilities(PROBE_ID[p]);
110
+ return {
111
+ provider: p,
112
+ label: PROVIDERS[p].label,
113
+ funded: r.funded,
114
+ transport: r.transport,
115
+ ...(r.gateway ? { gateway: r.gateway } : {}),
116
+ platform: p === 'ollama' ? 'local' : (r.platform ?? 'direct'),
117
+ keyEnv: r.keyEnv,
118
+ };
119
+ });
120
+ }
121
+ /** The serving variables the doctor reports by presence. */
122
+ export const SERVING_ENV = [
123
+ 'A2A_AUTH',
124
+ 'A2A_SERVER_SECRET',
125
+ 'A2A_CALLERS',
126
+ 'A2A_JWT_ISSUER',
127
+ 'A2A_JWT_AUDIENCE',
128
+ 'A2A_JWT_JWKS_URL',
129
+ 'A2A_JWT_SECRET',
130
+ 'A2A_TRUSTED_USER_HEADER',
131
+ 'A2A_KEY_MODE',
132
+ 'PUBLIC_URL',
133
+ ];
134
+ /**
135
+ * How the A2A server would authenticate callers and who would pay, from the
136
+ * environment; undefined when none of its variables is set. The checks are
137
+ * the ones scripts/a2a_server.ts stops on, reported by name.
138
+ */
139
+ export function servingReport(env = process.env) {
140
+ const has = (name) => !!env[name]?.trim();
141
+ const set = Object.fromEntries(SERVING_ENV.map((name) => [name, has(name)]));
142
+ if (!Object.values(set).some(Boolean))
143
+ return undefined;
144
+ // A value outside the known modes is reported as `invalid`, never echoed:
145
+ // a secret pasted into the wrong variable must not reach the terminal.
146
+ const rawAuth = (env.A2A_AUTH ?? '').trim().toLowerCase() || 'secret';
147
+ const rawKeyMode = (env.A2A_KEY_MODE ?? '').trim().toLowerCase() || 'server';
148
+ const auth = ['secret', 'callers', 'jwt', 'header'].includes(rawAuth) ? rawAuth : 'invalid';
149
+ const keyMode = rawKeyMode === 'server' || rawKeyMode === 'byok' ? rawKeyMode : 'invalid';
150
+ const problems = [];
151
+ if (auth === 'invalid')
152
+ problems.push('A2A_AUTH must be secret, callers, jwt or header');
153
+ if (auth === 'callers' && !has('A2A_CALLERS'))
154
+ problems.push('A2A_AUTH=callers needs A2A_CALLERS');
155
+ if (auth === 'jwt' && (!has('A2A_JWT_ISSUER') || !has('A2A_JWT_AUDIENCE')))
156
+ problems.push('A2A_AUTH=jwt needs A2A_JWT_ISSUER and A2A_JWT_AUDIENCE');
157
+ if (auth === 'jwt' && !has('A2A_JWT_JWKS_URL') && !has('A2A_JWT_SECRET'))
158
+ problems.push('A2A_AUTH=jwt needs A2A_JWT_JWKS_URL or A2A_JWT_SECRET');
159
+ if (auth === 'header' && (!has('A2A_TRUSTED_USER_HEADER') || !has('A2A_SERVER_SECRET')))
160
+ problems.push('A2A_AUTH=header needs A2A_TRUSTED_USER_HEADER and A2A_SERVER_SECRET');
161
+ if (keyMode !== 'server' && keyMode !== 'byok')
162
+ problems.push("A2A_KEY_MODE must be 'server' or 'byok'");
163
+ if (has('PUBLIC_URL') && !has('A2A_AUTH'))
164
+ problems.push('PUBLIC_URL is set, so A2A_AUTH must be set explicitly (with A2A_SERVED_AGENTS and A2A_TRUST_PROXY)');
165
+ if (auth === 'secret' && !has('A2A_SERVER_SECRET') && keyMode === 'byok')
166
+ problems.push('A2A_KEY_MODE=byok without A2A_SERVER_SECRET (or caller tokens) serves unauthenticated, loopback only');
167
+ return { auth, authDeclared: has('A2A_AUTH'), keyMode, set, problems };
168
+ }
98
169
  // ── Where to get a key ───────────────────────────────────────────────────────
99
170
  export const CONSOLE_URLS = {
100
171
  gemini: 'https://aistudio.google.com',
@@ -160,6 +231,18 @@ export function tierOf(rows) {
160
231
  return 'multi-provider';
161
232
  return providers[0] === 'ollama' ? 'keyless' : providers[0];
162
233
  }
234
+ /** The OAuth grants one agent's tools declare, with the variables not set here. */
235
+ function grantsOf(agent, prefix) {
236
+ return agentOAuthGrants(agent, prefix).map((use) => ({
237
+ agent: use.agent,
238
+ tools: use.tools,
239
+ provider: use.oauth2.provider,
240
+ grant: use.oauth2.grant,
241
+ scopes: [...(use.oauth2.scopes ?? [])],
242
+ missingEnv: oauthEnvNames(use.oauth2).filter((name) => !process.env[name]?.trim()),
243
+ hostProblems: grantHostProblems(use),
244
+ }));
245
+ }
163
246
  /** The first problem, without the file prefix the doctor already shows. */
164
247
  function firstProblem(err) {
165
248
  if (err instanceof SyndicateValidationError)
@@ -179,6 +262,8 @@ function needsOf(agent, delegates = false) {
179
262
  function walk(config, prefix, opts, rows) {
180
263
  const orch = config.orchestrator;
181
264
  const orchModel = orch?.model;
265
+ if (orch)
266
+ opts.grants.push(...grantsOf(orch, prefix));
182
267
  if (orch && orchModel) {
183
268
  // In DELEGATE mode the orchestrator calls its subagents as tools; under
184
269
  // plan-dispatch it is a tool-less classifier and code runs the route; in
@@ -218,6 +303,7 @@ function walk(config, prefix, opts, rows) {
218
303
  }
219
304
  continue;
220
305
  }
306
+ opts.grants.push(...grantsOf(sub, prefix));
221
307
  // A subagent without its own model inherits the orchestrator's.
222
308
  const model = sub.model ?? orchModel;
223
309
  if (!model)
@@ -283,7 +369,19 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
283
369
  const config = load(file);
284
370
  const rows = [];
285
371
  const nestedErrors = [];
286
- walk(config, '', { load, seen: new Set([file, path.basename(file)]), nestedErrors }, rows);
372
+ const grants = [];
373
+ walk(config, '', { load, seen: new Set([file, path.basename(file)]), nestedErrors, grants }, rows);
374
+ // Static credentials and the credential host allowlist (ADR 0122); a nested file that cannot load is reported above.
375
+ const loadQuiet = (ref) => {
376
+ try {
377
+ return load(ref);
378
+ }
379
+ catch {
380
+ return {};
381
+ }
382
+ };
383
+ const credentialEnvs = [...new Set(syndicateCredentialUses([config], loadQuiet).map((u) => u.env))].sort();
384
+ const credentials = credentialEnvs.length ? { envs: credentialEnvs, problems: credentialHostEnvProblems([config], process.env, loadQuiet) } : undefined;
287
385
  const memory = config.memory_system === 'long-term'
288
386
  ? {
289
387
  namespace: config.memory_namespace ?? LEGACY_MEMORY_APP_NAME,
@@ -302,6 +400,8 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
302
400
  ...(runCommand ? { runCommand } : {}),
303
401
  ...(nestedErrors.length ? { error: nestedErrors.join('\n') } : {}),
304
402
  ...(memory ? { memory } : {}),
403
+ ...(grants.length ? { grants } : {}),
404
+ ...(credentials ? { credentials } : {}),
305
405
  };
306
406
  }
307
407
  catch (err) {
@@ -383,8 +483,9 @@ export function runDoctor(options = {}) {
383
483
  const rawGateway = (process.env[GATEWAY_ENV] ?? '').trim();
384
484
  const gateway = rawGateway
385
485
  ? {
386
- id: cfg?.gateway.id ?? rawGateway,
387
- label: cfg?.gateway.label ?? rawGateway,
486
+ // An unrecognised value is never echoed: it may be a key pasted into the wrong variable.
487
+ id: cfg?.gateway.id ?? 'unrecognised',
488
+ label: cfg?.gateway.label ?? 'an unrecognised gateway',
388
489
  usable: gatewayUsable(),
389
490
  ...(gatewayProblem() ? { problem: gatewayProblem() } : {}),
390
491
  }
@@ -398,7 +499,43 @@ export function runDoctor(options = {}) {
398
499
  for (const s of syndicates)
399
500
  counts[s.verdict.state]++;
400
501
  flagMemoryNamespaces(syndicates);
401
- return { agentsDir, runtime: runtimeReport(), syndicates, unlocks, gateway, endpoints: endpointRows(), counts };
502
+ // The server's OAuth setup (ADR 0114): the key, the redirect URI and the allowlist, names only.
503
+ const grants = syndicates.flatMap((s) => s.grants ?? []).map((g) => ({ agent: g.agent, tools: g.tools, oauth2: { provider: g.provider, grant: g.grant, token_url: '' } }));
504
+ const set = Object.fromEntries([CREDENTIAL_KEY_ENV, OAUTH_REDIRECT_URI_ENV, OAUTH_HOSTS_ENV].map((name) => [name, !!process.env[name]?.trim()]));
505
+ const oauth = grants.length || Object.values(set).some(Boolean) ? { set, problems: oauthEnvProblems(grants) } : undefined;
506
+ // The credential host allowlist (ADR 0122): unbound variables named, refusals listed, names and hosts only.
507
+ const credentialSet = !!process.env[CREDENTIAL_HOSTS_ENV]?.trim();
508
+ const envs = [...new Set(syndicates.flatMap((s) => s.credentials?.envs ?? []))].sort();
509
+ let credentials;
510
+ if (envs.length || credentialSet) {
511
+ const problems = [];
512
+ try {
513
+ credentialHosts();
514
+ }
515
+ catch (err) {
516
+ problems.push(err instanceof Error ? err.message : `${CREDENTIAL_HOSTS_ENV} is malformed`);
517
+ }
518
+ if (!problems.length)
519
+ for (const s of syndicates)
520
+ for (const p of s.credentials?.problems ?? [])
521
+ problems.push(`${s.file}: ${p}`);
522
+ const warning = unboundCredentialWarning(envs);
523
+ credentials = { set: credentialSet, envs, ...(warning ? { warning } : {}), problems };
524
+ }
525
+ const serving = servingReport();
526
+ return {
527
+ agentsDir,
528
+ runtime: runtimeReport(),
529
+ syndicates,
530
+ unlocks,
531
+ gateway,
532
+ endpoints: endpointRows(),
533
+ counts,
534
+ ...(oauth ? { oauth } : {}),
535
+ ...(credentials ? { credentials } : {}),
536
+ providers: providerPaths(),
537
+ ...(serving ? { serving } : {}),
538
+ };
402
539
  }
403
540
  // ── Rendering ────────────────────────────────────────────────────────────────
404
541
  const ANSI = {
@@ -419,6 +556,29 @@ const PROBE_ID = {
419
556
  moonshot: 'kimi-x',
420
557
  ollama: 'ollama/x',
421
558
  };
559
+ /**
560
+ * Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
561
+ * it passes. A blocked syndicate, a runtime problem, the OAuth setup's and
562
+ * the credential allowlist's problems, and each grant whose hosts the OAuth
563
+ * host allowlist refuses (ADR 0114). Names and hosts only, never a value.
564
+ */
565
+ export function checkProblems(result) {
566
+ const problems = [];
567
+ if (result.counts.blocked > 0)
568
+ problems.push(`${result.counts.blocked} syndicate(s) blocked`);
569
+ if (result.runtime.problem)
570
+ problems.push(result.runtime.problem);
571
+ for (const p of result.oauth?.problems ?? [])
572
+ problems.push(`oauth: ${p}`);
573
+ for (const p of result.credentials?.problems ?? [])
574
+ problems.push(`credentials: ${p}`);
575
+ for (const s of result.syndicates) {
576
+ for (const g of s.grants ?? [])
577
+ for (const p of g.hostProblems ?? [])
578
+ problems.push(`${s.file}: ${g.agent} · ${g.provider}: ${p}`);
579
+ }
580
+ return problems;
581
+ }
422
582
  function pad(s, n) {
423
583
  return s.length >= n ? s.slice(0, n - 1) + '…' : s.padEnd(n);
424
584
  }
@@ -445,11 +605,10 @@ export function renderDoctor(result, opts = {}) {
445
605
  // Provider line: what is funded, and how. Each provider is probed with an
446
606
  // id of its own prefix, so the line names ITS key (a fallthrough once put
447
607
  // xAI's key beside Moonshot).
448
- const providerBits = Object.keys(PROVIDERS).map((p) => {
449
- const r = describeCapabilities(PROBE_ID[p]);
608
+ const providerBits = (result.providers ?? providerPaths()).map((r) => {
450
609
  const mark = !r.funded ? `${c.red}✗${c.reset}` : r.transport === 'gateway' ? `${c.yellow}◇${c.reset}` : `${c.green}✓${c.reset}`;
451
- const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : p === 'ollama' ? `${c.dim}local${c.reset}` : `${c.dim}${r.platform ?? 'direct'}${c.reset}`;
452
- return `${mark} ${PROVIDERS[p].label} ${how}`;
610
+ const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : r.platform === 'local' ? `${c.dim}local${c.reset}` : `${c.dim}${r.platform}${c.reset}`;
611
+ return `${mark} ${r.label} ${how}`;
453
612
  });
454
613
  lines.push('providers ' + providerBits.join(' '));
455
614
  for (const e of result.endpoints ?? []) {
@@ -464,6 +623,33 @@ export function renderDoctor(result, opts = {}) {
464
623
  ? `gateway ${c.red}✗${c.reset} ${result.gateway.problem}`
465
624
  : `gateway ${c.yellow}◇${c.reset} ${result.gateway.label} fills in for any provider whose direct key is absent ${c.dim}(native search is lost on that path)${c.reset}`);
466
625
  }
626
+ if (result.serving) {
627
+ // Names only: the A2A identity mode, who pays, and what the server would refuse.
628
+ const sv = result.serving;
629
+ const mark = sv.problems.length ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
630
+ const set = Object.entries(sv.set).filter(([, on]) => on).map(([name]) => name).join(', ');
631
+ lines.push(`serving ${mark} A2A_AUTH=${sv.auth}${sv.authDeclared ? '' : ' (default)'} · A2A_KEY_MODE=${sv.keyMode} ${c.dim}· set: ${set || 'none'}${c.reset}`);
632
+ for (const p of sv.problems)
633
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
634
+ }
635
+ if (result.oauth) {
636
+ // Names only: which variables are set, never their values (ADR 0114).
637
+ const set = Object.entries(result.oauth.set).map(([name, on]) => `${name} ${on ? 'set' : 'unset'}`).join(' · ');
638
+ const mark = result.oauth.problems.length ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
639
+ lines.push(`oauth ${mark} ${c.dim}${set}${c.reset}`);
640
+ for (const p of result.oauth.problems)
641
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
642
+ }
643
+ if (result.credentials) {
644
+ // Names only: which variables are sent, never their values (ADR 0122).
645
+ const cr = result.credentials;
646
+ const mark = cr.problems.length ? `${c.red}✗${c.reset}` : cr.warning ? `${c.yellow}⚠${c.reset}` : `${c.green}✓${c.reset}`;
647
+ lines.push(`credentials ${mark} ${c.dim}${CREDENTIAL_HOSTS_ENV} ${cr.set ? 'set' : 'unset'}${cr.envs.length ? ` · sent: ${cr.envs.join(', ')}` : ''}${c.reset}`);
648
+ if (cr.warning)
649
+ lines.push(` ${c.yellow}⚠ ${cr.warning}${c.reset}`);
650
+ for (const p of cr.problems)
651
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
652
+ }
467
653
  lines.push('');
468
654
  // The table: one line per syndicate (name, verdict, run command), then
469
655
  // one indented line per agent.
@@ -490,6 +676,16 @@ export function renderDoctor(result, opts = {}) {
490
676
  if (s.memory?.issue) {
491
677
  lines.push(` ${c.yellow}⚠ memory: ${s.memory.issue}${c.reset}`);
492
678
  }
679
+ for (const g of s.grants ?? []) {
680
+ const scopes = g.scopes.length ? ` ${c.dim}[${g.scopes.join(' ')}]${c.reset}` : '';
681
+ const what = g.grant === 'authorization_code'
682
+ ? `needs a grant: each user connects ${g.provider} (consent) before ${g.tools} works for them`
683
+ : `needs a grant: the server's own ${g.provider} token (client_credentials) for ${g.tools}`;
684
+ const missing = g.missingEnv.length ? ` ${c.red}✗ ${g.missingEnv.join(', ')} not set${c.reset}` : '';
685
+ lines.push(` ${c.cyan}⚿ ${g.agent}${c.reset} ${what}${scopes}${missing}`);
686
+ for (const p of g.hostProblems ?? [])
687
+ lines.push(` ${c.red}✗ ${p}${c.reset}`);
688
+ }
493
689
  if (s.declaredTier && s.declaredTier !== s.tier) {
494
690
  lines.push(` ${c.yellow}⚠ header says "tier: ${s.declaredTier}" but the models say ${s.tier}${c.reset}`);
495
691
  }
@@ -2,6 +2,7 @@ import type { DispatchConfig } from './dispatch.ts';
2
2
  export type { DispatchConfig } from './dispatch.ts';
3
3
  import type { SkillsConfig } from './tools/skillToolset.ts';
4
4
  import type { OpenApiConfig } from './tools/openapiTools.ts';
5
+ import type { McpAuthConfig } from './tools/oauthTools.ts';
5
6
  import type { ContextConfig, ExampleConfig } from './compile.ts';
6
7
  export type { OpenApiConfig } from './tools/openapiTools.ts';
7
8
  export type { SkillsConfig } from './tools/skillToolset.ts';
@@ -91,7 +92,9 @@ export interface AgentYamlConfig {
91
92
  outputKey?: string;
92
93
  /**
93
94
  * Model generation config (temperature, topP, safety, thinking, etc.)
94
- * Maps to LlmAgentConfig.generateContentConfig.
95
+ * Maps to LlmAgentConfig.generateContentConfig. In YAML a v1 spelling,
96
+ * deprecated (ADR 0115): write `sampling`, `output` and `reasoning`. It
97
+ * stays the engine form the loader hands downstream.
95
98
  */
96
99
  generateContentConfig?: GenerateContentConfig;
97
100
  /**
@@ -102,12 +105,54 @@ export interface AgentYamlConfig {
102
105
  reasoning?: ReasoningSetting;
103
106
  /**
104
107
  * Output schema for structured JSON responses.
105
- * Maps to LlmAgentConfig.outputSchema.
108
+ * Maps to LlmAgentConfig.outputSchema. In YAML a v1 spelling, deprecated
109
+ * (ADR 0115): write `output.schema`. It stays the engine form.
106
110
  */
107
111
  outputSchema?: Record<string, unknown>;
112
+ /**
113
+ * Sampling, provider-neutral (v2): replaces generateContentConfig's
114
+ * temperature, topP, maxOutputTokens and stopSequences. The loader folds it
115
+ * into generateContentConfig (lib/agentDialect.ts toEngineAgent), so a
116
+ * loaded config never carries it. Cannot be combined with the v1 key it replaces.
117
+ */
118
+ sampling?: {
119
+ temperature?: number;
120
+ top_p?: number;
121
+ max_output_tokens?: number;
122
+ stop?: string[];
123
+ };
124
+ /**
125
+ * Structured output (v2): `schema` replaces outputSchema, `mime` replaces
126
+ * generateContentConfig.responseMimeType. Folded into those by the loader.
127
+ */
128
+ output?: {
129
+ schema?: Record<string, unknown>;
130
+ mime?: 'application/json' | 'text/plain';
131
+ };
132
+ /**
133
+ * Which tools the model may call (v2, ADR 0117): `auto`, `none`,
134
+ * `required`, or `{ name }` for one tool. Replaces
135
+ * generateContentConfig.toolConfig.functionCallingConfig; folded into it
136
+ * by the loader (lib/agentDialect.ts toEngineAgent).
137
+ */
138
+ tool_choice?: 'auto' | 'none' | 'required' | {
139
+ name: string;
140
+ };
141
+ /**
142
+ * Per-provider instruction (v2), keyed by provider id (lib/models/providerMap.ts):
143
+ * `instruction` replaces the agent's instruction, `instruction_append` is
144
+ * appended after a blank line, for the provider of the model the agent runs
145
+ * on. Applied at compile time, before the skills index; not for fallback_model.
146
+ */
147
+ model_overrides?: Partial<Record<'gemini' | 'anthropic' | 'openai' | 'xai' | 'moonshot' | 'ollama', {
148
+ instruction?: string;
149
+ instruction_append?: string;
150
+ }>>;
108
151
  mcp_server_url?: string;
109
152
  /** The MCP server's tools this agent may use; the rest are not exposed. */
110
153
  mcp_tools?: string[];
154
+ /** The OAuth grant the MCP server takes (ADR 0112): each user's, or the server's own. */
155
+ mcp_auth?: McpAuthConfig;
111
156
  /** Answers when `model` fails provider-side, or its provider's circuit is open (ADR 0044). */
112
157
  fallback_model?: string;
113
158
  /**
@@ -216,6 +261,8 @@ export interface SyndicateYamlConfig {
216
261
  memory_retention_days?: number;
217
262
  /** Optional hard limit on the turn's model calls, subagents included (LLM -> Tool cycles). */
218
263
  max_steps?: number;
264
+ /** Delegated calls from one model step that run at once (ADR 0116). Default 4. */
265
+ max_concurrency?: number;
219
266
  /**
220
267
  * The nested `yaml_reference:` syndicates this one reaches, raw, keyed by
221
268
  * reference: written by the registry publisher (`bundleReferences`) so a
@@ -251,6 +298,11 @@ export interface LoadSyndicateOptions {
251
298
  * with the public example of the same name (ADR 0018).
252
299
  */
253
300
  shippedFallback?: boolean;
301
+ /**
302
+ * Where load-time warnings go (the deprecated v1 spellings of ADR 0115).
303
+ * Default console.warn. A warning names the file and key paths, never a value.
304
+ */
305
+ onWarning?: (message: string) => void;
254
306
  }
255
307
  /**
256
308
  * Parse CLI variable bindings into a VariableMap.
@@ -4,6 +4,7 @@ import * as path from 'path';
4
4
  import { createClient } from '@supabase/supabase-js';
5
5
  import { hasSupabaseCredentials } from './persistence/supabaseProvider.js';
6
6
  import { validateSyndicateConfig } from './syndicateSchema.js';
7
+ import { v1SpellingsOf } from './agentDialect.js';
7
8
  // ── Variable Injection (private) ──────────────────────────
8
9
  const TOKEN_RE = /\{\{(\w+)\}\}/g;
9
10
  const FULL_TOKEN_RE = /^\{\{(\w+)\}\}$/;
@@ -87,6 +88,7 @@ function resolveAndValidate(raw, label, options) {
87
88
  ...bindings,
88
89
  };
89
90
  const interpolated = isPlainObject(raw) ? interpolateDeep(raw, merged) : raw;
91
+ warnV1Spellings(interpolated, label, options.onWarning);
90
92
  const unresolved = findUnresolvedTokens(interpolated);
91
93
  if (unresolved.length > 0) {
92
94
  console.warn(`⚠ Unresolved template variables: ${unresolved.map((t) => `{{${t}}}`).join(', ')}`);
@@ -99,6 +101,24 @@ function resolveAndValidate(raw, label, options) {
99
101
  }
100
102
  return config;
101
103
  }
104
+ /** Labels already warned about, so a server loading one file per request warns once per process. */
105
+ const warnedV1 = new Set();
106
+ /**
107
+ * One line per label when a file still uses the v1 spellings ADR 0115
108
+ * deprecates. Key paths and the label only: never a value (a prompt, a key).
109
+ */
110
+ function warnV1Spellings(raw, label, onWarning = console.warn) {
111
+ if (warnedV1.has(label))
112
+ return;
113
+ const paths = v1SpellingsOf(raw);
114
+ if (!paths.length)
115
+ return;
116
+ warnedV1.add(label);
117
+ const fix = label.startsWith('registry:') || label.startsWith('bundled:')
118
+ ? 'Run npx melchizedek-codemod on the file it was published from, and republish'
119
+ : `Run npx melchizedek-codemod ${label} to rewrite them`;
120
+ onWarning(`⚠ ${label}: generateContentConfig and outputSchema are v1 spellings, deprecated (ADR 0115): ${paths.join(', ')}. ${fix}.`);
121
+ }
102
122
  function applyOverrides(config, overrides) {
103
123
  return { ...config, ...overrides };
104
124
  }
@@ -227,9 +247,9 @@ export function collectGuards(config, loadNested = nestedLoader(config), seen =
227
247
  return [...names];
228
248
  }
229
249
  export function loadSyndicate(filename, options = {}) {
230
- const { bindings = {}, overrides } = options;
250
+ const { bindings = {}, overrides, onWarning } = options;
231
251
  const { raw, label, filePath } = readSyndicateFile(filename, options);
232
- return anchorSpecPaths(resolveAndValidate(raw, label, { bindings, overrides }), path.dirname(filePath));
252
+ return anchorSpecPaths(resolveAndValidate(raw, label, { bindings, overrides, onWarning }), path.dirname(filePath));
233
253
  }
234
254
  /**
235
255
  * An `openapi:` spec path is written relative to the syndicate file, so a
@@ -2,11 +2,12 @@
2
2
  * lib/memory/namespace.ts — memory always resolves to the ROOT syndicate's
3
3
  * namespace (ADR 0020 item 3).
4
4
  *
5
- * WHY: a DELEGATE subagent runs on its own session row under
6
- * `appName = <the subagent's name>`, and its memory tools search with the
7
- * session's app name. Facts are written under the root syndicate's
8
- * namespace, so a `load_memory` declared on a subagent would search
9
- * `<SubAgent>/<user>`, find nothing, and say so without error. Wrapping the
5
+ * WHY: a DELEGATE subagent runs on its own session row, filed under its
6
+ * agent path (`appName = <app>/<caller>/<subagent>`, ADR 0111; the
7
+ * subagent's name alone in a conversation stored before), and its memory
8
+ * tools search with the session's app name. Facts are written under the
9
+ * root syndicate's namespace, so a `load_memory` declared on a subagent
10
+ * would search the child's own key, find nothing, and say so without error. Wrapping the
10
11
  * service the runtime receives pins every search and ingestion to one
11
12
  * namespace, whatever app name the caller carries.
12
13
  *
@@ -2,11 +2,12 @@
2
2
  * lib/memory/namespace.ts — memory always resolves to the ROOT syndicate's
3
3
  * namespace (ADR 0020 item 3).
4
4
  *
5
- * WHY: a DELEGATE subagent runs on its own session row under
6
- * `appName = <the subagent's name>`, and its memory tools search with the
7
- * session's app name. Facts are written under the root syndicate's
8
- * namespace, so a `load_memory` declared on a subagent would search
9
- * `<SubAgent>/<user>`, find nothing, and say so without error. Wrapping the
5
+ * WHY: a DELEGATE subagent runs on its own session row, filed under its
6
+ * agent path (`appName = <app>/<caller>/<subagent>`, ADR 0111; the
7
+ * subagent's name alone in a conversation stored before), and its memory
8
+ * tools search with the session's app name. Facts are written under the
9
+ * root syndicate's namespace, so a `load_memory` declared on a subagent
10
+ * would search the child's own key, find nothing, and say so without error. Wrapping the
10
11
  * service the runtime receives pins every search and ingestion to one
11
12
  * namespace, whatever app name the caller carries.
12
13
  *
@@ -30,12 +30,12 @@
30
30
  */
31
31
  import { endpointFromEnv } from '../models/endpoints.js';
32
32
  import { GoogleGenAI } from '@google/genai';
33
- import { trace, SpanStatusCode } from '@opentelemetry/api';
33
+ import { SpanStatusCode } from '@opentelemetry/api';
34
34
  import { llmRequestToModelRequest, modelResponseToLlmResponse } from '../models/genaiMapping.js';
35
35
  import { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL, MEMORY_EXTRACTION_MODEL } from '../config.js';
36
36
  import { providerForModel } from '../models/providerMap.js';
37
37
  import { resolveModel } from '../models/registry.js';
38
- import { initializeTracing, traceLlmGeneration } from '../observability/tracer.js';
38
+ import { startEngineSpan, traceLlmGeneration } from '../observability/tracer.js';
39
39
  import { trimTrailingSlashes } from '../models/urls.js';
40
40
  /**
41
41
  * Extraction on any model id. The key passed here reaches only a model of
@@ -86,8 +86,7 @@ export function modelExtractor(opts = {}) {
86
86
  * a reasoning step.
87
87
  */
88
88
  async function traced(provider, model, count, fn) {
89
- initializeTracing();
90
- const span = trace.getTracer('melchizedek-tracer').startSpan('llm.request');
89
+ const span = startEngineSpan('melchizedek-tracer', 'llm.request');
91
90
  span.setAttribute('llm.provider', provider);
92
91
  span.setAttribute('llm.model', model);
93
92
  span.setAttribute('llm.purpose', 'memory_embedding');
@@ -55,7 +55,7 @@ export declare function describeCapabilities(model: string, tools?: readonly str
55
55
  * a funded direct path with no dropped tool is the quiet default.
56
56
  */
57
57
  export declare function capabilitySummary(agentName: string, r: CapabilityReport): string | undefined;
58
- export declare const CAPABILITIES: readonly ['delegation', 'memory_tools', 'structured_output', 'thinking_with_tools', 'streaming', 'vision', 'native_search'];
58
+ export declare const CAPABILITIES: readonly ['delegation', 'memory_tools', 'structured_output', 'structured_output_with_tools', 'thinking_with_tools', 'streaming', 'vision', 'native_search'];
59
59
  export type Capability = (typeof CAPABILITIES)[number];
60
60
  export declare const CAPABILITY_LABELS: Record<Capability, string>;
61
61
  export type Support = 'supported' | 'degraded' | 'unsupported';
@@ -74,6 +74,30 @@ export declare function capabilityOf(model: string, capability: Capability, opts
74
74
  }): CapabilityCell & {
75
75
  row: MatrixRow;
76
76
  };
77
+ /**
78
+ * The reasoning levels above `high` (ADR 0117), per row: what `reasoning:
79
+ * xhigh` and `reasoning: max` reach the provider as. A level a model lacks
80
+ * goes as its highest setting, and the request's `llm.request` span carries
81
+ * `llm.reasoning.weakened` with the level asked for. Asserted against the
82
+ * request bodies in tests/toolChoiceEffort.test.ts; the direct adapters read
83
+ * lib/models/reasoning.ts effortCeiling.
84
+ */
85
+ export declare const REASONING_ABOVE_HIGH: Readonly<Record<MatrixRow, string>>;
86
+ /**
87
+ * Whether an outputSchema goes in the same request as the agent's tools, as
88
+ * the provider's own structured-output field, on the path `model` takes
89
+ * (ADR 0109). True only where the `structured_output_with_tools` cell is
90
+ * supported and the model's generation takes it: Claude from Opus 4.8,
91
+ * Sonnet 5 and Haiku 5.5 on (`output_config.format`), every OpenAI id
92
+ * (`text.format`), Gemini 2 and later on Vertex AI (`responseJsonSchema`).
93
+ * Elsewhere the native loop declares set_model_response beside the tools
94
+ * (lib/runtime/native/request.ts), which works on every path. A path that
95
+ * cannot be planned (a malformed gateway setting) answers false, the form
96
+ * that works everywhere.
97
+ */
98
+ export declare function outputSchemaBesideTools(model: string, opts?: {
99
+ callerKey?: boolean;
100
+ }): boolean;
77
101
  /** What one agent's YAML asks of its model. */
78
102
  export interface AgentNeedsInput {
79
103
  tools?: readonly string[];