melchizedek-agents 0.17.0 → 0.19.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 (290) hide show
  1. package/.env.example +35 -3
  2. package/AGENT_SETUP.md +2 -1
  3. package/CHANGELOG.md +929 -0
  4. package/DOCUMENTATION.md +290 -66
  5. package/README.md +42 -1
  6. package/config/agents/examples/harness.yaml +3 -1
  7. package/config/agents/examples/model_zoo.yaml +10 -9
  8. package/config/agents/syndicateSchema.yaml +63 -16
  9. package/config/agents/templates/account_memory.yaml +2 -6
  10. package/config/agents/templates/case_desk.yaml +3 -9
  11. package/config/agents/templates/draft_review.yaml +3 -9
  12. package/config/agents/templates/intake_extractor.yaml +1 -3
  13. package/config/agents/templates/research_brief.yaml +3 -9
  14. package/config/agents/templates/research_desk.yaml +3 -9
  15. package/config/agents/templates/review_panel.yaml +4 -12
  16. package/config/agents/templates/support_triage.yaml +4 -12
  17. package/config/agents/templates/systems_operator.yaml +2 -6
  18. package/db/hardening.sql +10 -3
  19. package/db/migrations/0012_audit_log.sql +98 -0
  20. package/db/migrations/0013_tool_credentials.sql +260 -0
  21. package/dist/lib/a2a/app.d.ts +90 -6
  22. package/dist/lib/a2a/app.js +201 -6
  23. package/dist/lib/a2a/executor.d.ts +54 -7
  24. package/dist/lib/a2a/executor.js +134 -16
  25. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  26. package/dist/lib/a2a/remoteAgent.js +31 -14
  27. package/dist/lib/compile.d.ts +121 -31
  28. package/dist/lib/compile.js +153 -113
  29. package/dist/lib/compileAdk.d.ts +29 -0
  30. package/dist/lib/compileAdk.js +132 -0
  31. package/dist/lib/compileNative.d.ts +85 -0
  32. package/dist/lib/compileNative.js +222 -0
  33. package/dist/lib/config.d.ts +13 -5
  34. package/dist/lib/config.js +13 -5
  35. package/dist/lib/doctor.js +1 -0
  36. package/dist/lib/index.d.ts +2 -1
  37. package/dist/lib/index.js +2 -1
  38. package/dist/lib/loadEnv.d.ts +5 -0
  39. package/dist/lib/loadEnv.js +7 -0
  40. package/dist/lib/loadSyndicate.d.ts +22 -0
  41. package/dist/lib/memory/erase.d.ts +5 -3
  42. package/dist/lib/memory/erase.js +5 -2
  43. package/dist/lib/memory/namespace.d.ts +8 -1
  44. package/dist/lib/memory/namespace.js +12 -6
  45. package/dist/lib/memory/providers.d.ts +27 -0
  46. package/dist/lib/memory/providers.js +30 -1
  47. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  48. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  49. package/dist/lib/model.d.ts +47 -0
  50. package/dist/lib/model.js +37 -0
  51. package/dist/lib/models/adapterResolver.d.ts +113 -0
  52. package/dist/lib/models/adapterResolver.js +136 -0
  53. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  54. package/dist/lib/models/adkGeminiAdapter.js +367 -0
  55. package/dist/lib/models/adkShim.d.ts +111 -0
  56. package/dist/lib/models/adkShim.js +136 -0
  57. package/dist/lib/models/capabilities.d.ts +3 -0
  58. package/dist/lib/models/capabilities.js +20 -13
  59. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  60. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  61. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  62. package/dist/lib/models/circuitBreaker.js +70 -0
  63. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  64. package/dist/lib/models/claudeAdapter.js +669 -0
  65. package/dist/lib/models/claudeLlm.d.ts +39 -62
  66. package/dist/lib/models/claudeLlm.js +44 -349
  67. package/dist/lib/models/claudeModels.d.ts +127 -0
  68. package/dist/lib/models/claudeModels.js +237 -0
  69. package/dist/lib/models/contract.d.ts +395 -0
  70. package/dist/lib/models/contract.js +39 -0
  71. package/dist/lib/models/endpoints.js +2 -1
  72. package/dist/lib/models/errorResponse.d.ts +50 -0
  73. package/dist/lib/models/errorResponse.js +48 -0
  74. package/dist/lib/models/fallback.d.ts +48 -0
  75. package/dist/lib/models/fallback.js +116 -0
  76. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  77. package/dist/lib/models/fallbackAdapter.js +62 -0
  78. package/dist/lib/models/gateway.js +2 -1
  79. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  80. package/dist/lib/models/gatewayAdapter.js +81 -0
  81. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  82. package/dist/lib/models/gatewayLlm.js +14 -75
  83. package/dist/lib/models/geminiAdapter.d.ts +123 -0
  84. package/dist/lib/models/geminiAdapter.js +831 -0
  85. package/dist/lib/models/geminiState.d.ts +25 -0
  86. package/dist/lib/models/geminiState.js +25 -0
  87. package/dist/lib/models/genaiMapping.d.ts +263 -0
  88. package/dist/lib/models/genaiMapping.js +862 -0
  89. package/dist/lib/models/gptAdapter.d.ts +240 -0
  90. package/dist/lib/models/gptAdapter.js +804 -0
  91. package/dist/lib/models/gptLlm.d.ts +63 -123
  92. package/dist/lib/models/gptLlm.js +88 -602
  93. package/dist/lib/models/grokAdapter.d.ts +86 -0
  94. package/dist/lib/models/grokAdapter.js +142 -0
  95. package/dist/lib/models/grokLlm.d.ts +22 -45
  96. package/dist/lib/models/grokLlm.js +22 -67
  97. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  98. package/dist/lib/models/kimiAdapter.js +203 -0
  99. package/dist/lib/models/kimiLlm.d.ts +15 -107
  100. package/dist/lib/models/kimiLlm.js +17 -153
  101. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  102. package/dist/lib/models/ollamaAdapter.js +155 -0
  103. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  104. package/dist/lib/models/ollamaLlm.js +13 -151
  105. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  106. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  107. package/dist/lib/models/providerState.d.ts +63 -0
  108. package/dist/lib/models/providerState.js +61 -0
  109. package/dist/lib/models/reasoning.d.ts +26 -0
  110. package/dist/lib/models/reasoning.js +59 -0
  111. package/dist/lib/models/registry.d.ts +58 -35
  112. package/dist/lib/models/registry.js +89 -118
  113. package/dist/lib/models/retry.d.ts +2 -1
  114. package/dist/lib/models/retry.js +3 -2
  115. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  116. package/dist/lib/models/schemaNormalize.js +321 -1
  117. package/dist/lib/models/tracedGemini.d.ts +41 -0
  118. package/dist/lib/models/tracedGemini.js +98 -0
  119. package/dist/lib/models/urls.d.ts +11 -0
  120. package/dist/lib/models/urls.js +16 -0
  121. package/dist/lib/net/redirects.d.ts +21 -2
  122. package/dist/lib/net/redirects.js +11 -34
  123. package/dist/lib/observability/audit.d.ts +25 -0
  124. package/dist/lib/observability/audit.js +39 -0
  125. package/dist/lib/observability/lineage.d.ts +14 -0
  126. package/dist/lib/observability/lineage.js +33 -0
  127. package/dist/lib/observability/metrics.d.ts +6 -0
  128. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  129. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  130. package/dist/lib/observability/tracer.d.ts +39 -8
  131. package/dist/lib/observability/tracer.js +59 -20
  132. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  133. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  134. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  135. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  136. package/dist/lib/runtime/credentials.d.ts +59 -0
  137. package/dist/lib/runtime/credentials.js +62 -0
  138. package/dist/lib/runtime/events.d.ts +229 -0
  139. package/dist/lib/runtime/events.js +274 -0
  140. package/dist/lib/runtime/logging.d.ts +32 -0
  141. package/dist/lib/runtime/logging.js +55 -0
  142. package/dist/lib/runtime/memoryService.d.ts +72 -0
  143. package/dist/lib/runtime/memoryService.js +25 -0
  144. package/dist/lib/runtime/native/agentLoop.d.ts +203 -0
  145. package/dist/lib/runtime/native/agentLoop.js +687 -0
  146. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  147. package/dist/lib/runtime/native/compaction.js +191 -0
  148. package/dist/lib/runtime/native/delegate.d.ts +98 -0
  149. package/dist/lib/runtime/native/delegate.js +173 -0
  150. package/dist/lib/runtime/native/history.d.ts +83 -0
  151. package/dist/lib/runtime/native/history.js +312 -0
  152. package/dist/lib/runtime/native/interrupts.d.ts +126 -0
  153. package/dist/lib/runtime/native/interrupts.js +291 -0
  154. package/dist/lib/runtime/native/request.d.ts +215 -0
  155. package/dist/lib/runtime/native/request.js +585 -0
  156. package/dist/lib/runtime/native/selfCorrection.d.ts +106 -0
  157. package/dist/lib/runtime/native/selfCorrection.js +253 -0
  158. package/dist/lib/runtime/native/step.d.ts +148 -0
  159. package/dist/lib/runtime/native/step.js +239 -0
  160. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  161. package/dist/lib/runtime/native/taskMode.js +126 -0
  162. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  163. package/dist/lib/runtime/native/telemetry.js +299 -0
  164. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  165. package/dist/lib/runtime/native/tempState.js +56 -0
  166. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  167. package/dist/lib/runtime/nativeTurn.js +107 -0
  168. package/dist/lib/runtime/questions.d.ts +29 -7
  169. package/dist/lib/runtime/questions.js +47 -32
  170. package/dist/lib/runtime/runtimeFlag.d.ts +30 -0
  171. package/dist/lib/runtime/runtimeFlag.js +50 -0
  172. package/dist/lib/runtime/sessions.d.ts +166 -0
  173. package/dist/lib/runtime/sessions.js +201 -0
  174. package/dist/lib/runtime/syndicateTurn.d.ts +44 -6
  175. package/dist/lib/runtime/syndicateTurn.js +205 -33
  176. package/dist/lib/session/supabaseSessionService.d.ts +64 -17
  177. package/dist/lib/session/supabaseSessionService.js +148 -105
  178. package/dist/lib/session/transcript.d.ts +68 -22
  179. package/dist/lib/session/transcript.js +102 -28
  180. package/dist/lib/skills.js +33 -7
  181. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  182. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  183. package/dist/lib/storage/postgres/index.d.ts +15 -0
  184. package/dist/lib/storage/postgres/index.js +7 -0
  185. package/dist/lib/storage/postgres/sessionService.d.ts +43 -15
  186. package/dist/lib/storage/postgres/sessionService.js +128 -66
  187. package/dist/lib/syndicateSchema.d.ts +55 -0
  188. package/dist/lib/syndicateSchema.js +91 -7
  189. package/dist/lib/toolRegistry.d.ts +15 -2
  190. package/dist/lib/toolRegistry.js +33 -17
  191. package/dist/lib/tools/adkTool.d.ts +81 -0
  192. package/dist/lib/tools/adkTool.js +268 -0
  193. package/dist/lib/tools/auth.d.ts +118 -0
  194. package/dist/lib/tools/auth.js +89 -0
  195. package/dist/lib/tools/collectionsSearchTool.d.ts +7 -6
  196. package/dist/lib/tools/collectionsSearchTool.js +11 -26
  197. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  198. package/dist/lib/tools/credentialCipher.js +116 -0
  199. package/dist/lib/tools/credentialStore.d.ts +77 -0
  200. package/dist/lib/tools/credentialStore.js +246 -0
  201. package/dist/lib/tools/examples.d.ts +43 -0
  202. package/dist/lib/tools/examples.js +62 -0
  203. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  204. package/dist/lib/tools/generateImageTool.js +19 -29
  205. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  206. package/dist/lib/tools/inspectImageTool.js +13 -17
  207. package/dist/lib/tools/mcpToolFactory.d.ts +68 -1
  208. package/dist/lib/tools/mcpToolFactory.js +106 -50
  209. package/dist/lib/tools/memoryTools.d.ts +61 -0
  210. package/dist/lib/tools/memoryTools.js +119 -0
  211. package/dist/lib/tools/nativeTools.d.ts +38 -0
  212. package/dist/lib/tools/nativeTools.js +43 -0
  213. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  214. package/dist/lib/tools/oauthConsent.js +330 -0
  215. package/dist/lib/tools/openapi/call.d.ts +108 -0
  216. package/dist/lib/tools/openapi/call.js +295 -0
  217. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  218. package/dist/lib/tools/openapi/parse.js +570 -0
  219. package/dist/lib/tools/openapiTools.d.ts +31 -17
  220. package/dist/lib/tools/openapiTools.js +150 -117
  221. package/dist/lib/tools/scienceTools.d.ts +14 -14
  222. package/dist/lib/tools/skillToolset.d.ts +58 -87
  223. package/dist/lib/tools/skillToolset.js +61 -142
  224. package/dist/lib/tools/skills/env.d.ts +58 -0
  225. package/dist/lib/tools/skills/env.js +165 -0
  226. package/dist/lib/tools/skills/executor.d.ts +114 -0
  227. package/dist/lib/tools/skills/executor.js +331 -0
  228. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  229. package/dist/lib/tools/skills/frontmatter.js +215 -0
  230. package/dist/lib/tools/skills/loader.d.ts +91 -0
  231. package/dist/lib/tools/skills/loader.js +257 -0
  232. package/dist/lib/tools/skills/tools.d.ts +187 -0
  233. package/dist/lib/tools/skills/tools.js +386 -0
  234. package/dist/lib/tools/taskTools.d.ts +15 -15
  235. package/dist/lib/tools/tool.d.ts +354 -0
  236. package/dist/lib/tools/tool.js +304 -0
  237. package/dist/lib/tools/toolContract.d.ts +89 -33
  238. package/dist/lib/tools/toolContract.js +104 -82
  239. package/dist/lib/tools/urlContextTool.d.ts +3 -0
  240. package/dist/lib/tools/urlContextTool.js +5 -1
  241. package/dist/lib/tools/webExtractTool.d.ts +2 -2
  242. package/dist/lib/tools/webExtractTool.js +2 -1
  243. package/dist/lib/tools/webSearchTool.d.ts +6 -9
  244. package/dist/lib/tools/webSearchTool.js +14 -59
  245. package/dist/lib/tools/wikiTools.d.ts +19 -19
  246. package/dist/lib/tools/wikiTools.js +13 -9
  247. package/dist/lib/tools/xApiSearchTool.d.ts +2 -2
  248. package/dist/lib/tools/xApiSearchTool.js +2 -1
  249. package/dist/lib/tools/xSearchTool.d.ts +7 -9
  250. package/dist/lib/tools/xSearchTool.js +11 -62
  251. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  252. package/dist/lib/tools/xaiSearchParams.js +126 -0
  253. package/dist/lib/wiki/agentRun.d.ts +12 -0
  254. package/dist/lib/wiki/agentRun.js +72 -34
  255. package/dist/lib/wiki/entities.d.ts +12 -0
  256. package/dist/lib/wiki/entities.js +45 -3
  257. package/dist/lib/workflow/agentNode.d.ts +136 -0
  258. package/dist/lib/workflow/agentNode.js +274 -0
  259. package/dist/lib/workflow/graph.d.ts +177 -0
  260. package/dist/lib/workflow/graph.js +458 -0
  261. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  262. package/dist/lib/workflow/nodeEvents.js +101 -0
  263. package/dist/lib/workflow/pause.d.ts +122 -0
  264. package/dist/lib/workflow/pause.js +180 -0
  265. package/dist/lib/workflow/resume.d.ts +165 -0
  266. package/dist/lib/workflow/resume.js +440 -0
  267. package/dist/lib/workflow/route.d.ts +58 -0
  268. package/dist/lib/workflow/route.js +69 -0
  269. package/dist/lib/workflow/scheduler.d.ts +415 -0
  270. package/dist/lib/workflow/scheduler.js +743 -0
  271. package/dist/lib/workflow/toolNode.d.ts +127 -0
  272. package/dist/lib/workflow/toolNode.js +236 -0
  273. package/dist/lib/workflow/turn.d.ts +118 -0
  274. package/dist/lib/workflow/turn.js +216 -0
  275. package/dist/lib/workflowConfig.d.ts +3 -2
  276. package/dist/lib/workflowConfig.js +3 -12
  277. package/dist/scripts/a2a_server.js +55 -11
  278. package/dist/scripts/assistant_worker.d.ts +2 -1
  279. package/dist/scripts/assistant_worker.js +9 -4
  280. package/dist/scripts/db.d.ts +9 -0
  281. package/dist/scripts/db.js +30 -15
  282. package/dist/scripts/syndicate_chat.js +19 -10
  283. package/package.json +6 -3
  284. package/skills/briefs/melchizedek-author.md +3 -2
  285. package/skills/briefs/melchizedek-models.md +13 -9
  286. package/skills/briefs/melchizedek.md +3 -3
  287. package/skills/melchizedek/SKILL.md +2 -1
  288. package/skills/melchizedek-author/SKILL.md +2 -1
  289. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  290. package/skills/melchizedek-models/SKILL.md +23 -6
