melchizedek-agents 0.18.0 → 0.20.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 (293) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +1001 -0
  3. package/DOCUMENTATION.md +252 -65
  4. package/QUICKSTART.md +9 -6
  5. package/README.md +42 -4
  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 +53 -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 +7 -1
  19. package/db/migrations/0013_tool_credentials.sql +260 -0
  20. package/dist/lib/a2a/app.d.ts +70 -6
  21. package/dist/lib/a2a/app.js +107 -2
  22. package/dist/lib/a2a/executor.d.ts +20 -3
  23. package/dist/lib/a2a/executor.js +66 -7
  24. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  25. package/dist/lib/a2a/remoteAgent.js +31 -14
  26. package/dist/lib/adkPeer.d.ts +63 -0
  27. package/dist/lib/adkPeer.js +240 -0
  28. package/dist/lib/compile.d.ts +159 -30
  29. package/dist/lib/compile.js +215 -134
  30. package/dist/lib/compileAdk.d.ts +32 -0
  31. package/dist/lib/compileAdk.js +142 -0
  32. package/dist/lib/compileNative.d.ts +111 -0
  33. package/dist/lib/compileNative.js +278 -0
  34. package/dist/lib/config.d.ts +6 -5
  35. package/dist/lib/config.js +6 -5
  36. package/dist/lib/doctor.d.ts +28 -1
  37. package/dist/lib/doctor.js +64 -2
  38. package/dist/lib/index.d.ts +6 -1
  39. package/dist/lib/index.js +7 -1
  40. package/dist/lib/loadSyndicate.d.ts +18 -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.js +2 -1
  46. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  47. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  48. package/dist/lib/model.d.ts +47 -0
  49. package/dist/lib/model.js +37 -0
  50. package/dist/lib/models/adapterResolver.d.ts +115 -0
  51. package/dist/lib/models/adapterResolver.js +137 -0
  52. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  53. package/dist/lib/models/adkGeminiAdapter.js +369 -0
  54. package/dist/lib/models/adkShim.d.ts +111 -0
  55. package/dist/lib/models/adkShim.js +136 -0
  56. package/dist/lib/models/capabilities.d.ts +3 -0
  57. package/dist/lib/models/capabilities.js +33 -23
  58. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  59. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  60. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  61. package/dist/lib/models/circuitBreaker.js +70 -0
  62. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  63. package/dist/lib/models/claudeAdapter.js +669 -0
  64. package/dist/lib/models/claudeLlm.d.ts +39 -62
  65. package/dist/lib/models/claudeLlm.js +45 -350
  66. package/dist/lib/models/claudeModels.d.ts +127 -0
  67. package/dist/lib/models/claudeModels.js +237 -0
  68. package/dist/lib/models/contract.d.ts +395 -0
  69. package/dist/lib/models/contract.js +39 -0
  70. package/dist/lib/models/endpoints.js +2 -1
  71. package/dist/lib/models/errorResponse.d.ts +50 -0
  72. package/dist/lib/models/errorResponse.js +48 -0
  73. package/dist/lib/models/fallback.d.ts +18 -11
  74. package/dist/lib/models/fallback.js +54 -43
  75. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  76. package/dist/lib/models/fallbackAdapter.js +62 -0
  77. package/dist/lib/models/gateway.js +2 -1
  78. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  79. package/dist/lib/models/gatewayAdapter.js +81 -0
  80. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  81. package/dist/lib/models/gatewayLlm.js +16 -77
  82. package/dist/lib/models/geminiAdapter.d.ts +125 -0
  83. package/dist/lib/models/geminiAdapter.js +854 -0
  84. package/dist/lib/models/geminiState.d.ts +46 -0
  85. package/dist/lib/models/geminiState.js +51 -0
  86. package/dist/lib/models/genaiMapping.d.ts +283 -0
  87. package/dist/lib/models/genaiMapping.js +965 -0
  88. package/dist/lib/models/gptAdapter.d.ts +240 -0
  89. package/dist/lib/models/gptAdapter.js +804 -0
  90. package/dist/lib/models/gptLlm.d.ts +63 -123
  91. package/dist/lib/models/gptLlm.js +89 -603
  92. package/dist/lib/models/grokAdapter.d.ts +86 -0
  93. package/dist/lib/models/grokAdapter.js +142 -0
  94. package/dist/lib/models/grokLlm.d.ts +22 -45
  95. package/dist/lib/models/grokLlm.js +24 -69
  96. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  97. package/dist/lib/models/kimiAdapter.js +203 -0
  98. package/dist/lib/models/kimiLlm.d.ts +15 -107
  99. package/dist/lib/models/kimiLlm.js +19 -155
  100. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  101. package/dist/lib/models/ollamaAdapter.js +155 -0
  102. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  103. package/dist/lib/models/ollamaLlm.js +15 -153
  104. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  105. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  106. package/dist/lib/models/providerState.d.ts +63 -0
  107. package/dist/lib/models/providerState.js +61 -0
  108. package/dist/lib/models/reasoning.d.ts +26 -0
  109. package/dist/lib/models/reasoning.js +59 -0
  110. package/dist/lib/models/registry.d.ts +62 -35
  111. package/dist/lib/models/registry.js +100 -124
  112. package/dist/lib/models/retry.d.ts +2 -1
  113. package/dist/lib/models/retry.js +3 -2
  114. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  115. package/dist/lib/models/schemaNormalize.js +321 -1
  116. package/dist/lib/models/tracedGemini.d.ts +41 -0
  117. package/dist/lib/models/tracedGemini.js +98 -0
  118. package/dist/lib/models/urls.d.ts +11 -0
  119. package/dist/lib/models/urls.js +16 -0
  120. package/dist/lib/net/redirects.d.ts +21 -2
  121. package/dist/lib/net/redirects.js +11 -34
  122. package/dist/lib/observability/audit.d.ts +5 -1
  123. package/dist/lib/observability/lineage.d.ts +14 -0
  124. package/dist/lib/observability/lineage.js +33 -0
  125. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  126. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  127. package/dist/lib/observability/tracer.d.ts +26 -8
  128. package/dist/lib/observability/tracer.js +43 -18
  129. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  130. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  131. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  132. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  133. package/dist/lib/runtime/approvals.d.ts +5 -0
  134. package/dist/lib/runtime/approvals.js +39 -8
  135. package/dist/lib/runtime/credentials.d.ts +61 -0
  136. package/dist/lib/runtime/credentials.js +67 -0
  137. package/dist/lib/runtime/events.d.ts +229 -0
  138. package/dist/lib/runtime/events.js +274 -0
  139. package/dist/lib/runtime/logging.d.ts +32 -0
  140. package/dist/lib/runtime/logging.js +55 -0
  141. package/dist/lib/runtime/memoryService.d.ts +72 -0
  142. package/dist/lib/runtime/memoryService.js +25 -0
  143. package/dist/lib/runtime/native/agentLoop.d.ts +209 -0
  144. package/dist/lib/runtime/native/agentLoop.js +705 -0
  145. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  146. package/dist/lib/runtime/native/compaction.js +191 -0
  147. package/dist/lib/runtime/native/delegate.d.ts +151 -0
  148. package/dist/lib/runtime/native/delegate.js +256 -0
  149. package/dist/lib/runtime/native/history.d.ts +83 -0
  150. package/dist/lib/runtime/native/history.js +312 -0
  151. package/dist/lib/runtime/native/interrupts.d.ts +129 -0
  152. package/dist/lib/runtime/native/interrupts.js +310 -0
  153. package/dist/lib/runtime/native/request.d.ts +215 -0
  154. package/dist/lib/runtime/native/request.js +585 -0
  155. package/dist/lib/runtime/native/selfCorrection.d.ts +164 -0
  156. package/dist/lib/runtime/native/selfCorrection.js +327 -0
  157. package/dist/lib/runtime/native/step.d.ts +156 -0
  158. package/dist/lib/runtime/native/step.js +263 -0
  159. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  160. package/dist/lib/runtime/native/taskMode.js +126 -0
  161. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  162. package/dist/lib/runtime/native/telemetry.js +299 -0
  163. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  164. package/dist/lib/runtime/native/tempState.js +56 -0
  165. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  166. package/dist/lib/runtime/nativeTurn.js +107 -0
  167. package/dist/lib/runtime/questions.d.ts +29 -7
  168. package/dist/lib/runtime/questions.js +47 -32
  169. package/dist/lib/runtime/runtimeFlag.d.ts +38 -0
  170. package/dist/lib/runtime/runtimeFlag.js +58 -0
  171. package/dist/lib/runtime/sessions.d.ts +166 -0
  172. package/dist/lib/runtime/sessions.js +201 -0
  173. package/dist/lib/runtime/syndicateTurn.d.ts +46 -5
  174. package/dist/lib/runtime/syndicateTurn.js +233 -36
  175. package/dist/lib/runtime/valueDepth.d.ts +23 -0
  176. package/dist/lib/runtime/valueDepth.js +41 -0
  177. package/dist/lib/session/supabaseSessionService.d.ts +65 -18
  178. package/dist/lib/session/supabaseSessionService.js +148 -105
  179. package/dist/lib/session/transcript.d.ts +69 -23
  180. package/dist/lib/session/transcript.js +105 -29
  181. package/dist/lib/skills.js +33 -7
  182. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  183. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  184. package/dist/lib/storage/postgres/index.d.ts +12 -0
  185. package/dist/lib/storage/postgres/index.js +6 -1
  186. package/dist/lib/storage/postgres/sessionService.d.ts +44 -16
  187. package/dist/lib/storage/postgres/sessionService.js +128 -66
  188. package/dist/lib/syndicateSchema.d.ts +49 -0
  189. package/dist/lib/syndicateSchema.js +99 -9
  190. package/dist/lib/toolRegistry.d.ts +26 -2
  191. package/dist/lib/toolRegistry.js +73 -17
  192. package/dist/lib/tools/adkTool.d.ts +81 -0
  193. package/dist/lib/tools/adkTool.js +268 -0
  194. package/dist/lib/tools/auth.d.ts +118 -0
  195. package/dist/lib/tools/auth.js +89 -0
  196. package/dist/lib/tools/collectionsSearchTool.d.ts +8 -7
  197. package/dist/lib/tools/collectionsSearchTool.js +12 -27
  198. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  199. package/dist/lib/tools/credentialCipher.js +116 -0
  200. package/dist/lib/tools/credentialStore.d.ts +77 -0
  201. package/dist/lib/tools/credentialStore.js +246 -0
  202. package/dist/lib/tools/examples.d.ts +43 -0
  203. package/dist/lib/tools/examples.js +62 -0
  204. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  205. package/dist/lib/tools/generateImageTool.js +19 -29
  206. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  207. package/dist/lib/tools/inspectImageTool.js +13 -17
  208. package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
  209. package/dist/lib/tools/mcpToolFactory.js +46 -47
  210. package/dist/lib/tools/memoryTools.d.ts +61 -0
  211. package/dist/lib/tools/memoryTools.js +119 -0
  212. package/dist/lib/tools/nativeTools.d.ts +38 -0
  213. package/dist/lib/tools/nativeTools.js +43 -0
  214. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  215. package/dist/lib/tools/oauthConsent.js +330 -0
  216. package/dist/lib/tools/openapi/call.d.ts +108 -0
  217. package/dist/lib/tools/openapi/call.js +295 -0
  218. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  219. package/dist/lib/tools/openapi/parse.js +570 -0
  220. package/dist/lib/tools/openapiTools.d.ts +25 -17
  221. package/dist/lib/tools/openapiTools.js +124 -121
  222. package/dist/lib/tools/scienceTools.d.ts +14 -14
  223. package/dist/lib/tools/skillToolset.d.ts +58 -87
  224. package/dist/lib/tools/skillToolset.js +61 -142
  225. package/dist/lib/tools/skills/env.d.ts +58 -0
  226. package/dist/lib/tools/skills/env.js +165 -0
  227. package/dist/lib/tools/skills/executor.d.ts +114 -0
  228. package/dist/lib/tools/skills/executor.js +331 -0
  229. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  230. package/dist/lib/tools/skills/frontmatter.js +215 -0
  231. package/dist/lib/tools/skills/loader.d.ts +91 -0
  232. package/dist/lib/tools/skills/loader.js +257 -0
  233. package/dist/lib/tools/skills/tools.d.ts +187 -0
  234. package/dist/lib/tools/skills/tools.js +386 -0
  235. package/dist/lib/tools/taskTools.d.ts +15 -15
  236. package/dist/lib/tools/tool.d.ts +354 -0
  237. package/dist/lib/tools/tool.js +304 -0
  238. package/dist/lib/tools/toolContract.d.ts +89 -33
  239. package/dist/lib/tools/toolContract.js +104 -82
  240. package/dist/lib/tools/urlContextTool.d.ts +4 -1
  241. package/dist/lib/tools/urlContextTool.js +6 -2
  242. package/dist/lib/tools/webExtractTool.d.ts +3 -3
  243. package/dist/lib/tools/webExtractTool.js +2 -1
  244. package/dist/lib/tools/webSearchTool.d.ts +7 -10
  245. package/dist/lib/tools/webSearchTool.js +15 -60
  246. package/dist/lib/tools/wikiTools.d.ts +19 -19
  247. package/dist/lib/tools/wikiTools.js +13 -9
  248. package/dist/lib/tools/xApiSearchTool.d.ts +3 -3
  249. package/dist/lib/tools/xApiSearchTool.js +2 -1
  250. package/dist/lib/tools/xSearchTool.d.ts +8 -10
  251. package/dist/lib/tools/xSearchTool.js +12 -63
  252. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  253. package/dist/lib/tools/xaiSearchParams.js +126 -0
  254. package/dist/lib/wiki/agentRun.d.ts +12 -0
  255. package/dist/lib/wiki/agentRun.js +74 -35
  256. package/dist/lib/wiki/entities.d.ts +12 -0
  257. package/dist/lib/wiki/entities.js +45 -3
  258. package/dist/lib/workflow/agentNode.d.ts +153 -0
  259. package/dist/lib/workflow/agentNode.js +365 -0
  260. package/dist/lib/workflow/graph.d.ts +178 -0
  261. package/dist/lib/workflow/graph.js +468 -0
  262. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  263. package/dist/lib/workflow/nodeEvents.js +101 -0
  264. package/dist/lib/workflow/pause.d.ts +122 -0
  265. package/dist/lib/workflow/pause.js +180 -0
  266. package/dist/lib/workflow/resume.d.ts +166 -0
  267. package/dist/lib/workflow/resume.js +441 -0
  268. package/dist/lib/workflow/route.d.ts +58 -0
  269. package/dist/lib/workflow/route.js +69 -0
  270. package/dist/lib/workflow/scheduler.d.ts +418 -0
  271. package/dist/lib/workflow/scheduler.js +756 -0
  272. package/dist/lib/workflow/toolNode.d.ts +127 -0
  273. package/dist/lib/workflow/toolNode.js +236 -0
  274. package/dist/lib/workflow/turn.d.ts +118 -0
  275. package/dist/lib/workflow/turn.js +216 -0
  276. package/dist/lib/workflow.d.ts +33 -11
  277. package/dist/lib/workflow.js +49 -27
  278. package/dist/lib/workflowConfig.d.ts +7 -2
  279. package/dist/lib/workflowConfig.js +7 -12
  280. package/dist/scripts/a2a_server.js +4 -3
  281. package/dist/scripts/assistant_worker.d.ts +2 -1
  282. package/dist/scripts/assistant_worker.js +9 -4
  283. package/dist/scripts/doctor.d.ts +3 -1
  284. package/dist/scripts/doctor.js +4 -2
  285. package/dist/scripts/syndicate_chat.js +19 -10
  286. package/package.json +9 -3
  287. package/skills/briefs/melchizedek-author.md +3 -2
  288. package/skills/briefs/melchizedek-models.md +13 -9
  289. package/skills/briefs/melchizedek.md +3 -3
  290. package/skills/melchizedek/SKILL.md +2 -1
  291. package/skills/melchizedek-author/SKILL.md +2 -1
  292. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  293. 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,27 @@ 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. Since 0.20.0
