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
@@ -0,0 +1,216 @@
1
+ /**
2
+ * lib/workflow/turn.ts — one turn of a workflow syndicate on the native
3
+ * runtime: the message stored, the graph walked by the engine's scheduler,
4
+ * and every event the walk stores yielded in the order it was stored, the
5
+ * node inputs aside, as ADK's Runner yields them (ADR 0095).
6
+ *
7
+ * WHY this file exists:
8
+ * runSyndicateTurn (lib/runtime/syndicateTurn.ts) runs a workflow on ADK
9
+ * by handing ADK's Workflow to its Runner and draining the events the
10
+ * Runner yields (drainAgentStream). On the native runtime it hands this
11
+ * generator to the same drain, under the same root span, so the progress
12
+ * lines, onProgress calls, node errors, the paused input and the ledger
13
+ * rows come out of one reader on both runtimes. Everything a node does is
14
+ * the WS4 modules': the scheduler (scheduler.ts), agent nodes on the
15
+ * native loop (agentNode.ts), tool nodes (toolNode.ts), ask_user nodes and
16
+ * the workflow's pause record (pause.ts), and the resume rebuilt from the
17
+ * session (resume.ts). This file wires them for one turn, as ADK's Runner
18
+ * and Workflow wire theirs:
19
+ *
20
+ * 1. THE MESSAGE. Stored as the user's event under a new `e-` invocation
21
+ * id, before the walk, as the Runner stores it (runNativeAgent does the
22
+ * same for one agent). A turn whose signal aborted first stores nothing.
23
+ * 2. THE START. workflowResume rebuilds every node's prior runs and the
24
+ * answers from the session, as ADK's rehydration does on every message:
25
+ * with nothing paused every node runs fresh. The walk's input is the
26
+ * message's text, not its content, as ADK hands its root workflow, and
27
+ * that text is what the workflow's pause record keeps. A pause only ADK
28
+ * can resume (UnsupportedWorkflowResumeError) fails the turn; it never
29
+ * walks afresh and asks the person again.
30
+ * 3. THE WALK. runWorkflowGraph with the runners chained (ask_user, tool,
31
+ * then agentNodeRuntime for agent nodes and map items), under the
32
+ * turn's signal. Every event a runner stores goes through the agent
33
+ * node runtime's one queue, in walk order: node inputs, the agents'
34
+ * events, tool and ask_user events, route steps, joins and maps, and
35
+ * the node-error event ADK writes for a node that gave up
36
+ * (nodeErrorEvent, on the scheduler's `workflow` node_error). A node's
37
+ * input turn is stored and not yielded, as ADK's Runner never yields
38
+ * it; a node agent's partial (streamed) event is yielded and not
39
+ * stored, as the Runner yields it.
40
+ * 4. THE END. A paused walk stores the workflow's own record
41
+ * (workflowPauseEvent) after every node's event. A walk the turn
42
+ * stopped (InvocationAbortedError, or any failure once the signal
43
+ * fired) ends quietly, as ADK's Runner ends an aborted run: the turn
44
+ * runner reads the stop reason from its control. A node that gave up
45
+ * rethrows its error once every event is stored and yielded, as ADK's
46
+ * Runner throws it.
47
+ * 5. THE SPANS. `workflow.invoke <name>` around the walk, `node.execute
48
+ * <name>` around each node run (the scheduler's traceNode hook), and
49
+ * `tool.execute <name>` around a tool node's call
50
+ * (lib/runtime/native/telemetry.ts). An agent node's `agent.invoke`
51
+ * opens inside its node's span, so each model call is attributed to
52
+ * its node's agent in the ledger.
53
+ *
54
+ * WHAT IT REFUSES before any model call: refuseUnrunnableNodes throws, for
55
+ * a tool node, ADK's compile-time refusals (an unregistered tool, a
56
+ * long-running one). A pause raised inside an agent node is refused by
57
+ * runAgentNode by name.
58
+ *
59
+ * It imports nothing from ADK: the caller passes the tool lookup (the
60
+ * registry builds ADK's tools) and the store as the engine's interface.
61
+ * The message, the answers and every stored event are data the walk
62
+ * carries, never instructions this module acts on.
63
+ */
64
+ import { randomUUID } from 'node:crypto';
65
+ import { createTurnEvent } from '../runtime/events.js';
66
+ import { traceNodeExecution, traceToolNodeCall, traceWorkflowInvocation } from '../runtime/native/telemetry.js';
67
+ import { agentNodeRuntime } from './agentNode.js';
68
+ import { askUserNodeRunner, workflowPauseEvent } from './pause.js';
69
+ import { workflowResume } from './resume.js';
70
+ import { InvocationAbortedError, nodeErrorEvent, runWorkflowGraph } from './scheduler.js';
71
+ import { resolveToolNode, toolNodeRunner } from './toolNode.js';
72
+ /**
73
+ * Throws, before any model call, what ADK's compileWorkflow throws for a
74
+ * tool node: an unregistered tool, or a long-running one (ADR 0091).
75
+ */
76
+ export function refuseUnrunnableNodes(graph, resolveTool) {
77
+ for (const node of graph.nodes.values())
78
+ if (node.kind === 'tool')
79
+ resolveToolNode(node, { resolveTool });
80
+ }
81
+ const isAborted = (error) => error instanceof InvocationAbortedError || error?.name === 'InvocationAbortedError';
82
+ /**
83
+ * Runs one turn of the workflow: stores the message, walks the graph (a
84
+ * resume when the session holds a paused walk), and yields every event the
85
+ * walk stores, in the order stored, but the node inputs (ADK's Runner
86
+ * yields none of them). Returns how the walk ended, or
87
+ * undefined when the signal fired before the message was stored. Throws
88
+ * the error a node gave up with (after its events), ADK's message for a
89
+ * reply that answers nothing, and UnsupportedWorkflowResumeError.
90
+ */
91
+ export async function* runNativeWorkflow(params) {
92
+ const { graph, sessions, appName, userId, sessionId, signal } = params;
93
+ const session = await sessions.get({ appName, userId, sessionId });
94
+ if (!session)
95
+ throw new Error(`Session not found: ${sessionId} (appName=${appName}, userId=${userId})`);
96
+ if (signal?.aborted)
97
+ return undefined;
98
+ if (params.userParts.length === 0)
99
+ throw new Error('No parts in the newMessage.');
100
+ // 1. The message, as the Runner stores it.
101
+ const invocationId = `e-${randomUUID()}`;
102
+ const userContent = { role: 'user', parts: params.userParts };
103
+ await sessions.append(session, createTurnEvent({ invocationId, author: 'user', content: userContent }));
104
+ if (signal?.aborted)
105
+ return { stopped: true };
106
+ // 2. The start: the message's text as the input, and every node's prior runs.
107
+ const start = workflowResume({ events: session.events, invocationId, userContent, workflowPath: graph.name });
108
+ // 3. The walk, its events handed on in the order they are stored.
109
+ const queue = [];
110
+ let wake;
111
+ const runtime = agentNodeRuntime({
112
+ agents: params.agents,
113
+ session,
114
+ sessions,
115
+ invocationId,
116
+ userContent,
117
+ loop: {
118
+ adapterFor: params.adapterFor,
119
+ stream: params.stream ?? false,
120
+ ...(params.memory ? { memory: params.memory } : {}),
121
+ ...(params.log ? { log: params.log } : {}),
122
+ ...(params.selfCorrection ? { selfCorrection: params.selfCorrection } : {}),
123
+ ...(params.credentials ? { credentials: params.credentials } : {}),
124
+ },
125
+ onEvent: (event) => {
126
+ // A node's input turn is appended straight to the session, as ADK's runLlmAgentAsNode appends it: stored, never yielded.
127
+ if (event.author === 'user')
128
+ return;
129
+ queue.push(event);
130
+ wake?.();
131
+ },
132
+ // A node agent's streamed text, yielded as ADK's Runner yields its partial events.
133
+ onPartial: (event) => {
134
+ queue.push(event);
135
+ wake?.();
136
+ },
137
+ });
138
+ // Every event handed over is queued a microtask later (agentNodeRuntime.store); `stored` waits for all of them.
139
+ const handed = new Set();
140
+ const store = (event) => {
141
+ const queued = runtime.store(event).catch(() => { }); // a store error surfaces from settled()
142
+ handed.add(queued);
143
+ void queued.finally(() => handed.delete(queued));
144
+ };
145
+ const stored = async () => {
146
+ while (handed.size > 0)
147
+ await Promise.all([...handed]);
148
+ await runtime.settled();
149
+ };
150
+ const runNode = askUserNodeRunner({ invocationId, onEvent: store }, toolNodeRunner({
151
+ invocationId,
152
+ appName,
153
+ userId,
154
+ sessionId,
155
+ userContent,
156
+ resolveTool: params.resolveTool,
157
+ state: () => session.state,
158
+ ...(params.memory ? { memory: params.memory } : {}),
159
+ ...(params.credentials ? { credentials: params.credentials } : {}),
160
+ onEvent: store,
161
+ traceCall: traceToolNodeCall,
162
+ }, runtime.runNode));
163
+ const onEvent = (event) => {
164
+ runtime.onEvent(event);
165
+ // ADK's reportNodeError: the workflow's own event for a node that gave up.
166
+ if (event.type === 'node_error' && event.source === 'workflow')
167
+ store(nodeErrorEvent(event, invocationId));
168
+ };
169
+ let end;
170
+ let failure;
171
+ let finished = false;
172
+ const walk = traceWorkflowInvocation({ name: graph.name, path: graph.name }, { sessionId, invocationId }, async () => {
173
+ try {
174
+ const run = await runWorkflowGraph(graph, {
175
+ input: start.input,
176
+ resume: start.resume,
177
+ runNode,
178
+ onEvent,
179
+ traceNode: traceNodeExecution,
180
+ ...(signal ? { signal } : {}),
181
+ });
182
+ // 4. A paused walk: the workflow's own record, after every node's event.
183
+ if (run.interruptIds.length > 0)
184
+ store(workflowPauseEvent({ name: graph.name, invocationId, input: start.input, interruptIds: run.interruptIds }));
185
+ await stored();
186
+ return run;
187
+ }
188
+ catch (error) {
189
+ await stored().catch(() => { });
190
+ throw error;
191
+ }
192
+ }).then((run) => {
193
+ end = { run };
194
+ }, (error) => {
195
+ // ADK's Runner ends an aborted run without an error; the turn's control holds why.
196
+ if (isAborted(error) || signal?.aborted)
197
+ end = { stopped: true };
198
+ else
199
+ failure = { error };
200
+ }).finally(() => {
201
+ finished = true;
202
+ wake?.();
203
+ });
204
+ for (;;) {
205
+ while (queue.length > 0)
206
+ yield queue.shift();
207
+ if (finished)
208
+ break;
209
+ await new Promise((resolve) => (wake = resolve));
210
+ wake = undefined;
211
+ }
212
+ await walk;
213
+ if (failure)
214
+ throw failure.error;
215
+ return end;
216
+ }
@@ -69,9 +69,10 @@ export declare function nodeSettings(node: WorkflowNodeYaml | undefined): {
69
69
  /**
70
70
  * The route an output names: the `routeKey` property of an object (a parsed
71
71
  * JSON output), else the trimmed text. Never undefined, so an edge with a
72
- * `default` key always has something to fall back from.
72
+ * `default` key always has something to fall back from. One function for
73
+ * both runtimes; it lives in lib/workflow/route.ts.
73
74
  */
74
- export declare function routeOf(output: unknown, routeKey?: string): string;
75
+ export { routeOf } from './workflow/route.ts';
75
76
  /** A pause raised by an `ask_user` node, read from the run's events (`adk_request_input`). */
76
77
  export interface PendingInput {
77
78
  /** The interrupt id. A plain-text next message answers it. */
@@ -57,19 +57,10 @@ export function nodeSettings(node) {
57
57
  /**
58
58
  * The route an output names: the `routeKey` property of an object (a parsed
59
59
  * JSON output), else the trimmed text. Never undefined, so an edge with a
60
- * `default` key always has something to fall back from.
60
+ * `default` key always has something to fall back from. One function for
61
+ * both runtimes; it lives in lib/workflow/route.ts.
61
62
  */
62
- export function routeOf(output, routeKey = 'route') {
63
- if (output && typeof output === 'object' && !Array.isArray(output)) {
64
- const value = output[routeKey];
65
- if (value === undefined || value === null)
66
- return '';
67
- return String(value).trim();
68
- }
69
- if (typeof output === 'string')
70
- return output.trim();
71
- return output === undefined || output === null ? '' : String(output).trim();
72
- }
63
+ export { routeOf } from './workflow/route.js';
73
64
  /** ADK's function-call name for a workflow input request. */
74
65
  export const INPUT_REQUEST = 'adk_request_input';
75
66
  /** The request an `adk_request_input` call carries, or undefined for another call. */
@@ -25,7 +25,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
25
25
  * with A2A_AUTH=callers it stays valid beside the caller
26
26
  * tokens, with its old scoping, until you remove it
27
27
  * A2A_AUTH who a caller is (ADR 0025): secret (default) | callers |
28
- * jwt | header
28
+ * jwt | header (required with PUBLIC_URL, ADR 0039)
29
29
  * A2A_CALLERS callers: `name:sha256[:scope]; …` — one token per calling
30
30
  * backend. Mint: melchizedek-serve --new-caller <name>
31
31
  * A2A_JWT_JWKS_URL jwt: your identity provider's JWKS (or A2A_JWT_SECRET, HS256)
@@ -39,20 +39,25 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
39
39
  * ALLOW_UNHARDENED_DB "true" to accept an unhardened Supabase schema
40
40
  * A2A_TASK_TIMEOUT_MS per-task wall-clock budget (900000; 0 = none)
41
41
  * A2A_MAX_CONCURRENT_TASKS concurrent tasks across all agents (0 = unlimited)
42
+ * A2A_MAX_CONCURRENT_PER_SCOPE concurrent tasks for one end user (4; 0 = unlimited)
43
+ * A2A_MAX_CONCURRENT_PER_CALLER concurrent tasks for one caller (0 = unlimited)
42
44
  * A2A_RATE_LIMIT_MAX task submissions per window per IP (60)
43
45
  * A2A_RATE_LIMIT_WINDOW_MS rate-limit window (900000)
44
46
  * A2A_AUTH_FAILURE_MAX failed authentications per window per IP (30)
45
- * A2A_TRUST_PROXY Express trust-proxy: hop count, true/false, or subnets (1)
47
+ * A2A_TRUST_PROXY Express trust-proxy: hop count, true/false, or subnets
48
+ * (false; required with PUBLIC_URL)
46
49
  * A2A_BODY_LIMIT JSON body limit ("1mb")
47
50
  * A2A_KEY_MODE server (default): models run on the server's keys;
48
51
  * byok: the caller's X-API-Key pays. Under A2A_AUTH=secret
49
52
  * the mode also scopes data: X-User-Id in server mode,
50
53
  * the key's hash in byok (the pre-0.16 behaviour)
51
54
  * A2A_SERVED_AGENTS comma list: the only agent ids /:agentId/ serves;
52
- * only these may fall back to examples/ and templates/
55
+ * only these may fall back to examples/ and templates/;
56
+ * * serves every one (required with PUBLIC_URL)
53
57
  * A2A_REGISTRY_AGENTS comma list: bare ids that load from adk_agent_registry
54
58
  * (others are files; registry:<id> always is the registry)
55
59
  * A2A_SHUTDOWN_GRACE_MS how long SIGTERM waits for running tasks (25000)
60
+ * A2A_SHUTDOWN_DELAY_MS of that, how long /readyz fails while requests are still served (5000)
56
61
  * A2A_BUDGETS daily budgets per UTC day (ADR 0026), JSON:
57
62
  * {"perCaller":{"tasks":500,"llmCalls":5000,"tokens":5000000},
58
63
  * "callers":{"reports":{"tasks":100}},"perScope":{"tasks":50}}.
@@ -74,7 +79,6 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
74
79
  import { randomBytes } from 'node:crypto';
75
80
  import { realpathSync, writeFileSync } from 'node:fs';
76
81
  import { pathToFileURL } from 'node:url';
77
- import { setLogLevel, LogLevel } from '@google/adk';
78
82
  import { createA2AApp } from '../lib/a2a/app.js';
79
83
  import { callerTokens, firstOf, hashCallerToken, jwtIdentity, parseCallers, sharedSecret, trustedHeader, } from '../lib/a2a/identity.js';
80
84
  import { budgets, memoryUsageStore, parseBudgets, postgresUsageStore, supabaseUsageStore } from '../lib/a2a/policy.js';
@@ -85,6 +89,7 @@ import { dbSchema } from '../lib/storage/schema.js';
85
89
  import { postgresStorage } from '../lib/storage/postgres/index.js';
86
90
  import { isPlaceholderValue, loadEnv } from '../lib/loadEnv.js';
87
91
  import { flushTracing } from '../lib/observability/tracer.js';
92
+ import { setLogLevel } from '../lib/runtime/logging.js';
88
93
  // Re-exported for existing importers (tests/failNarration.test.ts).
89
94
  export { describeTurnError } from '../lib/a2a/executor.js';
90
95
  /** Minimum bearer-secret length below which the server warns. */
@@ -101,8 +106,10 @@ function envInt(name, fallback) {
101
106
  }
102
107
  function envTrustProxy() {
103
108
  const raw = process.env.A2A_TRUST_PROXY?.trim();
109
+ // Unset: trust no proxy, so a client cannot choose its own IP with
110
+ // X-Forwarded-For. A public server must set it (the posture check below).
104
111
  if (!raw)
105
- return 1;
112
+ return false;
106
113
  if (/^\d+$/.test(raw))
107
114
  return Number(raw);
108
115
  if (raw === 'true')
@@ -131,8 +138,9 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
131
138
  // the root span carries the full user message and answer.
132
139
  if (process.env.OTEL_CONSOLE_SPANS === undefined)
133
140
  process.env.OTEL_CONSOLE_SPANS = 'false';
134
- // ADK's INFO/DEBUG logs carry raw event JSON; keep them quiet.
135
- setLogLevel(LogLevel.WARN);
141
+ // The engine's level, which ADK's logger follows: ADK's INFO/DEBUG logs
142
+ // carry raw event JSON, so keep them quiet.
143
+ setLogLevel('warn');
136
144
  const publicUrl = process.env.PUBLIC_URL?.trim() || undefined;
137
145
  const port = envInt('PORT', 4000);
138
146
  // ── Authentication posture ─────────────────────────────────────────────────
@@ -219,7 +227,28 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
219
227
  }
220
228
  return n;
221
229
  };
222
- const servedAgents = list('A2A_SERVED_AGENTS');
230
+ // `*` serves every file in the agents directory and every registry row,
231
+ // stated on purpose; unset means the same off a public URL (ADR 0039).
232
+ const servedList = list('A2A_SERVED_AGENTS');
233
+ const servedAgents = servedList?.length === 1 && servedList[0] === '*' ? undefined : servedList;
234
+ // ── Public posture (ADR 0039) ──────────────────────────────────────────────
235
+ // A server on a public URL states how it authenticates, which agents it
236
+ // answers, and how many proxies stand in front of it. Each default was the
237
+ // permissive choice, so none is taken silently here.
238
+ if (publicUrl) {
239
+ const missing = [];
240
+ if (!process.env.A2A_AUTH?.trim()) {
241
+ missing.push('A2A_AUTH: callers or jwt (one identity per caller; recommended), header (a gateway authenticates users), or secret (one shared secret; any holder can act as any user by naming them in X-User-Id)');
242
+ }
243
+ if (!servedList?.length) {
244
+ missing.push('A2A_SERVED_AGENTS: the agent ids this server answers, comma-separated, or * for every file in the agents directory and every registry row');
245
+ }
246
+ if (!process.env.A2A_TRUST_PROXY?.trim()) {
247
+ missing.push('A2A_TRUST_PROXY: how many proxies stand in front of this server (1 behind one load balancer), or false when clients connect directly');
248
+ }
249
+ if (missing.length)
250
+ fatal(`PUBLIC_URL is set, so this server must state its security posture. Set:\n - ${missing.join('\n - ')}`);
251
+ }
223
252
  const registryAgents = list('A2A_REGISTRY_AGENTS');
224
253
  // Storage: Postgres when DATABASE_URL is set (every durable store, safe
225
254
  // across instances); otherwise createA2AApp's default (Supabase, else memory).
@@ -305,6 +334,8 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
305
334
  allowSchemaMismatch: process.env.ALLOW_SCHEMA_MISMATCH?.trim().toLowerCase() === 'true',
306
335
  turnLockWaitMs: envInt('A2A_TURN_LOCK_WAIT_MS', 30_000),
307
336
  maxConcurrentTasks: envInt('A2A_MAX_CONCURRENT_TASKS', 0),
337
+ maxConcurrentPerScope: envInt('A2A_MAX_CONCURRENT_PER_SCOPE', 4),
338
+ maxConcurrentPerCaller: envInt('A2A_MAX_CONCURRENT_PER_CALLER', 0),
308
339
  rateLimit: { windowMs: envInt('A2A_RATE_LIMIT_WINDOW_MS', 15 * 60 * 1000), max: envInt('A2A_RATE_LIMIT_MAX', 60) },
309
340
  authFailureLimit: { windowMs: 15 * 60 * 1000, max: envInt('A2A_AUTH_FAILURE_MAX', 30) },
310
341
  trustProxy: envTrustProxy(),
@@ -334,7 +365,7 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
334
365
  const authMode = `${identityLabel || (secret ? 'bearer secret' : 'no secret (loopback only)')}; keys: ${keyMode === 'byok' ? "caller's X-API-Key (byok)" : "server's own"}`;
335
366
  // The secret is not needed past this point; drop the reference.
336
367
  secret = undefined;
337
- const { app, config, sessionBackend, shutdown } = built;
368
+ const { app, config, sessionBackend, shutdown, markUnready } = built;
338
369
  const server = await new Promise((resolve) => {
339
370
  const s = host ? app.listen(port, host, () => resolve(s)) : app.listen(port, () => resolve(s));
340
371
  });
@@ -364,13 +395,26 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
364
395
  // ── Lifecycle ──────────────────────────────────────────────────────────────
365
396
  let stopping = false;
366
397
  const graceMs = envInt('A2A_SHUTDOWN_GRACE_MS', 25_000);
398
+ // A load balancer keeps sending requests for a few seconds after SIGTERM,
399
+ // until it sees /readyz fail and deregisters this instance. Closing the
400
+ // listener at once would refuse those requests; fail readiness first and
401
+ // keep serving for the delay, then close and drain. The delay is part of
402
+ // the grace budget (running tasks keep running through it), so a stop still
403
+ // takes at most A2A_SHUTDOWN_GRACE_MS, inside Kubernetes' default 30 s.
404
+ const delayMs = Math.min(envInt('A2A_SHUTDOWN_DELAY_MS', 5_000), graceMs);
367
405
  const stop = async (signal) => {
368
406
  if (stopping)
369
407
  return;
370
408
  stopping = true;
371
- emit('info', `[A2A] ${signal}: draining — no new tasks; waiting up to ${graceMs} ms for running ones.`);
409
+ markUnready();
410
+ if (delayMs > 0) {
411
+ emit('info', `[A2A] ${signal}: /readyz now fails; still serving for ${delayMs} ms while the load balancer deregisters this instance.`);
412
+ await new Promise((r) => setTimeout(r, delayMs));
413
+ }
414
+ const drainMs = graceMs - delayMs;
415
+ emit('info', `[A2A] ${signal}: draining — no new tasks; waiting up to ${drainMs} ms for running ones.`);
372
416
  server.close();
373
- const canceled = await shutdown(graceMs);
417
+ const canceled = await shutdown(drainMs);
374
418
  if (canceled > 0)
375
419
  emit('warn', `[A2A] ⚠ ${canceled} task(s) did not finish in time and were canceled.`);
376
420
  // Give canceled tasks a moment to publish their final status, then flush
@@ -13,7 +13,8 @@
13
13
  * and writes the result (or the error) back to the store, where
14
14
  * `task_get` reads it in a later conversation.
15
15
  *
16
- * A job runs through lib/runtime/syndicateTurn.ts, the same runtime the
16
+ * A job runs through lib/runtime/syndicateTurn.ts (on the runtime
17
+ * MELCHIZEDEK_RUNTIME names, ADK by default), the same runtime the
17
18
  * A2A server, the REPL and the observatory use, so it runs the exact agent
18
19
  * the conversation would have called directly — with the same step cap —
19
20
  * and the job timeout ABORTS the run (the model call in flight included)
@@ -13,7 +13,8 @@
13
13
  * and writes the result (or the error) back to the store, where
14
14
  * `task_get` reads it in a later conversation.
15
15
  *
16
- * A job runs through lib/runtime/syndicateTurn.ts, the same runtime the
16
+ * A job runs through lib/runtime/syndicateTurn.ts (on the runtime
17
+ * MELCHIZEDEK_RUNTIME names, ADK by default), the same runtime the
17
18
  * A2A server, the REPL and the observatory use, so it runs the exact agent
18
19
  * the conversation would have called directly — with the same step cap —
19
20
  * and the job timeout ABORTS the run (the model call in flight included)
@@ -31,17 +32,19 @@
31
32
  * and must match the chat process's value. Run ONE worker per store.
32
33
  */
33
34
  import { randomUUID } from 'node:crypto';
34
- import { InMemorySessionService, LogLevel, setLogLevel } from '@google/adk';
35
35
  import { loadEnv } from '../lib/loadEnv.js';
36
36
  import { loadSyndicate } from '../lib/loadSyndicate.js';
37
37
  import { runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
38
+ import { asAdkSessionService } from '../lib/runtime/adkSessionBridge.js';
39
+ import { setLogLevel } from '../lib/runtime/logging.js';
40
+ import { InProcessSessionService } from '../lib/runtime/sessions.js';
38
41
  import { PROVIDERS, providerForModel, providerKeyPresent, registerAvailableProviders, } from '../lib/models/registry.js';
39
42
  import { getTaskBackend, setTaskBackend, taskStorePath } from '../lib/tools/taskTools.js';
40
43
  import { postgresStorage } from '../lib/storage/postgres/index.js';
41
44
  import { dbSchema } from '../lib/storage/schema.js';
42
45
  import { hostname } from 'node:os';
43
46
  loadEnv(import.meta.url);
44
- setLogLevel(LogLevel.WARN);
47
+ setLogLevel('warn');
45
48
  registerAvailableProviders();
46
49
  // ── CLI ──────────────────────────────────────────────────────────────────────
47
50
  const argv = process.argv.slice(2).filter((a) => a !== '--');
@@ -111,7 +114,9 @@ async function runJob(job) {
111
114
  appName: 'assistant-worker',
112
115
  userId: 'local-user',
113
116
  sessionId: randomUUID(),
114
- sessionService: new InMemorySessionService(),
117
+ // A fresh in-process store per job, as the turn runner's signature
118
+ // names it (ADR 0080). The runtime follows MELCHIZEDEK_RUNTIME.
119
+ sessionService: asAdkSessionService(new InProcessSessionService()),
115
120
  compile: { log, onUnknownTool: (n) => log(`unknown tool '${n}' skipped`) },
116
121
  signal: current.signal,
117
122
  deadlineMs: JOB_TIMEOUT_MS,
@@ -22,5 +22,14 @@
22
22
  /** db/migrations/NNNN_name.sql, in numeric order. */
23
23
  export declare function migrationFiles(dir?: string): string[];
24
24
  export declare function installOrder(withTelemetry: boolean): string[];
25
+ /**
26
+ * The whole install as one psql script: an advisory lock first, then every
27
+ * file in order with a progress line before each. psql runs it with
28
+ * --single-transaction and ON_ERROR_STOP, so a failure anywhere rolls the
29
+ * whole install back (no half-applied migration), and a second `apply`
30
+ * started meanwhile waits on the lock instead of interleaving with this one.
31
+ */
32
+ export declare const APPLY_LOCK_KEY = "melchizedek-db apply";
33
+ export declare function applyScript(files: string[], sqlOf: (file: string) => string, label?: (file: string) => string): string;
25
34
  /** postgres://user:pass@host:port/db?sslmode=… → libpq environment variables. */
26
35
  export declare function pgEnv(url: string): Record<string, string>;
@@ -42,6 +42,20 @@ export function installOrder(withTelemetry) {
42
42
  const base = [...migrationFiles(), 'hardening.sql'];
43
43
  return withTelemetry ? [...base, 'telemetry.sql', 'hardening.sql'] : base;
44
44
  }
45
+ /**
46
+ * The whole install as one psql script: an advisory lock first, then every
47
+ * file in order with a progress line before each. psql runs it with
48
+ * --single-transaction and ON_ERROR_STOP, so a failure anywhere rolls the
49
+ * whole install back (no half-applied migration), and a second `apply`
50
+ * started meanwhile waits on the lock instead of interleaving with this one.
51
+ */
52
+ export const APPLY_LOCK_KEY = 'melchizedek-db apply';
53
+ export function applyScript(files, sqlOf, label = (f) => `db/${f}`) {
54
+ const parts = [`SELECT pg_advisory_xact_lock(hashtext('${APPLY_LOCK_KEY}'));\n`];
55
+ for (const file of files)
56
+ parts.push(`\\echo '→ applying ${label(file).replace(/'/g, '')}'\n`, sqlOf(file), '\n');
57
+ return parts.join('');
58
+ }
45
59
  /** postgres://user:pass@host:port/db?sslmode=… → libpq environment variables. */
46
60
  export function pgEnv(url) {
47
61
  const u = new URL(url);
@@ -100,21 +114,22 @@ async function main() {
100
114
  console.error(' or set DATABASE_URL to the Postgres connection string (Supabase: Settings → Database).');
101
115
  process.exit(1);
102
116
  }
103
- for (const file of installOrder(withTelemetry)) {
104
- console.log(`→ applying db/${file}${schema === 'public' ? '' : ` into schema ${schema}`}`);
105
- const r = spawnSync('psql', ['-v', 'ON_ERROR_STOP=1', '-q', '-f', '-'], {
106
- input: sqlOf(file),
107
- stdio: ['pipe', 'inherit', 'inherit'],
108
- // The connection goes to psql as PG* variables, never as an
109
- // argument, so the password stays out of the process list.
110
- env: { ...process.env, ...pgEnv(url) },
111
- });
112
- if (r.error) {
113
- console.error(`✗ could not run psql (${r.error.message}). Install the Postgres client, or use \`print\`.`);
114
- process.exit(1);
115
- }
116
- if (r.status !== 0)
117
- process.exit(r.status ?? 1);
117
+ // One transaction under one lock (see applyScript): all of it, or none.
118
+ const script = applyScript(installOrder(withTelemetry), sqlOf, (f) => `db/${f}${schema === 'public' ? '' : ` into schema ${schema}`}`);
119
+ const r = spawnSync('psql', ['-v', 'ON_ERROR_STOP=1', '-q', '--single-transaction', '-f', '-'], {
120
+ input: script,
121
+ stdio: ['pipe', 'inherit', 'inherit'],
122
+ // The connection goes to psql as PG* variables, never as an
123
+ // argument, so the password stays out of the process list.
124
+ env: { ...process.env, ...pgEnv(url) },
125
+ });
126
+ if (r.error) {
127
+ console.error(`✗ could not run psql (${r.error.message}). Install the Postgres client, or use \`print\`.`);
128
+ process.exit(1);
129
+ }
130
+ if (r.status !== 0) {
131
+ console.error('✗ the install failed and was rolled back: the database is as it was before `apply`.');
132
+ process.exit(r.status ?? 1);
118
133
  }
119
134
  console.log('✓ database is installed and hardened');
120
135
  return;
@@ -5,7 +5,11 @@ import { join } from 'node:path';
5
5
  import { loadSyndicate, parseCliBindings } from '../lib/loadSyndicate.js';
6
6
  import { isDispatchSyndicate } from '../lib/dispatch.js';
7
7
  import { approvalResponsePart, describeApproval, describeInput, ingestTurnMemory, runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
8
- import { InMemorySessionService, getFunctionCalls, getFunctionResponses, setLogLevel, LogLevel, } from '@google/adk';
8
+ import { getFunctionCalls, getFunctionResponses } from '../lib/runtime/events.js';
9
+ import { InProcessSessionService } from '../lib/runtime/sessions.js';
10
+ import { asAdkSessionService, asSessionService } from '../lib/runtime/adkSessionBridge.js';
11
+ import { asAdkMemoryService } from '../lib/runtime/adkMemoryBridge.js';
12
+ import { setLogLevel } from '../lib/runtime/logging.js';
9
13
  import { randomUUID } from 'node:crypto';
10
14
  import { loadEnv } from '../lib/loadEnv.js';
11
15
  // ── LLM Provider Registration ─────────────────────────────────────────────────
@@ -18,8 +22,8 @@ import { registerAvailableProviders, providerForModel, providerKeyPresent, PROVI
18
22
  // ── Persistence Factory ───────────────────────────────────────────────────────
19
23
  // Supabase-specific initialization lives in supabaseProvider.ts.
20
24
  import { hasSupabaseCredentials, createSupabaseServices, } from '../lib/persistence/supabaseProvider.js';
21
- // Silence ADK verbose INFO logging natively
22
- setLogLevel(LogLevel.WARN);
25
+ // The engine's level, which ADK's logger follows: no INFO chatter in the chat.
26
+ setLogLevel('warn');
23
27
  const c = {
24
28
  reset: '\x1b[0m',
25
29
  bold: '\x1b[1m',
@@ -175,7 +179,10 @@ async function main() {
175
179
  // The same services the server would use for this syndicate's
176
180
  // memory_system: Supabase sessions (and the memory service for
177
181
  // long-term) when configured, process memory otherwise.
178
- let sessionService = new InMemorySessionService();
182
+ // The engine's stores (ADR 0080): the in-process one, or Supabase's,
183
+ // which has both faces. The turn runner takes ADK's face (its signature
184
+ // is fixed), and follows MELCHIZEDEK_RUNTIME for the runtime.
185
+ let sessionService = new InProcessSessionService();
179
186
  let memoryService;
180
187
  if (persistence.sessionService === 'supabase') {
181
188
  const services = await createSupabaseServices({
@@ -188,7 +195,9 @@ async function main() {
188
195
  memoryService = services.memoryService;
189
196
  }
190
197
  const appName = config.syndicate_name || 'melchizedek-syndicate';
191
- await sessionService.createSession({ appName, userId: SESSION_USER_ID, sessionId: SESSION_ID, state: {} });
198
+ await asSessionService(sessionService).create({ appName, userId: SESSION_USER_ID, sessionId: SESSION_ID, state: {} });
199
+ const turnSessions = asAdkSessionService(sessionService);
200
+ const turnMemory = memoryService ? asAdkMemoryService(memoryService) : undefined;
192
201
  banner(config, persistence, mergedBindings, SESSION_ID);
193
202
  // Plan-dispatch, nested yaml_reference syndicates, guards and the
194
203
  // max_steps cap run here exactly as the A2A server runs them: both call
@@ -227,8 +236,8 @@ async function main() {
227
236
  appName,
228
237
  userId: SESSION_USER_ID,
229
238
  sessionId: SESSION_ID,
230
- sessionService,
231
- memoryService,
239
+ sessionService: turnSessions,
240
+ memoryService: turnMemory,
232
241
  compile: {
233
242
  onUnknownTool: (name) => console.warn(`${c.yellow}⚠ Unknown tool: '${name}' — skipping.${c.reset}`),
234
243
  log: (message) => console.log(`${c.dim} ${message}${c.reset}`),
@@ -276,12 +285,12 @@ async function main() {
276
285
  }
277
286
  }
278
287
  const ingest = async () => {
279
- if (!memoryService)
288
+ if (!turnMemory)
280
289
  return;
281
290
  try {
282
291
  const ingested = await ingestTurnMemory({
283
- memoryService,
284
- sessionService,
292
+ memoryService: turnMemory,
293
+ sessionService: turnSessions,
285
294
  appName,
286
295
  userId: SESSION_USER_ID,
287
296
  sessionId: SESSION_ID,