@aaronsb/kg-cli 0.8.0 → 0.9.2

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.
@@ -57,14 +57,12 @@ const package_json_1 = __importDefault(require("../package.json"));
57
57
  /**
58
58
  * Default parameters for graph queries (ADR-048 Query Safety)
59
59
  *
60
- * These defaults balance performance and result quality:
61
- * - Higher thresholds (0.75+) prevent expensive full-graph scans
62
- * - Lower max_hops (3) prevent exponential traversal explosion
63
- * - Adjust based on graph size and performance characteristics
60
+ * Aligned with CLI defaults so MCP agents get the same results as CLI users.
61
+ * The API server enforces its own safety limits on the backend.
64
62
  */
65
63
  const DEFAULT_SEARCH_SIMILARITY = 0.7; // Search tool minimum similarity
66
- const DEFAULT_SEMANTIC_THRESHOLD = 0.75; // Connect queries semantic matching
67
- const DEFAULT_MAX_HOPS = 3; // Maximum path traversal depth
64
+ const DEFAULT_SEMANTIC_THRESHOLD = 0.5; // Connect queries semantic matching (matches CLI)
65
+ const DEFAULT_MAX_HOPS = 5; // Maximum path traversal depth (matches CLI)
68
66
  const DEFAULT_MAX_DEPTH = 2; // Related concepts neighborhood depth
69
67
  // Create server instance
