melchizedek-agents 0.9.2 → 0.9.4

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 (39) hide show
  1. package/AGENT_SETUP.md +2 -2
  2. package/DOCUMENTATION.md +17 -24
  3. package/QUICKSTART.md +3 -2
  4. package/README.md +11 -9
  5. package/config/agents/examples/ares.yaml +0 -2
  6. package/config/agents/examples/augustin.yaml +0 -2
  7. package/config/agents/examples/cartographers.yaml +2 -5
  8. package/config/agents/examples/council.yaml +1 -1
  9. package/config/agents/examples/image_production.yaml +0 -1
  10. package/config/agents/examples/librarian.yaml +2 -4
  11. package/config/agents/examples/patient_advocate.yaml +2 -4
  12. package/config/agents/examples/scriptorium.yaml +2 -5
  13. package/config/agents/examples/style_council.yaml +2 -6
  14. package/config/agents/examples/syndicate.yaml +0 -3
  15. package/config/agents/examples/tutor.yaml +62 -32
  16. package/config/agents/syndicateSchema.yaml +4 -2
  17. package/dist/lib/compile.d.ts +64 -0
  18. package/dist/lib/compile.js +105 -0
  19. package/dist/lib/models/claudeLlm.js +34 -4
  20. package/dist/lib/models/gptLlm.js +6 -3
  21. package/dist/lib/models/ollamaLlm.d.ts +2 -0
  22. package/dist/lib/models/ollamaLlm.js +5 -1
  23. package/dist/lib/models/openAiCompatibleLlm.d.ts +49 -0
  24. package/dist/lib/models/openAiCompatibleLlm.js +233 -9
  25. package/dist/lib/models/registry.js +1 -1
  26. package/dist/lib/models/schemaNormalize.d.ts +9 -0
  27. package/dist/lib/models/schemaNormalize.js +34 -0
  28. package/dist/lib/observability/embeddings.d.ts +21 -0
  29. package/dist/lib/observability/embeddings.js +47 -0
  30. package/dist/lib/observability/lineage.d.ts +35 -0
  31. package/dist/lib/observability/lineage.js +96 -0
  32. package/dist/lib/observability/supabaseSpanExporter.d.ts +191 -18
  33. package/dist/lib/observability/supabaseSpanExporter.js +388 -80
  34. package/dist/lib/observability/tracer.d.ts +82 -0
  35. package/dist/lib/observability/tracer.js +348 -4
  36. package/dist/scripts/a2a_server.d.ts +10 -0
  37. package/dist/scripts/a2a_server.js +132 -75
  38. package/dist/scripts/syndicate_chat.js +41 -10
  39. package/package.json +8 -4
@@ -10,19 +10,46 @@ import { loadEnv } from "../lib/loadEnv.js";
10
10
  import { DefaultRequestHandler, InMemoryTaskStore } from '@a2a-js/sdk/server';
11
11
  import { AsyncLocalStorage } from 'async_hooks';
12
12
  import { agentCardHandler, jsonRpcHandler, restHandler, UserBuilder } from '@a2a-js/sdk/server/express';
13
- import { Runner, LlmAgent, AgentTool, InMemorySessionService, getFunctionCalls, getFunctionResponses, setLogLevel, LogLevel } from '@google/adk';
13
+ import { Runner, InMemorySessionService, getFunctionCalls, getFunctionResponses, setLogLevel, LogLevel } from '@google/adk';
14
14
  import { loadSyndicate } from "../lib/loadSyndicate.js";
15
15
  import { isDispatchSyndicate, matchRouteOverride, resolveRoute } from "../lib/dispatch.js";
16
16
  import { traceAgentRun } from "../lib/observability/tracer.js";
17
+ import { configDigest } from "../lib/observability/lineage.js";
17
18
  import { resolveModel } from "../lib/models/registry.js";
18
- import { resolveTools as resolveNamedTools } from "../lib/toolRegistry.js";
19
- import { createMcpTools } from "../lib/tools/mcpToolFactory.js";
19
+ import { compileGraph as compileGraphShared, compileSubagent as compileSubagentShared } from "../lib/compile.js";
20
20
  import { hasSupabaseCredentials, createSupabaseServices } from "../lib/persistence/supabaseProvider.js";
21
21
  import { ProjectedSessionService, renderTranscriptDigest } from "../lib/session/transcript.js";