60
+ the native loop runs every turn by default
61
+ ([ADR 0102](./wiki/decisions/0102-native-default-and-optional-adk-peer.md)):
62
+ `@google/adk` is an optional peer, needed only when
63
+ `MELCHIZEDEK_RUNTIME=adk`, or `runtime: 'adk'` on `runSyndicateTurn`,
64
+ selects ADK's Runner, which 1.0.0 removes; without it that choice fails
65
+ before the session is touched, naming the package, and
66
+ `npx melchizedek-doctor` prints the runtime in use and whether ADK is
67
+ installed. The native loop runs a single-agent, delegating or
68
+ plan-dispatch syndicate with the same result and the same stored events
69
+ the ADK runtime writes, `context:` compaction and `mode: task` included
70
+ ([ADR 0078](./wiki/decisions/0078-native-compaction-ports-adk-compactor.md)),
71
+ and a `workflow:` syndicate on the engine's own scheduler, its pause and
72
+ resume included
73
+ ([ADR 0095](./wiki/decisions/0095-native-workflow-turn-drains-through-the-adk-reader.md));
74
+ what it does not run yet throws `UnsupportedOnRuntimeError` before any
75
+ model call
76
+ ([ADR 0073](./wiki/decisions/0073-one-agent-spec-and-a-runtime-flag.md)).
77
+
55
78
  ## 2. The syndicate YAML