package/DOCUMENTATION.md CHANGED
@@ -28,8 +28,10 @@ config/agents/examples/ the starter pack — shipped example syndicates
28
28
  lib/loadSyndicate.ts YAML → validated config (+ variable binding)
29
29
  lib/dispatch.ts plan-dispatch route resolution (§6)
30
30
  lib/toolRegistry.ts tool name → live ADK tool instance
31
- lib/models/claudeLlm.ts Claude adapter registered into the ADK registry
31
+ lib/models/claudeAdapter.ts Claude on the engine's model contract
32
+ lib/models/claudeLlm.ts that adapter behind the ADK shim, in the ADK registry
32
33
  lib/models/ollamaLlm.ts open-weight local adapter (Ollama, keyless)
34
+ lib/models/chatCompletionsAdapter.ts the chat-completions wire on the model contract
33
35
  lib/tools/mcpToolFactory.ts MCP client: remote tools → live ADK tools
34
36
  scripts/demo_mcp_server.ts demo MCP server (library catalog, SSE)
35
37
  lib/session/… Supabase-backed session service
@@ -52,6 +54,21 @@ subagents as `AgentTool`s and tool names through the registry → the ADK
52
54
  on session end, the memory service distills the transcript into tagged
53
55
  facts, embeds them (768-d), and stores them for future recall.
54
56
 
