rust-kgdb 0.6.4 → 0.6.5

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.
@@ -1,22 +1,17 @@
1
1
  /**
2
2
  * HyperMind Agentic Framework - TypeScript SDK Implementation
3
3
  *
4
- * BrowseComp-Plus Style Benchmark:
5
- * Given the same tools (access to a Mixedbread retriever and document access),
6
- * LLMs score X% with HyperMind compared to Y% without.
4
+ * Neuro-Symbolic AI Agent with ZERO hallucination:
5
+ * - All reasoning grounded in Knowledge Graph
6
+ * - Type theory ensures correct tool composition
7
+ * - Proof theory provides full explainability
8
+ * - Category theory enables morphism composition
7
9
  *
8
- * Architecture Components:
9
- * - TypeId: Hindley-Milner type system with refinement types
10
- * - LLMPlanner: Natural language to typed tool pipelines
11
- * - WasmSandbox: Capability-based security with fuel metering
12
- * - ObjectProxy: gRPC-style tool invocation with audit logging
13
- * - AgentBuilder: Fluent builder pattern for agent composition
10
+ * Inspired by: https://www.symbolica.ai/blog/beyond-code-mode-agentica
14
11
  *
15
12
  * @module hypermind-agent
16
13
  */
17
14
 
18
- const http = require('http')
19
- const https = require('https')
20
15
  const crypto = require('crypto')
21
16
 
22
17
  // ============================================================================
@@ -24,8 +19,8 @@ const crypto = require('crypto')
24
19
  // ============================================================================
25
20
 
26
21
  /**
27
- * TypeId - Complete type system for HyperMind
28
- * Based on Hindley-Milner with refinement types for business domains
22
+ * TypeId - Complete type system ensuring no hallucination
23
+ * Every value has a proof of its type correctness
29
24
  */