56
79
 
57
80
  Minimal complete example:
@@ -72,11 +95,9 @@ orchestrator:
72
95
  You are the Conductor… (persona, objective, workflow contract)
73
96
  tools:
74
97
  - "google_search" # names resolved via lib/toolRegistry.ts
98
+ reasoning: medium # none | low | medium | high, or { budget_tokens: 4096 }
75
99
  generateContentConfig:
76
100
  maxOutputTokens: 4096
77
- thinkingConfig:
78
- thinkingLevel: "MEDIUM" # or thinkingBudget on older models
79
- includeThoughts: true
80
101
 
81
102
  subagents:
82
103
  - name: "Researcher"
@@ -104,7 +125,8 @@ Field reference:
104
125
  | `name` / `model` / `instruction` | agent | The agent triple. Any Gemini id, `claude-*`, or `ollama/*` for open-weight local models (see §5). |
105
126
  | `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
127
  | `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. |
128
+ | `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. |
129
+ | `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
130
  | `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
131
  | `yaml_reference` | subagent | Mount another syndicate file as a nested subagent. |
110
132
  | `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. |
@@ -117,8 +139,8 @@ Field reference:
117
139
  | `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)). |
118
140
  | `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. |
119
141
  | `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. |
120
- | `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`. |
121
- | `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`. |
142
+ | `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`. |
143
+ | `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. |
122
144
 
123
145
  Validation happens at load: missing names, legacy option blocks, and
124
146
  malformed agents fail with pointed errors before any model is called.
@@ -128,28 +150,37 @@ skipped with a warning at compile time.
128
150
  ## 3. Tools
129
151
 
130
152
  Registered in `lib/toolRegistry.ts` — one map from YAML name to ADK tool
131
- instance:
153
+ instance. A **Contract** is the engine's own Tool (`lib/tools/tool.ts`),
154
+ defined once with `defineTool` and handed to the ADK runtime as a
155
+ `FunctionTool` by `toFunctionTool` (`lib/tools/adkTool.ts`). An
156
+ **Instruction tool** is the engine's own too: it declares no function and
157
+ only writes into each request's instruction, reaching the ADK runtime
158
+ through `toAdkInstructionTool`. A **server-side tool** is an own marker
159
+ (`lib/tools/nativeTools.ts`) that names the provider's tool and declares no
160
+ function; the ADK runtime runs its sentinel (`toAdkNativeTool`), and every
161
+ adapter recognises it by marker ([ADR 0062](./wiki/decisions/0062-server-side-tools-as-markers.md)):
132
162
 
133
163
  | Name | Kind | Does |
134
164
  |---|---|---|
135
165
  | `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. |
136
- | `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. |
166
+ | `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. |
137
167
  | `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). |
138
168
  | `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. |
139
169
  | `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. |
140
170
  | `google_search` | ADK built-in | Live web search — Gemini agents only (legacy alias; use `web_search`). |