57
+ The compiler (`lib/compile.ts`) first builds a runtime-neutral `AgentSpec`
58
+ per agent; `lib/compileAdk.ts` turns it into the `LlmAgent`, and
59
+ `lib/compileNative.ts` into the engine's own loop's agent. ADK runs every
60
+ turn by default. `MELCHIZEDEK_RUNTIME=native`, or `runtime: 'native'` on
61
+ `runSyndicateTurn`, runs a single-agent, delegating or plan-dispatch
62
+ syndicate on the native loop with the same result and the same stored
63
+ events, `context:` compaction and `mode: task` included
64
+ ([ADR 0078](./wiki/decisions/0078-native-compaction-ports-adk-compactor.md)),
65
+ and a `workflow:` syndicate on the engine's own scheduler, its pause and
66
+ resume included
67
+ ([ADR 0095](./wiki/decisions/0095-native-workflow-turn-drains-through-the-adk-reader.md));
68
+ what it does not run yet throws `UnsupportedOnRuntimeError` before any
69
+ model call
70
+ ([ADR 0073](./wiki/decisions/0073-one-agent-spec-and-a-runtime-flag.md)).
71
+
55
72
  ## 2. The syndicate YAML
56
73
 
57
74
  Minimal complete example:
@@ -72,11 +89,9 @@ orchestrator:
72
89
  You are the Conductor… (persona, objective, workflow contract)
73
90
  tools:
74
91
  - "google_search" # names resolved via lib/toolRegistry.ts
92
+ reasoning: medium # none | low | medium | high, or { budget_tokens: 4096 }
75
93
  generateContentConfig:
76
94
  maxOutputTokens: 4096
77
- thinkingConfig:
78
- thinkingLevel: "MEDIUM" # or thinkingBudget on older models
79
- includeThoughts: true
80
95
 
81
96
  subagents:
82
97
  - name: "Researcher"
@@ -104,19 +119,22 @@ Field reference:
104
119
  | `name` / `model` / `instruction` | agent | The agent triple. Any Gemini id, `claude-*`, or `ollama/*` for open-weight local models (see §5). |
105
120
  | `description` | subagent | **The delegation API.** The orchestrator reads this when deciding to hand off — write it like a function signature ("Use this subagent to…, pass it…"). |
106
121
  | `tools` | agent | Names resolved by the tool registry (§3). Long-term memory agents add `preload_memory` / `load_memory`. |
107
- | `generateContentConfig` | agent | Temperature, output caps, thinking budget/level. |
122
+ | `reasoning` | agent | How hard the agent reasons, on any provider: `none`, `low`, `medium`, `high`, or `{ budget_tokens: <int> }`. The compiler sends each provider the field it reads: a thinking level on Gemini 3, a thinking budget on Claude 4.6 and earlier (2,048 / 8,192 / 16,384 tokens for low / medium / high), adaptive thinking with that effort on later Claude models ([ADR 0049](./wiki/decisions/0049-claude-requests-by-model-generation.md)), an effort word on GPT, Grok, Kimi, Ollama and the gateway ([ADR 0047](./wiki/decisions/0047-provider-neutral-reasoning-key.md)). Unset, each adapter keeps its own default. |
123
+ | `generateContentConfig` | agent | Temperature, output caps. Its `thinkingConfig` and `reasoningEffort` are the older, provider-specific spelling of `reasoning`; setting either next to `reasoning` is a load error. |
108
124
  | `outputSchema` | agent | Structured-JSON contract. **Constraint:** an agent holding `outputSchema` cannot also hold transfer powers — the ADK deadlocks it. Keep schema-holders as leaf agents (see `critic.yaml`'s header comment for the war story). |
109
125
  | `yaml_reference` | subagent | Mount another syndicate file as a nested subagent. |
110
126
  | `a2a_agent_url` | subagent | A REMOTE agent over A2A (§6): the orchestrator delegates to it with one `request` argument; in plan-dispatch it can be a route. No `model`/`instruction` — the remote agent has its own. Credentials come from `A2A_AGENT_TOKENS`, never YAML. |
111
127
  | `max_steps` | root | Cap on model calls per turn, counted across every agent the turn reaches (orchestrator, subagents, nested syndicates). Exceeding it fails the turn with `STEP_LIMIT`. |
112
128
  | `includeContents` / `outputKey` / `globalInstruction` / `disallowTransferToParent` / `disallowTransferToPeers` | agent | Passed through to ADK's LlmAgent. `includeContents: none` makes an agent see only the current message. |
129
+ | `fallback_model` | any agent | A model, ideally on another provider, that answers when this agent's model fails provider-side (5xx, 429, a connection reset, after its own retries) before producing any output, or while that provider's circuit is open: after `MODEL_BREAKER_THRESHOLD` consecutive provider failures (default 5; 0 disables) the provider is skipped for `MODEL_BREAKER_COOLDOWN_MS` (default 30 s). A 4xx and a canceled turn are never redirected, and a stream that already produced text is never replayed elsewhere ([ADR 0044](./wiki/decisions/0044-fallback-model-and-circuit-breaker.md)). |
130
+ | `mcp_tools` | subagent | The MCP server's tools this agent may use; any other tool the server lists is not exposed. On a dispatch route `require_approval` may name them ([ADR 0041](./wiki/decisions/0041-tool-vendors-get-least-privilege.md)). |
113
131
  | `mcp_server_url` | subagent | Discover this subagent's tools from a remote MCP server at load time (§3). SSRF-guarded; `ALLOW_PRIVATE_MCP=true` permits localhost for development. |
114
132
  | `openapi` | any agent | HTTP APIs as tools, each from an OpenAPI 3 spec file (§3, OpenAPI tools): `spec`, and optionally `operations` (default: the GET operations only), `auth` (from environment variables), `base_url`, `prefix`. |
115
133
  | `code_execution` | any Gemini agent | `"gemini"`: the model writes Python and Gemini runs it in Google's server-side sandbox, returning the output to the model; nothing runs on this host. For arithmetic, data and checks a model gets wrong in its head. Gemini models only ([ADR 0033](./wiki/decisions/0033-context-task-code.md)). |
116
134
  | `context` | orchestrator | Compacts a long conversation: when the last request's prompt passed `compact_after_tokens`, earlier turns become one summary (written by `summary_model`, default the agent's own) and the last `keep_recent_events` stay verbatim. The full history stays stored; only what the model reads shrinks. The orchestrator of a delegate syndicate only: a dispatch route already reads a bounded projection, a workflow node sees only its input. |
117
135
  | `mode` | workflow node | `"task"`: the agent works with its tools until it calls `finish_task`, whose arguments (matching its `outputSchema`) become the node's output. Workflow nodes only. |
118
- | `examples` | any agent | Few-shot exchanges, `[{ input, output }]` (up to 20), added to every request's instruction by ADK's `ExampleTool`; the model never calls it. Keeps worked examples out of the prose of `instruction`. |
119
- | `skills` | any agent | Agent Skills (a directory of SKILL.md folders) the agent holds the way a coding harness does: every skill's name and description is appended to its instruction at compile time; `load_skill` reads one in full with the names of its files, `load_skill_resource` reads one file. `scripts: local` adds `run_skill_script`, which runs a skill's own scripts on this machine, each after a person approves (the `require_approval` pause); `tools:` names registry tools a skill's `allowed-tools` may unlock once loaded. Worked example: `examples/harness.yaml`; engine: `lib/tools/skillToolset.ts`. |
136
+ | `examples` | any agent | Few-shot exchanges, `[{ input, output }]` (up to 20), added to every request's instruction as a few-shot block, the one ADK's `ExampleTool` wrote (an Instruction tool, `lib/tools/examples.ts`); the model never calls it. Keeps worked examples out of the prose of `instruction`. |
137
+ | `skills` | any agent | Agent Skills (a directory of SKILL.md folders) the agent holds the way a coding harness does: every skill's name and description is appended to its instruction at compile time; `load_skill` reads one in full with the names of its files, `load_skill_resource` reads one file. `scripts: local` adds `run_skill_script`, which runs a skill's own scripts on this machine, each after a person approves (the `require_approval` pause), with PATH, HOME, the temp directory and the locale but none of the server's keys, plus the variable names listed under `env:` (or `secret_env:` for a secret-shaped name, ADR 0086), and stdout and stderr each cut at 20,000 characters; `tools:` names registry tools a skill's `allowed-tools` may unlock once loaded. Worked example: `examples/harness.yaml`; engine: `lib/tools/skillToolset.ts` over `lib/tools/skills/`, the same on both runtimes. |
120
138
 
121
139
  Validation happens at load: missing names, legacy option blocks, and
122
140
  malformed agents fail with pointed errors before any model is called.
@@ -126,28 +144,37 @@ skipped with a warning at compile time.
126
144
  ## 3. Tools
127
145
 
128
146
  Registered in `lib/toolRegistry.ts` — one map from YAML name to ADK tool
129
- instance:
147
+ instance. A **Contract** is the engine's own Tool (`lib/tools/tool.ts`),
148
+ defined once with `defineTool` and handed to the ADK runtime as a
149
+ `FunctionTool` by `toFunctionTool` (`lib/tools/adkTool.ts`). An
150
+ **Instruction tool** is the engine's own too: it declares no function and
151
+ only writes into each request's instruction, reaching the ADK runtime
152
+ through `toAdkInstructionTool`. A **server-side tool** is an own marker
153
+ (`lib/tools/nativeTools.ts`) that names the provider's tool and declares no
154
+ function; the ADK runtime runs its sentinel (`toAdkNativeTool`), and every
155
+ adapter recognises it by marker ([ADR 0062](./wiki/decisions/0062-server-side-tools-as-markers.md)):
130
156
 
