melchizedek-agents 1.1.0 → 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 (118) hide show
  1. package/.env.example +10 -0
  2. package/AGENT_SETUP.md +3 -1
  3. package/CHANGELOG.md +232 -0
  4. package/DOCUMENTATION.md +91 -16
  5. package/ONBOARDING.md +339 -0
  6. package/QUICKSTART.md +15 -2
  7. package/README.md +5 -2
  8. package/config/agents/examples/council.yaml +1 -1
  9. package/config/agents/examples/critic.yaml +8 -8
  10. package/config/agents/examples/image_production.yaml +4 -3
  11. package/config/agents/syndicate.schema.json +65 -7
  12. package/config/agents/syndicateSchema.yaml +18 -6
  13. package/config/agents/templates/intake_extractor.yaml +3 -2
  14. package/config/agents/templates/systems_operator.yaml +5 -2
  15. package/dist/lib/a2a/app.d.ts +12 -0
  16. package/dist/lib/a2a/app.js +22 -11
  17. package/dist/lib/a2a/executor.js +14 -6
  18. package/dist/lib/a2a/oauthSetup.d.ts +16 -0
  19. package/dist/lib/a2a/oauthSetup.js +57 -3
  20. package/dist/lib/agentDialect.d.ts +18 -8
  21. package/dist/lib/agentDialect.js +36 -19
  22. package/dist/lib/compile.d.ts +38 -7
  23. package/dist/lib/compile.js +57 -37
  24. package/dist/lib/compileNative.d.ts +11 -3
  25. package/dist/lib/compileNative.js +18 -1
  26. package/dist/lib/doctor.d.ts +76 -0
  27. package/dist/lib/doctor.js +159 -7
  28. package/dist/lib/loadSyndicate.d.ts +9 -0
  29. package/dist/lib/models/capabilities.d.ts +9 -0
  30. package/dist/lib/models/capabilities.js +24 -0
  31. package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
  32. package/dist/lib/models/chatCompletionsAdapter.js +19 -5
  33. package/dist/lib/models/claudeAdapter.js +4 -0
  34. package/dist/lib/models/contract.d.ts +5 -2
  35. package/dist/lib/models/gateway.d.ts +2 -1
  36. package/dist/lib/models/gateway.js +4 -2
  37. package/dist/lib/models/gatewayAdapter.d.ts +7 -1
  38. package/dist/lib/models/gatewayAdapter.js +8 -0
  39. package/dist/lib/models/geminiAdapter.js +8 -2
  40. package/dist/lib/models/genaiMapping.d.ts +8 -4
  41. package/dist/lib/models/genaiMapping.js +23 -9
  42. package/dist/lib/models/gptAdapter.d.ts +4 -2
  43. package/dist/lib/models/gptAdapter.js +11 -4
  44. package/dist/lib/models/grokAdapter.d.ts +3 -1
  45. package/dist/lib/models/grokAdapter.js +11 -3
  46. package/dist/lib/models/kimiAdapter.d.ts +2 -1
  47. package/dist/lib/models/kimiAdapter.js +3 -3
  48. package/dist/lib/models/reasoning.d.ts +30 -2
  49. package/dist/lib/models/reasoning.js +56 -5
  50. package/dist/lib/onboarding.d.ts +134 -0
  51. package/dist/lib/onboarding.js +539 -0
  52. package/dist/lib/runtime/approvals.d.ts +2 -0
  53. package/dist/lib/runtime/approvals.js +4 -3
  54. package/dist/lib/runtime/credentials.d.ts +6 -0
  55. package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
  56. package/dist/lib/runtime/native/agentLoop.js +2 -0
  57. package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
  58. package/dist/lib/runtime/native/checkpoint.js +13 -19
  59. package/dist/lib/runtime/native/delegate.d.ts +82 -1
  60. package/dist/lib/runtime/native/delegate.js +157 -2
  61. package/dist/lib/runtime/native/interrupts.d.ts +88 -5
  62. package/dist/lib/runtime/native/interrupts.js +220 -30
  63. package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
  64. package/dist/lib/runtime/syndicateTurn.js +295 -62
  65. package/dist/lib/runtime/turnControl.d.ts +41 -0
  66. package/dist/lib/session/transcript.d.ts +9 -0
  67. package/dist/lib/session/transcript.js +20 -1
  68. package/dist/lib/skills.d.ts +18 -0
  69. package/dist/lib/skills.js +73 -1
  70. package/dist/lib/storage/postgres/index.d.ts +6 -1
  71. package/dist/lib/storage/postgres/index.js +1 -1
  72. package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
  73. package/dist/lib/storage/postgres/taskQueue.js +10 -3
  74. package/dist/lib/syndicateSchema.d.ts +38 -1
  75. package/dist/lib/syndicateSchema.js +25 -18
  76. package/dist/lib/tools/auth.d.ts +13 -1
  77. package/dist/lib/tools/auth.js +10 -2
  78. package/dist/lib/tools/credentialHosts.d.ts +88 -0
  79. package/dist/lib/tools/credentialHosts.js +165 -0
  80. package/dist/lib/tools/credentialUses.d.ts +92 -0
  81. package/dist/lib/tools/credentialUses.js +112 -0
  82. package/dist/lib/tools/mcpToolFactory.js +1 -1
  83. package/dist/lib/tools/oauthConsent.js +8 -1
  84. package/dist/lib/tools/oauthHosts.d.ts +2 -0
  85. package/dist/lib/tools/oauthHosts.js +2 -2
  86. package/dist/lib/tools/oauthTools.d.ts +10 -1
  87. package/dist/lib/tools/oauthTools.js +29 -3
  88. package/dist/lib/tools/openapiTools.d.ts +7 -0
  89. package/dist/lib/tools/openapiTools.js +49 -3
  90. package/dist/lib/tools/taskTools.d.ts +32 -2
  91. package/dist/lib/tools/taskTools.js +93 -33
  92. package/dist/lib/workflow/turn.d.ts +22 -3
  93. package/dist/lib/workflow/turn.js +151 -31
  94. package/dist/lib/workflow.d.ts +10 -7
  95. package/dist/lib/workflow.js +10 -7
  96. package/dist/scripts/a2a_server.d.ts +2 -2
  97. package/dist/scripts/a2a_server.js +11 -2
  98. package/dist/scripts/assistant_worker.d.ts +2 -0
  99. package/dist/scripts/assistant_worker.js +13 -4
  100. package/dist/scripts/doctor.d.ts +4 -2
  101. package/dist/scripts/doctor.js +6 -4
  102. package/dist/scripts/setup.d.ts +23 -0
  103. package/dist/scripts/setup.js +154 -0
  104. package/dist/scripts/skills_install.d.ts +3 -1
  105. package/dist/scripts/skills_install.js +17 -2
  106. package/dist/scripts/yaml_codemod.d.ts +12 -2
  107. package/dist/scripts/yaml_codemod.js +115 -33
  108. package/package.json +4 -1
  109. package/skills/README.md +11 -2
  110. package/skills/briefs/_shared.md +1 -1
  111. package/skills/briefs/melchizedek-models.md +4 -2
  112. package/skills/briefs/skills-readme.md +3 -1
  113. package/skills/melchizedek-models/SKILL.md +6 -2
  114. package/skills/melchizedek-onboard/SKILL.md +49 -0
  115. package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
  116. package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
  117. package/skills/melchizedek-onboard-local/SKILL.md +41 -0
  118. package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