141
- | `preload_memory` | ADK built-in | Silently injects similarity-matched facts into every request (ambient recall). |
142
- | `load_memory` | ADK built-in | Explicit tool call to search the fact store (deliberate recall). |
143
- | `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. |
144
- | `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`). |
145
- | `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. |
146
- | `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). |
147
- | `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. |
171
+ | `preload_memory` | Instruction tool | Silently injects similarity-matched facts into every request's instruction (ambient recall). The model never calls it. |
172
+ | `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)). |
173
+ | `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. |
174
+ | `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`). |
175
+ | `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. |
176
+ | `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). |
177
+ | `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. |
148
178
 
149
179
  **MCP tools** are the exception to the registry: a subagent with
150
180
  `mcp_server_url:` in its YAML gets its tools from a remote MCP server at
151
181
  load time. `lib/tools/mcpToolFactory.ts` dials the server over SSE,
152
- lists its tools, and wraps each one as a live `FunctionTool` — the
182
+ lists its tools, and makes each one an own Tool (`loadMcpTools`), which
183
+ the ADK runtime runs as a `FunctionTool` (`createMcpTools`) — the
153
184
  agent's reach is decided by the server, not compiled in.
154
185
  `config/agents/examples/librarian.yaml` plus the demo catalog server
155
186
  (`npm run mcp:demo`, `scripts/demo_mcp_server.ts`) are the worked
@@ -160,8 +191,10 @@ treat any remote MCP server as an untrusted tool vendor whose results
160
191
  are data, never instructions.
161
192
 
162
193
  **OpenAPI tools** turn any HTTP API with an OpenAPI 3 spec into an
163
- agent's tools, with no tool code (`lib/tools/openapiTools.ts`, on ADK's
164
- `OpenAPIToolset`; [ADR 0032](./wiki/decisions/0032-openapi-tools.md)):
194
+ agent's tools, with no tool code (`lib/tools/openapiTools.ts`, on the
195
+ engine's own parser and caller in `lib/tools/openapi/`;
196
+ [ADR 0032](./wiki/decisions/0032-openapi-tools.md),
197
+ [ADR 0067](./wiki/decisions/0067-openapi-calls-on-the-engines-own-caller.md)):
165
198
 
166
199
  ```yaml
167
200
  orchestrator:
@@ -190,7 +223,10 @@ may name. A refused or unset variable fails the compile, and a static token is a
190
223
  never stored in session state. Every server must be http(s) and pass the
191
224
  SSRF guard: its literal rules when the agent compiles, the full check with