131
157
  | Name | Kind | Does |
132
158
  |---|---|---|
133
159
  | `web_search` | Provider-agnostic | Live web search via the agent model's NATIVE search: Gemini grounding, Anthropic `web_search` server tool, OpenAI Responses `web_search`, xAI Agent Tools `web_search`. On local `ollama/*` models, and on `kimi-*` (Moonshot's model-side search retires 2026-10-20; its successor is a REST API, not a request field), the tool is omitted with a one-time warning (keyless stays keyless). On grok-* agents, optional server-side domain filters via `XAI_WEB_SEARCH_ALLOWED_DOMAINS` / `_EXCLUDED_DOMAINS` in `.env` (max 5, mutually exclusive; xAI accepts no date bounds — those are `x_search`-only). Prefer this in new YAMLs. |
134
- | `web_extract` | FunctionTool | Deterministic page reading: fetches 1–5 agent-chosen URLs and returns clean page text (no LLM summarization). Keyless — works on every provider including local `ollama/*`. Per-page char budget (default 15k, `WEB_EXTRACT_CHAR_LIMIT`); long pages return a head+tail window with an `offset` continuation call served from a 15-minute cache. SSRF-guarded (http(s) only, private/link-local hosts refused, redirects re-checked). Block pages (bot checks, paywall stubs, JS shells) are detected code-side and returned as labeled `Error:` blocks, never as content. Pair with `web_search`: search to find, extract to read past the headline. |
160
+ | `web_extract` | Contract | Deterministic page reading: fetches 1–5 agent-chosen URLs and returns clean page text (no LLM summarization). Keyless — works on every provider including local `ollama/*`. Per-page char budget (default 15k, `WEB_EXTRACT_CHAR_LIMIT`); long pages return a head+tail window with an `offset` continuation call served from a 15-minute cache. SSRF-guarded (http(s) only, private/link-local hosts refused, redirects re-checked). Block pages (bot checks, paywall stubs, JS shells) are detected code-side and returned as labeled `Error:` blocks, never as content. Pair with `web_search`: search to find, extract to read past the headline. |
135
161
  | `x_search` | xAI-only | Live search over X (Twitter) posts via xAI Agent Tools. Self-gates to `grok-*` agents; a silent no-op on every other provider, so mixed-provider YAMLs stay safe. Optional server-side constraints in `.env`: `XAI_X_SEARCH_FROM_DATE`/`_TO_DATE` (inclusive `YYYY-MM-DD`) and `_ALLOWED_HANDLES`/`_EXCLUDED_HANDLES` (max 20, mutually exclusive — allowlist wins). |
136
162
  | `collections_search` | xAI-only | Semantic search over xAI **Collections** — hosted document stores (PDFs/text/CSVs) uploaded at console.x.ai — server-side RAG with `collections://…` citations. Which collections: `XAI_COLLECTION_IDS` in `.env` (optional `XAI_COLLECTIONS_MAX_RESULTS`). Declared with no ids → omitted with a warning; non-xAI providers → silent no-op. |
137
163
  | `url_context` | Gemini built-in | Gemini reads the pages at URLs in the conversation, server-side (Google fetches them, not this host). On any other provider it is a no-op the doctor reports as dropped; use `web_extract` there. |
138
164
  | `google_search` | ADK built-in | Live web search — Gemini agents only (legacy alias; use `web_search`). |
139
- | `preload_memory` | ADK built-in | Silently injects similarity-matched facts into every request (ambient recall). |
140
- | `load_memory` | ADK built-in | Explicit tool call to search the fact store (deliberate recall). |
141
- | `generate_image` | FunctionTool | Calls the Gemini image model directly, saves the result under `outputs/`, returns the path. A FunctionTool because binary `inlineData` cannot survive the AgentTool text boundary. |
142
- | `inspect_image` | FunctionTool | **Blind visual inventory** of a file under `outputs/`: subjects with exact counts, composition, light, palette, medium cues, artifacts — zero quality judgments. Its signature accepts *only* a file path, so an orchestrator cannot leak expectations into the observation (see `image_production.yaml`). |
143
- | `task_add` / `task_list` / `task_get` / `task_update` | FunctionTool | A to-do list and job queue. Default: a single-user JSON file (`MELCHIZEDEK_TASKS_FILE`, default `outputs/tasks.json`), so every caller of a shared endpoint shares one list. With `DATABASE_URL` (migration 0009) each caller has its own list, scoped by the caller's scope key, and any number of workers take jobs safely. |
144
- | `ask_user` | LongRunningFunctionTool | Asks the person one question (optionally with `options`) and ends the turn `input-required`; the next message on the conversation is the call's result. Orchestrator or plan-dispatch route only (§6, Questions). |
145
- | `task_queue` | FunctionTool | Queues a background job (a self-contained instruction). The tool only writes the queue; `npm run assistant:worker` (`melchizedek-worker`) claims each job, runs it through one agent compiled from YAML (default: the Assistant's Worker), and writes the result back for `task_get`. `--once` drains and exits, for cron. |
165
+ | `preload_memory` | Instruction tool | Silently injects similarity-matched facts into every request's instruction (ambient recall). The model never calls it. |
166
+ | `load_memory` | Contract | Explicit tool call to search the fact store (deliberate recall). Both memory tools read the caller's own silo only and send the model what ADK's tools of the same names sent ([ADR 0059](./wiki/decisions/0059-memory-on-the-engines-own-interfaces.md)). |
167
+ | `generate_image` | Contract | Calls the Gemini image model directly, saves the result under `outputs/`, returns the path. A function tool because binary `inlineData` cannot survive the AgentTool text boundary. |
168
+ | `inspect_image` | Contract | **Blind visual inventory** of a file under `outputs/`: subjects with exact counts, composition, light, palette, medium cues, artifacts — zero quality judgments. Its signature accepts *only* a file path, so an orchestrator cannot leak expectations into the observation (see `image_production.yaml`). |
169
+ | `task_add` / `task_list` / `task_get` / `task_update` | Contract | A to-do list and job queue. Default: a single-user JSON file (`MELCHIZEDEK_TASKS_FILE`, default `outputs/tasks.json`), so every caller of a shared endpoint shares one list. With `DATABASE_URL` (migration 0009) each caller has its own list, scoped by the caller's scope key, and any number of workers take jobs safely. |
170
+ | `ask_user` | Contract, long-running | Asks the person one question (optionally with `options`) and ends the turn `input-required`; the next message on the conversation is the call's result. Orchestrator or plan-dispatch route only (§6, Questions). |
171
+ | `task_queue` | Contract | Queues a background job (a self-contained instruction). The tool only writes the queue; `npm run assistant:worker` (`melchizedek-worker`) claims each job, runs it through one agent compiled from YAML (default: the Assistant's Worker), and writes the result back for `task_get`. `--once` drains and exits, for cron. |
146
172
 
147
173
  **MCP tools** are the exception to the registry: a subagent with
148
174
  `mcp_server_url:` in its YAML gets its tools from a remote MCP server at
149
175
  load time. `lib/tools/mcpToolFactory.ts` dials the server over SSE,
150
- lists its tools, and wraps each one as a live `FunctionTool` — the
176
+ lists its tools, and makes each one an own Tool (`loadMcpTools`), which
177
+ the ADK runtime runs as a `FunctionTool` (`createMcpTools`) — the
151
178
  agent's reach is decided by the server, not compiled in.
152
179
  `config/agents/examples/librarian.yaml` plus the demo catalog server
153
180
  (`npm run mcp:demo`, `scripts/demo_mcp_server.ts`) are the worked
@@ -158,8 +185,10 @@ treat any remote MCP server as an untrusted tool vendor whose results
158
185
  are data, never instructions.
159
186
 
160
187
  **OpenAPI tools** turn any HTTP API with an OpenAPI 3 spec into an
161
- agent's tools, with no tool code (`lib/tools/openapiTools.ts`, on ADK's
162
- `OpenAPIToolset`; [ADR 0032](./wiki/decisions/0032-openapi-tools.md)):
188
+ agent's tools, with no tool code (`lib/tools/openapiTools.ts`, on the
189
+ engine's own parser and caller in `lib/tools/openapi/`;
190
+ [ADR 0032](./wiki/decisions/0032-openapi-tools.md),
191
+ [ADR 0067](./wiki/decisions/0067-openapi-calls-on-the-engines-own-caller.md)):
163
192
 
164
193
  ```yaml
165
194
  orchestrator:
@@ -180,11 +209,18 @@ writes is exposed only by naming it, and a named operation can be listed
180
209
  under `require_approval` (as written under `operations`) so a person
181
210
  approves each call. `auth` names an environment variable, never a value
182
211
  (`bearer_env`, or `api_key` with `in: header | query` and `name`); an unset
183
- variable fails the compile, and a static token is applied to the request,
212
+ variable fails the compile, and so does one of the framework's own settings
213
+ (the database URL, a provider key, an `A2A_` secret: anything `.env.example`
214
+ documents), since the YAML chooses the host it goes to.
215
+ `OPENAPI_CREDENTIAL_ENVS`, when set, is the exact list of variables an `auth`
216
+ may name. A refused or unset variable fails the compile, and a static token is applied to the request,
184
217
  never stored in session state. Every server must be http(s) and pass the
185
218
  SSRF guard: its literal rules when the agent compiles, the full check with
186
219
  DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` permits private hosts for