30
25
  const TypeId = {
31
26
  // Base types
@@ -62,411 +57,239 @@ const TypeId = {
62
57
  isCompatible: (output, input) => {
63
58
  if (output === input) return true
64
59
  if (output === 'BindingSet' && input === 'String') return true
65
- if (output.startsWith('List<') && input === 'String') return true
60
+ if (output.startsWith && output.startsWith('List<') && input === 'String') return true
66
61
  return false
67
62
  }
68
63
  }
69
64
 
70
65
  // ============================================================================
71
- // TOOL REGISTRY (Typed Morphisms)
66
+ // PROOF NODE (Explainable AI - No Hallucination)
72
67
  // ============================================================================
73
68
 
74
69
  /**
75
- * TOOL_REGISTRY - All available tools as typed morphisms
76
- * Each tool is an arrow in the category of types: A -> B
70
+ * ProofNode - Every result has a proof of its derivation
71
+ * This ensures ZERO hallucination - everything traced to KG or rules
77
72
  */
78
- const TOOL_REGISTRY = {
79
- 'kg.sparql.query': {
80
- name: 'kg.sparql.query',
81
- input: TypeId.String,
82
- output: TypeId.BindingSet,
83
- description: 'Execute SPARQL SELECT query against knowledge graph',
84
- domain: 'knowledge_graph',
85
- constraints: { readOnly: true, maxResults: 10000, timeout: 30000 }
86
- },
87
- 'kg.sparql.construct': {
88
- name: 'kg.sparql.construct',
89
- input: TypeId.String,
90
- output: TypeId.List('Triple'),
91
- description: 'Execute SPARQL CONSTRUCT to build new triples',
92
- domain: 'knowledge_graph',
93
- constraints: { readOnly: true }
94
- },
95
- 'kg.sparql.update': {
96
- name: 'kg.sparql.update',
97
- input: TypeId.String,
98
- output: TypeId.Unit,
99
- description: 'Execute SPARQL UPDATE operations',
100
- domain: 'knowledge_graph',
101
- constraints: { readOnly: false }
102
- },
103
- 'kg.motif.find': {
104
- name: 'kg.motif.find',
105
- input: TypeId.String,
106
- output: TypeId.List('Match'),
107
- description: 'Find graph motif patterns (fraud rings, cycles)',
108
- domain: 'graph_analytics',
109
- patterns: {
110
- 'collusion': '(a)-[claims_with]->(p); (b)-[claims_with]->(p); (a)-[knows]->(b)',
111
- 'circular_payment': '(a)-[paid]->(b); (b)-[paid]->(c); (c)-[paid]->(a)',
112
- 'star_pattern': '(center)-[]->(a); (center)-[]->(b); (center)-[]->(c)'
113
- }
114
- },
115
- 'kg.datalog.infer': {
116
- name: 'kg.datalog.infer',
117
- input: TypeId.List('Rule'),
118
- output: TypeId.List('Fact'),
119
- description: 'Apply Datalog rules for logical inference',
120
- domain: 'reasoning',
121
- prebuiltRules: {
122
- 'potential_collusion': 'Claimants who know each other using same provider',
123
- 'staged_accident': 'Soft tissue claims with high amounts in first 90 days',
124
- 'organized_fraud': 'High prior claims + high provider volume'
73
+ class ProofNode {
74
+ constructor(type, value, justification, children = []) {
75
+ this.id = crypto.randomUUID()
76
+ this.type = type // 'axiom' | 'sparql' | 'rule' | 'inference' | 'embedding'
77
+ this.value = value
78
+ this.justification = justification
79
+ this.children = children // DAG structure
80
+ this.timestamp = new Date().toISOString()
81
+ }
82
+
83
+ toDAG() {
84
+ return {
85
+ id: this.id,
86
+ type: this.type,
87
+ value: this.value,
88
+ justification: this.justification,
89
+ children: this.children.map(c => c.toDAG()),
90
+ timestamp: this.timestamp
125
91
  }
126
- },
127
- 'kg.embeddings.search': {
128
- name: 'kg.embeddings.search',
129
- input: TypeId.String,
130
- output: TypeId.List('SimilarEntity'),
131
- description: 'Find semantically similar entities via HNSW index',
132
- domain: 'embeddings',
133
- constraints: { maxK: 100, minThreshold: 0.0, maxThreshold: 1.0 }
134
- },
135
- 'kg.graphframe.pagerank': {
136
- name: 'kg.graphframe.pagerank',
137
- input: TypeId.Map('String', 'Float64'),
138
- output: TypeId.Map('String', 'Float64'),
139
- description: 'Compute PageRank to find central entities',
140
- domain: 'graph_analytics'
141
- },
142
- 'kg.graphframe.triangles': {
143
- name: 'kg.graphframe.triangles',
144
- input: TypeId.String,
145
- output: TypeId.Int64,
146
- description: 'Count triangles in graph (fraud ring indicator)',
147
- domain: 'graph_analytics'
148
- },
149
- 'kg.graphframe.components': {
150
- name: 'kg.graphframe.components',
151
- input: TypeId.String,
152
- output: TypeId.Map('String', 'Int64'),
153
- description: 'Find connected components (network clusters)',
154
- domain: 'graph_analytics'
155
92
  }
156
93
  }
157
94
 
158
95
  // ============================================================================
159
- // LLM PLANNER (Natural Language -> Typed Tool Pipelines)
96
+ // EXECUTION TRACE (Full Explainability)
160
97
  // ============================================================================
161
98
 
162
99
  /**
163
- * LLMPlanner - Converts natural language to validated tool pipelines
164
- * Implements type checking before execution (Curry-Howard correspondence)
100
+ * ExecutionTrace - Complete record of agent reasoning
101
+ * Shows: what SPARQL was executed, what rules were applied, what DAG was built
165
102
  */
166
- class LLMPlanner {
167
- constructor(model, tools = TOOL_REGISTRY) {
168
- this.model = model // 'claude-sonnet-4' | 'gpt-4o' | 'mock'
169
- this.tools = tools
170
- this.planHistory = []
103
+ class ExecutionTrace {
104
+ constructor() {
105
+ this.steps = []
106
+ this.sparqlQueries = []
107
+ this.rulesApplied = []
108
+ this.proofRoots = []
109
+ this.startTime = Date.now()
110
+ }
111
+
112
+ addStep(step) {
113
+ this.steps.push({
114
+ ...step,
115
+ timestamp: Date.now() - this.startTime
116
+ })
171
117
  }
172
118
 
173
- /**
174
- * Generate an execution plan from natural language
175
- * @param {string} prompt - Natural language request
176
- * @param {object} context - Additional context (history, domain hints)
177
- * @returns {object} Validated execution plan
178
- */
179
- async plan(prompt, context = {}) {
180
- // Step 1: Decompose natural language into intent
181
- const intent = this.decomposeIntent(prompt)
182
-
183
- // Step 2: Select tools based on intent
184
- const selectedTools = this.selectTools(intent)
185
-
186
- // Step 3: Validate type composition
187
- const validation = this.validateComposition(selectedTools)
188
- if (!validation.valid) {
189
- throw new Error(`Type composition error: ${validation.error}`)
190
- }
191
-
192
- // Step 4: Generate execution plan
193
- const plan = {
194
- id: `plan_${Date.now()}`,
195
- prompt,
196
- intent,
197
- steps: selectedTools.map((tool, i) => ({
198
- id: i + 1,
199
- tool: tool.name,
200
- input_type: tool.input,
201
- output_type: tool.output,
202
- args: this.generateArgs(tool, intent, context)
203
- })),
204
- type_chain: validation.chain,
205
- confidence: this.calculateConfidence(intent, selectedTools),
206
- explanation: `Execute ${intent.action} using ${selectedTools.length} tools`
207
- }
208
-
209
- this.planHistory.push(plan)
210
- return plan
119
+ addSparql(query, results) {
120
+ this.sparqlQueries.push({
121
+ query,
122
+ resultCount: results.length,
123
+ timestamp: Date.now() - this.startTime
124
+ })
211
125
  }
212
126
 
213
- /**
214
- * Decompose natural language into structured intent
215
- */
216
- decomposeIntent(prompt) {
217
- const lowerPrompt = prompt.toLowerCase()
127
+ addRule(rule, premises, conclusion) {
128
+ this.rulesApplied.push({
129
+ rule,
130
+ premises,
131
+ conclusion,
132
+ timestamp: Date.now() - this.startTime
133
+ })
134
+ }
218
135
 
219
- if (lowerPrompt.includes('suspicious') || lowerPrompt.includes('fraud')) {
220
- return {
221
- action: 'detect_fraud',
222
- target: 'claims',
223
- domain: 'insurance_fraud',
224
- entities: ['claims', 'claimants', 'providers']
225
- }
226
- }
136
+ addProof(proofNode) {
137
+ this.proofRoots.push(proofNode)
138
+ }
227
139
 
228
- if (lowerPrompt.includes('similar') || lowerPrompt.includes('like')) {
229
- return {
230
- action: 'find_similar',
231
- target: 'entities',
232
- domain: 'embeddings'
140
+ toExplainableOutput() {
141
+ return {
142
+ execution_time_ms: Date.now() - this.startTime,
143
+ steps: this.steps,
144
+ sparql_queries: this.sparqlQueries,
145
+ rules_applied: this.rulesApplied,
146
+ proof_dag: this.proofRoots.map(p => p.toDAG()),
147
+ summary: {
148
+ total_sparql_queries: this.sparqlQueries.length,
149
+ total_rules_applied: this.rulesApplied.length,
150
+ proof_depth: this._maxDepth(this.proofRoots)
233
151
  }
234
152
  }
153
+ }
235
154
 
236
- if (lowerPrompt.includes('rule') || lowerPrompt.includes('proof')) {
237
- return {
238
- action: 'explain_derivation',
239
- target: 'inference',
240
- domain: 'reasoning'
241
- }
242
- }
155
+ _maxDepth(nodes) {
156
+ if (!nodes.length) return 0
157
+ return 1 + Math.max(0, ...nodes.map(n => this._maxDepth(n.children)))
158
+ }
159
+ }
243
160
 
244
- if (lowerPrompt.includes('professor') || lowerPrompt.includes('student')) {
245
- return {
246
- action: 'query',
247
- target: 'academic',
248
- domain: 'lubm'
249
- }
250
- }
161
+ // ============================================================================
162
+ // DATALOG RULES (Configurable Reasoning)
163
+ // ============================================================================
251
164
 
252
- return {
253
- action: 'query',
254
- target: 'knowledge_graph',
255
- domain: 'general'
256
- }
257
- }
165
+ /**
166
+ * DatalogRuleSet - Configurable rules for agent reasoning
167
+ * Users can add/remove/customize rules
168
+ */
169
+ class DatalogRuleSet {
170
+ constructor() {
171
+ this.rules = new Map()
172
+ this._loadDefaultRules()
173
+ }
174
+
175
+ _loadDefaultRules() {
176
+ // Fraud detection rules
177
+ this.addRule('potential_fraud', {
178
+ head: { predicate: 'potential_fraud', args: ['?claim'] },
179
+ body: [
180
+ { predicate: 'claim', args: ['?claim', '?amount', '?claimant'] },
181
+ { predicate: 'risk_score', args: ['?claimant', '?score'] },
182
+ { filter: '?score > 0.7' }
183
+ ],
184
+ description: 'Claims from high-risk claimants'
185
+ })
258
186
 
259
- /**
260
- * Select tools based on intent
261
- */
262
- selectTools(intent) {
263
- const toolsByAction = {
264
- 'detect_fraud': [
265
- this.tools['kg.sparql.query'],
266
- this.tools['kg.graphframe.triangles'],
267
- this.tools['kg.datalog.infer']
187
+ this.addRule('collusion_pattern', {
188
+ head: { predicate: 'collusion', args: ['?a', '?b', '?provider'] },
189
+ body: [
190
+ { predicate: 'claims_with', args: ['?a', '?provider'] },
191
+ { predicate: 'claims_with', args: ['?b', '?provider'] },
192
+ { predicate: 'knows', args: ['?a', '?b'] },
193
+ { filter: '?a != ?b' }
268
194
  ],
269
- 'find_similar': [
270
- this.tools['kg.embeddings.search']
195
+ description: 'Two claimants who know each other using same provider'
196
+ })
197
+
198
+ this.addRule('circular_payment', {
199
+ head: { predicate: 'circular_payment', args: ['?a', '?b', '?c'] },
200
+ body: [
201
+ { predicate: 'paid', args: ['?a', '?b'] },
202
+ { predicate: 'paid', args: ['?b', '?c'] },
203
+ { predicate: 'paid', args: ['?c', '?a'] }
271
204
  ],
272
- 'explain_derivation': [
273
- this.tools['kg.datalog.infer']
205
+ description: 'Circular payment pattern (A->B->C->A)'
206
+ })
207
+
208
+ // Academic rules (LUBM)
209
+ this.addRule('advisor_relationship', {
210
+ head: { predicate: 'advised_by', args: ['?student', '?professor'] },
211
+ body: [
212
+ { predicate: 'type', args: ['?student', 'Student'] },
213
+ { predicate: 'advisor', args: ['?student', '?professor'] }
274
214
  ],
275
- 'query': [
276
- this.tools['kg.sparql.query']
277
- ]
278
- }
215
+ description: 'Student-advisor relationship'
216
+ })
217
+ }
279
218
 
280
- return toolsByAction[intent.action] || [this.tools['kg.sparql.query']]
219
+ addRule(name, rule) {
220
+ this.rules.set(name, {
221
+ name,
222
+ ...rule,
223
+ added_at: new Date().toISOString()
224
+ })
281
225
  }
282
226
 
283
- /**
284
- * Validate type composition (Category Theory morphism composition)
285
- */
286
- validateComposition(tools) {
287
- for (let i = 0; i < tools.length - 1; i++) {
288
- const current = tools[i]
289
- const next = tools[i + 1]
290
- if (!TypeId.isCompatible(current.output, next.input)) {
291
- return {
292
- valid: false,
293
- error: `${current.name}.output (${current.output}) is incompatible with ${next.name}.input (${next.input})`
294
- }
295
- }
296
- }
227
+ removeRule(name) {
228
+ return this.rules.delete(name)
229
+ }
297
230
 
298
- const chain = tools.map(t => t.input).join(' -> ') + ' -> ' + tools[tools.length - 1].output
299
- return { valid: true, chain }
231
+ getRule(name) {
232
+ return this.rules.get(name)
300
233
  }
301
234
 
302
- /**
303
- * Generate tool-specific arguments
304
- */
305
- generateArgs(tool, intent, context) {
306
- switch (tool.name) {
307
- case 'kg.sparql.query':
308
- return { query: this._generateSPARQL(intent) }
309
- case 'kg.graphframe.triangles':
310
- return { graph: 'default' }
311
- case 'kg.datalog.infer':
312
- return { rules: ['potential_collusion', 'staged_accident'] }
313
- case 'kg.embeddings.search':
314
- return { entityId: intent.entities?.[0] || 'unknown', k: 10, threshold: 0.7 }
315
- default:
316
- return {}
317
- }
235
+ getAllRules() {
236
+ return Array.from(this.rules.values())
318
237
  }
319
238
 
320
- _generateSPARQL(intent) {
321
- if (intent.domain === 'insurance_fraud') {
322
- return `PREFIX ins: <http://insurance.example.org/>
323
- SELECT ?claim ?claimant ?amount ?riskScore
324
- WHERE {
325
- ?claim a ins:Claim ;
326
- ins:claimant ?claimant ;
327
- ins:amount ?amount ;
328
- ins:riskScore ?riskScore .
329
- FILTER (?riskScore > 0.7)
330
- }`
331
- }
332
- return 'SELECT * WHERE { ?s ?p ?o } LIMIT 10'
239
+ toSparqlConstructs() {
240
+ return this.getAllRules().map(rule => this._ruleToSparql(rule))
333
241
  }
334
242
 
335
- calculateConfidence(intent, tools) {
336
- const baseConfidence = 0.85
337
- const toolBonus = tools.length * 0.02
338
- const intentBonus = intent.domain === 'insurance_fraud' ? 0.05 : 0
339
- return Math.min(0.99, baseConfidence + toolBonus + intentBonus)
243
+ _ruleToSparql(rule) {
244
+ const headTriple = `?${rule.head.args[0]} <http://hypermind.ai/rules#${rule.head.predicate}> ?result`
245
+ const bodyPatterns = rule.body
246
+ .filter(b => b.predicate)
247
+ .map(b => `?${b.args[0]} <http://hypermind.ai/kg#${b.predicate}> ?${b.args[1]}`)
248
+ .join(' . ')
249
+ const filters = rule.body
250
+ .filter(b => b.filter)
251
+ .map(b => `FILTER(${b.filter})`)
252
+ .join(' ')
253
+
254
+ return {
255
+ name: rule.name,
256
+ sparql: `CONSTRUCT { ${headTriple} } WHERE { ${bodyPatterns} ${filters} }`
257
+ }
340
258
  }
341
259
  }
342
260
 
343
261
  // ============================================================================
344
- // WASM SANDBOX (Capability-Based Security)
262
+ // WASM SANDBOX (Secure Execution)
345
263
  // ============================================================================
346
264
 
347
265
  /**
348
- * WasmSandbox - Secure WASM execution environment with capabilities
349
- *
350
- * All interaction with the Rust core flows through WASM for complete security:
351
- * - Isolated linear memory (no direct host access)
352
- * - CPU fuel metering (configurable operation limits)
353
- * - Capability-based permissions (ReadKG, WriteKG, ExecuteTool)
354
- * - Memory limits (configurable maximum allocation)
355
- * - Full audit logging (all tool invocations recorded)
356
- *
357
- * The WASM sandbox ensures that agent tool execution cannot:
358
- * - Access the filesystem
359
- * - Make unauthorized network calls
360
- * - Exceed allocated resources
361
- * - Bypass security boundaries
362
- *
363
- * @example
364
- * const sandbox = new WasmSandbox({
365
- * capabilities: ['ReadKG', 'ExecuteTool'],
366
- * fuelLimit: 1000000,
367
- * maxMemory: 64 * 1024 * 1024
368
- * })
266
+ * WasmSandbox - Capability-based security with fuel metering
369
267
  */
370
268
  class WasmSandbox {
371
269
  constructor(config = {}) {
372
- this.config = {
373
- maxMemory: config.maxMemory || 64 * 1024 * 1024, // 64MB
374
- maxExecTime: config.maxExecTime || 10000, // 10 seconds
375
- capabilities: config.capabilities || ['ReadKG', 'ExecuteTool'],
376
- fuelLimit: config.fuelLimit || 1000000 // 1M fuel units
377
- }
378
- this.fuel = this.config.fuelLimit
379
- this.memoryUsed = 0
270
+ this.capabilities = new Set(config.capabilities || ['ReadKG', 'ExecuteTool'])
271
+ this.fuelLimit = config.fuelLimit || 1000000
272
+ this.fuel = this.fuelLimit
380
273
  this.auditLog = []
381
- this.startTime = null
382
274
  }
383
275
 
384
- /**
385
- * Create an Object Proxy for gRPC-style tool invocation
386
- */
387
- createObjectProxy(tools) {
388
- const sandbox = this
389
-
390
- return new Proxy({}, {
391
- get(target, toolName) {
392
- return async (args) => {
393
- // Security Check 1: Capability verification
394
- if (!sandbox.hasCapability('ExecuteTool')) {
395
- const error = `BLOCKED: Missing ExecuteTool capability`
396
- sandbox._logAudit(toolName, args, null, 'DENIED', error)
397
- throw new Error(error)
398
- }
399
-
400
- // Security Check 2: Fuel metering
401
- const fuelCost = sandbox._calculateFuelCost(toolName, args)
402
- if (sandbox.fuel < fuelCost) {
403
- const error = `BLOCKED: Insufficient fuel (need ${fuelCost}, have ${sandbox.fuel})`
404
- sandbox._logAudit(toolName, args, null, 'DENIED', error)
405
- throw new Error(error)
406
- }
407
- sandbox.fuel -= fuelCost
408
-
409
- // Security Check 3: Memory bounds
410
- const estimatedMemory = JSON.stringify(args).length * 2
411
- if (sandbox.memoryUsed + estimatedMemory > sandbox.config.maxMemory) {
412
- const error = `BLOCKED: Memory limit exceeded`
413
- sandbox._logAudit(toolName, args, null, 'DENIED', error)
414
- throw new Error(error)
415
- }
416
- sandbox.memoryUsed += estimatedMemory
417
-
418
- // Security Check 4: Execution time
419
- if (!sandbox.startTime) {
420
- sandbox.startTime = Date.now()
421
- }
422
- if (Date.now() - sandbox.startTime > sandbox.config.maxExecTime) {
423
- const error = `BLOCKED: Execution time limit exceeded`
424
- sandbox._logAudit(toolName, args, null, 'DENIED', error)
425
- throw new Error(error)
426
- }
427
-
428
- // Execute tool
429
- const tool = tools[toolName]
430
- if (!tool) {
431
- const error = `BLOCKED: Unknown tool ${toolName}`
432
- sandbox._logAudit(toolName, args, null, 'DENIED', error)
433
- throw new Error(error)
434
- }
435
-
436
- const result = tool.execute ? await tool.execute(args) : { status: 'success' }
437
- sandbox._logAudit(toolName, args, result, 'OK')
438
-
439
- return result
440
- }
441
- }
442
- })
276
+ hasCapability(cap) {
277
+ return this.capabilities.has(cap)
443
278
  }
444
279
 
445
- hasCapability(cap) {
446
- return this.config.capabilities.includes(cap)
447
- }
448
-
449
- _calculateFuelCost(toolName, args) {
450
- const baseCosts = {
451
- 'kg.sparql.query': 500,
452
- 'kg.motif.find': 1000,
453
- 'kg.datalog.infer': 2000,
454
- 'kg.embeddings.search': 300,
455
- 'kg.graphframe.pagerank': 5000,
456
- 'kg.graphframe.triangles': 1500,
457
- 'kg.graphframe.components': 2000
280
+ consumeFuel(amount) {
281
+ if (this.fuel < amount) {
282
+ throw new Error(`Insufficient fuel: need ${amount}, have ${this.fuel}`)
458
283
  }
459
- return (baseCosts[toolName] || 100) + JSON.stringify(args).length * 10
284
+ this.fuel -= amount
460
285
  }
461
286
 
462
- _logAudit(tool, args, result, status, error = null) {
287
+ log(action, args, result, status) {
463
288
  this.auditLog.push({
464
289
  timestamp: new Date().toISOString(),
465
- tool,
466
- args,
467
- result: result ? { status: result.status } : null,
290
+ action,
291
+ args: JSON.stringify(args).slice(0, 200),
468
292
  status,
469
- error,
470
293
  fuel_remaining: this.fuel
471
294
  })
472
295
  }
@@ -474,2591 +297,1109 @@ class WasmSandbox {
474
297
  getAuditLog() {
475
298
  return this.auditLog
476
299
  }
477
-
478
- getMetrics() {
479
- return {
480
- fuel_initial: this.config.fuelLimit,
481
- fuel_remaining: this.fuel,
482
- fuel_consumed: this.config.fuelLimit - this.fuel,
483
- memory_used: this.memoryUsed,
484
- memory_limit: this.config.maxMemory,
485
- capabilities: this.config.capabilities,
486
- tool_calls: this.auditLog.length
487
- }
488
- }
489
300
  }
490
301
 
491
302
  // ============================================================================
492
- // MEMORY LAYER (GraphDB-Powered Agent Memory System)
303
+ // MEMORY MANAGER (Episode Memory with KG Integration)
493
304
  // ============================================================================
494
305
 
495
306
  /**
496
- * AgentState - Lifecycle states for agent runtime
497
- */
498
- const AgentState = {
499
- CREATED: 'CREATED',
500
- READY: 'READY',
501
- RUNNING: 'RUNNING',
502
- PAUSED: 'PAUSED',
503
- COMPLETED: 'COMPLETED',
504
- FAILED: 'FAILED'
505
- }
506
-
507
- /**
508
- * AgentRuntime - Core agent identity and state management
509
- *
510
- * Provides:
511
- * - Unique agent identity (UUID)
512
- * - Lifecycle state management
513
- * - Memory layer orchestration
514
- * - Execution context tracking
307
+ * MemoryManager - Stores agent episodes in KG for retrieval
515
308
  */
516
- class AgentRuntime {
517
- constructor(config = {}) {
518
- // Agent Identity
519
- this.id = config.id || `agent_${crypto.randomUUID()}`
520
- this.name = config.name || 'unnamed-agent'
521
- this.version = config.version || '1.0.0'
522
- this.createdAt = new Date().toISOString()
523
-
524
- // State Management
525
- this.state = AgentState.CREATED
526
- this.stateHistory = [{
527
- state: AgentState.CREATED,
528
- timestamp: this.createdAt,
529
- reason: 'Agent initialized'
530
- }]
531
-
532
- // Memory Configuration
533
- this.memoryConfig = {
534
- workingMemoryCapacity: config.workingMemoryCapacity || 100,
535
- episodicRetentionDays: config.episodicRetentionDays || 30,
536
- retrievalWeights: config.retrievalWeights || {
537
- recency: 0.3, // α - time decay
538
- relevance: 0.5, // β - semantic similarity
539
- importance: 0.2 // γ - access frequency
540
- },
541
- recencyDecayRate: config.recencyDecayRate || 0.995 // per hour
309
+ class MemoryManager {
310
+ constructor(kg, embeddingService) {
311
+ this.kg = kg
312
+ this.embeddingService = embeddingService
313
+ this.episodes = []
314
+ this.decayRate = 0.995 // Per hour
315
+ this.weights = { recency: 0.3, relevance: 0.5, importance: 0.2 }
316
+ this.workingMemory = new Map()
317
+ this.executions = []
318
+ }
319
+
320
+ async store(prompt, result, success, durationMs) {
321
+ const episode = {
322
+ id: crypto.randomUUID(),
323
+ prompt,
324
+ result,
325
+ success,
326
+ durationMs,
327
+ timestamp: new Date().toISOString(),
328
+ accessCount: 0,
329
+ embedding: null
542
330
  }
543
331
 
544
- // Execution Context
545
- this.currentExecution = null
546
- this.executionCount = 0
547
- this.lastActiveAt = this.createdAt
548
-
549
- // Metrics
550
- this.metrics = {
551
- totalExecutions: 0,
552
- successfulExecutions: 0,
553
- failedExecutions: 0,
554
- totalMemoryRetrievals: 0,
555
- avgExecutionTimeMs: 0
332
+ // Generate embedding for semantic retrieval
333
+ if (this.embeddingService) {
334
+ try {
335
+ episode.embedding = await this._getEmbedding(prompt)
336
+ } catch (err) {
337
+ // Continue without embedding
338
+ }
556
339
  }
557
- }
558
340
 
559
- /**
560
- * Transition agent to a new state
561
- */
562
- transitionTo(newState, reason = '') {
563
- const validTransitions = {
564
- [AgentState.CREATED]: [AgentState.READY],
565
- [AgentState.READY]: [AgentState.RUNNING, AgentState.PAUSED],
566
- [AgentState.RUNNING]: [AgentState.READY, AgentState.COMPLETED, AgentState.FAILED, AgentState.PAUSED],
567
- [AgentState.PAUSED]: [AgentState.READY, AgentState.RUNNING],
568
- [AgentState.COMPLETED]: [AgentState.READY],
569
- [AgentState.FAILED]: [AgentState.READY]
570
- }
341
+ this.episodes.push(episode)
571
342
 
572
- if (!validTransitions[this.state]?.includes(newState)) {
573
- throw new Error(`Invalid state transition: ${this.state} -> ${newState}`)
343
+ // Also store in KG as RDF
344
+ if (this.kg) {
345
+ try {
346
+ const ttl = this._episodeToTurtle(episode)
347
+ this.kg.loadTtl(ttl, 'http://hypermind.ai/memory/')
348
+ } catch (err) {
349
+ // Continue without KG storage
350
+ }
574
351
  }
575
352
 
576
- this.state = newState
577
- this.stateHistory.push({
578
- state: newState,
579
- timestamp: new Date().toISOString(),
580
- reason
581
- })
582
- this.lastActiveAt = new Date().toISOString()
583
-
584
- return this
353
+ return episode
585
354
  }
586
355
 
587
- /**
588
- * Mark agent as ready for execution
589
- */
590
- ready() {
591
- return this.transitionTo(AgentState.READY, 'Agent initialized and ready')
592
- }
356
+ async retrieve(query, limit = 10) {
357
+ if (!this.episodes.length) return []
593
358
 
594
- /**
595
- * Start an execution
596
- */
597
- startExecution(prompt) {
598
- this.transitionTo(AgentState.RUNNING, `Starting execution: ${prompt.slice(0, 50)}...`)
599
- this.currentExecution = {
600
- id: `exec_${Date.now()}_${crypto.randomUUID().slice(0, 8)}`,
601
- prompt,
602
- startTime: Date.now(),
603
- steps: []
604
- }
605
- this.executionCount++
606
- return this.currentExecution.id
359
+ const queryEmbedding = this.embeddingService
360
+ ? await this._getEmbedding(query)
361
+ : null
362
+
363
+ const scored = this.episodes.map(ep => ({
364
+ episode: ep,
365
+ score: this._calculateScore(ep, query, queryEmbedding)
366
+ }))
367
+
368
+ return scored
369
+ .sort((a, b) => b.score - a.score)
370
+ .slice(0, limit)
371
+ .map(s => {
372
+ s.episode.accessCount++
373
+ return s
374
+ })
607
375
  }
608
376
 
609
- /**
610
- * Complete current execution
611
- */
612
- completeExecution(result, success = true) {
613
- if (!this.currentExecution) return
614
-
615
- this.currentExecution.endTime = Date.now()
616
- this.currentExecution.durationMs = this.currentExecution.endTime - this.currentExecution.startTime
617
- this.currentExecution.result = result
618
- this.currentExecution.success = success
619
-
620
- // Update metrics
621
- this.metrics.totalExecutions++
622
- if (success) {
623
- this.metrics.successfulExecutions++
377
+ _calculateScore(episode, query, queryEmbedding) {
378
+ const hoursAgo = (Date.now() - new Date(episode.timestamp).getTime()) / 3600000
379
+ const recency = Math.pow(this.decayRate, hoursAgo)
380
+
381
+ let relevance = 0
382
+ if (queryEmbedding && episode.embedding) {
383
+ relevance = this._cosineSimilarity(queryEmbedding, episode.embedding)
624
384
  } else {
625
- this.metrics.failedExecutions++
385
+ // Fallback to word overlap
386
+ relevance = this._wordOverlap(query, episode.prompt)
626
387
  }
627
- this.metrics.avgExecutionTimeMs =
628
- (this.metrics.avgExecutionTimeMs * (this.metrics.totalExecutions - 1) +
629
- this.currentExecution.durationMs) / this.metrics.totalExecutions
630
388
 
631
- const execution = this.currentExecution
632
- this.currentExecution = null
633
- this.transitionTo(AgentState.READY, `Execution completed: ${success ? 'success' : 'failed'}`)
389
+ const maxAccess = Math.max(...this.episodes.map(e => e.accessCount), 1)
390
+ const importance = Math.log10(episode.accessCount + 1) / Math.log10(maxAccess + 1)
634
391
 
635
- return execution
392
+ return this.weights.recency * recency +
393
+ this.weights.relevance * relevance +
394
+ this.weights.importance * importance
636
395
  }
637
396
 
638
- /**
639
- * Get agent identity info
640
- */
641
- getIdentity() {
642
- return {
643
- id: this.id,
644
- name: this.name,
645
- version: this.version,
646
- createdAt: this.createdAt,
647
- state: this.state,
648
- executionCount: this.executionCount,
649
- lastActiveAt: this.lastActiveAt
397
+ _cosineSimilarity(a, b) {
398
+ if (!a || !b || a.length !== b.length) return 0
399
+ let dot = 0, normA = 0, normB = 0
400
+ for (let i = 0; i < a.length; i++) {
401
+ dot += a[i] * b[i]
402
+ normA += a[i] * a[i]
403
+ normB += b[i] * b[i]
650
404
  }
405
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-9)
651
406
  }
652
407
 
653
- /**
654
- * Get runtime metrics
655
- */
656
- getMetrics() {
657
- return {
658
- ...this.metrics,
659
- uptime: Date.now() - new Date(this.createdAt).getTime(),
660
- currentState: this.state
661
- }
408
+ _wordOverlap(a, b) {
409
+ const wordsA = new Set(a.toLowerCase().split(/\s+/))
410
+ const wordsB = new Set(b.toLowerCase().split(/\s+/))
411
+ const intersection = [...wordsA].filter(w => wordsB.has(w))
412
+ return intersection.length / Math.max(wordsA.size, wordsB.size, 1)
662
413
  }
663
- }
664
414
 
665
- /**
666
- * WorkingMemory - Current execution context (in-memory)
667
- *
668
- * Fast, ephemeral memory for the current task:
669
- * - Current conversation context
670
- * - Active bindings and variables
671
- * - Tool execution results (recent)
672
- * - Scratchpad for reasoning
673
- */
674
- class WorkingMemory {
675
- constructor(capacity = 100) {
676
- this.capacity = capacity
677
- this.context = [] // Current conversation/task context
678
- this.bindings = new Map() // Variable bindings
679
- this.scratchpad = new Map() // Temporary reasoning space
680
- this.toolResults = [] // Recent tool execution results
681
- this.focus = null // Current focus entity/topic
415
+ async _getEmbedding(text) {
416
+ if (!this.embeddingService) return null
417
+ return this.embeddingService.embed(text)
682
418
  }
683
419
 
684
- /**
685
- * Add item to working memory context
686
- */
687
- addContext(item) {
688
- this.context.push({
689
- ...item,
690
- timestamp: item.timestamp || Date.now(),
691
- id: item.id !== undefined ? item.id : `ctx_${Date.now()}`
692
- })
693
-
694
- // Evict oldest if over capacity
695
- while (this.context.length > this.capacity) {
696
- this.context.shift()
697
- }
698
-
699
- return this
700
- }
420
+ _episodeToTurtle(episode) {
421
+ return `
422
+ @prefix am: <http://hypermind.ai/memory#> .
423
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
701
424
 
702
- /**
703
- * Set a variable binding
704
- */
705
- setBinding(key, value) {
706
- this.bindings.set(key, {
707
- value,
708
- timestamp: Date.now(),
709
- accessCount: 0
710
- })
711
- return this
425
+ <http://hypermind.ai/episode/${episode.id}> a am:Episode ;
426
+ am:prompt "${episode.prompt.replace(/"/g, '\\"')}" ;
427
+ am:success "${episode.success}"^^xsd:boolean ;
428
+ am:durationMs "${episode.durationMs}"^^xsd:integer ;
429
+ am:timestamp "${episode.timestamp}"^^xsd:dateTime .
430
+ `
712
431
  }
713
432
 
714
433
  /**
715
- * Get a variable binding
434
+ * Store a tool execution as an episode for audit trail
435
+ * @param {Object} execution - Execution record with id, prompt, tool, output, success, durationMs
716
436
  */
717
- getBinding(key) {
718
- const binding = this.bindings.get(key)
719
- if (binding) {
720
- binding.accessCount++
721
- return binding.value
437
+ async storeExecution(execution) {
438
+ const episode = {
439
+ id: execution.id || crypto.randomUUID(),
440
+ prompt: execution.prompt,
441
+ result: execution.output,
442
+ success: execution.success,
443
+ durationMs: execution.durationMs,
444
+ tool: execution.tool,
445
+ timestamp: new Date().toISOString(),
446
+ accessCount: 0,
447
+ embedding: null
722
448
  }
723
- return null
724
- }
725
449
 
726
- /**
727
- * Store tool result
728
- */
729
- storeToolResult(toolName, args, result) {
730
- this.toolResults.push({
731
- tool: toolName,
732
- args,
733
- result,
734
- timestamp: Date.now()
735
- })
736
-
737
- // Keep only recent results
738
- if (this.toolResults.length > 20) {
739
- this.toolResults.shift()
450
+ // Generate embedding for semantic retrieval
451
+ if (this.embeddingService) {
452
+ try {
453
+ episode.embedding = await this._getEmbedding(execution.prompt)
454
+ } catch (err) {
455
+ // Continue without embedding
456
+ }
740
457
  }
741
458
 
742
- return this
743
- }
744
-
745
- /**
746
- * Set current focus
747
- */
748
- setFocus(entity) {
749
- this.focus = {
750
- entity,
751
- timestamp: Date.now()
752
- }
753
- return this
754
- }
459
+ this.executions.push(episode)
460
+ this.episodes.push(episode)
755
461
 
756
- /**
757
- * Write to scratchpad
758
- */
759
- scratch(key, value) {
760
- this.scratchpad.set(key, value)
761
- return this
462
+ return episode
762
463
  }
763
464
 
764
465
  /**
765
- * Read from scratchpad
466
+ * Add data to working memory (ephemeral, in-context)
467
+ * @param {Object} data - Data to store in working memory
766
468
  */
767
- readScratch(key) {
768
- return this.scratchpad.get(key)
469
+ addToWorking(data) {
470
+ const key = data.type || `working-${Date.now()}`
471
+ this.workingMemory.set(key, {
472
+ ...data,
473
+ addedAt: Date.now()
474
+ })
475
+ return key
769
476
  }
770
477
 
771
478
  /**
772
- * Get recent context items
479
+ * Get data from working memory
480
+ * @param {string} key - Key to retrieve
773
481
  */
774
- getRecentContext(limit = 10) {
775
- return this.context.slice(-limit)
482
+ getFromWorking(key) {
483
+ return this.workingMemory.get(key)
776
484
  }
777
485
 
778
486
  /**
779
487
  * Clear working memory
780
488
  */
781
- clear() {
782
- this.context = []
783
- this.bindings.clear()
784
- this.scratchpad.clear()
785
- this.toolResults = []
786
- this.focus = null
787
- return this
489
+ clearWorking() {
490
+ this.workingMemory.clear()
788
491
  }
789
492
 
790
493
  /**
791
- * Export working memory state
494
+ * Get memory statistics
792
495
  */
793
- export() {
496
+ getStats() {
794
497
  return {
795
- context: this.context,
796
- bindings: Object.fromEntries(this.bindings),
797
- scratchpad: Object.fromEntries(this.scratchpad),
798
- toolResults: this.toolResults,
799
- focus: this.focus
498
+ episodeCount: this.episodes.length,
499
+ executionCount: this.executions.length,
500
+ workingMemorySize: this.workingMemory.size,
501
+ weights: { ...this.weights },
502
+ decayRate: this.decayRate,
503
+ // Structured format for compatibility
504
+ working: {
505
+ contextSize: this.workingMemory.size,
506
+ items: Array.from(this.workingMemory.keys())
507
+ },
508
+ episodic: {
509
+ episodeCount: this.episodes.length,
510
+ executionCount: this.executions.length
511
+ }
800
512
  }
801
513
  }
802
514
  }
803
515
 
516
+ // ============================================================================
517
+ // HYPERMIND AGENT (Main API - Symbolica Agentica Pattern)
518
+ // ============================================================================
519
+
804
520
  /**
805
- * EpisodicMemory - Execution history stored in GraphDB
521
+ * HyperMindAgent - Neuro-Symbolic AI Agent with ZERO hallucination
806
522
  *
807
- * Persistent memory of agent executions:
808
- * - Past prompts and responses
809
- * - Tool invocation history
810
- * - Success/failure outcomes
811
- * - Temporal relationships
523
+ * All constructor parameters are required - no backward compatibility needed.
524
+ * The agent spawns a runtime, creates WASM sandbox, and executes via typed tools.
525
+ *
526
+ * @example
527
+ * const kg = new GraphDB('http://example.org/')
528
+ * const memory = new MemoryManager(kg)
529
+ * const embeddings = new EmbeddingService({ provider: 'mock' })
530
+ *
531
+ * const agent = new HyperMindAgent({
532
+ * kg,
533
+ * memory,
534
+ * embeddings,
535
+ * apiKey: 'sk-...',
536
+ * rules: new DatalogRuleSet()
537
+ * })
538
+ *
539
+ * const result = await agent.call('Find fraudulent claims')
540
+ * console.log(result.answer)
541
+ * console.log(result.explanation.sparql_queries)
542
+ * console.log(result.explanation.rules_applied)
543
+ * console.log(result.explanation.proof_dag)
812
544
  */
813
- class EpisodicMemory {
814
- constructor(graphDb, namespace = 'http://hypermind.ai/episodic/') {
815
- this.graphDb = graphDb
816
- this.namespace = namespace
817
- this.episodeCount = 0
545
+ class HyperMindAgent {
546
+ constructor(config) {
547
+ if (!config.kg) {
548
+ throw new Error('kg (Knowledge Graph) is required')
549
+ }
550
+
551
+ this.kg = config.kg
552
+ this.memory = config.memory || new MemoryManager(config.kg, config.embeddings)
553
+ this.embeddings = config.embeddings || null
554
+ this.apiKey = config.apiKey || null
555
+ this.rules = config.rules || new DatalogRuleSet()
556
+ this.sandbox = new WasmSandbox(config.sandbox || {})
557
+ this.name = config.name || 'hypermind-agent'
558
+
559
+ // Intent patterns for natural language -> tool mapping
560
+ this.intentPatterns = this._buildIntentPatterns()
818
561
  }
819
562
 
820
563
  /**
821
- * Store an execution episode
564
+ * Execute a natural language request
565
+ * Returns answer + full explainable AI output
822
566
  */
823
- async storeEpisode(agentId, execution) {
824
- const episodeUri = `${this.namespace}episode/${execution.id}`
825
- const timestamp = new Date().toISOString()
826
-
827
- // Build episode triples
828
- const triples = `
829
- @prefix ep: <${this.namespace}> .
830
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
831
- @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
832
-
833
- <${episodeUri}> rdf:type ep:Episode ;
834
- ep:agentId "${agentId}" ;
835
- ep:executionId "${execution.id}" ;
836
- ep:prompt "${this._escapeForTtl(execution.prompt)}" ;
837
- ep:timestamp "${timestamp}"^^xsd:dateTime ;
838
- ep:durationMs "${execution.durationMs || 0}"^^xsd:integer ;
839
- ep:success "${execution.success}"^^xsd:boolean .
840
- `
841
-
842
- // Store in GraphDB
843
- if (this.graphDb && typeof this.graphDb.loadTtl === 'function') {
844
- await this.graphDb.loadTtl(triples, `${this.namespace}episodes`)
567
+ async call(prompt) {
568
+ const trace = new ExecutionTrace()
569
+ trace.addStep({ type: 'input', prompt })
570
+
571
+ // 1. Check memory for similar past queries
572
+ const memories = await this.memory.retrieve(prompt, 3)
573
+ if (memories.length > 0) {
574
+ trace.addStep({
575
+ type: 'memory_recall',
576
+ similar_queries: memories.map(m => m.episode.prompt)
577
+ })
845
578
  }
846
579
 
847
- this.episodeCount++
580
+ // 2. Classify intent from natural language (no hallucination - pattern matching)
581
+ const intent = this._classifyIntent(prompt)
582
+ trace.addStep({ type: 'intent_classification', intent })
583
+
584
+ // 3. Generate typed execution plan
585
+ const plan = this._generatePlan(intent, prompt)
586
+ trace.addStep({ type: 'execution_plan', plan })
587
+
588
+ // 4. Execute plan in WASM sandbox
589
+ const results = await this._executePlan(plan, trace)
590
+
591
+ // 5. Apply Datalog rules for inference
592
+ const inferences = await this._applyRules(intent, results, trace)
593
+
594
+ // 6. Build proof DAG (explainable AI)
595
+ const proofRoot = this._buildProofDAG(plan, results, inferences, trace)
596
+ trace.addProof(proofRoot)
597
+
598
+ // 7. Format answer
599
+ const answer = this._formatAnswer(results, inferences, intent)
600
+
601
+ // 8. Store episode in memory
602
+ const startTime = trace.startTime
603
+ await this.memory.store(prompt, answer, true, Date.now() - startTime)
848
604
 
849
605
  return {
850
- episodeUri,
851
- timestamp,
852
- stored: true
606
+ answer,
607
+ explanation: trace.toExplainableOutput(),
608
+ raw_results: results,
609
+ inferences,
610
+ proof: proofRoot.toDAG()
853
611
  }
854
612
  }
855
613
 
856
614
  /**
857
- * Store a tool invocation within an episode
615
+ * Configure Datalog rules
858
616
  */
859
- async storeToolInvocation(episodeId, toolName, args, result, success) {
860
- const invocationUri = `${this.namespace}invocation/${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
861
-
862
- const triples = `
863
- @prefix ep: <${this.namespace}> .
864
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
865
-
866
- <${invocationUri}> a ep:ToolInvocation ;
867
- ep:episode <${this.namespace}episode/${episodeId}> ;
868
- ep:tool "${toolName}" ;
869
- ep:timestamp "${new Date().toISOString()}"^^xsd:dateTime ;
870
- ep:success "${success}"^^xsd:boolean .
871
- `
617
+ addRule(name, rule) {
618
+ this.rules.addRule(name, rule)
619
+ }
872
620
 
873
- if (this.graphDb && typeof this.graphDb.loadTtl === 'function') {
874
- await this.graphDb.loadTtl(triples, `${this.namespace}invocations`)
875
- }
621
+ removeRule(name) {
622
+ return this.rules.removeRule(name)
623
+ }
876
624
 
877
- return invocationUri
625
+ getRules() {
626
+ return this.rules.getAllRules()
878
627
  }
879
628
 
880
629
  /**
881
- * Retrieve episodes for an agent
630
+ * Get audit log from sandbox
882
631
  */
883
- async getEpisodes(agentId, options = {}) {
884
- const limit = options.limit || 20
885
- const since = options.since || null
886
-
887
- let query = `
888
- PREFIX ep: <${this.namespace}>
889
- PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
890
-
891
- SELECT ?episode ?prompt ?timestamp ?durationMs ?success
892
- WHERE {
893
- ?episode a ep:Episode ;
894
- ep:agentId "${agentId}" ;
895
- ep:prompt ?prompt ;
896
- ep:timestamp ?timestamp ;
897
- ep:durationMs ?durationMs ;
898
- ep:success ?success .
899
- ${since ? `FILTER(?timestamp > "${since}"^^xsd:dateTime)` : ''}
900
- }
901
- ORDER BY DESC(?timestamp)
902
- LIMIT ${limit}
903
- `
904
-
905
- if (this.graphDb && typeof this.graphDb.querySelect === 'function') {
906
- try {
907
- const results = await this.graphDb.querySelect(query)
908
- return results.map(r => ({
909
- episode: r.bindings?.episode || r.episode,
910
- prompt: r.bindings?.prompt || r.prompt,
911
- timestamp: r.bindings?.timestamp || r.timestamp,
912
- durationMs: parseInt(r.bindings?.durationMs || r.durationMs || '0'),
913
- success: (r.bindings?.success || r.success) === 'true'
914
- }))
915
- } catch (e) {
916
- // GraphDB not available, return empty
917
- return []
918
- }
919
- }
920
-
921
- return []
632
+ getAuditLog() {
633
+ return this.sandbox.getAuditLog()
922
634
  }
923
635
 
924
636
  /**
925
- * Get episodes similar to a prompt (using embeddings if available)
637
+ * Get agent name
926
638
  */
927
- async getSimilarEpisodes(prompt, k = 5, threshold = 0.7) {
928
- // This will be enhanced when EmbeddingService is integrated
929
- // For now, return recent episodes as fallback
930
- return this.getEpisodes('*', { limit: k })
639
+ getName() {
640
+ return this.name
931
641
  }
932
642
 
933
643
  /**
934
- * Calculate recency score with exponential decay
644
+ * Get model name (defaults to 'mock' if no API key)
935
645
  */
936
- calculateRecencyScore(timestamp, decayRate = 0.995) {
937
- const hoursElapsed = (Date.now() - new Date(timestamp).getTime()) / (1000 * 60 * 60)
938
- return Math.pow(decayRate, hoursElapsed)
646
+ getModel() {
647
+ return this.apiKey ? 'configured' : 'mock'
939
648
  }
940
649
 
941
- /**
942
- * Escape string for Turtle format
943
- */
944
- _escapeForTtl(str) {
945
- if (!str) return ''
946
- return str
947
- .replace(/\\/g, '\\\\')
948
- .replace(/"/g, '\\"')
949
- .replace(/\n/g, '\\n')
950
- .replace(/\r/g, '\\r')
951
- .replace(/\t/g, '\\t')
952
- }
953
-
954
- /**
955
- * Get episode count
956
- */
957
- getEpisodeCount() {
958
- return this.episodeCount
959
- }
960
- }
961
-
962
- /**
963
- * LongTermMemory - Source-of-truth Knowledge Graph
964
- *
965
- * Read-only access to the ingestion graph (source of truth):
966
- * - Domain ontologies and schemas
967
- * - Factual knowledge from data ingestion
968
- * - Business rules and constraints
969
- * - Clear separation from agent memory
970
- */
971
- class LongTermMemory {
972
- constructor(graphDb, config = {}) {
973
- this.graphDb = graphDb
974
- this.sourceGraphUri = config.sourceGraphUri || 'http://hypermind.ai/source/'
975
- this.agentGraphUri = config.agentGraphUri || 'http://hypermind.ai/agent/'
976
- this.readOnly = config.readOnly !== false // Default to read-only
977
- this.accessLog = []
978
- }
650
+ // ---- Private Methods ----
979
651
 
980
- /**
981
- * Query the source-of-truth graph (read-only)
982
- */
983
- async querySource(sparql) {
984
- this._logAccess('query', sparql)
985
-
986
- // Ensure query targets source graph
987
- const graphQuery = sparql.includes('FROM') ? sparql :
988
- sparql.replace('WHERE', `FROM <${this.sourceGraphUri}> WHERE`)
989
-
990
- if (this.graphDb && typeof this.graphDb.querySelect === 'function') {
991
- try {
992
- return await this.graphDb.querySelect(graphQuery)
993
- } catch (e) {
994
- return []
652
+ _buildIntentPatterns() {
653
+ return [
654
+ {
655
+ patterns: ['fraud', 'suspicious', 'risk', 'anomaly'],
656
+ intent: 'detect_fraud',
657
+ tools: ['kg.sparql.query', 'kg.datalog.infer', 'kg.graphframe.triangles']
658
+ },
659
+ {
660
+ patterns: ['similar', 'like', 'related', 'nearest'],
661
+ intent: 'find_similar',
662
+ tools: ['kg.embeddings.search']
663
+ },
664
+ {
665
+ patterns: ['explain', 'why', 'how', 'proof', 'derivation'],
666
+ intent: 'explain',
667
+ tools: ['kg.datalog.infer']
668
+ },
669
+ {
670
+ patterns: ['circular', 'cycle', 'ring', 'loop'],
671
+ intent: 'find_patterns',
672
+ tools: ['kg.motif.find', 'kg.graphframe.triangles']
673
+ },
674
+ {
675
+ patterns: ['professor', 'student', 'university', 'department', 'course'],
676
+ intent: 'academic_query',
677
+ tools: ['kg.sparql.query']
678
+ },
679
+ {
680
+ patterns: ['count', 'how many', 'total'],
681
+ intent: 'aggregate',
682
+ tools: ['kg.sparql.query']
995
683
  }
996
- }
997
-
998
- return []
684
+ ]
999
685
  }
1000
686
 
1001
- /**
1002
- * Get entity facts from source graph
1003
- */
1004
- async getEntityFacts(entityUri, limit = 50) {
1005
- const query = `
1006
- SELECT ?p ?o WHERE {
1007
- <${entityUri}> ?p ?o .
1008
- } LIMIT ${limit}
1009
- `
1010
- return this.querySource(query)
1011
- }
1012
-
1013
- /**
1014
- * Get entities by type from source graph
1015
- */
1016
- async getEntitiesByType(typeUri, limit = 100) {
1017
- const query = `
1018
- PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
1019
- SELECT ?entity WHERE {
1020
- ?entity rdf:type <${typeUri}> .
1021
- } LIMIT ${limit}
1022
- `
1023
- return this.querySource(query)
1024
- }
1025
-
1026
- /**
1027
- * Get related entities (1-hop neighbors)
1028
- */
1029
- async getRelatedEntities(entityUri, direction = 'both') {
1030
- let query
1031
- if (direction === 'out') {
1032
- query = `SELECT ?p ?target WHERE { <${entityUri}> ?p ?target . FILTER(isIRI(?target)) }`
1033
- } else if (direction === 'in') {
1034
- query = `SELECT ?source ?p WHERE { ?source ?p <${entityUri}> . FILTER(isIRI(?source)) }`
1035
- } else {
1036
- query = `
1037
- SELECT ?direction ?p ?entity WHERE {
1038
- { <${entityUri}> ?p ?entity . BIND("out" AS ?direction) FILTER(isIRI(?entity)) }
1039
- UNION
1040
- { ?entity ?p <${entityUri}> . BIND("in" AS ?direction) FILTER(isIRI(?entity)) }
1041
- }
1042
- `
1043
- }
1044
- return this.querySource(query)
1045
- }
687
+ _classifyIntent(prompt) {
688
+ const lowerPrompt = prompt.toLowerCase()
1046
689
 
1047
- /**
1048
- * Get schema/ontology information
1049
- */
1050
- async getSchema(prefix = null) {
1051
- const query = `
1052
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
1053
- PREFIX owl: <http://www.w3.org/2002/07/owl#>
1054
-
1055
- SELECT ?class ?property ?domain ?range WHERE {
1056
- {
1057
- ?class a owl:Class .
1058
- } UNION {
1059
- ?property a owl:ObjectProperty .
1060
- OPTIONAL { ?property rdfs:domain ?domain }
1061
- OPTIONAL { ?property rdfs:range ?range }
1062
- } UNION {
1063
- ?property a owl:DatatypeProperty .
1064
- OPTIONAL { ?property rdfs:domain ?domain }
1065
- OPTIONAL { ?property rdfs:range ?range }
690
+ for (const pattern of this.intentPatterns) {
691
+ if (pattern.patterns.some(p => lowerPrompt.includes(p))) {
692
+ return {
693
+ type: pattern.intent,
694
+ tools: pattern.tools,
695
+ confidence: 0.95
1066
696
  }
1067
697
  }
1068
- `
1069
- return this.querySource(query)
1070
- }
1071
-
1072
- /**
1073
- * Log access for audit
1074
- */
1075
- _logAccess(operation, details) {
1076
- this.accessLog.push({
1077
- operation,
1078
- details: details.slice(0, 200),
1079
- timestamp: new Date().toISOString()
1080
- })
1081
-
1082
- // Keep only recent access log entries
1083
- if (this.accessLog.length > 1000) {
1084
- this.accessLog = this.accessLog.slice(-500)
1085
698
  }
1086
- }
1087
-
1088
- /**
1089
- * Get access log
1090
- */
1091
- getAccessLog(limit = 100) {
1092
- return this.accessLog.slice(-limit)
1093
- }
1094
699
 
1095
- /**
1096
- * Get graph URIs
1097
- */
1098
- getGraphUris() {
1099
700
  return {
1100
- source: this.sourceGraphUri,
1101
- agent: this.agentGraphUri,
1102
- separation: 'Source graph is read-only truth from ingestion. Agent graph is agent-writable memory.'
1103
- }
1104
- }
1105
- }
1106
-
1107
- /**
1108
- * MemoryManager - Unified memory retrieval with weighted scoring
1109
- *
1110
- * Orchestrates all memory layers with intelligent retrieval:
1111
- * - Score = α × Recency + β × Relevance + γ × Importance
1112
- * - α = 0.3 (time decay), β = 0.5 (semantic similarity), γ = 0.2 (access frequency)
1113
- */
1114
- class MemoryManager {
1115
- constructor(runtime, config = {}) {
1116
- this.runtime = runtime
1117
-
1118
- // Initialize memory layers
1119
- this.working = new WorkingMemory(config.workingCapacity || 100)
1120
- this.episodic = new EpisodicMemory(config.graphDb, config.episodicNamespace)
1121
- this.longTerm = new LongTermMemory(config.graphDb, {
1122
- sourceGraphUri: config.sourceGraphUri,
1123
- agentGraphUri: config.agentGraphUri
1124
- })
1125
-
1126
- // Retrieval weights
1127
- this.weights = config.weights || {
1128
- recency: 0.3,
1129
- relevance: 0.5,
1130
- importance: 0.2
701
+ type: 'general_query',
702
+ tools: ['kg.sparql.query'],
703
+ confidence: 0.85
1131
704
  }
1132
-
1133
- // Access tracking for importance scoring
1134
- this.accessCounts = new Map()
1135
- this.retrievalHistory = []
1136
705
  }
1137
706
 
1138
- /**
1139
- * Unified memory retrieval with weighted scoring
1140
- */
1141
- async retrieve(query, options = {}) {
1142
- const results = {
1143
- working: [],
1144
- episodic: [],
1145
- longTerm: [],
1146
- combined: []
1147
- }
1148
-
1149
- // 1. Working Memory (always fast, in-memory)
1150
- results.working = this._searchWorkingMemory(query)
1151
-
1152
- // 2. Episodic Memory (past executions)
1153
- if (options.includeEpisodic !== false) {
1154
- const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
1155
- results.episodic = this._scoreEpisodicResults(episodes, query)
1156
- }
707
+ _generatePlan(intent, prompt) {
708
+ const steps = intent.tools.map((tool, i) => ({
709
+ id: i + 1,
710
+ tool,
711
+ args: this._generateToolArgs(tool, intent, prompt)
712
+ }))
1157
713
 
1158
- // 3. Long-term Memory (source of truth)
1159
- if (options.includeLongTerm !== false && options.entityUri) {
1160
- const facts = await this.longTerm.getEntityFacts(options.entityUri)
1161
- results.longTerm = facts.map(f => ({
1162
- ...f,
1163
- score: 0.8, // Facts from source of truth have high base score
1164
- source: 'longTerm'
1165
- }))
714
+ return {
715
+ id: `plan_${Date.now()}`,
716
+ intent: intent.type,
717
+ steps,
718
+ type_chain: this._buildTypeChain(steps)
1166
719
  }
1167
-
1168
- // 4. Combine and rank
1169
- results.combined = this._combineAndRank([
1170
- ...results.working.map(r => ({ ...r, source: 'working' })),
1171
- ...results.episodic,
1172
- ...results.longTerm
1173
- ])
1174
-
1175
- // Track retrieval
1176
- this._trackRetrieval(query, results)
1177
- this.runtime.metrics.totalMemoryRetrievals++
1178
-
1179
- return results
1180
720
  }
1181
721
 
1182
- /**
1183
- * Search working memory
1184
- */
1185
- _searchWorkingMemory(query) {
1186
- const results = []
1187
- const queryLower = query.toLowerCase()
1188
-
1189
- // Search context
1190
- for (const ctx of this.working.context) {
1191
- const content = JSON.stringify(ctx).toLowerCase()
1192
- if (content.includes(queryLower)) {
1193
- results.push({
1194
- type: 'context',
1195
- data: ctx,
1196
- score: this._calculateScore(ctx.timestamp, 0.8, this._getAccessCount(ctx.id))
1197
- })
1198
- }
1199
- }
1200
-
1201
- // Search tool results
1202
- for (const tr of this.working.toolResults) {
1203
- if (tr.tool.toLowerCase().includes(queryLower) ||
1204
- JSON.stringify(tr.result).toLowerCase().includes(queryLower)) {
1205
- results.push({
1206
- type: 'toolResult',
1207
- data: tr,
1208
- score: this._calculateScore(tr.timestamp, 0.7, 1)
1209
- })
1210
- }
722
+ _generateToolArgs(tool, intent, prompt) {
723
+ switch (tool) {
724
+ case 'kg.sparql.query':
725
+ return { query: this._generateSparql(intent, prompt) }
726
+ case 'kg.datalog.infer':
727
+ return { rules: this._selectRules(intent) }
728
+ case 'kg.embeddings.search':
729
+ return { text: prompt, k: 10, threshold: 0.7 }
730
+ case 'kg.motif.find':
731
+ return { pattern: this._selectMotifPattern(intent) }
732
+ case 'kg.graphframe.triangles':
733
+ return {}
734
+ default:
735
+ return {}
1211
736
  }
1212
-
1213
- return results.sort((a, b) => b.score - a.score)
1214
- }
1215
-
1216
- /**
1217
- * Score episodic results
1218
- */
1219
- _scoreEpisodicResults(episodes, query) {
1220
- return episodes.map(ep => {
1221
- const recencyScore = this.episodic.calculateRecencyScore(ep.timestamp)
1222
- const relevanceScore = this._calculateRelevance(ep.prompt, query)
1223
- const importanceScore = this._getAccessCount(`ep_${ep.episode}`) / 10
1224
-
1225
- return {
1226
- ...ep,
1227
- source: 'episodic',
1228
- score: this._calculateScore(ep.timestamp, relevanceScore, importanceScore)
1229
- }
1230
- }).sort((a, b) => b.score - a.score)
1231
- }
1232
-
1233
- /**
1234
- * Calculate weighted score
1235
- */
1236
- _calculateScore(timestamp, relevance, accessCount) {
1237
- const recency = this.episodic.calculateRecencyScore(timestamp)
1238
- const importance = Math.min(accessCount / 10, 1)
1239
-
1240
- return (
1241
- this.weights.recency * recency +
1242
- this.weights.relevance * relevance +
1243
- this.weights.importance * importance
1244
- )
1245
- }
1246
-
1247
- /**
1248
- * Calculate text relevance (simple term overlap)
1249
- */
1250
- _calculateRelevance(text1, text2) {
1251
- if (!text1 || !text2) return 0
1252
- const words1 = new Set(text1.toLowerCase().split(/\s+/))
1253
- const words2 = new Set(text2.toLowerCase().split(/\s+/))
1254
- const intersection = [...words1].filter(w => words2.has(w))
1255
- return intersection.length / Math.max(words1.size, words2.size)
1256
- }
1257
-
1258
- /**
1259
- * Get access count for importance scoring
1260
- */
1261
- _getAccessCount(id) {
1262
- return this.accessCounts.get(id) || 0
1263
737
  }
1264
738
 
1265
- /**
1266
- * Increment access count
1267
- */
1268
- _incrementAccess(id) {
1269
- this.accessCounts.set(id, (this.accessCounts.get(id) || 0) + 1)
1270
- }
739
+ _generateSparql(intent, prompt) {
740
+ switch (intent.type) {
741
+ case 'detect_fraud':
742
+ return `PREFIX ins: <http://insurance.example.org/>
743
+ SELECT ?claim ?claimant ?amount ?riskScore
744
+ WHERE {
745
+ ?claim a ins:Claim ;
746
+ ins:claimant ?claimant ;
747
+ ins:amount ?amount .
748
+ OPTIONAL { ?claimant ins:riskScore ?riskScore }
749
+ FILTER(BOUND(?riskScore) && ?riskScore > 0.7)
750
+ }
751
+ ORDER BY DESC(?riskScore)
752
+ LIMIT 100`
1271
753
 
1272
- /**
1273
- * Combine and rank results from all memory layers
1274
- */
1275
- _combineAndRank(results) {
1276
- return results
1277
- .sort((a, b) => b.score - a.score)
1278
- .slice(0, 20) // Top 20 results
1279
- }
754
+ case 'academic_query':
755
+ if (prompt.toLowerCase().includes('professor')) {
756
+ return `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
757
+ SELECT ?professor ?name ?dept
758
+ WHERE {
759
+ ?professor a ub:Professor .
760
+ OPTIONAL { ?professor ub:name ?name }
761
+ OPTIONAL { ?professor ub:worksFor ?dept }
762
+ }
763
+ LIMIT 100`
764
+ }
765
+ return `SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 100`
1280
766
 
1281
- /**
1282
- * Track retrieval for analytics
1283
- */
1284
- _trackRetrieval(query, results) {
1285
- this.retrievalHistory.push({
1286
- query,
1287
- timestamp: Date.now(),
1288
- counts: {
1289
- working: results.working.length,
1290
- episodic: results.episodic.length,
1291
- longTerm: results.longTerm.length
1292
- }
1293
- })
767
+ case 'aggregate':
768
+ return `SELECT (COUNT(*) as ?count) WHERE { ?s ?p ?o }`
1294
769
 
1295
- // Increment access counts for returned results
1296
- for (const r of results.combined) {
1297
- if (r.data?.id) {
1298
- this._incrementAccess(r.data.id)
1299
- }
770
+ default:
771
+ return `SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 100`
1300
772
  }
1301
773
  }
1302
774
 
1303
- /**
1304
- * Store execution in episodic memory
1305
- */
1306
- async storeExecution(execution) {
1307
- return this.episodic.storeEpisode(this.runtime.id, execution)
1308
- }
1309
-
1310
- /**
1311
- * Add to working memory
1312
- */
1313
- addToWorking(item) {
1314
- this.working.addContext(item)
1315
- return this
1316
- }
1317
-
1318
- /**
1319
- * Get memory statistics
1320
- */
1321
- getStats() {
1322
- return {
1323
- working: {
1324
- contextSize: this.working.context.length,
1325
- bindingsCount: this.working.bindings.size,
1326
- toolResultsCount: this.working.toolResults.length
1327
- },
1328
- episodic: {
1329
- episodeCount: this.episodic.getEpisodeCount()
1330
- },
1331
- longTerm: {
1332
- accessLogSize: this.longTerm.accessLog.length
1333
- },
1334
- retrieval: {
1335
- totalRetrievals: this.retrievalHistory.length,
1336
- uniqueAccessedItems: this.accessCounts.size
1337
- },
1338
- weights: this.weights
775
+ _selectRules(intent) {
776
+ switch (intent.type) {
777
+ case 'detect_fraud':
778
+ return ['potential_fraud', 'collusion_pattern', 'circular_payment']
779
+ case 'find_patterns':
780
+ return ['circular_payment', 'collusion_pattern']
781
+ default:
782
+ return []
1339
783
  }
1340
784
  }
1341
785
 
1342
- /**
1343
- * Clear working memory (episodic and long-term persist)
1344
- */
1345
- clearWorking() {
1346
- this.working.clear()
1347
- return this
1348
- }
1349
-
1350
- // ==========================================================================
1351
- // SEMANTIC HASHING (LSH - Locality Sensitive Hashing)
1352
- // Research: SimHash (Charikar 2002), Semantic Hashing (Salakhutdinov & Hinton 2009)
1353
- // ==========================================================================
1354
-
1355
- /**
1356
- * Generate semantic hash using LSH with random hyperplane projections
1357
- * 384-dim embeddings → 64 hyperplanes → 64-bit semantic hash
1358
- *
1359
- * @param {string} text - Text to hash semantically
1360
- * @returns {string} Semantic hash in format "semhash:xxx-xxx-xxx"
1361
- */
1362
- generateSemanticHash(text) {
1363
- // Normalize and tokenize
1364
- const tokens = text.toLowerCase()
1365
- .replace(/[^\w\s]/g, '')
1366
- .split(/\s+/)
1367
- .filter(t => t.length > 2)
1368
-
1369
- // Generate hash components from key terms
1370
- const hashParts = []
1371
-
1372
- // Extract entity references (Provider, Claim, Policy patterns)
1373
- const entityPattern = /([A-Z][a-z]+)[:\s]?([A-Z0-9]+)/g
1374
- const entities = [...text.matchAll(entityPattern)]
1375
- for (const match of entities) {
1376
- hashParts.push(`${match[1].toLowerCase()}-${match[2].toLowerCase()}`)
786
+ _selectMotifPattern(intent) {
787
+ switch (intent.type) {
788
+ case 'find_patterns':
789
+ return '(a)-[paid]->(b); (b)-[paid]->(c); (c)-[paid]->(a)'
790
+ default:
791
+ return '(a)-[]->(b)'
1377
792
  }
1378
-
1379
- // Extract action/intent keywords
1380
- const actionWords = ['fraud', 'detect', 'analyze', 'find', 'claim', 'deny', 'approve', 'risk', 'pattern', 'investigation']
1381
- const foundActions = tokens.filter(t => actionWords.some(a => t.includes(a)))
1382
- hashParts.push(...foundActions.slice(0, 3))
1383
-
1384
- // Combine into semantic hash
1385
- const semanticParts = hashParts.slice(0, 5).join('-') || 'general-query'
1386
- return `semhash:${semanticParts}`
1387
793
  }
1388
794
 
1389
- /**
1390
- * Check if two semantic hashes represent the same intent
1391
- * Uses Jaccard similarity on hash components
1392
- */
1393
- semanticHashMatch(hash1, hash2, threshold = 0.6) {
1394
- const parts1 = new Set(hash1.replace('semhash:', '').split('-'))
1395
- const parts2 = new Set(hash2.replace('semhash:', '').split('-'))
1396
-
1397
- const intersection = [...parts1].filter(p => parts2.has(p)).length
1398
- const union = new Set([...parts1, ...parts2]).size
1399
-
1400
- return (intersection / union) >= threshold
795
+ _buildTypeChain(steps) {
796
+ return steps.map(s => s.tool).join(' -> ')
1401
797
  }
1402
798
 
1403
- // ==========================================================================
1404
- // RECALL WITH KG - Typed API for Memory + Knowledge Graph Joins
1405
- // ==========================================================================
1406
-
1407
- /**
1408
- * Recall memories enriched with Knowledge Graph context
1409
- * Typed API that generates optimized SPARQL internally
1410
- *
1411
- * @param {Object} options - Recall options
1412
- * @param {string} options.query - Natural language query
1413
- * @param {Object} options.kgFilter - Optional KG filter {predicate, operator, value}
1414
- * @param {number} options.limit - Max results (default 10)
1415
- * @returns {Promise<Array>} Enriched memory results with KG context
1416
- */
1417
- async recallWithKG(options = {}) {
1418
- const { query, kgFilter, limit = 10 } = options
1419
-
1420
- // Generate semantic hash for caching
1421
- const semanticHash = this.generateSemanticHash(query)
799
+ async _executePlan(plan, trace) {
800
+ const results = []
1422
801
 
1423
- // Check semantic cache first
1424
- const cached = this._checkSemanticCache(semanticHash)
1425
- if (cached) {
1426
- this.runtime.metrics.semanticCacheHits = (this.runtime.metrics.semanticCacheHits || 0) + 1
1427
- return { ...cached, fromCache: true, semanticHash }
1428
- }
802
+ for (const step of plan.steps) {
803
+ this.sandbox.consumeFuel(100)
1429
804
 
1430
- // Get episodic memories first
1431
- const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
1432
- const scoredEpisodes = this._scoreEpisodicResults(episodes, query)
805
+ try {
806
+ const result = await this._executeTool(step.tool, step.args)
807
+ results.push({ step: step.id, tool: step.tool, result, success: true })
1433
808
 
1434
- // Build SPARQL for memory + KG join
1435
- const sparql = this._buildMemoryKGQuery(scoredEpisodes, kgFilter)
809
+ if (step.tool === 'kg.sparql.query') {
810
+ trace.addSparql(step.args.query, result)
811
+ }
1436
812
 
1437
- // Execute if we have a graphDb
1438
- let kgContext = []
1439
- if (this.runtime.graphDb && sparql) {
1440
- try {
1441
- const results = this.runtime.graphDb.querySelect(sparql)
1442
- kgContext = results.map(r => ({
1443
- ...r.bindings,
1444
- source: 'knowledgeGraph'
1445
- }))
813
+ this.sandbox.log(step.tool, step.args, result, 'OK')
1446
814
  } catch (err) {
1447
- // KG query failed, continue with episodes only
1448
- console.warn('KG enrichment query failed:', err.message)
815
+ results.push({ step: step.id, tool: step.tool, error: err.message, success: false })
816
+ this.sandbox.log(step.tool, step.args, null, 'ERROR')
1449
817
  }
1450
818
  }
1451
819
 
1452
- // Combine episodes with KG context
1453
- const enrichedResults = scoredEpisodes.slice(0, limit).map(ep => {
1454
- const relatedKG = kgContext.filter(kg =>
1455
- JSON.stringify(kg).toLowerCase().includes(
1456
- ep.prompt?.toLowerCase().split(' ').slice(0, 3).join(' ') || ''
1457
- )
1458
- )
1459
-
1460
- return {
1461
- episode: ep.episode,
1462
- finding: ep.prompt,
1463
- timestamp: ep.timestamp,
1464
- score: ep.score,
1465
- kgContext: relatedKG.length > 0 ? relatedKG : null,
1466
- semanticHash
1467
- }
1468
- })
1469
-
1470
- // Cache result
1471
- this._storeSemanticCache(semanticHash, enrichedResults)
1472
-
1473
- return enrichedResults
820
+ return results
1474
821
  }
1475
822
 
1476
- /**
1477
- * Build SPARQL query for memory + KG join
1478
- * @private
1479
- */
1480
- _buildMemoryKGQuery(episodes, kgFilter) {
1481
- if (!episodes.length) return null
1482
-
1483
- // Extract entity URIs from episodes
1484
- const entityPattern = /([A-Z][a-z]+):?([A-Z0-9]+)/g
1485
- const entities = new Set()
1486
- for (const ep of episodes) {
1487
- const matches = ep.prompt?.matchAll(entityPattern) || []
1488
- for (const match of matches) {
1489
- entities.add(`<http://example.org/${match[1]}/${match[2]}>`)
1490
- }
823
+ async _executeTool(tool, args) {
824
+ if (!this.sandbox.hasCapability('ExecuteTool')) {
825
+ throw new Error('Missing ExecuteTool capability')
1491
826
  }
1492
827
 
1493
- if (entities.size === 0) return null
1494
-
1495
- const entityValues = [...entities].join(' ')
1496
- let filterClause = ''
1497
- if (kgFilter) {
1498
- filterClause = `FILTER(?value ${kgFilter.operator} ${kgFilter.value})`
828
+ switch (tool) {
829
+ case 'kg.sparql.query':
830
+ return this._executeSparql(args.query)
831
+ case 'kg.datalog.infer':
832
+ return this._executeDatalog(args.rules)
833
+ case 'kg.embeddings.search':
834
+ return this._executeEmbeddingSearch(args)
835
+ case 'kg.motif.find':
836
+ return this._executeMotifFind(args)
837
+ case 'kg.graphframe.triangles':
838
+ return this._executeTriangleCount()
839
+ default:
840
+ return { status: 'unknown_tool' }
1499
841
  }
1500
-
1501
- return `
1502
- PREFIX am: <https://gonnect.ai/ontology/agent-memory#>
1503
-
1504
- SELECT ?entity ?predicate ?value WHERE {
1505
- VALUES ?entity { ${entityValues} }
1506
- ?entity ?predicate ?value .
1507
- ${filterClause}
1508
- } LIMIT 100`
1509
842
  }
1510
843
 
1511
- /**
1512
- * Semantic cache storage
1513
- * @private
1514
- */
1515
- _semanticCache = new Map()
1516
-
1517
- _checkSemanticCache(hash) {
1518
- // Check for exact match
1519
- if (this._semanticCache.has(hash)) {
1520
- return this._semanticCache.get(hash)
844
+ _executeSparql(query) {
845
+ if (!this.kg || !this.kg.querySelect) {
846
+ return []
1521
847
  }
1522
848
 
1523
- // Check for semantic similarity match
1524
- for (const [cachedHash, value] of this._semanticCache) {
1525
- if (this.semanticHashMatch(hash, cachedHash)) {
1526
- return value
1527
- }
849
+ try {
850
+ const results = this.kg.querySelect(query)
851
+ return results.map(r => r.bindings || r)
852
+ } catch (err) {
853
+ return { error: err.message }
1528
854
  }
1529
-
1530
- return null
1531
855
  }
1532
856
 
1533
- _storeSemanticCache(hash, value) {
1534
- // Keep cache bounded
1535
- if (this._semanticCache.size > 1000) {
1536
- const firstKey = this._semanticCache.keys().next().value
1537
- this._semanticCache.delete(firstKey)
857
+ _executeDatalog(ruleNames) {
858
+ const results = []
859
+ for (const name of ruleNames) {
860
+ const rule = this.rules.getRule(name)
861
+ if (rule) {
862
+ results.push({
863
+ rule: name,
864
+ description: rule.description,
865
+ applied: true
866
+ })
867
+ }
1538
868
  }
1539
- this._semanticCache.set(hash, value)
869
+ return results
1540
870
  }
1541
- }
1542
871
 
1543
- // ============================================================================
1544
- // GOVERNANCE LAYER (Policy Engine & Capability Grants)
1545
- // ============================================================================
1546
-
1547
- /**
1548
- * GovernancePolicy - Defines access control and behavioral policies
1549
- */
1550
- class GovernancePolicy {
1551
- constructor(config = {}) {
1552
- this.name = config.name || 'default-policy'
1553
- this.version = config.version || '1.0.0'
1554
-
1555
- // Capability definitions
1556
- this.capabilities = new Set(config.capabilities || [
1557
- 'ReadKG',
1558
- 'WriteKG',
1559
- 'ExecuteTool',
1560
- 'AccessMemory',
1561
- 'ModifyMemory'
1562
- ])
1563
-
1564
- // Resource limits
1565
- this.limits = config.limits || {
1566
- maxExecutionTimeMs: 60000,
1567
- maxMemoryMB: 256,
1568
- maxToolCalls: 100,
1569
- maxGraphQueries: 1000
872
+ _executeEmbeddingSearch(args) {
873
+ if (!this.embeddings || !this.embeddings.search) {
874
+ return []
1570
875
  }
1571
876
 
1572
- // Audit requirements
1573
- this.audit = config.audit || {
1574
- logAllToolCalls: true,
1575
- logMemoryAccess: true,
1576
- logGraphQueries: true,
1577
- retentionDays: 90
1578
- }
1579
-
1580
- // Behavioral constraints
1581
- this.constraints = config.constraints || {
1582
- allowExternalApi: false,
1583
- allowFileAccess: false,
1584
- requireApprovalForWrites: false
877
+ try {
878
+ return this.embeddings.search(args.text, args.k, args.threshold)
879
+ } catch (err) {
880
+ return { error: err.message }
1585
881
  }
1586
882
  }
1587
883
 
1588
- /**
1589
- * Check if capability is granted
1590
- */
1591
- hasCapability(capability) {
1592
- return this.capabilities.has(capability)
884
+ _executeMotifFind(args) {
885
+ // Motif finding would use GraphFrame.find()
886
+ return { pattern: args.pattern, matches: [] }
1593
887
  }
1594
888
 
1595
- /**
1596
- * Grant a capability
1597
- */
1598
- grantCapability(capability) {
1599
- this.capabilities.add(capability)
1600
- return this
889
+ _executeTriangleCount() {
890
+ // Triangle counting would use GraphFrame.triangleCount()
891
+ return { count: 0 }
1601
892
  }
1602
893
 
1603
- /**
1604
- * Revoke a capability
1605
- */
1606
- revokeCapability(capability) {
1607
- this.capabilities.delete(capability)
1608
- return this
1609
- }
1610
-
1611
- /**
1612
- * Check resource limit
1613
- */
1614
- checkLimit(resource, current) {
1615
- const limit = this.limits[resource]
1616
- return limit === undefined || current <= limit
1617
- }
1618
-
1619
- /**
1620
- * Export policy as JSON
1621
- */
1622
- export() {
1623
- return {
1624
- name: this.name,
1625
- version: this.version,
1626
- capabilities: [...this.capabilities],
1627
- limits: this.limits,
1628
- audit: this.audit,
1629
- constraints: this.constraints
1630
- }
1631
- }
1632
- }
1633
-
1634
- /**
1635
- * GovernanceEngine - Enforces policies and manages capability grants
1636
- */
1637
- class GovernanceEngine {
1638
- constructor(policy = null) {
1639
- this.policy = policy || new GovernancePolicy()
1640
- this.auditLog = []
1641
- this.denials = []
1642
- }
1643
-
1644
- /**
1645
- * Authorize an action
1646
- */
1647
- authorize(action, context = {}) {
1648
- const result = {
1649
- allowed: false,
1650
- reason: '',
1651
- capability: action.capability,
1652
- timestamp: new Date().toISOString()
1653
- }
1654
-
1655
- // Check capability
1656
- if (action.capability && !this.policy.hasCapability(action.capability)) {
1657
- result.reason = `Missing capability: ${action.capability}`
1658
- this._logDenial(action, result.reason)
1659
- return result
1660
- }
894
+ async _applyRules(intent, results, trace) {
895
+ const inferences = []
1661
896
 
1662
- // Check resource limits
1663
- if (action.resource && action.current !== undefined) {
1664
- if (!this.policy.checkLimit(action.resource, action.current)) {
1665
- result.reason = `Resource limit exceeded: ${action.resource}`
1666
- this._logDenial(action, result.reason)
1667
- return result
897
+ if (intent.type === 'detect_fraud') {
898
+ const selectedRules = this._selectRules(intent)
899
+ for (const ruleName of selectedRules) {
900
+ const rule = this.rules.getRule(ruleName)
901
+ if (rule) {
902
+ inferences.push({
903
+ rule: ruleName,
904
+ description: rule.description,
905
+ head: rule.head,
906
+ body: rule.body
907
+ })
908
+ trace.addRule(ruleName, rule.body, rule.head)
909
+ }
1668
910
  }
1669
911
  }
1670
912
 
1671
- // Check constraints
1672
- if (action.type === 'externalApi' && !this.policy.constraints.allowExternalApi) {
1673
- result.reason = 'External API access not allowed'
1674
- this._logDenial(action, result.reason)
1675
- return result
1676
- }
1677
-
1678
- result.allowed = true
1679
- result.reason = 'Authorized'
1680
- this._logAudit(action, result)
1681
-
1682
- return result
1683
- }
1684
-
1685
- /**
1686
- * Log audit entry
1687
- */
1688
- _logAudit(action, result) {
1689
- if (this.policy.audit.logAllToolCalls ||
1690
- (this.policy.audit.logMemoryAccess && action.type === 'memory') ||
1691
- (this.policy.audit.logGraphQueries && action.type === 'graphQuery')) {
1692
- this.auditLog.push({
1693
- action,
1694
- result,
1695
- timestamp: new Date().toISOString()
1696
- })
1697
- }
1698
- }
1699
-
1700
- /**
1701
- * Log denial
1702
- */
1703
- _logDenial(action, reason) {
1704
- this.denials.push({
1705
- action,
1706
- reason,
1707
- timestamp: new Date().toISOString()
1708
- })
1709
- }
1710
-
1711
- /**
1712
- * Get audit log
1713
- */
1714
- getAuditLog(limit = 100) {
1715
- return this.auditLog.slice(-limit)
1716
- }
1717
-
1718
- /**
1719
- * Get denials
1720
- */
1721
- getDenials(limit = 50) {
1722
- return this.denials.slice(-limit)
1723
- }
1724
-
1725
- /**
1726
- * Update policy
1727
- */
1728
- setPolicy(policy) {
1729
- this.policy = policy
1730
- return this
1731
- }
1732
- }
1733
-
1734
- // ============================================================================
1735
- // SCOPE LAYER (Namespace Isolation & Resource Limits)
1736
- // ============================================================================
1737
-
1738
- /**
1739
- * AgentScope - Defines namespace and resource boundaries for agents
1740
- */
1741
- class AgentScope {
1742
- constructor(config = {}) {
1743
- this.id = config.id || `scope_${crypto.randomUUID()}`
1744
- this.name = config.name || 'default-scope'
1745
-
1746
- // Namespace configuration
1747
- this.namespace = config.namespace || {
1748
- prefix: 'http://hypermind.ai/agent/',
1749
- graphUri: `http://hypermind.ai/agent/${this.id}/`,
1750
- allowedGraphs: ['*'], // '*' means all, or list specific graph URIs
1751
- deniedGraphs: []
1752
- }
1753
-
1754
- // Resource limits
1755
- this.resources = {
1756
- maxMemoryMB: config.maxMemoryMB || 256,
1757
- maxExecutionTimeMs: config.maxExecutionTimeMs || 60000,
1758
- maxToolCalls: config.maxToolCalls || 100,
1759
- maxGraphQueries: config.maxGraphQueries || 1000,
1760
- maxEpisodes: config.maxEpisodes || 10000
1761
- }
1762
-
1763
- // Current usage tracking
1764
- this.usage = {
1765
- memoryMB: 0,
1766
- executionTimeMs: 0,
1767
- toolCalls: 0,
1768
- graphQueries: 0,
1769
- episodes: 0
1770
- }
1771
-
1772
- // Isolation settings
1773
- this.isolation = {
1774
- shareMemory: config.shareMemory || false,
1775
- shareEpisodes: config.shareEpisodes || false,
1776
- parentScope: config.parentScope || null
1777
- }
913
+ return inferences
1778
914
  }
1779
915
 
1780
- /**
1781
- * Check if graph access is allowed
1782
- */
1783
- isGraphAllowed(graphUri) {
1784
- // Check denied first
1785
- if (this.namespace.deniedGraphs.includes(graphUri)) {
1786
- return false
1787
- }
1788
-
1789
- // Check allowed
1790
- if (this.namespace.allowedGraphs.includes('*')) {
1791
- return true
1792
- }
916
+ _buildProofDAG(plan, results, inferences, trace) {
917
+ // Build proof tree showing how answer was derived
918
+ const children = []
1793
919
 
1794
- return this.namespace.allowedGraphs.some(pattern => {
1795
- if (pattern.endsWith('*')) {
1796
- return graphUri.startsWith(pattern.slice(0, -1))
920
+ // Add SPARQL results as axioms
921
+ for (const r of results) {
922
+ if (r.success && r.tool === 'kg.sparql.query') {
923
+ children.push(new ProofNode(
924
+ 'sparql',
925
+ r.result,
926
+ `Executed SPARQL query`,
927
+ []
928
+ ))
1797
929
  }
1798
- return graphUri === pattern
1799
- })
1800
- }
1801
-
1802
- /**
1803
- * Track resource usage
1804
- */
1805
- trackUsage(resource, amount) {
1806
- if (this.usage[resource] !== undefined) {
1807
- this.usage[resource] += amount
1808
930
  }
1809
- return this
1810
- }
1811
931
 
1812
- /**
1813
- * Check if resource limit exceeded
1814
- */
1815
- isWithinLimits(resource) {
1816
- const limit = this.resources[`max${resource.charAt(0).toUpperCase()}${resource.slice(1)}`]
1817
- return limit === undefined || this.usage[resource] <= limit
1818
- }
1819
-
1820
- /**
1821
- * Get remaining resources
1822
- */
1823
- getRemainingResources() {
1824
- const remaining = {}
1825
- for (const [key, limit] of Object.entries(this.resources)) {
1826
- // Convert maxToolCalls -> toolCalls (preserve camelCase)
1827
- const withoutMax = key.replace(/^max/, '')
1828
- const usageKey = withoutMax.charAt(0).toLowerCase() + withoutMax.slice(1)
1829
- remaining[usageKey] = limit - (this.usage[usageKey] || 0)
932
+ // Add rule applications as inferences
933
+ for (const inf of inferences) {
934
+ children.push(new ProofNode(
935
+ 'rule',
936
+ inf,
937
+ `Applied rule: ${inf.description}`,
938
+ inf.body.map(b => new ProofNode('premise', b, `Premise: ${JSON.stringify(b)}`))
939
+ ))
1830
940
  }
1831
- return remaining
1832
- }
1833
941
 
1834
- /**
1835
- * Reset usage counters
1836
- */
1837
- resetUsage() {
1838
- for (const key of Object.keys(this.usage)) {
1839
- this.usage[key] = 0
1840
- }
1841
- return this
1842
- }
1843
-
1844
- /**
1845
- * Get scoped graph URI for agent
1846
- */
1847
- getScopedGraphUri(suffix = '') {
1848
- return `${this.namespace.graphUri}${suffix}`
1849
- }
1850
-
1851
- /**
1852
- * Export scope configuration
1853
- */
1854
- export() {
1855
- return {
1856
- id: this.id,
1857
- name: this.name,
1858
- namespace: this.namespace,
1859
- resources: this.resources,
1860
- usage: this.usage,
1861
- isolation: this.isolation
1862
- }
1863
- }
1864
- }
1865
-
1866
- // ============================================================================
1867
- // AGENT BUILDER (Fluent Composition Pattern)
1868
- // ============================================================================
1869
-
1870
- /**
1871
- * ComposedAgent - Agent built from composed tools, planner, and sandbox
1872
- */
1873
- class ComposedAgent {
1874
- constructor(spec) {
1875
- this.name = spec.name
1876
- this.tools = spec.tools
1877
- this.planner = spec.planner
1878
- this.sandbox = spec.sandbox
1879
- this.hooks = spec.hooks || []
1880
- this.conversationHistory = []
942
+ return new ProofNode(
943
+ 'inference',
944
+ { plan: plan.id, intent: plan.intent },
945
+ `Derived answer via ${plan.steps.length} steps`,
946
+ children
947
+ )
1881
948
  }
1882
949
 
1883
- async call(prompt) {
1884
- this._fireHooks('beforePlan', { prompt })
1885
-
1886
- const plan = await this.planner.plan(prompt, {
1887
- history: this.conversationHistory
1888
- })
1889
-
1890
- this._fireHooks('afterPlan', { plan })
950
+ _formatAnswer(results, inferences, intent) {
951
+ const sparqlResults = results.filter(r => r.tool === 'kg.sparql.query' && r.success)
952
+ const totalResults = sparqlResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0)
1891
953
 
1892
- const proxy = this.sandbox.createObjectProxy(this.tools)
1893
- const results = []
1894
-
1895
- for (const step of plan.steps) {
1896
- this._fireHooks('beforeExecute', { step })
954
+ let answer = `Found ${totalResults} results`
1897
955
 
1898
- try {
1899
- const result = await proxy[step.tool](step.args)
1900
- results.push({ step, result, status: 'success' })
1901
- this._fireHooks('afterExecute', { step, result })
1902
- } catch (error) {
1903
- results.push({ step, error: error.message, status: 'failed' })
1904
- this._fireHooks('onError', { step, error })
1905
- }
956
+ if (inferences.length > 0) {
957
+ answer += ` using ${inferences.length} reasoning rules`
1906
958
  }
1907
959
 
1908
- const witness = this._generateWitness(plan, results)
1909
-
1910
- this.conversationHistory.push({ prompt, plan, results, witness })
1911
-
1912
- return {
1913
- response: `Executed ${results.length} tools`,
1914
- plan,
1915
- results,
1916
- witness,
1917
- metrics: this.sandbox.getMetrics()
960
+ if (intent.type === 'detect_fraud' && totalResults > 0) {
961
+ answer = `Detected ${totalResults} potential fraud cases using ${inferences.length} detection rules`
1918
962
  }
1919
- }
1920
963
 
1921
- _fireHooks(event, data) {
1922
- for (const hook of this.hooks) {
1923
- if (hook.event === event) hook.handler(data)
1924
- }
964
+ return answer
1925
965
  }
966
+ }
1926
967
 
1927
- _generateWitness(plan, results) {
1928
- const witnessData = {
1929
- witness_version: '1.0',
1930
- timestamp: new Date().toISOString(),
1931
- agent: this.name,
1932
- model: this.planner.model,
1933
- plan: { id: plan.id, steps: plan.steps.length, confidence: plan.confidence },
1934
- execution: {
1935
- tool_calls: results.map(r => ({
1936
- tool: r.step.tool,
1937
- status: r.status
1938
- }))
1939
- },
1940
- sandbox_metrics: this.sandbox.getMetrics(),
1941
- audit_log: this.sandbox.getAuditLog()
1942
- }
1943
-
1944
- const proofHash = crypto
1945
- .createHash('sha256')
1946
- .update(JSON.stringify(witnessData))
1947
- .digest('hex')
968
+ // ============================================================================
969
+ // AGENT STATE MACHINE
970
+ // ============================================================================
1948
971
 
1949
- return { ...witnessData, proof_hash: `sha256:${proofHash}` }
1950
- }
972
+ /**
973
+ * AgentState - State machine for agent lifecycle
974
+ */
975
+ const AgentState = {
976
+ IDLE: 'IDLE',
977
+ READY: 'READY',
978
+ PLANNING: 'PLANNING',
979
+ EXECUTING: 'EXECUTING',
980
+ WAITING: 'WAITING',
981
+ ERROR: 'ERROR',
982
+ TERMINATED: 'TERMINATED'
1951
983
  }
1952
984
 
985
+ // ============================================================================
986
+ // AGENT RUNTIME
987
+ // ============================================================================
988
+
1953
989
  /**
1954
- * AgentBuilder - Fluent builder for composing agents
990
+ * AgentRuntime - Runtime context for agent execution
1955
991
  */
1956
- class AgentBuilder {
1957
- constructor(name) {
1958
- this.spec = {
1959
- name,
1960
- tools: {},
1961
- planner: null,
1962
- sandbox: null,
1963
- hooks: []
1964
- }
992
+ class AgentRuntime {
993
+ constructor(config = {}) {
994
+ this.id = crypto.randomUUID()
995
+ this.name = config.name || 'agent'
996
+ this.model = config.model || 'mock'
997
+ this.tools = config.tools || []
998
+ this.state = AgentState.IDLE
999
+ this.memoryCapacity = config.memoryCapacity || 100
1000
+ this.episodeLimit = config.episodeLimit || 1000
1001
+ this.createdAt = new Date().toISOString()
1002
+ this.stateHistory = [{ state: this.state, timestamp: Date.now() }]
1003
+ this.executions = []
1004
+ this.currentExecution = null
1965
1005
  }
1966
1006
 
1967
- withTool(toolName, toolImpl = null) {
1968
- if (TOOL_REGISTRY[toolName]) {
1969
- this.spec.tools[toolName] = {
1970
- ...TOOL_REGISTRY[toolName],
1971
- execute: toolImpl || this._createMockExecutor(toolName)
1972
- }
1007
+ transitionTo(newState) {
1008
+ const validTransitions = {
1009
+ [AgentState.IDLE]: [AgentState.READY, AgentState.TERMINATED],
1010
+ [AgentState.READY]: [AgentState.PLANNING, AgentState.EXECUTING, AgentState.TERMINATED],
1011
+ [AgentState.PLANNING]: [AgentState.EXECUTING, AgentState.WAITING, AgentState.ERROR],
1012
+ [AgentState.EXECUTING]: [AgentState.READY, AgentState.WAITING, AgentState.ERROR],
1013
+ [AgentState.WAITING]: [AgentState.READY, AgentState.PLANNING, AgentState.ERROR],
1014
+ [AgentState.ERROR]: [AgentState.READY, AgentState.TERMINATED],
1015
+ [AgentState.TERMINATED]: []
1973
1016
  }
1974
- return this
1975
- }
1976
1017
 
1977
- withPlanner(model) {
1978
- this.spec.planner = new LLMPlanner(model, this.spec.tools)
1979
- return this
1980
- }
1018
+ if (!validTransitions[this.state]?.includes(newState)) {
1019
+ throw new Error(`Invalid state transition: ${this.state} -> ${newState}`)
1020
+ }
1981
1021
 
1982
- withSandbox(config) {
1983
- this.spec.sandbox = new WasmSandbox(config)
1022
+ this.state = newState
1023
+ this.stateHistory.push({ state: newState, timestamp: Date.now() })
1984
1024
  return this
1985
1025
  }
1986
1026
 
1987
- withHook(event, handler) {
1988
- this.spec.hooks.push({ event, handler })
1989
- return this
1027
+ getStateHistory() {
1028
+ return this.stateHistory
1990
1029
  }
1991
1030
 
1992
- _createMockExecutor(toolName) {
1993
- return async (args) => {
1994
- const mockResults = {
1995
- 'kg.sparql.query': { status: 'success', bindings: [], count: 3 },
1996
- 'kg.graphframe.triangles': { status: 'success', count: 2 },
1997
- 'kg.datalog.infer': { status: 'success', facts: [], count: 5 },
1998
- 'kg.embeddings.search': { status: 'success', similar: [], count: 8 }
1999
- }
2000
- return mockResults[toolName] || { status: 'success', resultCount: 1 }
1031
+ startExecution(description) {
1032
+ const execId = crypto.randomUUID()
1033
+ this.currentExecution = {
1034
+ id: execId,
1035
+ description,
1036
+ startTime: Date.now(),
1037
+ steps: []
2001
1038
  }
1039
+ this.state = AgentState.EXECUTING
1040
+ return execId
2002
1041
  }
2003
1042
 
2004
- build() {
2005
- if (!this.spec.planner) this.withPlanner('mock')
2006
- if (!this.spec.sandbox) this.withSandbox({})
2007
- return new ComposedAgent(this.spec)
1043
+ completeExecution(result, success) {
1044
+ if (this.currentExecution) {
1045
+ this.currentExecution.endTime = Date.now()
1046
+ this.currentExecution.result = result
1047
+ this.currentExecution.success = success
1048
+ this.executions.push(this.currentExecution)
1049
+ this.currentExecution = null
1050
+ }
1051
+ this.state = AgentState.READY
2008
1052
  }
2009
1053
  }
2010
1054
 
2011
1055
  // ============================================================================
2012
- // ORIGINAL HYPERMIND AGENT CODE (Preserved Below)
1056
+ // MEMORY TIERS (Working, Episodic, Long-Term)
2013
1057
  // ============================================================================
2014
1058
 
2015
- // LUBM Benchmark Test Suite (12 questions)
2016
- const LUBM_TEST_SUITE = [
2017
- // Easy (3 tests)
2018
- {
2019
- index: 1,
2020
- question: 'Find all professors in the university database',
2021
- difficulty: 'easy',
2022
- expectedPattern: 'Professor',
2023
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2024
- SELECT ?x WHERE { ?x a ub:Professor }`
2025
- },
2026
- {
2027
- index: 2,
2028
- question: 'List all graduate students',
2029
- difficulty: 'easy',
2030
- expectedPattern: 'GraduateStudent',
2031
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2032
- SELECT ?x WHERE { ?x a ub:GraduateStudent }`
2033
- },
2034
- {
2035
- index: 3,
2036
- question: 'How many courses are offered?',
2037
- difficulty: 'easy',
2038
- expectedPattern: 'COUNT',
2039
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2040
- SELECT (COUNT(?x) AS ?count) WHERE { ?x a ub:Course }`
2041
- },
2042
- // Medium (5 tests)
2043
- {
2044
- index: 4,
2045
- question: 'Find all students and their advisors',
2046
- difficulty: 'medium',
2047
- expectedPattern: 'advisor',
2048
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2049
- SELECT ?student ?advisor WHERE { ?student ub:advisor ?advisor }`
2050
- },
2051
- {
2052
- index: 5,
2053
- question: 'List professors and the courses they teach',
2054
- difficulty: 'medium',
2055
- expectedPattern: 'teach',
2056
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2057
- SELECT ?prof ?course WHERE { ?prof ub:teacherOf ?course }`
2058
- },
2059
- {
2060
- index: 6,
2061
- question: 'Find all departments and their parent universities',
2062
- difficulty: 'medium',
2063
- expectedPattern: 'subOrganization',
2064
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2065
- SELECT ?dept ?univ WHERE { ?dept ub:subOrganizationOf ?univ }`
2066
- },
2067
- {
2068
- index: 7,
2069
- question: 'Count the number of students per department',
2070
- difficulty: 'medium',
2071
- expectedPattern: 'GROUP BY',
2072
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2073
- SELECT ?dept (COUNT(?student) AS ?count) WHERE {
2074
- ?student ub:memberOf ?dept
2075
- } GROUP BY ?dept`
2076
- },
2077
- {
2078
- index: 8,
2079
- question: 'Find the average credit hours for graduate courses',
2080
- difficulty: 'medium',
2081
- expectedPattern: 'AVG',
2082
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2083
- SELECT (AVG(?credits) AS ?avg) WHERE {
2084
- ?course a ub:GraduateCourse .
2085
- ?course ub:creditHours ?credits
2086
- }`
2087
- },
2088
- // Hard (4 tests)
2089
- {
2090
- index: 9,
2091
- question: 'Find graduate students whose advisors have research interests in ML',
2092
- difficulty: 'hard',
2093
- expectedPattern: 'advisor',
2094
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2095
- SELECT ?student WHERE {
2096
- ?student a ub:GraduateStudent .
2097
- ?student ub:advisor ?advisor .
2098
- ?advisor ub:researchInterest ?interest .
2099
- FILTER(CONTAINS(STR(?interest), "ML"))
2100
- }`
2101
- },
2102
- {
2103
- index: 10,
2104
- question: 'List publications with authors who are professors at California universities',
2105
- difficulty: 'hard',
2106
- expectedPattern: 'publicationAuthor',
2107
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2108
- SELECT ?pub WHERE {
2109
- ?pub ub:publicationAuthor ?author .
2110
- ?author a ub:Professor .
2111
- ?author ub:worksFor ?dept .
2112
- ?dept ub:subOrganizationOf ?univ .
2113
- FILTER(CONTAINS(STR(?univ), "California"))
2114
- }`
2115
- },
2116
- {
2117
- index: 11,
2118
- question: 'Find students who take courses taught by professors in the same department',
2119
- difficulty: 'hard',
2120
- expectedPattern: 'memberOf',
2121
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2122
- SELECT ?student ?course WHERE {
2123
- ?student ub:takesCourse ?course .
2124
- ?prof ub:teacherOf ?course .
2125
- ?student ub:memberOf ?dept .
2126
- ?prof ub:worksFor ?dept
2127
- }`
2128
- },
2129
- {
2130
- index: 12,
2131
- question: 'Find pairs of students who share the same advisor and take common courses',
2132
- difficulty: 'hard',
2133
- expectedPattern: 'advisor',
2134
- expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2135
- SELECT ?s1 ?s2 ?course WHERE {
2136
- ?s1 ub:advisor ?advisor .
2137
- ?s2 ub:advisor ?advisor .
2138
- ?s1 ub:takesCourse ?course .
2139
- ?s2 ub:takesCourse ?course .
2140
- FILTER(?s1 != ?s2)
2141
- }`
2142
- }
2143
- ]
2144
-
2145
- // Typed tool definitions for the planner
2146
- const HYPERMIND_TOOLS = [
2147
- {
2148
- id: 'kg.sparql.query',
2149
- description: 'Execute SPARQL SELECT/CONSTRUCT/ASK queries against the knowledge graph',
2150
- inputType: 'String',
2151
- outputType: 'BindingSet',
2152
- preconditions: ['Valid SPARQL syntax'],
2153
- postconditions: ['Returns solution mappings']
2154
- },
2155
- {
2156
- id: 'kg.sparql.update',
2157
- description: 'Execute SPARQL UPDATE operations (INSERT/DELETE/LOAD/CLEAR)',
2158
- inputType: 'String',
2159
- outputType: 'Unit',
2160
- preconditions: ['Valid SPARQL Update syntax'],
2161
- postconditions: ['Graph modified']
2162
- },
2163
- {
2164
- id: 'kg.motif.find',
2165
- description: 'Find graph patterns using motif DSL: (a)-[e]->(b); (b)-[]->(c)',
2166
- inputType: 'String',
2167
- outputType: 'MatchSet',
2168
- preconditions: ['Valid motif pattern'],
2169
- postconditions: ['Returns matched subgraphs']
2170
- },
2171
- {
2172
- id: 'kg.datalog.apply',
2173
- description: 'Apply Datalog rules for reasoning and inference',
2174
- inputType: 'Program',
2175
- outputType: 'FactSet',
2176
- preconditions: ['Valid Datalog program'],
2177
- postconditions: ['Returns derived facts']
2178
- },
2179
- {
2180
- id: 'kg.semantic.search',
2181
- description: 'Find semantically similar entities using vector embeddings',
2182
- inputType: 'Record(entity: Node, k: Int, threshold: Float)',
2183
- outputType: 'List(Record(entity: Node, similarity: Float))',
2184
- preconditions: ['Entity has embedding', 'k > 0', 'threshold in [0, 1]'],
2185
- postconditions: ['Returns top-k similar entities']
2186
- },
2187
- {
2188
- id: 'kg.traversal.oneHop',
2189
- description: 'Get immediate neighbors of an entity',
2190
- inputType: 'Node',
2191
- outputType: 'List(Node)',
2192
- preconditions: ['Entity exists in graph'],
2193
- postconditions: ['Returns adjacent nodes']
2194
- },
2195
- {
2196
- id: 'kg.traversal.paths',
2197
- description: 'Find all paths between two entities up to max length',
2198
- inputType: 'Record(source: Node, target: Node, maxLength: Int)',
2199
- outputType: 'List(Path)',
2200
- preconditions: ['Entities exist', 'maxLength > 0'],
2201
- postconditions: ['Returns discovered paths']
2202
- }
2203
- ]
2204
-
2205
- // Default LUBM ontology hints
2206
- const LUBM_HINTS = [
2207
- 'Database uses LUBM (Lehigh University Benchmark) ontology',
2208
- 'Namespace: http://swat.cse.lehigh.edu/onto/univ-bench.owl#',
2209
- 'Key classes: University, Department, Professor, AssociateProfessor, AssistantProfessor, Lecturer, GraduateStudent, UndergraduateStudent, Course, GraduateCourse, Publication, Research',
2210
- 'Key properties: worksFor, memberOf, advisor, takesCourse, teacherOf, publicationAuthor, subOrganizationOf, researchInterest, name, emailAddress, telephone',
2211
- 'Professor subtypes: AssociateProfessor, AssistantProfessor, FullProfessor'
2212
- ]
2213
-
2214
1059
  /**
2215
- * HTTP request helper
1060
+ * WorkingMemory - Fast, ephemeral context (like CPU registers)
2216
1061
  */
2217
- function httpRequest(url, options = {}) {
2218
- return new Promise((resolve, reject) => {
2219
- const urlObj = new URL(url)
2220
- const isHttps = urlObj.protocol === 'https:'
2221
- const client = isHttps ? https : http
2222
-
2223
- const reqOptions = {
2224
- hostname: urlObj.hostname,
2225
- port: urlObj.port || (isHttps ? 443 : 80),
2226
- path: urlObj.pathname + urlObj.search,
2227
- method: options.method || 'GET',
2228
- headers: options.headers || {},
2229
- timeout: options.timeout || 30000
2230
- }
2231
-
2232
- const req = client.request(reqOptions, res => {
2233
- let data = ''
2234
- res.on('data', chunk => (data += chunk))
2235
- res.on('end', () => {
2236
- resolve({
2237
- status: res.statusCode,
2238
- headers: res.headers,
2239
- data: data
2240
- })
2241
- })
2242
- })
2243
-
2244
- req.on('error', reject)
2245
- req.on('timeout', () => {
2246
- req.destroy()
2247
- reject(new Error('Request timeout'))
2248
- })
1062
+ class WorkingMemory {
1063
+ constructor(capacity = 100) {
1064
+ this.capacity = capacity
1065
+ this.items = new Map()
1066
+ this.accessOrder = []
1067
+ }
2249
1068
 
2250
- if (options.body) {
2251
- req.write(options.body)
1069
+ set(key, value) {
1070
+ if (this.items.size >= this.capacity && !this.items.has(key)) {
1071
+ // LRU eviction
1072
+ const oldest = this.accessOrder.shift()
1073
+ this.items.delete(oldest)
2252
1074
  }
2253
- req.end()
2254
- })
2255
- }
2256
-
2257
- /**
2258
- * Validate SPARQL syntax (basic validation)
2259
- */
2260
- function validateSparqlSyntax(sparql) {
2261
- if (!sparql || typeof sparql !== 'string') return false
2262
-
2263
- // Remove markdown code blocks if present
2264
- let cleaned = sparql.trim()
2265
- if (cleaned.startsWith('```')) {
2266
- cleaned = cleaned.replace(/^```\w*\n?/, '').replace(/\n?```$/, '')
1075
+ this.items.set(key, { value, timestamp: Date.now() })
1076
+ this._updateAccess(key)
2267
1077
  }
2268
1078
 
2269
- // Basic syntax checks
2270
- const hasSelect = /\bSELECT\b/i.test(cleaned)
2271
- const hasConstruct = /\bCONSTRUCT\b/i.test(cleaned)
2272
- const hasAsk = /\bASK\b/i.test(cleaned)
2273
- const hasDescribe = /\bDESCRIBE\b/i.test(cleaned)
2274
- const hasWhere = /\bWHERE\b/i.test(cleaned)
2275
-
2276
- // Must have at least one query form
2277
- const hasQueryForm = hasSelect || hasConstruct || hasAsk || hasDescribe
1079
+ get(key) {
1080
+ const item = this.items.get(key)
1081
+ if (item) {
1082
+ this._updateAccess(key)
1083
+ return item.value
1084
+ }
1085
+ return null
1086
+ }
2278
1087
 
2279
- // Check for balanced braces
2280
- const openBraces = (cleaned.match(/{/g) || []).length
2281
- const closeBraces = (cleaned.match(/}/g) || []).length
2282
- const balancedBraces = openBraces === closeBraces && openBraces > 0
1088
+ _updateAccess(key) {
1089
+ const idx = this.accessOrder.indexOf(key)
1090
+ if (idx > -1) this.accessOrder.splice(idx, 1)
1091
+ this.accessOrder.push(key)
1092
+ }
2283
1093
 
2284
- return hasQueryForm && balancedBraces
2285
- }
1094
+ clear() {
1095
+ this.items.clear()
1096
+ this.accessOrder = []
1097
+ }
2286
1098
 
2287
- /**
2288
- * Create a planning context with typed tool definitions
2289
- */
2290
- function createPlanningContext(endpoint, hints = []) {
2291
- return {
2292
- tools: HYPERMIND_TOOLS,
2293
- hints: [...LUBM_HINTS, ...hints],
2294
- endpoint: endpoint
1099
+ size() {
1100
+ return this.items.size
2295
1101
  }
2296
- }
2297
1102
 
2298
- /**
2299
- * Get the LUBM benchmark test suite
2300
- */
2301
- function getHyperMindBenchmarkSuite() {
2302
- return LUBM_TEST_SUITE.map(t => ({
2303
- index: t.index,
2304
- question: t.question,
2305
- difficulty: t.difficulty,
2306
- expectedPattern: t.expectedPattern
2307
- }))
1103
+ toJSON() {
1104
+ return Object.fromEntries(this.items)
1105
+ }
2308
1106
  }
2309
1107
 
2310
1108
  /**
2311
- * HyperMind Agent Class
1109
+ * EpisodicMemory - Execution history with temporal ordering
2312
1110
  */
2313
- class HyperMindAgent {
2314
- constructor(spec) {
2315
- this.name = spec.name
2316
- this.model = spec.model
2317
- this.tools = spec.tools || ['kg.sparql.query']
2318
- this.endpoint = spec.endpoint || 'http://rust-kgdb-coordinator:30080'
2319
- this.timeout = spec.timeout || 30000
2320
- this.tracing = spec.tracing || false
2321
- this.trace = []
2322
- this.context = createPlanningContext(this.endpoint)
1111
+ class EpisodicMemory {
1112
+ constructor(limit = 1000) {
1113
+ this.limit = limit
1114
+ this.episodes = []
2323
1115
  }
2324
1116
 
2325
- /**
2326
- * Spawn a new HyperMind agent
2327
- */
2328
- static async spawn(spec) {
2329
- const agent = new HyperMindAgent(spec)
2330
-
2331
- // Verify connection
2332
- try {
2333
- const connected = await agent.isConnected()
2334
- if (!connected) {
2335
- console.warn(`Warning: Could not connect to ${spec.endpoint}`)
2336
- }
2337
- } catch (err) {
2338
- console.warn(`Warning: Connection check failed: ${err.message}`)
1117
+ store(episode) {
1118
+ this.episodes.push({
1119
+ ...episode,
1120
+ id: crypto.randomUUID(),
1121
+ timestamp: new Date().toISOString(),
1122
+ accessCount: 0
1123
+ })
1124
+ if (this.episodes.length > this.limit) {
1125
+ this.episodes.shift()
2339
1126
  }
2340
-
2341
- return agent
2342
1127
  }
2343
1128
 
2344
- /**
2345
- * Execute a natural language request
2346
- * For LLM models, tracks both raw and cleaned SPARQL for benchmark comparison
2347
- */
2348
- async call(prompt) {
2349
- const startTime = Date.now()
2350
-
2351
- try {
2352
- // For mock model, generate deterministic SPARQL
2353
- let sparql
2354
- let rawSparql = null
2355
- let rawIsValid = null
2356
-
2357
- if (this.model === 'mock') {
2358
- sparql = this._generateMockSparql(prompt)
2359
- rawSparql = sparql // Mock always produces clean output
2360
- rawIsValid = true
2361
- } else {
2362
- // Call LLM API - returns { raw, cleaned, rawIsValid }
2363
- const llmResponse = await this._callLlmForSparql(prompt)
2364
- this._lastLlmResponse = llmResponse
2365
- rawSparql = llmResponse.raw
2366
- rawIsValid = llmResponse.rawIsValid
2367
- sparql = llmResponse.cleaned // HyperMind uses cleaned version
2368
- }
1129
+ retrieve(query, limit = 10) {
1130
+ // Simple relevance: count matching words
1131
+ const queryWords = new Set(query.toLowerCase().split(/\s+/))
1132
+ return this.episodes
1133
+ .map(ep => {
1134
+ const promptWords = new Set((ep.prompt || '').toLowerCase().split(/\s+/))
1135
+ const overlap = [...queryWords].filter(w => promptWords.has(w)).length
1136
+ return { episode: ep, relevance: overlap / Math.max(queryWords.size, 1) }
1137
+ })
1138
+ .sort((a, b) => b.relevance - a.relevance)
1139
+ .slice(0, limit)
1140
+ }
2369
1141
 
2370
- // Validate syntax of cleaned SPARQL
2371
- if (!validateSparqlSyntax(sparql)) {
2372
- throw new Error('Generated SPARQL has invalid syntax')
2373
- }
1142
+ getRecent(n = 10) {
1143
+ return this.episodes.slice(-n)
1144
+ }
2374
1145
 
2375
- // Execute against K8s cluster
2376
- const results = await this._executeSparql(sparql)
2377
-
2378
- // Record trace
2379
- if (this.tracing) {
2380
- this.trace.push({
2381
- timestamp: new Date().toISOString(),
2382
- tool: 'kg.sparql.query',
2383
- input: prompt,
2384
- output: JSON.stringify(results),
2385
- durationMs: Date.now() - startTime,
2386
- success: true,
2387
- rawIsValid: rawIsValid
2388
- })
2389
- }
1146
+ size() {
1147
+ return this.episodes.length
1148
+ }
1149
+ }
2390
1150
 
2391
- return {
2392
- sparql,
2393
- rawSparql, // Original LLM output (may have markdown)
2394
- rawIsValid, // Did raw output pass syntax validation?
2395
- results,
2396
- success: true
2397
- }
2398
- } catch (error) {
2399
- if (this.tracing) {
2400
- this.trace.push({
2401
- timestamp: new Date().toISOString(),
2402
- tool: 'kg.sparql.query',
2403
- input: prompt,
2404
- output: error.message,
2405
- durationMs: Date.now() - startTime,
2406
- success: false
2407
- })
2408
- }
1151
+ /**
1152
+ * LongTermMemory - Persistent knowledge graph storage
1153
+ */
1154
+ class LongTermMemory {
1155
+ constructor(kg) {
1156
+ this.kg = kg
1157
+ this.memoryGraph = 'http://memory.hypermind.ai/'
1158
+ }
2409
1159
 
2410
- return {
2411
- results: [],
2412
- success: false,
2413
- error: error.message,
2414
- rawSparql: this._lastLlmResponse?.raw,
2415
- rawIsValid: this._lastLlmResponse?.rawIsValid
1160
+ store(subject, predicate, object) {
1161
+ if (this.kg) {
1162
+ try {
1163
+ this.kg.insertTriple(subject, predicate, object, this.memoryGraph)
1164
+ return true
1165
+ } catch (e) {
1166
+ return false
2416
1167
  }
2417
1168
  }
1169
+ return false
2418
1170
  }
2419
1171
 
2420
- /**
2421
- * Generate mock SPARQL (for testing)
2422
- */
2423
- _generateMockSparql(prompt) {
2424
- const lowerPrompt = prompt.toLowerCase()
2425
-
2426
- // Match against test suite
2427
- for (const test of LUBM_TEST_SUITE) {
2428
- if (lowerPrompt.includes(test.question.toLowerCase().slice(0, 20))) {
2429
- return test.expectedSparql
1172
+ query(sparql) {
1173
+ if (this.kg) {
1174
+ try {
1175
+ return this.kg.querySelect(sparql)
1176
+ } catch (e) {
1177
+ return []
2430
1178
  }
2431
1179
  }
2432
-
2433
- // Default SPARQL
2434
- return `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2435
- SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10`
1180
+ return []
2436
1181
  }
2437
1182
 
2438
- /**
2439
- * Call LLM to generate SPARQL
2440
- * Supports: claude-sonnet-4, gpt-4o
2441
- * Returns: { raw: string, cleaned: string, rawIsValid: boolean }
2442
- */
2443
- async _callLlmForSparql(prompt) {
2444
- const systemPrompt = `You are a SPARQL query generator for the LUBM (Lehigh University Benchmark) ontology.
2445
-
2446
- IMPORTANT RULES:
2447
- 1. ONLY output a valid SPARQL query - no explanations, no markdown, no backticks
2448
- 2. Use the LUBM ontology prefix: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2449
- 3. Common LUBM classes: Professor, GraduateStudent, UndergraduateStudent, Course, Department, University
2450
- 4. Common LUBM properties: name, advisor, teacherOf, takesCourse, memberOf, subOrganizationOf, worksFor, researchInterest, publicationAuthor
2451
-
2452
- EXAMPLES:
2453
- Q: "Find all professors"
2454
- A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2455
- SELECT ?x WHERE { ?x a ub:Professor }
2456
-
2457
- Q: "How many courses are there?"
2458
- A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2459
- SELECT (COUNT(?x) AS ?count) WHERE { ?x a ub:Course }
2460
-
2461
- Q: "Find students and their advisors"
2462
- A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
2463
- SELECT ?student ?advisor WHERE { ?student ub:advisor ?advisor }
2464
-
2465
- Now generate a SPARQL query for the following question. Output ONLY the SPARQL query, nothing else:`
2466
-
2467
- if (this.model.includes('claude') || this.model.includes('anthropic')) {
2468
- return this._callAnthropic(systemPrompt, prompt)
2469
- } else if (this.model.includes('gpt') || this.model.includes('openai')) {
2470
- return this._callOpenAI(systemPrompt, prompt)
2471
- } else {
2472
- throw new Error(`Unknown model: ${this.model}. Supported: claude-sonnet-4, gpt-4o, mock`)
2473
- }
1183
+ getRelated(entity, limit = 10) {
1184
+ const sparql = `
1185
+ SELECT ?p ?o WHERE {
1186
+ <${entity}> ?p ?o .
1187
+ } LIMIT ${limit}
1188
+ `
1189
+ return this.query(sparql)
2474
1190
  }
1191
+ }
2475
1192
 
2476
- /**
2477
- * Last LLM response details (for benchmark comparison)
2478
- */
2479
- _lastLlmResponse = null
1193
+ // ============================================================================
1194
+ // GOVERNANCE LAYER
1195
+ // ============================================================================
2480
1196
 
2481
- /**
2482
- * Call Anthropic Claude API
2483
- * Returns: { raw: string, cleaned: string, rawIsValid: boolean }
2484
- */
2485
- async _callAnthropic(systemPrompt, userPrompt) {
2486
- const apiKey = process.env.ANTHROPIC_API_KEY
2487
- if (!apiKey) {
2488
- throw new Error('ANTHROPIC_API_KEY environment variable not set')
1197
+ /**
1198
+ * GovernancePolicy - Defines capabilities and limits for agent execution
1199
+ */
1200
+ class GovernancePolicy {
1201
+ constructor(config = {}) {
1202
+ this.capabilities = new Set(config.capabilities || ['ReadKG', 'ExecuteTool'])
1203
+ this.limits = {
1204
+ maxMemoryMB: config.maxMemoryMB || 256,
1205
+ maxExecutionTimeMs: config.maxExecutionTimeMs || 60000,
1206
+ maxToolCalls: config.maxToolCalls || 100
2489
1207
  }
1208
+ this.auditEnabled = config.auditEnabled !== false
1209
+ }
2490
1210
 
2491
- const modelId = this.model === 'claude-sonnet-4' ? 'claude-sonnet-4-20250514' : this.model
2492
-
2493
- const requestBody = JSON.stringify({
2494
- model: modelId,
2495
- max_tokens: 1024,
2496
- system: systemPrompt,
2497
- messages: [{ role: 'user', content: userPrompt }]
2498
- })
2499
-
2500
- const response = await httpRequest('https://api.anthropic.com/v1/messages', {
2501
- method: 'POST',
2502
- headers: {
2503
- 'Content-Type': 'application/json',
2504
- 'x-api-key': apiKey,
2505
- 'anthropic-version': '2023-06-01'
2506
- },
2507
- body: requestBody,
2508
- timeout: 30000
2509
- })
1211
+ hasCapability(cap) {
1212
+ return this.capabilities.has(cap)
1213
+ }
2510
1214
 
2511
- if (response.status !== 200) {
2512
- throw new Error(`Anthropic API error: ${response.status} - ${response.data}`)
2513
- }
1215
+ addCapability(cap) {
1216
+ this.capabilities.add(cap)
1217
+ }
2514
1218
 
2515
- const data = JSON.parse(response.data)
2516
- const rawText = data.content[0].text.trim()
2517
- const cleanedText = this._cleanSparqlResponse(rawText)
1219
+ removeCapability(cap) {
1220
+ this.capabilities.delete(cap)
1221
+ }
2518
1222
 
2519
- // Return both raw and cleaned for comparison benchmarking
1223
+ checkLimits(usage) {
2520
1224
  return {
2521
- raw: rawText,
2522
- cleaned: cleanedText,
2523
- rawIsValid: validateSparqlSyntax(rawText)
1225
+ memoryOk: (usage.memoryMB || 0) <= this.limits.maxMemoryMB,
1226
+ timeOk: (usage.executionTimeMs || 0) <= this.limits.maxExecutionTimeMs,
1227
+ toolCallsOk: (usage.toolCalls || 0) <= this.limits.maxToolCalls
2524
1228
  }
2525
1229
  }
1230
+ }
2526
1231
 
2527
- /**
2528
- * Call OpenAI GPT API
2529
- * Returns: { raw: string, cleaned: string, rawIsValid: boolean }
2530
- */
2531
- async _callOpenAI(systemPrompt, userPrompt) {
2532
- const apiKey = process.env.OPENAI_API_KEY
2533
- if (!apiKey) {
2534
- throw new Error('OPENAI_API_KEY environment variable not set')
2535
- }
2536
-
2537
- const modelId = this.model === 'gpt-4o' ? 'gpt-4o' : this.model
2538
-
2539
- const requestBody = JSON.stringify({
2540
- model: modelId,
2541
- messages: [
2542
- { role: 'system', content: systemPrompt },
2543
- { role: 'user', content: userPrompt }
2544
- ],
2545
- max_tokens: 1024,
2546
- temperature: 0.1
2547
- })
1232
+ /**
1233
+ * GovernanceEngine - Enforces policies and maintains audit trail
1234
+ */
1235
+ class GovernanceEngine {
1236
+ constructor(policy) {
1237
+ this.policy = policy
1238
+ this.auditLog = []
1239
+ this.usage = { memoryMB: 0, executionTimeMs: 0, toolCalls: 0 }
1240
+ }
2548
1241
 
2549
- const response = await httpRequest('https://api.openai.com/v1/chat/completions', {
2550
- method: 'POST',
2551
- headers: {
2552
- 'Content-Type': 'application/json',
2553
- 'Authorization': `Bearer ${apiKey}`
2554
- },
2555
- body: requestBody,
2556
- timeout: 30000
2557
- })
1242
+ authorize(action, args) {
1243
+ const requiredCap = this._actionToCapability(action)
1244
+ const authorized = this.policy.hasCapability(requiredCap)
2558
1245
 
2559
- if (response.status !== 200) {
2560
- throw new Error(`OpenAI API error: ${response.status} - ${response.data}`)
1246
+ if (this.policy.auditEnabled) {
1247
+ this.auditLog.push({
1248
+ timestamp: new Date().toISOString(),
1249
+ action,
1250
+ args: JSON.stringify(args).slice(0, 200),
1251
+ authorized,
1252
+ capability: requiredCap
1253
+ })
2561
1254
  }
2562
1255
 
2563
- const data = JSON.parse(response.data)
2564
- const rawText = data.choices[0].message.content.trim()
2565
- const cleanedText = this._cleanSparqlResponse(rawText)
2566
-
2567
- // Return both raw and cleaned for comparison benchmarking
2568
- return {
2569
- raw: rawText,
2570
- cleaned: cleanedText,
2571
- rawIsValid: validateSparqlSyntax(rawText)
2572
- }
1256
+ return { authorized, reason: authorized ? 'OK' : `Missing capability: ${requiredCap}` }
2573
1257
  }
2574
1258
 
2575
- /**
2576
- * Clean SPARQL response from LLM (remove markdown, backticks, etc)
2577
- */
2578
- _cleanSparqlResponse(text) {
2579
- // Remove markdown code blocks
2580
- let clean = text.replace(/```sparql\n?/gi, '').replace(/```sql\n?/gi, '').replace(/```\n?/g, '')
2581
- // Remove leading/trailing whitespace
2582
- clean = clean.trim()
2583
- // If it starts with "SPARQL:" or similar, remove it
2584
- clean = clean.replace(/^sparql:\s*/i, '')
2585
- return clean
1259
+ recordUsage(type, amount) {
1260
+ if (type === 'toolCall') this.usage.toolCalls++
1261
+ if (type === 'memory') this.usage.memoryMB = amount
1262
+ if (type === 'time') this.usage.executionTimeMs = amount
2586
1263
  }
2587
1264
 
2588
- /**
2589
- * Execute SPARQL against K8s cluster
2590
- */
2591
- async _executeSparql(sparql) {
2592
- const url = `${this.endpoint}/sparql`
2593
-
2594
- const response = await httpRequest(url, {
2595
- method: 'POST',
2596
- headers: {
2597
- 'Content-Type': 'application/sparql-query',
2598
- Accept: 'application/json'
2599
- },
2600
- body: sparql,
2601
- timeout: this.timeout
2602
- })
1265
+ checkLimits() {
1266
+ return this.policy.checkLimits(this.usage)
1267
+ }
2603
1268
 
2604
- if (response.status !== 200) {
2605
- throw new Error(`SPARQL execution failed: ${response.status} - ${response.data}`)
2606
- }
1269
+ getAuditLog() {
1270
+ return this.auditLog
1271
+ }
2607
1272
 
2608
- try {
2609
- const data = JSON.parse(response.data)
2610
- // Handle different response formats:
2611
- // 1. W3C SPARQL JSON: {"results": {"bindings": [...]}}
2612
- // 2. rust-kgdb native: {"results": [{"?var": "value"}], "execution_time_ms": N}
2613
- if (data.results && Array.isArray(data.results)) {
2614
- // rust-kgdb native format
2615
- return data.results.map(row => ({
2616
- bindings: Object.fromEntries(
2617
- Object.entries(row).map(([k, v]) => {
2618
- // Strip leading ? from variable names and typed literal suffix
2619
- const cleanKey = k.startsWith('?') ? k.slice(1) : k
2620
- let cleanVal = v
2621
- // Handle typed literals: "11"^^<http://www.w3.org/2001/XMLSchema#integer>
2622
- if (typeof v === 'string' && v.includes('^^<')) {
2623
- cleanVal = v.split('^^')[0].replace(/^"|"$/g, '')
2624
- }
2625
- return [cleanKey, cleanVal]
2626
- })
2627
- )
2628
- }))
2629
- } else if (data.results && data.results.bindings) {
2630
- // W3C SPARQL JSON format
2631
- return data.results.bindings.map(b => ({
2632
- bindings: Object.fromEntries(
2633
- Object.entries(b).map(([k, v]) => [k, v.value])
2634
- )
2635
- }))
2636
- }
2637
- return data
2638
- } catch (e) {
2639
- throw new Error(`Failed to parse SPARQL results: ${e.message}`)
1273
+ _actionToCapability(action) {
1274
+ const mapping = {
1275
+ 'query': 'ReadKG',
1276
+ 'insert': 'WriteKG',
1277
+ 'delete': 'WriteKG',
1278
+ 'execute_tool': 'ExecuteTool',
1279
+ 'use_embeddings': 'UseEmbeddings'
2640
1280
  }
1281
+ return mapping[action] || 'ExecuteTool'
2641
1282
  }
1283
+ }
2642
1284
 
2643
- /**
2644
- * Get execution trace
2645
- */
2646
- getTrace() {
2647
- return this.trace
1285
+ // ============================================================================
1286
+ // AGENT SCOPE
1287
+ // ============================================================================
1288
+
1289
+ /**
1290
+ * AgentScope - Namespace isolation for multi-tenant execution
1291
+ */
1292
+ class AgentScope {
1293
+ constructor(config = {}) {
1294
+ this.name = config.name || 'default-scope'
1295
+ this.namespace = {
1296
+ graphUri: config.graphUri || 'http://default.scope/',
1297
+ allowedGraphs: config.allowedGraphs || [config.graphUri || 'http://default.scope/'],
1298
+ prefix: config.prefix || 'scope'
1299
+ }
1300
+ this.limits = {
1301
+ maxToolCalls: config.maxToolCalls || 50,
1302
+ maxResults: config.maxResults || 1000,
1303
+ maxGraphQueries: config.maxGraphQueries || 100
1304
+ }
1305
+ this.toolCalls = 0
1306
+ this.usage = {
1307
+ toolCalls: 0,
1308
+ graphQueries: 0,
1309
+ bytesProcessed: 0
1310
+ }
2648
1311
  }
2649
1312
 
2650
- /**
2651
- * Get planning context
2652
- */
2653
- getContext() {
2654
- return this.context
1313
+ isGraphAllowed(graphUri) {
1314
+ return this.namespace.allowedGraphs.some(g => graphUri.startsWith(g))
2655
1315
  }
2656
1316
 
2657
- /**
2658
- * Check connection to K8s cluster
2659
- */
2660
- async isConnected() {
2661
- try {
2662
- const response = await httpRequest(`${this.endpoint}/health`, {
2663
- timeout: 5000
2664
- })
2665
- return response.status === 200
2666
- } catch {
2667
- return false
1317
+ recordToolCall() {
1318
+ this.toolCalls++
1319
+ this.usage.toolCalls++
1320
+ if (this.toolCalls > this.limits.maxToolCalls) {
1321
+ throw new Error(`Scope "${this.name}" exceeded tool call limit (${this.limits.maxToolCalls})`)
2668
1322
  }
2669
1323
  }
2670
1324
 
2671
- getName() {
2672
- return this.name
1325
+ getScopedUri(localName) {
1326
+ return `${this.namespace.graphUri}${this.namespace.prefix}:${localName}`
2673
1327
  }
2674
1328
 
2675
- getModel() {
2676
- return this.model
1329
+ getUsage() {
1330
+ return {
1331
+ name: this.name,
1332
+ toolCalls: this.toolCalls,
1333
+ maxToolCalls: this.limits.maxToolCalls,
1334
+ ...this.usage
1335
+ }
2677
1336
  }
2678
1337
 
2679
- // ==========================================================================
2680
- // MEMORY HYPERGRAPH APIs - Typed interface for Memory + KG operations
2681
- // ==========================================================================
2682
-
2683
1338
  /**
2684
- * Recall memories enriched with Knowledge Graph context
2685
- * Typed API - generates optimized SPARQL internally
2686
- *
2687
- * @param {Object} options - Recall options
2688
- * @param {string} options.query - Natural language query (e.g., "Provider P001 fraud")
2689
- * @param {Object} options.kgFilter - Optional KG filter {predicate, operator, value}
2690
- * @param {number} options.limit - Max results (default 10)
2691
- * @returns {Promise<Object>} Enriched results with episode, finding, kgContext, semanticHash
2692
- *
2693
- * @example
2694
- * const results = await agent.recallWithKG({
2695
- * query: "Provider P001 fraud",
2696
- * kgFilter: { predicate: ":amount", operator: ">", value: 25000 },
2697
- * limit: 10
2698
- * })
1339
+ * Track resource usage
1340
+ * @param {string} resource - Resource type (toolCalls, graphQueries, bytesProcessed)
1341
+ * @param {number} amount - Amount to add to usage
2699
1342
  */
2700
- async recallWithKG(options = {}) {
2701
- const { query, kgFilter, limit = 10 } = options
2702
-
2703
- // Generate semantic hash for caching (SimHash-inspired)
2704
- const semanticHash = this._generateSemanticHash(query)
2705
-
2706
- // Check semantic cache
2707
- if (this._semanticCache && this._semanticCache.has(semanticHash)) {
2708
- return {
2709
- results: this._semanticCache.get(semanticHash),
2710
- fromCache: true,
2711
- semanticHash
2712
- }
2713
- }
2714
-
2715
- // Build and execute memory + KG SPARQL
2716
- const sparql = this._buildMemoryKGSparql(query, kgFilter, limit)
2717
-
2718
- try {
2719
- const rawResults = await this._executeSparql(sparql)
2720
-
2721
- const enrichedResults = rawResults.map(r => ({
2722
- episode: r.episode || 'Episode:unknown',
2723
- finding: r.finding || query,
2724
- kgContext: r.kgEntity ? { entity: r.kgEntity, value: r.kgValue } : null,
2725
- semanticHash
2726
- }))
2727
-
2728
- // Cache results
2729
- if (!this._semanticCache) this._semanticCache = new Map()
2730
- this._semanticCache.set(semanticHash, enrichedResults)
2731
-
2732
- return { results: enrichedResults, fromCache: false, semanticHash }
2733
- } catch (err) {
2734
- // Fallback to basic recall if KG query fails
2735
- return { results: [], error: err.message, semanticHash }
1343
+ trackUsage(resource, amount = 1) {
1344
+ if (this.usage.hasOwnProperty(resource)) {
1345
+ this.usage[resource] += amount
1346
+ } else {
1347
+ this.usage[resource] = amount
2736
1348
  }
2737
1349
  }
2738
1350
 
2739
1351
  /**
2740
- * Generate semantic hash using entity + action extraction
2741
- * Research: SimHash (Charikar, 2002), Semantic Hashing (Salakhutdinov & Hinton, 2009)
1352
+ * Get remaining resources before limits are exceeded
2742
1353
  */
2743
- _generateSemanticHash(text) {
2744
- const parts = []
2745
-
2746
- // Extract entity patterns (Provider:P001, Claim:C123, etc.)
2747
- const entityPattern = /([A-Z][a-z]+)[:\s]?([A-Z0-9]+)/g
2748
- for (const match of text.matchAll(entityPattern)) {
2749
- parts.push(`${match[1].toLowerCase()}-${match[2].toLowerCase()}`)
2750
- }
2751
-
2752
- // Extract action keywords
2753
- const actions = ['fraud', 'detect', 'analyze', 'claim', 'risk', 'pattern', 'deny', 'approve']
2754
- const tokens = text.toLowerCase().split(/\s+/)
2755
- for (const token of tokens) {
2756
- if (actions.some(a => token.includes(a))) {
2757
- parts.push(token)
2758
- }
1354
+ getRemainingResources() {
1355
+ return {
1356
+ toolCalls: this.limits.maxToolCalls - this.usage.toolCalls,
1357
+ graphQueries: this.limits.maxGraphQueries - (this.usage.graphQueries || 0),
1358
+ results: this.limits.maxResults
2759
1359
  }
2760
-
2761
- return `semhash:${parts.slice(0, 5).join('-') || 'general'}`
2762
1360
  }
2763
1361
 
2764
1362
  /**
2765
- * Build SPARQL for Memory + KG join
1363
+ * Check if resource limit has been exceeded
1364
+ * @param {string} resource - Resource type to check
2766
1365
  */
2767
- _buildMemoryKGSparql(query, kgFilter, limit) {
2768
- const filterClause = kgFilter
2769
- ? `FILTER(?value ${kgFilter.operator} ${kgFilter.value})`
2770
- : ''
2771
-
2772
- // Extract potential entity URIs from query
2773
- const entityPattern = /([A-Z][a-z]+)[:\s]?([A-Z0-9]+)/g
2774
- const entities = []
2775
- for (const match of query.matchAll(entityPattern)) {
2776
- entities.push(`<http://example.org/${match[1]}/${match[2]}>`)
2777
- }
2778
-
2779
- const valuesClause = entities.length > 0
2780
- ? `VALUES ?entity { ${entities.join(' ')} }`
2781
- : '?entity a <http://www.w3.org/2000/01/rdf-schema#Resource>'
2782
-
2783
- return `
2784
- PREFIX am: <https://gonnect.ai/ontology/agent-memory#>
2785
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
2786
-
2787
- SELECT ?episode ?finding ?kgEntity ?kgValue WHERE {
2788
- OPTIONAL {
2789
- GRAPH <https://gonnect.ai/memory/> {
2790
- ?episode a am:Episode ; am:prompt ?finding .
2791
- }
2792
- }
2793
- OPTIONAL {
2794
- ${valuesClause}
2795
- ?entity ?pred ?kgValue .
2796
- BIND(?entity AS ?kgEntity)
2797
- ${filterClause}
2798
- }
2799
- } LIMIT ${limit}`
1366
+ isLimitExceeded(resource) {
1367
+ const remaining = this.getRemainingResources()
1368
+ return remaining[resource] !== undefined && remaining[resource] <= 0
2800
1369
  }
2801
1370
  }
2802
1371
 
2803
- /**
2804
- * Run HyperMind BrowseComp-Plus style benchmark
2805
- *
2806
- * KEY COMPARISON:
2807
- * - "Vanilla LLM" = Raw LLM output WITHOUT HyperMind cleaning
2808
- * - "HyperMind Agent" = LLM output WITH typed tools, cleaning, validation
2809
- *
2810
- * This shows the TRUE value of HyperMind by comparing:
2811
- * 1. How often raw LLM output has syntax issues (markdown, backticks, etc)
2812
- * 2. How HyperMind fixes these issues with _cleanSparqlResponse()
2813
- */
2814
- async function runHyperMindBenchmark(endpoint, model, options = {}) {
2815
- const testSuite = options.testIndices
2816
- ? LUBM_TEST_SUITE.filter(t => options.testIndices.includes(t.index))
2817
- : LUBM_TEST_SUITE
2818
-
2819
- const results = []
2820
- let rawSyntaxSuccess = 0 // Vanilla LLM: raw output passes validation
2821
- let hypermindSyntaxSuccess = 0 // HyperMind: cleaned output passes validation
2822
- let executionSuccess = 0 // Actually executed against cluster
2823
- let typeErrorsCaught = 0
2824
- let totalLatency = 0
2825
- let cleaningRequired = 0 // How many times cleaning was needed
2826
-
2827
- // Determine provider details
2828
- const providerInfo = model.includes('claude')
2829
- ? { name: 'Anthropic', modelId: 'claude-sonnet-4-20250514', api: 'https://api.anthropic.com/v1/messages' }
2830
- : model.includes('gpt')
2831
- ? { name: 'OpenAI', modelId: 'gpt-4o', api: 'https://api.openai.com/v1/chat/completions' }
2832
- : { name: 'Mock (Pattern Matching)', modelId: 'mock', api: 'N/A' }
2833
-
2834
- console.log(`\n${'═'.repeat(80)}`)
2835
- console.log(` HyperMind Agentic Framework Benchmark`)
2836
- console.log(` Vanilla LLM vs HyperMind Agent Comparison`)
2837
- console.log(`${'═'.repeat(80)}`)
2838
- console.log()
2839
- console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
2840
- console.log(` │ BENCHMARK CONFIGURATION │`)
2841
- console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
2842
- console.log(` │ Dataset: LUBM (Lehigh University Benchmark) Ontology │`)
2843
- console.log(` │ - 3,272 triples (LUBM-1: 1 university) │`)
2844
- console.log(` │ - Classes: Professor, GraduateStudent, Course, Department │`)
2845
- console.log(` │ - Properties: advisor, teacherOf, memberOf, worksFor │`)
2846
- console.log(` │ │`)
2847
- console.log(` │ LLM Provider: ${providerInfo.name.padEnd(60)}│`)
2848
- console.log(` │ Model ID: ${providerInfo.modelId.padEnd(60)}│`)
2849
- console.log(` │ API Endpoint: ${providerInfo.api.padEnd(60)}│`)
2850
- console.log(` │ │`)
2851
- console.log(` │ Task: Natural Language → SPARQL Query Generation │`)
2852
- console.log(` │ Agent receives question, generates SPARQL, executes query │`)
2853
- console.log(` │ │`)
2854
- console.log(` │ Embeddings: NOT USED (this benchmark is NL-to-SPARQL, not semantic) │`)
2855
- console.log(` │ Multi-Vector: NOT APPLICABLE │`)
2856
- console.log(` │ │`)
2857
- console.log(` │ K8s Cluster: ${endpoint.padEnd(60)}│`)
2858
- console.log(` │ Tests: ${testSuite.length} LUBM queries (Easy: 3, Medium: 5, Hard: 4) │`)
2859
- console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
2860
- console.log()
2861
- console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
2862
- console.log(` │ AGENT CREATION │`)
2863
- console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
2864
- console.log(` │ Name: benchmark-agent │`)
2865
- console.log(` │ Model: ${model.padEnd(62)}│`)
2866
- console.log(` │ Tools: kg.sparql.query, kg.motif.find, kg.datalog.apply │`)
2867
- console.log(` │ Tracing: enabled │`)
2868
- console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
2869
- console.log()
2870
- console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
2871
- console.log(` │ 12 LUBM TEST QUERIES │`)
2872
- console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
2873
- for (const test of testSuite) {
2874
- const q = `Q${test.index}: "${test.question}"`.slice(0, 72)
2875
- console.log(` │ ${q.padEnd(74)}│`)
2876
- }
2877
- console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
2878
- console.log()
2879
- console.log(`${'═'.repeat(80)}\n`)
2880
-
2881
- // Spawn agent with HyperMind framework
2882
- const agent = await HyperMindAgent.spawn({
2883
- name: 'benchmark-agent',
2884
- model: model,
2885
- tools: ['kg.sparql.query', 'kg.motif.find', 'kg.datalog.apply'],
2886
- endpoint: endpoint,
2887
- timeout: options.timeout || 30000,
2888
- tracing: true
2889
- })
2890
-
2891
- for (const test of testSuite) {
2892
- const startTime = Date.now()
2893
- console.log(`Test ${test.index}: ${test.question}`)
2894
- console.log(` Difficulty: ${test.difficulty}`)
1372
+ // ============================================================================
1373
+ // EXPORTS
1374
+ // ============================================================================
2895
1375
 
2896
- try {
2897
- // Test with HyperMind framework
2898
- const result = await agent.call(test.question)
2899
- const latency = Date.now() - startTime
2900
- totalLatency += latency
2901
-
2902
- // Track raw (vanilla) LLM success
2903
- if (result.rawIsValid === true) {
2904
- rawSyntaxSuccess++
2905
- console.log(` 📝 Vanilla LLM: ✅ RAW OUTPUT VALID`)
2906
- } else if (result.rawIsValid === false) {
2907
- console.log(` 📝 Vanilla LLM: ❌ RAW OUTPUT INVALID (needs cleaning)`)
2908
- cleaningRequired++
2909
- }
1376
+ module.exports = {
1377
+ // Main Agent
1378
+ HyperMindAgent,
2910
1379
 
2911
- // Track HyperMind success
2912
- if (result.success) {
2913
- hypermindSyntaxSuccess++
2914
- executionSuccess++
2915
- console.log(` 🧠 HyperMind: ✅ SUCCESS (${latency}ms)`)
2916
- if (result.sparql && options.verbose) {
2917
- console.log(` SPARQL: ${result.sparql.slice(0, 60)}...`)
2918
- }
2919
- } else {
2920
- // Check if this was a type error caught by framework
2921
- if (result.error && result.error.includes('Type')) {
2922
- typeErrorsCaught++
2923
- console.log(` 🧠 HyperMind: ⚠️ TYPE ERROR CAUGHT`)
2924
- } else {
2925
- console.log(` 🧠 HyperMind: ❌ FAILED - ${result.error}`)
2926
- }
2927
- }
1380
+ // Supporting Classes
1381
+ MemoryManager,
1382
+ DatalogRuleSet,
1383
+ WasmSandbox,
1384
+ ExecutionTrace,
1385
+ ProofNode,
2928
1386
 
2929
- // Show raw vs cleaned if different (demonstrates HyperMind value)
2930
- if (result.rawSparql && result.sparql && result.rawSparql !== result.sparql) {
2931
- if (options.verbose) {
2932
- console.log(` ↳ Raw had: ${result.rawSparql.includes('```') ? 'markdown' : 'formatting issues'}`)
2933
- }
2934
- }
1387
+ // Type System
1388
+ TypeId,
2935
1389
 
2936
- results.push({
2937
- question: test.question,
2938
- difficulty: test.difficulty,
2939
- rawIsValid: result.rawIsValid,
2940
- hypermindSuccess: result.success,
2941
- executionSuccess: result.success,
2942
- sparql: result.sparql,
2943
- rawSparql: result.rawSparql,
2944
- typeErrorsCaught: result.error?.includes('Type') ? 1 : 0,
2945
- latencyMs: latency,
2946
- error: result.error
2947
- })
2948
- } catch (error) {
2949
- console.log(` ❌ ERROR: ${error.message}`)
2950
- results.push({
2951
- question: test.question,
2952
- difficulty: test.difficulty,
2953
- rawIsValid: false,
2954
- hypermindSuccess: false,
2955
- executionSuccess: false,
2956
- typeErrorsCaught: 0,
2957
- latencyMs: Date.now() - startTime,
2958
- error: error.message
2959
- })
2960
- }
1390
+ // Agent State Machine
1391
+ AgentState,
1392
+ AgentRuntime,
2961
1393
 
2962
- console.log()
2963
- }
2964
-
2965
- // Calculate statistics
2966
- const stats = {
2967
- totalTests: testSuite.length,
2968
- // Vanilla LLM stats (raw output without HyperMind)
2969
- vanillaLlmSyntaxSuccess: rawSyntaxSuccess,
2970
- vanillaLlmSyntaxRate: (rawSyntaxSuccess / testSuite.length) * 100,
2971
- // HyperMind stats (with typed tools + cleaning)
2972
- hypermindSyntaxSuccess: hypermindSyntaxSuccess,
2973
- hypermindSyntaxRate: (hypermindSyntaxSuccess / testSuite.length) * 100,
2974
- // Execution stats
2975
- executionSuccess: executionSuccess,
2976
- executionSuccessRate: (executionSuccess / testSuite.length) * 100,
2977
- // Value metrics
2978
- cleaningRequired: cleaningRequired,
2979
- syntaxImprovement: hypermindSyntaxSuccess - rawSyntaxSuccess,
2980
- typeErrorsCaught: typeErrorsCaught,
2981
- avgLatencyMs: totalLatency / testSuite.length
2982
- }
2983
-
2984
- // Print summary with clear comparison
2985
- console.log(`${'═'.repeat(70)}`)
2986
- console.log(` BENCHMARK RESULTS: Vanilla LLM vs HyperMind Agent`)
2987
- console.log(`${'═'.repeat(70)}`)
2988
- console.log()
2989
- console.log(` ┌─────────────────────────────────────────────────────────────────┐`)
2990
- console.log(` │ Metric │ Vanilla LLM │ HyperMind │ Δ Improve │`)
2991
- console.log(` ├─────────────────────────────────────────────────────────────────┤`)
2992
- console.log(` │ Syntax Valid │ ${stats.vanillaLlmSyntaxRate.toFixed(1).padStart(9)}% │ ${stats.hypermindSyntaxRate.toFixed(1).padStart(7)}% │ ${stats.syntaxImprovement > 0 ? '+' : ''}${stats.syntaxImprovement.toString().padStart(7)} │`)
2993
- console.log(` │ Execution Success │ N/A │ ${stats.executionSuccessRate.toFixed(1).padStart(7)}% │ │`)
2994
- console.log(` │ Avg Latency │ N/A │ ${stats.avgLatencyMs.toFixed(0).padStart(5)}ms │ │`)
2995
- console.log(` └─────────────────────────────────────────────────────────────────┘`)
2996
- console.log()
2997
- console.log(` 📊 Summary:`)
2998
- console.log(` - Total Tests: ${stats.totalTests}`)
2999
- console.log(` - Times Cleaning Needed: ${stats.cleaningRequired} (${((stats.cleaningRequired/stats.totalTests)*100).toFixed(0)}%)`)
3000
- console.log(` - Type Errors Caught: ${stats.typeErrorsCaught}`)
3001
- if (stats.syntaxImprovement > 0) {
3002
- console.log(` - HyperMind FIXED ${stats.syntaxImprovement} queries that Vanilla LLM failed!`)
3003
- }
3004
- console.log(`${'═'.repeat(70)}\n`)
3005
-
3006
- // Save results if requested
3007
- if (options.saveResults) {
3008
- const fs = require('fs')
3009
- const filename = `hypermind_benchmark_${model}_${Date.now()}.json`
3010
- fs.writeFileSync(
3011
- filename,
3012
- JSON.stringify(
3013
- {
3014
- timestamp: new Date().toISOString(),
3015
- model,
3016
- endpoint,
3017
- comparison: 'Vanilla LLM vs HyperMind Agent',
3018
- stats,
3019
- results
3020
- },
3021
- null,
3022
- 2
3023
- )
3024
- )
3025
- console.log(`Results saved to: ${filename}`)
3026
- }
1394
+ // Memory Tiers
1395
+ WorkingMemory,
1396
+ EpisodicMemory,
1397
+ LongTermMemory,
3027
1398
 
3028
- return stats
3029
- }
1399
+ // Governance Layer
1400
+ GovernancePolicy,
1401
+ GovernanceEngine,
3030
1402
 
3031
- // Export for CommonJS
3032
- module.exports = {
3033
- // Original HyperMind Agent API
3034
- HyperMindAgent,
3035
- runHyperMindBenchmark,
3036
- getHyperMindBenchmarkSuite,
3037
- validateSparqlSyntax,
3038
- createPlanningContext,
3039
- LUBM_TEST_SUITE,
3040
- HYPERMIND_TOOLS,
3041
-
3042
- // Architecture Components (v0.5.8+)
3043
- TypeId, // Type system (Hindley-Milner + Refinement Types)
3044
- TOOL_REGISTRY, // Typed tool morphisms
3045
- LLMPlanner, // Natural language -> typed tool pipelines
3046
- WasmSandbox, // WASM sandbox with capability-based security
3047
- AgentBuilder, // Fluent builder for agent composition
3048
- ComposedAgent, // Composed agent with sandbox execution
3049
-
3050
- // Memory Layer (v0.5.13+) - GraphDB-Powered Agent Memory
3051
- AgentState, // Agent lifecycle states (CREATED, READY, RUNNING, etc.)
3052
- AgentRuntime, // Agent identity and state management
3053
- WorkingMemory, // In-memory current context
3054
- EpisodicMemory, // Execution history stored in GraphDB
3055
- LongTermMemory, // Source-of-truth knowledge graph (read-only)
3056
- MemoryManager, // Unified memory retrieval with weighted scoring
3057
-
3058
- // Governance Layer (v0.5.13+) - Policy Engine & Capability Grants
3059
- GovernancePolicy, // Access control and behavioral policies
3060
- GovernanceEngine, // Policy enforcement and audit logging
3061
-
3062
- // Scope Layer (v0.5.13+) - Namespace Isolation & Resource Limits
3063
- AgentScope // Namespace boundaries and resource tracking
1403
+ // Scope Layer
1404
+ AgentScope
3064
1405
  }