192
225
  DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` permits private hosts for
193
- local development. A response over 20,000 characters is cut and says so; a
226
+ local development. Redirects are followed one hop at a time, each hop held
227
+ to the guard, and a hop to another origin carries no credential. A
228
+ credential's value never appears in an error the model reads. Only OpenAPI
229
+ 3.x specs are read. A response over 20,000 characters is cut and says so; a
194
230
  network failure comes back to the model as an error. Specs are files, never
195
231
  URLs: save the spec beside the YAML and review it like code (trim it to the
196
232
  operations the agent needs, and write each `summary` for the model). Worked
@@ -311,12 +347,12 @@ accordingly — model optionality is a single YAML line per agent:
311
347
  | Model id | Provider | Adapter | Key | Native `web_search` |
312
348
  |---|---|---|---|---|
313
349
  | `gemini-*` | Google Gemini | ADK-native (`TracedGemini`) | `GOOGLE_GENAI_API_KEY` | ✅ grounding |
314
- | `claude-*` | Anthropic | `lib/models/claudeLlm.ts` | `ANTHROPIC_API_KEY` | ✅ server tool |
315
- | `gpt-*`, o-series | OpenAI | `lib/models/gptLlm.ts` (Responses API) | `OPENAI_API_KEY` | ✅ web_search tool |
316
- | `grok-*` | xAI | `lib/models/grokLlm.ts` (Responses API) | `XAI_API_KEY` | ✅ Agent Tools search |
317
- | `kimi-*` | Moonshot AI (Kimi) | `lib/models/kimiLlm.ts` (chat completions) | `MOONSHOT_API_KEY` | ⚠ omitted + warning |
318
- | `ollama/*` | Local Ollama | `lib/models/ollamaLlm.ts` | none | ⚠ omitted + warning |
319
- | *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 |
350
+ | `claude-*` | Anthropic | `lib/models/claudeAdapter.ts` (Messages API; `ClaudeLlm` is its ADK shim) | `ANTHROPIC_API_KEY` | ✅ server tool |
351
+ | `gpt-*`, o-series | OpenAI | `lib/models/gptLlm.ts` around `gptAdapter.ts` (Responses API) | `OPENAI_API_KEY` | ✅ web_search tool |
352
+ | `grok-*` | xAI | `lib/models/grokLlm.ts` around `grokAdapter.ts` (Responses API) | `XAI_API_KEY` | ✅ Agent Tools search |
353
+ | `kimi-*` | Moonshot AI (Kimi) | `lib/models/kimiLlm.ts` around `kimiAdapter.ts` (chat completions) | `MOONSHOT_API_KEY` | ⚠ omitted + warning |
354
+ | `ollama/*` | Local Ollama | `lib/models/ollamaLlm.ts` around `ollamaAdapter.ts` (chat completions) | none | ⚠ omitted + warning |
355
+ | *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 |
320
356
 
321
357
  The xAI adapter carries the deepest capability surface: `grok-4.5`
322
358
  requests pin `reasoning.effort: "medium"` (`lib/config.ts`), SSE
@@ -326,8 +362,9 @@ structured outputs ride `outputSchema` → `text.format`, and two
326
362
  xAI-only tools — `x_search` and `collections_search` (§3) — turn on
327
363
  live X search and hosted-document RAG. All verified live on grok-4.5.
328
364
 
329
- **Moonshot AI (Kimi).** `kimi-*` ids route to `lib/models/kimiLlm.ts`, a
330
- subclass of the chat-completions base, against `https://api.moonshot.ai/v1`
365
+ **Moonshot AI (Kimi).** `kimi-*` ids route to `lib/models/kimiLlm.ts`, the
366
+ ADK shim around `KimiAdapter` (`lib/models/kimiAdapter.ts`, on the
367
+ chat-completions base), against `https://api.moonshot.ai/v1`
331
368
  (`MOONSHOT_BASE_URL` for a proxy). Get a key at platform.moonshot.ai; the
332
369
  `.cn` console serves mainland China. The family (USD per 1M tokens, October
333
370
  2026): `kimi-k3`, the flagship — 2.8T-parameter open-weight MoE, 1M context,
@@ -348,11 +385,12 @@ document it; K2.6 is unstable on `$ref`/`oneOf`), SSE streaming, images as
348
385
  base64 (Moonshot takes no public image URLs), MCP tools (they are function
349
386
  tools to the model), `reasoning_content` surfaced as THINKING. Reasoning:
350
387
  K3 takes a top-level `reasoning_effort` (`low` | `high` | `max`); the adapter
351
- pins `DEFAULT_KIMI_REASONING_EFFORT` (`high`, `lib/config.ts`) unless
352
- `generateContentConfig.reasoningEffort` says otherwise, and maps `none` to
353
- `low` because K3 cannot stop thinking. K2.x models take a `thinking:
354
- { type }` switch instead: `reasoningEffort: "none"` or `thinkingBudget: 0`
355
- sends `disabled`, and any effort value is dropped from the body. What does
388
+ pins `DEFAULT_KIMI_REASONING_EFFORT` (`high`, `lib/config.ts`) unless the
389
+ agent's `reasoning:` says otherwise (`medium` is sent as `high`, which K3
390
+ has in its place), and maps `none` to `low` because K3 cannot stop
391
+ thinking. K2.x models take a `thinking: { type }` switch instead:
392
+ `reasoning: none` sends `disabled`, and any effort value is dropped from
393
+ the body. What does
356
394
  not: native `web_search` (dropped with a warning; `web_extract` works, and
357
395
  Moonshot's standalone `POST /v1/tools/search`, billed per call, could back
358
396
  a client-side tool), and thinking carried across a tool loop (Moonshot asks
@@ -436,13 +474,64 @@ prompt to every available provider, printing input, thinking (qwen3
436
474
  `<think>` blocks, Claude extended thinking, GPT reasoning summaries,
437
475
  Grok reasoning, Kimi reasoning_content), output, and a per-request token/latency trace; add
438
476
  `-- --search` to watch four native web searches plus the local
439
- omission. Providers without keys are skipped, never fatal.
477
+ omission. Providers without keys are skipped, never fatal. Each agent
478
+ runs as a one-agent syndicate through `runSyndicateTurn`, so
479
+ `MELCHIZEDEK_RUNTIME=native npm run demo:models` runs the same demo on
480
+ the engine's own loop.
440
481
 
441
482
  Reasoning/thinking: scratchpads from every provider are surfaced as
442
- dimmed THINKING output and kept out of session history. On Claude,
443
- `generateContentConfig.thinkingConfig.thinkingBudget` enables
444
- Anthropic extended thinking (thinking + tool use on the same Claude
445
- agent is not supported yet).
483
+ dimmed THINKING output and kept out of session history. On Claude, any
484
+ `reasoning:` other than `none` (or the older
485
+ `generateContentConfig.thinkingConfig.thinkingBudget`) enables Anthropic
486
+ extended thinking, tools included: the signed thinking blocks ride on the
487
+ response's parts as `providerState` and are replayed verbatim within the
488
+ turn's tool loop (ADR 0046). GPT's reasoning ids (o-series, `gpt-5*`) and
489
+ `grok-4.5`, `grok-4.6` and `grok-4.7` do the same with their encrypted
490
+ reasoning items.
491
+ Their requests send `store: false`, so the vendor keeps no copy of the
492
+ response (ADR 0050).
493
+
494
+ The engine's own model contract is `lib/models/contract.ts` (ADR 0048),
495
+ importable as `melchizedek-agents/models/contract`. It defines the message
496
+ format, request, response stream and adapter interface of the native
497
+ runtime (ADR 0045), with no `@google/*` in its import graph, and the
498
+ adapters move onto it in stages. `wiki/models/model-contract.md` gives
499
+ each field's purpose and its mapping onto every provider's wire. While ADK
500
+ runs the turn, an adapter on the contract runs behind `AdkShim`
501
+ (`melchizedek-agents/models/adkShim`), an ADK `BaseLlm` that charges each
502
+ call against `max_steps`, passes the turn's abort signal and opens the
503
+ `llm.request` span, as the ADK-path adapters do (ADR 0053).
504
+ GPT and Grok run on the contract already: `GptLlm` and `GrokLlm` are that
505
+ shim around `GptAdapter` (`melchizedek-agents/models/gptAdapter`) and
506
+ `GrokAdapter` (`melchizedek-agents/models/grokAdapter`), and keep the token
507
+ counts and server-side tool record the ledger has always had for them
508
+ (ADR 0056).
509
+ Gemini has two adapters on the contract:
510
+ `melchizedek-agents/models/geminiAdapter` (`GeminiAdapter`, on
511
+ `@google/genai` with no ADK) and, until that one passes its live parity
512
+ run, `melchizedek-agents/models/adkGeminiAdapter` (`AdkGeminiAdapter`,
513
+ a temporary wrapper that runs the request through `TracedGemini`, now in
514
+ `melchizedek-agents/models/tracedGemini`).
515
+ `resolveAdapter(modelId, { apiKey, keyProvider, endpoint, gemini })` in
516
+ `melchizedek-agents/models/registry` returns any id's contract adapter
517
+ from the same prefix table, gateway rule, BYOK scoping and endpoints as
518
+ `resolveModel`, with no ADK registry (ADR 0060). A Gemini id gets
519
+ `AdkGeminiAdapter` unless `GEMINI_ADAPTER=engine` (or `gemini: 'engine'`)
520
+ asks for `GeminiAdapter`. `resolveAdapterWithFallback` wraps an agent's
521
+ model and `fallback_model` in a `FallbackAdapter`.
522
+
523
+ `melchizedek-agents/model` is the model layer on its own, with no
524
+ `@google/adk` in its import graph (ADR 0068): the contract's types,
525
+ `ClaudeAdapter`, `GptAdapter`, `GrokAdapter`, `KimiAdapter`,
526
+ `OllamaAdapter`, `GatewayAdapter`, `GeminiAdapter`, the
527
+ `ChatCompletionsAdapter` base, `resolveAdapter`,
528
+ `resolveAdapterWithFallback`, `FallbackAdapter` and the circuit breaker's
529
+ helpers. A project that only calls models installs the package without ADK
530
+ and imports from there. Its `resolveAdapter` gives a Gemini id
531
+ `GeminiAdapter`; asking it for `adk` (`gemini: 'adk'` or
532
+ `GEMINI_ADAPTER=adk`) throws and names `melchizedek-agents/models/registry`,
533
+ whose `resolveAdapter` keeps the ADK default above. `AdkGeminiAdapter`, the
534
+ ADK shims, `TracedGemini` and `resolveModel` are not in it.
446
535
 
447
536
  Every model request also emits an `llm.request` OpenTelemetry span
448
537
  (provider, model, input/output/thinking tokens, latency). Scripts print it
@@ -461,7 +550,14 @@ full-text `search` column; `adk_telemetry` — one row per `llm.request` /
461
550
  root span; and `adk_payloads` — full prompts and responses per model call,
462
551
  kept by policy (`TELEMETRY_PAYLOADS=off|errors|sample|all`,
463
552
  `TELEMETRY_PAYLOAD_SAMPLE`, `TELEMETRY_PAYLOAD_TTL_DAYS`) and expired by
464
- `melchizedek_prune_telemetry()`. The view `adk_turns_production` excludes
553
+ `melchizedek_prune_telemetry()`. A row from a failed call's `llm.request`
554
+ span holds the request in the model contract's shape (`model`, `system`,
555
+ `messages`, `tools`, …), whichever adapter made the call; a row from ADK's
556
+ own `call_llm` span holds ADK's request. On the native runtime the loop
557
+ opens `agent.invoke <name>`, `model.call` and `tool.execute <name>` spans
558
+ where ADK opens `invoke_agent`, `call_llm` and `execute_tool`, and the ledger
559
+ reads both (ADR 0076); a clean call's row then comes from `model.call` and
560
+ holds the request and the adapter's response in the contract's shapes. The view `adk_turns_production` excludes
465
561
  eval and classifier turns. Operate it with `npm run telemetry:stats`,
466
562
  `telemetry:prune` and `telemetry:replay` (the exporter spools failed
467
563
  batches to `outputs/telemetry-deadletter.ndjson`).
@@ -509,7 +605,7 @@ options. `demo/a2a_demo.mjs` is a complete client.
509
605
  | `POST /a2a/jsonrpc`, `/a2a/rest` | bearer | the default syndicate |
510
606
  | `GET /<agentId>/.well-known/agent-card.json` | bearer | another syndicate's card; its URLs point at `/<agentId>/a2a/...` |
511
607
  | `POST /<agentId>/a2a/jsonrpc`, `/<agentId>/a2a/rest` | bearer | another syndicate (`A2A_SERVED_AGENTS` restricts which) |
512
- | `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 |
608
+ | `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 |
513
609
 
514
610
  "bearer" means the credential `A2A_AUTH` asks for (below); with no
515
611
  `A2A_SERVER_SECRET` and no authenticator the server binds `127.0.0.1` only.
@@ -567,8 +663,10 @@ or pass your own `resolveRequest`.
567
663
  `A2A_LOG_FORMAT=json` prints every server line as JSON, this record included.
568
664
  - **The audit trail** (`melchizedek_audit`, `db/migrations/0012_audit_log.sql`;
569
665
  option `audit`, supplied by `postgresStorage`): one row per failed
570
- authentication (`auth.failure`), task outcome (`task.end`) and erasure
571
- (`memory.erase`), with the caller's name, the source address, the agent and
666
+ authentication (`auth.failure`), task outcome (`task.end`), erasure
667
+ (`memory.erase`) and tool credential stored, refreshed, revoked or erased
668
+ (`credential.put`, `.refresh`, `.revoke`, `.erase`, naming the provider and
669
+ app, never a token), with the caller's name, the source address, the agent and
572
670
  task ids, and a hash of the scope. Never a scope key and never conversation
573
671
  content. A trigger refuses UPDATE and DELETE; rows leave only through
574
672
  `SELECT melchizedek_prune_audit(<days>)`, which you schedule (pg_cron, or a
@@ -600,6 +698,7 @@ names; nothing is kept by the framework anywhere else.
600
698
  | 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` |
601
699
  | Sampled full model requests | Your Postgres, `adk_payloads` | 30 days | `TELEMETRY_PAYLOADS`, `TELEMETRY_PAYLOAD_TTL_DAYS` |
602
700
  | 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`) |
701
+ | 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 |
603
702
  | The audit trail (no content) | Your Postgres, `melchizedek_audit` | Until `melchizedek_prune_audit(<days>)` | The prune schedule |
604
703
  | The task record (no content) | stdout | Your log platform's policy | `A2A_LOG_FORMAT` |
605
704
 
@@ -695,7 +794,8 @@ without a model call. ADK pins the call and its arguments, so an approval
695
794
  cannot run a different call.
696
795
 
697
796
  Gates are allowed on the orchestrator and on the subagents of a
698
- plan-dispatch syndicate, which run as the turn's own agent. A delegated
797
+ plan-dispatch syndicate, which run as the turn's own agent, and on the
798
+ agent nodes of a workflow on the native runtime (§6). A delegated
699
799
  subagent runs inside a tool call, where a pause cannot reach the caller, so a
700
800
  gate there is a load error, as is any gate inside a nested `yaml_reference`
701
801
  syndicate. Only function tools from the registry can be gated, not MCP tools
@@ -732,6 +832,59 @@ subagent or a workflow node listing it is a load error. In code,
732
832
  `runSyndicateTurn` returns `status: 'input-required'` with `input`
733
833
  (`node`, `message`, `payload`), and the next message's text answers it.
734
834
 
835
+ #### OAuth consent for tools
836
+
837
+ A tool that acts for the person against a third-party API reads their
838
+ delegated token with `ctx.accessToken(provider)` from the sealed credential
839
+ store ([ADR 0072](./wiki/decisions/0072-tool-credentials-sealed-per-user.md)).
840
+ When they have not granted the provider yet, the turn pauses for their
841
+ consent ([ADR 0085](./wiki/decisions/0085-oauth-consent-pauses-on-adks-credential-request.md)).
842
+ This runs on the native runtime:
843
+
844
+ ```ts
845
+ import { createA2AApp } from 'melchizedek-agents/a2a';
846
+ import { oauthConsent } from 'melchizedek-agents/tools/oauthConsent';
847
+
848
+ const consent = oauthConsent({
849
+ providers: {
850
+ github: {
851
+ authorizationUrl: 'https://github.com/login/oauth/authorize',
852
+ tokenUrl: 'https://github.com/login/oauth/access_token',
853
+ clientId: process.env.GITHUB_CLIENT_ID!,
854
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
855
+ scopes: ['repo'],
856
+ },
857
+ },
858
+ redirectUri: 'https://agents.example.com/oauth/callback', // as registered at the provider
859
+ credentials: store, // storage.credentials, or credentialStore(...)
860
+ });
861
+ await createA2AApp({ /* … */ toolCredentials: { store, consent } });
862
+ ```
863
+
864
+ 1. **The pause.** The task ends `input-required` with the link in its status
865
+ message and a data part `{ type: 'consent_request', consent_id, agent,
866
+ provider, authorization_url, state, scopes }`.
867
+ 2. **The grant.** The person opens the link and consents. The provider
868
+ redirects their browser to the callback, which the server mounts at the
869
+ redirect URI's path. The callback exchanges the code with PKCE and stores
870
+ the grant, sealed.
871
+ 3. **The resume.** Their next message on the conversation runs the paused
872
+ call again. A message sent before the grant gets the same request back,
873
+ without a model call.
874
+
875
+ The callback refuses these, and stores nothing:
876
+
877
+ - a state that is replayed, tampered with or older than ten minutes;
878
+ - a provider's `error`;
879
+ - a caller who is authenticated as another user.
880
+
881
+ No token, code, client secret or verifier is written to a page, an event, a
882
+ log line or the model's context. The pending flows live in the process: run
883
+ one replica, or route the callback to the instance that paused the call. In
884
+ code, `runSyndicateTurn({ …, toolCredentials })` returns
885
+ `status: 'input-required'` with `consent`. On the ADK runtime an open consent
886
+ request throws `UnsupportedOnRuntimeError`.
887
+
735
888
  #### Limits
736
889
 
737
890
  | Setting | Default |
@@ -1053,7 +1206,11 @@ the list of results); `tool: <registry name>` (runs the tool with the
1053
1206
  node input as its arguments); `ask_user: "<question>"` (below). An entry
1054
1207
  named after an agent carries modifiers only: `route_key`, `retry`
1055
1208
  (`max_attempts`, `initial_delay`, `max_delay`, `backoff_factor`, in
1056
- seconds) and `timeout` (seconds), which any node may carry.
1209
+ seconds; `jitter`, the backoff's randomness, 0 for none; `exceptions`,
1210
+ the error names to retry on, every error when absent) and `timeout`
1211
+ (seconds), which any node but a `map` may carry. A map's items run under
1212
+ the mapped agent's own `retry` and `timeout`, so a `map` entry refuses
1213
+ both and the load error names the agent's entry instead.
1057
1214
 
1058
1215
  **The pause.** An `ask_user` node ends the turn `input-required`
1059
1216
  (`result.input`: the node, the question, and what it was asked about).
@@ -1078,22 +1235,47 @@ again (the attempt is recorded in `answer.nodeErrors`), and a node that
1078
1235
  gives up fails the turn with `NODE_FAILED` naming it. `max_steps` and
1079
1236
  the deadline cap the whole graph as they cap any turn.
1080
1237
 
1081
- **Not yet.** Approval gates (`require_approval`, `skills.scripts: local`)
1082
- and remote `a2a_agent_url` subagents are refused inside a workflow by
1083
- the schema, and a workflow cannot be another syndicate's `yaml_reference`
1084
- (ADK cannot yet make a `Workflow` a subagent; nested, only its
1085
- orchestrator runs). The record is [ADR 0030](./wiki/decisions/0030-workflow-graphs.md);
1086
- the contract is `lib/workflow.ts`, on ADK's `Workflow`.
1238
+ **Approval gates.** A tool in a node agent's `require_approval` pauses
1239
+ that node, and the graph with it: the turn ends `input-required` with
1240
+ `result.approval`, as any approval does, and the next message, the
1241
+ person's decision, resumes the node's own run, which runs or refuses the
1242
+ pinned call once and walks on. Any other message repeats the request and
1243
+ runs nothing. Gates on workflow nodes run on the native runtime only: on
1244
+ ADK, `runSyndicateTurn` refuses such a workflow with
1245
+ `UnsupportedOnRuntimeError` before any model call, because ADK's resume
1246
+ starts the node afresh and never runs the pinned call. The schema refuses
1247
+ a gate on an agent a `map` node runs.
1248
+
1249
+ **As a subagent.** A DELEGATE syndicate's `yaml_reference` to a workflow
1250
+ syndicate runs the whole graph as the subagent tool, under the entry's
1251
+ name and description: the graph's last output is the tool's answer, and
1252
+ its events are kept in the subagent's own session, as for any subagent.
1253
+ A nested workflow may not have an `ask_user` node (a pause inside a tool
1254
+ call cannot reach the caller); it is refused by name. As a dispatch route
1255
+ or a workflow node, a workflow syndicate is still its orchestrator alone.
1256
+
1257
+ **Not yet.** Skill scripts (`skills.scripts: local`) and remote
1258
+ `a2a_agent_url` subagents are refused inside a workflow by the schema. An
1259
+ `ask_user` tool on a node agent is refused too: use an `ask_user` node. The records are [ADR 0030](./wiki/decisions/0030-workflow-graphs.md) and
1260
+ [ADR 0098](./wiki/decisions/0098-workflow-subagent-and-node-approvals.md);
1261
+ the contract is `lib/workflow.ts`, on ADK's `Workflow`, and on the native
1262
+ runtime `lib/workflow/turn.ts`, on the engine's own scheduler
1263
+ ([ADR 0095](./wiki/decisions/0095-native-workflow-turn-drains-through-the-adk-reader.md)).
1264
+ On native, a conversation paused inside an agent node on anything but an
1265
+ approval, or inside a map item (which only ADK resumes), fails the next
1266
+ turn with `RESUME_UNSUPPORTED`.
1087
1267
 
1088
1268
  ## 7. Extending the framework
1089
1269
 
1090
- **Call ADK directly (no syndicate)**: the YAML layer is a convenience,
1270
+ **Call a model directly (no syndicate)**: the YAML layer is a convenience,
1091
1271
  never a requirement. `scripts/direct_call.ts` (`npm run demo:direct`) is
1092
- the canonical minimal block — `LlmAgent` + `Runner` +
1093
- `InMemorySessionService` straight from `@google/adk`, ~30 lines you can
1094
- copy into any repo that has `@google/adk` installed. Add
1095
- `registerAvailableProviders()` from `lib/models/registry.ts` and the
1096
- same block runs `claude-*` / `gpt-*` / `grok-*` / `kimi-*` / `ollama/*` ids too.
1272
+ the canonical minimal block on the engine's model contract:
1273
+ `resolveAdapter(modelId)` from `melchizedek-agents/model`, then
1274
+ `adapter.generate({ model, system, messages, stream })`, reading partial
1275
+ and final responses. The id's prefix picks the provider (`gemini-*`,
1276
+ `claude-*`, `gpt-*`, `grok-*`, `kimi-*`, `ollama/*`), the key comes from
1277
+ the environment, and nothing in that entry loads `@google/adk`. A turn
1278
+ with tools, sessions and telemetry is `runSyndicateTurn`.
1097
1279
 
1098
1280
  **Add a syndicate**: create `config/agents/<name>.yaml` — copy the
1099
1281
  closest starter-pack file from `config/agents/examples/` or start from
@@ -1101,15 +1283,20 @@ closest starter-pack file from `config/agents/examples/` or start from
1101
1283
  <name>`. No code changes. The loader checks the root first, then
1102
1284
  `examples/`, so your syndicate and the starter pack never collide.
1103
1285
 
1104
- **Add a tool**: implement a `FunctionTool` in `lib/tools/`, register the
1105
- name in `lib/toolRegistry.ts`, reference it from YAML. The two image
1106
- tools are the worked examples — including why binary data forces
1107
- FunctionTools over subagents, and how a tool signature can enforce an
1108
- epistemic rule (the blind inventory).
1109
-
1110
- **Add a provider**: follow `claudeLlm.ts` (SDK-based, key-gated) or
1111
- `ollamaLlm.ts` (fetch-based, keyless) — implement the ADK LLM
1112
- interface, register it behind a model-id prefix.
1286
+ **Add a tool**: write a `defineTool` contract in `lib/tools/` (name,
1287
+ description, zod schema, execute), register the name in
1288
+ `lib/toolRegistry.ts` (or call `registerTool` from your own code),
1289
+ reference it from YAML. The contract runs on either runtime; see
1290
+ `wiki/tools/tool-contracts.md`. The two image tools are the worked
1291
+ examples — including why binary data forces function tools over
1292
+ subagents, and how a tool signature can enforce an epistemic rule (the
1293
+ blind inventory).
1294
+
1295
+ **Add a provider**: follow `claudeAdapter.ts` (SDK-based, key-gated, a
1296
+ `ModelAdapter` on the engine's model contract, run under ADK by its shim
1297
+ `claudeLlm.ts`) or, for a chat-completions API, `ollamaAdapter.ts`
1298
+ (fetch-based, keyless: a `ChatCompletionsAdapter` subclass, run under ADK
1299
+ by its shim `ollamaLlm.ts`), and register it behind a model-id prefix.
1113
1300
 
1114
1301
  **Point an agent at an MCP server**: set `mcp_server_url:` on a
1115
1302
  subagent. `scripts/demo_mcp_server.ts` is a complete server to copy —
package/QUICKSTART.md CHANGED
@@ -180,14 +180,13 @@ Everything above runs inside a clone. When your syndicates deserve their
180
180
  own repo, the same engine is a typed dependency:
181
181
 
182
182
  ```bash
183
- npm install melchizedek-agents @google/adk@2.2.0
183
+ npm install melchizedek-agents # @google/adk only for MELCHIZEDEK_RUNTIME=adk
184
184
  npx melchizedek-init # config/agents/conversational.yaml + .env; --list for others
185
185
  npx melchizedek-doctor # which keys it needs, and whether it is ready
186
186
  ```
187
187
 
188
188
  ```typescript
189
- import { InMemorySessionService } from '@google/adk';
190
- import { loadSyndicate, registerAvailableProviders, runSyndicateTurn } from 'melchizedek-agents';
189
+ import { InProcessSessionService, asAdkSessionService, loadSyndicate, registerAvailableProviders, runSyndicateTurn } from 'melchizedek-agents';
191
190
 
192
191
  registerAvailableProviders(); // registers every model whose key is present
193
192
  const config = loadSyndicate('mine.yaml'); // reads <your-repo>/config/agents/mine.yaml
@@ -196,7 +195,7 @@ const result = await runSyndicateTurn({
196
195
  config,
197
196
  parts: [{ text: 'Hello' }],
198
197
  appName: 'my-app', userId: 'u1', sessionId: 'c1',
199
- sessionService: new InMemorySessionService(),
198
+ sessionService: asAdkSessionService(new InProcessSessionService()),
200
199
  });
201
200
  console.log(result.text);
202
201
  ```
@@ -222,8 +221,12 @@ console.log(result.text);
222
221
  `config/agents/`.
223
222
  - Deeper imports are available as subpaths — `melchizedek-agents/models/registry`,
224
223
  `melchizedek-agents/tools/webExtractTool`, `melchizedek-agents/memory`,
225
- and friends — all typed. `@google/adk` installs alongside as a peer:
226
- your app owns the ADK version.
224
+ and friends — all typed.
225
+ - **Native by default; ADK optional.** Since 0.20.0 every turn runs on the
226
+ engine's own loop. `@google/adk` is an optional peer: install it
227
+ (`npm install @google/adk@~2.2.0`) and set `MELCHIZEDEK_RUNTIME=adk` to
228
+ run on ADK, which 1.0.0 removes; your app then owns the ADK version.
229
+ `npx melchizedek-doctor` prints which runtime is in use.
227
230
  - **Your coding agent can learn all of this.** The package ships a
228
231
  six-skill suite (`skills/`, the open SKILL.md standard) covering the
229
232
  catalog, authoring, serving, memory, models, and the Scribe:
package/README.md CHANGED
@@ -73,11 +73,12 @@ Install into your existing project:
73
73
  npm install melchizedek-agents
74
74
  ```
75
75
 
76
+ `@google/adk` is an optional peer since 0.20.0: install it beside the package (`npm install @google/adk@~2.2.0`) only to run on the `adk` runtime.
77
+
76
78
  Define your agents in `./config/agents/mine.yaml`, then run turns programmatically:
77
79
 
78
80
  ```typescript
79
- import { InMemorySessionService } from '@google/adk';
80
- import { loadSyndicate, registerAvailableProviders, runSyndicateTurn } from 'melchizedek-agents';
81
+ import { InProcessSessionService, asAdkSessionService, loadSyndicate, registerAvailableProviders, runSyndicateTurn } from 'melchizedek-agents';
81
82
 
82
83
  // 1. Register a provider for every key that is set
83
84
  registerAvailableProviders();
@@ -92,7 +93,7 @@ const result = await runSyndicateTurn({
92
93
  appName: 'my-app',
93
94
  userId: 'user-42',
94
95
  sessionId: 'conversation-7', // same id, same conversation
95
- sessionService: new InMemorySessionService(),
96
+ sessionService: asAdkSessionService(new InProcessSessionService()),
96
97
  events: {
97
98
  onProgress: (line) => console.log('…', line)
98
99
  },
@@ -101,7 +102,7 @@ const result = await runSyndicateTurn({
101
102
  console.log(result.status, result.text);
102
103
  ```
103
104
 
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`.
105
+ `runSyndicateTurn` is the same runtime the server, CLI, and eval harness use. Since 0.20.0 it runs every syndicate on the engine's own loop and scheduler (the `native` runtime) by default. `MELCHIZEDEK_RUNTIME=adk` (or `runtime: 'adk'`) runs it on Google ADK instead, with `@google/adk` installed, until 1.0.0 removes that runtime; `npx melchizedek-doctor` prints the runtime in use and whether ADK is installed ([ADR 0102](./wiki/decisions/0102-native-default-and-optional-adk-peer.md)). 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
106
 
106
107
  The example syndicates ship inside the package at `node_modules/melchizedek-agents/config/agents/examples/`; copy one out as a starting point.
107
108
 
@@ -116,6 +117,40 @@ registerTool('myCustomTool', defineTool({ /* ... */ }));
116
117
  registerGuard(myGuardInstance);