187
- local development. A response over 20,000 characters is cut and says so; a
220
+ local development. Redirects are followed one hop at a time, each hop held
221
+ to the guard, and a hop to another origin carries no credential. A
222
+ credential's value never appears in an error the model reads. Only OpenAPI
223
+ 3.x specs are read. A response over 20,000 characters is cut and says so; a
188
224
  network failure comes back to the model as an error. Specs are files, never
189
225
  URLs: save the spec beside the YAML and review it like code (trim it to the
190
226
  operations the agent needs, and write each `summary` for the model). Worked
@@ -223,7 +259,11 @@ npx melchizedek-db status # schema version, hardening, session counts
223
259
 
224
260
  That runs the migrations in [`db/migrations/`](./db/migrations/) and then
225
261
  [`db/hardening.sql`](./db/hardening.sql). Both are idempotent, so re-running
226
- them is also the upgrade path from any earlier layout.
262
+ them is also the upgrade path from any earlier layout. `apply` runs the whole
263
+ install as one transaction under an advisory lock: if any statement fails,
264
+ nothing is changed, and two applies started together run one after the other.
265
+ Rolling back a migration that succeeded is a restore from backup (§6), since
266
+ the migrations only move forward.
227
267
 
228
268
  The tables go in the `public` schema by default. To keep them out of reach of
229
269
  a REST layer (Supabase exposes `public`), install into a private schema and
@@ -301,12 +341,12 @@ accordingly — model optionality is a single YAML line per agent:
301
341
  | Model id | Provider | Adapter | Key | Native `web_search` |
302
342
  |---|---|---|---|---|
303
343
  | `gemini-*` | Google Gemini | ADK-native (`TracedGemini`) | `GOOGLE_GENAI_API_KEY` | ✅ grounding |
304
- | `claude-*` | Anthropic | `lib/models/claudeLlm.ts` | `ANTHROPIC_API_KEY` | ✅ server tool |
305
- | `gpt-*`, o-series | OpenAI | `lib/models/gptLlm.ts` (Responses API) | `OPENAI_API_KEY` | ✅ web_search tool |
306
- | `grok-*` | xAI | `lib/models/grokLlm.ts` (Responses API) | `XAI_API_KEY` | ✅ Agent Tools search |
307
- | `kimi-*` | Moonshot AI (Kimi) | `lib/models/kimiLlm.ts` (chat completions) | `MOONSHOT_API_KEY` | ⚠ omitted + warning |
308
- | `ollama/*` | Local Ollama | `lib/models/ollamaLlm.ts` | none | ⚠ omitted + warning |
309
- | *any cloud id whose direct key is absent* | the id's own provider, via a gateway | `lib/models/gatewayLlm.ts` (chat completions) | `MODEL_GATEWAY` + `MODEL_GATEWAY_API_KEY` | ⚠ omitted + reported |
344
+ | `claude-*` | Anthropic | `lib/models/claudeAdapter.ts` (Messages API; `ClaudeLlm` is its ADK shim) | `ANTHROPIC_API_KEY` | ✅ server tool |
345
+ | `gpt-*`, o-series | OpenAI | `lib/models/gptLlm.ts` around `gptAdapter.ts` (Responses API) | `OPENAI_API_KEY` | ✅ web_search tool |
346
+ | `grok-*` | xAI | `lib/models/grokLlm.ts` around `grokAdapter.ts` (Responses API) | `XAI_API_KEY` | ✅ Agent Tools search |
347
+ | `kimi-*` | Moonshot AI (Kimi) | `lib/models/kimiLlm.ts` around `kimiAdapter.ts` (chat completions) | `MOONSHOT_API_KEY` | ⚠ omitted + warning |
348
+ | `ollama/*` | Local Ollama | `lib/models/ollamaLlm.ts` around `ollamaAdapter.ts` (chat completions) | none | ⚠ omitted + warning |
349
+ | *any cloud id whose direct key is absent* | the id's own provider, via a gateway | `lib/models/gatewayLlm.ts` around `gatewayAdapter.ts` (chat completions) | `MODEL_GATEWAY` + `MODEL_GATEWAY_API_KEY` | ⚠ omitted + reported |
310
350
 
311
351
  The xAI adapter carries the deepest capability surface: `grok-4.5`
312
352
  requests pin `reasoning.effort: "medium"` (`lib/config.ts`), SSE
@@ -316,8 +356,9 @@ structured outputs ride `outputSchema` → `text.format`, and two
316
356
  xAI-only tools — `x_search` and `collections_search` (§3) — turn on
317
357
  live X search and hosted-document RAG. All verified live on grok-4.5.
318
358
 
319
- **Moonshot AI (Kimi).** `kimi-*` ids route to `lib/models/kimiLlm.ts`, a
320
- subclass of the chat-completions base, against `https://api.moonshot.ai/v1`
359
+ **Moonshot AI (Kimi).** `kimi-*` ids route to `lib/models/kimiLlm.ts`, the
360
+ ADK shim around `KimiAdapter` (`lib/models/kimiAdapter.ts`, on the
361
+ chat-completions base), against `https://api.moonshot.ai/v1`
321
362
  (`MOONSHOT_BASE_URL` for a proxy). Get a key at platform.moonshot.ai; the
322
363
  `.cn` console serves mainland China. The family (USD per 1M tokens, October
323
364
  2026): `kimi-k3`, the flagship — 2.8T-parameter open-weight MoE, 1M context,
@@ -338,11 +379,12 @@ document it; K2.6 is unstable on `$ref`/`oneOf`), SSE streaming, images as
338
379
  base64 (Moonshot takes no public image URLs), MCP tools (they are function
339
380
  tools to the model), `reasoning_content` surfaced as THINKING. Reasoning:
340
381
  K3 takes a top-level `reasoning_effort` (`low` | `high` | `max`); the adapter
