rust-kgdb 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/core-concepts-demo.ts +50 -35
- package/examples/datalog-example.ts +246 -339
- package/examples/fraud-detection-agent.js +23 -36
- package/examples/hypermind-fraud-underwriter.ts +54 -51
- package/examples/underwriting-agent.js +16 -20
- package/hypermind-agent.js +1183 -2588
- package/index.js +6 -0
- package/package.json +1 -1
- package/rust-kgdb-napi.darwin-x64.node +0 -0
package/hypermind-agent.js
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HyperMind Agentic Framework - TypeScript SDK Implementation
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
|
28
|
-
*
|
|
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
|
-
//
|
|
66
|
+
// PROOF NODE (Explainable AI - No Hallucination)
|
|
72
67
|
// ============================================================================
|
|
73
68
|
|
|
74
69
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
70
|
+
* ProofNode - Every result has a proof of its derivation
|
|
71
|
+
* This ensures ZERO hallucination - everything traced to KG or rules
|
|
77
72
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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'
|
|
125
|
-
}
|
|
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'
|
|
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
|
|
91
|
+
}
|
|
155
92
|
}
|
|
156
93
|
}
|
|
157
94
|
|
|
158
95
|
// ============================================================================
|
|
159
|
-
//
|
|
96
|
+
// EXECUTION TRACE (Full Explainability)
|
|
160
97
|
// ============================================================================
|
|
161
98
|
|
|
162
99
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
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
|
|
167
|
-
constructor(
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
target: 'academic',
|
|
248
|
-
domain: 'lubm'
|
|
249
|
-
}
|
|
250
|
-
}
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// DATALOG RULES (Configurable Reasoning)
|
|
163
|
+
// ============================================================================
|
|
251
164
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
'
|
|
270
|
-
|
|
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
|
-
'
|
|
273
|
-
|
|
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
|
-
'
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
215
|
+
description: 'Student-advisor relationship'
|
|
216
|
+
})
|
|
217
|
+
}
|
|
279
218
|
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
return
|
|
231
|
+
getRule(name) {
|
|
232
|
+
return this.rules.get(name)
|
|
300
233
|
}
|
|
301
234
|
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
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 (
|
|
262
|
+
// WASM SANDBOX (Secure Execution)
|
|
345
263
|
// ============================================================================
|
|
346
264
|
|
|
347
265
|
/**
|
|
348
|
-
* WasmSandbox -
|
|
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.
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
}
|
|
383
|
-
|
|
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
|
-
})
|
|
443
274
|
}
|
|
444
275
|
|
|
445
276
|
hasCapability(cap) {
|
|
446
|
-
return this.
|
|
277
|
+
return this.capabilities.has(cap)
|
|
447
278
|
}
|
|
448
279
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
284
|
+
this.fuel -= amount
|
|
460
285
|
}
|
|
461
286
|
|
|
462
|
-
|
|
287
|
+
log(action, args, result, status) {
|
|
463
288
|
this.auditLog.push({
|
|
464
289
|
timestamp: new Date().toISOString(),
|
|
465
|
-
|
|
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,1363 @@ 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
|
|
303
|
+
// MEMORY MANAGER (Episode Memory with KG Integration)
|
|
493
304
|
// ============================================================================
|
|
494
305
|
|
|
495
306
|
/**
|
|
496
|
-
*
|
|
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
|
|
517
|
-
constructor(
|
|
518
|
-
|
|
519
|
-
this.
|
|
520
|
-
this.
|
|
521
|
-
this.
|
|
522
|
-
this.
|
|
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
|
|
330
|
+
}
|
|
523
331
|
|
|
524
|
-
//
|
|
525
|
-
this.
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
|
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
|
+
}
|
|
542
339
|
}
|
|
543
340
|
|
|
544
|
-
|
|
545
|
-
this.currentExecution = null
|
|
546
|
-
this.executionCount = 0
|
|
547
|
-
this.lastActiveAt = this.createdAt
|
|
341
|
+
this.episodes.push(episode)
|
|
548
342
|
|
|
549
|
-
//
|
|
550
|
-
this.
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
+
}
|
|
556
351
|
}
|
|
557
|
-
}
|
|
558
352
|
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
}
|
|
353
|
+
return episode
|
|
354
|
+
}
|
|
571
355
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
356
|
+
async retrieve(query, limit = 10) {
|
|
357
|
+
if (!this.episodes.length) return []
|
|
575
358
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
timestamp: new Date().toISOString(),
|
|
580
|
-
reason
|
|
581
|
-
})
|
|
582
|
-
this.lastActiveAt = new Date().toISOString()
|
|
359
|
+
const queryEmbedding = this.embeddingService
|
|
360
|
+
? await this._getEmbedding(query)
|
|
361
|
+
: null
|
|
583
362
|
|
|
584
|
-
|
|
585
|
-
|
|
363
|
+
const scored = this.episodes.map(ep => ({
|
|
364
|
+
episode: ep,
|
|
365
|
+
score: this._calculateScore(ep, query, queryEmbedding)
|
|
366
|
+
}))
|
|
586
367
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
})
|
|
592
375
|
}
|
|
593
376
|
|
|
594
|
-
|
|
595
|
-
|
|
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
|
|
607
|
-
}
|
|
377
|
+
_calculateScore(episode, query, queryEmbedding) {
|
|
378
|
+
const hoursAgo = (Date.now() - new Date(episode.timestamp).getTime()) / 3600000
|
|
379
|
+
const recency = Math.pow(this.decayRate, hoursAgo)
|
|
608
380
|
|
|
609
|
-
|
|
610
|
-
|
|
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++
|
|
381
|
+
let relevance = 0
|
|
382
|
+
if (queryEmbedding && episode.embedding) {
|
|
383
|
+
relevance = this._cosineSimilarity(queryEmbedding, episode.embedding)
|
|
624
384
|
} else {
|
|
625
|
-
|
|
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
|
|
632
|
-
|
|
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
|
|
392
|
+
return this.weights.recency * recency +
|
|
393
|
+
this.weights.relevance * relevance +
|
|
394
|
+
this.weights.importance * importance
|
|
636
395
|
}
|
|
637
396
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
466
|
+
* Add data to working memory (ephemeral, in-context)
|
|
467
|
+
* @param {Object} data - Data to store in working memory
|
|
766
468
|
*/
|
|
767
|
-
|
|
768
|
-
|
|
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
|
|
479
|
+
* Get data from working memory
|
|
480
|
+
* @param {string} key - Key to retrieve
|
|
773
481
|
*/
|
|
774
|
-
|
|
775
|
-
return this.
|
|
482
|
+
getFromWorking(key) {
|
|
483
|
+
return this.workingMemory.get(key)
|
|
776
484
|
}
|
|
777
485
|
|
|
778
486
|
/**
|
|
779
487
|
* Clear working memory
|
|
780
488
|
*/
|
|
781
|
-
|
|
782
|
-
this.
|
|
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
|
-
*
|
|
494
|
+
* Get memory statistics
|
|
792
495
|
*/
|
|
793
|
-
|
|
496
|
+
getStats() {
|
|
794
497
|
return {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
*
|
|
521
|
+
* HyperMindAgent - Neuro-Symbolic AI Agent with ZERO hallucination
|
|
806
522
|
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
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
|
|
814
|
-
constructor(
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
*
|
|
564
|
+
* Execute a natural language request
|
|
565
|
+
* Returns answer + full explainable AI output
|
|
822
566
|
*/
|
|
823
|
-
async
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
//
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
*
|
|
615
|
+
* Configure Datalog rules
|
|
858
616
|
*/
|
|
859
|
-
|
|
860
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
621
|
+
removeRule(name) {
|
|
622
|
+
return this.rules.removeRule(name)
|
|
623
|
+
}
|
|
876
624
|
|
|
877
|
-
|
|
625
|
+
getRules() {
|
|
626
|
+
return this.rules.getAllRules()
|
|
878
627
|
}
|
|
879
628
|
|
|
880
629
|
/**
|
|
881
|
-
*
|
|
630
|
+
* Get audit log from sandbox
|
|
882
631
|
*/
|
|
883
|
-
|
|
884
|
-
|
|
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
|
|
637
|
+
* Get agent name
|
|
926
638
|
*/
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
*
|
|
644
|
+
* Get model name (defaults to 'mock' if no API key)
|
|
935
645
|
*/
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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 []
|
|
999
|
-
}
|
|
1000
|
-
|
|
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)
|
|
684
|
+
]
|
|
1024
685
|
}
|
|
1025
686
|
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
-
})
|
|
698
|
+
}
|
|
1081
699
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
700
|
+
return {
|
|
701
|
+
type: 'general_query',
|
|
702
|
+
tools: ['kg.sparql.query'],
|
|
703
|
+
confidence: 0.85
|
|
1085
704
|
}
|
|
1086
705
|
}
|
|
1087
706
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
+
}))
|
|
1094
713
|
|
|
1095
|
-
/**
|
|
1096
|
-
* Get graph URIs
|
|
1097
|
-
*/
|
|
1098
|
-
getGraphUris() {
|
|
1099
714
|
return {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
715
|
+
id: `plan_${Date.now()}`,
|
|
716
|
+
intent: intent.type,
|
|
717
|
+
steps,
|
|
718
|
+
type_chain: this._buildTypeChain(steps)
|
|
1103
719
|
}
|
|
1104
720
|
}
|
|
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
721
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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 {}
|
|
1131
736
|
}
|
|
1132
|
-
|
|
1133
|
-
// Access tracking for importance scoring
|
|
1134
|
-
this.accessCounts = new Map()
|
|
1135
|
-
this.retrievalHistory = []
|
|
1136
737
|
}
|
|
1137
738
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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`
|
|
1148
753
|
|
|
1149
|
-
|
|
1150
|
-
|
|
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`
|
|
1151
766
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
|
|
1155
|
-
results.episodic = this._scoreEpisodicResults(episodes, query)
|
|
1156
|
-
}
|
|
767
|
+
case 'aggregate':
|
|
768
|
+
return `SELECT (COUNT(*) as ?count) WHERE { ?s ?p ?o }`
|
|
1157
769
|
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
}))
|
|
770
|
+
default:
|
|
771
|
+
return `SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 100`
|
|
1166
772
|
}
|
|
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
773
|
}
|
|
1181
774
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
-
}
|
|
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 []
|
|
1199
783
|
}
|
|
784
|
+
}
|
|
1200
785
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
data: tr,
|
|
1208
|
-
score: this._calculateScore(tr.timestamp, 0.7, 1)
|
|
1209
|
-
})
|
|
1210
|
-
}
|
|
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)'
|
|
1211
792
|
}
|
|
1212
|
-
|
|
1213
|
-
return results.sort((a, b) => b.score - a.score)
|
|
1214
793
|
}
|
|
1215
794
|
|
|
1216
|
-
|
|
1217
|
-
|
|
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)
|
|
795
|
+
_buildTypeChain(steps) {
|
|
796
|
+
return steps.map(s => s.tool).join(' -> ')
|
|
1231
797
|
}
|
|
1232
798
|
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
}
|
|
799
|
+
async _executePlan(plan, trace) {
|
|
800
|
+
const results = []
|
|
1246
801
|
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
-
}
|
|
802
|
+
for (const step of plan.steps) {
|
|
803
|
+
this.sandbox.consumeFuel(100)
|
|
1257
804
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
_getAccessCount(id) {
|
|
1262
|
-
return this.accessCounts.get(id) || 0
|
|
1263
|
-
}
|
|
805
|
+
try {
|
|
806
|
+
const result = await this._executeTool(step.tool, step.args)
|
|
807
|
+
results.push({ step: step.id, tool: step.tool, result, success: true })
|
|
1264
808
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
809
|
+
if (step.tool === 'kg.sparql.query') {
|
|
810
|
+
trace.addSparql(step.args.query, result)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
this.sandbox.log(step.tool, step.args, result, 'OK')
|
|
814
|
+
} catch (err) {
|
|
815
|
+
results.push({ step: step.id, tool: step.tool, error: err.message, success: false })
|
|
816
|
+
this.sandbox.log(step.tool, step.args, null, 'ERROR')
|
|
817
|
+
}
|
|
818
|
+
}
|
|
1271
819
|
|
|
1272
|
-
/**
|
|
1273
|
-
* Combine and rank results from all memory layers
|
|
1274
|
-
*/
|
|
1275
|
-
_combineAndRank(results) {
|
|
1276
820
|
return results
|
|
1277
|
-
.sort((a, b) => b.score - a.score)
|
|
1278
|
-
.slice(0, 20) // Top 20 results
|
|
1279
821
|
}
|
|
1280
822
|
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
})
|
|
1294
|
-
|
|
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
|
-
}
|
|
823
|
+
async _executeTool(tool, args) {
|
|
824
|
+
if (!this.sandbox.hasCapability('ExecuteTool')) {
|
|
825
|
+
throw new Error('Missing ExecuteTool capability')
|
|
1300
826
|
}
|
|
1301
|
-
}
|
|
1302
827
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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' }
|
|
841
|
+
}
|
|
1308
842
|
}
|
|
1309
843
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
this.working.addContext(item)
|
|
1315
|
-
return this
|
|
1316
|
-
}
|
|
844
|
+
_executeSparql(query) {
|
|
845
|
+
if (!this.kg || !this.kg.querySelect) {
|
|
846
|
+
return []
|
|
847
|
+
}
|
|
1317
848
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
|
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 }
|
|
1339
854
|
}
|
|
1340
855
|
}
|
|
1341
856
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return results
|
|
1348
870
|
}
|
|
1349
871
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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()}`)
|
|
872
|
+
_executeEmbeddingSearch(args) {
|
|
873
|
+
if (!this.embeddings || !this.embeddings.search) {
|
|
874
|
+
return []
|
|
1377
875
|
}
|
|
1378
876
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
// Combine into semantic hash
|
|
1385
|
-
const semanticParts = hashParts.slice(0, 5).join('-') || 'general-query'
|
|
1386
|
-
return `semhash:${semanticParts}`
|
|
877
|
+
try {
|
|
878
|
+
return this.embeddings.search(args.text, args.k, args.threshold)
|
|
879
|
+
} catch (err) {
|
|
880
|
+
return { error: err.message }
|
|
881
|
+
}
|
|
1387
882
|
}
|
|
1388
883
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
|
884
|
+
_executeMotifFind(args) {
|
|
885
|
+
// Motif finding would use GraphFrame.find()
|
|
886
|
+
return { pattern: args.pattern, matches: [] }
|
|
1401
887
|
}
|
|
1402
888
|
|
|
1403
|
-
|
|
1404
|
-
|
|
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)
|
|
1422
|
-
|
|
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
|
-
}
|
|
1429
|
-
|
|
1430
|
-
// Get episodic memories first
|
|
1431
|
-
const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
|
|
1432
|
-
const scoredEpisodes = this._scoreEpisodicResults(episodes, query)
|
|
889
|
+
_executeTriangleCount() {
|
|
890
|
+
// Triangle counting would use GraphFrame.triangleCount()
|
|
891
|
+
return { count: 0 }
|
|
892
|
+
}
|
|
1433
893
|
|
|
1434
|
-
|
|
1435
|
-
const
|
|
894
|
+
async _applyRules(intent, results, trace) {
|
|
895
|
+
const inferences = []
|
|
1436
896
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
+
}
|
|
1449
910
|
}
|
|
1450
911
|
}
|
|
1451
912
|
|
|
1452
|
-
|
|
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
|
|
913
|
+
return inferences
|
|
1474
914
|
}
|
|
1475
915
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
entities.add(`<http://example.org/${match[1]}/${match[2]}>`)
|
|
916
|
+
_buildProofDAG(plan, results, inferences, trace) {
|
|
917
|
+
// Build proof tree showing how answer was derived
|
|
918
|
+
const children = []
|
|
919
|
+
|
|
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
|
+
))
|
|
1490
929
|
}
|
|
1491
930
|
}
|
|
1492
931
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
+
))
|
|
1499
940
|
}
|
|
1500
941
|
|
|
1501
|
-
return
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
${filterClause}
|
|
1508
|
-
} LIMIT 100`
|
|
942
|
+
return new ProofNode(
|
|
943
|
+
'inference',
|
|
944
|
+
{ plan: plan.id, intent: plan.intent },
|
|
945
|
+
`Derived answer via ${plan.steps.length} steps`,
|
|
946
|
+
children
|
|
947
|
+
)
|
|
1509
948
|
}
|
|
1510
949
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
*/
|
|
1515
|
-
_semanticCache = new Map()
|
|
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)
|
|
1516
953
|
|
|
1517
|
-
|
|
1518
|
-
// Check for exact match
|
|
1519
|
-
if (this._semanticCache.has(hash)) {
|
|
1520
|
-
return this._semanticCache.get(hash)
|
|
1521
|
-
}
|
|
954
|
+
let answer = `Found ${totalResults} results`
|
|
1522
955
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (this.semanticHashMatch(hash, cachedHash)) {
|
|
1526
|
-
return value
|
|
1527
|
-
}
|
|
956
|
+
if (inferences.length > 0) {
|
|
957
|
+
answer += ` using ${inferences.length} reasoning rules`
|
|
1528
958
|
}
|
|
1529
959
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
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)
|
|
960
|
+
if (intent.type === 'detect_fraud' && totalResults > 0) {
|
|
961
|
+
answer = `Detected ${totalResults} potential fraud cases using ${inferences.length} detection rules`
|
|
1538
962
|
}
|
|
1539
|
-
|
|
963
|
+
|
|
964
|
+
return answer
|
|
1540
965
|
}
|
|
1541
966
|
}
|
|
1542
967
|
|
|
1543
968
|
// ============================================================================
|
|
1544
|
-
//
|
|
969
|
+
// AGENT STATE MACHINE
|
|
1545
970
|
// ============================================================================
|
|
1546
971
|
|
|
1547
972
|
/**
|
|
1548
|
-
*
|
|
973
|
+
* AgentState - State machine for agent lifecycle
|
|
1549
974
|
*/
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
|
1570
|
-
}
|
|
1571
|
-
|
|
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
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
/**
|
|
1589
|
-
* Check if capability is granted
|
|
1590
|
-
*/
|
|
1591
|
-
hasCapability(capability) {
|
|
1592
|
-
return this.capabilities.has(capability)
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
/**
|
|
1596
|
-
* Grant a capability
|
|
1597
|
-
*/
|
|
1598
|
-
grantCapability(capability) {
|
|
1599
|
-
this.capabilities.add(capability)
|
|
1600
|
-
return this
|
|
1601
|
-
}
|
|
1602
|
-
|
|
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
|
-
}
|
|
975
|
+
const AgentState = {
|
|
976
|
+
IDLE: 'IDLE',
|
|
977
|
+
READY: 'READY',
|
|
978
|
+
PLANNING: 'PLANNING',
|
|
979
|
+
EXECUTING: 'EXECUTING',
|
|
980
|
+
WAITING: 'WAITING',
|
|
981
|
+
ERROR: 'ERROR',
|
|
982
|
+
TERMINATED: 'TERMINATED'
|
|
1632
983
|
}
|
|
1633
984
|
|
|
985
|
+
// ============================================================================
|
|
986
|
+
// AGENT RUNTIME
|
|
987
|
+
// ============================================================================
|
|
988
|
+
|
|
1634
989
|
/**
|
|
1635
|
-
*
|
|
990
|
+
* AgentRuntime - Runtime context for agent execution
|
|
1636
991
|
*/
|
|
1637
|
-
class
|
|
1638
|
-
constructor(
|
|
1639
|
-
this.
|
|
1640
|
-
this.
|
|
1641
|
-
this.
|
|
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
|
|
1642
1005
|
}
|
|
1643
1006
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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
|
-
}
|
|
1661
|
-
|
|
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
|
|
1668
|
-
}
|
|
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]: []
|
|
1669
1016
|
}
|
|
1670
1017
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
result.reason = 'External API access not allowed'
|
|
1674
|
-
this._logDenial(action, result.reason)
|
|
1675
|
-
return result
|
|
1018
|
+
if (!validTransitions[this.state]?.includes(newState)) {
|
|
1019
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`)
|
|
1676
1020
|
}
|
|
1677
1021
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
this
|
|
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
|
-
})
|
|
1022
|
+
this.state = newState
|
|
1023
|
+
this.stateHistory.push({ state: newState, timestamp: Date.now() })
|
|
1024
|
+
return this
|
|
1709
1025
|
}
|
|
1710
1026
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
*/
|
|
1714
|
-
getAuditLog(limit = 100) {
|
|
1715
|
-
return this.auditLog.slice(-limit)
|
|
1027
|
+
getStateHistory() {
|
|
1028
|
+
return this.stateHistory
|
|
1716
1029
|
}
|
|
1717
1030
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1031
|
+
startExecution(description) {
|
|
1032
|
+
const execId = crypto.randomUUID()
|
|
1033
|
+
this.currentExecution = {
|
|
1034
|
+
id: execId,
|
|
1035
|
+
description,
|
|
1036
|
+
startTime: Date.now(),
|
|
1037
|
+
steps: []
|
|
1038
|
+
}
|
|
1039
|
+
this.state = AgentState.EXECUTING
|
|
1040
|
+
return execId
|
|
1723
1041
|
}
|
|
1724
1042
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
|
1731
1052
|
}
|
|
1732
1053
|
}
|
|
1733
1054
|
|
|
1734
1055
|
// ============================================================================
|
|
1735
|
-
//
|
|
1056
|
+
// MEMORY TIERS (Working, Episodic, Long-Term)
|
|
1736
1057
|
// ============================================================================
|
|
1737
1058
|
|
|
1738
1059
|
/**
|
|
1739
|
-
*
|
|
1060
|
+
* WorkingMemory - Fast, ephemeral context (like CPU registers)
|
|
1740
1061
|
*/
|
|
1741
|
-
class
|
|
1742
|
-
constructor(
|
|
1743
|
-
this.
|
|
1744
|
-
this.
|
|
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
|
-
}
|
|
1062
|
+
class WorkingMemory {
|
|
1063
|
+
constructor(capacity = 100) {
|
|
1064
|
+
this.capacity = capacity
|
|
1065
|
+
this.items = new Map()
|
|
1066
|
+
this.accessOrder = []
|
|
1778
1067
|
}
|
|
1779
1068
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
if (this.namespace.deniedGraphs.includes(graphUri)) {
|
|
1786
|
-
return false
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
// Check allowed
|
|
1790
|
-
if (this.namespace.allowedGraphs.includes('*')) {
|
|
1791
|
-
return true
|
|
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)
|
|
1792
1074
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
if (pattern.endsWith('*')) {
|
|
1796
|
-
return graphUri.startsWith(pattern.slice(0, -1))
|
|
1797
|
-
}
|
|
1798
|
-
return graphUri === pattern
|
|
1799
|
-
})
|
|
1075
|
+
this.items.set(key, { value, timestamp: Date.now() })
|
|
1076
|
+
this._updateAccess(key)
|
|
1800
1077
|
}
|
|
1801
1078
|
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
this.usage[resource] += amount
|
|
1079
|
+
get(key) {
|
|
1080
|
+
const item = this.items.get(key)
|
|
1081
|
+
if (item) {
|
|
1082
|
+
this._updateAccess(key)
|
|
1083
|
+
return item.value
|
|
1808
1084
|
}
|
|
1809
|
-
return
|
|
1810
|
-
}
|
|
1811
|
-
|
|
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
|
|
1085
|
+
return null
|
|
1818
1086
|
}
|
|
1819
1087
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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)
|
|
1830
|
-
}
|
|
1831
|
-
return remaining
|
|
1088
|
+
_updateAccess(key) {
|
|
1089
|
+
const idx = this.accessOrder.indexOf(key)
|
|
1090
|
+
if (idx > -1) this.accessOrder.splice(idx, 1)
|
|
1091
|
+
this.accessOrder.push(key)
|
|
1832
1092
|
}
|
|
1833
1093
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
resetUsage() {
|
|
1838
|
-
for (const key of Object.keys(this.usage)) {
|
|
1839
|
-
this.usage[key] = 0
|
|
1840
|
-
}
|
|
1841
|
-
return this
|
|
1094
|
+
clear() {
|
|
1095
|
+
this.items.clear()
|
|
1096
|
+
this.accessOrder = []
|
|
1842
1097
|
}
|
|
1843
1098
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
*/
|
|
1847
|
-
getScopedGraphUri(suffix = '') {
|
|
1848
|
-
return `${this.namespace.graphUri}${suffix}`
|
|
1099
|
+
size() {
|
|
1100
|
+
return this.items.size
|
|
1849
1101
|
}
|
|
1850
1102
|
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
-
}
|
|
1103
|
+
toJSON() {
|
|
1104
|
+
return Object.fromEntries(this.items)
|
|
1863
1105
|
}
|
|
1864
1106
|
}
|
|
1865
1107
|
|
|
1866
|
-
// ============================================================================
|
|
1867
|
-
// AGENT BUILDER (Fluent Composition Pattern)
|
|
1868
|
-
// ============================================================================
|
|
1869
|
-
|
|
1870
1108
|
/**
|
|
1871
|
-
*
|
|
1109
|
+
* EpisodicMemory - Execution history with temporal ordering
|
|
1872
1110
|
*/
|
|
1873
|
-
class
|
|
1874
|
-
constructor(
|
|
1875
|
-
this.
|
|
1876
|
-
this.
|
|
1877
|
-
this.planner = spec.planner
|
|
1878
|
-
this.sandbox = spec.sandbox
|
|
1879
|
-
this.hooks = spec.hooks || []
|
|
1880
|
-
this.conversationHistory = []
|
|
1111
|
+
class EpisodicMemory {
|
|
1112
|
+
constructor(limit = 1000) {
|
|
1113
|
+
this.limit = limit
|
|
1114
|
+
this.episodes = []
|
|
1881
1115
|
}
|
|
1882
1116
|
|
|
1883
|
-
|
|
1884
|
-
this.
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1117
|
+
store(episode) {
|
|
1118
|
+
this.episodes.push({
|
|
1119
|
+
...episode,
|
|
1120
|
+
id: crypto.randomUUID(),
|
|
1121
|
+
timestamp: new Date().toISOString(),
|
|
1122
|
+
accessCount: 0
|
|
1888
1123
|
})
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
const proxy = this.sandbox.createObjectProxy(this.tools)
|
|
1893
|
-
const results = []
|
|
1894
|
-
|
|
1895
|
-
for (const step of plan.steps) {
|
|
1896
|
-
this._fireHooks('beforeExecute', { step })
|
|
1897
|
-
|
|
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
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
|
|
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()
|
|
1124
|
+
if (this.episodes.length > this.limit) {
|
|
1125
|
+
this.episodes.shift()
|
|
1918
1126
|
}
|
|
1919
1127
|
}
|
|
1920
1128
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
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)
|
|
1925
1140
|
}
|
|
1926
1141
|
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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')
|
|
1142
|
+
getRecent(n = 10) {
|
|
1143
|
+
return this.episodes.slice(-n)
|
|
1144
|
+
}
|
|
1948
1145
|
|
|
1949
|
-
|
|
1146
|
+
size() {
|
|
1147
|
+
return this.episodes.length
|
|
1950
1148
|
}
|
|
1951
1149
|
}
|
|
1952
1150
|
|
|
1953
1151
|
/**
|
|
1954
|
-
*
|
|
1152
|
+
* LongTermMemory - Persistent knowledge graph storage
|
|
1955
1153
|
*/
|
|
1956
|
-
class
|
|
1957
|
-
constructor(
|
|
1958
|
-
this.
|
|
1959
|
-
|
|
1960
|
-
tools: {},
|
|
1961
|
-
planner: null,
|
|
1962
|
-
sandbox: null,
|
|
1963
|
-
hooks: []
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
|
|
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
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
return this
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
|
-
withPlanner(model) {
|
|
1978
|
-
this.spec.planner = new LLMPlanner(model, this.spec.tools)
|
|
1979
|
-
return this
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
withSandbox(config) {
|
|
1983
|
-
this.spec.sandbox = new WasmSandbox(config)
|
|
1984
|
-
return this
|
|
1154
|
+
class LongTermMemory {
|
|
1155
|
+
constructor(kg) {
|
|
1156
|
+
this.kg = kg
|
|
1157
|
+
this.memoryGraph = 'http://memory.hypermind.ai/'
|
|
1985
1158
|
}
|
|
1986
1159
|
|
|
1987
|
-
|
|
1988
|
-
this.
|
|
1989
|
-
|
|
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
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return false
|
|
1990
1170
|
}
|
|
1991
1171
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
'kg.embeddings.search': { status: 'success', similar: [], count: 8 }
|
|
1172
|
+
query(sparql) {
|
|
1173
|
+
if (this.kg) {
|
|
1174
|
+
try {
|
|
1175
|
+
return this.kg.querySelect(sparql)
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
return []
|
|
1999
1178
|
}
|
|
2000
|
-
return mockResults[toolName] || { status: 'success', resultCount: 1 }
|
|
2001
1179
|
}
|
|
1180
|
+
return []
|
|
2002
1181
|
}
|
|
2003
1182
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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)
|
|
2008
1190
|
}
|
|
2009
1191
|
}
|
|
2010
1192
|
|
|
2011
1193
|
// ============================================================================
|
|
2012
|
-
//
|
|
1194
|
+
// GOVERNANCE LAYER
|
|
2013
1195
|
// ============================================================================
|
|
2014
1196
|
|
|
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
1197
|
/**
|
|
2215
|
-
*
|
|
1198
|
+
* GovernancePolicy - Defines capabilities and limits for agent execution
|
|
2216
1199
|
*/
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
})
|
|
2249
|
-
|
|
2250
|
-
if (options.body) {
|
|
2251
|
-
req.write(options.body)
|
|
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
|
|
2252
1207
|
}
|
|
2253
|
-
|
|
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?```$/, '')
|
|
1208
|
+
this.auditEnabled = config.auditEnabled !== false
|
|
2267
1209
|
}
|
|
2268
1210
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
|
2278
|
-
|
|
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
|
|
1211
|
+
hasCapability(cap) {
|
|
1212
|
+
return this.capabilities.has(cap)
|
|
1213
|
+
}
|
|
2283
1214
|
|
|
2284
|
-
|
|
2285
|
-
|
|
1215
|
+
addCapability(cap) {
|
|
1216
|
+
this.capabilities.add(cap)
|
|
1217
|
+
}
|
|
2286
1218
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
*/
|
|
2290
|
-
function createPlanningContext(endpoint, hints = []) {
|
|
2291
|
-
return {
|
|
2292
|
-
tools: HYPERMIND_TOOLS,
|
|
2293
|
-
hints: [...LUBM_HINTS, ...hints],
|
|
2294
|
-
endpoint: endpoint
|
|
1219
|
+
removeCapability(cap) {
|
|
1220
|
+
this.capabilities.delete(cap)
|
|
2295
1221
|
}
|
|
2296
|
-
}
|
|
2297
1222
|
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
difficulty: t.difficulty,
|
|
2306
|
-
expectedPattern: t.expectedPattern
|
|
2307
|
-
}))
|
|
1223
|
+
checkLimits(usage) {
|
|
1224
|
+
return {
|
|
1225
|
+
memoryOk: (usage.memoryMB || 0) <= this.limits.maxMemoryMB,
|
|
1226
|
+
timeOk: (usage.executionTimeMs || 0) <= this.limits.maxExecutionTimeMs,
|
|
1227
|
+
toolCallsOk: (usage.toolCalls || 0) <= this.limits.maxToolCalls
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
2308
1230
|
}
|
|
2309
1231
|
|
|
2310
1232
|
/**
|
|
2311
|
-
*
|
|
1233
|
+
* GovernanceEngine - Enforces policies and maintains audit trail
|
|
2312
1234
|
*/
|
|
2313
|
-
class
|
|
2314
|
-
constructor(
|
|
2315
|
-
this.
|
|
2316
|
-
this.
|
|
2317
|
-
this.
|
|
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)
|
|
1235
|
+
class GovernanceEngine {
|
|
1236
|
+
constructor(policy) {
|
|
1237
|
+
this.policy = policy
|
|
1238
|
+
this.auditLog = []
|
|
1239
|
+
this.usage = { memoryMB: 0, executionTimeMs: 0, toolCalls: 0 }
|
|
2323
1240
|
}
|
|
2324
1241
|
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
static async spawn(spec) {
|
|
2329
|
-
const agent = new HyperMindAgent(spec)
|
|
1242
|
+
authorize(action, args) {
|
|
1243
|
+
const requiredCap = this._actionToCapability(action)
|
|
1244
|
+
const authorized = this.policy.hasCapability(requiredCap)
|
|
2330
1245
|
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
+
})
|
|
2339
1254
|
}
|
|
2340
1255
|
|
|
2341
|
-
return
|
|
1256
|
+
return { authorized, reason: authorized ? 'OK' : `Missing capability: ${requiredCap}` }
|
|
2342
1257
|
}
|
|
2343
1258
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
const startTime = Date.now()
|
|
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
|
|
1263
|
+
}
|
|
2350
1264
|
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
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
|
-
}
|
|
1265
|
+
checkLimits() {
|
|
1266
|
+
return this.policy.checkLimits(this.usage)
|
|
1267
|
+
}
|
|
2369
1268
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
}
|
|
1269
|
+
getAuditLog() {
|
|
1270
|
+
return this.auditLog
|
|
1271
|
+
}
|
|
2374
1272
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
success: true,
|
|
2387
|
-
rawIsValid: rawIsValid
|
|
2388
|
-
})
|
|
2389
|
-
}
|
|
1273
|
+
_actionToCapability(action) {
|
|
1274
|
+
const mapping = {
|
|
1275
|
+
'query': 'ReadKG',
|
|
1276
|
+
'insert': 'WriteKG',
|
|
1277
|
+
'delete': 'WriteKG',
|
|
1278
|
+
'execute_tool': 'ExecuteTool',
|
|
1279
|
+
'use_embeddings': 'UseEmbeddings'
|
|
1280
|
+
}
|
|
1281
|
+
return mapping[action] || 'ExecuteTool'
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
2390
1284
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
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
|
-
}
|
|
1285
|
+
// ============================================================================
|
|
1286
|
+
// AGENT SCOPE
|
|
1287
|
+
// ============================================================================
|
|
2409
1288
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
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
|
|
2417
1310
|
}
|
|
2418
1311
|
}
|
|
2419
1312
|
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
_generateMockSparql(prompt) {
|
|
2424
|
-
const lowerPrompt = prompt.toLowerCase()
|
|
1313
|
+
isGraphAllowed(graphUri) {
|
|
1314
|
+
return this.namespace.allowedGraphs.some(g => graphUri.startsWith(g))
|
|
1315
|
+
}
|
|
2425
1316
|
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
}
|
|
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})`)
|
|
2431
1322
|
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
getScopedUri(localName) {
|
|
1326
|
+
return `${this.namespace.graphUri}${this.namespace.prefix}:${localName}`
|
|
1327
|
+
}
|
|
2432
1328
|
|
|
2433
|
-
|
|
2434
|
-
return
|
|
2435
|
-
|
|
1329
|
+
getUsage() {
|
|
1330
|
+
return {
|
|
1331
|
+
name: this.name,
|
|
1332
|
+
toolCalls: this.toolCalls,
|
|
1333
|
+
maxToolCalls: this.limits.maxToolCalls,
|
|
1334
|
+
...this.usage
|
|
1335
|
+
}
|
|
2436
1336
|
}
|
|
2437
1337
|
|
|
2438
1338
|
/**
|
|
2439
|
-
*
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
1339
|
+
* Track resource usage
|
|
1340
|
+
* @param {string} resource - Resource type (toolCalls, graphQueries, bytesProcessed)
|
|
1341
|
+
* @param {number} amount - Amount to add to usage
|
|
2442
1342
|
*/
|
|
2443
|
-
|
|
2444
|
-
|
|
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)
|
|
1343
|
+
trackUsage(resource, amount = 1) {
|
|
1344
|
+
if (this.usage.hasOwnProperty(resource)) {
|
|
1345
|
+
this.usage[resource] += amount
|
|
2471
1346
|
} else {
|
|
2472
|
-
|
|
1347
|
+
this.usage[resource] = amount
|
|
2473
1348
|
}
|
|
2474
1349
|
}
|
|
2475
1350
|
|
|
2476
1351
|
/**
|
|
2477
|
-
*
|
|
1352
|
+
* Get remaining resources before limits are exceeded
|
|
2478
1353
|
*/
|
|
2479
|
-
|
|
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
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
2480
1361
|
|
|
2481
1362
|
/**
|
|
2482
|
-
*
|
|
2483
|
-
*
|
|
1363
|
+
* Check if resource limit has been exceeded
|
|
1364
|
+
* @param {string} resource - Resource type to check
|
|
2484
1365
|
*/
|
|
2485
|
-
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
1366
|
+
isLimitExceeded(resource) {
|
|
1367
|
+
const remaining = this.getRemainingResources()
|
|
1368
|
+
return remaining[resource] !== undefined && remaining[resource] <= 0
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
2490
1371
|
|
|
2491
|
-
|
|
1372
|
+
// ============================================================================
|
|
1373
|
+
// ComposedAgent - Agent with sandbox execution and witness generation
|
|
1374
|
+
// ============================================================================
|
|
2492
1375
|
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
1376
|
+
/**
|
|
1377
|
+
* ComposedAgent - Agent with sandbox execution and witness generation
|
|
1378
|
+
* Built using AgentBuilder fluent API
|
|
1379
|
+
*/
|
|
1380
|
+
class ComposedAgent {
|
|
1381
|
+
constructor(config) {
|
|
1382
|
+
this.name = config.name
|
|
1383
|
+
this.tools = config.tools || []
|
|
1384
|
+
this.planner = config.planner || 'claude-sonnet-4'
|
|
1385
|
+
this.sandboxConfig = config.sandbox || { capabilities: ['ReadKG'], fuelLimit: 1000000 }
|
|
1386
|
+
this.hooks = config.hooks || {}
|
|
1387
|
+
this.sandbox = new WasmSandbox(this.sandboxConfig)
|
|
1388
|
+
}
|
|
2499
1389
|
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
timeout: 30000
|
|
2509
|
-
})
|
|
1390
|
+
/**
|
|
1391
|
+
* Execute with natural language prompt
|
|
1392
|
+
* @param {string} prompt - Natural language query
|
|
1393
|
+
* @returns {Promise<Object>} - Execution result with witness
|
|
1394
|
+
*/
|
|
1395
|
+
async call(prompt) {
|
|
1396
|
+
const startTime = Date.now()
|
|
1397
|
+
const planId = `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
2510
1398
|
|
|
2511
|
-
|
|
2512
|
-
|
|
1399
|
+
// Fire beforePlan hook
|
|
1400
|
+
if (this.hooks.beforePlan) {
|
|
1401
|
+
this.hooks.beforePlan({ prompt, agent: this.name })
|
|
2513
1402
|
}
|
|
2514
1403
|
|
|
2515
|
-
|
|
2516
|
-
const
|
|
2517
|
-
|
|
1404
|
+
// Create plan (in production, this would call LLM)
|
|
1405
|
+
const plan = {
|
|
1406
|
+
id: planId,
|
|
1407
|
+
steps: this.tools.map((tool, i) => ({
|
|
1408
|
+
step: i + 1,
|
|
1409
|
+
tool: tool.name || tool,
|
|
1410
|
+
input: prompt
|
|
1411
|
+
})),
|
|
1412
|
+
confidence: 0.95
|
|
1413
|
+
}
|
|
2518
1414
|
|
|
2519
|
-
//
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
cleaned: cleanedText,
|
|
2523
|
-
rawIsValid: validateSparqlSyntax(rawText)
|
|
1415
|
+
// Fire afterPlan hook
|
|
1416
|
+
if (this.hooks.afterPlan) {
|
|
1417
|
+
this.hooks.afterPlan({ plan, agent: this.name })
|
|
2524
1418
|
}
|
|
2525
|
-
}
|
|
2526
1419
|
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
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')
|
|
1420
|
+
// Fire beforeExecute hook
|
|
1421
|
+
if (this.hooks.beforeExecute) {
|
|
1422
|
+
this.hooks.beforeExecute({ plan, agent: this.name })
|
|
2535
1423
|
}
|
|
2536
1424
|
|
|
2537
|
-
|
|
1425
|
+
// Execute plan steps
|
|
1426
|
+
const results = []
|
|
1427
|
+
for (const step of plan.steps) {
|
|
1428
|
+
try {
|
|
1429
|
+
// Execute in sandbox
|
|
1430
|
+
const result = await this.sandbox.execute(step.tool, step.input)
|
|
1431
|
+
results.push({
|
|
1432
|
+
step: step,
|
|
1433
|
+
result: result,
|
|
1434
|
+
status: 'completed'
|
|
1435
|
+
})
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
results.push({
|
|
1438
|
+
step: step,
|
|
1439
|
+
error: error.message,
|
|
1440
|
+
status: 'failed'
|
|
1441
|
+
})
|
|
1442
|
+
if (this.hooks.onError) {
|
|
1443
|
+
this.hooks.onError({ step, error, agent: this.name })
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
2538
1447
|
|
|
2539
|
-
const
|
|
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
|
-
})
|
|
1448
|
+
const endTime = Date.now()
|
|
2548
1449
|
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
1450
|
+
// Generate execution witness
|
|
1451
|
+
const witness = {
|
|
1452
|
+
witness_version: '1.0.0',
|
|
1453
|
+
timestamp: new Date().toISOString(),
|
|
1454
|
+
agent: this.name,
|
|
1455
|
+
model: this.planner,
|
|
1456
|
+
plan: {
|
|
1457
|
+
id: plan.id,
|
|
1458
|
+
steps: plan.steps.length,
|
|
1459
|
+
confidence: plan.confidence
|
|
2554
1460
|
},
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
1461
|
+
execution: {
|
|
1462
|
+
tool_calls: results.map(r => ({
|
|
1463
|
+
tool: r.step.tool,
|
|
1464
|
+
status: r.status
|
|
1465
|
+
}))
|
|
1466
|
+
},
|
|
1467
|
+
sandbox_metrics: this.sandbox.getMetrics ? this.sandbox.getMetrics() : {},
|
|
1468
|
+
audit_log: this.sandbox.getAuditLog ? this.sandbox.getAuditLog() : [],
|
|
1469
|
+
proof_hash: this._generateProofHash(plan, results)
|
|
2561
1470
|
}
|
|
2562
1471
|
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
1472
|
+
const response = {
|
|
1473
|
+
response: `Executed ${results.filter(r => r.status === 'completed').length}/${results.length} steps successfully`,
|
|
1474
|
+
plan,
|
|
1475
|
+
results,
|
|
1476
|
+
witness,
|
|
1477
|
+
metrics: {
|
|
1478
|
+
total_time_ms: endTime - startTime,
|
|
1479
|
+
tool_calls: results.length
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
2566
1482
|
|
|
2567
|
-
//
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
cleaned: cleanedText,
|
|
2571
|
-
rawIsValid: validateSparqlSyntax(rawText)
|
|
1483
|
+
// Fire afterExecute hook
|
|
1484
|
+
if (this.hooks.afterExecute) {
|
|
1485
|
+
this.hooks.afterExecute(response)
|
|
2572
1486
|
}
|
|
2573
|
-
}
|
|
2574
1487
|
|
|
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
|
|
1488
|
+
return response
|
|
2586
1489
|
}
|
|
2587
1490
|
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
headers: {
|
|
2597
|
-
'Content-Type': 'application/sparql-query',
|
|
2598
|
-
Accept: 'application/json'
|
|
2599
|
-
},
|
|
2600
|
-
body: sparql,
|
|
2601
|
-
timeout: this.timeout
|
|
2602
|
-
})
|
|
2603
|
-
|
|
2604
|
-
if (response.status !== 200) {
|
|
2605
|
-
throw new Error(`SPARQL execution failed: ${response.status} - ${response.data}`)
|
|
1491
|
+
_generateProofHash(plan, results) {
|
|
1492
|
+
// Simple hash generation - in production use crypto
|
|
1493
|
+
const data = JSON.stringify({ plan: plan.id, results: results.length, timestamp: Date.now() })
|
|
1494
|
+
let hash = 0
|
|
1495
|
+
for (let i = 0; i < data.length; i++) {
|
|
1496
|
+
const char = data.charCodeAt(i)
|
|
1497
|
+
hash = ((hash << 5) - hash) + char
|
|
1498
|
+
hash = hash & hash
|
|
2606
1499
|
}
|
|
1500
|
+
return `sha256:${Math.abs(hash).toString(16).padStart(16, '0')}`
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
2607
1503
|
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
return data
|
|
2638
|
-
} catch (e) {
|
|
2639
|
-
throw new Error(`Failed to parse SPARQL results: ${e.message}`)
|
|
2640
|
-
}
|
|
1504
|
+
// ============================================================================
|
|
1505
|
+
// AgentBuilder - Fluent builder for agent composition
|
|
1506
|
+
// ============================================================================
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* AgentBuilder - Fluent builder pattern for composing agents
|
|
1510
|
+
*
|
|
1511
|
+
* @example
|
|
1512
|
+
* const agent = new AgentBuilder('compliance-checker')
|
|
1513
|
+
* .withTool('kg.sparql.query')
|
|
1514
|
+
* .withTool('kg.datalog.infer')
|
|
1515
|
+
* .withPlanner('claude-sonnet-4')
|
|
1516
|
+
* .withPolicy({
|
|
1517
|
+
* maxExecutionTime: 30000,
|
|
1518
|
+
* allowedTools: ['kg.sparql.query'],
|
|
1519
|
+
* auditLevel: 'full'
|
|
1520
|
+
* })
|
|
1521
|
+
* .withSandbox({ capabilities: ['ReadKG'], fuelLimit: 1000000 })
|
|
1522
|
+
* .withHook('afterExecute', (data) => console.log(data))
|
|
1523
|
+
* .build()
|
|
1524
|
+
*/
|
|
1525
|
+
class AgentBuilder {
|
|
1526
|
+
constructor(name) {
|
|
1527
|
+
this._name = name
|
|
1528
|
+
this._tools = []
|
|
1529
|
+
this._planner = 'claude-sonnet-4'
|
|
1530
|
+
this._sandbox = { capabilities: ['ReadKG'], fuelLimit: 1000000 }
|
|
1531
|
+
this._hooks = {}
|
|
1532
|
+
this._policy = null
|
|
2641
1533
|
}
|
|
2642
1534
|
|
|
2643
1535
|
/**
|
|
2644
|
-
*
|
|
1536
|
+
* Add tool to agent (from TOOL_REGISTRY)
|
|
1537
|
+
* @param {string} toolName - Tool name (e.g., 'kg.sparql.query')
|
|
1538
|
+
* @param {Function} [toolImpl] - Optional custom implementation
|
|
1539
|
+
* @returns {this} - Builder for chaining
|
|
2645
1540
|
*/
|
|
2646
|
-
|
|
2647
|
-
|
|
1541
|
+
withTool(toolName, toolImpl) {
|
|
1542
|
+
this._tools.push({ name: toolName, impl: toolImpl })
|
|
1543
|
+
return this
|
|
2648
1544
|
}
|
|
2649
1545
|
|
|
2650
1546
|
/**
|
|
2651
|
-
*
|
|
1547
|
+
* Set LLM planner model
|
|
1548
|
+
* @param {string} model - Model name (e.g., 'claude-sonnet-4', 'gpt-4o')
|
|
1549
|
+
* @returns {this} - Builder for chaining
|
|
2652
1550
|
*/
|
|
2653
|
-
|
|
2654
|
-
|
|
1551
|
+
withPlanner(model) {
|
|
1552
|
+
this._planner = model
|
|
1553
|
+
return this
|
|
2655
1554
|
}
|
|
2656
1555
|
|
|
2657
1556
|
/**
|
|
2658
|
-
*
|
|
1557
|
+
* Configure WASM sandbox
|
|
1558
|
+
* @param {Object} config - Sandbox configuration
|
|
1559
|
+
* @param {number} [config.maxMemory] - Maximum memory in bytes
|
|
1560
|
+
* @param {number} [config.maxExecTime] - Maximum execution time in ms
|
|
1561
|
+
* @param {string[]} [config.capabilities] - Capabilities: 'ReadKG', 'WriteKG', 'ExecuteTool'
|
|
1562
|
+
* @param {number} [config.fuelLimit] - Fuel limit for operations
|
|
1563
|
+
* @returns {this} - Builder for chaining
|
|
2659
1564
|
*/
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
timeout: 5000
|
|
2664
|
-
})
|
|
2665
|
-
return response.status === 200
|
|
2666
|
-
} catch {
|
|
2667
|
-
return false
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
getName() {
|
|
2672
|
-
return this.name
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
getModel() {
|
|
2676
|
-
return this.model
|
|
1565
|
+
withSandbox(config) {
|
|
1566
|
+
this._sandbox = { ...this._sandbox, ...config }
|
|
1567
|
+
return this
|
|
2677
1568
|
}
|
|
2678
1569
|
|
|
2679
|
-
// ==========================================================================
|
|
2680
|
-
// MEMORY HYPERGRAPH APIs - Typed interface for Memory + KG operations
|
|
2681
|
-
// ==========================================================================
|
|
2682
|
-
|
|
2683
1570
|
/**
|
|
2684
|
-
*
|
|
2685
|
-
*
|
|
2686
|
-
*
|
|
2687
|
-
* @param {
|
|
2688
|
-
* @param {string}
|
|
2689
|
-
* @param {
|
|
2690
|
-
* @
|
|
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
|
-
* })
|
|
1571
|
+
* Set governance policy
|
|
1572
|
+
* @param {Object} policy - Policy configuration
|
|
1573
|
+
* @param {number} [policy.maxExecutionTime] - Maximum execution time in ms
|
|
1574
|
+
* @param {string[]} [policy.allowedTools] - List of allowed tools
|
|
1575
|
+
* @param {string[]} [policy.deniedTools] - List of denied tools
|
|
1576
|
+
* @param {string} [policy.auditLevel] - Audit level: 'none', 'basic', 'full'
|
|
1577
|
+
* @returns {this} - Builder for chaining
|
|
2699
1578
|
*/
|
|
2700
|
-
|
|
2701
|
-
|
|
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 }
|
|
2736
|
-
}
|
|
1579
|
+
withPolicy(policy) {
|
|
1580
|
+
this._policy = policy
|
|
1581
|
+
return this
|
|
2737
1582
|
}
|
|
2738
1583
|
|
|
2739
1584
|
/**
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
1585
|
+
* Add execution hook
|
|
1586
|
+
* @param {string} event - Event name: 'beforePlan', 'afterPlan', 'beforeExecute', 'afterExecute', 'onError'
|
|
1587
|
+
* @param {Function} handler - Event handler function
|
|
1588
|
+
* @returns {this} - Builder for chaining
|
|
2742
1589
|
*/
|
|
2743
|
-
|
|
2744
|
-
|
|
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
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
return `semhash:${parts.slice(0, 5).join('-') || 'general'}`
|
|
1590
|
+
withHook(event, handler) {
|
|
1591
|
+
this._hooks[event] = handler
|
|
1592
|
+
return this
|
|
2762
1593
|
}
|
|
2763
1594
|
|
|
2764
1595
|
/**
|
|
2765
|
-
* Build
|
|
1596
|
+
* Build the composed agent
|
|
1597
|
+
* @returns {ComposedAgent} - Configured agent ready for execution
|
|
2766
1598
|
*/
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
1599
|
+
build() {
|
|
1600
|
+
// Apply policy restrictions to tools if policy is set
|
|
1601
|
+
let tools = this._tools
|
|
1602
|
+
if (this._policy) {
|
|
1603
|
+
if (this._policy.allowedTools) {
|
|
1604
|
+
tools = tools.filter(t => this._policy.allowedTools.includes(t.name))
|
|
1605
|
+
}
|
|
1606
|
+
if (this._policy.deniedTools) {
|
|
1607
|
+
tools = tools.filter(t => !this._policy.deniedTools.includes(t.name))
|
|
1608
|
+
}
|
|
2777
1609
|
}
|
|
2778
1610
|
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
:
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
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}`
|
|
1611
|
+
return new ComposedAgent({
|
|
1612
|
+
name: this._name,
|
|
1613
|
+
tools: tools,
|
|
1614
|
+
planner: this._planner,
|
|
1615
|
+
sandbox: this._sandbox,
|
|
1616
|
+
hooks: this._hooks,
|
|
1617
|
+
policy: this._policy
|
|
1618
|
+
})
|
|
2800
1619
|
}
|
|
2801
1620
|
}
|
|
2802
1621
|
|
|
2803
|
-
|
|
2804
|
-
|
|
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}`)
|
|
1622
|
+
// ============================================================================
|
|
1623
|
+
// EXPORTS
|
|
1624
|
+
// ============================================================================
|
|
2895
1625
|
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
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
|
-
}
|
|
1626
|
+
module.exports = {
|
|
1627
|
+
// Main Agent
|
|
1628
|
+
HyperMindAgent,
|
|
2910
1629
|
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
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
|
-
}
|
|
1630
|
+
// Builder Pattern
|
|
1631
|
+
AgentBuilder,
|
|
1632
|
+
ComposedAgent,
|
|
2928
1633
|
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
1634
|
+
// Supporting Classes
|
|
1635
|
+
MemoryManager,
|
|
1636
|
+
DatalogRuleSet,
|
|
1637
|
+
WasmSandbox,
|
|
1638
|
+
ExecutionTrace,
|
|
1639
|
+
ProofNode,
|
|
2935
1640
|
|
|
2936
|
-
|
|
2937
|
-
|
|
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
|
-
}
|
|
1641
|
+
// Type System
|
|
1642
|
+
TypeId,
|
|
2961
1643
|
|
|
2962
|
-
|
|
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
|
-
}
|
|
1644
|
+
// Agent State Machine
|
|
1645
|
+
AgentState,
|
|
1646
|
+
AgentRuntime,
|
|
3027
1647
|
|
|
3028
|
-
|
|
3029
|
-
|
|
1648
|
+
// Memory Tiers
|
|
1649
|
+
WorkingMemory,
|
|
1650
|
+
EpisodicMemory,
|
|
1651
|
+
LongTermMemory,
|
|
3030
1652
|
|
|
3031
|
-
//
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
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
|
|
1653
|
+
// Governance Layer
|
|
1654
|
+
GovernancePolicy,
|
|
1655
|
+
GovernanceEngine,
|
|
1656
|
+
|
|
1657
|
+
// Scope Layer
|
|
1658
|
+
AgentScope
|
|
3064
1659
|
}
|