@@ -11,7 +11,9 @@
11
11
  * removed that runtime). Lists the tools that need an OAuth grant (an
12
12
  * `auth: { oauth2 }` or `mcp_auth`, ADR 0112): each user's consent, or the
13
13
  * server's own client credentials and whether their variables are set
14
- * (names only). Closes with the env vars that would
14
+ * (names only). When any A2A identity or billing variable is set, a
15
+ * `serving` line names the A2A_AUTH mode and A2A_KEY_MODE and what the
16
+ * server would refuse to start on. Closes with the env vars that would
15
17
  * unlock the most, where to get each, and the first command to try.
16
18
  *
17
19
  * Read-only: never edits .env, never sends a request, never prints a key
@@ -20,7 +22,7 @@
20
22
  * Usage:
21
23
  * npm run doctor
22
24
  * npm run doctor -- --json # machine-readable
23
- * npm run doctor -- --check # exit 1 when any syndicate is blocked
25
+ * npm run doctor -- --check # exit 1 on a blocked syndicate, a runtime problem, or an OAuth or credential host problem
24
26
  * npm run doctor -- --matrix # the provider × capability matrix
25
27
  * npm run doctor -- --fix-namespaces [file…] # give long-term syndicates a memory_namespace
26
28
  * MELCHIZEDEK_AGENTS_DIR=/path npm run doctor
@@ -11,7 +11,9 @@
11
11
  * removed that runtime). Lists the tools that need an OAuth grant (an
12
12
  * `auth: { oauth2 }` or `mcp_auth`, ADR 0112): each user's consent, or the
13
13
  * server's own client credentials and whether their variables are set
14
- * (names only). Closes with the env vars that would
14
+ * (names only). When any A2A identity or billing variable is set, a
15
+ * `serving` line names the A2A_AUTH mode and A2A_KEY_MODE and what the
16
+ * server would refuse to start on. Closes with the env vars that would
15
17
  * unlock the most, where to get each, and the first command to try.
16
18
  *
17
19
  * Read-only: never edits .env, never sends a request, never prints a key
@@ -20,7 +22,7 @@
20
22
  * Usage:
21
23
  * npm run doctor
22
24
  * npm run doctor -- --json # machine-readable
23
- * npm run doctor -- --check # exit 1 when any syndicate is blocked
25
+ * npm run doctor -- --check # exit 1 on a blocked syndicate, a runtime problem, or an OAuth or credential host problem
24
26
  * npm run doctor -- --matrix # the provider × capability matrix
25
27
  * npm run doctor -- --fix-namespaces [file…] # give long-term syndicates a memory_namespace
26
28
  * MELCHIZEDEK_AGENTS_DIR=/path npm run doctor
@@ -28,7 +30,7 @@
28
30
  import fs from 'node:fs';
29
31
  import path from 'node:path';
30
32
  import { loadEnv } from '../lib/loadEnv.js';
31
- import { renderDoctor, runDoctor } from '../lib/doctor.js';
33
+ import { checkProblems, renderDoctor, runDoctor } from '../lib/doctor.js';
32
34
  import { renderCapabilityMatrix } from '../lib/models/capabilities.js';
33
35
  import { assignMemoryNamespace, LEGACY_MEMORY_APP_NAME } from '../lib/memory/namespace.js';
34
36
  loadEnv();