341
- pins `DEFAULT_KIMI_REASONING_EFFORT` (`high`, `lib/config.ts`) unless
342
- `generateContentConfig.reasoningEffort` says otherwise, and maps `none` to
343
- `low` because K3 cannot stop thinking. K2.x models take a `thinking:
344
- { type }` switch instead: `reasoningEffort: "none"` or `thinkingBudget: 0`
345
- sends `disabled`, and any effort value is dropped from the body. What does
382
+ pins `DEFAULT_KIMI_REASONING_EFFORT` (`high`, `lib/config.ts`) unless the
383
+ agent's `reasoning:` says otherwise (`medium` is sent as `high`, which K3
384
+ has in its place), and maps `none` to `low` because K3 cannot stop
385
+ thinking. K2.x models take a `thinking: { type }` switch instead:
386
+ `reasoning: none` sends `disabled`, and any effort value is dropped from
387
+ the body. What does
346
388
  not: native `web_search` (dropped with a warning; `web_extract` works, and
347
389
  Moonshot's standalone `POST /v1/tools/search`, billed per call, could back
348
390
  a client-side tool), and thinking carried across a tool loop (Moonshot asks
@@ -426,13 +468,64 @@ prompt to every available provider, printing input, thinking (qwen3
426
468
  `<think>` blocks, Claude extended thinking, GPT reasoning summaries,
427
469
  Grok reasoning, Kimi reasoning_content), output, and a per-request token/latency trace; add
428
470
  `-- --search` to watch four native web searches plus the local
429
- omission. Providers without keys are skipped, never fatal.
471
+ omission. Providers without keys are skipped, never fatal. Each agent
472
+ runs as a one-agent syndicate through `runSyndicateTurn`, so
473
+ `MELCHIZEDEK_RUNTIME=native npm run demo:models` runs the same demo on
474
+ the engine's own loop.
430
475
 
431
476
  Reasoning/thinking: scratchpads from every provider are surfaced as
432
- dimmed THINKING output and kept out of session history. On Claude,
433
- `generateContentConfig.thinkingConfig.thinkingBudget` enables
434
- Anthropic extended thinking (thinking + tool use on the same Claude
435
- agent is not supported yet).
477
+ dimmed THINKING output and kept out of session history. On Claude, any
478
+ `reasoning:` other than `none` (or the older
479
+ `generateContentConfig.thinkingConfig.thinkingBudget`) enables Anthropic
480
+ extended thinking, tools included: the signed thinking blocks ride on the
481
+ response's parts as `providerState` and are replayed verbatim within the
482
+ turn's tool loop (ADR 0046). GPT's reasoning ids (o-series, `gpt-5*`) and
483
+ `grok-4.5`, `grok-4.6` and `grok-4.7` do the same with their encrypted
484
+ reasoning items.
485
+ Their requests send `store: false`, so the vendor keeps no copy of the
486
+ response (ADR 0050).
487
+
488
+ The engine's own model contract is `lib/models/contract.ts` (ADR 0048),
489
+ importable as `melchizedek-agents/models/contract`. It defines the message
490
+ format, request, response stream and adapter interface of the native
491
+ runtime (ADR 0045), with no `@google/*` in its import graph, and the
492
+ adapters move onto it in stages. `wiki/models/model-contract.md` gives
493
+ each field's purpose and its mapping onto every provider's wire. While ADK
494
+ runs the turn, an adapter on the contract runs behind `AdkShim`
495
+ (`melchizedek-agents/models/adkShim`), an ADK `BaseLlm` that charges each
496
+ call against `max_steps`, passes the turn's abort signal and opens the
497
+ `llm.request` span, as the ADK-path adapters do (ADR 0053).
498
+ GPT and Grok run on the contract already: `GptLlm` and `GrokLlm` are that
499
+ shim around `GptAdapter` (`melchizedek-agents/models/gptAdapter`) and
500
+ `GrokAdapter` (`melchizedek-agents/models/grokAdapter`), and keep the token
501
+ counts and server-side tool record the ledger has always had for them
502
+ (ADR 0056).
503
+ Gemini has two adapters on the contract:
504
+ `melchizedek-agents/models/geminiAdapter` (`GeminiAdapter`, on
505
+ `@google/genai` with no ADK) and, until that one passes its live parity
506
+ run, `melchizedek-agents/models/adkGeminiAdapter` (`AdkGeminiAdapter`,
507
+ a temporary wrapper that runs the request through `TracedGemini`, now in
508
+ `melchizedek-agents/models/tracedGemini`).
509
+ `resolveAdapter(modelId, { apiKey, keyProvider, endpoint, gemini })` in
510
+ `melchizedek-agents/models/registry` returns any id's contract adapter
511
+ from the same prefix table, gateway rule, BYOK scoping and endpoints as
512
+ `resolveModel`, with no ADK registry (ADR 0060). A Gemini id gets
513
+ `AdkGeminiAdapter` unless `GEMINI_ADAPTER=engine` (or `gemini: 'engine'`)
514
+ asks for `GeminiAdapter`. `resolveAdapterWithFallback` wraps an agent's
515
+ model and `fallback_model` in a `FallbackAdapter`.
516
+
517
+ `melchizedek-agents/model` is the model layer on its own, with no
518
+ `@google/adk` in its import graph (ADR 0068): the contract's types,
519
+ `ClaudeAdapter`, `GptAdapter`, `GrokAdapter`, `KimiAdapter`,
520
+ `OllamaAdapter`, `GatewayAdapter`, `GeminiAdapter`, the
521
+ `ChatCompletionsAdapter` base, `resolveAdapter`,
522
+ `resolveAdapterWithFallback`, `FallbackAdapter` and the circuit breaker's
523
+ helpers. A project that only calls models installs the package without ADK
524
+ and imports from there. Its `resolveAdapter` gives a Gemini id
525
+ `GeminiAdapter`; asking it for `adk` (`gemini: 'adk'` or
526
+ `GEMINI_ADAPTER=adk`) throws and names `melchizedek-agents/models/registry`,
527
+ whose `resolveAdapter` keeps the ADK default above. `AdkGeminiAdapter`, the
528
+ ADK shims, `TracedGemini` and `resolveModel` are not in it.
436
529
 
437
530
  Every model request also emits an `llm.request` OpenTelemetry span
438
531
  (provider, model, input/output/thinking tokens, latency). Scripts print it
@@ -451,7 +544,14 @@ full-text `search` column; `adk_telemetry` — one row per `llm.request` /
451
544
  root span; and `adk_payloads` — full prompts and responses per model call,
452
545
  kept by policy (`TELEMETRY_PAYLOADS=off|errors|sample|all`,
453
546
  `TELEMETRY_PAYLOAD_SAMPLE`, `TELEMETRY_PAYLOAD_TTL_DAYS`) and expired by
454
- `melchizedek_prune_telemetry()`. The view `adk_turns_production` excludes
547
+ `melchizedek_prune_telemetry()`. A row from a failed call's `llm.request`
548
+ span holds the request in the model contract's shape (`model`, `system`,
549
+ `messages`, `tools`, …), whichever adapter made the call; a row from ADK's
550
+ own `call_llm` span holds ADK's request. On the native runtime the loop
551
+ opens `agent.invoke <name>`, `model.call` and `tool.execute <name>` spans
552
+ where ADK opens `invoke_agent`, `call_llm` and `execute_tool`, and the ledger
553
+ reads both (ADR 0076); a clean call's row then comes from `model.call` and
554
+ holds the request and the adapter's response in the contract's shapes. The view `adk_turns_production` excludes
455
555
  eval and classifier turns. Operate it with `npm run telemetry:stats`,
456
556
  `telemetry:prune` and `telemetry:replay` (the exporter spools failed
457
557
  batches to `outputs/telemetry-deadletter.ndjson`).
@@ -499,7 +599,7 @@ options. `demo/a2a_demo.mjs` is a complete client.
499
599
  | `POST /a2a/jsonrpc`, `/a2a/rest` | bearer | the default syndicate |
500
600
  | `GET /<agentId>/.well-known/agent-card.json` | bearer | another syndicate's card; its URLs point at `/<agentId>/a2a/...` |
501
601
  | `POST /<agentId>/a2a/jsonrpc`, `/<agentId>/a2a/rest` | bearer | another syndicate (`A2A_SERVED_AGENTS` restricts which) |
502
- | `DELETE /memory` | bearer | erase everything stored for the calling scope: facts, sessions (with subagent rows), ledger rows and A2A tasks, including conversations whose sessions have expired, with per-store counts; `?all=1` covers every memory namespace |
602
+ | `DELETE /memory` | bearer | erase everything stored for the calling scope: facts, sessions (with subagent rows), ledger rows, A2A tasks and the third-party tokens held for its tools, including conversations whose sessions have expired, with per-store counts; `?all=1` covers every memory namespace |
503
603
 
504
604
  "bearer" means the credential `A2A_AUTH` asks for (below); with no
505
605
  `A2A_SERVER_SECRET` and no authenticator the server binds `127.0.0.1` only.
@@ -545,9 +645,30 @@ or pass your own `resolveRequest`.
545
645
  The counts live in the `melchizedek_usage` table (`db/migrations/0004_usage.sql`)
546
646
  when Postgres or Supabase is configured, else in process memory. A store
547
647
  that cannot be read refuses the task.
548
- - **One record per task** (option `onTaskEnd`): agent, caller, a hash of the
549
- scope, status, reason, duration, model calls and tokens.
648
+ - **One record per task** (option `onTaskEnd`): task id, trace id, agent,
649
+ caller, a hash of the scope, status, reason, duration, model calls and
650
+ tokens. The trace id is the one the turn's spans and its ledger row carry,
651
+ and the audit trail's `task.end` row names both ids, so one id joins the
652
+ log, the ledger, the audit trail and your tracing backend. A request
653
+ carrying a W3C `traceparent` header is linked, not joined: the turn's root
654
+ span links to the caller's span and records `caller.trace_id`, and the
655
+ record carries `callerTraceId`. The turn keeps a trace id of its own, since
656
+ ledger attribution and erasure key on it and a caller must not choose it.
550
657
  `A2A_LOG_FORMAT=json` prints every server line as JSON, this record included.
658
+ - **The audit trail** (`melchizedek_audit`, `db/migrations/0012_audit_log.sql`;
659
+ option `audit`, supplied by `postgresStorage`): one row per failed
660
+ authentication (`auth.failure`), task outcome (`task.end`), erasure
661
+ (`memory.erase`) and tool credential stored, refreshed, revoked or erased
662
+ (`credential.put`, `.refresh`, `.revoke`, `.erase`, naming the provider and
663
+ app, never a token), with the caller's name, the source address, the agent and
664
+ task ids, and a hash of the scope. Never a scope key and never conversation
665
+ content. A trigger refuses UPDATE and DELETE; rows leave only through
666
+ `SELECT melchizedek_prune_audit(<days>)`, which you schedule (pg_cron, or a
667
+ job) for the retention your evidence needs, since the source address is
668
+ personal data. A failed write is logged once and each event is printed to
669
+ stderr as JSON until writes recover. A database owner can still alter the
670
+ table; ship the rows to a store you do not administer when the evidence must
671
+ survive that.
551
672
  - **Rate limit**: with an authenticator it counts per caller (an operator's
552
673
  backend) or per scope (an end user); under the shared secret, per IP.
553
674
  - **The telemetry ledger is redacted before it is written**:
@@ -556,6 +677,31 @@ or pass your own `resolveRequest`.
556
677
  `setTelemetryRedactor(fn)`. Sessions and memory are not redacted; use
557
678
  `DELETE /memory` there.
558
679
 