117
118
  ```
118
119
 
120
+ ### The Model Adapters Alone
121
+
122
+ `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`. Since 0.20.0 ADK is an optional peer, so `npm install melchizedek-agents` leaves it out; the other peers (redis, the cloud SDKs) are optional too.
123
+
124
+ ```typescript
125
+ import { ClaudeAdapter, OllamaAdapter, resolveAdapter } from 'melchizedek-agents/model';
126
+ import type { ModelAdapter, ModelRequest } from 'melchizedek-agents/model';
127
+
128
+ async function ask(adapter: ModelAdapter, text: string): Promise<string> {
129
+ const request: ModelRequest = {
130
+ model: adapter.model,
131
+ system: 'Answer in one sentence.',
132
+ messages: [{ role: 'user', parts: [{ type: 'text', text }] }],
133
+ };
134
+ for await (const response of adapter.generate(request)) {
135
+ if (response.partial) continue; // deltas while the model writes
136
+ if (response.error) throw new Error(response.error.message);
137
+ return response.parts.map((p) => (p.type === 'text' ? p.text : '')).join('');
138
+ }
139
+ return '';
140
+ }
141
+
142
+ // Claude: the key comes from ANTHROPIC_API_KEY in the environment.
143
+ console.log(await ask(new ClaudeAdapter({ model: 'claude-sonnet-4-6' }), 'What is A2A?'));
144
+
145
+ // Ollama: a local model, no key (OLLAMA_BASE_URL, else http://localhost:11434/v1).
146
+ console.log(await ask(new OllamaAdapter({ model: 'ollama/qwen3:8b' }), 'What is A2A?'));
147
+
148
+ // Or by id, with the same routing as a syndicate's `model:` field.
149
+ const adapter = resolveAdapter('ollama/qwen3:8b');
150
+ ```
151
+
152
+ 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.
153
+
119
154
  ### CLI & Server Utilities
120
155
 
121
156
  - `npx melchizedek-chat --syndicate <name>`: Interactive CLI REPL for any syndicate.
@@ -164,6 +199,9 @@ See `config/agents/syndicateSchema.yaml` for the annotated schema reference.
164
199
  - [`DOCUMENTATION.md`](./DOCUMENTATION.md) — Full reference: architecture, syndicate YAML, tools, memory, providers, A2A, extending, security.
165
200
  - [`AGENT_SETUP.md`](./AGENT_SETUP.md) — A paste-ready prompt that walks your coding agent (Claude Code, Cursor, Codex) through setup.
166
201
  - [`skills/README.md`](./skills/README.md) — The bundled `SKILL.md` suite.
202
+ - [`VERSIONING.md`](./VERSIONING.md) — What an upgrade may break, deprecation, and which versions get fixes.
203
+ - [`SUPPORT.md`](./SUPPORT.md) — Where to ask, and what to expect: a single-maintainer project with no SLA.
204
+ - [`SECURITY.md`](./SECURITY.md) and [`CONTRIBUTING.md`](./CONTRIBUTING.md).
167
205
 
168
206
  ## License
169
207