@@ -91,5 +93,5 @@ if (json) {
91
93
  else {
92
94
  console.log(renderDoctor(result, { color: !noColor }));
93
95
  }
94
- if (check && (result.counts.blocked > 0 || result.runtime.problem || result.oauth?.problems.length))
96
+ if (check && checkProblems(result).length)
95
97
  process.exit(1);
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/setup.ts — the `melchizedek-setup` bin and `npm run setup`.
4
+ *
5
+ * The onboarding command: a menu of authentication levels (no key, one
6
+ * provider, several, a gateway, a cloud platform, per-caller keys, caller
7
+ * tokens, OAuth tool grants, and an honest entry for subscription sign-ins),
8
+ * each with a startup guide: the variables to set (names and shapes only),
9
+ * the doctor command that confirms it, the shipped files that run there and
10
+ * the first commands to try. The levels and guides live in lib/onboarding.ts;
11
+ * ONBOARDING.md is generated from the same function.
12
+ *
13
+ * npx melchizedek-setup # the menu (interactive terminal)
14
+ * npx melchizedek-setup --auto # detect from the environment, names only
15
+ * npx melchizedek-setup --level gateway # one guide, by id or number
16
+ * npx melchizedek-setup --level 2 --write-env # also create .env from .env.example
17
+ * npx melchizedek-setup --list
18
+ * npx melchizedek-setup --markdown # ONBOARDING.md, regenerated
19
+ *
20
+ * Never prints a value. `--write-env` never overwrites `.env`, leaves every
21
+ * name blank, and refuses unless `git check-ignore` confirms `.env` is ignored.
22
+ */
23
+ export declare function main(argv?: string[]): Promise<number>;
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/setup.ts — the `melchizedek-setup` bin and `npm run setup`.
4
+ *
5
+ * The onboarding command: a menu of authentication levels (no key, one
6
+ * provider, several, a gateway, a cloud platform, per-caller keys, caller
7
+ * tokens, OAuth tool grants, and an honest entry for subscription sign-ins),
8
+ * each with a startup guide: the variables to set (names and shapes only),
9
+ * the doctor command that confirms it, the shipped files that run there and
10
+ * the first commands to try. The levels and guides live in lib/onboarding.ts;
11
+ * ONBOARDING.md is generated from the same function.
12
+ *
13
+ * npx melchizedek-setup # the menu (interactive terminal)
14
+ * npx melchizedek-setup --auto # detect from the environment, names only
15
+ * npx melchizedek-setup --level gateway # one guide, by id or number
16
+ * npx melchizedek-setup --level 2 --write-env # also create .env from .env.example
17
+ * npx melchizedek-setup --list
18
+ * npx melchizedek-setup --markdown # ONBOARDING.md, regenerated
19
+ *
20
+ * Never prints a value. `--write-env` never overwrites `.env`, leaves every
21
+ * name blank, and refuses unless `git check-ignore` confirms `.env` is ignored.
22
+ */
23
+ import { realpathSync } from 'node:fs';
24
+ import { createInterface } from 'node:readline/promises';
25
+ import { fileURLToPath, pathToFileURL } from 'node:url';
26
+ import { loadEnv } from '../lib/loadEnv.js';
27
+ import { runDoctor } from '../lib/doctor.js';
28
+ import { detectLevels, highestLevel, LEVELS, levelById, renderAuto, renderGuide, renderMenu, renderOnboardingDoc, writeEnvForLevel, } from '../lib/onboarding.js';
29
+ const USAGE = `melchizedek-setup — start from what you have
30
+
31
+ melchizedek-setup the menu of authentication levels
32
+ melchizedek-setup --auto detect the highest level from the environment (names only)
33
+ melchizedek-setup --level <id|number> print one level's startup guide
34
+ melchizedek-setup --list the levels
35
+ melchizedek-setup --markdown every guide as Markdown (ONBOARDING.md)
36
+
37
+ --write-env with --level or --auto: create .env from .env.example with the level's
38
+ names left blank (never overwrites; requires git to ignore .env)
39
+ --clone | --package spell commands for a clone or an installed package (default: detected)
40
+
41
+ Levels:
42
+ ${renderMenu()}
43
+ `;
44
+ function pickLevel(raw) {
45
+ const n = Number(raw);
46
+ if (Number.isInteger(n) && n >= 1 && n <= LEVELS.length)
47
+ return LEVELS[n - 1];
48
+ return levelById(raw.trim());
49
+ }
50
+ function writeEnv(level) {
51
+ const r = writeEnvForLevel(level);
52
+ switch (r.status) {
53
+ case 'written':
54
+ console.log(`\n✓ wrote ${r.path} (mode 600) from .env.example${r.added.length ? `, adding blank: ${r.added.join(', ')}` : ''}. Fill in the values yourself, in your editor.`);
55
+ return 0;
56
+ case 'exists':
57
+ console.log(`\n· kept ${r.path}: it exists, and setup never overwrites it. Add the names above to it yourself.`);
58
+ return 0;
59
+ case 'not-ignored':
60
+ console.error(`\n✗ did not write .env: ${r.reason}`);
61
+ return 1;
62
+ case 'no-template':
63
+ console.error('\n✗ did not write .env: .env.example was not found beside the package');
64
+ return 1;
65
+ }
66
+ }
67
+ export async function main(argv = process.argv.slice(2)) {
68
+ const args = argv.filter((a) => a !== '--');
69
+ const flag = (name) => {
70
+ const i = args.indexOf(`--${name}`);
71
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : undefined;
72
+ };
73
+ const inPackage = fileURLToPath(import.meta.url).includes('node_modules');
74
+ const form = args.includes('--clone') ? 'clone' : args.includes('--package') ? 'package' : inPackage ? 'package' : 'clone';
75
+ if (args.includes('--help') || args.includes('-h')) {
76
+ console.log(USAGE);
77
+ return 0;
78
+ }
79
+ if (args.includes('--list')) {
80
+ console.log(renderMenu());
81
+ return 0;
82
+ }
83
+ if (args.includes('--markdown')) {
84
+ process.stdout.write(renderOnboardingDoc());
85
+ return 0;
86
+ }
87
+ let level;
88
+ if (args.includes('--auto')) {
89
+ loadEnv();
90
+ const result = runDoctor();
91
+ console.log(renderAuto(result, form));
92
+ if (!args.includes('--write-env'))
93
+ return 0;
94
+ return writeEnv(highestLevel(detectLevels(result)));
95
+ }
96
+ const raw = flag('level');
97
+ if (raw !== undefined) {
98
+ level = pickLevel(raw);
99
+ if (!level) {
100
+ console.error(`✗ unknown level "${raw}"\n\n${USAGE}`);
101
+ return 2;
102
+ }
103
+ console.log(renderGuide(level, form));
104
+ return args.includes('--write-env') ? writeEnv(level) : 0;
105
+ }
106
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
107
+ console.log(USAGE);
108
+ return 0;
109
+ }
110
+ // The interactive menu.
111
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
112
+ try {
113
+ console.log(`What do you have? Pick the closest; you can come back for another.\n\n${renderMenu()}\n`);
114
+ const answer = (await rl.question(`Level [1-${LEVELS.length}], a = detect from my environment, q = quit: `)).trim().toLowerCase();
115
+ if (answer === 'q' || answer === '')
116
+ return 0;
117
+ if (answer === 'a') {
118
+ loadEnv();
119
+ console.log(`\n${renderAuto(runDoctor(), form)}`);
120
+ return 0;
121
+ }
122
+ level = pickLevel(answer);
123
+ if (!level) {
124
+ console.error(`✗ "${answer}" is not a level`);
125
+ return 2;
126
+ }
127
+ console.log(`\n${renderGuide(level, form)}\n`);
128
+ if (level.detectable) {
129
+ const write = (await rl.question('Create .env from .env.example with these names left blank? (never overwrites) [y/N]: ')).trim().toLowerCase();
130
+ if (write === 'y' || write === 'yes')
131
+ return writeEnv(level);
132
+ }
133
+ return 0;
134
+ }
135
+ finally {
136
+ rl.close();
137
+ }
138
+ }
139
+ const invokedAsMain = (() => {
140
+ if (!process.argv[1])
141
+ return false;
142
+ try {
143
+ return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ })();
149
+ if (invokedAsMain) {
150
+ main().then((code) => process.exit(code), (err) => {
151
+ console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
152
+ process.exit(1);
153
+ });
154
+ }
@@ -2,6 +2,8 @@
2
2
  import { type InstallOptions } from '../lib/skills.ts';
3
3
  export declare function parseArgs(argv: string[]): {
4
4
  command: string;
5
- opts: InstallOptions;
5
+ opts: InstallOptions & {
6
+ agentsMd?: boolean;
7
+ };
6
8
  };
7
9
  export declare function main(argv?: string[]): number;
@@ -16,6 +16,7 @@
16
16
  * melchizedek-skills install --only melchizedek,melchizedek-scribe
17
17
  * melchizedek-skills install --force # overwrite files that differ
18
18
  * melchizedek-skills install --dry-run
19
+ * melchizedek-skills install --agents-md # also point AGENTS.md at the skills
19
20
  *
20
21
  * Clone equivalent: npm run skills:install -- <same flags>.
21
22
  * Nothing is read from the network; nothing is written outside the chosen
@@ -23,12 +24,12 @@
23
24
  */
24
25
  import { realpathSync } from 'node:fs';
25
26
  import { pathToFileURL } from 'node:url';
26
- import { DEFAULT_TARGETS, SKILL_TARGETS, destinationsFor, installSkills, listSkills, } from '../lib/skills.js';
27
+ import { DEFAULT_TARGETS, SKILL_TARGETS, destinationsFor, installSkills, listSkills, writeAgentsMdPointer, } from '../lib/skills.js';
27
28
  const USAGE = `melchizedek-skills — install the Melchizedek agent-skills suite into your coding agent
28
29
 
29
30
  melchizedek-skills list
30
31
  melchizedek-skills paths
31
- melchizedek-skills install [--for <targets>] [--global] [--dir <path>] [--only <skills>] [--force] [--dry-run]
32
+ melchizedek-skills install [--for <targets>] [--global] [--dir <path>] [--only <skills>] [--force] [--dry-run] [--agents-md]
32
33
 
33
34
  --for comma list of: ${Object.keys(SKILL_TARGETS).join(', ')}, all (default: ${DEFAULT_TARGETS.join(',')})
34
35
  --global write to the home-directory locations instead of this project's
@@ -36,6 +37,10 @@ const USAGE = `melchizedek-skills — install the Melchizedek agent-skills suite
36
37
  --only comma list of skill names (default: every skill)
37
38
  --force overwrite a file that exists with different content
38
39
  --dry-run print what would be written, write nothing
40
+ --agents-md also write a pointer block into ./AGENTS.md (created if absent; only the
41
+ block between its markers is ever rewritten). Project installs only.
42
+
43
+ Onboarding starts with the melchizedek-onboard skill, or: npx melchizedek-setup
39
44
  `;
40
45
  export function parseArgs(argv) {
41
46
  const [command = 'help', ...rest] = argv.filter((a) => a !== '--');
@@ -62,6 +67,8 @@ export function parseArgs(argv) {
62
67
  opts.force = true;
63
68
  else if (a === '--dry-run')
64
69
  opts.dryRun = true;
70
+ else if (a === '--agents-md')
71
+ opts.agentsMd = true;
65
72
  else if (a === '-h' || a === '--help')
66
73
  return { command: 'help', opts };
67
74
  else
@@ -117,6 +124,14 @@ export function main(argv = process.argv.slice(2)) {
117
124
  for (const f of r.skipped)
118
125
  console.log(` ! ${f} exists with different content (use --force to overwrite)`);
119
126
  }
127
+ if (opts.agentsMd) {
128
+ if (opts.global || opts.dir) {
129
+ console.error('✗ --agents-md writes ./AGENTS.md beside a project install; it is not used with --global or --dir');
130
+ return 2;
131
+ }
132
+ const r = writeAgentsMdPointer(process.cwd(), destinationsFor(opts), { dryRun: opts.dryRun });
133
+ console.log(`${opts.dryRun ? '○' : '✓'} ${r.path}: ${r.status}${opts.dryRun ? ' (dry run)' : ''}`);
134
+ }
120
135
  if (!opts.dir) {
121
136
  const sample = destinationsFor(opts);
122
137
  console.log(`\nOpen your coding agent in ${opts.global ? 'any project' : 'this project'} and ask it about a Melchizedek syndicate; the skills load from ${sample.map((d) => d).join(' and ')}.`);
@@ -19,8 +19,18 @@
19
19
  * generateContentConfig.thinkingConfig / reasoningEffort → reasoning:, ONLY
20
20
  * when the engine reads the same ReasoningSetting before and after for
21
21
  * the agent's model (reasoningOf over the old config equals reasoningOf
22
- * over what reasoningConfig makes of the new key).
23
- * generateContentConfig.thinkingConfig.includeThoughts: false → dropped (the default; read by nothing)
22
+ * over what reasoningConfig makes of the new key). `xhigh` and `max`
23
+ * included (ADR 0117).
24
+ * generateContentConfig.thinkingConfig.includeThoughts → dropped: the
25
+ * engine reads it nowhere, and the Gemini adapter asks for the thought
26
+ * trace itself whenever reasoning is not `none` (ADR 0117); `true` gets a
27
+ * note saying so.
28
+ * generateContentConfig.toolConfig → tool_choice: (ADR 0117), when it holds
29
+ * only functionCallingConfig (mode AUTO, NONE or ANY, and
30
+ * allowedFunctionNames) and includeServerSideToolInvocations, which the
31
+ * compiler sets on every agent: AUTO is `auto`, NONE `none`, ANY with
32
+ * one name `{ name }`, and ANY with none or several `required`, which is
33
+ * what the engine reads (genaiMapping toolChoiceOf) either way.
24
34
  *
25
35
  * Anything else stays under generateContentConfig with a note naming its key
26
36
  * path (never its value); an emptied generateContentConfig is removed. An
@@ -19,8 +19,18 @@
19
19
  * generateContentConfig.thinkingConfig / reasoningEffort → reasoning:, ONLY
20
20
  * when the engine reads the same ReasoningSetting before and after for
21
21
  * the agent's model (reasoningOf over the old config equals reasoningOf
22
- * over what reasoningConfig makes of the new key).
23
- * generateContentConfig.thinkingConfig.includeThoughts: false → dropped (the default; read by nothing)
22
+ * over what reasoningConfig makes of the new key). `xhigh` and `max`
23
+ * included (ADR 0117).
24
+ * generateContentConfig.thinkingConfig.includeThoughts → dropped: the
25
+ * engine reads it nowhere, and the Gemini adapter asks for the thought
26
+ * trace itself whenever reasoning is not `none` (ADR 0117); `true` gets a
27
+ * note saying so.
28
+ * generateContentConfig.toolConfig → tool_choice: (ADR 0117), when it holds
29
+ * only functionCallingConfig (mode AUTO, NONE or ANY, and
30
+ * allowedFunctionNames) and includeServerSideToolInvocations, which the
31
+ * compiler sets on every agent: AUTO is `auto`, NONE `none`, ANY with
32
+ * one name `{ name }`, and ANY with none or several `required`, which is
33
+ * what the engine reads (genaiMapping toolChoiceOf) either way.
24
34
  *
25
35
  * Anything else stays under generateContentConfig with a note naming its key
26
36
  * path (never its value); an emptied generateContentConfig is removed. An
@@ -41,7 +51,8 @@ import { join } from 'node:path';
41
51
  import { pathToFileURL } from 'node:url';
42
52
  import { isDeepStrictEqual } from 'node:util';
43
53
  import { isMap, isPair, isScalar, isSeq, parseDocument } from 'yaml';
44
- import { reasoningOf } from '../lib/models/genaiMapping.js';
54
+ import { functionCallingConfigOf } from '../lib/agentDialect.js';
55
+ import { reasoningOf, toolChoiceOf } from '../lib/models/genaiMapping.js';
45
56
  import { reasoningConfig } from '../lib/models/reasoning.js';
46
57
  /** generateContentConfig keys with a `sampling:` form, and the check its value must pass. */
47
58
  const SAMPLING = new Map([
@@ -51,9 +62,11 @@ const SAMPLING = new Map([
51
62
  ['stopSequences', { key: 'stop', ok: (v) => Array.isArray(v) && v.length > 0 && v.every((s) => typeof s === 'string') }],
52
63
  ]);
53
64
  const OUTPUT_MIMES = new Set(['application/json', 'text/plain']);
54
- const THINKING_LEVEL = new Map([['MINIMAL', 'none'], ['LOW', 'low'], ['MEDIUM', 'medium'], ['HIGH', 'high']]);
55
- const EFFORT_WORD = new Map([['none', 'none'], ['minimal', 'none'], ['low', 'low'], ['medium', 'medium'], ['high', 'high']]);
56
65
  const THINKING_KEYS = new Set(['thinkingLevel', 'thinkingBudget', 'includeThoughts']);
66
+ /** toolConfig keys the codemod can account for: the choice, and the flag the compiler sets on every agent. */
67
+ const TOOL_CONFIG_KEYS = new Set(['functionCallingConfig', 'includeServerSideToolInvocations']);
68
+ const FUNCTION_CALLING_KEYS = new Set(['mode', 'allowedFunctionNames']);
69
+ const FUNCTION_CALLING_MODES = new Set(['AUTO', 'NONE', 'ANY']);
57
70
  // ── The source, by line ──────────────────────────────────────────────────────
58
71
  class Source {
59
72
  text;
@@ -256,11 +269,17 @@ function planAgent(doc, src, map, path, notes) {
256
269
  const re = chunks.find((c) => c.name === 'reasoningEffort');
257
270
  if (tc || re)
258
271
  planThinking(plan, map, tc, re, valueOf, notes);
272
+ // Tool choice.
273
+ const toolConfig = chunks.find((c) => c.name === 'toolConfig');
274
+ if (toolConfig)
275
+ planToolChoice(plan, map, toolConfig, valueOf(toolConfig), notes);
259
276
  // Leftovers: everything not moved, converted or dropped.
260
277
  const handled = new Set([...plan.sampling, ...(plan.mime ? [plan.mime] : []), ...(plan.reasoning?.remove ?? [])]);
261
278
  if (plan.dropInclude?.empties)
262
279
  handled.add(plan.dropInclude.tc);
263
- const noted = new Set(['thinkingConfig', 'reasoningEffort', 'responseMimeType', ...SAMPLING.keys()]);
280
+ if (plan.toolChoice)
281
+ handled.add(plan.toolChoice.chunk);
282
+ const noted = new Set(['thinkingConfig', 'reasoningEffort', 'responseMimeType', 'toolConfig', ...SAMPLING.keys()]);
264
283
  for (const c of chunks) {
265
284
  if (handled.has(c) || noted.has(c.name))
266
285
  continue;
@@ -268,27 +287,74 @@ function planAgent(doc, src, map, path, notes) {
268
287
  }
269
288
  }
270
289
  }
271
- const changes = plan.sampling.length > 0 || plan.mime || plan.schema || plan.reasoning || plan.dropInclude;
290
+ const changes = plan.sampling.length > 0 || plan.mime || plan.schema || plan.reasoning || plan.dropInclude || plan.toolChoice;
272
291
  return changes ? plan : undefined;
273
292
  }
293
+ /**
294
+ * toolConfig → tool_choice, when the engine reads the same ToolChoice before
295
+ * and after (genaiMapping toolChoiceOf) and nothing else in it would be lost.
296
+ */
297
+ function planToolChoice(plan, map, chunk, value, notes) {
298
+ const where = `${plan.path}.generateContentConfig.toolConfig`;
299
+ const leave = (why) => {
300
+ notes.push(`${where}: ${why}; left in place`);
301
+ };
302
+ if (findPair(map, 'tool_choice'))
303
+ return leave(`${plan.path}.tool_choice is already set (the loader refuses both)`);
304
+ if (!value || typeof value !== 'object' || Array.isArray(value))
305
+ return leave('not a mapping');
306
+ const tc = value;
307
+ if (Object.keys(tc).some((k) => !TOOL_CONFIG_KEYS.has(k)))
308
+ return leave('it holds a key with no v2 form');
309
+ const fcc = tc.functionCallingConfig;
310
+ if (fcc !== undefined) {
311
+ if (!fcc || typeof fcc !== 'object' || Array.isArray(fcc))
312
+ return leave('functionCallingConfig is not a mapping');
313
+ const f = fcc;
314
+ if (Object.keys(f).some((k) => !FUNCTION_CALLING_KEYS.has(k)))
315
+ return leave('functionCallingConfig holds a key with no tool_choice form');
316
+ if (f.mode === 'VALIDATED')
317
+ return leave('mode VALIDATED (strict tool schemas) has no tool_choice form');
318
+ if (!FUNCTION_CALLING_MODES.has(f.mode))
319
+ return leave('its mode is not AUTO, NONE or ANY');
320
+ const names = f.allowedFunctionNames;
321
+ if (names !== undefined && !(Array.isArray(names) && names.every((n) => typeof n === 'string')))
322
+ return leave('allowedFunctionNames is not a list of names');
323
+ if (f.mode === 'ANY' && Array.isArray(names) && names.length > 1) {
324
+ notes.push(`${where}.functionCallingConfig.allowedFunctionNames: several names, which the engine sends as required (any tool); tool_choice: required says the same`);
325
+ }
326
+ }
327
+ // What the engine reads today is the v2 setting; the proof is that the v2
328
+ // key's engine form reads back the same.
329
+ const before = toolChoiceOf({ toolConfig: tc });
330
+ const setting = before.toolChoice ?? 'auto';
331
+ const after = toolChoiceOf({ toolConfig: { functionCallingConfig: functionCallingConfigOf(setting) } });
332
+ if (!isDeepStrictEqual(before, after))
333
+ return leave('tool_choice: would change what the engine reads');
334
+ plan.toolChoice = { setting, chunk };
335
+ }
274
336
  function planThinking(plan, map, tc, re, valueOf, notes) {
275
337
  const gccPath = `${plan.path}.generateContentConfig`;
276
338
  const where = [tc, re].filter((c) => !!c).map((c) => `${gccPath}.${c.name}`).join(', ');
277
339
  const tcValue = tc ? valueOf(tc) : undefined;
278
340
  const tcObject = tcValue && typeof tcValue === 'object' && !Array.isArray(tcValue) ? tcValue : undefined;
279
341
  const model = scalarString(map, 'model');
280
- // includeThoughts: false is the default and read by nothing: drop it from a
281
- // block thinkingConfig whatever else happens to the thinking keys.
342
+ // includeThoughts is read by nothing: the Gemini adapter asks for the
343
+ // thought trace whenever reasoning is not `none` (ADR 0117). Drop it from
344
+ // a block thinkingConfig whatever else happens to the thinking keys.
282
345
  const incPair = tc && isMap(tc.pair.value) && !tc.pair.value.flow ? findPair(tc.pair.value, 'includeThoughts') : undefined;
283
- const incFalse = incPair && isScalar(incPair.value) && incPair.value.value === false;
346
+ const incValue = incPair && isScalar(incPair.value) ? incPair.value.value : undefined;
347
+ const incBool = typeof incValue === 'boolean';
348
+ if (incValue === true) {
349
+ notes.push(`${gccPath}.thinkingConfig.includeThoughts: read by nothing (the Gemini adapter asks for the thought trace whenever reasoning is not none); dropped`);
350
+ }
351
+ const dropInclude = () => {
352
+ if (tc && incBool)
353
+ plan.dropInclude = { tc, inc: incPair, empties: tc.pair.value.items.length === 1 };
354
+ };
284
355
  const leave = (why) => {
285
356
  notes.push(`${where}: ${why}; left in place`);
286
- if (tc && incFalse) {
287
- const tcMap = tc.pair.value;
288
- plan.dropInclude = { tc, inc: incPair, empties: tcMap.items.length === 1 };
289
- }
290
- if (tcObject?.includeThoughts === true)
291
- notes.push(`${gccPath}.thinkingConfig.includeThoughts: no v2 form; left in place`);
357
+ dropInclude();
292
358
  };
293
359
  if (findPair(map, 'reasoning'))
294
360
  return leave(`${plan.path}.reasoning is already set (the loader refuses both)`);
@@ -298,35 +364,39 @@ function planThinking(plan, map, tc, re, valueOf, notes) {
298
364
  return leave('thinkingConfig is not a mapping');
299
365
  if (tcObject && Object.keys(tcObject).some((k) => !THINKING_KEYS.has(k)))
300
366
  return leave('thinkingConfig holds a key with no reasoning: form');
301
- if (tcObject && tcObject.includeThoughts !== undefined && tcObject.includeThoughts !== false)
302
- return leave('includeThoughts has no reasoning: form');
303
367
  const v1 = {};
304
368
  if (tc)
305
369
  v1.thinkingConfig = tcValue;
306
370
  if (re)
307
371
  v1.reasoningEffort = valueOf(re);
308
- let candidate;
309
- const level = THINKING_LEVEL.get(tcObject?.thinkingLevel);
310
- const budget = tcObject?.thinkingBudget;
311
- if (level)
312
- candidate = level;
313
- else if (typeof budget === 'number' && Number.isInteger(budget) && budget >= 0)
314
- candidate = { budget_tokens: budget };
315
- else if (EFFORT_WORD.has(v1.reasoningEffort))
316
- candidate = EFFORT_WORD.get(v1.reasoningEffort);
317
- if (candidate === undefined)
318
- return leave('no reasoning: setting reads the same');
372
+ // The setting is what the engine reads today; it converts when the v2
373
+ // key's engine form reads back the same for the agent's model.
319
374
  const before = reasoningOf(v1);
320
- const after = reasoningOf(reasoningConfig(model, candidate));
375
+ if (before === undefined) {
376
+ // A thinkingConfig holding includeThoughts alone: nothing to convert, and nothing left.
377
+ if (!re && tcObject && Object.keys(tcObject).every((k) => k === 'includeThoughts'))
378
+ return dropInclude();
379
+ return leave('no reasoning: setting reads the same');
380
+ }
381
+ const after = reasoningOf(reasoningConfig(model, before));
321
382
  if (!isDeepStrictEqual(before, after))
322
383
  return leave(`reasoning: would change what the engine reads for ${providerWord(model)}`);
323
- plan.reasoning = { setting: candidate, remove: [tc, re].filter((c) => !!c) };
384
+ plan.reasoning = { setting: before, remove: [tc, re].filter((c) => !!c) };
324
385
  }
325
386
  /** A model family word for a note: never the id's value beyond its prefix. */
326
387
  function providerWord(model) {
327
388
  return model.startsWith('gemini-') ? 'this Gemini model' : 'this model';
328
389
  }
329
390
  const reasoningText = (s) => (typeof s === 'string' ? s : `{ budget_tokens: ${s.budget_tokens} }`);
391
+ /** A tool name YAML reads back as that string unquoted: an identifier that is not a YAML keyword. */
392
+ const PLAIN_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
393
+ const YAML_WORDS = new Set(['true', 'false', 'null', 'yes', 'no', 'on', 'off', 'y', 'n']);
394
+ const toolChoiceText = (s) => {
395
+ if (typeof s === 'string')
396
+ return s;
397
+ const plain = PLAIN_NAME.test(s.name) && !YAML_WORDS.has(s.name.toLowerCase());
398
+ return `{ name: ${plain ? s.name : JSON.stringify(s.name)} }`;
399
+ };
330
400
  function chunkLines(src, c) {
331
401
  return src.lines.slice(c.from, c.to + 1);
332
402
  }
@@ -343,12 +413,14 @@ function renderGcc(src, plan) {
343
413
  const g = plan.gcc;
344
414
  if (!g)
345
415
  return undefined;
346
- if (!(plan.sampling.length || plan.mime || plan.reasoning || plan.dropInclude))
416
+ if (!(plan.sampling.length || plan.mime || plan.reasoning || plan.dropInclude || plan.toolChoice))
347
417
  return undefined;
348
418
  const pad = ' '.repeat(g.indent);
349
419
  const removed = new Set([...plan.sampling, ...(plan.mime ? [plan.mime] : []), ...(plan.reasoning?.remove ?? [])]);
350
420
  if (plan.dropInclude?.empties)
351
421
  removed.add(plan.dropInclude.tc);
422
+ if (plan.toolChoice)
423
+ removed.add(plan.toolChoice.chunk);
352
424
  const leftovers = g.chunks.filter((c) => !removed.has(c));
353
425
  const out = [];
354
426
  if (leftovers.length === 0) {
@@ -367,6 +439,12 @@ function renderGcc(src, plan) {
367
439
  out.push(pad + comment);
368
440
  out.push(`${pad}reasoning: ${reasoningText(plan.reasoning.setting)}`);
369
441
  }
442
+ if (plan.toolChoice) {
443
+ const c = plan.toolChoice.chunk;
444
+ for (const comment of commentsIn(src, c.from, c.to, c.pair))
445
+ out.push(pad + comment);
446
+ out.push(`${pad}tool_choice: ${toolChoiceText(plan.toolChoice.setting)}`);
447
+ }
370
448
  if (plan.mime && !plan.schema) {
371
449
  out.push(`${pad}output:`);
372
450
  out.push(...renamed(src, plan.mime, 'mime'));
@@ -444,6 +522,10 @@ function expectAgent(agent, plan) {
444
522
  delete gcc[c.name];
445
523
  agent.reasoning = plan.reasoning.setting;
446
524
  }
525
+ if (plan.toolChoice) {
526
+ delete gcc.toolConfig;
527
+ agent.tool_choice = plan.toolChoice.setting;
528
+ }
447
529
  if (plan.dropInclude) {
448
530
  const tc = gcc.thinkingConfig;
449
531
  delete tc.includeThoughts;
@@ -522,7 +604,7 @@ function yamlFiles(path) {
522
604
  }
523
605
  const USAGE = `usage: yaml_codemod [--check] <file|dir>...
524
606
  Rewrites agent v1 spellings (generateContentConfig, outputSchema) into the
525
- v2 keys (sampling, output, reasoning), in place. A directory means every
607
+ v2 keys (sampling, output, reasoning, tool_choice), in place. A directory means every
526
608
  *.yaml below it. --check writes nothing and exits 1 if any file would change.`;
527
609
  export function main(argv) {
528
610
  const check = argv.includes('--check');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melchizedek-agents",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A multi-model, multi-agent orchestration framework — YAML-defined syndicates on the engine's own agent loop, Supabase or Postgres session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,6 +54,7 @@
54
54
  "melchizedek-worker": "./dist/scripts/assistant_worker.js",
55
55
  "melchizedek-db": "./dist/scripts/db.js",
56
56
  "melchizedek-init": "./dist/scripts/init.js",
57
+ "melchizedek-setup": "./dist/scripts/setup.js",
57
58
  "melchizedek-registry": "./dist/scripts/registry.js",
58
59
  "melchizedek-codemod": "./dist/scripts/yaml_codemod.js"
59
60
  },
@@ -69,6 +70,7 @@
69
70
  ".env.example",
70
71
  "CHANGELOG.md",
71
72
  "QUICKSTART.md",
73
+ "ONBOARDING.md",
72
74
  "DOCUMENTATION.md",
73
75
  "AGENT_SETUP.md",
74
76
  "skills"
@@ -117,6 +119,7 @@
117
119
  "yaml:codemod": "node --disable-warning=DEP0040 --experimental-strip-types scripts/yaml_codemod.ts",
118
120
  "start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
119
121
  "test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
122
+ "setup": "node --disable-warning=DEP0040 --experimental-strip-types scripts/setup.ts",
120
123
  "init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/init.ts",
121
124
  "db": "node --disable-warning=DEP0040 --experimental-strip-types scripts/db.ts",
122
125
  "registry": "node --disable-warning=DEP0040 --experimental-strip-types scripts/registry.ts",
package/skills/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What this is
4
4
 
5
- This directory contains a suite of six Agent Skills built to the open SKILL.md standard: one directory per skill, a SKILL.md file with `name` and `description` frontmatter, and optional supporting files. The skills teach a coding agent how to find, run, author, serve, remember with, and write with Melchizedek syndicates. They ship in the npm package melchizedek-agents and in the public repository, inside `skills/`.
5
+ This directory contains a suite of eleven Agent Skills built to the open SKILL.md standard: one directory per skill, a SKILL.md file with `name` and `description` frontmatter, and optional supporting files. The skills teach a coding agent how to onboard a person from the credentials they have, and how to find, run, author, serve, remember with, and write with Melchizedek syndicates. They ship in the npm package melchizedek-agents and in the public repository, inside `skills/`.
6
6
 
7
7
  ## The skills
8
8
 
@@ -13,6 +13,14 @@ This directory contains a suite of six Agent Skills built to the open SKILL.md s
13
13
  - `melchizedek-models`: Teaches model ids and providers, keys, keyless Ollama, the gateway fallback, per-agent settings, and the errors.
14
14
  - `melchizedek-scribe`: Teaches writing documents from a brief with the Scribe syndicate.
15
15
 
16
+ Five onboarding skills route a person by what they have and walk the startup guide for it (`npx melchizedek-setup --level <id>` prints each guide):
17
+
18
+ - `melchizedek-onboard`: The triage. Asks what the person has and routes to one of nine authentication levels and the skill below that owns it.
19
+ - `melchizedek-onboard-local`: No key: Ollama and the keyless syndicates.
20
+ - `melchizedek-onboard-keys`: One provider's key, several, a gateway key, and the honest answer for a ChatGPT / Codex, Claude.ai or Gemini CLI sign-in.
21
+ - `melchizedek-onboard-cloud`: Vertex AI, Amazon Bedrock and Azure OpenAI credentials.
22
+ - `melchizedek-onboard-serve`: Serving to others: per-caller keys (BYOK), caller identities (`A2A_AUTH`), and OAuth tool grants.
23
+
16
24
  ## Install
17
25
 
18
26
  Install the skills using any of three methods.
@@ -37,6 +45,7 @@ The command accepts these flags:
37
45
  - `--only <names>` installs a subset.
38
46
  - `--force` overwrites a file that differs.
39
47
  - `--dry-run` prints without writing.
48
+ - `--agents-md` also writes a pointer block into the project's `AGENTS.md` (created when absent; only the block between its markers is ever rewritten).
40
49
 
41
50
  You can inspect target paths and available skills:
42
51
 
@@ -69,6 +78,6 @@ Install by hand by copying any `skills/<name>/` directory into a target location
69
78
 
70
79
  ## How the prose was written
71
80
 
72
- The Scribe syndicate (`config/agents/examples/scribe.yaml`, a Gemini agent that writes a document from a technical brief and audits it against the brief) wrote each SKILL.md body from one brief per skill. A person then reviewed each file. The briefs carry the facts; the Scribe carries the voice. To change a skill, change the facts and rerun with the melchizedek-scribe skill.
81
+ The five onboarding skills are short procedures written by hand: they print their facts from `melchizedek-setup` instead of restating them, so the facts live in one place (`lib/onboarding.ts`). The Scribe syndicate (`config/agents/examples/scribe.yaml`, a Gemini agent that writes a document from a technical brief and audits it against the brief) wrote every other SKILL.md body from one brief per skill. A person then reviewed each file. The briefs carry the facts; the Scribe carries the voice. To change a skill, change the facts and rerun with the melchizedek-scribe skill.
73
82
 
74
83
  The suite shares the MIT license of the package.