680
+ #### Where your data goes
681
+
682
+ What a deployment sends where, how long it is kept, and the setting that
683
+ changes it. "Your Postgres" is the database `DATABASE_URL` (or Supabase)
684
+ names; nothing is kept by the framework anywhere else.
685
+
686
+ | Data | Goes to | Kept | Change it with |
687
+ |---|---|---|---|
688
+ | The prompt: the user's message, the conversation, tool results | The model provider of each agent that runs (the model-id prefix) | The provider's policy | The `model:` lines in the YAML; `ollama/` ids stay on your machine |
689
+ | Tool calls (`web_search`, `web_extract`, OpenAPI, MCP) | The tool's own host | That host's policy | The agent's `tools:`, `openapi:`, `mcp_server_url` / `mcp_tools` |
690
+ | Sessions (conversation history) | Your Postgres, `adk_sessions` | 7 days after the last message | `ttlDays` (`postgresStorage`) |
691
+ | Long-term memory facts | Your Postgres, `adk_memory_facts`; **the transcript is also sent to the extraction and embedding providers, Gemini by default whatever the agents run on** (the server warns at boot when they differ) | Until erased, or `memory_retention_days` | `MEMORY_EXTRACTION_MODEL`, `MEMORY_EMBEDDING_PROVIDER`, `memory_retention_days` |
692
+ | The ledger: each turn's input, output and tool results (key-shaped secrets redacted) | Your Postgres, `adk_turns`, `adk_telemetry` | **Until you prune it**: schedule `melchizedek_prune_telemetry(<days>)` | `TELEMETRY_REDACT`, the prune's `turn_days` |
693
+ | Sampled full model requests | Your Postgres, `adk_payloads` | 30 days | `TELEMETRY_PAYLOADS`, `TELEMETRY_PAYLOAD_TTL_DAYS` |
694
+ | Traces, when `OTEL_EXPORTER_OTLP_ENDPOINT` is set | Your OTLP collector, conversation content included with key-shaped secrets redacted (`off` drops it) | Your collector's policy | `OTEL_EXPORT_CONTENT` (`redacted`, `off`, `raw`) |
695
+ | Third-party OAuth tokens held for tools, per end user (when `postgresStorage({ credentials })` is used) | Your Postgres, `melchizedek_tool_credentials`, sealed with AES-256-GCM under `MELCHIZEDEK_CREDENTIAL_KEY`, which never reaches the database (migration 0013) | Until revoked or erased | The provider config; the key |
696
+ | The audit trail (no content) | Your Postgres, `melchizedek_audit` | Until `melchizedek_prune_audit(<days>)` | The prune schedule |
697
+ | The task record (no content) | stdout | Your log platform's policy | `A2A_LOG_FORMAT` |
698
+
699
+ `DELETE /memory` erases a user from every Postgres store above except the
700
+ audit trail, which holds no content, only a hash of the scope. It does not
701
+ reach a provider's own retention: read each provider's data terms, and the
702
+ business associate or data processing agreement you need with it, before
703
+ sending it regulated data.
704
+
559
705
  #### Who pays
560
706
 
561
707
  `A2A_KEY_MODE` (option `keyMode`), whatever the authenticator:
@@ -679,6 +825,59 @@ subagent or a workflow node listing it is a load error. In code,
679
825
  `runSyndicateTurn` returns `status: 'input-required'` with `input`
680
826
  (`node`, `message`, `payload`), and the next message's text answers it.
681
827
 
828
+ #### OAuth consent for tools
829
+
830
+ A tool that acts for the person against a third-party API reads their
831
+ delegated token with `ctx.accessToken(provider)` from the sealed credential
832
+ store ([ADR 0072](./wiki/decisions/0072-tool-credentials-sealed-per-user.md)).
833
+ When they have not granted the provider yet, the turn pauses for their
834
+ consent ([ADR 0085](./wiki/decisions/0085-oauth-consent-pauses-on-adks-credential-request.md)).
835
+ This runs on the native runtime:
836
+
837
+ ```ts
838
+ import { createA2AApp } from 'melchizedek-agents/a2a';
839
+ import { oauthConsent } from 'melchizedek-agents/tools/oauthConsent';
840
+
841
+ const consent = oauthConsent({
842
+ providers: {
843
+ github: {
844
+ authorizationUrl: 'https://github.com/login/oauth/authorize',
845
+ tokenUrl: 'https://github.com/login/oauth/access_token',
846
+ clientId: process.env.GITHUB_CLIENT_ID!,
847
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
848
+ scopes: ['repo'],
849
+ },
850
+ },
851
+ redirectUri: 'https://agents.example.com/oauth/callback', // as registered at the provider
852
+ credentials: store, // storage.credentials, or credentialStore(...)
853
+ });
854
+ await createA2AApp({ /* … */ toolCredentials: { store, consent } });
855
+ ```
856
+
857
+ 1. **The pause.** The task ends `input-required` with the link in its status
858
+ message and a data part `{ type: 'consent_request', consent_id, agent,
859
+ provider, authorization_url, state, scopes }`.
860
+ 2. **The grant.** The person opens the link and consents. The provider
861
+ redirects their browser to the callback, which the server mounts at the
862
+ redirect URI's path. The callback exchanges the code with PKCE and stores
863
+ the grant, sealed.
864
+ 3. **The resume.** Their next message on the conversation runs the paused
865
+ call again. A message sent before the grant gets the same request back,
866
+ without a model call.
867
+
868
+ The callback refuses these, and stores nothing:
869
+
870
+ - a state that is replayed, tampered with or older than ten minutes;
871
+ - a provider's `error`;
872
+ - a caller who is authenticated as another user.
873
+
874
+ No token, code, client secret or verifier is written to a page, an event, a
875
+ log line or the model's context. The pending flows live in the process: run
876
+ one replica, or route the callback to the instance that paused the call. In
877
+ code, `runSyndicateTurn({ …, toolCredentials })` returns
878
+ `status: 'input-required'` with `consent`. On the ADK runtime an open consent
879
+ request throws `UnsupportedOnRuntimeError`.
880
+
682
881
  #### Limits
683
882
 
684
883
  | Setting | Default |
@@ -687,9 +886,13 @@ subagent or a workflow node listing it is a load error. In code,
687
886
  | `A2A_AUTH_FAILURE_MAX` failed logins per IP per 15 min, then blocked | 30 |
688
887
  | `A2A_TASK_TIMEOUT_MS` per task | 15 min |
689
888
  | `A2A_MAX_CONCURRENT_TASKS` | unlimited |
690
- | `max_steps` (YAML): model calls per turn, subagents included | none |
889
+ | `A2A_MAX_CONCURRENT_PER_SCOPE`: tasks at once for one end user | 4 |
890
+ | `A2A_MAX_CONCURRENT_PER_CALLER`: tasks at once for one caller | unlimited |
891
+ | `max_steps` (YAML): model calls per turn, subagents included | 50 |
892
+ | `A2A_TRUST_PROXY`: proxies in front (required with `PUBLIC_URL`) | none trusted |
691
893
  | `A2A_BODY_LIMIT` | 1 MB |
692
894
  | `A2A_SHUTDOWN_GRACE_MS`: SIGTERM waits for running tasks | 25 s |
895
+ | `A2A_SHUTDOWN_DELAY_MS`: of that, `/readyz` fails while requests are still served | 5 s |
693
896
 
694
897
  The rate limit and the failed-login limit count in the process by default,
695
898
  so each replica keeps its own window. With `A2A_REDIS_URL` set (and the
@@ -719,7 +922,11 @@ Without `A2A_SERVER_SECRET` the server binds `127.0.0.1` only; binding
719
922
  another `HOST` requires the secret or `ALLOW_UNAUTHENTICATED=true`. With
720
923
  `PUBLIC_URL` set it refuses to start without the secret, with the
721
924
  `.env.example` placeholder as the secret, or against an unhardened
722
- database (unless `ALLOW_UNHARDENED_DB=true`). With durable storage it reads
925
+ database (unless `ALLOW_UNHARDENED_DB=true`), and until the deployment
926
+ states its posture: `A2A_AUTH` (prefer `callers` or `jwt`; `secret` lets any
927
+ holder of the secret act as any user), `A2A_SERVED_AGENTS` (a list, or `*`
928
+ for every agent) and `A2A_TRUST_PROXY` (the number of proxies in front, or
929
+ `false`). The refusal names each one missing. With durable storage it reads
723
930
  `melchizedek_schema_version` and refuses to start on a database behind the
724
931
  migrations it ships, naming `melchizedek-db apply` (or
725
932
  `ALLOW_SCHEMA_MISMATCH=true` to start anyway); a database ahead of it only
@@ -745,9 +952,14 @@ local conversation keeps talking to the same remote conversation.
745
952
  `Dockerfile` builds the compiled server and runs it as a non-root user with
746
953
  a health check; `compose.yaml` adds optional Ollama and Phoenix (traces).
747
954
  `npx melchizedek-db print|apply|status` installs and checks the database.
748
- Set `PUBLIC_URL`, `A2A_SERVER_SECRET` and the provider keys from your
749
- secret manager; give the orchestrator's stop timeout at least
750
- `A2A_SHUTDOWN_GRACE_MS`.
955
+ Set `PUBLIC_URL`, `A2A_SERVER_SECRET`, `A2A_AUTH`, `A2A_SERVED_AGENTS`,
956
+ `A2A_TRUST_PROXY` and the provider keys from your secret manager; give the orchestrator's stop timeout at least
957
+ `A2A_SHUTDOWN_GRACE_MS`. On SIGTERM the server first fails `/readyz` and
958
+ keeps serving for `A2A_SHUTDOWN_DELAY_MS` so the load balancer deregisters
959
+ it, then closes the listener and drains running tasks for the rest of the
960
+ grace period; point the readiness probe at `/readyz`, which also fails while
961
+ durable storage does not answer. `MELCHIZEDEK_DOTENV=off` makes every bin
962
+ ignore `.env` files and run on its environment alone.
751
963
 
752
964
  #### Backups
753
965
 
@@ -1016,18 +1228,25 @@ the deadline cap the whole graph as they cap any turn.
1016
1228
  and remote `a2a_agent_url` subagents are refused inside a workflow by
1017
1229
  the schema, and a workflow cannot be another syndicate's `yaml_reference`
1018
1230
  (ADK cannot yet make a `Workflow` a subagent; nested, only its
1019
- orchestrator runs). The record is [ADR 0030](./wiki/decisions/0030-workflow-graphs.md);
1020
- the contract is `lib/workflow.ts`, on ADK's `Workflow`.
1231
+ orchestrator runs). An `ask_user` tool on a node agent is refused too:
1232
+ use an `ask_user` node. The record is [ADR 0030](./wiki/decisions/0030-workflow-graphs.md);
1233
+ the contract is `lib/workflow.ts`, on ADK's `Workflow`, and on the native
1234
+ runtime `lib/workflow/turn.ts`, on the engine's own scheduler
1235
+ ([ADR 0095](./wiki/decisions/0095-native-workflow-turn-drains-through-the-adk-reader.md)).
1236
+ On native, a conversation paused inside an agent node or a map item
1237
+ (which only ADK resumes) fails the next turn with `RESUME_UNSUPPORTED`.
1021
1238
 
