melchizedek-agents 0.9.6 → 0.16.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 (246) hide show
  1. package/.env.example +272 -0
  2. package/AGENT_SETUP.md +39 -23
  3. package/CHANGELOG.md +623 -0
  4. package/DOCUMENTATION.md +429 -109
  5. package/QUICKSTART.md +92 -18
  6. package/README.md +45 -5
  7. package/config/agents/README.md +46 -1
  8. package/config/agents/examples/ares.yaml +117 -40
  9. package/config/agents/examples/assistant.yaml +159 -0
  10. package/config/agents/examples/augustin.yaml +154 -51
  11. package/config/agents/examples/cartographers.yaml +91 -23
  12. package/config/agents/examples/claude.yaml +45 -6
  13. package/config/agents/examples/council.yaml +23 -8
  14. package/config/agents/examples/critic.yaml +57 -21
  15. package/config/agents/examples/delegation.yaml +76 -9
  16. package/config/agents/examples/hierarchical.yaml +55 -8
  17. package/config/agents/examples/image_production.yaml +54 -16
  18. package/config/agents/examples/librarian.yaml +70 -14
  19. package/config/agents/examples/model_zoo.yaml +105 -29
  20. package/config/agents/examples/patient_advocate.yaml +102 -34
  21. package/config/agents/examples/research.yaml +250 -0
  22. package/config/agents/examples/scribe.yaml +242 -0
  23. package/config/agents/examples/scriptorium.yaml +87 -24
  24. package/config/agents/examples/style_council.yaml +31 -9
  25. package/config/agents/examples/syndicate.yaml +29 -10
  26. package/config/agents/examples/tutor.yaml +10 -3
  27. package/config/agents/syndicateSchema.yaml +44 -9
  28. package/config/agents/templates/README.md +58 -0
  29. package/config/agents/templates/account_memory.yaml +153 -0
  30. package/config/agents/templates/case_desk.yaml +188 -0
  31. package/config/agents/templates/conversational.yaml +110 -0
  32. package/config/agents/templates/draft_review.yaml +130 -0
  33. package/config/agents/templates/intake_extractor.yaml +152 -0
  34. package/config/agents/templates/research_brief.yaml +169 -0
  35. package/config/agents/templates/research_desk.yaml +152 -0
  36. package/config/agents/templates/review_panel.yaml +188 -0
  37. package/config/agents/templates/support_triage.yaml +273 -0
  38. package/config/agents/templates/systems_operator.yaml +157 -0
  39. package/db/hardening.sql +202 -0
  40. package/db/memory_v2.sql +77 -0
  41. package/db/migrations/0001_base.sql +167 -0
  42. package/db/migrations/0002_erase_scope.sql +172 -0
  43. package/db/migrations/0003_postgres_storage.sql +58 -0
  44. package/db/migrations/0004_usage.sql +94 -0
  45. package/db/migrations/0005_agent_registry.sql +200 -0
  46. package/db/migrations/0006_task_leases.sql +25 -0
  47. package/db/migrations/0007_memory_commit.sql +134 -0
  48. package/db/migrations/0008_memory_retention.sql +37 -0
  49. package/db/migrations/0009_task_queue.sql +51 -0
  50. package/db/migrations/0010_erase_complete.sql +180 -0
  51. package/db/telemetry.sql +401 -0
  52. package/demo/README.md +77 -0
  53. package/demo/a2a_demo.mjs +95 -0
  54. package/dist/lib/a2a/app.d.ts +253 -0
  55. package/dist/lib/a2a/app.js +751 -0
  56. package/dist/lib/a2a/executor.d.ts +167 -0
  57. package/dist/lib/a2a/executor.js +548 -0
  58. package/dist/lib/a2a/identity.d.ts +122 -0
  59. package/dist/lib/a2a/identity.js +254 -0
  60. package/dist/lib/a2a/policy.d.ts +110 -0
  61. package/dist/lib/a2a/policy.js +178 -0
  62. package/dist/lib/a2a/redisLimits.d.ts +26 -0
  63. package/dist/lib/a2a/redisLimits.js +31 -0
  64. package/dist/lib/a2a/remoteAgent.d.ts +91 -0
  65. package/dist/lib/a2a/remoteAgent.js +268 -0
  66. package/dist/lib/a2a/turnLock.d.ts +28 -0
  67. package/dist/lib/a2a/turnLock.js +76 -0
  68. package/dist/lib/compile.d.ts +11 -1
  69. package/dist/lib/compile.js +109 -8
  70. package/dist/lib/config.d.ts +7 -7
  71. package/dist/lib/config.js +7 -7
  72. package/dist/lib/doctor.d.ts +145 -0
  73. package/dist/lib/doctor.js +517 -0
  74. package/dist/lib/grounding.d.ts +2 -2
  75. package/dist/lib/grounding.js +2 -2
  76. package/dist/lib/guards/index.d.ts +37 -0
  77. package/dist/lib/guards/index.js +66 -0
  78. package/dist/lib/guards/science.d.ts +117 -0
  79. package/dist/lib/guards/science.js +444 -0
  80. package/dist/lib/index.d.ts +37 -2
  81. package/dist/lib/index.js +35 -4
  82. package/dist/lib/loadEnv.d.ts +23 -11
  83. package/dist/lib/loadEnv.js +77 -29
  84. package/dist/lib/loadSyndicate.d.ts +97 -5
  85. package/dist/lib/loadSyndicate.js +176 -76
  86. package/dist/lib/memory/erase.d.ts +24 -0
  87. package/dist/lib/memory/erase.js +47 -0
  88. package/dist/lib/memory/namespace.d.ts +37 -0
  89. package/dist/lib/memory/namespace.js +74 -0
  90. package/dist/lib/memory/providers.d.ts +99 -0
  91. package/dist/lib/memory/providers.js +240 -0
  92. package/dist/lib/memory/store.d.ts +74 -0
  93. package/dist/lib/memory/store.js +108 -0
  94. package/dist/lib/memory/supabaseMemoryService.d.ts +69 -9
  95. package/dist/lib/memory/supabaseMemoryService.js +239 -125
  96. package/dist/lib/models/capabilities.d.ts +103 -0
  97. package/dist/lib/models/capabilities.js +291 -0
  98. package/dist/lib/models/claudeLlm.d.ts +10 -1
  99. package/dist/lib/models/claudeLlm.js +57 -27
  100. package/dist/lib/models/endpoints.d.ts +100 -0
  101. package/dist/lib/models/endpoints.js +282 -0
  102. package/dist/lib/models/gateway.d.ts +107 -0
  103. package/dist/lib/models/gateway.js +157 -0
  104. package/dist/lib/models/gatewayLlm.d.ts +57 -0
  105. package/dist/lib/models/gatewayLlm.js +105 -0
  106. package/dist/lib/models/gptLlm.d.ts +51 -2
  107. package/dist/lib/models/gptLlm.js +169 -26
  108. package/dist/lib/models/grokLlm.d.ts +19 -10
  109. package/dist/lib/models/grokLlm.js +29 -14
  110. package/dist/lib/models/ollamaLlm.d.ts +26 -0
  111. package/dist/lib/models/ollamaLlm.js +43 -1
  112. package/dist/lib/models/openAiCompatibleLlm.d.ts +67 -2
  113. package/dist/lib/models/openAiCompatibleLlm.js +200 -37
  114. package/dist/lib/models/providerMap.js +9 -2
  115. package/dist/lib/models/registry.d.ts +42 -5
  116. package/dist/lib/models/registry.js +140 -29
  117. package/dist/lib/models/retry.d.ts +146 -0
  118. package/dist/lib/models/retry.js +305 -0
  119. package/dist/lib/models/schemaNormalize.d.ts +20 -0
  120. package/dist/lib/models/schemaNormalize.js +43 -0
  121. package/dist/lib/net/addressGuard.d.ts +60 -0
  122. package/dist/lib/net/addressGuard.js +216 -0
  123. package/dist/lib/observability/embeddings.d.ts +11 -2
  124. package/dist/lib/observability/embeddings.js +18 -14
  125. package/dist/lib/observability/metrics.d.ts +36 -0
  126. package/dist/lib/observability/metrics.js +86 -0
  127. package/dist/lib/observability/redact.d.ts +29 -0
  128. package/dist/lib/observability/redact.js +102 -0
  129. package/dist/lib/observability/supabaseSpanExporter.d.ts +2 -0
  130. package/dist/lib/observability/supabaseSpanExporter.js +22 -6
  131. package/dist/lib/observability/tracer.d.ts +10 -0
  132. package/dist/lib/observability/tracer.js +70 -3
  133. package/dist/lib/persistence/supabaseProvider.d.ts +11 -0
  134. package/dist/lib/persistence/supabaseProvider.js +32 -6
  135. package/dist/lib/registry.d.ts +87 -0
  136. package/dist/lib/registry.js +146 -0
  137. package/dist/lib/runtime/approvals.d.ts +44 -0
  138. package/dist/lib/runtime/approvals.js +67 -0
  139. package/dist/lib/runtime/syndicateTurn.d.ts +233 -0
  140. package/dist/lib/runtime/syndicateTurn.js +597 -0
  141. package/dist/lib/runtime/turnControl.d.ts +90 -0
  142. package/dist/lib/runtime/turnControl.js +131 -0
  143. package/dist/lib/session/supabaseSessionService.js +1 -1
  144. package/dist/lib/session/transcript.d.ts +9 -1
  145. package/dist/lib/session/transcript.js +15 -7
  146. package/dist/lib/skills.d.ts +50 -0
  147. package/dist/lib/skills.js +138 -0
  148. package/dist/lib/storage/postgres/index.d.ts +98 -0
  149. package/dist/lib/storage/postgres/index.js +146 -0
  150. package/dist/lib/storage/postgres/memoryStore.d.ts +11 -0
  151. package/dist/lib/storage/postgres/memoryStore.js +82 -0
  152. package/dist/lib/storage/postgres/sessionService.d.ts +51 -0
  153. package/dist/lib/storage/postgres/sessionService.js +225 -0
  154. package/dist/lib/storage/postgres/taskQueue.d.ts +22 -0
  155. package/dist/lib/storage/postgres/taskQueue.js +101 -0
  156. package/dist/lib/storage/postgres/taskStore.d.ts +55 -0
  157. package/dist/lib/storage/postgres/taskStore.js +168 -0
  158. package/dist/lib/storage/schema.d.ts +27 -0
  159. package/dist/lib/storage/schema.js +50 -0
  160. package/dist/lib/storage/schemaVersion.d.ts +25 -0
  161. package/dist/lib/storage/schemaVersion.js +46 -0
  162. package/dist/lib/syndicateSchema.d.ts +275 -0
  163. package/dist/lib/syndicateSchema.js +534 -0
  164. package/dist/lib/toolRegistry.d.ts +15 -0
  165. package/dist/lib/toolRegistry.js +59 -10
  166. package/dist/lib/tools/collectionsSearchTool.js +1 -1
  167. package/dist/lib/tools/mcpServe.d.ts +45 -0
  168. package/dist/lib/tools/mcpServe.js +88 -0
  169. package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
  170. package/dist/lib/tools/mcpToolFactory.js +41 -21
  171. package/dist/lib/tools/science/corrections.d.ts +42 -0
  172. package/dist/lib/tools/science/corrections.js +115 -0
  173. package/dist/lib/tools/science/europepmc.d.ts +51 -0
  174. package/dist/lib/tools/science/europepmc.js +127 -0
  175. package/dist/lib/tools/science/http.d.ts +31 -0
  176. package/dist/lib/tools/science/http.js +104 -0
  177. package/dist/lib/tools/science/openalex.d.ts +30 -0
  178. package/dist/lib/tools/science/openalex.js +81 -0
  179. package/dist/lib/tools/science/trials.d.ts +58 -0
  180. package/dist/lib/tools/science/trials.js +203 -0
  181. package/dist/lib/tools/science/types.d.ts +69 -0
  182. package/dist/lib/tools/science/types.js +48 -0
  183. package/dist/lib/tools/scienceTools.d.ts +100 -0
  184. package/dist/lib/tools/scienceTools.js +277 -0
  185. package/dist/lib/tools/taskTools.d.ts +204 -0
  186. package/dist/lib/tools/taskTools.js +374 -0
  187. package/dist/lib/tools/toolContract.d.ts +17 -4
  188. package/dist/lib/tools/toolContract.js +13 -5
  189. package/dist/lib/tools/webExtractTool.d.ts +13 -10
  190. package/dist/lib/tools/webExtractTool.js +23 -55
  191. package/dist/lib/tools/webSearchTool.d.ts +2 -1
  192. package/dist/lib/tools/webSearchTool.js +4 -3
  193. package/dist/lib/tools/wikiTools.d.ts +2 -2
  194. package/dist/lib/tools/wikiTools.js +12 -12
  195. package/dist/lib/tools/xApiSearchTool.d.ts +194 -0
  196. package/dist/lib/tools/xApiSearchTool.js +583 -0
  197. package/dist/lib/tools/xSearchTool.js +1 -1
  198. package/dist/lib/wiki/agentRun.js +1 -1
  199. package/dist/lib/wiki/builder.js +21 -6
  200. package/dist/lib/wiki/dive.js +2 -2
  201. package/dist/lib/wiki/entities.d.ts +2 -2
  202. package/dist/lib/wiki/entities.js +7 -7
  203. package/dist/lib/wiki/extract.js +5 -1
  204. package/dist/lib/wiki/fill.js +5 -5
  205. package/dist/lib/wiki/format.d.ts +7 -5
  206. package/dist/lib/wiki/format.js +2 -2
  207. package/dist/lib/wiki/graph.js +2 -2
  208. package/dist/lib/wiki/lint.js +2 -2
  209. package/dist/lib/wiki/markdown.d.ts +1 -1
  210. package/dist/lib/wiki/markdown.js +6 -1
  211. package/dist/lib/wiki/pipeline.js +2 -2
  212. package/dist/lib/wiki/vault.js +2 -2
  213. package/dist/scripts/a2a_server.d.ts +17 -11
  214. package/dist/scripts/a2a_server.js +405 -1028
  215. package/dist/scripts/assistant_worker.d.ts +33 -0
  216. package/dist/scripts/assistant_worker.js +193 -0
  217. package/dist/scripts/db.d.ts +26 -0
  218. package/dist/scripts/db.js +179 -0
  219. package/dist/scripts/doctor.d.ts +23 -0
  220. package/dist/scripts/doctor.js +90 -0
  221. package/dist/scripts/init.d.ts +43 -0
  222. package/dist/scripts/init.js +152 -0
  223. package/dist/scripts/registry.d.ts +25 -0
  224. package/dist/scripts/registry.js +182 -0
  225. package/dist/scripts/skills_install.d.ts +7 -0
  226. package/dist/scripts/skills_install.js +137 -0
  227. package/dist/scripts/syndicate_chat.js +159 -276
  228. package/package.json +90 -16
  229. package/skills/README.md +74 -0
  230. package/skills/briefs/README.md +16 -0
  231. package/skills/briefs/_shared.md +16 -0
  232. package/skills/briefs/melchizedek-author.md +60 -0
  233. package/skills/briefs/melchizedek-memory.md +47 -0
  234. package/skills/briefs/melchizedek-models.md +47 -0
  235. package/skills/briefs/melchizedek-scribe.md +37 -0
  236. package/skills/briefs/melchizedek-serve.md +56 -0
  237. package/skills/briefs/melchizedek.md +69 -0
  238. package/skills/briefs/skills-readme.md +33 -0
  239. package/skills/melchizedek/SKILL.md +159 -0
  240. package/skills/melchizedek-author/SKILL.md +99 -0
  241. package/skills/melchizedek-author/templates/minimal.yaml +60 -0
  242. package/skills/melchizedek-memory/SKILL.md +138 -0
  243. package/skills/melchizedek-models/SKILL.md +137 -0
  244. package/skills/melchizedek-scribe/SKILL.md +72 -0
  245. package/skills/melchizedek-scribe/templates/brief.md +27 -0
  246. package/skills/melchizedek-serve/SKILL.md +119 -0