22
22
  const requestContextStorage = new AsyncLocalStorage();
23
23
  const A2A_APP_NAME = 'melchizedek-a2a';
24
24
  /** X-User-Id must be short and filesystem/key-safe; anything else is rejected. */
25
25
  const SITE_USER_ID_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
26
+ /** X-Surface-* values obey the same charset rule, for the same reason. */
27
+ const SURFACE_VALUE_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
28
+ const SURFACE_HEADERS = [
29
+ ['x-surface', 'name'],
30
+ ['x-surface-guild', 'guild'],
31
+ ['x-surface-channel', 'channel'],
32
+ ['x-surface-user', 'user'],
33
+ ];
34
+ /**
35
+ * Root-span attributes for the caller's surface. Absent headers produce no
36
+ * attributes at all, so a caller that names nothing writes NULLs rather than
37
+ * a row full of 'unknown' — an unnamed surface and a surface named 'unknown'
38
+ * are different facts and the ledger should not conflate them.
39
+ */
40
+ function surfaceAttributes(ctx) {
41
+ const s = ctx?.surface;
42
+ if (!s)
43
+ return {};
44
+ const attrs = { 'surface.name': s.name };
45
+ if (s.guild)
46
+ attrs['surface.guild'] = s.guild;
47
+ if (s.channel)
48
+ attrs['surface.channel'] = s.channel;
49
+ if (s.user)
50
+ attrs['surface.user'] = s.user;
51
+ return attrs;
52
+ }
26
53
  /**
27
54
  * Memory/session silo derivation.
28
55
  * Base silo: a hash of the caller's API key (one bucket per credential).
@@ -105,16 +132,50 @@ function publishWorking(eventBus, taskId, contextId, text) {
105
132
  final: false
106
133
  });
107
134
  }
135
+ /** One human-readable line naming WHY a turn failed, for the failed task's
136
+ * status message — the text the Discord surface renders verbatim. Provider
137
+ * errors sometimes arrive as a raw JSON blob (Gemini ApiError:
138
+ * `{"error":{"code":503,"message":"...","status":"UNAVAILABLE"}}`), so the
139
+ * inner message is dug out before falling back to the string itself. An
140
+ * empty message keeps the old pointer at the server logs. */
141
+ export function describeTurnError(error) {
142
+ let msg = (error.message ?? '').trim();
143
+ if (msg.startsWith('{')) {
144
+ try {
145
+ const parsed = JSON.parse(msg);
146
+ const inner = parsed?.error?.message ?? parsed?.message;
147
+ if (typeof inner === 'string' && inner.trim())
148
+ msg = inner.trim();
149
+ }
150
+ catch { /* not JSON — use as-is */ }
151
+ }
152
+ if (!msg)
153
+ return 'see server logs for details.';
154
+ return msg.length > 300 ? `${msg.slice(0, 297)}...` : msg;
155
+ }
108
156
  // ── Native ADK AgentExecutor Bridge ───────────────────────────────────────