1022
1239
  ## 7. Extending the framework
1023
1240
 
1024
- **Call ADK directly (no syndicate)**: the YAML layer is a convenience,
1241
+ **Call a model directly (no syndicate)**: the YAML layer is a convenience,
1025
1242
  never a requirement. `scripts/direct_call.ts` (`npm run demo:direct`) is
1026
- the canonical minimal block — `LlmAgent` + `Runner` +
1027
- `InMemorySessionService` straight from `@google/adk`, ~30 lines you can
1028
- copy into any repo that has `@google/adk` installed. Add
1029
- `registerAvailableProviders()` from `lib/models/registry.ts` and the
1030
- same block runs `claude-*` / `gpt-*` / `grok-*` / `kimi-*` / `ollama/*` ids too.
1243
+ the canonical minimal block on the engine's model contract:
1244
+ `resolveAdapter(modelId)` from `melchizedek-agents/model`, then
1245
+ `adapter.generate({ model, system, messages, stream })`, reading partial
1246
+ and final responses. The id's prefix picks the provider (`gemini-*`,
1247
+ `claude-*`, `gpt-*`, `grok-*`, `kimi-*`, `ollama/*`), the key comes from
1248
+ the environment, and nothing in that entry loads `@google/adk`. A turn
1249
+ with tools, sessions and telemetry is `runSyndicateTurn`.
1031
1250
 
1032
1251
  **Add a syndicate**: create `config/agents/<name>.yaml` — copy the
1033
1252
  closest starter-pack file from `config/agents/examples/` or start from
@@ -1035,15 +1254,20 @@ closest starter-pack file from `config/agents/examples/` or start from
1035
1254
  <name>`. No code changes. The loader checks the root first, then
1036
1255
  `examples/`, so your syndicate and the starter pack never collide.
1037
1256
 
1038
- **Add a tool**: implement a `FunctionTool` in `lib/tools/`, register the
1039
- name in `lib/toolRegistry.ts`, reference it from YAML. The two image
1040
- tools are the worked examples — including why binary data forces
1041
- FunctionTools over subagents, and how a tool signature can enforce an
1042
- epistemic rule (the blind inventory).
1043
-
1044
- **Add a provider**: follow `claudeLlm.ts` (SDK-based, key-gated) or
1045
- `ollamaLlm.ts` (fetch-based, keyless) — implement the ADK LLM
1046
- interface, register it behind a model-id prefix.
1257
+ **Add a tool**: write a `defineTool` contract in `lib/tools/` (name,
1258
+ description, zod schema, execute), register the name in
1259
+ `lib/toolRegistry.ts` (or call `registerTool` from your own code),
1260
+ reference it from YAML. The contract runs on either runtime; see
1261
+ `wiki/tools/tool-contracts.md`. The two image tools are the worked
1262
+ examples — including why binary data forces function tools over
1263
+ subagents, and how a tool signature can enforce an epistemic rule (the
1264
+ blind inventory).
1265
+
1266
+ **Add a provider**: follow `claudeAdapter.ts` (SDK-based, key-gated, a
1267
+ `ModelAdapter` on the engine's model contract, run under ADK by its shim
1268
+ `claudeLlm.ts`) or, for a chat-completions API, `ollamaAdapter.ts`
1269
+ (fetch-based, keyless: a `ChatCompletionsAdapter` subclass, run under ADK
1270
+ by its shim `ollamaLlm.ts`), and register it behind a model-id prefix.
1047
1271
 
1048
1272
  **Point an agent at an MCP server**: set `mcp_server_url:` on a
1049
1273
  subagent. `scripts/demo_mcp_server.ts` is a complete server to copy —
package/README.md CHANGED
@@ -101,7 +101,7 @@ const result = await runSyndicateTurn({
101
101
  console.log(result.status, result.text);
102
102
  ```
103
103
 
104
- `runSyndicateTurn` is the same runtime the server, CLI, and eval harness use. To read syndicates from somewhere else, pass `loadSyndicate(file, { agentsDir })` or set `MELCHIZEDEK_AGENTS_DIR`. To serve inside your own Express app, mount `(await createA2AApp({ defaultSyndicate: 'mine.yaml', serverSecret })).app`.
104
+ `runSyndicateTurn` is the same runtime the server, CLI, and eval harness use. It runs on Google ADK by default; `runtime: 'native'` (or `MELCHIZEDEK_RUNTIME=native`) runs a single-agent, delegating, plan-dispatch or workflow syndicate on the engine's own loop and scheduler instead. To read syndicates from somewhere else, pass `loadSyndicate(file, { agentsDir })` or set `MELCHIZEDEK_AGENTS_DIR`. To serve inside your own Express app, mount `(await createA2AApp({ defaultSyndicate: 'mine.yaml', serverSecret })).app`.
105
105
 
106
106
  The example syndicates ship inside the package at `node_modules/melchizedek-agents/config/agents/examples/`; copy one out as a starting point.
107
107
 
@@ -116,6 +116,44 @@ registerTool('myCustomTool', defineTool({ /* ... */ }));
116
116
  registerGuard(myGuardInstance);
117
117
  ```
118
118
 
119
+ ### The Model Adapters Alone
120
+
121
+ `melchizedek-agents/model` is the engine's model layer on its own: one message format for every provider, an adapter per provider, `resolveAdapter` over the same model-id prefixes the YAML uses, and `FallbackAdapter`. It loads no `@google/adk`, so a project that only calls models can leave ADK out. npm 7 and later install peer dependencies by default; skip them with:
122
+
123
+ ```bash
124
+ npm install melchizedek-agents --legacy-peer-deps
125
+ ```
126
+
127
+ ```typescript
128
+ import { ClaudeAdapter, OllamaAdapter, resolveAdapter } from 'melchizedek-agents/model';
129
+ import type { ModelAdapter, ModelRequest } from 'melchizedek-agents/model';
130
+
131
+ async function ask(adapter: ModelAdapter, text: string): Promise<string> {
132
+ const request: ModelRequest = {
133
+ model: adapter.model,
134
+ system: 'Answer in one sentence.',
135
+ messages: [{ role: 'user', parts: [{ type: 'text', text }] }],
136
+ };
137
+ for await (const response of adapter.generate(request)) {
138
+ if (response.partial) continue; // deltas while the model writes
139
+ if (response.error) throw new Error(response.error.message);
140
+ return response.parts.map((p) => (p.type === 'text' ? p.text : '')).join('');
141
+ }
142
+ return '';
143
+ }
144
+
145
+ // Claude: the key comes from ANTHROPIC_API_KEY in the environment.
146
+ console.log(await ask(new ClaudeAdapter({ model: 'claude-sonnet-4-6' }), 'What is A2A?'));
147
+
148
+ // Ollama: a local model, no key (OLLAMA_BASE_URL, else http://localhost:11434/v1).
149
+ console.log(await ask(new OllamaAdapter({ model: 'ollama/qwen3:8b' }), 'What is A2A?'));
150
+
151
+ // Or by id, with the same routing as a syndicate's `model:` field.
152
+ const adapter = resolveAdapter('ollama/qwen3:8b');
153
+ ```
154
+
155
+ A Gemini id resolves to the engine's `GeminiAdapter` here. The ADK-backed Gemini adapter, the ADK shims and `resolveModel` stay under `melchizedek-agents/models/*`, which need ADK.
156
+
119
157
  ### CLI & Server Utilities
120
158
 
121
159
  - `npx melchizedek-chat --syndicate <name>`: Interactive CLI REPL for any syndicate.
@@ -164,6 +202,9 @@ See `config/agents/syndicateSchema.yaml` for the annotated schema reference.
164
202
  - [`DOCUMENTATION.md`](./DOCUMENTATION.md) — Full reference: architecture, syndicate YAML, tools, memory, providers, A2A, extending, security.
165
203
  - [`AGENT_SETUP.md`](./AGENT_SETUP.md) — A paste-ready prompt that walks your coding agent (Claude Code, Cursor, Codex) through setup.
166
204
  - [`skills/README.md`](./skills/README.md) — The bundled `SKILL.md` suite.
205
+ - [`VERSIONING.md`](./VERSIONING.md) — What an upgrade may break, deprecation, and which versions get fixes.
206
+ - [`SUPPORT.md`](./SUPPORT.md) — Where to ask, and what to expect: a single-maintainer project with no SLA.
207
+ - [`SECURITY.md`](./SECURITY.md) and [`CONTRIBUTING.md`](./CONTRIBUTING.md).
167
208
 
168
209
  ## License
169
210