70
68
  const server = new index_js_1.Server({
@@ -85,17 +83,26 @@ const server = new index_js_1.Server({
85
83
  * - Diversity score (0-100%): Conceptual richness and connection breadth
86
84
  * - Authenticated diversity (✅✓⚠❌): Directional quality combining grounding + diversity
87
85
  *
88
- * Explore by:
89
- * 1. search - Find entry points (returns all scores + evidence samples)
90
- * 2. concept - Work with concepts (details, related, connections)
91
- * 3. ontology - Manage ontologies (list, info, files, delete)
92
- * 4. job - Manage jobs (status, list, approve, cancel)
93
- * 5. ingest - Ingest content (text, inspect-file, file, directory)
94
- * 6. source - Retrieve source images for visual verification
86
+ * Three tiers of exploration (prefer higher tiers for efficiency):
87
+ *
88
+ * TIER 3 Programs & Chains (most efficient):
89
+ * program (action: "execute") - Compose multi-step queries into a single server-side program
90
+ * program (action: "chain") - Thread results through multiple stored programs
91
+ * program (action: "list") - Discover reusable stored programs
92
+ * Read the program/syntax resource for the full language reference.
93
+ *
94
+ * TIER 2 — Individual tools (quick lookups):
95
+ * search - Find concepts, sources, or documents by semantic similarity
96
+ * concept - Get details, find related concepts, discover connection paths
97
+ * source - Retrieve original source text or images
98
+ *
99
+ * TIER 1 — Management:
100
+ * ontology, job, ingest, graph, document, epistemic_status, analyze_polarity_axis
95
101
  *
96
102
  * Resources provide fresh data on-demand without consuming tool budget:
97
103
  * - database/stats, database/info, database/health
98
104
  * - system/status, api/health
105
+ * - program/syntax — full GraphProgram language reference
99
106
  *
100
107
  * Use high grounding + high diversity to find reliable, central concepts.
101
108
  * Negative grounding often shows the most interesting problems/contradictions.
@@ -248,6 +255,8 @@ DOCUMENT SEARCH (type: "documents") - Find documents by semantic similarity (ADR
248
255
 
249
256
  RECOMMENDED WORKFLOW: After search, use concept (action: "connect") to find HOW concepts relate - this reveals narrative flows and cause/effect chains that individual searches cannot show. Connection paths are often more valuable than isolated concepts.
250
257
 
258
+ For multi-step exploration, compose searches into a GraphProgram (program tool) instead of making individual calls. One program can seed from search, expand relationships, and filter — all server-side in a single round-trip. Use program (action: "list") to find reusable stored programs, or read the program/syntax resource for composition examples.
259
+
251
260
  Use 2-3 word phrases (e.g., "linear thinking patterns").`,
252
261
  inputSchema: {
253
262
  type: 'object',
@@ -289,7 +298,9 @@ Use 2-3 word phrases (e.g., "linear thinking patterns").`,
289
298
  name: 'concept',
290
299
  description: `Work with concepts: get details (ALL evidence + relationships), find related concepts (neighborhood exploration), or discover connections (paths between concepts).
291
300
 
292
- PERFORMANCE CRITICAL: For "connect" action, use threshold >= 0.75 to avoid database overload. Lower thresholds create exponentially larger searches that can hang for minutes. Start with threshold=0.8, max_hops=3, then adjust if needed.`,
301
+ For "connect" action, defaults (threshold=0.5, max_hops=5) match the CLI and work well for most queries. Use higher thresholds (0.75+) only if you need to narrow results for precision.
302
+
303
+ For multi-step workflows (search → connect → expand → filter), compose these into a GraphProgram instead of making individual calls. See the program tool and program/syntax resource.`,
293
304
  inputSchema: {
294
305
  type: 'object',
295
306
  properties: {
@@ -370,13 +381,13 @@ PERFORMANCE CRITICAL: For "connect" action, use threshold >= 0.75 to avoid datab
370
381
  },
371
382
  max_hops: {
372
383
  type: 'number',
373
- description: 'Max path length (default: 3). WARNING: Values >5 combined with threshold <0.75 can cause severe performance issues.',
374
- default: 3,
384
+ description: 'Max path length (default: 5). Higher values find longer paths but take more time.',
385
+ default: 5,
375
386
  },
376
387
  threshold: {
377
388
  type: 'number',
378
- description: 'Similarity threshold for semantic mode (default: 0.75). PERFORMANCE GUIDE: 0.85+ = precise/fast, 0.75-0.84 = balanced, 0.60-0.74 = exploratory/SLOW, <0.60 = DANGEROUS (can hang database for minutes)',
379
- default: 0.75,
389
+ description: 'Similarity threshold for semantic mode (default: 0.5). Lower values find broader matches. The API enforces backend safety limits.',
390
+ default: 0.5,
380
391
  },
381
392
  },
382
393
  required: ['action'],
@@ -1026,6 +1037,105 @@ Queue executes sequentially, stops on first error (unless continue_on_error=true
1026
1037
  required: ['action'],
1027
1038
  },
1028
1039
  },
1040
+ // ADR-500: GraphProgram notarization and execution
1041
+ {
1042
+ name: 'program',
1043
+ description: `Compose and execute GraphProgram queries against the knowledge graph (ADR-500).
1044
+
1045
+ Programs are JSON ASTs that compose Cypher queries and API calls using set-algebra operators.
1046
+ Each statement applies an operator to merge/filter results into a mutable Working Graph (W).
1047
+
1048
+ **Actions:**
1049
+ - "validate": Dry-run validation. Returns errors/warnings without storing.
1050
+ - "create": Notarize + store. Returns program ID for later execution.
1051
+ - "get": Retrieve a stored program by ID.
1052
+ - "list": Search stored programs by name/description. Returns lightweight metadata.
1053
+ - "execute": Run a program server-side. Pass inline AST (program) or stored ID (program_id).
1054
+ - "chain": Run multiple programs sequentially. W threads through each — program N's output becomes program N+1's input. Pass a deck array of {program_id} or {program} entries (max 10).
1055
+
1056
+ **Program Structure:**
1057
+ { version: 1, metadata?: { name, description, author }, statements: [{ op, operation, label? }] }
1058
+
1059
+ **Operators** (applied to Working Graph W using result R):
1060
+ + Union: merge R into W (dedup by concept_id / link compound key)
1061
+ - Difference: remove R's nodes from W, cascade dangling links
1062
+ & Intersect: keep only W nodes that appear in R
1063
+ ? Optional: union if R non-empty, silent no-op if empty
1064
+ ! Assert: union if R non-empty, abort program if empty
1065
+
1066
+ **CypherOp** — run read-only openCypher against the source graph:
1067
+ { type: "cypher", query: "MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t", limit?: 20 }
1068
+ Queries must be read-only (no CREATE/SET/DELETE/MERGE). RETURN nodes and relationships.
1069
+
1070
+ **ApiOp** — call internal service functions (no HTTP):
1071
+ { type: "api", endpoint: "/search/concepts", params: { query: "...", limit: 10 } }
1072
+
1073
+ Allowed endpoints:
1074
+ /search/concepts — params: query (required), min_similarity?, limit?
1075
+ /search/sources — params: query (required), min_similarity?, limit?
1076
+ /concepts/details — params: concept_id (required)
1077
+ /concepts/related — params: concept_id (required), max_depth?, relationship_types?
1078
+ /concepts/batch — params: concept_ids (required, list)
1079
+ /vocabulary/status — params: relationship_type?, status_filter?
1080
+
1081
+ **Example** — find concepts about "machine learning", add their relationships:
1082
+ { version: 1, statements: [
1083
+ { op: "+", operation: { type: "api", endpoint: "/search/concepts",
1084
+ params: { query: "machine learning", limit: 5 } }, label: "seed" },
1085
+ { op: "+", operation: { type: "cypher",
1086
+ query: "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.concept_id IN $W_IDS RETURN c, r, t" },
1087
+ label: "expand relationships" }
1088
+ ]}
1089
+
1090
+ Read the program/syntax resource for the complete language reference with more examples.`,
1091
+ inputSchema: {
1092
+ type: 'object',
1093
+ properties: {
1094
+ action: {
1095
+ type: 'string',
1096
+ enum: ['validate', 'create', 'get', 'list', 'execute', 'chain'],
1097
+ description: 'Operation: "validate" (dry run), "create" (notarize + store), "get" (retrieve by ID), "list" (search stored programs), "execute" (run server-side), "chain" (run multiple programs sequentially)',
1098
+ },
1099
+ program: {
1100
+ type: 'object',
1101
+ description: 'GraphProgram AST (required for validate, create, execute). Must have version:1 and statements array.',
1102
+ },
1103
+ name: {
1104
+ type: 'string',
1105
+ description: 'Program name (optional for create)',
1106
+ },
1107
+ program_id: {
1108
+ type: 'number',
1109
+ description: 'Program ID (required for get, optional for execute as alternative to inline program)',
1110
+ },
1111
+ params: {
1112
+ type: 'object',
1113
+ description: 'Runtime parameter values for execute (optional)',
1114
+ },
1115
+ search: {
1116
+ type: 'string',
1117
+ description: 'Search text for list action (matches name and description)',
1118
+ },
1119
+ limit: {
1120
+ type: 'number',
1121
+ description: 'Max results for list action (default: 20)',
1122
+ },
1123
+ deck: {
1124
+ type: 'array',
1125
+ items: {
1126
+ type: 'object',
1127
+ properties: {
1128
+ program_id: { type: 'number', description: 'Stored program ID' },
1129
+ program: { type: 'object', description: 'Inline program AST' },
1130
+ params: { type: 'object', description: 'Runtime params for this program' },
1131
+ },
1132
+ },
1133
+ description: 'Array of program entries for chain action (max 10). Each entry needs program_id or program.',
1134
+ },
1135
+ },
1136
+ required: ['action'],
1137
+ },
1138
+ },
1029
1139
  ],
1030
1140
  };
1031
1141
  });
@@ -1068,6 +1178,12 @@ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
1068
1178
  name: 'MCP File Access Allowlist',
1069
1179
  description: 'Path allowlist configuration for secure file/directory ingestion (ADR-062)',
1070
1180
  mimeType: 'application/json'
1181
+ },
1182
+ {
1183
+ uri: 'program/syntax',
1184
+ name: 'GraphProgram Language Reference',
1185
+ description: 'Complete syntax reference for composing GraphProgram ASTs (ADR-500). Includes operators, operation types, allowed endpoints, conditions, and examples.',
1186
+ mimeType: 'text/plain'
1071
1187
  }
1072
1188
  ]
1073
1189
  };
@@ -1160,6 +1276,15 @@ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) =
1160
1276
  }]
1161
1277
  };
1162
1278
  }
1279
+ case 'program/syntax': {
1280
+ return {
1281
+ contents: [{
1282
+ uri,
1283
+ mimeType: 'text/plain',
1284
+ text: getGraphProgramSyntaxReference()
1285
+ }]
1286
+ };
1287
+ }
1163
1288
  default:
1164
1289
  throw new Error(`Unknown resource: ${uri}`);
1165
1290
  }
@@ -1298,6 +1423,8 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1298
1423
  from_id: toolArgs.from_id,
1299
1424
  to_id: toolArgs.to_id,
1300
1425
  max_hops: toolArgs.max_hops || DEFAULT_MAX_HOPS,
1426
+ include_grounding: true,
1427
+ include_evidence: true,
1301
1428
  // ADR-065: Epistemic status filtering
1302
1429
  include_epistemic_status: toolArgs.include_epistemic_status,
1303
1430
  exclude_epistemic_status: toolArgs.exclude_epistemic_status,
@@ -2240,6 +2367,273 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
2240
2367
  throw new Error(`Unknown graph action: ${action}. Use: create, edit, delete, list, or queue`);
2241
2368
  }
2242
2369
  }
2370
+ // ADR-500: GraphProgram notarization
2371
+ case 'program': {
2372
+ const action = toolArgs.action;
2373
+ const client = (0, client_js_1.createClientFromEnv)();
2374
+ switch (action) {
2375
+ case 'validate': {
2376
+ if (!toolArgs.program) {
2377
+ throw new Error('program is required for validate action');
2378
+ }
2379
+ const result = await client.validateProgram(toolArgs.program);
2380
+ const lines = [];
2381
+ lines.push(result.valid ? '✓ Program is valid' : '✗ Program is invalid');
2382
+ if (result.errors.length > 0) {
2383
+ lines.push('\nErrors:');
2384
+ for (const err of result.errors) {
2385
+ const loc = err.statement !== null && err.statement !== undefined
2386
+ ? `stmt ${err.statement}`
2387
+ : 'program';
2388
+ lines.push(` [${err.rule_id}] ${loc}: ${err.message}`);
2389
+ }
2390
+ }
2391
+ if (result.warnings.length > 0) {
2392
+ lines.push('\nWarnings:');
2393
+ for (const warn of result.warnings) {
2394
+ const loc = warn.statement !== null && warn.statement !== undefined
2395
+ ? `stmt ${warn.statement}`
2396
+ : 'program';
2397
+ lines.push(` [${warn.rule_id}] ${loc}: ${warn.message}`);
2398
+ }
2399
+ }
2400
+ return {
2401
+ content: [{ type: 'text', text: lines.join('\n') }],
2402
+ };
2403
+ }
2404
+ case 'create': {
2405
+ if (!toolArgs.program) {
2406
+ throw new Error('program is required for create action');
2407
+ }
2408
+ try {
2409
+ const result = await client.createProgram(toolArgs.program, toolArgs.name);
2410
+ const lines = [];
2411
+ lines.push(`Notarized program "${result.name}" (ID ${result.id})`);
2412
+ lines.push(` Statements: ${result.program.statements.length}`);
2413
+ lines.push(` Created: ${result.created_at}`);
2414
+ return {
2415
+ content: [{ type: 'text', text: lines.join('\n') }],
2416
+ };
2417
+ }
2418
+ catch (err) {
2419
+ if (err.response?.status === 400 && err.response?.data?.detail?.validation) {
2420
+ const validation = err.response.data.detail.validation;
2421
+ const lines = ['✗ Program validation failed'];
2422
+ if (validation.errors) {
2423
+ for (const e of validation.errors) {
2424
+ const loc = e.statement !== null && e.statement !== undefined
2425
+ ? `stmt ${e.statement}`
2426
+ : 'program';
2427
+ lines.push(` [${e.rule_id}] ${loc}: ${e.message}`);
2428
+ }
2429
+ }
2430
+ return {
2431
+ content: [{ type: 'text', text: lines.join('\n') }],
2432
+ isError: true,
2433
+ };
2434
+ }
2435
+ throw err;
2436
+ }
2437
+ }
2438
+ case 'get': {
2439
+ if (!toolArgs.program_id) {
2440
+ throw new Error('program_id is required for get action');
2441
+ }
2442
+ const result = await client.getProgram(toolArgs.program_id);
2443
+ const lines = [];
2444
+ lines.push(`Program ${result.id}: ${result.name}`);
2445
+ lines.push(` Owner: ${result.owner_id ?? '(system)'}`);
2446
+ lines.push(` Version: ${result.program.version}`);
2447
+ lines.push(` Statements: ${result.program.statements.length}`);
2448
+ lines.push(` Created: ${result.created_at}`);
2449
+ lines.push(` Updated: ${result.updated_at}`);
2450
+ if (result.program.metadata?.description) {
2451
+ lines.push(` Description: ${result.program.metadata.description}`);
2452
+ }
2453
+ lines.push('\nStatements:');
2454
+ for (let i = 0; i < result.program.statements.length; i++) {
2455
+ const stmt = result.program.statements[i];
2456
+ const op = stmt.operation;
2457
+ const label = stmt.label ? ` (${stmt.label})` : '';
2458
+ if (op.type === 'cypher') {
2459
+ lines.push(` [${i}] ${stmt.op} cypher: ${op.query}${label}`);
2460
+ }
2461
+ else if (op.type === 'api') {
2462
+ lines.push(` [${i}] ${stmt.op} api: ${op.endpoint}${label}`);
2463
+ }
2464
+ else if (op.type === 'conditional') {
2465
+ lines.push(` [${i}] ${stmt.op} conditional${label}`);
2466
+ }
2467
+ }
2468
+ lines.push('\nProgram AST:');
2469
+ lines.push(JSON.stringify(result.program, null, 2));
2470
+ return {
2471
+ content: [{ type: 'text', text: lines.join('\n') }],
2472
+ };
2473
+ }
2474
+ case 'execute': {
2475
+ if (!toolArgs.program && !toolArgs.program_id) {
2476
+ throw new Error('program or program_id is required for execute action');
2477
+ }
2478
+ try {
2479
+ const result = await client.executeProgram({
2480
+ program: toolArgs.program,
2481
+ programId: toolArgs.program_id,
2482
+ params: toolArgs.params,
2483
+ });
2484
+ const lines = [];
2485
+ const totalMs = result.log.reduce((sum, e) => sum + e.duration_ms, 0);
2486
+ if (result.aborted) {
2487
+ lines.push(`✗ Aborted at statement ${result.aborted.statement}: ${result.aborted.reason}`);
2488
+ }
2489
+ else {
2490
+ lines.push(`✓ Execution completed (${totalMs.toFixed(0)}ms)`);
2491
+ }
2492
+ lines.push(` Nodes: ${result.result.nodes.length}`);
2493
+ lines.push(` Links: ${result.result.links.length}`);
2494
+ if (result.log.length > 0) {
2495
+ lines.push('\nExecution Log:');
2496
+ const opNames = { '+': 'union', '-': 'diff', '&': 'intersect', '?': 'optional', '!': 'assert' };
2497
+ for (const entry of result.log) {
2498
+ const opLabel = opNames[entry.op] || entry.op;
2499
+ const branch = entry.branch_taken ? ` → ${entry.branch_taken}` : '';
2500
+ const affected = entry.operation_type === 'conditional'
2501
+ ? ''
2502
+ : ` (${entry.nodes_affected}n, ${entry.links_affected}l)`;
2503
+ lines.push(` [${entry.statement}] ${opLabel} ${entry.operation_type}${branch}${affected} ${entry.duration_ms.toFixed(0)}ms`);
2504
+ }
2505
+ }
2506
+ if (result.result.nodes.length > 0) {
2507
+ lines.push('\nNodes:');
2508
+ for (const node of result.result.nodes.slice(0, 50)) {
2509
+ const ont = node.ontology ? ` [${node.ontology}]` : '';
2510
+ lines.push(` • ${node.label} (${node.concept_id})${ont}`);
2511
+ }
2512
+ if (result.result.nodes.length > 50) {
2513
+ lines.push(` ... and ${result.result.nodes.length - 50} more`);
2514
+ }
2515
+ }
2516
+ if (result.result.links.length > 0) {
2517
+ lines.push('\nLinks:');
2518
+ for (const link of result.result.links.slice(0, 30)) {
2519
+ lines.push(` ${link.from_id} → ${link.relationship_type} → ${link.to_id}`);
2520
+ }
2521
+ if (result.result.links.length > 30) {
2522
+ lines.push(` ... and ${result.result.links.length - 30} more`);
2523
+ }
2524
+ }
2525
+ return {
2526
+ content: [{ type: 'text', text: lines.join('\n') }],
2527
+ isError: !!result.aborted,
2528
+ };
2529
+ }
2530
+ catch (err) {
2531
+ if (err.response?.status === 400 && err.response?.data?.detail?.validation) {
2532
+ const validation = err.response.data.detail.validation;
2533
+ const lines = ['✗ Program validation failed'];
2534
+ if (validation.errors) {
2535
+ for (const e of validation.errors) {
2536
+ const loc = e.statement !== null && e.statement !== undefined
2537
+ ? `stmt ${e.statement}`
2538
+ : 'program';
2539
+ lines.push(` [${e.rule_id}] ${loc}: ${e.message}`);
2540
+ }
2541
+ }
2542
+ return {
2543
+ content: [{ type: 'text', text: lines.join('\n') }],
2544
+ isError: true,
2545
+ };
2546
+ }
2547
+ throw err;
2548
+ }
2549
+ }
2550
+ case 'list': {
2551
+ const programs = await client.listPrograms({
2552
+ search: toolArgs.search,
2553
+ limit: toolArgs.limit,
2554
+ });
2555
+ if (programs.length === 0) {
2556
+ return {
2557
+ content: [{ type: 'text', text: 'No stored programs found.' }],
2558
+ };
2559
+ }
2560
+ const lines = [`Found ${programs.length} program(s):\n`];
2561
+ for (const p of programs) {
2562
+ const desc = p.description ? ` ${p.description}` : '';
2563
+ lines.push(` [${p.id}] ${p.name} (${p.statement_count} statements)${desc}`);
2564
+ }
2565
+ return {
2566
+ content: [{ type: 'text', text: lines.join('\n') }],
2567
+ };
2568
+ }
2569
+ case 'chain': {
2570
+ if (!toolArgs.deck || !Array.isArray(toolArgs.deck)) {
2571
+ throw new Error('deck array is required for chain action');
2572
+ }
2573
+ if (toolArgs.deck.length === 0) {
2574
+ throw new Error('deck must contain at least one entry');
2575
+ }
2576
+ if (toolArgs.deck.length > 10) {
2577
+ throw new Error(`Deck too large: ${toolArgs.deck.length} entries (max 10)`);
2578
+ }
2579
+ try {
2580
+ const result = await client.chainPrograms(toolArgs.deck);
2581
+ const lines = [];
2582
+ if (result.aborted) {
2583
+ lines.push(`✗ Chain aborted at statement ${result.aborted.statement}: ${result.aborted.reason}`);
2584
+ }
2585
+ else {
2586
+ const totalMs = result.programs.reduce((sum, p) => sum + p.log.reduce((s, e) => s + e.duration_ms, 0), 0);
2587
+ lines.push(`✓ Chain completed: ${result.programs.length} programs (${totalMs.toFixed(0)}ms)`);
2588
+ }
2589
+ lines.push(` Nodes: ${result.result.nodes.length}`);
2590
+ lines.push(` Links: ${result.result.links.length}`);
2591
+ // Per-program summary
2592
+ lines.push('\nProgram Results:');
2593
+ for (let i = 0; i < result.programs.length; i++) {
2594
+ const pr = result.programs[i];
2595
+ const ms = pr.log.reduce((s, e) => s + e.duration_ms, 0);
2596
+ const status = pr.aborted ? '✗' : '✓';
2597
+ lines.push(` ${status} [${i}] ${pr.log.length} steps, ${pr.result.nodes.length}n/${pr.result.links.length}l (${ms.toFixed(0)}ms)`);
2598
+ }
2599
+ if (result.result.nodes.length > 0) {
2600
+ lines.push('\nNodes:');
2601
+ for (const node of result.result.nodes.slice(0, 50)) {
2602
+ const ont = node.ontology ? ` [${node.ontology}]` : '';
2603
+ lines.push(` • ${node.label} (${node.concept_id})${ont}`);
2604
+ }
2605
+ if (result.result.nodes.length > 50) {
2606
+ lines.push(` ... and ${result.result.nodes.length - 50} more`);
2607
+ }
2608
+ }
2609
+ if (result.result.links.length > 0) {
2610
+ lines.push('\nLinks:');
2611
+ for (const link of result.result.links.slice(0, 30)) {
2612
+ lines.push(` ${link.from_id} → ${link.relationship_type} → ${link.to_id}`);
2613
+ }
2614
+ if (result.result.links.length > 30) {
2615
+ lines.push(` ... and ${result.result.links.length - 30} more`);
2616
+ }
2617
+ }
2618
+ return {
2619
+ content: [{ type: 'text', text: lines.join('\n') }],
2620
+ isError: !!result.aborted,
2621
+ };
2622
+ }
2623
+ catch (err) {
2624
+ if (err.response?.status === 400) {
2625
+ return {
2626
+ content: [{ type: 'text', text: `✗ Chain error: ${err.response?.data?.detail || err.message}` }],
2627
+ isError: true,
2628
+ };
2629
+ }
2630
+ throw err;
2631
+ }
2632
+ }
2633
+ default:
2634
+ throw new Error(`Unknown program action: ${action}. Use: validate, create, get, list, execute, or chain`);
2635
+ }
2636
+ }
2243
2637
  default:
2244
2638
  throw new Error(`Unknown tool: ${name}`);
2245
2639
  }
@@ -2292,90 +2686,73 @@ server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
2292
2686
 
2293
2687
  This system transforms documents into semantic concept graphs with grounding strength (reliability scores) and evidence (quoted text).
2294
2688
 
2295
- ## Exploration Workflow:
2689
+ ## Choose Your Approach
2690
+
2691
+ ### Quick lookup (1-2 results needed)
2692
+ Use individual tools directly:
2693
+ - **search** — find concepts by semantic similarity (2-3 word phrases)
2694
+ - **concept** (action: "connect") — trace paths between two concepts
2695
+ - **concept** (action: "details") — get all evidence for one concept
2696
+
2697
+ ### Multi-step exploration (recommended for most tasks)
2698
+ Compose a **GraphProgram** using the **program** tool. One program replaces
2699
+ multiple individual tool calls and runs entirely server-side:
2700
+ \`\`\`json
2701
+ { "version": 1, "statements": [
2702
+ { "op": "+", "operation": { "type": "api", "endpoint": "/search/concepts",
2703
+ "params": { "query": "your topic", "limit": 10 } }, "label": "seed" },
2704
+ { "op": "+", "operation": { "type": "cypher",
2705
+ "query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.concept_id IN $W_IDS RETURN c, r, t" },
2706
+ "label": "expand relationships" }
2707
+ ]}
2708
+ \`\`\`
2709
+ Use **program** (action: "execute") to run inline, or (action: "create") to store for reuse.
2710
+
2711
+ ### Chained exploration (complex investigations)
2712
+ Stack multiple programs with **program** (action: "chain"). Each program's
2713
+ output becomes the next program's input. Use (action: "list") to find stored
2714
+ programs you can compose.
2715
+
2716
+ Read the **program/syntax** resource for the full language reference with examples.
2296
2717
 
2297
- 1. **search** - Your entry point. Find concepts by semantic similarity.
2298
- - Returns RICH DATA:
2299
- * Grounding strength: Reliability score (-1.0 to 1.0)
2300
- * Diversity score: % showing conceptual richness
2301
- * Authenticated diversity: Support/contradiction indicator (✅✓⚠❌)
2302
- * Evidence samples: Quoted text from sources
2303
- * Image indicators: Visual evidence available
2718
+ ## Individual Tool Reference
2719
+
2720
+ 1. **search** Find concepts, source passages, or documents
2721
+ - Returns: grounding strength, diversity score, authenticated diversity (✅✓⚠❌), evidence samples
2304
2722
  - Use 2-3 word phrases (e.g., "configuration management", "licensing issues")
2305
2723
 
2306
- 2. **concept (action: connect)** - **PRIORITIZE THIS** - Discover HOW concepts connect.
2307
- - This is often MORE VALUABLE than isolated concept details
2308
- - Trace problem→solution chains, cause/effect relationships
2309
- - See grounding + evidence at each step in the path
2310
- - Reveals narrative flow through ideas
2311
- - **PERFORMANCE CRITICAL**: Start with threshold=0.8, max_hops=3
2312
- - Lower thresholds exponentially increase query time (0.6 = slow, <0.6 = very slow)
2313
-
2314
- 3. **concept (action: details)** - See the complete picture for any concept.
2315
- - Returns: ALL quoted evidence + relationships
2316
- - IMPORTANT: Contradicted concepts (negative grounding) are VALUABLE - they show problems/outdated approaches
2317
- - Use this after finding interesting concepts via search or connect
2318
-
2319
- 4. **concept (action: related)** - Explore neighborhoods.
2320
- - Find what's nearby in the concept graph
2321
- - Discover clusters and themes
2322
- - Use depth=1-2 for neighbors, 3-4 for broader exploration
2323
-
2324
- ## Resources (Fresh Data):
2325
-
2326
- Use MCP resources for quick status checks without consuming tool budget:
2327
- - **database/stats** - Concept counts, relationships, ontologies
2328
- - **database/info** - PostgreSQL version, AGE extension
2329
- - **database/health** - Connection status
2330
- - **system/status** - Job scheduler, resource usage
2331
- - **api/health** - API server status
2332
-
2333
- ## Understanding the Scores:
2334
-
2335
- **Grounding Strength (-1.0 to 1.0)** - Reliability/Contradiction
2336
- - **Positive (>0.7)**: Well-supported, reliable concept
2337
- - **Moderate (0.3-0.7)**: Mixed evidence, use with caution
2338
- - **Negative (<0)**: Contradicted or presented as a problem
2339
- - **Contradicted (-1.0)**: Often the most interesting - shows pain points!
2340
-
2341
- **Diversity Score (0-100%)** - Conceptual Richness
2342
- - High diversity: Concept connects to many different related concepts
2343
- - Shows breadth of connections in the knowledge graph
2344
- - Higher scores indicate more interconnected, central concepts
2345
-
2346
- **Authenticated Diversity (✅✓⚠❌)** - Directional Quality
2347
- - ✅ Diverse support: Strong positive evidence across many connections
2348
- - ✓ Some support: Moderate positive evidence
2349
- - ⚠ Weak contradiction: Some conflicting evidence
2350
- - ❌ Diverse contradiction: Strong negative evidence across connections
2351
- - Combines grounding with diversity to show reliability + breadth
2352
-
2353
- ## How to Use the Scores:
2354
-
2355
- **High Grounding + High Diversity** → Central, well-established concepts
2356
- - These are reliable anchor points for exploration
2357
- - Good starting points for connection queries
2358
-
2359
- **High Grounding + Low Diversity** → Specialized, focused concepts
2360
- - Domain-specific, niche ideas with strong support
2361
- - May be isolated or newly added
2362
-
2363
- **Low/Negative Grounding + High Diversity** → Controversial or evolving concepts
2364
- - Contested ideas with multiple perspectives
2365
- - Often the most interesting for understanding debates
2366
-
2367
- **Authenticated Diversity** → Quick quality check
2368
- - Use ✅ results confidently
2369
- - Investigate ⚠ and ❌ results for contradictions and problems
2370
-
2371
- ## Pro Tips:
2372
- - **Connection queries first**: After search, immediately explore HOW concepts connect - this reveals causality and narrative
2373
- - Use diversity scores to find central vs. peripheral concepts
2374
- - Start broad with search, then drill down with concept details
2375
- - Contradicted concepts reveal what DIDN'T work - goldmines for learning
2376
- - Connection paths tell stories - follow the evidence chain
2377
- - Use resources for quick status checks instead of tools
2378
- - **Performance**: Keep threshold >= 0.75 for connect queries to avoid slow/hung queries`,
2724
+ 2. **concept** (action: "connect") Discover HOW concepts relate
2725
+ - Traces paths: problem→solution, cause→effect
2726
+ - Defaults (threshold=0.5, max_hops=5) match CLI for broad discovery
2727
+ - Often MORE VALUABLE than isolated concept details
2728
+
2729
+ 3. **concept** (action: "details") Complete picture for one concept
2730
+ - ALL quoted evidence + relationships
2731
+ - Contradicted concepts (negative grounding) show problems/outdated approaches
2732
+
2733
+ 4. **concept** (action: "related") Explore neighborhoods
2734
+ - depth=1-2 for neighbors, 3-4 for broader exploration
2735
+
2736
+ ## Understanding the Scores
2737
+
2738
+ **Grounding Strength (-1.0 to 1.0)** Reliability/Contradiction
2739
+ - Positive (>0.7): Well-supported, reliable
2740
+ - Moderate (0.3-0.7): Mixed evidence
2741
+ - Negative (<0): Contradicted — often the most interesting!
2742
+
2743
+ **Diversity Score (0-100%)** — Conceptual Richness
2744
+ - High: Connects to many different related concepts (central)
2745
+ - Low: Specialized or newly added
2746
+
2747
+ **Authenticated Diversity (✅✓⚠❌)** Combines grounding + diversity
2748
+ - Use confidently · Moderate support · ⚠ Investigate · ❌ Contradicted
2749
+
2750
+ ## Tips
2751
+ - **Programs over individual calls**: A 3-statement program is faster and cheaper than 3 tool calls
2752
+ - **Contradictions are valuable**: Negative grounding reveals pain points and what didn't work
2753
+ - **Connection paths tell stories**: Follow the evidence chain between concepts
2754
+ - **Use resources for status**: database/stats, system/status — no tool budget cost
2755
+ - **Defaults match CLI**: threshold=0.5, max_hops=5 broad discovery out of the box`,
2379
2756
  },
2380
2757
  },
2381
2758
  ],
@@ -2383,6 +2760,308 @@ Use MCP resources for quick status checks without consuming tool budget:
2383
2760
  }
2384
2761
  throw new Error(`Unknown prompt: ${name}`);
2385
2762
  });
2763
+ /**
2764
+ * GraphProgram language reference for agent consumption (ADR-500).
2765
+ * Returned by the program/syntax MCP resource.
2766
+ */
2767
+ function getGraphProgramSyntaxReference() {
2768
+ return `# GraphProgram Language Reference (ADR-500)
2769
+
2770
+ GraphProgram is a JSON-based query composition language for the knowledge graph.
2771
+ Programs compose openCypher queries and REST API calls using set-algebra operators
2772
+ to build a mutable Working Graph (W) from the persistent Source Graph (H).
2773
+
2774
+ ## Program Structure
2775
+
2776
+ \`\`\`json
2777
+ {
2778
+ "version": 1,
2779
+ "metadata": { "name": "...", "description": "...", "author": "agent" },
2780
+ "statements": [
2781
+ { "op": "+", "operation": { ... }, "label": "description of this step" }
2782
+ ]
2783
+ }
2784
+ \`\`\`
2785
+
2786
+ - version: Must be 1 (integer)
2787
+ - metadata: Optional. author can be "human", "agent", or "system"
2788
+ - statements: Non-empty array. Executed sequentially. Min 1, max 100.
2789
+
2790
+ ## Operators
2791
+
2792
+ Each statement has an "op" that controls how the operation's result (R) modifies W:
2793
+
2794
+ + UNION — Merge R into W. Dedup by concept_id (nodes) and (from_id, relationship_type, to_id) (links). W wins on collision.
2795
+ - DIFFERENCE — Remove R's nodes from W by concept_id. Links referencing removed nodes are cascade-removed.
2796
+ & INTERSECT — Keep only W nodes whose concept_id appears in R. Remove all others + dangling links.
2797
+ ? OPTIONAL — If R is non-empty: apply union. If R is empty: silent no-op. Use for exploratory steps.
2798
+ ! ASSERT — If R is non-empty: apply union. If R is empty: ABORT the entire program. Use for guard steps.
2799
+
2800
+ Invariant: After every operator, links whose from_id or to_id has no matching node are removed (dangling link invariant).
2801
+
2802
+ ## Operation Types
2803
+
2804
+ ### CypherOp — Query the source graph with openCypher
2805
+
2806
+ \`\`\`json
2807
+ {
2808
+ "type": "cypher",
2809
+ "query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.ontology = 'physics' RETURN c, r, t",
2810
+ "limit": 20
2811
+ }
2812
+ \`\`\`
2813
+
2814
+ - query: Read-only openCypher. Must RETURN nodes (c) and/or relationships (r). No CREATE/SET/DELETE/MERGE.
2815
+ - limit: Optional positive integer. Appended as LIMIT if query doesn't already have one.
2816
+
2817
+ Common Cypher patterns:
2818
+ MATCH (c:Concept) RETURN c LIMIT 10 — fetch concepts
2819
+ MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t LIMIT 20 — concepts + relationships
2820
+ MATCH (c:Concept) WHERE c.ontology = 'name' RETURN c — filter by ontology
2821
+ MATCH (c:Concept) WHERE c.concept_id IN ['id1','id2'] RETURN c — batch by ID
2822
+ MATCH p=(a:Concept)-[*1..3]->(b:Concept) RETURN p — paths (max depth 6)
2823
+
2824
+ ### ApiOp — Call internal service functions
2825
+
2826
+ \`\`\`json
2827
+ {
2828
+ "type": "api",
2829
+ "endpoint": "/search/concepts",
2830
+ "params": { "query": "neural networks", "limit": 10, "min_similarity": 0.7 }
2831
+ }
2832
+ \`\`\`
2833
+
2834
+ Allowed endpoints and their parameters:
2835
+
2836
+ /search/concepts
2837
+ Required: query (string) — semantic search text
2838
+ Optional: min_similarity (number, default 0.7), limit (integer, default 10)
2839
+ Returns: concept nodes matching the search
2840
+
2841
+ /search/sources
2842
+ Required: query (string) — semantic search text
2843
+ Optional: min_similarity (number, default 0.7), limit (integer, default 10)
2844
+ Returns: concept nodes extracted from matching source passages
2845
+
2846
+ /concepts/details
2847
+ Required: concept_id (string)
2848
+ Returns: the concept node + its outgoing relationships and target nodes
2849
+
2850
+ /concepts/related
2851
+ Required: concept_id (string)
2852
+ Optional: max_depth (integer, default 2), relationship_types (string array)
2853
+ Returns: neighborhood concept nodes
2854
+
2855
+ /concepts/batch
2856
+ Required: concept_ids (string array)
2857
+ Returns: concept nodes for all matching IDs
2858
+
2859
+ /vocabulary/status
2860
+ Optional: relationship_type (string), status_filter (string)
2861
+ Returns: vocabulary type nodes with epistemic status metadata
2862
+
2863
+ ### ConditionalOp — Branch based on Working Graph state
2864
+
2865
+ \`\`\`json
2866
+ {
2867
+ "type": "conditional",
2868
+ "condition": { "test": "has_results" },
2869
+ "then": [ { "op": "+", "operation": { ... } } ],
2870
+ "else": [ { "op": "+", "operation": { ... } } ]
2871
+ }
2872
+ \`\`\`
2873
+
2874
+ Condition tests (evaluated against current W):
2875
+ has_results — W has at least one node
2876
+ empty — W has zero nodes
2877
+ count_gte — W node count >= value (needs "value": number)
2878
+ count_lte — W node count <= value (needs "value": number)
2879
+ has_ontology — any W node has matching ontology (needs "ontology": string)
2880
+ has_relationship — any W link has matching type (needs "type": string)
2881
+
2882
+ Max nesting depth: 3.
2883
+
2884
+ ## Working Graph Shape
2885
+
2886
+ Nodes are keyed by concept_id (string). Links are keyed by (from_id, relationship_type, to_id).
2887
+
2888
+ Node: { concept_id, label, ontology?, description?, properties? }
2889
+ Link: { from_id, to_id, relationship_type, category?, confidence? }
2890
+
2891
+ ## Examples
2892
+
2893
+ ### 1. Simple concept search + relationship expansion
2894
+ \`\`\`json
2895
+ {
2896
+ "version": 1,
2897
+ "metadata": { "name": "explore topic", "author": "agent" },
2898
+ "statements": [
2899
+ {
2900
+ "op": "+",
2901
+ "operation": { "type": "api", "endpoint": "/search/concepts",
2902
+ "params": { "query": "machine learning", "limit": 5 } },
2903
+ "label": "seed with search results"
2904
+ },
2905
+ {
2906
+ "op": "+",
2907
+ "operation": { "type": "cypher",
2908
+ "query": "MATCH (c:Concept)-[r]->(t:Concept) RETURN c, r, t LIMIT 30" },
2909
+ "label": "add relationships between any concepts"
2910
+ }
2911
+ ]
2912
+ }
2913
+ \`\`\`
2914
+
2915
+ ### 2. Ontology-filtered exploration
2916
+ \`\`\`json
2917
+ {
2918
+ "version": 1,
2919
+ "metadata": { "name": "ontology deep dive", "author": "agent" },
2920
+ "statements": [
2921
+ {
2922
+ "op": "+",
2923
+ "operation": { "type": "cypher",
2924
+ "query": "MATCH (c:Concept) WHERE c.ontology = 'distributed-systems' RETURN c LIMIT 20" },
2925
+ "label": "get ontology concepts"
2926
+ },
2927
+ {
2928
+ "op": "+",
2929
+ "operation": { "type": "cypher",
2930
+ "query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE c.ontology = 'distributed-systems' AND t.ontology = 'distributed-systems' RETURN c, r, t" },
2931
+ "label": "intra-ontology relationships"
2932
+ }
2933
+ ]
2934
+ }
2935
+ \`\`\`
2936
+
2937
+ ### 3. Assert + conditional pattern
2938
+ \`\`\`json
2939
+ {
2940
+ "version": 1,
2941
+ "metadata": { "name": "guarded expansion", "author": "agent" },
2942
+ "statements": [
2943
+ {
2944
+ "op": "!",
2945
+ "operation": { "type": "api", "endpoint": "/search/concepts",
2946
+ "params": { "query": "quantum computing", "limit": 3, "min_similarity": 0.8 } },
2947
+ "label": "must find quantum concepts (abort if not)"
2948
+ },
2949
+ {
2950
+ "op": "?",
2951
+ "operation": { "type": "api", "endpoint": "/search/concepts",
2952
+ "params": { "query": "quantum entanglement", "limit": 5 } },
2953
+ "label": "optionally add entanglement concepts"
2954
+ },
2955
+ {
2956
+ "op": "+",
2957
+ "operation": { "type": "cypher",
2958
+ "query": "MATCH (c:Concept)-[r:SUPPORTS|IMPLIES]->(t:Concept) RETURN c, r, t LIMIT 50" },
2959
+ "label": "add supporting/implied relationships"
2960
+ }
2961
+ ]
2962
+ }
2963
+ \`\`\`
2964
+
2965
+ ### 4. Cross-ontology comparison (intersect)
2966
+ \`\`\`json
2967
+ {
2968
+ "version": 1,
2969
+ "metadata": { "name": "cross-ontology overlap", "author": "agent" },
2970
+ "statements": [
2971
+ {
2972
+ "op": "+",
2973
+ "operation": { "type": "cypher",
2974
+ "query": "MATCH (c:Concept) WHERE c.ontology = 'machine-learning' RETURN c" },
2975
+ "label": "load ML concepts"
2976
+ },
2977
+ {
2978
+ "op": "&",
2979
+ "operation": { "type": "cypher",
2980
+ "query": "MATCH (c:Concept)-[r]->(t:Concept) WHERE t.ontology = 'statistics' RETURN c" },
2981
+ "label": "keep only ML concepts that relate to statistics"
2982
+ }
2983
+ ]
2984
+ }
2985
+ \`\`\`
2986
+
2987
+ ### 5. Enrichment pipeline (concept details)
2988
+ \`\`\`json
2989
+ {
2990
+ "version": 1,
2991
+ "metadata": { "name": "enrich concepts", "author": "agent" },
2992
+ "statements": [
2993
+ {
2994
+ "op": "+",
2995
+ "operation": { "type": "api", "endpoint": "/search/concepts",
2996
+ "params": { "query": "neural architecture", "limit": 3 } },
2997
+ "label": "find seed concepts"
2998
+ },
2999
+ {
3000
+ "op": "+",
3001
+ "operation": { "type": "api", "endpoint": "/concepts/details",
3002
+ "params": { "concept_id": "CONCEPT_ID_FROM_SEARCH" } },
3003
+ "label": "enrich first concept with relationships"
3004
+ }
3005
+ ]
3006
+ }
3007
+ \`\`\`
3008
+ Note: For enrichment, you typically execute a search first, inspect the results,
3009
+ then compose a second program using specific concept_ids from the first result.
3010
+
3011
+ ## Execution
3012
+
3013
+ Use the program tool with action "execute":
3014
+ - Inline: { action: "execute", program: { version: 1, statements: [...] } }
3015
+ - Stored: { action: "execute", program_id: 42 }
3016
+
3017
+ The response includes:
3018
+ - result: { nodes: [...], links: [...] } — the final Working Graph
3019
+ - log: step-by-step execution record with timing
3020
+ - aborted: present only if an assert (!) failed
3021
+
3022
+ ## Tips for Agent Program Composition
3023
+
3024
+ 1. Start with + (union) to seed W with initial data.
3025
+ 2. Use ? (optional) for speculative searches that might return nothing.
3026
+ 3. Use ! (assert) for critical steps — if the data doesn't exist, abort early.
3027
+ 4. Use - (difference) to remove unwanted nodes and their links.
3028
+ 5. Use & (intersect) to filter W down to a specific subset.
3029
+ 6. Cypher queries that RETURN c, r, t give both nodes and relationships.
3030
+ 7. API searches return only nodes. Use Cypher to add relationships between them.
3031
+ 8. Validate before execute: use action "validate" to catch errors without side effects.
3032
+ 9. Programs are bounded: max 100 operations, max conditional nesting depth 3.
3033
+ 10. All queries are read-only. Programs cannot modify the source graph.
3034
+ 11. Use "list" to discover stored programs, then "chain" to compose them.
3035
+ 12. Chain programs that build on each other: seed → expand → filter → enrich.
3036
+
3037
+ ## Program Chaining (Deck Mode)
3038
+
3039
+ Chain multiple programs into a single execution. W threads through each program
3040
+ sequentially — program N's final W becomes program N+1's initial W.
3041
+
3042
+ Use the program tool with action "chain" and a "deck" array:
3043
+ { action: "chain", deck: [
3044
+ { program_id: 1 },
3045
+ { program_id: 5 },
3046
+ { program: { version: 1, statements: [...] } }
3047
+ ]}
3048
+
3049
+ Each deck entry can be a stored program (program_id) or an inline AST (program).
3050
+ Max 10 programs per chain. If any program aborts (! assert fails), the chain stops.
3051
+
3052
+ The response includes:
3053
+ - result: final W after all programs
3054
+ - programs: per-program results (each with own log)
3055
+ - aborted: present only if any program aborted
3056
+
3057
+ ## Discovering Programs
3058
+
3059
+ Use action "list" to find stored programs:
3060
+ { action: "list" } — list all
3061
+ { action: "list", search: "ontology" } — search by name/description
3062
+
3063
+ Returns: id, name, description, statement_count. Use the IDs in chain decks.`;
3064
+ }
2386
3065
  /**
2387
3066
  * Start the MCP server
2388
3067
  */