109
157
  class SyndicateExecutor {
110
158
  config;
111
159
  sessionService;
112
160
  memoryService;
161
+ /** Provenance stamp for every turn this executor serves (lineage.ts). */
162
+ configHash;
113
163
  constructor(config, sessionService, memoryService) {
114
164
  this.config = config;
115
165
  this.sessionService = sessionService;
116
166
  this.memoryService = memoryService;
117
167
  }
168
+ configHashFor() {
169
+ if (!this.configHash) {
170
+ try {
171
+ this.configHash = configDigest(this.config, (ref) => loadSyndicate(ref));
172
+ }
173
+ catch {
174
+ this.configHash = 'unhashable';
175
+ }
176
+ }
177
+ return this.configHash;
178
+ }
118
179
  /**
119
180
  * Run ONE agent for ONE turn against a named session, draining its event
120
181
  * stream. DELEGATE mode calls this once (the orchestrator); PLAN-DISPATCH
@@ -138,16 +199,37 @@ class SyndicateExecutor {
138
199
  newMessage: { role: 'user', parts: userParts },
139
200
  ...(this.config.max_steps !== undefined ? { maxSteps: this.config.max_steps } : {})
140
201
  });
202
+ let combinedText = '';
203
+ let lastToolResultText = '';
204
+ const invokedToolNames = new Set();
205
+ // The DELEGATE relay-fallback predicate, evaluated by the tracer in its
206
+ // finally (after the last event) so the turn row records whether the
207
+ // orchestrator's relay failed. Same rule as the substitution below.
208
+ const normalizeName = (s) => s.replace(/[^a-z0-9]/gi, '').toLowerCase();
209
+ const relayFallbackFired = () => {
210
+ const text = combinedText.trim();
211
+ const echoed = text.length > 0 && [...invokedToolNames].some((n) => normalizeName(n) === normalizeName(text));
212
+ return (!text || echoed) && !!lastToolResultText;
213
+ };
141
214
  if (params.trace) {
142
215
  stream = traceAgentRun(stream, {
143
216
  syndicateName: this.config.syndicate_name || 'melchizedek-syndicate',
144
217
  bindings: this.config.variables || {},
145
- input: userParts
218
+ input: userParts,
219
+ route: params.route,
220
+ // Identity: the CONVERSATION id (contextId), not the classifier's
221
+ // throwaway lane — every stage of one turn shares session/task ids.
222
+ sessionId: contextId,
223
+ userId,
224
+ taskId,
225
+ stage: params.stage,
226
+ configHash: this.configHashFor(),
227
+ attributes: surfaceAttributes(requestContextStorage.getStore()),
228
+ onEnd: () => ({
229
+ 'syndicate.relay_fallback': params.stage === 'delegate' && relayFallbackFired(),
230
+ }),
146
231
  });
147
232
  }
148
- let combinedText = '';
149
- let lastToolResultText = '';
150
- const invokedToolNames = new Set();
151
233
  let lastTokenTotal = 0;
152
234
  let lastThinkingTokens = 0;
153
235
  let error;
@@ -309,71 +391,19 @@ class SyndicateExecutor {
309
391
  apiKey: authContext.apiKey,
310
392
  defaultProvider: authContext.provider,
311
393
  });
312
- const resolveTools = (toolNames = []) => resolveNamedTools(toolNames, (name) => console.warn(`[A2A] WARNING: Unknown tool '${name}' — skipping.`));
313
- // Compile ADK Nodes dynamically.
314
- //
315
- // compileSubagent builds ONE runnable agent from a subagent entry. In
316
- // DELEGATE mode compileGraph wraps its result in an AgentTool; in
317
- // PLAN-DISPATCH the same agent is run directly as the chosen route, so
318
- // both methods dispatch to identical agents and can never drift.
319
- const compileSubagent = async (subCfg) => {
320
- if (subCfg.yaml_reference) {
321
- console.log(`[A2A] Loading nested syndicate: ${subCfg.yaml_reference}`);
322
- const nestedConfig = loadSyndicate(subCfg.yaml_reference);
323
- return compileGraph(nestedConfig, subCfg.name, subCfg.description);
324
- }
325
- const subTools = resolveTools(subCfg.tools);
326
- if (subCfg.mcp_server_url) {
327
- console.log(`[A2A] Loading MCP tools: ${subCfg.mcp_server_url}`);
328
- const mcpTools = await createMcpTools(subCfg.mcp_server_url);
329
- for (const mcpTool of mcpTools) {
330
- if (!subTools.some(t => t.name === mcpTool.name)) {
331
- subTools.push(mcpTool);
332
- }
333
- }
334
- }
335
- return new LlmAgent({
336
- name: subCfg.name,
337
- description: subCfg.description,
338
- model: resolveModelForRequest(subCfg.model),
339
- instruction: subCfg.instruction,
340
- tools: subTools.length > 0 ? subTools : undefined,
341
- outputSchema: subCfg.outputSchema,
342
- generateContentConfig: {
343
- ...subCfg.generateContentConfig,
344
- toolConfig: {
345
- ...subCfg.generateContentConfig?.toolConfig,
346
- includeServerSideToolInvocations: true
347
- }
348
- }
349
- });
350
- };
351
- const compileGraph = async (configObj, overrideName, overrideDesc) => {
352
- // PLAN-DISPATCH: the orchestrator gets NO subagent tools. ADK refuses
353
- // to combine outputSchema with AgentTool delegation on one agent (see
354
- // config/agents/critic.yaml), and the classifier needs the schema —
355
- // code, not the model, performs the hand-off.
356
- const compiledTools = isDispatchSyndicate(configObj)
357
- ? []
358
- : await Promise.all((configObj.subagents || []).map(async (subCfg) => new AgentTool({ agent: await compileSubagent(subCfg) })));
359
- const orchestratorDirectTools = resolveTools(configObj.orchestrator.tools);
360
- compiledTools.push(...orchestratorDirectTools);
361
- return new LlmAgent({
362
- name: overrideName || configObj.orchestrator.name,
363
- description: overrideDesc || configObj.orchestrator.description,
364
- model: resolveModelForRequest(configObj.orchestrator.model),
365
- instruction: configObj.orchestrator.instruction,
366
- tools: compiledTools.length > 0 ? compiledTools : undefined,
367
- outputSchema: configObj.orchestrator.outputSchema,
368
- generateContentConfig: {
369
- ...configObj.orchestrator.generateContentConfig,
370
- toolConfig: {
371
- ...configObj.orchestrator.generateContentConfig?.toolConfig,
372
- includeServerSideToolInvocations: true
373
- }
374
- }
375
- });
394
+ // Compile ADK nodes through the shared compiler (lib/compile.ts) — the
395
+ // observatory runs the same function, so the graph an eval measures is
396
+ // the graph this server serves. compileSubagent builds ONE runnable
397
+ // agent from a subagent entry; in DELEGATE mode compileGraph wraps it in
398
+ // an AgentTool, in PLAN-DISPATCH the same agent is run directly as the
399
+ // chosen route, so both methods dispatch to identical agents.
400
+ const compileOpts = {
401
+ resolveModel: resolveModelForRequest,
402
+ onUnknownTool: (name) => console.warn(`[A2A] WARNING: Unknown tool '${name}' — skipping.`),
403
+ log: (message) => console.log(`[A2A] ${message}`),
376
404
  };
405
+ const compileSubagent = (subCfg) => compileSubagentShared(subCfg, compileOpts);
406
+ const compileGraph = (configObj) => compileGraphShared(configObj, compileOpts);
377
407
  const ensureSession = async (sessionId) => {
378
408
  const existing = await this.sessionService.getSession({
379
409
  sessionId,
@@ -481,7 +511,8 @@ class SyndicateExecutor {
481
511
  taskId,
482
512
  contextId,
483
513
  eventBus,
484
- trace: false, // one cheap classification — not worth a span
514
+ trace: true, // one row per classification: misroutes are diagnosable from stored data
515
+ stage: 'classify',
485
516
  publishToolStatus: false, // a dispatch classifier holds no tools
486
517
  sessionService: routerSessions
487
518
  });
@@ -530,11 +561,13 @@ class SyndicateExecutor {
530
561
  contextId,
531
562
  eventBus,
532
563
  trace: true,
564
+ stage: 'dispatch',
533
565
  publishToolStatus: true,
566
+ route: resolution,
534
567
  sessionService: new ProjectedSessionService(this.sessionService, routeCfg.name)
535
568
  });
536
569
  if (turn.error) {
537
- failTask(`Error: [${turn.error.code}] ${resolution.route} failed to answer. See server logs for details.`);
570
+ failTask(`Error: [${turn.error.code}] ${resolution.route} failed to answer — ${describeTurnError(turn.error)}`);
538
571
  return;
539
572
  }
540
573
  tokenInfo = turn.tokenInfo;
@@ -560,10 +593,11 @@ class SyndicateExecutor {
560
593
  contextId,
561
594
  eventBus,
562
595
  trace: true,
596
+ stage: 'delegate',
563
597
  publishToolStatus: true
564
598
  });
565
599
  if (turn.error) {
566
- failTask(`Error: [${turn.error.code}] The agent run failed. See server logs for details.`);
600
+ failTask(`Error: [${turn.error.code}] The agent run failed — ${describeTurnError(turn.error)}`);
567
601
  return;
568
602
  }
569
603
  tokenInfo = turn.tokenInfo;
@@ -809,7 +843,30 @@ export async function startServer(syndicateName = 'syndicate.yaml') {
809
843
  }
810
844
  siteUserId = rawUserId;
811
845
  }
812
- requestContextStorage.run({ apiKey, provider, siteUserId }, () => {
846
+ // X-Surface-* (optional): where this request came from, for telemetry
847
+ // only. Same validation as X-User-Id, and a bad value is refused rather
848
+ // than dropped — a silently ignored header makes a dashboard lie about
849
+ // coverage. `X-Surface` is required for the others to mean anything.
850
+ let surface;
851
+ for (const [header, field] of SURFACE_HEADERS) {
852
+ const raw = req.headers[header];
853
+ if (raw === undefined || raw === '')
854
+ continue;
855
+ if (!SURFACE_VALUE_PATTERN.test(raw)) {
856
+ res.status(400).json({ error: `Invalid ${header}: must match [A-Za-z0-9._-]{1,64}` });
857
+ return;
858
+ }
859
+ if (field === 'name') {
860
+ surface = { name: raw };
861
+ continue;
862
+ }
863
+ if (!surface) {
864
+ res.status(400).json({ error: `${header} requires X-Surface to name the surface` });
865
+ return;
866
+ }
867
+ surface[field] = raw;
868
+ }
869
+ requestContextStorage.run({ apiKey, provider, siteUserId, surface }, () => {
813
870
  next();
814
871
  });
815
872
  });
@@ -5,7 +5,7 @@ import { join } from 'node:path';
5
5
  import { loadSyndicate, parseCliBindings } from "../lib/loadSyndicate.js";
6
6
  import { traceAgentRun } from "../lib/observability/tracer.js";
7
7
  // ADK Imports
8
- import { LlmAgent, Runner, InMemorySessionService, getFunctionCalls, getFunctionResponses, AgentTool, setLogLevel, LogLevel } from '@google/adk';
8
+ import { LlmAgent, Runner, InMemorySessionService, getFunctionCalls, getFunctionResponses, AgentTool, setLogLevel, LogLevel, StreamingMode } from '@google/adk';
9
9
  import { randomUUID } from 'node:crypto';
10
10
  // Tool resolution (shared registry) + MCP factory
11
11
  import { resolveTools as resolveNamedTools } from "../lib/toolRegistry.js";
@@ -94,6 +94,12 @@ function banner(config, persistence, bindings = {}, sessionId) {
94
94
  }
95
95
  async function main() {
96
96
  loadEnv(import.meta.url);
97
+ // Interactive chat is for reading the agent, not its telemetry: the
98
+ // [OTEL_SPAN_JSON] lines are off here unless OTEL_CONSOLE_SPANS is set
99
+ // (in .env, which loadEnv() has just read, or in the shell). Spans still
100
+ // reach the in-process listeners and the Supabase sink either way.
101
+ if (process.env.OTEL_CONSOLE_SPANS === undefined)
102
+ process.env.OTEL_CONSOLE_SPANS = 'false';
97
103
  // ── Register LLM providers ───────────────────────────────────────────────
98
104
  // WHY: Must run AFTER loadEnv() so that API keys from .env are available.
99
105
  // One call registers every adapter whose credentials exist (Ollama needs
@@ -119,14 +125,17 @@ async function main() {
119
125
  break;
120
126
  }
121
127
  }
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 };
128
+ // ONLY what the caller actually asked for on the command line.
129
+ //
130
+ // WHY nothing is seeded here: loadSyndicate merges these OVER a syndicate's
131
+ // own `variables:` block, so a hardcoded default is not a fallback — it is
132
+ // an override applied to every syndicate. `headline_count: 3` used to ride
133
+ // along into unrelated agents (a Tutor turn announced it in the banner and
134
+ // recorded it in syndicate.bindings telemetry) while silently beating the
135
+ // 5 that syndicate.yaml declares for itself. Universal tokens belong to
136
+ // defaultBindings() in lib/loadSyndicate.ts, which the loader applies UNDER
137
+ // the YAML — that is where current_date lives, timezone-pinned.
138
+ const mergedBindings = parseCliBindings(argv);
130
139
  // Load the syndicate definition
131
140
  const config = loadSyndicate(syndicateFile, {
132
141
  bindings: mergedBindings,
@@ -162,6 +171,9 @@ async function main() {
162
171
  // people) — otherwise every CLI session shares the 'local-user' bucket.
163
172
  const SESSION_USER_ID = process.env.MELCHIZEDEK_USER_ID?.trim() || 'local-user';
164
173
  const SESSION_ID = randomUUID();
174
+ // Live token-by-token output. Off gives one block per turn — useful when
175
+ // piping a transcript, or for an agent whose reply is structured JSON.
176
+ const STREAM_REPLIES = process.env.CHAT_STREAMING !== 'false';
165
177
  // ── Detect persistence mode ──────────────────────────────────────────────
166
178
  const persistence = detectPersistenceConfig(config.memory_system);
167
179
  // ── Tool resolver ─────────────────────────────────────────────────────────
@@ -327,7 +339,13 @@ async function main() {
327
339
  let stream = runner.runAsync({
328
340
  userId: SESSION_USER_ID,
329
341
  sessionId: SESSION_ID,
330
- newMessage: { role: 'user', parts: [{ text: trimmed }] }
342
+ newMessage: { role: 'user', parts: [{ text: trimmed }] },
343
+ // 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
+ }
331
349
  });
332
350
  stream = traceAgentRun(stream, {
333
351
  syndicateName: config.syndicate_name || 'melchizedek-syndicate',
@@ -335,7 +353,13 @@ async function main() {
335
353
  input: trimmed
336
354
  });
337
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;
338
361
  for await (const event of stream) {
362
+ const isPartial = event.partial === true;
339
363
  const calls = getFunctionCalls(event);
340
364
  if (calls && calls.length > 0) {
341
365
  for (const call of calls) {
@@ -371,6 +395,8 @@ async function main() {
371
395
  process.stdout.write(c.dim + part.text + c.reset);
372
396
  }
373
397
  else if (part.text) {
398
+ if (!isPartial && streamedText)
399
+ continue;
374
400
  if (currentMode === 'thinking') {
375
401
  console.log(`\n\n${c.magenta}${c.bold}${event.author}${c.reset} › `);
376
402
  currentMode = 'text';
@@ -380,6 +406,8 @@ async function main() {
380
406
  currentMode = 'text';
381
407
  }
382
408
  process.stdout.write(part.text);
409
+ if (isPartial)
410
+ streamedText = true;
383
411
  }
384
412
  else if (part.inlineData) {
385
413
  // WHY: Image generation models return binary data as base64-encoded
@@ -396,6 +424,9 @@ async function main() {
396
424
  }
397
425
  }
398
426
  }
427
+ // A complete event ends the turn; the next one starts fresh.
428
+ if (!isPartial)
429
+ streamedText = false;
399
430
  }
400
431
  console.log('\n');
401
432
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "melchizedek-agents",
3
- "version": "0.9.2",
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.",
3
+ "version": "0.9.4",
4
+ "description": "A multi-model, multi-agent orchestration framework on Google ADK \u2014 YAML-defined syndicates, Supabase session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
5
5
  "type": "module",
6
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.",
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 \u2014 the consumer's app owns the ADK instance so the model registry is a singleton.",
8
8
  "engines": {
9
9
  "node": ">=22.0.0"
10
10
  },
@@ -67,7 +67,10 @@
67
67
  "start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
68
68
  "test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
69
69
  "memory": "node --disable-warning=DEP0040 --experimental-strip-types scripts/memory_admin.ts",
70
- "db:purge": "node --disable-warning=DEP0040 --experimental-strip-types scripts/purge_supabase.ts"
70
+ "db:purge": "node --disable-warning=DEP0040 --experimental-strip-types scripts/purge_supabase.ts",
71
+ "telemetry:stats": "node --disable-warning=DEP0040 --experimental-strip-types scripts/telemetry_admin.ts stats",
72
+ "telemetry:prune": "node --disable-warning=DEP0040 --experimental-strip-types scripts/telemetry_admin.ts prune",
73
+ "telemetry:replay": "node --disable-warning=DEP0040 --experimental-strip-types scripts/telemetry_admin.ts replay"
71
74
  },
72
75
  "peerDependencies": {
73
76
  "@google/adk": "^1.3.0"
@@ -78,6 +81,7 @@
78
81
  "@google/genai": "2.2.0",
79
82
  "@modelcontextprotocol/sdk": "^1.29.0",
80
83
  "@opentelemetry/api": "^1.9.1",
84
+ "@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
81
85
  "@opentelemetry/resources": "^2.7.1",
82
86
  "@opentelemetry/sdk-trace-base": "^2.7.1",
83
87
  "@opentelemetry/sdk-trace-node": "^2.7.1",