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.
- package/AGENT_SETUP.md +182 -0
- package/DOCUMENTATION.md +614 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +164 -0
- package/README.md +151 -0
- package/config/agents/README.md +27 -0
- package/config/agents/examples/ares.yaml +74 -0
- package/config/agents/examples/cartographers.yaml +115 -0
- package/config/agents/examples/claude.yaml +17 -0
- package/config/agents/examples/council.yaml +65 -0
- package/config/agents/examples/critic.yaml +74 -0
- package/config/agents/examples/delegation.yaml +29 -0
- package/config/agents/examples/hierarchical.yaml +26 -0
- package/config/agents/examples/image_production.yaml +112 -0
- package/config/agents/examples/librarian.yaml +75 -0
- package/config/agents/examples/model_zoo.yaml +81 -0
- package/config/agents/examples/patient_advocate.yaml +107 -0
- package/config/agents/examples/scriptorium.yaml +96 -0
- package/config/agents/examples/style_council.yaml +83 -0
- package/config/agents/examples/syndicate.yaml +34 -0
- package/config/agents/examples/tutor.yaml +66 -0
- package/config/agents/syndicateSchema.yaml +291 -0
- package/dist/lib/config.d.ts +108 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/dispatch.d.ts +133 -0
- package/dist/lib/dispatch.js +182 -0
- package/dist/lib/grounding.d.ts +28 -0
- package/dist/lib/grounding.js +61 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/loadEnv.d.ts +21 -0
- package/dist/lib/loadEnv.js +58 -0
- package/dist/lib/loadSyndicate.d.ts +177 -0
- package/dist/lib/loadSyndicate.js +264 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
- package/dist/lib/memory/supabaseMemoryService.js +499 -0
- package/dist/lib/models/claudeLlm.d.ts +93 -0
- package/dist/lib/models/claudeLlm.js +332 -0
- package/dist/lib/models/gptLlm.d.ts +110 -0
- package/dist/lib/models/gptLlm.js +497 -0
- package/dist/lib/models/grokLlm.d.ts +64 -0
- package/dist/lib/models/grokLlm.js +86 -0
- package/dist/lib/models/ollamaLlm.d.ts +105 -0
- package/dist/lib/models/ollamaLlm.js +134 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
- package/dist/lib/models/openAiCompatibleLlm.js +331 -0
- package/dist/lib/models/providerMap.d.ts +28 -0
- package/dist/lib/models/providerMap.js +45 -0
- package/dist/lib/models/registry.d.ts +71 -0
- package/dist/lib/models/registry.js +159 -0
- package/dist/lib/models/schemaNormalize.d.ts +22 -0
- package/dist/lib/models/schemaNormalize.js +58 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
- package/dist/lib/observability/supabaseSpanExporter.js +115 -0
- package/dist/lib/observability/tracer.d.ts +44 -0
- package/dist/lib/observability/tracer.js +242 -0
- package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
- package/dist/lib/persistence/supabaseProvider.js +88 -0
- package/dist/lib/session/supabaseSessionService.d.ts +26 -0
- package/dist/lib/session/supabaseSessionService.js +193 -0
- package/dist/lib/session/transcript.d.ts +193 -0
- package/dist/lib/session/transcript.js +309 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +58 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
- package/dist/lib/tools/collectionsSearchTool.js +86 -0
- package/dist/lib/tools/generateImageTool.d.ts +16 -0
- package/dist/lib/tools/generateImageTool.js +119 -0
- package/dist/lib/tools/inspectImageTool.d.ts +21 -0
- package/dist/lib/tools/inspectImageTool.js +97 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +116 -0
- package/dist/lib/tools/toolContract.d.ts +78 -0
- package/dist/lib/tools/toolContract.js +129 -0
- package/dist/lib/tools/webExtractTool.d.ts +70 -0
- package/dist/lib/tools/webExtractTool.js +461 -0
- package/dist/lib/tools/webSearchTool.d.ts +53 -0
- package/dist/lib/tools/webSearchTool.js +126 -0
- package/dist/lib/tools/wikiTools.d.ts +112 -0
- package/dist/lib/tools/wikiTools.js +697 -0
- package/dist/lib/tools/xSearchTool.d.ts +61 -0
- package/dist/lib/tools/xSearchTool.js +125 -0
- package/dist/lib/wiki/agentRun.d.ts +39 -0
- package/dist/lib/wiki/agentRun.js +79 -0
- package/dist/lib/wiki/builder.d.ts +71 -0
- package/dist/lib/wiki/builder.js +165 -0
- package/dist/lib/wiki/dive.d.ts +56 -0
- package/dist/lib/wiki/dive.js +208 -0
- package/dist/lib/wiki/entities.d.ts +220 -0
- package/dist/lib/wiki/entities.js +661 -0
- package/dist/lib/wiki/extract.d.ts +97 -0
- package/dist/lib/wiki/extract.js +305 -0
- package/dist/lib/wiki/fill.d.ts +37 -0
- package/dist/lib/wiki/fill.js +175 -0
- package/dist/lib/wiki/format.d.ts +132 -0
- package/dist/lib/wiki/format.js +177 -0
- package/dist/lib/wiki/graph.d.ts +80 -0
- package/dist/lib/wiki/graph.js +147 -0
- package/dist/lib/wiki/lint.d.ts +58 -0
- package/dist/lib/wiki/lint.js +267 -0
- package/dist/lib/wiki/markdown.d.ts +127 -0
- package/dist/lib/wiki/markdown.js +336 -0
- package/dist/lib/wiki/pipeline.d.ts +69 -0
- package/dist/lib/wiki/pipeline.js +119 -0
- package/dist/lib/wiki/vault.d.ts +84 -0
- package/dist/lib/wiki/vault.js +194 -0
- package/dist/scripts/a2a_server.d.ts +2 -0
- package/dist/scripts/a2a_server.js +997 -0
- package/dist/scripts/syndicate_chat.d.ts +2 -0
- package/dist/scripts/syndicate_chat.js +485 -0
- package/package.json +96 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { DispatchConfig } from './dispatch.ts';
|
|
2
|
+
export type { DispatchConfig } from './dispatch.ts';
|
|
3
|
+
export interface ThinkingConfig {
|
|
4
|
+
/** Token budget for internal reasoning. 0 = off, -1 = dynamic. */
|
|
5
|
+
thinkingBudget?: number;
|
|
6
|
+
/** Stream the thinking trace in the response. */
|
|
7
|
+
includeThoughts?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SafetySetting {
|
|
10
|
+
category: string;
|
|
11
|
+
threshold: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Maps to GenerateContentConfig from @google/genai.
|
|
15
|
+
* Controls model sampling and generation behaviour.
|
|
16
|
+
* ⚠ Do NOT set `tools` here — use the top-level `tools` field on the agent.
|
|
17
|
+
* ⚠ Do NOT set `thinkingConfig` via the tools param — use this block.
|
|
18
|
+
*/
|
|
19
|
+
export interface GenerateContentConfig {
|
|
20
|
+
temperature?: number;
|
|
21
|
+
topP?: number;
|
|
22
|
+
topK?: number;
|
|
23
|
+
maxOutputTokens?: number;
|
|
24
|
+
stopSequences?: string[];
|
|
25
|
+
candidateCount?: number;
|
|
26
|
+
presencePenalty?: number;
|
|
27
|
+
frequencyPenalty?: number;
|
|
28
|
+
seed?: number;
|
|
29
|
+
responseMimeType?: string;
|
|
30
|
+
safetySettings?: SafetySetting[];
|
|
31
|
+
thinkingConfig?: ThinkingConfig;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Maps to LlmAgentConfig (+ BaseAgentConfig) from @google/adk.
|
|
35
|
+
*/
|
|
36
|
+
export interface AgentYamlConfig {
|
|
37
|
+
/** Unique agent name. Must be a valid JS identifier. Cannot be "user". */
|
|
38
|
+
name: string;
|
|
39
|
+
/** One-line description used by other agents to decide when to delegate here. */
|
|
40
|
+
description?: string;
|
|
41
|
+
/** Gemini model identifier. Subagents inherit this if not set. */
|
|
42
|
+
model: string;
|
|
43
|
+
/** System prompt / persona. Maps to LlmAgentConfig.instruction. */
|
|
44
|
+
instruction: string;
|
|
45
|
+
/**
|
|
46
|
+
* Global instruction applied to ALL agents in the tree.
|
|
47
|
+
* Only the root agent's value takes effect.
|
|
48
|
+
*/
|
|
49
|
+
globalInstruction?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Named built-in tools to attach to this agent.
|
|
52
|
+
* Supported: "google_search", "code_execution"
|
|
53
|
+
* Maps to LlmAgentConfig.tools[].
|
|
54
|
+
*/
|
|
55
|
+
tools?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* Whether conversation history is included in model requests.
|
|
58
|
+
* "default" = stateful, "none" = stateless.
|
|
59
|
+
* Maps to LlmAgentConfig.includeContents.
|
|
60
|
+
*/
|
|
61
|
+
includeContents?: 'default' | 'none';
|
|
62
|
+
/** Prevents LLM from transferring control back to parent agent. */
|
|
63
|
+
disallowTransferToParent?: boolean;
|
|
64
|
+
/** Prevents LLM from transferring control to peer agents. */
|
|
65
|
+
disallowTransferToPeers?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Saves final reply to session state under this key.
|
|
68
|
+
* Useful for chaining agents.
|
|
69
|
+
*/
|
|
70
|
+
outputKey?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Model generation config (temperature, topP, safety, thinking, etc.)
|
|
73
|
+
* Maps to LlmAgentConfig.generateContentConfig.
|
|
74
|
+
*/
|
|
75
|
+
generateContentConfig?: GenerateContentConfig;
|
|
76
|
+
/**
|
|
77
|
+
* Output schema for structured JSON responses.
|
|
78
|
+
* Maps to LlmAgentConfig.outputSchema.
|
|
79
|
+
*/
|
|
80
|
+
outputSchema?: Record<string, unknown>;
|
|
81
|
+
mcp_server_url?: string;
|
|
82
|
+
orchestration?: {
|
|
83
|
+
role?: 'primary' | 'sub-agent';
|
|
84
|
+
delegates?: string[];
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface SubagentYamlConfig extends Partial<AgentYamlConfig> {
|
|
88
|
+
/** Required when used as AgentTool — orchestrator reads this to delegate. */
|
|
89
|
+
name: string;
|
|
90
|
+
description: string;
|
|
91
|
+
yaml_reference?: string;
|
|
92
|
+
}
|
|
93
|
+
export type VariableMap = Record<string, string | number | boolean>;
|
|
94
|
+
export interface SyndicateYamlConfig {
|
|
95
|
+
syndicate_name: string;
|
|
96
|
+
orchestrator: AgentYamlConfig;
|
|
97
|
+
subagents: SubagentYamlConfig[];
|
|
98
|
+
/**
|
|
99
|
+
* Opts this syndicate into PLAN-DISPATCH orchestration: the orchestrator is
|
|
100
|
+
* a tool-less classifier whose `outputSchema` names one route, and code runs
|
|
101
|
+
* that subagent directly so its output is the answer — no relay turn.
|
|
102
|
+
* Absent = classic DELEGATE mode (subagents as AgentTools).
|
|
103
|
+
* See lib/dispatch.ts for the full contract.
|
|
104
|
+
*/
|
|
105
|
+
dispatch?: DispatchConfig;
|
|
106
|
+
/** Optional default variable definitions for {{token}} interpolation. */
|
|
107
|
+
variables?: VariableMap;
|
|
108
|
+
/** Defines the persistence and semantic memory layer for the syndicate. */
|
|
109
|
+
memory_system?: 'internal-only' | 'session-only' | 'long-term';
|
|
110
|
+
/**
|
|
111
|
+
* Domain rules appended to the shared fact-extraction prompt for THIS
|
|
112
|
+
* syndicate only. The prompt is global (every long-term memory consumer
|
|
113
|
+
* shares it), so anything domain-specific — "never store a market quote" —
|
|
114
|
+
* belongs here rather than edited into the prompt itself.
|
|
115
|
+
* Read by the A2A server at ingestion time; ignored without `long-term`.
|
|
116
|
+
*/
|
|
117
|
+
memory_extraction_rules?: string;
|
|
118
|
+
/** Optional hard technical limit for maximum ADK runner loops (LLM -> Tool cycles). */
|
|
119
|
+
max_steps?: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Options for `loadSyndicate()`.
|
|
123
|
+
*/
|
|
124
|
+
export interface LoadSyndicateOptions {
|
|
125
|
+
/** Variable bindings that resolve `{{token}}` placeholders in the YAML. */
|
|
126
|
+
bindings?: VariableMap;
|
|
127
|
+
/**
|
|
128
|
+
* Direct property overrides applied after binding interpolation.
|
|
129
|
+
*/
|
|
130
|
+
overrides?: Partial<SyndicateYamlConfig>;
|
|
131
|
+
/**
|
|
132
|
+
* Root directory the loader is jailed to. Resolution order:
|
|
133
|
+
* this option → `MELCHIZEDEK_AGENTS_DIR` env → `<cwd>/config/agents`.
|
|
134
|
+
* Lets a consumer repo keep its syndicates anywhere (the engine is a
|
|
135
|
+
* package, not a place). NOTE: nested `yaml_reference:` loads made by
|
|
136
|
+
* callers that don't thread options through resolve via env/default,
|
|
137
|
+
* so a non-default root is best set process-wide via the env var.
|
|
138
|
+
*/
|
|
139
|
+
agentsDir?: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Parse CLI variable bindings into a VariableMap.
|
|
143
|
+
*
|
|
144
|
+
* Supports two formats:
|
|
145
|
+
* --bindings '{"focus_area": "AI Regulation", "headline_count": 10}'
|
|
146
|
+
* --bind key=value (individual overrides, take priority over --bindings)
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseCliBindings(argv: string[]): VariableMap;
|
|
149
|
+
/**
|
|
150
|
+
* Load and fully resolve a syndicate YAML definition.
|
|
151
|
+
*
|
|
152
|
+
* @param filename YAML filename inside `config/agents/`. Production
|
|
153
|
+
* syndicates live at the root; the shipped starter-pack examples live in
|
|
154
|
+
* `config/agents/examples/`. A bare filename is resolved against the root
|
|
155
|
+
* first, then `examples/`, so callers (and nested `yaml_reference`s) never
|
|
156
|
+
* need to know which side of the split a file is on.
|
|
157
|
+
* @param options Bindings and optional overrides
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const config = loadSyndicate('syndicate.yaml', {
|
|
162
|
+
* bindings: { headline_count: 10 },
|
|
163
|
+
* overrides: { syndicate_name: 'Custom Council' },
|
|
164
|
+
* });
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export declare function loadSyndicate(filename: string, options?: LoadSyndicateOptions): SyndicateYamlConfig;
|
|
168
|
+
/**
|
|
169
|
+
* Validates registry configurations against strict ADK and project constraints.
|
|
170
|
+
*/
|
|
171
|
+
export declare function validateRegistryConfig(config: Record<string, any>): void;
|
|
172
|
+
/**
|
|
173
|
+
* Loads a syndicate configuration from the Supabase registry.
|
|
174
|
+
* @param registryId The document ID in the adk_agent_registry table
|
|
175
|
+
* @param options Load options (bindings and overrides)
|
|
176
|
+
*/
|
|
177
|
+
export declare function loadSyndicateFromRegistry(registryId: string, options?: LoadSyndicateOptions): Promise<SyndicateYamlConfig>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { parse } from 'yaml';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { createClient } from '@supabase/supabase-js';
|
|
5
|
+
import { hasSupabaseCredentials } from "./persistence/supabaseProvider.js";
|
|
6
|
+
// ── Variable Injection (private) ──────────────────────────
|
|
7
|
+
const TOKEN_RE = /\{\{(\w+)\}\}/g;
|
|
8
|
+
const FULL_TOKEN_RE = /^\{\{(\w+)\}\}$/;
|
|
9
|
+
function interpolateString(value, vars) {
|
|
10
|
+
const fullMatch = value.match(FULL_TOKEN_RE);
|
|
11
|
+
if (fullMatch) {
|
|
12
|
+
const key = fullMatch[1];
|
|
13
|
+
if (key in vars)
|
|
14
|
+
return vars[key];
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return value.replace(TOKEN_RE, (_match, key) => {
|
|
18
|
+
if (key in vars)
|
|
19
|
+
return String(vars[key]);
|
|
20
|
+
return _match;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function interpolateDeep(obj, vars) {
|
|
24
|
+
if (typeof obj === 'string') {
|
|
25
|
+
return interpolateString(obj, vars);
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(obj)) {
|
|
28
|
+
return obj.map((item) => interpolateDeep(item, vars));
|
|
29
|
+
}
|
|
30
|
+
if (obj !== null && typeof obj === 'object') {
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
33
|
+
result[key] = interpolateDeep(value, vars);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
function findUnresolvedTokens(obj) {
|
|
40
|
+
const tokens = new Set();
|
|
41
|
+
function walk(val) {
|
|
42
|
+
if (typeof val === 'string') {
|
|
43
|
+
let m;
|
|
44
|
+
TOKEN_RE.lastIndex = 0;
|
|
45
|
+
while ((m = TOKEN_RE.exec(val)) !== null) {
|
|
46
|
+
tokens.add(m[1]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (Array.isArray(val)) {
|
|
50
|
+
val.forEach(walk);
|
|
51
|
+
}
|
|
52
|
+
else if (val !== null && typeof val === 'object') {
|
|
53
|
+
Object.values(val).forEach(walk);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
walk(obj);
|
|
57
|
+
return [...tokens];
|
|
58
|
+
}
|
|
59
|
+
function applyOverrides(config, overrides) {
|
|
60
|
+
return { ...config, ...overrides };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Bindings every load starts from, resolved at load time so date-anchored
|
|
64
|
+
* prompts never ship a stale hardcoded date. YAML `variables` and caller
|
|
65
|
+
* `bindings` both take precedence.
|
|
66
|
+
*/
|
|
67
|
+
function defaultBindings() {
|
|
68
|
+
return {
|
|
69
|
+
current_date: new Date().toLocaleDateString('en-US', {
|
|
70
|
+
year: 'numeric',
|
|
71
|
+
month: 'long',
|
|
72
|
+
day: 'numeric',
|
|
73
|
+
timeZone: 'America/New_York',
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// ── CLI Binding Parser ────────────────────────────────────
|
|
78
|
+
function coerce(raw) {
|
|
79
|
+
if (raw === 'true')
|
|
80
|
+
return true;
|
|
81
|
+
if (raw === 'false')
|
|
82
|
+
return false;
|
|
83
|
+
const num = Number(raw);
|
|
84
|
+
if (!isNaN(num) && raw !== '')
|
|
85
|
+
return num;
|
|
86
|
+
return raw;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse CLI variable bindings into a VariableMap.
|
|
90
|
+
*
|
|
91
|
+
* Supports two formats:
|
|
92
|
+
* --bindings '{"focus_area": "AI Regulation", "headline_count": 10}'
|
|
93
|
+
* --bind key=value (individual overrides, take priority over --bindings)
|
|
94
|
+
*/
|
|
95
|
+
export function parseCliBindings(argv) {
|
|
96
|
+
let vars = {};
|
|
97
|
+
for (let i = 0; i < argv.length; i++) {
|
|
98
|
+
if (argv[i] === '--bindings' && i + 1 < argv.length) {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(argv[i + 1]);
|
|
101
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
102
|
+
for (const [key, val] of Object.entries(parsed)) {
|
|
103
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
104
|
+
vars[key] = val;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
console.warn(`⚠ Failed to parse --bindings JSON: ${argv[i + 1]}`);
|
|
111
|
+
}
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (argv[i] === '--bind' && i + 1 < argv.length) {
|
|
116
|
+
const pair = argv[i + 1];
|
|
117
|
+
const eq = pair.indexOf('=');
|
|
118
|
+
if (eq === -1)
|
|
119
|
+
continue;
|
|
120
|
+
const key = pair.slice(0, eq).trim();
|
|
121
|
+
const raw = pair.slice(eq + 1).trim();
|
|
122
|
+
vars[key] = coerce(raw);
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return vars;
|
|
127
|
+
}
|
|
128
|
+
// ── Primary Entrypoint ────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Load and fully resolve a syndicate YAML definition.
|
|
131
|
+
*
|
|
132
|
+
* @param filename YAML filename inside `config/agents/`. Production
|
|
133
|
+
* syndicates live at the root; the shipped starter-pack examples live in
|
|
134
|
+
* `config/agents/examples/`. A bare filename is resolved against the root
|
|
135
|
+
* first, then `examples/`, so callers (and nested `yaml_reference`s) never
|
|
136
|
+
* need to know which side of the split a file is on.
|
|
137
|
+
* @param options Bindings and optional overrides
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const config = loadSyndicate('syndicate.yaml', {
|
|
142
|
+
* bindings: { headline_count: 10 },
|
|
143
|
+
* overrides: { syndicate_name: 'Custom Council' },
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export function loadSyndicate(filename, options = {}) {
|
|
148
|
+
const { bindings = {}, overrides } = options;
|
|
149
|
+
// Security: confine reads to the agents root. `filename` can originate from
|
|
150
|
+
// a network-controlled path segment (see a2a_server dynamic routes), so we
|
|
151
|
+
// must reject any value that resolves outside that root (e.g. "../../.env").
|
|
152
|
+
// The root itself is trusted configuration: option → env → cwd default.
|
|
153
|
+
const agentsDir = path.resolve(options.agentsDir ??
|
|
154
|
+
process.env.MELCHIZEDEK_AGENTS_DIR ??
|
|
155
|
+
path.join(process.cwd(), 'config', 'agents'));
|
|
156
|
+
let filePath = path.resolve(agentsDir, filename);
|
|
157
|
+
if (filePath !== agentsDir && !filePath.startsWith(agentsDir + path.sep)) {
|
|
158
|
+
throw new Error(`Invalid syndicate path: '${filename}' resolves outside the agents directory`);
|
|
159
|
+
}
|
|
160
|
+
// Starter-pack fallback: a name not found at the root is looked up in
|
|
161
|
+
// examples/ (still inside the jail), so moving a YAML between the two
|
|
162
|
+
// never breaks a bare-id A2A route or an npm script.
|
|
163
|
+
if (!fs.existsSync(filePath)) {
|
|
164
|
+
const inExamples = path.resolve(agentsDir, 'examples', filename);
|
|
165
|
+
if (inExamples.startsWith(agentsDir + path.sep) && fs.existsSync(inExamples)) {
|
|
166
|
+
filePath = inExamples;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
170
|
+
const raw = parse(fileContent);
|
|
171
|
+
const merged = {
|
|
172
|
+
...defaultBindings(),
|
|
173
|
+
...(raw.variables ?? {}),
|
|
174
|
+
...bindings,
|
|
175
|
+
};
|
|
176
|
+
const hasVars = Object.keys(merged).length > 0;
|
|
177
|
+
const interpolated = hasVars ? interpolateDeep(raw, merged) : raw;
|
|
178
|
+
if (hasVars) {
|
|
179
|
+
const unresolved = findUnresolvedTokens(interpolated);
|
|
180
|
+
if (unresolved.length > 0) {
|
|
181
|
+
console.warn(`⚠ Unresolved template variables: ${unresolved.map(t => `{{${t}}}`).join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const { variables: _stripped, ...clean } = interpolated;
|
|
185
|
+
let config = clean;
|
|
186
|
+
if (overrides) {
|
|
187
|
+
config = applyOverrides(config, overrides);
|
|
188
|
+
}
|
|
189
|
+
return config;
|
|
190
|
+
}
|
|
191
|
+
// ── Registry Validation (Step 7) ──────────────────────────
|
|
192
|
+
const VALID_SYSTEM_TOOLS = [
|
|
193
|
+
'google_search',
|
|
194
|
+
'generate_image',
|
|
195
|
+
'load_memory',
|
|
196
|
+
'preload_memory',
|
|
197
|
+
'get_company_fundamentals',
|
|
198
|
+
'get_macro_metrics',
|
|
199
|
+
'get_technical_indicators'
|
|
200
|
+
];
|
|
201
|
+
/**
|
|
202
|
+
* Validates registry configurations against strict ADK and project constraints.
|
|
203
|
+
*/
|
|
204
|
+
export function validateRegistryConfig(config) {
|
|
205
|
+
// 1. Enforce native property mapping rules
|
|
206
|
+
if ('options' in config) {
|
|
207
|
+
throw new Error(`[Validation Error] Legacy 'options' object found. Model parameters must reside in 'generateContentConfig'.`);
|
|
208
|
+
}
|
|
209
|
+
// 2. Validate declared tool references (Relaxed for MCP compatibility)
|
|
210
|
+
// We no longer strictly validate against VALID_SYSTEM_TOOLS because tools may be
|
|
211
|
+
// dynamically provided by an external MCP server.
|
|
212
|
+
// 3. Ensure core ADK properties exist
|
|
213
|
+
if (!config.name || (!config.yaml_reference && (!config.model || !config.instruction))) {
|
|
214
|
+
throw new Error(`[Validation Error] Configuration fails validation against ADK LlmAgentConfig specifications.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Loads a syndicate configuration from the Supabase registry.
|
|
219
|
+
* @param registryId The document ID in the adk_agent_registry table
|
|
220
|
+
* @param options Load options (bindings and overrides)
|
|
221
|
+
*/
|
|
222
|
+
export async function loadSyndicateFromRegistry(registryId, options = {}) {
|
|
223
|
+
if (!hasSupabaseCredentials()) {
|
|
224
|
+
throw new Error('Supabase credentials not available for registry loading.');
|
|
225
|
+
}
|
|
226
|
+
const supabaseUrl = process.env.SUPABASE_URL;
|
|
227
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
228
|
+
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
229
|
+
const { data, error } = await supabase
|
|
230
|
+
.from('adk_agent_registry')
|
|
231
|
+
.select('yaml_content')
|
|
232
|
+
.eq('id', registryId)
|
|
233
|
+
.single();
|
|
234
|
+
if (error || !data) {
|
|
235
|
+
throw new Error(`Registry document ${registryId} not found`);
|
|
236
|
+
}
|
|
237
|
+
const raw = data.yaml_content;
|
|
238
|
+
// Validate orchestrator
|
|
239
|
+
validateRegistryConfig(raw.orchestrator);
|
|
240
|
+
// Validate subagents
|
|
241
|
+
for (const sub of raw.subagents || []) {
|
|
242
|
+
validateRegistryConfig(sub);
|
|
243
|
+
}
|
|
244
|
+
const { bindings = {}, overrides } = options;
|
|
245
|
+
const merged = {
|
|
246
|
+
...defaultBindings(),
|
|
247
|
+
...(raw.variables ?? {}),
|
|
248
|
+
...bindings,
|
|
249
|
+
};
|
|
250
|
+
const hasVars = Object.keys(merged).length > 0;
|
|
251
|
+
const interpolated = hasVars ? interpolateDeep(raw, merged) : raw;
|
|
252
|
+
if (hasVars) {
|
|
253
|
+
const unresolved = findUnresolvedTokens(interpolated);
|
|
254
|
+
if (unresolved.length > 0) {
|
|
255
|
+
console.warn(`⚠ Unresolved template variables: ${unresolved.map((t) => `{{${t}}}`).join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const { variables: _stripped, ...clean } = interpolated;
|
|
259
|
+
let config = clean;
|
|
260
|
+
if (overrides) {
|
|
261
|
+
config = applyOverrides(config, overrides);
|
|
262
|
+
}
|
|
263
|
+
return config;
|
|
264
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { BaseMemoryService, SearchMemoryRequest, SearchMemoryResponse, Session } from '@google/adk';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
/**
|
|
4
|
+
* Harness-injected blocks the Discord surface prefixes to a user message
|
|
5
|
+
* (nihilistic-penguin stocks.py): the date marker and the portfolio payload.
|
|
6
|
+
* Neither is something the USER said. Stripped from the memory SEARCH query
|
|
7
|
+
* (a portfolio JSON would dominate the embedding and drown the question) and
|
|
8
|
+
* from the extraction transcript (a snapshot that changes daily would be
|
|
9
|
+
* re-stored as "what the user holds" every session — the analyst reads the
|
|
10
|
+
* live payload directly, memory only needs what the user SAID about it).
|
|
11
|
+
*/
|
|
12
|
+
export declare function stripHarnessBlocks(text: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Cosine similarity at or above which a new record is treated as a restatement
|
|
15
|
+
* of one already stored, and dropped.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately high. The extraction model rephrases the same fact on every
|
|
18
|
+
* pass ("The user has $831 in a forgotten Roth IRA" / "…an $831 balance in a
|
|
19
|
+
* forgotten Roth IRA account"), and those land around 0.95+; genuinely
|
|
20
|
+
* different facts about one subject ("NVDA closed at $217.50" vs "Wells Fargo
|
|
21
|
+
* put a $315 target on NVDA") sit far below. Tuning this DOWN risks silently
|
|
22
|
+
* discarding a real fact, which is the worse failure — a duplicate only costs
|
|
23
|
+
* a shortlist slot.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MEMORY_DEDUP_SIMILARITY = 0.93;
|
|
26
|
+
/**
|
|
27
|
+
* Which of a session's events still need ingesting.
|
|
28
|
+
*
|
|
29
|
+
* The A2A server is stateless, so `addSessionToMemory` runs after EVERY task
|
|
30
|
+
* with the WHOLE session — an N-turn conversation was being extracted N times
|
|
31
|
+
* over a growing transcript. That produced 13 EPISODE records narrating the
|
|
32
|
+
* same session and one balance stored six ways. A high-water mark per session
|
|
33
|
+
* means each turn is distilled once.
|
|
34
|
+
*/
|
|
35
|
+
export declare function eventsToIngest<T>(events: T[], alreadyIngested: number): T[];
|
|
36
|
+
/**
|
|
37
|
+
* True when a candidate record restates something already stored.
|
|
38
|
+
*
|
|
39
|
+
* Same tag is required: an EPISODE narrating a discussion of the user's Roth
|
|
40
|
+
* IRA is not the FACT of its balance, however close the wording sits. Retired
|
|
41
|
+
* rows never suppress a new one — a superseded record is history, and the
|
|
42
|
+
* fact it was corrected to may legitimately be re-asserted later.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isSemanticDuplicate(candidate: {
|
|
45
|
+
tag: string;
|
|
46
|
+
}, matches: Array<{
|
|
47
|
+
tag: string | null;
|
|
48
|
+
status: string | null;
|
|
49
|
+
similarity: number;
|
|
50
|
+
}>, threshold?: number): boolean;
|
|
51
|
+
export declare class SupabaseVectorMemoryService implements BaseMemoryService {
|
|
52
|
+
private genai;
|
|
53
|
+
private supabase;
|
|
54
|
+
/**
|
|
55
|
+
* How many of each session's events have already been distilled.
|
|
56
|
+
* Keyed `{userKey}::{sessionId}`.
|
|
57
|
+
*
|
|
58
|
+
* In-process on purpose: a restart resets it, so the next turn re-reads
|
|
59
|
+
* that session once. Semantic dedup absorbs that batch, which is why this
|
|
60
|
+
* needs no table of its own — the two fixes cover each other's edges.
|
|
61
|
+
*/
|
|
62
|
+
private ingestedEventCount;
|
|
63
|
+
constructor(config: {
|
|
64
|
+
apiKey: string;
|
|
65
|
+
}, supabaseClient: SupabaseClient);
|
|
66
|
+
/**
|
|
67
|
+
* @param extractionRules Optional domain rules appended to the shared
|
|
68
|
+
* extraction prompt for THIS consumer only. The prompt is global (the
|
|
69
|
+
* patient advocate uses the same one), so anything domain-specific —
|
|
70
|
+
* "never store a market quote" — arrives here rather than being edited
|
|
71
|
+
* into it. Declared as `memory_extraction_rules` on the served syndicate.
|
|
72
|
+
*/
|
|
73
|
+
addSessionToMemory(session: Session, extractionRules?: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Right-to-erasure: permanently deletes every stored fact for a user key
|
|
76
|
+
* (`{appName}/{userId}`). Returns the number of rows removed. Throws on
|
|
77
|
+
* database error so callers can surface the failure — a silent no-op on
|
|
78
|
+
* a deletion request is unacceptable for user data.
|
|
79
|
+
*
|
|
80
|
+
* NOTE: this clears `adk_memory_facts` only. Session transcripts live in
|
|
81
|
+
* `adk_sessions` and must be cleared separately if full erasure is needed.
|
|
82
|
+
*/
|
|
83
|
+
deleteUserMemory(userKey: string): Promise<number>;
|
|
84
|
+
searchMemory(request: SearchMemoryRequest): Promise<SearchMemoryResponse>;
|
|
85
|
+
private extractRecords;
|
|
86
|
+
/**
|
|
87
|
+
* Parses one structured record line. Lines that don't match the format
|
|
88
|
+
* are dropped (extraction is best-effort; a malformed line must never
|
|
89
|
+
* poison the store). Header fields are optional individually — a bare
|
|
90
|
+
* `[FACT] text` line still parses, with defaults.
|
|
91
|
+
*/
|
|
92
|
+
private parseRecord;
|
|
93
|
+
private embedTexts;
|
|
94
|
+
private serializeEvents;
|
|
95
|
+
/**
|
|
96
|
+
* Inserts new records and returns a map of record line → inserted row id
|
|
97
|
+
* (needed to link superseded rows to their replacement).
|
|
98
|
+
*/
|
|
99
|
+
private upsertToSupabase;
|
|
100
|
+
/**
|
|
101
|
+
* The record evolves: a stored record carrying a `supersedes` header
|
|
102
|
+
* retires the older rows it corrects. Candidates are found by embedding
|
|
103
|
+
* the superseded quote and similarity-searching the user's ACTIVE rows;
|
|
104
|
+
* a candidate is retired when it is both semantically close and shares
|
|
105
|
+
* an index key with the correction (or is a near-exact match). Retired
|
|
106
|
+
* rows are kept — marked status='superseded' and linked via
|
|
107
|
+
* superseded_by — so history and contradictions stay inspectable.
|
|
108
|
+
*/
|
|
109
|
+
private applySupersessions;
|
|
110
|
+
/**
|
|
111
|
+
* Hybrid recall: candidates come from pgvector cosine similarity, then
|
|
112
|
+
* are re-ranked by the query's connection to the record's index keys and
|
|
113
|
+
* dates. Active records outrank retired ones; retired records that still
|
|
114
|
+
* surface are relabeled so the model sees them as history, never as the
|
|
115
|
+
* current state.
|
|
116
|
+
*/
|
|
117
|
+
private searchSupabase;
|
|
118
|
+
}
|