@@ -2,27 +2,22 @@
2
2
  import { createInterface } from 'node:readline';
3
3
  import { writeFileSync, mkdirSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
- import { loadSyndicate, parseCliBindings } from "../lib/loadSyndicate.js";
6
- import { traceAgentRun } from "../lib/observability/tracer.js";
7
- // ADK Imports
8
- import { LlmAgent, Runner, InMemorySessionService, getFunctionCalls, getFunctionResponses, AgentTool, setLogLevel, LogLevel, StreamingMode } from '@google/adk';
5
+ import { loadSyndicate, parseCliBindings } from '../lib/loadSyndicate.js';
6
+ import { isDispatchSyndicate } from '../lib/dispatch.js';
7
+ import { ingestTurnMemory, runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
8
+ import { InMemorySessionService, getFunctionCalls, getFunctionResponses, setLogLevel, LogLevel, } from '@google/adk';
9
9
  import { randomUUID } from 'node:crypto';
10
- // Tool resolution (shared registry) + MCP factory
11
- import { resolveTools as resolveNamedTools } from "../lib/toolRegistry.js";
12
- import { createMcpTools } from "../lib/tools/mcpToolFactory.js";
13
- import { loadEnv } from "../lib/loadEnv.js";
10
+ import { loadEnv } from '../lib/loadEnv.js';
14
11
  // ── LLM Provider Registration ─────────────────────────────────────────────────
15
12
  // WHY: Import only — registration is deferred to main() so it runs AFTER
16
13
  // loadEnv() has populated process.env from .env. If registered here at module
17
14
  // load time, provider API keys would always be undefined and the non-Gemini
18
15
  // adapters would never be added to the LLMRegistry, causing "Model not
19
16
  // found" errors. registerAvailableProviders() gates each adapter on its key.
20
- import { registerAvailableProviders, providerForModel, providerKeyPresent, PROVIDERS, } from "../lib/models/registry.js";
17
+ import { registerAvailableProviders, providerForModel, providerKeyPresent, PROVIDERS, } from '../lib/models/registry.js';
21
18
  // ── Persistence Factory ───────────────────────────────────────────────────────
22
- // WHY: All Firebase-specific initialization is encapsulated in firebaseProvider.
23
- // To swap the session/memory backend (e.g. Postgres, SQLite), replace this
24
- // import with a sibling provider module that exports the same interface.
25
- import { hasSupabaseCredentials, createSupabaseServices, } from "../lib/persistence/supabaseProvider.js";
19
+ // Supabase-specific initialization lives in supabaseProvider.ts.
20
+ import { hasSupabaseCredentials, createSupabaseServices, } from '../lib/persistence/supabaseProvider.js';
26
21
  // Silence ADK verbose INFO logging natively
27
22
  setLogLevel(LogLevel.WARN);
28
23
  const c = {
@@ -63,7 +58,7 @@ function detectPersistenceConfig(memorySystem) {
63
58
  function banner(config, persistence, bindings = {}, sessionId) {
64
59
  console.log('');
65
60
  console.log(`${c.cyan}${c.bold} ╔══════════════════════════════════════╗${c.reset}`);
66
- console.log(`${c.cyan}${c.bold} ║ Melchizedek Syndicate (ADK v1.0) ║${c.reset}`);
61
+ console.log(`${c.cyan}${c.bold} ║ Melchizedek Syndicate ║${c.reset}`);
67
62
  console.log(`${c.cyan}${c.bold} ╚══════════════════════════════════════╝${c.reset}`);
68
63
  console.log(`${c.dim} Syndicate : ${c.yellow}${config.syndicate_name}${c.reset}`);
69
64
  console.log(`${c.dim} Orchestrator : ${c.yellow}${config.orchestrator.name} (${config.orchestrator.model})${c.reset}`);
@@ -176,144 +171,32 @@ async function main() {
176
171
  const STREAM_REPLIES = process.env.CHAT_STREAMING !== 'false';
177
172
  // ── Detect persistence mode ──────────────────────────────────────────────
178
173
  const persistence = detectPersistenceConfig(config.memory_system);
179
- // ── Tool resolver ─────────────────────────────────────────────────────────
180
- // WHY: Tool names in YAML are strings. This function maps them to live ADK
181
- // tool instances. Add new FunctionTools here as the syndicate library grows.
182
- const resolveTools = (toolNames = []) => resolveNamedTools(toolNames, (name) => console.warn(`${c.yellow}⚠ Unknown tool: '${name}' — skipping.${c.reset}`));
183
- // 1. Build ADK Subagents
184
- const orchestratorTools = [];
185
- for (const subConfig of config.subagents) {
186
- const tools = resolveTools(subConfig.tools);
187
- if (subConfig.mcp_server_url) {
188
- const mcpTools = await createMcpTools(subConfig.mcp_server_url);
189
- for (const mcpTool of mcpTools) {
190
- if (!tools.some(t => t.name === mcpTool.name)) {
191
- tools.push(mcpTool);
192
- }
193
- }
194
- }
195
- const agent = new LlmAgent({
196
- name: subConfig.name,
197
- model: subConfig.model,
198
- instruction: subConfig.instruction,
199
- description: subConfig.description,
200
- tools: tools.length > 0 ? tools : undefined,
201
- inputSchema: {
202
- type: "OBJECT",
203
- properties: {
204
- query: {
205
- type: "STRING",
206
- description: `The search query or instruction for ${subConfig.name}`
207
- }
208
- },
209
- required: ["query"]
210
- },
211
- outputSchema: subConfig.outputSchema,
212
- generateContentConfig: {
213
- ...subConfig.generateContentConfig,
214
- toolConfig: {
215
- ...subConfig.generateContentConfig?.toolConfig,
216
- includeServerSideToolInvocations: true
217
- }
218
- }
219
- });
220
- orchestratorTools.push(new AgentTool({ agent }));
221
- }
222
- // 2. Build ADK Orchestrator and provide subagents + its own direct tools
223
- // WHY: The orchestrator may declare its own tools in YAML (e.g. generate_image,
224
- // load_memory, preload_memory) in addition to — or instead of — subagent AgentTools.
225
- const orchestratorDirectTools = resolveTools(config.orchestrator.tools);
226
- const allOrchestratorTools = [...orchestratorTools, ...orchestratorDirectTools];
227
- const orchestrator = new LlmAgent({
228
- name: config.orchestrator.name,
229
- model: config.orchestrator.model,
230
- instruction: config.orchestrator.instruction,
231
- description: 'The master orchestrator.',
232
- tools: allOrchestratorTools.length > 0 ? allOrchestratorTools : undefined,
233
- outputSchema: config.orchestrator.outputSchema,
234
- generateContentConfig: {
235
- ...config.orchestrator.generateContentConfig,
236
- toolConfig: {
237
- ...config.orchestrator.generateContentConfig?.toolConfig,
238
- includeServerSideToolInvocations: true
239
- }
240
- }
241
- });
242
- // 3. Build the runner.
243
- //
244
- // WHY: We now construct a full `Runner` (not InMemoryRunner) when cloud
245
- // credentials are available. Runner accepts explicit sessionService and
246
- // memoryService instances, enabling persistent state across process restarts
247
- // (Firebase) and semantic long-term memory (Vertex). When no cloud credentials
248
- // are detected, we fall back to InMemoryRunner for local development.
249
- const appName = config.syndicate_name || 'melchizedek-syndicate';
250
- let runner;
251
- // ── Runner Construction ───────────────────────────────────────────────────
252
- // WHY: The persistence provider is resolved here, not inline. Supabase init,
253
- // service construction, and credential handling all live in supabaseProvider.ts.
254
- // To swap backends, update the import at the top of this file — nothing here
255
- // needs to change.
174
+ // ── Persistence ───────────────────────────────────────────────────────────
175
+ // The same services the server would use for this syndicate's
176
+ // memory_system: Supabase sessions (and the memory service for
177
+ // long-term) when configured, process memory otherwise.
178
+ let sessionService = new InMemorySessionService();
179
+ let memoryService;
256
180
  if (persistence.sessionService === 'supabase') {
257
- // ── Supabase Mode ─────────────────────────────────────────
258
- // WHY: Full cloud persistence. Session state survives process restarts
259
- // (Supabase) and long-term memory accumulates across sessions (Supabase
260
- // pgvector). All Supabase-specific init is delegated to the factory.
261
- const { sessionService, memoryService } = await createSupabaseServices({
262
- // Empty only when an all-local syndicate runs keyless — in that case
263
- // detectPersistenceConfig has already forced withMemory to false.
181
+ const services = await createSupabaseServices({
182
+ // Empty only when an all-local syndicate runs keyless — then
183
+ // detectPersistenceConfig has already left the memory service off.
264
184
  apiKey: apiKey ?? '',
265
185
  withMemory: persistence.memoryService === 'supabase-vector',
266
186
  });
267
- runner = new Runner({
268
- agent: orchestrator,
269
- appName,
270
- sessionService,
271
- memoryService,
272
- });
273
- }
274
- else {
275
- // ── In-Memory Mode ────────────────────────────────────────────────────
276
- // WHY: No Supabase credentials found. Use InMemorySessionService for
277
- // session state (volatile, process-scoped). Memory service is omitted
278
- // since it requires Supabase to function.
279
- const sessionService = new InMemorySessionService();
280
- runner = new Runner({
281
- agent: orchestrator,
282
- appName,
283
- sessionService,
284
- memoryService: undefined,
285
- });
187
+ sessionService = services.sessionService;
188
+ memoryService = services.memoryService;
286
189
  }
287
- // WHY: We create the session explicitly so we control the ID and can
288
- // display it in the banner. The session record lives in whichever
289
- // sessionService was wired into the runner.
290
- await runner.sessionService.createSession({
291
- appName,
292
- userId: SESSION_USER_ID,
293
- sessionId: SESSION_ID,
294
- state: {}
295
- });
296
- // Now that the session is live, render the startup banner with the session ID.
190
+ const appName = config.syndicate_name || 'melchizedek-syndicate';
191
+ await sessionService.createSession({ appName, userId: SESSION_USER_ID, sessionId: SESSION_ID, state: {} });
297
192
  banner(config, persistence, mergedBindings, SESSION_ID);
298
- // Suppress verbose ADK info and benign warnings
299
- const originalInfo = console.info;
300
- console.info = (...args) => {
301
- if (typeof args[0] === 'string' && args[0].includes('[ADK]'))
302
- return;
303
- originalInfo(...args);
304
- };
305
- const originalWarn = console.warn;
306
- console.warn = (...args) => {
307
- if (typeof args[0] === 'string' && args[0].includes('[ADK]') && args[0].includes('Event from an unknown agent'))
308
- return;
309
- originalWarn(...args);
310
- };
311
- const originalLog = console.log;
312
- console.log = (...args) => {
313
- if (typeof args[0] === 'string' && args[0].includes('[ADK]'))
314
- return;
315
- originalLog(...args);
316
- };
193
+ // Plan-dispatch, nested yaml_reference syndicates, guards and the
194
+ // max_steps cap run here exactly as the A2A server runs them: both call
195
+ // lib/runtime/syndicateTurn.ts. (This file used to build its own graph
196
+ // and silently ran dispatch syndicates in DELEGATE mode.)
197
+ if (isDispatchSyndicate(config)) {
198
+ console.log(`${c.dim} Mode : plan-dispatch (classifier → one route answers)${c.reset}\n`);
199
+ }
317
200
  // Strip --bind/--bindings/--syndicate pairs AND bare '--' separators from
318
201
  // argv to get the actual user query. The bare '--' leaks in when npm passes
319
202
  // the named syndicate shortcuts (e.g. syndicate:image) to the node process.
@@ -330,103 +213,44 @@ async function main() {
330
213
  }
331
214
  }
332
215
  const cliInput = queryParts.join(' ');
216
+ // Ctrl+C during a turn cancels the turn; Ctrl+C at the prompt exits.
217
+ let activeTurn;
333
218
  async function runChat(trimmed) {
219
+ const printer = makePrinter();
220
+ activeTurn = new AbortController();
334
221
  try {
335
- // WHY: runAsync (not runEphemeral) is used here so that the runner
336
- // reads the existing session from InMemorySessionService, appends the
337
- // new user message, runs the agent, and writes the assistant reply back
338
- // — preserving full multi-turn history within this process lifetime.
339
- let stream = runner.runAsync({
222
+ const result = await runSyndicateTurn({
223
+ config,
224
+ parts: [{ text: trimmed }],
225
+ appName,
340
226
  userId: SESSION_USER_ID,
341
227
  sessionId: SESSION_ID,
342
- newMessage: { role: 'user', parts: [{ text: trimmed }] },
228
+ sessionService,
229
+ memoryService,
230
+ compile: {
231
+ onUnknownTool: (name) => console.warn(`${c.yellow}⚠ Unknown tool: '${name}' — skipping.${c.reset}`),
232
+ log: (message) => console.log(`${c.dim} ${message}${c.reset}`),
233
+ },
343
234
  // SSE makes the adapters emit display-only partials as tokens
344
- // land, so thinking and the reply appear as they are produced
345
- // rather than in one block when the call returns.
346
- runConfig: {
347
- streamingMode: STREAM_REPLIES ? StreamingMode.SSE : StreamingMode.NONE
348
- }
349
- });
350
- stream = traceAgentRun(stream, {
351
- syndicateName: config.syndicate_name || 'melchizedek-syndicate',
352
- bindings: mergedBindings,
353
- input: trimmed
235
+ // land, so thinking and the reply appear as they are produced.
236
+ streaming: STREAM_REPLIES,
237
+ signal: activeTurn.signal,
238
+ trace: { bindings: mergedBindings },
239
+ events: {
240
+ onEvent: printer.onEvent,
241
+ onProgress: (text) => {
242
+ if (text.startsWith('Routed to') || text.startsWith('Guard '))
243
+ console.log(`\n${c.dim}[${text}]${c.reset}`);
244
+ },
245
+ },
354
246
  });
355
- let currentMode = 'none';
356
- // Under SSE the reply arrives as partials and is then REPEATED whole
357
- // on the final event — the one ADK persists to session history. Show
358
- // the partials, skip the repeat. With streaming off nothing is
359
- // partial, so this stays false and every event prints as before.
360
- let streamedText = false;
361
- for await (const event of stream) {
362
- const isPartial = event.partial === true;
363
- const calls = getFunctionCalls(event);
364
- if (calls && calls.length > 0) {
365
- for (const call of calls) {
366
- if (call.name === 'transfer_to_agent') {
367
- console.log(`\n${c.dim}[${event.author} is delegating to subagent: ${JSON.stringify(call.args)}]${c.reset}`);
368
- }
369
- else {
370
- console.log(`\n${c.dim}[${event.author} is calling tool: ${call.name}]${c.reset}`);
371
- }
372
- }
373
- }
374
- const responses = getFunctionResponses(event);
375
- if (responses && responses.length > 0) {
376
- for (const response of responses) {
377
- console.log(`\n${c.dim}[Received response from: ${response.name}]${c.reset}`);
378
- }
379
- }
380
- // WHY: LlmResponse can carry errorCode/errorMessage (e.g. billing errors,
381
- // rate limits, invalid model) without raising a JS exception. Without this
382
- // check those errors are silently dropped, producing a blank response.
383
- // NOTE: 'STOP' is a normal finishReason mapped to errorCode by ADK when content is empty.
384
- const evAny = event;
385
- if ((evAny.errorCode || evAny.errorMessage) && evAny.errorCode !== 'STOP') {
386
- console.error(`\n${c.yellow}⚠ [${evAny.errorCode ?? 'ERROR'}] ${evAny.errorMessage ?? ''}${c.reset}`);
387
- }
388
- if (event.content && event.content.parts) {
389
- for (const part of event.content.parts) {
390
- if (part.thought) {
391
- if (currentMode !== 'thinking') {
392
- console.log(`\n${c.cyan}✦ ${event.author} is thinking...${c.reset}`);
393
- currentMode = 'thinking';
394
- }
395
- process.stdout.write(c.dim + part.text + c.reset);
396
- }
397
- else if (part.text) {
398
- if (!isPartial && streamedText)
399
- continue;
400
- if (currentMode === 'thinking') {
401
- console.log(`\n\n${c.magenta}${c.bold}${event.author}${c.reset} › `);
402
- currentMode = 'text';
403
- }
404
- else if (currentMode === 'none') {
405
- process.stdout.write(`\n${c.magenta}${c.bold}${event.author}${c.reset} › `);
406
- currentMode = 'text';
407
- }
408
- process.stdout.write(part.text);
409
- if (isPartial)
410
- streamedText = true;
411
- }
412
- else if (part.inlineData) {
413
- // WHY: Image generation models return binary data as base64-encoded
414
- // inlineData parts. We detect these, decode them, and save to disk
415
- // so the user gets a real file rather than raw base64 in the terminal.
416
- const { mimeType, data } = part.inlineData;
417
- const ext = mimeType?.split('/')[1] ?? 'png';
418
- const outputDir = join(process.cwd(), 'outputs');
419
- mkdirSync(outputDir, { recursive: true });
420
- const filename = `image_${Date.now()}.${ext}`;
421
- const filepath = join(outputDir, filename);
422
- writeFileSync(filepath, Buffer.from(data, 'base64'));
423
- process.stdout.write(`\n${c.green}✓ Image saved → outputs/${filename}${c.reset}\n`);
424
- }
425
- }
426
- }
427
- // A complete event ends the turn; the next one starts fresh.
428
- if (!isPartial)
429
- streamedText = false;
247
+ printer.finish();
248
+ if (result.relayFallback) {
249
+ // The orchestrator's relay came back empty; show what shipped.
250
+ console.log(`\n${c.dim}[relay fallback — the specialist's answer]${c.reset}\n${result.text}`);
251
+ }
252
+ if (result.status !== 'completed') {
253
+ console.error(`\n${c.yellow}⚠ [${result.error?.code ?? 'ERROR'}] ${result.error?.message ?? ''}${c.reset}`);
430
254
  }
431
255
  console.log('\n');
432
256
  }
@@ -434,14 +258,36 @@ async function main() {
434
258
  const msg = err instanceof Error ? err.message : String(err);
435
259
  console.error(`\n${c.yellow}⚠ Error: ${msg}${c.reset}\n`);
436
260
  }
261
+ finally {
262
+ activeTurn = undefined;
263
+ }
437
264
  }
265
+ const ingest = async () => {
266
+ if (!memoryService)
267
+ return;
268
+ try {
269
+ const ingested = await ingestTurnMemory({
270
+ memoryService,
271
+ sessionService,
272
+ appName,
273
+ userId: SESSION_USER_ID,
274
+ sessionId: SESSION_ID,
275
+ extractionRules: config.memory_extraction_rules,
276
+ extractionModel: config.memory_extraction_model,
277
+ });
278
+ console.log(ingested
279
+ ? `${c.green} ✓ Session ingested into long-term memory.${c.reset}`
280
+ : `${c.dim} No session data to ingest.${c.reset}`);
281
+ }
282
+ catch (err) {
283
+ console.error(`${c.yellow} ⚠ Memory ingestion failed: ${err instanceof Error ? err.message : String(err)}${c.reset}`);
284
+ }
285
+ };
438
286
  if (cliInput) {
439
287
  console.log(`${c.green}${c.bold}You${c.reset} › ${cliInput}`);
440
288
  await runChat(cliInput);
441
- // WHY: After a single-shot CLI invocation, ingest the session into
442
- // long-term memory before exiting. This ensures even one-off queries
443
- // contribute to the agent's knowledge base.
444
- await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
289
+ // A one-shot query still contributes to long-term memory.
290
+ await ingest();
445
291
  return;
446
292
  }
447
293
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -453,12 +299,9 @@ async function main() {
453
299
  return;
454
300
  }
455
301
  if (['exit', 'quit', 'bye'].includes(trimmed.toLowerCase())) {
456
- // WHY: On session end, ingest the full conversation into long-term
457
- // memory. This is the critical moment where ephemeral session data
458
- // becomes persistent knowledge — the Runner's memoryService
459
- // extracts facts, embeds them, and stores them for future retrieval.
302
+ // Session end is where the conversation becomes long-term memory.
460
303
  console.log(`\n${c.dim} Ingesting session into long-term memory...${c.reset}`);
461
- await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
304
+ await ingest();
462
305
  console.log(`${c.cyan} Goodbye! 👋${c.reset}\n`);
463
306
  rl.close();
464
307
  return;
@@ -467,46 +310,86 @@ async function main() {
467
310
  ask();
468
311
  });
469
312
  };
470
- // WHY: If the user forcefully exits the CLI (Ctrl+C), we must intercept the
471
- // termination signal to ensure the current session is gracefully ingested
472
- // into long-term memory before the process dies.
473
313
  process.on('SIGINT', async () => {
314
+ if (activeTurn) {
315
+ console.log(`\n${c.dim} Canceling the turn...${c.reset}`);
316
+ activeTurn.abort();
317
+ return;
318
+ }
474
319
  console.log(`\n${c.dim} Intercepted exit signal. Ingesting session into long-term memory...${c.reset}`);
475
- await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
320
+ await ingest();
476
321
  console.log(`${c.cyan} Goodbye! 👋${c.reset}\n`);
477
322
  process.exit(0);
478
323
  });
479
324
  ask();
480
325
  }
481
326
  /**
482
- * Ingests the current session into the Runner's memory service.
327
+ * Prints a turn's events as they arrive: tool calls, thinking (dimmed), the
328
+ * reply streamed token by token, and inline images saved to outputs/.
483
329
  *
484
- * WHY: The ADK Runner does not automatically call addSessionToMemory() —
485
- * that responsibility falls to the application layer. We fetch the full
486
- * session (with all accumulated events) from the session service and pass
487
- * it to the memory service's extraction pipeline. This is where session
488
- * transcripts get compressed into semantic facts and stored for future recall.
330
+ * Under SSE the reply arrives as partials and is then REPEATED whole on the
331
+ * final event (the one ADK persists to session history): show the partials,
332
+ * skip the repeat.
489
333
  */
490
- async function ingestSessionMemory(runner, appName, userId, sessionId) {
491
- if (!runner.memoryService)
492
- return;
493
- try {
494
- const session = await runner.sessionService.getSession({
495
- appName,
496
- userId,
497
- sessionId,
498
- });
499
- if (!session || session.events.length === 0) {
500
- console.log(`${c.dim} No session data to ingest.${c.reset}`);
501
- return;
502
- }
503
- await runner.memoryService.addSessionToMemory(session);
504
- console.log(`${c.green} ✓ Session ingested into long-term memory.${c.reset}`);
505
- }
506
- catch (err) {
507
- const msg = err instanceof Error ? err.message : String(err);
508
- console.error(`${c.yellow} ⚠ Memory ingestion failed: ${msg}${c.reset}`);
509
- }
334
+ function makePrinter() {
335
+ let currentMode = 'none';
336
+ let streamedText = false;
337
+ return {
338
+ onEvent(event) {
339
+ const e = event;
340
+ const isPartial = e.partial === true;
341
+ for (const call of getFunctionCalls(event) ?? []) {
342
+ if (call.name === 'transfer_to_agent') {
343
+ console.log(`\n${c.dim}[${e.author} is delegating to subagent: ${JSON.stringify(call.args)}]${c.reset}`);
344
+ }
345
+ else {
346
+ console.log(`\n${c.dim}[${e.author} is calling tool: ${call.name}]${c.reset}`);
347
+ }
348
+ currentMode = 'none';
349
+ }
350
+ for (const response of getFunctionResponses(event) ?? []) {
351
+ console.log(`\n${c.dim}[Received response from: ${response.name}]${c.reset}`);
352
+ currentMode = 'none';
353
+ }
354
+ for (const part of event.content?.parts ?? []) {
355
+ const p = part;
356
+ if (p.thought) {
357
+ if (currentMode !== 'thinking') {
358
+ console.log(`\n${c.cyan}✦ ${e.author} is thinking...${c.reset}`);
359
+ currentMode = 'thinking';
360
+ }
361
+ process.stdout.write(c.dim + p.text + c.reset);
362
+ }
363
+ else if (p.text) {
364
+ if (!isPartial && streamedText)
365
+ continue;
366
+ if (currentMode !== 'text') {
367
+ process.stdout.write(`\n${c.magenta}${c.bold}${e.author}${c.reset} › `);
368
+ currentMode = 'text';
369
+ }
370
+ process.stdout.write(p.text);
371
+ if (isPartial)
372
+ streamedText = true;
373
+ }
374
+ else if (p.inlineData) {
375
+ // Image models return base64 inlineData; save it as a file.
376
+ const { mimeType, data } = p.inlineData;
377
+ const ext = mimeType?.split('/')[1] ?? 'png';
378
+ const outputDir = join(process.cwd(), 'outputs');
379
+ mkdirSync(outputDir, { recursive: true });
380
+ const filename = `image_${Date.now()}.${ext}`;
381
+ writeFileSync(join(outputDir, filename), Buffer.from(data, 'base64'));
382
+ process.stdout.write(`\n${c.green}✓ Image saved → outputs/${filename}${c.reset}\n`);
383
+ }
384
+ }
385
+ // A complete event ends that agent's turn; the next starts fresh.
386
+ if (!isPartial)
387
+ streamedText = false;
388
+ },
389
+ finish() {
390
+ currentMode = 'none';
391
+ },
392
+ };
510
393
  }
511
394
  main().catch((err) => {
512
395
  if (err.code !== 'ERR_USE_AFTER_CLOSE') {