melchizedek-agents 0.9.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 (111) hide show
  1. package/AGENT_SETUP.md +182 -0
  2. package/DOCUMENTATION.md +614 -0
  3. package/LICENSE +21 -0
  4. package/QUICKSTART.md +164 -0
  5. package/README.md +151 -0
  6. package/config/agents/README.md +27 -0
  7. package/config/agents/examples/ares.yaml +74 -0
  8. package/config/agents/examples/cartographers.yaml +115 -0
  9. package/config/agents/examples/claude.yaml +17 -0
  10. package/config/agents/examples/council.yaml +65 -0
  11. package/config/agents/examples/critic.yaml +74 -0
  12. package/config/agents/examples/delegation.yaml +29 -0
  13. package/config/agents/examples/hierarchical.yaml +26 -0
  14. package/config/agents/examples/image_production.yaml +112 -0
  15. package/config/agents/examples/librarian.yaml +75 -0
  16. package/config/agents/examples/model_zoo.yaml +81 -0
  17. package/config/agents/examples/patient_advocate.yaml +107 -0
  18. package/config/agents/examples/scriptorium.yaml +96 -0
  19. package/config/agents/examples/style_council.yaml +83 -0
  20. package/config/agents/examples/syndicate.yaml +34 -0
  21. package/config/agents/examples/tutor.yaml +66 -0
  22. package/config/agents/syndicateSchema.yaml +291 -0
  23. package/dist/lib/config.d.ts +108 -0
  24. package/dist/lib/config.js +110 -0
  25. package/dist/lib/dispatch.d.ts +133 -0
  26. package/dist/lib/dispatch.js +182 -0
  27. package/dist/lib/grounding.d.ts +28 -0
  28. package/dist/lib/grounding.js +61 -0
  29. package/dist/lib/index.d.ts +14 -0
  30. package/dist/lib/index.js +13 -0
  31. package/dist/lib/loadEnv.d.ts +21 -0
  32. package/dist/lib/loadEnv.js +58 -0
  33. package/dist/lib/loadSyndicate.d.ts +177 -0
  34. package/dist/lib/loadSyndicate.js +264 -0
  35. package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
  36. package/dist/lib/memory/supabaseMemoryService.js +499 -0
  37. package/dist/lib/models/claudeLlm.d.ts +93 -0
  38. package/dist/lib/models/claudeLlm.js +332 -0
  39. package/dist/lib/models/gptLlm.d.ts +110 -0
  40. package/dist/lib/models/gptLlm.js +497 -0
  41. package/dist/lib/models/grokLlm.d.ts +64 -0
  42. package/dist/lib/models/grokLlm.js +86 -0
  43. package/dist/lib/models/ollamaLlm.d.ts +105 -0
  44. package/dist/lib/models/ollamaLlm.js +134 -0
  45. package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
  46. package/dist/lib/models/openAiCompatibleLlm.js +331 -0
  47. package/dist/lib/models/providerMap.d.ts +28 -0
  48. package/dist/lib/models/providerMap.js +45 -0
  49. package/dist/lib/models/registry.d.ts +71 -0
  50. package/dist/lib/models/registry.js +159 -0
  51. package/dist/lib/models/schemaNormalize.d.ts +22 -0
  52. package/dist/lib/models/schemaNormalize.js +58 -0
  53. package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
  54. package/dist/lib/observability/supabaseSpanExporter.js +115 -0
  55. package/dist/lib/observability/tracer.d.ts +44 -0
  56. package/dist/lib/observability/tracer.js +242 -0
  57. package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
  58. package/dist/lib/persistence/supabaseProvider.js +88 -0
  59. package/dist/lib/session/supabaseSessionService.d.ts +26 -0
  60. package/dist/lib/session/supabaseSessionService.js +193 -0
  61. package/dist/lib/session/transcript.d.ts +193 -0
  62. package/dist/lib/session/transcript.js +309 -0
  63. package/dist/lib/toolRegistry.d.ts +15 -0
  64. package/dist/lib/toolRegistry.js +58 -0
  65. package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
  66. package/dist/lib/tools/collectionsSearchTool.js +86 -0
  67. package/dist/lib/tools/generateImageTool.d.ts +16 -0
  68. package/dist/lib/tools/generateImageTool.js +119 -0
  69. package/dist/lib/tools/inspectImageTool.d.ts +21 -0
  70. package/dist/lib/tools/inspectImageTool.js +97 -0
  71. package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
  72. package/dist/lib/tools/mcpToolFactory.js +116 -0
  73. package/dist/lib/tools/toolContract.d.ts +78 -0
  74. package/dist/lib/tools/toolContract.js +129 -0
  75. package/dist/lib/tools/webExtractTool.d.ts +70 -0
  76. package/dist/lib/tools/webExtractTool.js +461 -0
  77. package/dist/lib/tools/webSearchTool.d.ts +53 -0
  78. package/dist/lib/tools/webSearchTool.js +126 -0
  79. package/dist/lib/tools/wikiTools.d.ts +112 -0
  80. package/dist/lib/tools/wikiTools.js +697 -0
  81. package/dist/lib/tools/xSearchTool.d.ts +61 -0
  82. package/dist/lib/tools/xSearchTool.js +125 -0
  83. package/dist/lib/wiki/agentRun.d.ts +39 -0
  84. package/dist/lib/wiki/agentRun.js +79 -0
  85. package/dist/lib/wiki/builder.d.ts +71 -0
  86. package/dist/lib/wiki/builder.js +165 -0
  87. package/dist/lib/wiki/dive.d.ts +56 -0
  88. package/dist/lib/wiki/dive.js +208 -0
  89. package/dist/lib/wiki/entities.d.ts +220 -0
  90. package/dist/lib/wiki/entities.js +661 -0
  91. package/dist/lib/wiki/extract.d.ts +97 -0
  92. package/dist/lib/wiki/extract.js +305 -0
  93. package/dist/lib/wiki/fill.d.ts +37 -0
  94. package/dist/lib/wiki/fill.js +175 -0
  95. package/dist/lib/wiki/format.d.ts +132 -0
  96. package/dist/lib/wiki/format.js +177 -0
  97. package/dist/lib/wiki/graph.d.ts +80 -0
  98. package/dist/lib/wiki/graph.js +147 -0
  99. package/dist/lib/wiki/lint.d.ts +58 -0
  100. package/dist/lib/wiki/lint.js +267 -0
  101. package/dist/lib/wiki/markdown.d.ts +127 -0
  102. package/dist/lib/wiki/markdown.js +336 -0
  103. package/dist/lib/wiki/pipeline.d.ts +69 -0
  104. package/dist/lib/wiki/pipeline.js +119 -0
  105. package/dist/lib/wiki/vault.d.ts +84 -0
  106. package/dist/lib/wiki/vault.js +194 -0
  107. package/dist/scripts/a2a_server.d.ts +2 -0
  108. package/dist/scripts/a2a_server.js +997 -0
  109. package/dist/scripts/syndicate_chat.d.ts +2 -0
  110. package/dist/scripts/syndicate_chat.js +485 -0
  111. package/package.json +96 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline';
3
+ import { writeFileSync, mkdirSync } from 'node:fs';
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 } from '@google/adk';
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";
14
+ // ── LLM Provider Registration ─────────────────────────────────────────────────
15
+ // WHY: Import only — registration is deferred to main() so it runs AFTER
16
+ // loadEnv() has populated process.env from .env. If registered here at module
17
+ // load time, provider API keys would always be undefined and the non-Gemini
18
+ // adapters would never be added to the LLMRegistry, causing "Model not
19
+ // found" errors. registerAvailableProviders() gates each adapter on its key.
20
+ import { registerAvailableProviders, providerForModel, providerKeyPresent, PROVIDERS, } from "../lib/models/registry.js";
21
+ // ── 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";
26
+ // Silence ADK verbose INFO logging natively
27
+ setLogLevel(LogLevel.WARN);
28
+ const c = {
29
+ reset: '\x1b[0m',
30
+ bold: '\x1b[1m',
31
+ dim: '\x1b[2m',
32
+ cyan: '\x1b[36m',
33
+ green: '\x1b[32m',
34
+ yellow: '\x1b[33m',
35
+ magenta: '\x1b[35m',
36
+ red: '\x1b[31m',
37
+ };
38
+ function detectPersistenceConfig(memorySystem) {
39
+ // WHY: Credential detection is delegated to the persistence factory so that
40
+ // the same logic governs both detection here and initialization below.
41
+ const supabaseAvailable = hasSupabaseCredentials();
42
+ const hasGeminiKey = !!(process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY);
43
+ if (!memorySystem) {
44
+ return {
45
+ sessionService: supabaseAvailable ? 'supabase' : 'in-memory',
46
+ memoryService: supabaseAvailable && hasGeminiKey ? 'supabase-vector' : 'none',
47
+ };
48
+ }
49
+ let sessionService = 'in-memory';
50
+ let memoryService = 'none';
51
+ if (memorySystem === 'session-only' || memorySystem === 'long-term') {
52
+ sessionService = supabaseAvailable ? 'supabase' : 'in-memory';
53
+ if (!supabaseAvailable)
54
+ console.warn(`\n${c.yellow}⚠ Requested ${memorySystem} memory system but missing Supabase credentials. Falling back to in-memory sessions.${c.reset}`);
55
+ }
56
+ if (memorySystem === 'long-term') {
57
+ memoryService = supabaseAvailable && hasGeminiKey ? 'supabase-vector' : 'none';
58
+ if (!hasGeminiKey)
59
+ console.warn(`\n${c.yellow}⚠ Requested long-term memory system but missing Gemini API Key. Falling back to no long-term memory.${c.reset}`);
60
+ }
61
+ return { sessionService, memoryService };
62
+ }
63
+ function banner(config, persistence, bindings = {}, sessionId) {
64
+ console.log('');
65
+ console.log(`${c.cyan}${c.bold} ╔══════════════════════════════════════╗${c.reset}`);
66
+ console.log(`${c.cyan}${c.bold} ║ Melchizedek Syndicate (ADK v1.0) ║${c.reset}`);
67
+ console.log(`${c.cyan}${c.bold} ╚══════════════════════════════════════╝${c.reset}`);
68
+ console.log(`${c.dim} Syndicate : ${c.yellow}${config.syndicate_name}${c.reset}`);
69
+ console.log(`${c.dim} Orchestrator : ${c.yellow}${config.orchestrator.name} (${config.orchestrator.model})${c.reset}`);
70
+ const subagentNames = config.subagents.filter(s => s.name).map(s => s.name);
71
+ if (subagentNames.length > 0) {
72
+ console.log(`${c.dim} Subagents : ${c.yellow}${subagentNames.join(', ')}${c.reset}`);
73
+ }
74
+ const keys = Object.keys(bindings);
75
+ if (keys.length > 0) {
76
+ const bindStr = keys.map(k => `${k}=${bindings[k]}`).join(', ');
77
+ console.log(`${c.dim} Bindings : ${c.magenta}${bindStr}${c.reset}`);
78
+ }
79
+ // Persistence indicators
80
+ const sessionLabel = persistence.sessionService === 'supabase'
81
+ ? `${c.green}Supabase Postgres${c.reset}`
82
+ : `${c.yellow}In-Memory${c.reset}`;
83
+ const memoryLabel = persistence.memoryService === 'supabase-vector'
84
+ ? `${c.green}Supabase Vector Search (semantic)${c.reset}`
85
+ : `${c.yellow}None${c.reset}`;
86
+ console.log(`${c.dim} Sessions : ${sessionLabel}`);
87
+ console.log(`${c.dim} Memory : ${memoryLabel}`);
88
+ if (sessionId) {
89
+ const sessionMode = persistence.sessionService === 'supabase' ? 'persistent' : 'in-memory';
90
+ console.log(`${c.dim} Session ID : ${c.green}${sessionId}${c.reset} ${c.dim}(${sessionMode}, multi-turn)${c.reset}`);
91
+ }
92
+ console.log(`${c.dim} Type : ${c.green}"exit"${c.dim} to end the session${c.reset}`);
93
+ console.log('');
94
+ }
95
+ async function main() {
96
+ loadEnv(import.meta.url);
97
+ // ── Register LLM providers ───────────────────────────────────────────────
98
+ // WHY: Must run AFTER loadEnv() so that API keys from .env are available.
99
+ // One call registers every adapter whose credentials exist (Ollama needs
100
+ // none): the YAML `model` string then routes through the LLMRegistry to
101
+ // the right provider — claude-*, gpt-*, grok-*, ollama/*, gemini-*.
102
+ registerAvailableProviders();
103
+ const apiKey = process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY;
104
+ if (apiKey) {
105
+ process.env.GEMINI_API_KEY = apiKey;
106
+ }
107
+ // The key requirement is enforced AFTER the syndicate loads: a syndicate
108
+ // whose every agent is an open-weight ollama/* model needs no key at all.
109
+ // ── Parse --syndicate flag ────────────────────────────────
110
+ // WHY: Allows invoking any syndicate config without editing code.
111
+ // Usage: npm run chat:syndicate -- --syndicate critic
112
+ // Defaults to 'syndicate.yaml' for backwards compatibility.
113
+ let syndicateFile = 'syndicate.yaml';
114
+ const argv = process.argv.slice(2);
115
+ for (let i = 0; i < argv.length; i++) {
116
+ if (argv[i] === '--syndicate' && i + 1 < argv.length) {
117
+ const name = argv[i + 1];
118
+ syndicateFile = name.endsWith('.yaml') ? name : `${name}.yaml`;
119
+ break;
120
+ }
121
+ }
122
+ // Define syndicate variable bindings
123
+ const bindings = {
124
+ headline_count: 3,
125
+ current_date: new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
126
+ };
127
+ // Merge CLI overrides on top (--bind/--bindings flags win)
128
+ const cliBindings = parseCliBindings(argv);
129
+ const mergedBindings = { ...bindings, ...cliBindings };
130
+ // Load the syndicate definition
131
+ const config = loadSyndicate(syndicateFile, {
132
+ bindings: mergedBindings,
133
+ });
134
+ // ── Enforce the key requirement per declared provider ────────────────────
135
+ // WHY: An all-ollama/* syndicate runs entirely on the user's machine, so
136
+ // demanding any cloud key would be an artificial gate. Each cloud model in
137
+ // the graph requires ITS provider's key: claude-* → ANTHROPIC_API_KEY,
138
+ // gpt-* → OPENAI_API_KEY, grok-* → XAI_API_KEY, gemini-* (or no model,
139
+ // the ADK default) → the Gemini key. Gemini-backed tools (google_search /
140
+ // generate_image / long-term memory embeddings) still need the Gemini key
141
+ // regardless of the inference model, and fail with a clear API error.
142
+ const declaredModels = [
143
+ config.orchestrator.model,
144
+ ...config.subagents.map((s) => s.model),
145
+ ].filter((m) => !!m);
146
+ const requiredProviders = new Set(declaredModels.length > 0
147
+ ? declaredModels.map((m) => providerForModel(m))
148
+ : ['gemini']);
149
+ if (!config.orchestrator.model)
150
+ requiredProviders.add('gemini');
151
+ const missingKeys = [...requiredProviders].filter((p) => !providerKeyPresent(p));
152
+ if (missingKeys.length > 0) {
153
+ for (const p of missingKeys) {
154
+ console.error(`${c.yellow}⚠ ${PROVIDERS[p].label} requires ${PROVIDERS[p].keyEnv}, which is not set.${c.reset}`);
155
+ }
156
+ console.error(`${c.dim} (Only syndicates whose every agent uses an ollama/* model run keyless.)${c.reset}`);
157
+ process.exit(1);
158
+ }
159
+ // Generate stable session identifiers upfront so the banner can display them.
160
+ // Memory/session silo for the local CLI. Override with MELCHIZEDEK_USER_ID
161
+ // when testing per-user isolation (e.g. long-term memory for two different
162
+ // people) — otherwise every CLI session shares the 'local-user' bucket.
163
+ const SESSION_USER_ID = process.env.MELCHIZEDEK_USER_ID?.trim() || 'local-user';
164
+ const SESSION_ID = randomUUID();
165
+ // ── Detect persistence mode ──────────────────────────────────────────────
166
+ const persistence = detectPersistenceConfig(config.memory_system);
167
+ // ── Tool resolver ─────────────────────────────────────────────────────────
168
+ // WHY: Tool names in YAML are strings. This function maps them to live ADK
169
+ // tool instances. Add new FunctionTools here as the syndicate library grows.
170
+ const resolveTools = (toolNames = []) => resolveNamedTools(toolNames, (name) => console.warn(`${c.yellow}⚠ Unknown tool: '${name}' — skipping.${c.reset}`));
171
+ // 1. Build ADK Subagents
172
+ const orchestratorTools = [];
173
+ for (const subConfig of config.subagents) {
174
+ const tools = resolveTools(subConfig.tools);
175
+ if (subConfig.mcp_server_url) {
176
+ const mcpTools = await createMcpTools(subConfig.mcp_server_url);
177
+ for (const mcpTool of mcpTools) {
178
+ if (!tools.some(t => t.name === mcpTool.name)) {
179
+ tools.push(mcpTool);
180
+ }
181
+ }
182
+ }
183
+ const agent = new LlmAgent({
184
+ name: subConfig.name,
185
+ model: subConfig.model,
186
+ instruction: subConfig.instruction,
187
+ description: subConfig.description,
188
+ tools: tools.length > 0 ? tools : undefined,
189
+ inputSchema: {
190
+ type: "OBJECT",
191
+ properties: {
192
+ query: {
193
+ type: "STRING",
194
+ description: `The search query or instruction for ${subConfig.name}`
195
+ }
196
+ },
197
+ required: ["query"]
198
+ },
199
+ outputSchema: subConfig.outputSchema,
200
+ generateContentConfig: {
201
+ ...subConfig.generateContentConfig,
202
+ toolConfig: {
203
+ ...subConfig.generateContentConfig?.toolConfig,
204
+ includeServerSideToolInvocations: true
205
+ }
206
+ }
207
+ });
208
+ orchestratorTools.push(new AgentTool({ agent }));
209
+ }
210
+ // 2. Build ADK Orchestrator and provide subagents + its own direct tools
211
+ // WHY: The orchestrator may declare its own tools in YAML (e.g. generate_image,
212
+ // load_memory, preload_memory) in addition to — or instead of — subagent AgentTools.
213
+ const orchestratorDirectTools = resolveTools(config.orchestrator.tools);
214
+ const allOrchestratorTools = [...orchestratorTools, ...orchestratorDirectTools];
215
+ const orchestrator = new LlmAgent({
216
+ name: config.orchestrator.name,
217
+ model: config.orchestrator.model,
218
+ instruction: config.orchestrator.instruction,
219
+ description: 'The master orchestrator.',
220
+ tools: allOrchestratorTools.length > 0 ? allOrchestratorTools : undefined,
221
+ outputSchema: config.orchestrator.outputSchema,
222
+ generateContentConfig: {
223
+ ...config.orchestrator.generateContentConfig,
224
+ toolConfig: {
225
+ ...config.orchestrator.generateContentConfig?.toolConfig,
226
+ includeServerSideToolInvocations: true
227
+ }
228
+ }
229
+ });
230
+ // 3. Build the runner.
231
+ //
232
+ // WHY: We now construct a full `Runner` (not InMemoryRunner) when cloud
233
+ // credentials are available. Runner accepts explicit sessionService and
234
+ // memoryService instances, enabling persistent state across process restarts
235
+ // (Firebase) and semantic long-term memory (Vertex). When no cloud credentials
236
+ // are detected, we fall back to InMemoryRunner for local development.
237
+ const appName = config.syndicate_name || 'melchizedek-syndicate';
238
+ let runner;
239
+ // ── Runner Construction ───────────────────────────────────────────────────
240
+ // WHY: The persistence provider is resolved here, not inline. Supabase init,
241
+ // service construction, and credential handling all live in supabaseProvider.ts.
242
+ // To swap backends, update the import at the top of this file — nothing here
243
+ // needs to change.
244
+ if (persistence.sessionService === 'supabase') {
245
+ // ── Supabase Mode ─────────────────────────────────────────
246
+ // WHY: Full cloud persistence. Session state survives process restarts
247
+ // (Supabase) and long-term memory accumulates across sessions (Supabase
248
+ // pgvector). All Supabase-specific init is delegated to the factory.
249
+ const { sessionService, memoryService } = await createSupabaseServices({
250
+ // Empty only when an all-local syndicate runs keyless — in that case
251
+ // detectPersistenceConfig has already forced withMemory to false.
252
+ apiKey: apiKey ?? '',
253
+ withMemory: persistence.memoryService === 'supabase-vector',
254
+ });
255
+ runner = new Runner({
256
+ agent: orchestrator,
257
+ appName,
258
+ sessionService,
259
+ memoryService,
260
+ });
261
+ }
262
+ else {
263
+ // ── In-Memory Mode ────────────────────────────────────────────────────
264
+ // WHY: No Supabase credentials found. Use InMemorySessionService for
265
+ // session state (volatile, process-scoped). Memory service is omitted
266
+ // since it requires Supabase to function.
267
+ const sessionService = new InMemorySessionService();
268
+ runner = new Runner({
269
+ agent: orchestrator,
270
+ appName,
271
+ sessionService,
272
+ memoryService: undefined,
273
+ });
274
+ }
275
+ // WHY: We create the session explicitly so we control the ID and can
276
+ // display it in the banner. The session record lives in whichever
277
+ // sessionService was wired into the runner.
278
+ await runner.sessionService.createSession({
279
+ appName,
280
+ userId: SESSION_USER_ID,
281
+ sessionId: SESSION_ID,
282
+ state: {}
283
+ });
284
+ // Now that the session is live, render the startup banner with the session ID.
285
+ banner(config, persistence, mergedBindings, SESSION_ID);
286
+ // Suppress verbose ADK info and benign warnings
287
+ const originalInfo = console.info;
288
+ console.info = (...args) => {
289
+ if (typeof args[0] === 'string' && args[0].includes('[ADK]'))
290
+ return;
291
+ originalInfo(...args);
292
+ };
293
+ const originalWarn = console.warn;
294
+ console.warn = (...args) => {
295
+ if (typeof args[0] === 'string' && args[0].includes('[ADK]') && args[0].includes('Event from an unknown agent'))
296
+ return;
297
+ originalWarn(...args);
298
+ };
299
+ const originalLog = console.log;
300
+ console.log = (...args) => {
301
+ if (typeof args[0] === 'string' && args[0].includes('[ADK]'))
302
+ return;
303
+ originalLog(...args);
304
+ };
305
+ // Strip --bind/--bindings/--syndicate pairs AND bare '--' separators from
306
+ // argv to get the actual user query. The bare '--' leaks in when npm passes
307
+ // the named syndicate shortcuts (e.g. syndicate:image) to the node process.
308
+ const rawArgs = process.argv.slice(2);
309
+ const queryParts = [];
310
+ for (let i = 0; i < rawArgs.length; i++) {
311
+ if (rawArgs[i] === '--')
312
+ continue; // drop bare separator
313
+ if ((rawArgs[i] === '--bind' || rawArgs[i] === '--bindings' || rawArgs[i] === '--syndicate') && i + 1 < rawArgs.length) {
314
+ i++; // skip the value
315
+ }
316
+ else {
317
+ queryParts.push(rawArgs[i]);
318
+ }
319
+ }
320
+ const cliInput = queryParts.join(' ');
321
+ async function runChat(trimmed) {
322
+ try {
323
+ // WHY: runAsync (not runEphemeral) is used here so that the runner
324
+ // reads the existing session from InMemorySessionService, appends the
325
+ // new user message, runs the agent, and writes the assistant reply back
326
+ // — preserving full multi-turn history within this process lifetime.
327
+ let stream = runner.runAsync({
328
+ userId: SESSION_USER_ID,
329
+ sessionId: SESSION_ID,
330
+ newMessage: { role: 'user', parts: [{ text: trimmed }] }
331
+ });
332
+ stream = traceAgentRun(stream, {
333
+ syndicateName: config.syndicate_name || 'melchizedek-syndicate',
334
+ bindings: mergedBindings,
335
+ input: trimmed
336
+ });
337
+ let currentMode = 'none';
338
+ for await (const event of stream) {
339
+ const calls = getFunctionCalls(event);
340
+ if (calls && calls.length > 0) {
341
+ for (const call of calls) {
342
+ if (call.name === 'transfer_to_agent') {
343
+ console.log(`\n${c.dim}[${event.author} is delegating to subagent: ${JSON.stringify(call.args)}]${c.reset}`);
344
+ }
345
+ else {
346
+ console.log(`\n${c.dim}[${event.author} is calling tool: ${call.name}]${c.reset}`);
347
+ }
348
+ }
349
+ }
350
+ const responses = getFunctionResponses(event);
351
+ if (responses && responses.length > 0) {
352
+ for (const response of responses) {
353
+ console.log(`\n${c.dim}[Received response from: ${response.name}]${c.reset}`);
354
+ }
355
+ }
356
+ // WHY: LlmResponse can carry errorCode/errorMessage (e.g. billing errors,
357
+ // rate limits, invalid model) without raising a JS exception. Without this
358
+ // check those errors are silently dropped, producing a blank response.
359
+ // NOTE: 'STOP' is a normal finishReason mapped to errorCode by ADK when content is empty.
360
+ const evAny = event;
361
+ if ((evAny.errorCode || evAny.errorMessage) && evAny.errorCode !== 'STOP') {
362
+ console.error(`\n${c.yellow}⚠ [${evAny.errorCode ?? 'ERROR'}] ${evAny.errorMessage ?? ''}${c.reset}`);
363
+ }
364
+ if (event.content && event.content.parts) {
365
+ for (const part of event.content.parts) {
366
+ if (part.thought) {
367
+ if (currentMode !== 'thinking') {
368
+ console.log(`\n${c.cyan}✦ ${event.author} is thinking...${c.reset}`);
369
+ currentMode = 'thinking';
370
+ }
371
+ process.stdout.write(c.dim + part.text + c.reset);
372
+ }
373
+ else if (part.text) {
374
+ if (currentMode === 'thinking') {
375
+ console.log(`\n\n${c.magenta}${c.bold}${event.author}${c.reset} › `);
376
+ currentMode = 'text';
377
+ }
378
+ else if (currentMode === 'none') {
379
+ process.stdout.write(`\n${c.magenta}${c.bold}${event.author}${c.reset} › `);
380
+ currentMode = 'text';
381
+ }
382
+ process.stdout.write(part.text);
383
+ }
384
+ else if (part.inlineData) {
385
+ // WHY: Image generation models return binary data as base64-encoded
386
+ // inlineData parts. We detect these, decode them, and save to disk
387
+ // so the user gets a real file rather than raw base64 in the terminal.
388
+ const { mimeType, data } = part.inlineData;
389
+ const ext = mimeType?.split('/')[1] ?? 'png';
390
+ const outputDir = join(process.cwd(), 'outputs');
391
+ mkdirSync(outputDir, { recursive: true });
392
+ const filename = `image_${Date.now()}.${ext}`;
393
+ const filepath = join(outputDir, filename);
394
+ writeFileSync(filepath, Buffer.from(data, 'base64'));
395
+ process.stdout.write(`\n${c.green}✓ Image saved → outputs/${filename}${c.reset}\n`);
396
+ }
397
+ }
398
+ }
399
+ }
400
+ console.log('\n');
401
+ }
402
+ catch (err) {
403
+ const msg = err instanceof Error ? err.message : String(err);
404
+ console.error(`\n${c.yellow}⚠ Error: ${msg}${c.reset}\n`);
405
+ }
406
+ }
407
+ if (cliInput) {
408
+ console.log(`${c.green}${c.bold}You${c.reset} › ${cliInput}`);
409
+ await runChat(cliInput);
410
+ // WHY: After a single-shot CLI invocation, ingest the session into
411
+ // long-term memory before exiting. This ensures even one-off queries
412
+ // contribute to the agent's knowledge base.
413
+ await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
414
+ return;
415
+ }
416
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
417
+ const ask = () => {
418
+ rl.question(`${c.green}${c.bold}You${c.reset} › `, async (userInput) => {
419
+ const trimmed = userInput.trim();
420
+ if (!trimmed) {
421
+ ask();
422
+ return;
423
+ }
424
+ if (['exit', 'quit', 'bye'].includes(trimmed.toLowerCase())) {
425
+ // WHY: On session end, ingest the full conversation into long-term
426
+ // memory. This is the critical moment where ephemeral session data
427
+ // becomes persistent knowledge — the Runner's memoryService
428
+ // extracts facts, embeds them, and stores them for future retrieval.
429
+ console.log(`\n${c.dim} Ingesting session into long-term memory...${c.reset}`);
430
+ await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
431
+ console.log(`${c.cyan} Goodbye! 👋${c.reset}\n`);
432
+ rl.close();
433
+ return;
434
+ }
435
+ await runChat(trimmed);
436
+ ask();
437
+ });
438
+ };
439
+ // WHY: If the user forcefully exits the CLI (Ctrl+C), we must intercept the
440
+ // termination signal to ensure the current session is gracefully ingested
441
+ // into long-term memory before the process dies.
442
+ process.on('SIGINT', async () => {
443
+ console.log(`\n${c.dim} Intercepted exit signal. Ingesting session into long-term memory...${c.reset}`);
444
+ await ingestSessionMemory(runner, appName, SESSION_USER_ID, SESSION_ID);
445
+ console.log(`${c.cyan} Goodbye! 👋${c.reset}\n`);
446
+ process.exit(0);
447
+ });
448
+ ask();
449
+ }
450
+ /**
451
+ * Ingests the current session into the Runner's memory service.
452
+ *
453
+ * WHY: The ADK Runner does not automatically call addSessionToMemory() —
454
+ * that responsibility falls to the application layer. We fetch the full
455
+ * session (with all accumulated events) from the session service and pass
456
+ * it to the memory service's extraction pipeline. This is where session
457
+ * transcripts get compressed into semantic facts and stored for future recall.
458
+ */
459
+ async function ingestSessionMemory(runner, appName, userId, sessionId) {
460
+ if (!runner.memoryService)
461
+ return;
462
+ try {
463
+ const session = await runner.sessionService.getSession({
464
+ appName,
465
+ userId,
466
+ sessionId,
467
+ });
468
+ if (!session || session.events.length === 0) {
469
+ console.log(`${c.dim} No session data to ingest.${c.reset}`);
470
+ return;
471
+ }
472
+ await runner.memoryService.addSessionToMemory(session);
473
+ console.log(`${c.green} ✓ Session ingested into long-term memory.${c.reset}`);
474
+ }
475
+ catch (err) {
476
+ const msg = err instanceof Error ? err.message : String(err);
477
+ console.error(`${c.yellow} ⚠ Memory ingestion failed: ${msg}${c.reset}`);
478
+ }
479
+ }
480
+ main().catch((err) => {
481
+ if (err.code !== 'ERR_USE_AFTER_CLOSE') {
482
+ console.error(err);
483
+ process.exit(1);
484
+ }
485
+ });
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "melchizedek-agents",
3
+ "version": "0.9.0",
4
+ "description": "A multi-model, multi-agent orchestration framework on Google ADK — YAML-defined syndicates, Supabase session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "//": "adk + genai are pinned exact in dev: newer genai minors change response part shapes and silently break the chat printer. Bump deliberately, verify a live run, then update the pin. adk itself is a PEER dependency — the consumer's app owns the ADK instance so the model registry is a singleton.",
8
+ "engines": {
9
+ "node": ">=22.0.0"
10
+ },
11
+ "exports": {
12
+ ".": "./dist/lib/index.js",
13
+ "./loadSyndicate": "./dist/lib/loadSyndicate.js",
14
+ "./loadEnv": "./dist/lib/loadEnv.js",
15
+ "./dispatch": "./dist/lib/dispatch.js",
16
+ "./grounding": "./dist/lib/grounding.js",
17
+ "./config": "./dist/lib/config.js",
18
+ "./models/*": "./dist/lib/models/*.js",
19
+ "./tools": "./dist/lib/toolRegistry.js",
20
+ "./tools/*": "./dist/lib/tools/*.js",
21
+ "./memory": "./dist/lib/memory/supabaseMemoryService.js",
22
+ "./session/*": "./dist/lib/session/*.js",
23
+ "./persistence/*": "./dist/lib/persistence/*.js",
24
+ "./observability/*": "./dist/lib/observability/*.js",
25
+ "./wiki/*": "./dist/lib/wiki/*.js",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "bin": {
29
+ "melchizedek-serve": "./dist/scripts/a2a_server.js",
30
+ "melchizedek-chat": "./dist/scripts/syndicate_chat.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "config/agents/README.md",
35
+ "config/agents/syndicateSchema.yaml",
36
+ "config/agents/examples",
37
+ "QUICKSTART.md",
38
+ "DOCUMENTATION.md",
39
+ "AGENT_SETUP.md"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "prepack": "npm run build",
44
+ "prepublishOnly": "npm test",
45
+ "chat:syndicate": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts",
46
+ "syndicate:synthesis": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate syndicate",
47
+ "syndicate:delegation": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate delegation",
48
+ "syndicate:critic": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate critic",
49
+ "syndicate:hierarchical": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate hierarchical",
50
+ "syndicate:style": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate style_council",
51
+ "syndicate:image": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate image_production",
52
+ "syndicate:ares": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate ares",
53
+ "syndicate:claude": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate claude",
54
+ "syndicate:advocate": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate patient_advocate",
55
+ "syndicate:tutor": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate tutor",
56
+ "syndicate:council": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate council",
57
+ "syndicate:librarian": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate librarian",
58
+ "syndicate:scriptorium": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate scriptorium",
59
+ "syndicate:cartographers": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate cartographers",
60
+ "demo:models": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_model_optionality.ts",
61
+ "demo:direct": "node --disable-warning=DEP0040 --experimental-strip-types scripts/direct_call.ts",
62
+ "mcp:demo": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_mcp_server.ts",
63
+ "mcp:wiki": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/mcp_server.ts",
64
+ "wiki:init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/init.ts",
65
+ "wiki:check": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/check.ts",
66
+ "start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
67
+ "test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
68
+ "memory": "node --disable-warning=DEP0040 --experimental-strip-types scripts/memory_admin.ts",
69
+ "db:purge": "node --disable-warning=DEP0040 --experimental-strip-types scripts/purge_supabase.ts"
70
+ },
71
+ "peerDependencies": {
72
+ "@google/adk": "^1.3.0"
73
+ },
74
+ "dependencies": {
75
+ "@a2a-js/sdk": "^0.3.13",
76
+ "@anthropic-ai/sdk": "^0.96.0",
77
+ "@google/genai": "2.2.0",
78
+ "@modelcontextprotocol/sdk": "^1.29.0",
79
+ "@opentelemetry/api": "^1.9.1",
80
+ "@opentelemetry/resources": "^2.7.1",
81
+ "@opentelemetry/sdk-trace-base": "^2.7.1",
82
+ "@opentelemetry/sdk-trace-node": "^2.7.1",
83
+ "@supabase/supabase-js": "^2.43.0",
84
+ "express": "^5.2.1",
85
+ "express-rate-limit": "^8.5.2",
86
+ "openai": "^6.48.0",
87
+ "yaml": "^2.9.0",
88
+ "zod": "^4.4.3"
89
+ },
90
+ "devDependencies": {
91
+ "@google/adk": "1.3.0",
92
+ "@types/express": "^5.0.6",
93
+ "@types/node": "^22",
94
+ "typescript": "^5.9.0"
95
+ }
96
+ }