rust-kgdb 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -23
- 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 +1003 -2347
- package/index.d.ts +79 -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
|
-
name: 'kg.sparql.update',
|
|
97
|
-
input: TypeId.String,
|
|
98
|
-
output: TypeId.Unit,
|
|
99
|
-
description: 'Execute SPARQL UPDATE operations',
|
|
100
|
-
domain: 'knowledge_graph',
|
|
101
|
-
constraints: { readOnly: false }
|
|
102
|
-
},
|
|
103
|
-
'kg.motif.find': {
|
|
104
|
-
name: 'kg.motif.find',
|
|
105
|
-
input: TypeId.String,
|
|
106
|
-
output: TypeId.List('Match'),
|
|
107
|
-
description: 'Find graph motif patterns (fraud rings, cycles)',
|
|
108
|
-
domain: 'graph_analytics',
|
|
109
|
-
patterns: {
|
|
110
|
-
'collusion': '(a)-[claims_with]->(p); (b)-[claims_with]->(p); (a)-[knows]->(b)',
|
|
111
|
-
'circular_payment': '(a)-[paid]->(b); (b)-[paid]->(c); (c)-[paid]->(a)',
|
|
112
|
-
'star_pattern': '(center)-[]->(a); (center)-[]->(b); (center)-[]->(c)'
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
'kg.datalog.infer': {
|
|
116
|
-
name: 'kg.datalog.infer',
|
|
117
|
-
input: TypeId.List('Rule'),
|
|
118
|
-
output: TypeId.List('Fact'),
|
|
119
|
-
description: 'Apply Datalog rules for logical inference',
|
|
120
|
-
domain: 'reasoning',
|
|
121
|
-
prebuiltRules: {
|
|
122
|
-
'potential_collusion': 'Claimants who know each other using same provider',
|
|
123
|
-
'staged_accident': 'Soft tissue claims with high amounts in first 90 days',
|
|
124
|
-
'organized_fraud': 'High prior claims + high provider volume'
|
|
73
|
+
class ProofNode {
|
|
74
|
+
constructor(type, value, justification, children = []) {
|
|
75
|
+
this.id = crypto.randomUUID()
|
|
76
|
+
this.type = type // 'axiom' | 'sparql' | 'rule' | 'inference' | 'embedding'
|
|
77
|
+
this.value = value
|
|
78
|
+
this.justification = justification
|
|
79
|
+
this.children = children // DAG structure
|
|
80
|
+
this.timestamp = new Date().toISOString()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toDAG() {
|
|
84
|
+
return {
|
|
85
|
+
id: this.id,
|
|
86
|
+
type: this.type,
|
|
87
|
+
value: this.value,
|
|
88
|
+
justification: this.justification,
|
|
89
|
+
children: this.children.map(c => c.toDAG()),
|
|
90
|
+
timestamp: this.timestamp
|
|
125
91
|
}
|
|
126
|
-
},
|
|
127
|
-
'kg.embeddings.search': {
|
|
128
|
-
name: 'kg.embeddings.search',
|
|
129
|
-
input: TypeId.String,
|
|
130
|
-
output: TypeId.List('SimilarEntity'),
|
|
131
|
-
description: 'Find semantically similar entities via HNSW index',
|
|
132
|
-
domain: 'embeddings',
|
|
133
|
-
constraints: { maxK: 100, minThreshold: 0.0, maxThreshold: 1.0 }
|
|
134
|
-
},
|
|
135
|
-
'kg.graphframe.pagerank': {
|
|
136
|
-
name: 'kg.graphframe.pagerank',
|
|
137
|
-
input: TypeId.Map('String', 'Float64'),
|
|
138
|
-
output: TypeId.Map('String', 'Float64'),
|
|
139
|
-
description: 'Compute PageRank to find central entities',
|
|
140
|
-
domain: 'graph_analytics'
|
|
141
|
-
},
|
|
142
|
-
'kg.graphframe.triangles': {
|
|
143
|
-
name: 'kg.graphframe.triangles',
|
|
144
|
-
input: TypeId.String,
|
|
145
|
-
output: TypeId.Int64,
|
|
146
|
-
description: 'Count triangles in graph (fraud ring indicator)',
|
|
147
|
-
domain: 'graph_analytics'
|
|
148
|
-
},
|
|
149
|
-
'kg.graphframe.components': {
|
|
150
|
-
name: 'kg.graphframe.components',
|
|
151
|
-
input: TypeId.String,
|
|
152
|
-
output: TypeId.Map('String', 'Int64'),
|
|
153
|
-
description: 'Find connected components (network clusters)',
|
|
154
|
-
domain: 'graph_analytics'
|
|
155
92
|
}
|
|
156
93
|
}
|
|
157
94
|
|
|
158
95
|
// ============================================================================
|
|
159
|
-
//
|
|
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
274
|
}
|
|
383
275
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
*/
|
|
387
|
-
createObjectProxy(tools) {
|
|
388
|
-
const sandbox = this
|
|
389
|
-
|
|
390
|
-
return new Proxy({}, {
|
|
391
|
-
get(target, toolName) {
|
|
392
|
-
return async (args) => {
|
|
393
|
-
// Security Check 1: Capability verification
|
|
394
|
-
if (!sandbox.hasCapability('ExecuteTool')) {
|
|
395
|
-
const error = `BLOCKED: Missing ExecuteTool capability`
|
|
396
|
-
sandbox._logAudit(toolName, args, null, 'DENIED', error)
|
|
397
|
-
throw new Error(error)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Security Check 2: Fuel metering
|
|
401
|
-
const fuelCost = sandbox._calculateFuelCost(toolName, args)
|
|
402
|
-
if (sandbox.fuel < fuelCost) {
|
|
403
|
-
const error = `BLOCKED: Insufficient fuel (need ${fuelCost}, have ${sandbox.fuel})`
|
|
404
|
-
sandbox._logAudit(toolName, args, null, 'DENIED', error)
|
|
405
|
-
throw new Error(error)
|
|
406
|
-
}
|
|
407
|
-
sandbox.fuel -= fuelCost
|
|
408
|
-
|
|
409
|
-
// Security Check 3: Memory bounds
|
|
410
|
-
const estimatedMemory = JSON.stringify(args).length * 2
|
|
411
|
-
if (sandbox.memoryUsed + estimatedMemory > sandbox.config.maxMemory) {
|
|
412
|
-
const error = `BLOCKED: Memory limit exceeded`
|
|
413
|
-
sandbox._logAudit(toolName, args, null, 'DENIED', error)
|
|
414
|
-
throw new Error(error)
|
|
415
|
-
}
|
|
416
|
-
sandbox.memoryUsed += estimatedMemory
|
|
417
|
-
|
|
418
|
-
// Security Check 4: Execution time
|
|
419
|
-
if (!sandbox.startTime) {
|
|
420
|
-
sandbox.startTime = Date.now()
|
|
421
|
-
}
|
|
422
|
-
if (Date.now() - sandbox.startTime > sandbox.config.maxExecTime) {
|
|
423
|
-
const error = `BLOCKED: Execution time limit exceeded`
|
|
424
|
-
sandbox._logAudit(toolName, args, null, 'DENIED', error)
|
|
425
|
-
throw new Error(error)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Execute tool
|
|
429
|
-
const tool = tools[toolName]
|
|
430
|
-
if (!tool) {
|
|
431
|
-
const error = `BLOCKED: Unknown tool ${toolName}`
|
|
432
|
-
sandbox._logAudit(toolName, args, null, 'DENIED', error)
|
|
433
|
-
throw new Error(error)
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const result = tool.execute ? await tool.execute(args) : { status: 'success' }
|
|
437
|
-
sandbox._logAudit(toolName, args, result, 'OK')
|
|
438
|
-
|
|
439
|
-
return result
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
})
|
|
276
|
+
hasCapability(cap) {
|
|
277
|
+
return this.capabilities.has(cap)
|
|
443
278
|
}
|
|
444
279
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
_calculateFuelCost(toolName, args) {
|
|
450
|
-
const baseCosts = {
|
|
451
|
-
'kg.sparql.query': 500,
|
|
452
|
-
'kg.motif.find': 1000,
|
|
453
|
-
'kg.datalog.infer': 2000,
|
|
454
|
-
'kg.embeddings.search': 300,
|
|
455
|
-
'kg.graphframe.pagerank': 5000,
|
|
456
|
-
'kg.graphframe.triangles': 1500,
|
|
457
|
-
'kg.graphframe.components': 2000
|
|
280
|
+
consumeFuel(amount) {
|
|
281
|
+
if (this.fuel < amount) {
|
|
282
|
+
throw new Error(`Insufficient fuel: need ${amount}, have ${this.fuel}`)
|
|
458
283
|
}
|
|
459
|
-
|
|
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,2276 +297,1109 @@ class WasmSandbox {
|
|
|
474
297
|
getAuditLog() {
|
|
475
298
|
return this.auditLog
|
|
476
299
|
}
|
|
477
|
-
|
|
478
|
-
getMetrics() {
|
|
479
|
-
return {
|
|
480
|
-
fuel_initial: this.config.fuelLimit,
|
|
481
|
-
fuel_remaining: this.fuel,
|
|
482
|
-
fuel_consumed: this.config.fuelLimit - this.fuel,
|
|
483
|
-
memory_used: this.memoryUsed,
|
|
484
|
-
memory_limit: this.config.maxMemory,
|
|
485
|
-
capabilities: this.config.capabilities,
|
|
486
|
-
tool_calls: this.auditLog.length
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
300
|
}
|
|
490
301
|
|
|
491
302
|
// ============================================================================
|
|
492
|
-
// MEMORY
|
|
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.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
recency: 0.3, // α - time decay
|
|
538
|
-
relevance: 0.5, // β - semantic similarity
|
|
539
|
-
importance: 0.2 // γ - access frequency
|
|
540
|
-
},
|
|
541
|
-
recencyDecayRate: config.recencyDecayRate || 0.995 // per hour
|
|
309
|
+
class MemoryManager {
|
|
310
|
+
constructor(kg, embeddingService) {
|
|
311
|
+
this.kg = kg
|
|
312
|
+
this.embeddingService = embeddingService
|
|
313
|
+
this.episodes = []
|
|
314
|
+
this.decayRate = 0.995 // Per hour
|
|
315
|
+
this.weights = { recency: 0.3, relevance: 0.5, importance: 0.2 }
|
|
316
|
+
this.workingMemory = new Map()
|
|
317
|
+
this.executions = []
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async store(prompt, result, success, durationMs) {
|
|
321
|
+
const episode = {
|
|
322
|
+
id: crypto.randomUUID(),
|
|
323
|
+
prompt,
|
|
324
|
+
result,
|
|
325
|
+
success,
|
|
326
|
+
durationMs,
|
|
327
|
+
timestamp: new Date().toISOString(),
|
|
328
|
+
accessCount: 0,
|
|
329
|
+
embedding: null
|
|
542
330
|
}
|
|
543
331
|
|
|
544
|
-
//
|
|
545
|
-
this.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
totalExecutions: 0,
|
|
552
|
-
successfulExecutions: 0,
|
|
553
|
-
failedExecutions: 0,
|
|
554
|
-
totalMemoryRetrievals: 0,
|
|
555
|
-
avgExecutionTimeMs: 0
|
|
332
|
+
// Generate embedding for semantic retrieval
|
|
333
|
+
if (this.embeddingService) {
|
|
334
|
+
try {
|
|
335
|
+
episode.embedding = await this._getEmbedding(prompt)
|
|
336
|
+
} catch (err) {
|
|
337
|
+
// Continue without embedding
|
|
338
|
+
}
|
|
556
339
|
}
|
|
557
|
-
}
|
|
558
340
|
|
|
559
|
-
|
|
560
|
-
* Transition agent to a new state
|
|
561
|
-
*/
|
|
562
|
-
transitionTo(newState, reason = '') {
|
|
563
|
-
const validTransitions = {
|
|
564
|
-
[AgentState.CREATED]: [AgentState.READY],
|
|
565
|
-
[AgentState.READY]: [AgentState.RUNNING, AgentState.PAUSED],
|
|
566
|
-
[AgentState.RUNNING]: [AgentState.READY, AgentState.COMPLETED, AgentState.FAILED, AgentState.PAUSED],
|
|
567
|
-
[AgentState.PAUSED]: [AgentState.READY, AgentState.RUNNING],
|
|
568
|
-
[AgentState.COMPLETED]: [AgentState.READY],
|
|
569
|
-
[AgentState.FAILED]: [AgentState.READY]
|
|
570
|
-
}
|
|
341
|
+
this.episodes.push(episode)
|
|
571
342
|
|
|
572
|
-
|
|
573
|
-
|
|
343
|
+
// Also store in KG as RDF
|
|
344
|
+
if (this.kg) {
|
|
345
|
+
try {
|
|
346
|
+
const ttl = this._episodeToTurtle(episode)
|
|
347
|
+
this.kg.loadTtl(ttl, 'http://hypermind.ai/memory/')
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// Continue without KG storage
|
|
350
|
+
}
|
|
574
351
|
}
|
|
575
352
|
|
|
576
|
-
|
|
577
|
-
this.stateHistory.push({
|
|
578
|
-
state: newState,
|
|
579
|
-
timestamp: new Date().toISOString(),
|
|
580
|
-
reason
|
|
581
|
-
})
|
|
582
|
-
this.lastActiveAt = new Date().toISOString()
|
|
583
|
-
|
|
584
|
-
return this
|
|
353
|
+
return episode
|
|
585
354
|
}
|
|
586
355
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
*/
|
|
590
|
-
ready() {
|
|
591
|
-
return this.transitionTo(AgentState.READY, 'Agent initialized and ready')
|
|
592
|
-
}
|
|
356
|
+
async retrieve(query, limit = 10) {
|
|
357
|
+
if (!this.episodes.length) return []
|
|
593
358
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
359
|
+
const queryEmbedding = this.embeddingService
|
|
360
|
+
? await this._getEmbedding(query)
|
|
361
|
+
: null
|
|
362
|
+
|
|
363
|
+
const scored = this.episodes.map(ep => ({
|
|
364
|
+
episode: ep,
|
|
365
|
+
score: this._calculateScore(ep, query, queryEmbedding)
|
|
366
|
+
}))
|
|
367
|
+
|
|
368
|
+
return scored
|
|
369
|
+
.sort((a, b) => b.score - a.score)
|
|
370
|
+
.slice(0, limit)
|
|
371
|
+
.map(s => {
|
|
372
|
+
s.episode.accessCount++
|
|
373
|
+
return s
|
|
374
|
+
})
|
|
607
375
|
}
|
|
608
376
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
this.currentExecution.durationMs = this.currentExecution.endTime - this.currentExecution.startTime
|
|
617
|
-
this.currentExecution.result = result
|
|
618
|
-
this.currentExecution.success = success
|
|
619
|
-
|
|
620
|
-
// Update metrics
|
|
621
|
-
this.metrics.totalExecutions++
|
|
622
|
-
if (success) {
|
|
623
|
-
this.metrics.successfulExecutions++
|
|
377
|
+
_calculateScore(episode, query, queryEmbedding) {
|
|
378
|
+
const hoursAgo = (Date.now() - new Date(episode.timestamp).getTime()) / 3600000
|
|
379
|
+
const recency = Math.pow(this.decayRate, hoursAgo)
|
|
380
|
+
|
|
381
|
+
let relevance = 0
|
|
382
|
+
if (queryEmbedding && episode.embedding) {
|
|
383
|
+
relevance = this._cosineSimilarity(queryEmbedding, episode.embedding)
|
|
624
384
|
} else {
|
|
625
|
-
|
|
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
|
-
|
|
726
|
-
/**
|
|
727
|
-
* Store tool result
|
|
728
|
-
*/
|
|
729
|
-
storeToolResult(toolName, args, result) {
|
|
730
|
-
this.toolResults.push({
|
|
731
|
-
tool: toolName,
|
|
732
|
-
args,
|
|
733
|
-
result,
|
|
734
|
-
timestamp: Date.now()
|
|
735
|
-
})
|
|
736
449
|
|
|
737
|
-
//
|
|
738
|
-
if (this.
|
|
739
|
-
|
|
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
|
|
522
|
+
*
|
|
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
|
+
* })
|
|
806
538
|
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
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 })
|
|
848
583
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
stored: true
|
|
853
|
-
}
|
|
854
|
-
}
|
|
584
|
+
// 3. Generate typed execution plan
|
|
585
|
+
const plan = this._generatePlan(intent, prompt)
|
|
586
|
+
trace.addStep({ type: 'execution_plan', plan })
|
|
855
587
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
*/
|
|
859
|
-
async storeToolInvocation(episodeId, toolName, args, result, success) {
|
|
860
|
-
const invocationUri = `${this.namespace}invocation/${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
861
|
-
|
|
862
|
-
const triples = `
|
|
863
|
-
@prefix ep: <${this.namespace}> .
|
|
864
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
865
|
-
|
|
866
|
-
<${invocationUri}> a ep:ToolInvocation ;
|
|
867
|
-
ep:episode <${this.namespace}episode/${episodeId}> ;
|
|
868
|
-
ep:tool "${toolName}" ;
|
|
869
|
-
ep:timestamp "${new Date().toISOString()}"^^xsd:dateTime ;
|
|
870
|
-
ep:success "${success}"^^xsd:boolean .
|
|
871
|
-
`
|
|
588
|
+
// 4. Execute plan in WASM sandbox
|
|
589
|
+
const results = await this._executePlan(plan, trace)
|
|
872
590
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
591
|
+
// 5. Apply Datalog rules for inference
|
|
592
|
+
const inferences = await this._applyRules(intent, results, trace)
|
|
876
593
|
|
|
877
|
-
|
|
878
|
-
|
|
594
|
+
// 6. Build proof DAG (explainable AI)
|
|
595
|
+
const proofRoot = this._buildProofDAG(plan, results, inferences, trace)
|
|
596
|
+
trace.addProof(proofRoot)
|
|
879
597
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
*/
|
|
883
|
-
async getEpisodes(agentId, options = {}) {
|
|
884
|
-
const limit = options.limit || 20
|
|
885
|
-
const since = options.since || null
|
|
886
|
-
|
|
887
|
-
let query = `
|
|
888
|
-
PREFIX ep: <${this.namespace}>
|
|
889
|
-
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
890
|
-
|
|
891
|
-
SELECT ?episode ?prompt ?timestamp ?durationMs ?success
|
|
892
|
-
WHERE {
|
|
893
|
-
?episode a ep:Episode ;
|
|
894
|
-
ep:agentId "${agentId}" ;
|
|
895
|
-
ep:prompt ?prompt ;
|
|
896
|
-
ep:timestamp ?timestamp ;
|
|
897
|
-
ep:durationMs ?durationMs ;
|
|
898
|
-
ep:success ?success .
|
|
899
|
-
${since ? `FILTER(?timestamp > "${since}"^^xsd:dateTime)` : ''}
|
|
900
|
-
}
|
|
901
|
-
ORDER BY DESC(?timestamp)
|
|
902
|
-
LIMIT ${limit}
|
|
903
|
-
`
|
|
598
|
+
// 7. Format answer
|
|
599
|
+
const answer = this._formatAnswer(results, inferences, intent)
|
|
904
600
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
}
|
|
601
|
+
// 8. Store episode in memory
|
|
602
|
+
const startTime = trace.startTime
|
|
603
|
+
await this.memory.store(prompt, answer, true, Date.now() - startTime)
|
|
920
604
|
|
|
921
|
-
return
|
|
605
|
+
return {
|
|
606
|
+
answer,
|
|
607
|
+
explanation: trace.toExplainableOutput(),
|
|
608
|
+
raw_results: results,
|
|
609
|
+
inferences,
|
|
610
|
+
proof: proofRoot.toDAG()
|
|
611
|
+
}
|
|
922
612
|
}
|
|
923
613
|
|
|
924
614
|
/**
|
|
925
|
-
*
|
|
615
|
+
* Configure Datalog rules
|
|
926
616
|
*/
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
617
|
+
addRule(name, rule) {
|
|
618
|
+
this.rules.addRule(name, rule)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
removeRule(name) {
|
|
622
|
+
return this.rules.removeRule(name)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
getRules() {
|
|
626
|
+
return this.rules.getAllRules()
|
|
931
627
|
}
|
|
932
628
|
|
|
933
629
|
/**
|
|
934
|
-
*
|
|
630
|
+
* Get audit log from sandbox
|
|
935
631
|
*/
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
return Math.pow(decayRate, hoursElapsed)
|
|
632
|
+
getAuditLog() {
|
|
633
|
+
return this.sandbox.getAuditLog()
|
|
939
634
|
}
|
|
940
635
|
|
|
941
636
|
/**
|
|
942
|
-
*
|
|
637
|
+
* Get agent name
|
|
943
638
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
return str
|
|
947
|
-
.replace(/\\/g, '\\\\')
|
|
948
|
-
.replace(/"/g, '\\"')
|
|
949
|
-
.replace(/\n/g, '\\n')
|
|
950
|
-
.replace(/\r/g, '\\r')
|
|
951
|
-
.replace(/\t/g, '\\t')
|
|
639
|
+
getName() {
|
|
640
|
+
return this.name
|
|
952
641
|
}
|
|
953
642
|
|
|
954
643
|
/**
|
|
955
|
-
* Get
|
|
644
|
+
* Get model name (defaults to 'mock' if no API key)
|
|
956
645
|
*/
|
|
957
|
-
|
|
958
|
-
return this.
|
|
646
|
+
getModel() {
|
|
647
|
+
return this.apiKey ? 'configured' : 'mock'
|
|
959
648
|
}
|
|
960
|
-
}
|
|
961
649
|
|
|
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)
|
|
684
|
+
]
|
|
1011
685
|
}
|
|
1012
686
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
*/
|
|
1016
|
-
async getEntitiesByType(typeUri, limit = 100) {
|
|
1017
|
-
const query = `
|
|
1018
|
-
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
1019
|
-
SELECT ?entity WHERE {
|
|
1020
|
-
?entity rdf:type <${typeUri}> .
|
|
1021
|
-
} LIMIT ${limit}
|
|
1022
|
-
`
|
|
1023
|
-
return this.querySource(query)
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Get related entities (1-hop neighbors)
|
|
1028
|
-
*/
|
|
1029
|
-
async getRelatedEntities(entityUri, direction = 'both') {
|
|
1030
|
-
let query
|
|
1031
|
-
if (direction === 'out') {
|
|
1032
|
-
query = `SELECT ?p ?target WHERE { <${entityUri}> ?p ?target . FILTER(isIRI(?target)) }`
|
|
1033
|
-
} else if (direction === 'in') {
|
|
1034
|
-
query = `SELECT ?source ?p WHERE { ?source ?p <${entityUri}> . FILTER(isIRI(?source)) }`
|
|
1035
|
-
} else {
|
|
1036
|
-
query = `
|
|
1037
|
-
SELECT ?direction ?p ?entity WHERE {
|
|
1038
|
-
{ <${entityUri}> ?p ?entity . BIND("out" AS ?direction) FILTER(isIRI(?entity)) }
|
|
1039
|
-
UNION
|
|
1040
|
-
{ ?entity ?p <${entityUri}> . BIND("in" AS ?direction) FILTER(isIRI(?entity)) }
|
|
1041
|
-
}
|
|
1042
|
-
`
|
|
1043
|
-
}
|
|
1044
|
-
return this.querySource(query)
|
|
1045
|
-
}
|
|
687
|
+
_classifyIntent(prompt) {
|
|
688
|
+
const lowerPrompt = prompt.toLowerCase()
|
|
1046
689
|
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
})
|
|
1081
|
-
|
|
1082
|
-
// Keep only recent access log entries
|
|
1083
|
-
if (this.accessLog.length > 1000) {
|
|
1084
|
-
this.accessLog = this.accessLog.slice(-500)
|
|
1085
698
|
}
|
|
1086
|
-
}
|
|
1087
699
|
|
|
1088
|
-
/**
|
|
1089
|
-
* Get access log
|
|
1090
|
-
*/
|
|
1091
|
-
getAccessLog(limit = 100) {
|
|
1092
|
-
return this.accessLog.slice(-limit)
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
/**
|
|
1096
|
-
* Get graph URIs
|
|
1097
|
-
*/
|
|
1098
|
-
getGraphUris() {
|
|
1099
700
|
return {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/**
|
|
1108
|
-
* MemoryManager - Unified memory retrieval with weighted scoring
|
|
1109
|
-
*
|
|
1110
|
-
* Orchestrates all memory layers with intelligent retrieval:
|
|
1111
|
-
* - Score = α × Recency + β × Relevance + γ × Importance
|
|
1112
|
-
* - α = 0.3 (time decay), β = 0.5 (semantic similarity), γ = 0.2 (access frequency)
|
|
1113
|
-
*/
|
|
1114
|
-
class MemoryManager {
|
|
1115
|
-
constructor(runtime, config = {}) {
|
|
1116
|
-
this.runtime = runtime
|
|
1117
|
-
|
|
1118
|
-
// Initialize memory layers
|
|
1119
|
-
this.working = new WorkingMemory(config.workingCapacity || 100)
|
|
1120
|
-
this.episodic = new EpisodicMemory(config.graphDb, config.episodicNamespace)
|
|
1121
|
-
this.longTerm = new LongTermMemory(config.graphDb, {
|
|
1122
|
-
sourceGraphUri: config.sourceGraphUri,
|
|
1123
|
-
agentGraphUri: config.agentGraphUri
|
|
1124
|
-
})
|
|
1125
|
-
|
|
1126
|
-
// Retrieval weights
|
|
1127
|
-
this.weights = config.weights || {
|
|
1128
|
-
recency: 0.3,
|
|
1129
|
-
relevance: 0.5,
|
|
1130
|
-
importance: 0.2
|
|
701
|
+
type: 'general_query',
|
|
702
|
+
tools: ['kg.sparql.query'],
|
|
703
|
+
confidence: 0.85
|
|
1131
704
|
}
|
|
1132
|
-
|
|
1133
|
-
// Access tracking for importance scoring
|
|
1134
|
-
this.accessCounts = new Map()
|
|
1135
|
-
this.retrievalHistory = []
|
|
1136
705
|
}
|
|
1137
706
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
episodic: [],
|
|
1145
|
-
longTerm: [],
|
|
1146
|
-
combined: []
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// 1. Working Memory (always fast, in-memory)
|
|
1150
|
-
results.working = this._searchWorkingMemory(query)
|
|
1151
|
-
|
|
1152
|
-
// 2. Episodic Memory (past executions)
|
|
1153
|
-
if (options.includeEpisodic !== false) {
|
|
1154
|
-
const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
|
|
1155
|
-
results.episodic = this._scoreEpisodicResults(episodes, query)
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// 3. Long-term Memory (source of truth)
|
|
1159
|
-
if (options.includeLongTerm !== false && options.entityUri) {
|
|
1160
|
-
const facts = await this.longTerm.getEntityFacts(options.entityUri)
|
|
1161
|
-
results.longTerm = facts.map(f => ({
|
|
1162
|
-
...f,
|
|
1163
|
-
score: 0.8, // Facts from source of truth have high base score
|
|
1164
|
-
source: 'longTerm'
|
|
1165
|
-
}))
|
|
1166
|
-
}
|
|
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
|
-
}
|
|
1181
|
-
|
|
1182
|
-
/**
|
|
1183
|
-
* Search working memory
|
|
1184
|
-
*/
|
|
1185
|
-
_searchWorkingMemory(query) {
|
|
1186
|
-
const results = []
|
|
1187
|
-
const queryLower = query.toLowerCase()
|
|
1188
|
-
|
|
1189
|
-
// Search context
|
|
1190
|
-
for (const ctx of this.working.context) {
|
|
1191
|
-
const content = JSON.stringify(ctx).toLowerCase()
|
|
1192
|
-
if (content.includes(queryLower)) {
|
|
1193
|
-
results.push({
|
|
1194
|
-
type: 'context',
|
|
1195
|
-
data: ctx,
|
|
1196
|
-
score: this._calculateScore(ctx.timestamp, 0.8, this._getAccessCount(ctx.id))
|
|
1197
|
-
})
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// Search tool results
|
|
1202
|
-
for (const tr of this.working.toolResults) {
|
|
1203
|
-
if (tr.tool.toLowerCase().includes(queryLower) ||
|
|
1204
|
-
JSON.stringify(tr.result).toLowerCase().includes(queryLower)) {
|
|
1205
|
-
results.push({
|
|
1206
|
-
type: 'toolResult',
|
|
1207
|
-
data: tr,
|
|
1208
|
-
score: this._calculateScore(tr.timestamp, 0.7, 1)
|
|
1209
|
-
})
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
return results.sort((a, b) => b.score - a.score)
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
/**
|
|
1217
|
-
* Score episodic results
|
|
1218
|
-
*/
|
|
1219
|
-
_scoreEpisodicResults(episodes, query) {
|
|
1220
|
-
return episodes.map(ep => {
|
|
1221
|
-
const recencyScore = this.episodic.calculateRecencyScore(ep.timestamp)
|
|
1222
|
-
const relevanceScore = this._calculateRelevance(ep.prompt, query)
|
|
1223
|
-
const importanceScore = this._getAccessCount(`ep_${ep.episode}`) / 10
|
|
1224
|
-
|
|
1225
|
-
return {
|
|
1226
|
-
...ep,
|
|
1227
|
-
source: 'episodic',
|
|
1228
|
-
score: this._calculateScore(ep.timestamp, relevanceScore, importanceScore)
|
|
1229
|
-
}
|
|
1230
|
-
}).sort((a, b) => b.score - a.score)
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
/**
|
|
1234
|
-
* Calculate weighted score
|
|
1235
|
-
*/
|
|
1236
|
-
_calculateScore(timestamp, relevance, accessCount) {
|
|
1237
|
-
const recency = this.episodic.calculateRecencyScore(timestamp)
|
|
1238
|
-
const importance = Math.min(accessCount / 10, 1)
|
|
1239
|
-
|
|
1240
|
-
return (
|
|
1241
|
-
this.weights.recency * recency +
|
|
1242
|
-
this.weights.relevance * relevance +
|
|
1243
|
-
this.weights.importance * importance
|
|
1244
|
-
)
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
/**
|
|
1248
|
-
* Calculate text relevance (simple term overlap)
|
|
1249
|
-
*/
|
|
1250
|
-
_calculateRelevance(text1, text2) {
|
|
1251
|
-
if (!text1 || !text2) return 0
|
|
1252
|
-
const words1 = new Set(text1.toLowerCase().split(/\s+/))
|
|
1253
|
-
const words2 = new Set(text2.toLowerCase().split(/\s+/))
|
|
1254
|
-
const intersection = [...words1].filter(w => words2.has(w))
|
|
1255
|
-
return intersection.length / Math.max(words1.size, words2.size)
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Get access count for importance scoring
|
|
1260
|
-
*/
|
|
1261
|
-
_getAccessCount(id) {
|
|
1262
|
-
return this.accessCounts.get(id) || 0
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
/**
|
|
1266
|
-
* Increment access count
|
|
1267
|
-
*/
|
|
1268
|
-
_incrementAccess(id) {
|
|
1269
|
-
this.accessCounts.set(id, (this.accessCounts.get(id) || 0) + 1)
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
/**
|
|
1273
|
-
* Combine and rank results from all memory layers
|
|
1274
|
-
*/
|
|
1275
|
-
_combineAndRank(results) {
|
|
1276
|
-
return results
|
|
1277
|
-
.sort((a, b) => b.score - a.score)
|
|
1278
|
-
.slice(0, 20) // Top 20 results
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
/**
|
|
1282
|
-
* Track retrieval for analytics
|
|
1283
|
-
*/
|
|
1284
|
-
_trackRetrieval(query, results) {
|
|
1285
|
-
this.retrievalHistory.push({
|
|
1286
|
-
query,
|
|
1287
|
-
timestamp: Date.now(),
|
|
1288
|
-
counts: {
|
|
1289
|
-
working: results.working.length,
|
|
1290
|
-
episodic: results.episodic.length,
|
|
1291
|
-
longTerm: results.longTerm.length
|
|
1292
|
-
}
|
|
1293
|
-
})
|
|
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
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Store execution in episodic memory
|
|
1305
|
-
*/
|
|
1306
|
-
async storeExecution(execution) {
|
|
1307
|
-
return this.episodic.storeEpisode(this.runtime.id, execution)
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
/**
|
|
1311
|
-
* Add to working memory
|
|
1312
|
-
*/
|
|
1313
|
-
addToWorking(item) {
|
|
1314
|
-
this.working.addContext(item)
|
|
1315
|
-
return this
|
|
1316
|
-
}
|
|
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
|
+
}))
|
|
1317
713
|
|
|
1318
|
-
/**
|
|
1319
|
-
* Get memory statistics
|
|
1320
|
-
*/
|
|
1321
|
-
getStats() {
|
|
1322
714
|
return {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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
|
|
715
|
+
id: `plan_${Date.now()}`,
|
|
716
|
+
intent: intent.type,
|
|
717
|
+
steps,
|
|
718
|
+
type_chain: this._buildTypeChain(steps)
|
|
1339
719
|
}
|
|
1340
720
|
}
|
|
1341
721
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
* GovernancePolicy - Defines access control and behavioral policies
|
|
1357
|
-
*/
|
|
1358
|
-
class GovernancePolicy {
|
|
1359
|
-
constructor(config = {}) {
|
|
1360
|
-
this.name = config.name || 'default-policy'
|
|
1361
|
-
this.version = config.version || '1.0.0'
|
|
1362
|
-
|
|
1363
|
-
// Capability definitions
|
|
1364
|
-
this.capabilities = new Set(config.capabilities || [
|
|
1365
|
-
'ReadKG',
|
|
1366
|
-
'WriteKG',
|
|
1367
|
-
'ExecuteTool',
|
|
1368
|
-
'AccessMemory',
|
|
1369
|
-
'ModifyMemory'
|
|
1370
|
-
])
|
|
1371
|
-
|
|
1372
|
-
// Resource limits
|
|
1373
|
-
this.limits = config.limits || {
|
|
1374
|
-
maxExecutionTimeMs: 60000,
|
|
1375
|
-
maxMemoryMB: 256,
|
|
1376
|
-
maxToolCalls: 100,
|
|
1377
|
-
maxGraphQueries: 1000
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// Audit requirements
|
|
1381
|
-
this.audit = config.audit || {
|
|
1382
|
-
logAllToolCalls: true,
|
|
1383
|
-
logMemoryAccess: true,
|
|
1384
|
-
logGraphQueries: true,
|
|
1385
|
-
retentionDays: 90
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// Behavioral constraints
|
|
1389
|
-
this.constraints = config.constraints || {
|
|
1390
|
-
allowExternalApi: false,
|
|
1391
|
-
allowFileAccess: false,
|
|
1392
|
-
requireApprovalForWrites: false
|
|
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 {}
|
|
1393
736
|
}
|
|
1394
737
|
}
|
|
1395
738
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
this.capabilities.add(capability)
|
|
1408
|
-
return this
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
/**
|
|
1412
|
-
* Revoke a capability
|
|
1413
|
-
*/
|
|
1414
|
-
revokeCapability(capability) {
|
|
1415
|
-
this.capabilities.delete(capability)
|
|
1416
|
-
return this
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
/**
|
|
1420
|
-
* Check resource limit
|
|
1421
|
-
*/
|
|
1422
|
-
checkLimit(resource, current) {
|
|
1423
|
-
const limit = this.limits[resource]
|
|
1424
|
-
return limit === undefined || current <= limit
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/**
|
|
1428
|
-
* Export policy as JSON
|
|
1429
|
-
*/
|
|
1430
|
-
export() {
|
|
1431
|
-
return {
|
|
1432
|
-
name: this.name,
|
|
1433
|
-
version: this.version,
|
|
1434
|
-
capabilities: [...this.capabilities],
|
|
1435
|
-
limits: this.limits,
|
|
1436
|
-
audit: this.audit,
|
|
1437
|
-
constraints: this.constraints
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
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)
|
|
1440
750
|
}
|
|
751
|
+
ORDER BY DESC(?riskScore)
|
|
752
|
+
LIMIT 100`
|
|
1441
753
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
*/
|
|
1455
|
-
authorize(action, context = {}) {
|
|
1456
|
-
const result = {
|
|
1457
|
-
allowed: false,
|
|
1458
|
-
reason: '',
|
|
1459
|
-
capability: action.capability,
|
|
1460
|
-
timestamp: new Date().toISOString()
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Check capability
|
|
1464
|
-
if (action.capability && !this.policy.hasCapability(action.capability)) {
|
|
1465
|
-
result.reason = `Missing capability: ${action.capability}`
|
|
1466
|
-
this._logDenial(action, result.reason)
|
|
1467
|
-
return result
|
|
1468
|
-
}
|
|
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`
|
|
1469
766
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
if (!this.policy.checkLimit(action.resource, action.current)) {
|
|
1473
|
-
result.reason = `Resource limit exceeded: ${action.resource}`
|
|
1474
|
-
this._logDenial(action, result.reason)
|
|
1475
|
-
return result
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
767
|
+
case 'aggregate':
|
|
768
|
+
return `SELECT (COUNT(*) as ?count) WHERE { ?s ?p ?o }`
|
|
1478
769
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
result.reason = 'External API access not allowed'
|
|
1482
|
-
this._logDenial(action, result.reason)
|
|
1483
|
-
return result
|
|
770
|
+
default:
|
|
771
|
+
return `SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 100`
|
|
1484
772
|
}
|
|
1485
|
-
|
|
1486
|
-
result.allowed = true
|
|
1487
|
-
result.reason = 'Authorized'
|
|
1488
|
-
this._logAudit(action, result)
|
|
1489
|
-
|
|
1490
|
-
return result
|
|
1491
773
|
}
|
|
1492
774
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
action,
|
|
1502
|
-
result,
|
|
1503
|
-
timestamp: new Date().toISOString()
|
|
1504
|
-
})
|
|
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 []
|
|
1505
783
|
}
|
|
1506
784
|
}
|
|
1507
785
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
timestamp: new Date().toISOString()
|
|
1516
|
-
})
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
/**
|
|
1520
|
-
* Get audit log
|
|
1521
|
-
*/
|
|
1522
|
-
getAuditLog(limit = 100) {
|
|
1523
|
-
return this.auditLog.slice(-limit)
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
/**
|
|
1527
|
-
* Get denials
|
|
1528
|
-
*/
|
|
1529
|
-
getDenials(limit = 50) {
|
|
1530
|
-
return this.denials.slice(-limit)
|
|
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)'
|
|
792
|
+
}
|
|
1531
793
|
}
|
|
1532
794
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
*/
|
|
1536
|
-
setPolicy(policy) {
|
|
1537
|
-
this.policy = policy
|
|
1538
|
-
return this
|
|
795
|
+
_buildTypeChain(steps) {
|
|
796
|
+
return steps.map(s => s.tool).join(' -> ')
|
|
1539
797
|
}
|
|
1540
|
-
}
|
|
1541
798
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
// ============================================================================
|
|
799
|
+
async _executePlan(plan, trace) {
|
|
800
|
+
const results = []
|
|
1545
801
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
*/
|
|
1549
|
-
class AgentScope {
|
|
1550
|
-
constructor(config = {}) {
|
|
1551
|
-
this.id = config.id || `scope_${crypto.randomUUID()}`
|
|
1552
|
-
this.name = config.name || 'default-scope'
|
|
802
|
+
for (const step of plan.steps) {
|
|
803
|
+
this.sandbox.consumeFuel(100)
|
|
1553
804
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
graphUri: `http://hypermind.ai/agent/${this.id}/`,
|
|
1558
|
-
allowedGraphs: ['*'], // '*' means all, or list specific graph URIs
|
|
1559
|
-
deniedGraphs: []
|
|
1560
|
-
}
|
|
805
|
+
try {
|
|
806
|
+
const result = await this._executeTool(step.tool, step.args)
|
|
807
|
+
results.push({ step: step.id, tool: step.tool, result, success: true })
|
|
1561
808
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
maxExecutionTimeMs: config.maxExecutionTimeMs || 60000,
|
|
1566
|
-
maxToolCalls: config.maxToolCalls || 100,
|
|
1567
|
-
maxGraphQueries: config.maxGraphQueries || 1000,
|
|
1568
|
-
maxEpisodes: config.maxEpisodes || 10000
|
|
1569
|
-
}
|
|
809
|
+
if (step.tool === 'kg.sparql.query') {
|
|
810
|
+
trace.addSparql(step.args.query, result)
|
|
811
|
+
}
|
|
1570
812
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
graphQueries: 0,
|
|
1577
|
-
episodes: 0
|
|
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
|
+
}
|
|
1578
818
|
}
|
|
1579
819
|
|
|
1580
|
-
|
|
1581
|
-
this.isolation = {
|
|
1582
|
-
shareMemory: config.shareMemory || false,
|
|
1583
|
-
shareEpisodes: config.shareEpisodes || false,
|
|
1584
|
-
parentScope: config.parentScope || null
|
|
1585
|
-
}
|
|
820
|
+
return results
|
|
1586
821
|
}
|
|
1587
822
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
isGraphAllowed(graphUri) {
|
|
1592
|
-
// Check denied first
|
|
1593
|
-
if (this.namespace.deniedGraphs.includes(graphUri)) {
|
|
1594
|
-
return false
|
|
823
|
+
async _executeTool(tool, args) {
|
|
824
|
+
if (!this.sandbox.hasCapability('ExecuteTool')) {
|
|
825
|
+
throw new Error('Missing ExecuteTool capability')
|
|
1595
826
|
}
|
|
1596
827
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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' }
|
|
1600
841
|
}
|
|
1601
|
-
|
|
1602
|
-
return this.namespace.allowedGraphs.some(pattern => {
|
|
1603
|
-
if (pattern.endsWith('*')) {
|
|
1604
|
-
return graphUri.startsWith(pattern.slice(0, -1))
|
|
1605
|
-
}
|
|
1606
|
-
return graphUri === pattern
|
|
1607
|
-
})
|
|
1608
842
|
}
|
|
1609
843
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
trackUsage(resource, amount) {
|
|
1614
|
-
if (this.usage[resource] !== undefined) {
|
|
1615
|
-
this.usage[resource] += amount
|
|
844
|
+
_executeSparql(query) {
|
|
845
|
+
if (!this.kg || !this.kg.querySelect) {
|
|
846
|
+
return []
|
|
1616
847
|
}
|
|
1617
|
-
return this
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
/**
|
|
1621
|
-
* Check if resource limit exceeded
|
|
1622
|
-
*/
|
|
1623
|
-
isWithinLimits(resource) {
|
|
1624
|
-
const limit = this.resources[`max${resource.charAt(0).toUpperCase()}${resource.slice(1)}`]
|
|
1625
|
-
return limit === undefined || this.usage[resource] <= limit
|
|
1626
|
-
}
|
|
1627
848
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
for (const [key, limit] of Object.entries(this.resources)) {
|
|
1634
|
-
// Convert maxToolCalls -> toolCalls (preserve camelCase)
|
|
1635
|
-
const withoutMax = key.replace(/^max/, '')
|
|
1636
|
-
const usageKey = withoutMax.charAt(0).toLowerCase() + withoutMax.slice(1)
|
|
1637
|
-
remaining[usageKey] = limit - (this.usage[usageKey] || 0)
|
|
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 }
|
|
1638
854
|
}
|
|
1639
|
-
return remaining
|
|
1640
855
|
}
|
|
1641
856
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
+
}
|
|
1648
868
|
}
|
|
1649
|
-
return
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Get scoped graph URI for agent
|
|
1654
|
-
*/
|
|
1655
|
-
getScopedGraphUri(suffix = '') {
|
|
1656
|
-
return `${this.namespace.graphUri}${suffix}`
|
|
869
|
+
return results
|
|
1657
870
|
}
|
|
1658
871
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
export() {
|
|
1663
|
-
return {
|
|
1664
|
-
id: this.id,
|
|
1665
|
-
name: this.name,
|
|
1666
|
-
namespace: this.namespace,
|
|
1667
|
-
resources: this.resources,
|
|
1668
|
-
usage: this.usage,
|
|
1669
|
-
isolation: this.isolation
|
|
872
|
+
_executeEmbeddingSearch(args) {
|
|
873
|
+
if (!this.embeddings || !this.embeddings.search) {
|
|
874
|
+
return []
|
|
1670
875
|
}
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// ============================================================================
|
|
1675
|
-
// AGENT BUILDER (Fluent Composition Pattern)
|
|
1676
|
-
// ============================================================================
|
|
1677
|
-
|
|
1678
|
-
/**
|
|
1679
|
-
* ComposedAgent - Agent built from composed tools, planner, and sandbox
|
|
1680
|
-
*/
|
|
1681
|
-
class ComposedAgent {
|
|
1682
|
-
constructor(spec) {
|
|
1683
|
-
this.name = spec.name
|
|
1684
|
-
this.tools = spec.tools
|
|
1685
|
-
this.planner = spec.planner
|
|
1686
|
-
this.sandbox = spec.sandbox
|
|
1687
|
-
this.hooks = spec.hooks || []
|
|
1688
|
-
this.conversationHistory = []
|
|
1689
|
-
}
|
|
1690
876
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
877
|
+
try {
|
|
878
|
+
return this.embeddings.search(args.text, args.k, args.threshold)
|
|
879
|
+
} catch (err) {
|
|
880
|
+
return { error: err.message }
|
|
881
|
+
}
|
|
882
|
+
}
|
|
1697
883
|
|
|
1698
|
-
|
|
884
|
+
_executeMotifFind(args) {
|
|
885
|
+
// Motif finding would use GraphFrame.find()
|
|
886
|
+
return { pattern: args.pattern, matches: [] }
|
|
887
|
+
}
|
|
1699
888
|
|
|
1700
|
-
|
|
1701
|
-
|
|
889
|
+
_executeTriangleCount() {
|
|
890
|
+
// Triangle counting would use GraphFrame.triangleCount()
|
|
891
|
+
return { count: 0 }
|
|
892
|
+
}
|
|
1702
893
|
|
|
1703
|
-
|
|
1704
|
-
|
|
894
|
+
async _applyRules(intent, results, trace) {
|
|
895
|
+
const inferences = []
|
|
1705
896
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
this.
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
+
}
|
|
1713
910
|
}
|
|
1714
911
|
}
|
|
1715
912
|
|
|
1716
|
-
|
|
913
|
+
return inferences
|
|
914
|
+
}
|
|
1717
915
|
|
|
1718
|
-
|
|
916
|
+
_buildProofDAG(plan, results, inferences, trace) {
|
|
917
|
+
// Build proof tree showing how answer was derived
|
|
918
|
+
const children = []
|
|
1719
919
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
+
))
|
|
929
|
+
}
|
|
1726
930
|
}
|
|
1727
|
-
}
|
|
1728
931
|
|
|
1729
|
-
|
|
1730
|
-
for (const
|
|
1731
|
-
|
|
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
|
+
))
|
|
1732
940
|
}
|
|
941
|
+
|
|
942
|
+
return new ProofNode(
|
|
943
|
+
'inference',
|
|
944
|
+
{ plan: plan.id, intent: plan.intent },
|
|
945
|
+
`Derived answer via ${plan.steps.length} steps`,
|
|
946
|
+
children
|
|
947
|
+
)
|
|
1733
948
|
}
|
|
1734
949
|
|
|
1735
|
-
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
tool_calls: results.map(r => ({
|
|
1744
|
-
tool: r.step.tool,
|
|
1745
|
-
status: r.status
|
|
1746
|
-
}))
|
|
1747
|
-
},
|
|
1748
|
-
sandbox_metrics: this.sandbox.getMetrics(),
|
|
1749
|
-
audit_log: this.sandbox.getAuditLog()
|
|
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)
|
|
953
|
+
|
|
954
|
+
let answer = `Found ${totalResults} results`
|
|
955
|
+
|
|
956
|
+
if (inferences.length > 0) {
|
|
957
|
+
answer += ` using ${inferences.length} reasoning rules`
|
|
1750
958
|
}
|
|
1751
959
|
|
|
1752
|
-
|
|
1753
|
-
.
|
|
1754
|
-
|
|
1755
|
-
.digest('hex')
|
|
960
|
+
if (intent.type === 'detect_fraud' && totalResults > 0) {
|
|
961
|
+
answer = `Detected ${totalResults} potential fraud cases using ${inferences.length} detection rules`
|
|
962
|
+
}
|
|
1756
963
|
|
|
1757
|
-
return
|
|
964
|
+
return answer
|
|
1758
965
|
}
|
|
1759
966
|
}
|
|
1760
967
|
|
|
968
|
+
// ============================================================================
|
|
969
|
+
// AGENT STATE MACHINE
|
|
970
|
+
// ============================================================================
|
|
971
|
+
|
|
1761
972
|
/**
|
|
1762
|
-
*
|
|
973
|
+
* AgentState - State machine for agent lifecycle
|
|
1763
974
|
*/
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
975
|
+
const AgentState = {
|
|
976
|
+
IDLE: 'IDLE',
|
|
977
|
+
READY: 'READY',
|
|
978
|
+
PLANNING: 'PLANNING',
|
|
979
|
+
EXECUTING: 'EXECUTING',
|
|
980
|
+
WAITING: 'WAITING',
|
|
981
|
+
ERROR: 'ERROR',
|
|
982
|
+
TERMINATED: 'TERMINATED'
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// ============================================================================
|
|
986
|
+
// AGENT RUNTIME
|
|
987
|
+
// ============================================================================
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* AgentRuntime - Runtime context for agent execution
|
|
991
|
+
*/
|
|
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
|
|
1773
1005
|
}
|
|
1774
1006
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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]: []
|
|
1781
1016
|
}
|
|
1782
|
-
return this
|
|
1783
|
-
}
|
|
1784
1017
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
}
|
|
1018
|
+
if (!validTransitions[this.state]?.includes(newState)) {
|
|
1019
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`)
|
|
1020
|
+
}
|
|
1789
1021
|
|
|
1790
|
-
|
|
1791
|
-
this.
|
|
1022
|
+
this.state = newState
|
|
1023
|
+
this.stateHistory.push({ state: newState, timestamp: Date.now() })
|
|
1792
1024
|
return this
|
|
1793
1025
|
}
|
|
1794
1026
|
|
|
1795
|
-
|
|
1796
|
-
this.
|
|
1797
|
-
return this
|
|
1027
|
+
getStateHistory() {
|
|
1028
|
+
return this.stateHistory
|
|
1798
1029
|
}
|
|
1799
1030
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
}
|
|
1808
|
-
return mockResults[toolName] || { status: 'success', resultCount: 1 }
|
|
1031
|
+
startExecution(description) {
|
|
1032
|
+
const execId = crypto.randomUUID()
|
|
1033
|
+
this.currentExecution = {
|
|
1034
|
+
id: execId,
|
|
1035
|
+
description,
|
|
1036
|
+
startTime: Date.now(),
|
|
1037
|
+
steps: []
|
|
1809
1038
|
}
|
|
1039
|
+
this.state = AgentState.EXECUTING
|
|
1040
|
+
return execId
|
|
1810
1041
|
}
|
|
1811
1042
|
|
|
1812
|
-
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
1815
|
-
|
|
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
|
|
1816
1052
|
}
|
|
1817
1053
|
}
|
|
1818
1054
|
|
|
1819
1055
|
// ============================================================================
|
|
1820
|
-
//
|
|
1056
|
+
// MEMORY TIERS (Working, Episodic, Long-Term)
|
|
1821
1057
|
// ============================================================================
|
|
1822
1058
|
|
|
1823
|
-
// LUBM Benchmark Test Suite (12 questions)
|
|
1824
|
-
const LUBM_TEST_SUITE = [
|
|
1825
|
-
// Easy (3 tests)
|
|
1826
|
-
{
|
|
1827
|
-
index: 1,
|
|
1828
|
-
question: 'Find all professors in the university database',
|
|
1829
|
-
difficulty: 'easy',
|
|
1830
|
-
expectedPattern: 'Professor',
|
|
1831
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1832
|
-
SELECT ?x WHERE { ?x a ub:Professor }`
|
|
1833
|
-
},
|
|
1834
|
-
{
|
|
1835
|
-
index: 2,
|
|
1836
|
-
question: 'List all graduate students',
|
|
1837
|
-
difficulty: 'easy',
|
|
1838
|
-
expectedPattern: 'GraduateStudent',
|
|
1839
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1840
|
-
SELECT ?x WHERE { ?x a ub:GraduateStudent }`
|
|
1841
|
-
},
|
|
1842
|
-
{
|
|
1843
|
-
index: 3,
|
|
1844
|
-
question: 'How many courses are offered?',
|
|
1845
|
-
difficulty: 'easy',
|
|
1846
|
-
expectedPattern: 'COUNT',
|
|
1847
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1848
|
-
SELECT (COUNT(?x) AS ?count) WHERE { ?x a ub:Course }`
|
|
1849
|
-
},
|
|
1850
|
-
// Medium (5 tests)
|
|
1851
|
-
{
|
|
1852
|
-
index: 4,
|
|
1853
|
-
question: 'Find all students and their advisors',
|
|
1854
|
-
difficulty: 'medium',
|
|
1855
|
-
expectedPattern: 'advisor',
|
|
1856
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1857
|
-
SELECT ?student ?advisor WHERE { ?student ub:advisor ?advisor }`
|
|
1858
|
-
},
|
|
1859
|
-
{
|
|
1860
|
-
index: 5,
|
|
1861
|
-
question: 'List professors and the courses they teach',
|
|
1862
|
-
difficulty: 'medium',
|
|
1863
|
-
expectedPattern: 'teach',
|
|
1864
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1865
|
-
SELECT ?prof ?course WHERE { ?prof ub:teacherOf ?course }`
|
|
1866
|
-
},
|
|
1867
|
-
{
|
|
1868
|
-
index: 6,
|
|
1869
|
-
question: 'Find all departments and their parent universities',
|
|
1870
|
-
difficulty: 'medium',
|
|
1871
|
-
expectedPattern: 'subOrganization',
|
|
1872
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1873
|
-
SELECT ?dept ?univ WHERE { ?dept ub:subOrganizationOf ?univ }`
|
|
1874
|
-
},
|
|
1875
|
-
{
|
|
1876
|
-
index: 7,
|
|
1877
|
-
question: 'Count the number of students per department',
|
|
1878
|
-
difficulty: 'medium',
|
|
1879
|
-
expectedPattern: 'GROUP BY',
|
|
1880
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1881
|
-
SELECT ?dept (COUNT(?student) AS ?count) WHERE {
|
|
1882
|
-
?student ub:memberOf ?dept
|
|
1883
|
-
} GROUP BY ?dept`
|
|
1884
|
-
},
|
|
1885
|
-
{
|
|
1886
|
-
index: 8,
|
|
1887
|
-
question: 'Find the average credit hours for graduate courses',
|
|
1888
|
-
difficulty: 'medium',
|
|
1889
|
-
expectedPattern: 'AVG',
|
|
1890
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1891
|
-
SELECT (AVG(?credits) AS ?avg) WHERE {
|
|
1892
|
-
?course a ub:GraduateCourse .
|
|
1893
|
-
?course ub:creditHours ?credits
|
|
1894
|
-
}`
|
|
1895
|
-
},
|
|
1896
|
-
// Hard (4 tests)
|
|
1897
|
-
{
|
|
1898
|
-
index: 9,
|
|
1899
|
-
question: 'Find graduate students whose advisors have research interests in ML',
|
|
1900
|
-
difficulty: 'hard',
|
|
1901
|
-
expectedPattern: 'advisor',
|
|
1902
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1903
|
-
SELECT ?student WHERE {
|
|
1904
|
-
?student a ub:GraduateStudent .
|
|
1905
|
-
?student ub:advisor ?advisor .
|
|
1906
|
-
?advisor ub:researchInterest ?interest .
|
|
1907
|
-
FILTER(CONTAINS(STR(?interest), "ML"))
|
|
1908
|
-
}`
|
|
1909
|
-
},
|
|
1910
|
-
{
|
|
1911
|
-
index: 10,
|
|
1912
|
-
question: 'List publications with authors who are professors at California universities',
|
|
1913
|
-
difficulty: 'hard',
|
|
1914
|
-
expectedPattern: 'publicationAuthor',
|
|
1915
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1916
|
-
SELECT ?pub WHERE {
|
|
1917
|
-
?pub ub:publicationAuthor ?author .
|
|
1918
|
-
?author a ub:Professor .
|
|
1919
|
-
?author ub:worksFor ?dept .
|
|
1920
|
-
?dept ub:subOrganizationOf ?univ .
|
|
1921
|
-
FILTER(CONTAINS(STR(?univ), "California"))
|
|
1922
|
-
}`
|
|
1923
|
-
},
|
|
1924
|
-
{
|
|
1925
|
-
index: 11,
|
|
1926
|
-
question: 'Find students who take courses taught by professors in the same department',
|
|
1927
|
-
difficulty: 'hard',
|
|
1928
|
-
expectedPattern: 'memberOf',
|
|
1929
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1930
|
-
SELECT ?student ?course WHERE {
|
|
1931
|
-
?student ub:takesCourse ?course .
|
|
1932
|
-
?prof ub:teacherOf ?course .
|
|
1933
|
-
?student ub:memberOf ?dept .
|
|
1934
|
-
?prof ub:worksFor ?dept
|
|
1935
|
-
}`
|
|
1936
|
-
},
|
|
1937
|
-
{
|
|
1938
|
-
index: 12,
|
|
1939
|
-
question: 'Find pairs of students who share the same advisor and take common courses',
|
|
1940
|
-
difficulty: 'hard',
|
|
1941
|
-
expectedPattern: 'advisor',
|
|
1942
|
-
expectedSparql: `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
1943
|
-
SELECT ?s1 ?s2 ?course WHERE {
|
|
1944
|
-
?s1 ub:advisor ?advisor .
|
|
1945
|
-
?s2 ub:advisor ?advisor .
|
|
1946
|
-
?s1 ub:takesCourse ?course .
|
|
1947
|
-
?s2 ub:takesCourse ?course .
|
|
1948
|
-
FILTER(?s1 != ?s2)
|
|
1949
|
-
}`
|
|
1950
|
-
}
|
|
1951
|
-
]
|
|
1952
|
-
|
|
1953
|
-
// Typed tool definitions for the planner
|
|
1954
|
-
const HYPERMIND_TOOLS = [
|
|
1955
|
-
{
|
|
1956
|
-
id: 'kg.sparql.query',
|
|
1957
|
-
description: 'Execute SPARQL SELECT/CONSTRUCT/ASK queries against the knowledge graph',
|
|
1958
|
-
inputType: 'String',
|
|
1959
|
-
outputType: 'BindingSet',
|
|
1960
|
-
preconditions: ['Valid SPARQL syntax'],
|
|
1961
|
-
postconditions: ['Returns solution mappings']
|
|
1962
|
-
},
|
|
1963
|
-
{
|
|
1964
|
-
id: 'kg.sparql.update',
|
|
1965
|
-
description: 'Execute SPARQL UPDATE operations (INSERT/DELETE/LOAD/CLEAR)',
|
|
1966
|
-
inputType: 'String',
|
|
1967
|
-
outputType: 'Unit',
|
|
1968
|
-
preconditions: ['Valid SPARQL Update syntax'],
|
|
1969
|
-
postconditions: ['Graph modified']
|
|
1970
|
-
},
|
|
1971
|
-
{
|
|
1972
|
-
id: 'kg.motif.find',
|
|
1973
|
-
description: 'Find graph patterns using motif DSL: (a)-[e]->(b); (b)-[]->(c)',
|
|
1974
|
-
inputType: 'String',
|
|
1975
|
-
outputType: 'MatchSet',
|
|
1976
|
-
preconditions: ['Valid motif pattern'],
|
|
1977
|
-
postconditions: ['Returns matched subgraphs']
|
|
1978
|
-
},
|
|
1979
|
-
{
|
|
1980
|
-
id: 'kg.datalog.apply',
|
|
1981
|
-
description: 'Apply Datalog rules for reasoning and inference',
|
|
1982
|
-
inputType: 'Program',
|
|
1983
|
-
outputType: 'FactSet',
|
|
1984
|
-
preconditions: ['Valid Datalog program'],
|
|
1985
|
-
postconditions: ['Returns derived facts']
|
|
1986
|
-
},
|
|
1987
|
-
{
|
|
1988
|
-
id: 'kg.semantic.search',
|
|
1989
|
-
description: 'Find semantically similar entities using vector embeddings',
|
|
1990
|
-
inputType: 'Record(entity: Node, k: Int, threshold: Float)',
|
|
1991
|
-
outputType: 'List(Record(entity: Node, similarity: Float))',
|
|
1992
|
-
preconditions: ['Entity has embedding', 'k > 0', 'threshold in [0, 1]'],
|
|
1993
|
-
postconditions: ['Returns top-k similar entities']
|
|
1994
|
-
},
|
|
1995
|
-
{
|
|
1996
|
-
id: 'kg.traversal.oneHop',
|
|
1997
|
-
description: 'Get immediate neighbors of an entity',
|
|
1998
|
-
inputType: 'Node',
|
|
1999
|
-
outputType: 'List(Node)',
|
|
2000
|
-
preconditions: ['Entity exists in graph'],
|
|
2001
|
-
postconditions: ['Returns adjacent nodes']
|
|
2002
|
-
},
|
|
2003
|
-
{
|
|
2004
|
-
id: 'kg.traversal.paths',
|
|
2005
|
-
description: 'Find all paths between two entities up to max length',
|
|
2006
|
-
inputType: 'Record(source: Node, target: Node, maxLength: Int)',
|
|
2007
|
-
outputType: 'List(Path)',
|
|
2008
|
-
preconditions: ['Entities exist', 'maxLength > 0'],
|
|
2009
|
-
postconditions: ['Returns discovered paths']
|
|
2010
|
-
}
|
|
2011
|
-
]
|
|
2012
|
-
|
|
2013
|
-
// Default LUBM ontology hints
|
|
2014
|
-
const LUBM_HINTS = [
|
|
2015
|
-
'Database uses LUBM (Lehigh University Benchmark) ontology',
|
|
2016
|
-
'Namespace: http://swat.cse.lehigh.edu/onto/univ-bench.owl#',
|
|
2017
|
-
'Key classes: University, Department, Professor, AssociateProfessor, AssistantProfessor, Lecturer, GraduateStudent, UndergraduateStudent, Course, GraduateCourse, Publication, Research',
|
|
2018
|
-
'Key properties: worksFor, memberOf, advisor, takesCourse, teacherOf, publicationAuthor, subOrganizationOf, researchInterest, name, emailAddress, telephone',
|
|
2019
|
-
'Professor subtypes: AssociateProfessor, AssistantProfessor, FullProfessor'
|
|
2020
|
-
]
|
|
2021
|
-
|
|
2022
1059
|
/**
|
|
2023
|
-
*
|
|
1060
|
+
* WorkingMemory - Fast, ephemeral context (like CPU registers)
|
|
2024
1061
|
*/
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
const reqOptions = {
|
|
2032
|
-
hostname: urlObj.hostname,
|
|
2033
|
-
port: urlObj.port || (isHttps ? 443 : 80),
|
|
2034
|
-
path: urlObj.pathname + urlObj.search,
|
|
2035
|
-
method: options.method || 'GET',
|
|
2036
|
-
headers: options.headers || {},
|
|
2037
|
-
timeout: options.timeout || 30000
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
const req = client.request(reqOptions, res => {
|
|
2041
|
-
let data = ''
|
|
2042
|
-
res.on('data', chunk => (data += chunk))
|
|
2043
|
-
res.on('end', () => {
|
|
2044
|
-
resolve({
|
|
2045
|
-
status: res.statusCode,
|
|
2046
|
-
headers: res.headers,
|
|
2047
|
-
data: data
|
|
2048
|
-
})
|
|
2049
|
-
})
|
|
2050
|
-
})
|
|
2051
|
-
|
|
2052
|
-
req.on('error', reject)
|
|
2053
|
-
req.on('timeout', () => {
|
|
2054
|
-
req.destroy()
|
|
2055
|
-
reject(new Error('Request timeout'))
|
|
2056
|
-
})
|
|
1062
|
+
class WorkingMemory {
|
|
1063
|
+
constructor(capacity = 100) {
|
|
1064
|
+
this.capacity = capacity
|
|
1065
|
+
this.items = new Map()
|
|
1066
|
+
this.accessOrder = []
|
|
1067
|
+
}
|
|
2057
1068
|
|
|
2058
|
-
|
|
2059
|
-
|
|
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)
|
|
2060
1074
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
/**
|
|
2066
|
-
* Validate SPARQL syntax (basic validation)
|
|
2067
|
-
*/
|
|
2068
|
-
function validateSparqlSyntax(sparql) {
|
|
2069
|
-
if (!sparql || typeof sparql !== 'string') return false
|
|
2070
|
-
|
|
2071
|
-
// Remove markdown code blocks if present
|
|
2072
|
-
let cleaned = sparql.trim()
|
|
2073
|
-
if (cleaned.startsWith('```')) {
|
|
2074
|
-
cleaned = cleaned.replace(/^```\w*\n?/, '').replace(/\n?```$/, '')
|
|
1075
|
+
this.items.set(key, { value, timestamp: Date.now() })
|
|
1076
|
+
this._updateAccess(key)
|
|
2075
1077
|
}
|
|
2076
1078
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
const hasQueryForm = hasSelect || hasConstruct || hasAsk || hasDescribe
|
|
1079
|
+
get(key) {
|
|
1080
|
+
const item = this.items.get(key)
|
|
1081
|
+
if (item) {
|
|
1082
|
+
this._updateAccess(key)
|
|
1083
|
+
return item.value
|
|
1084
|
+
}
|
|
1085
|
+
return null
|
|
1086
|
+
}
|
|
2086
1087
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
1088
|
+
_updateAccess(key) {
|
|
1089
|
+
const idx = this.accessOrder.indexOf(key)
|
|
1090
|
+
if (idx > -1) this.accessOrder.splice(idx, 1)
|
|
1091
|
+
this.accessOrder.push(key)
|
|
1092
|
+
}
|
|
2091
1093
|
|
|
2092
|
-
|
|
2093
|
-
|
|
1094
|
+
clear() {
|
|
1095
|
+
this.items.clear()
|
|
1096
|
+
this.accessOrder = []
|
|
1097
|
+
}
|
|
2094
1098
|
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
*/
|
|
2098
|
-
function createPlanningContext(endpoint, hints = []) {
|
|
2099
|
-
return {
|
|
2100
|
-
tools: HYPERMIND_TOOLS,
|
|
2101
|
-
hints: [...LUBM_HINTS, ...hints],
|
|
2102
|
-
endpoint: endpoint
|
|
1099
|
+
size() {
|
|
1100
|
+
return this.items.size
|
|
2103
1101
|
}
|
|
2104
|
-
}
|
|
2105
1102
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
function getHyperMindBenchmarkSuite() {
|
|
2110
|
-
return LUBM_TEST_SUITE.map(t => ({
|
|
2111
|
-
index: t.index,
|
|
2112
|
-
question: t.question,
|
|
2113
|
-
difficulty: t.difficulty,
|
|
2114
|
-
expectedPattern: t.expectedPattern
|
|
2115
|
-
}))
|
|
1103
|
+
toJSON() {
|
|
1104
|
+
return Object.fromEntries(this.items)
|
|
1105
|
+
}
|
|
2116
1106
|
}
|
|
2117
1107
|
|
|
2118
1108
|
/**
|
|
2119
|
-
*
|
|
1109
|
+
* EpisodicMemory - Execution history with temporal ordering
|
|
2120
1110
|
*/
|
|
2121
|
-
class
|
|
2122
|
-
constructor(
|
|
2123
|
-
this.
|
|
2124
|
-
this.
|
|
2125
|
-
this.tools = spec.tools || ['kg.sparql.query']
|
|
2126
|
-
this.endpoint = spec.endpoint || 'http://rust-kgdb-coordinator:30080'
|
|
2127
|
-
this.timeout = spec.timeout || 30000
|
|
2128
|
-
this.tracing = spec.tracing || false
|
|
2129
|
-
this.trace = []
|
|
2130
|
-
this.context = createPlanningContext(this.endpoint)
|
|
1111
|
+
class EpisodicMemory {
|
|
1112
|
+
constructor(limit = 1000) {
|
|
1113
|
+
this.limit = limit
|
|
1114
|
+
this.episodes = []
|
|
2131
1115
|
}
|
|
2132
1116
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
if (!connected) {
|
|
2143
|
-
console.warn(`Warning: Could not connect to ${spec.endpoint}`)
|
|
2144
|
-
}
|
|
2145
|
-
} catch (err) {
|
|
2146
|
-
console.warn(`Warning: Connection check failed: ${err.message}`)
|
|
1117
|
+
store(episode) {
|
|
1118
|
+
this.episodes.push({
|
|
1119
|
+
...episode,
|
|
1120
|
+
id: crypto.randomUUID(),
|
|
1121
|
+
timestamp: new Date().toISOString(),
|
|
1122
|
+
accessCount: 0
|
|
1123
|
+
})
|
|
1124
|
+
if (this.episodes.length > this.limit) {
|
|
1125
|
+
this.episodes.shift()
|
|
2147
1126
|
}
|
|
2148
|
-
|
|
2149
|
-
return agent
|
|
2150
1127
|
}
|
|
2151
1128
|
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
if (this.model === 'mock') {
|
|
2166
|
-
sparql = this._generateMockSparql(prompt)
|
|
2167
|
-
rawSparql = sparql // Mock always produces clean output
|
|
2168
|
-
rawIsValid = true
|
|
2169
|
-
} else {
|
|
2170
|
-
// Call LLM API - returns { raw, cleaned, rawIsValid }
|
|
2171
|
-
const llmResponse = await this._callLlmForSparql(prompt)
|
|
2172
|
-
this._lastLlmResponse = llmResponse
|
|
2173
|
-
rawSparql = llmResponse.raw
|
|
2174
|
-
rawIsValid = llmResponse.rawIsValid
|
|
2175
|
-
sparql = llmResponse.cleaned // HyperMind uses cleaned version
|
|
2176
|
-
}
|
|
1129
|
+
retrieve(query, limit = 10) {
|
|
1130
|
+
// Simple relevance: count matching words
|
|
1131
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/))
|
|
1132
|
+
return this.episodes
|
|
1133
|
+
.map(ep => {
|
|
1134
|
+
const promptWords = new Set((ep.prompt || '').toLowerCase().split(/\s+/))
|
|
1135
|
+
const overlap = [...queryWords].filter(w => promptWords.has(w)).length
|
|
1136
|
+
return { episode: ep, relevance: overlap / Math.max(queryWords.size, 1) }
|
|
1137
|
+
})
|
|
1138
|
+
.sort((a, b) => b.relevance - a.relevance)
|
|
1139
|
+
.slice(0, limit)
|
|
1140
|
+
}
|
|
2177
1141
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
}
|
|
1142
|
+
getRecent(n = 10) {
|
|
1143
|
+
return this.episodes.slice(-n)
|
|
1144
|
+
}
|
|
2182
1145
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
if (this.tracing) {
|
|
2188
|
-
this.trace.push({
|
|
2189
|
-
timestamp: new Date().toISOString(),
|
|
2190
|
-
tool: 'kg.sparql.query',
|
|
2191
|
-
input: prompt,
|
|
2192
|
-
output: JSON.stringify(results),
|
|
2193
|
-
durationMs: Date.now() - startTime,
|
|
2194
|
-
success: true,
|
|
2195
|
-
rawIsValid: rawIsValid
|
|
2196
|
-
})
|
|
2197
|
-
}
|
|
1146
|
+
size() {
|
|
1147
|
+
return this.episodes.length
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
2198
1150
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
if (this.tracing) {
|
|
2208
|
-
this.trace.push({
|
|
2209
|
-
timestamp: new Date().toISOString(),
|
|
2210
|
-
tool: 'kg.sparql.query',
|
|
2211
|
-
input: prompt,
|
|
2212
|
-
output: error.message,
|
|
2213
|
-
durationMs: Date.now() - startTime,
|
|
2214
|
-
success: false
|
|
2215
|
-
})
|
|
2216
|
-
}
|
|
1151
|
+
/**
|
|
1152
|
+
* LongTermMemory - Persistent knowledge graph storage
|
|
1153
|
+
*/
|
|
1154
|
+
class LongTermMemory {
|
|
1155
|
+
constructor(kg) {
|
|
1156
|
+
this.kg = kg
|
|
1157
|
+
this.memoryGraph = 'http://memory.hypermind.ai/'
|
|
1158
|
+
}
|
|
2217
1159
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
|
2224
1167
|
}
|
|
2225
1168
|
}
|
|
1169
|
+
return false
|
|
2226
1170
|
}
|
|
2227
1171
|
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
// Match against test suite
|
|
2235
|
-
for (const test of LUBM_TEST_SUITE) {
|
|
2236
|
-
if (lowerPrompt.includes(test.question.toLowerCase().slice(0, 20))) {
|
|
2237
|
-
return test.expectedSparql
|
|
1172
|
+
query(sparql) {
|
|
1173
|
+
if (this.kg) {
|
|
1174
|
+
try {
|
|
1175
|
+
return this.kg.querySelect(sparql)
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
return []
|
|
2238
1178
|
}
|
|
2239
1179
|
}
|
|
2240
|
-
|
|
2241
|
-
// Default SPARQL
|
|
2242
|
-
return `PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
2243
|
-
SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10`
|
|
1180
|
+
return []
|
|
2244
1181
|
}
|
|
2245
1182
|
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
IMPORTANT RULES:
|
|
2255
|
-
1. ONLY output a valid SPARQL query - no explanations, no markdown, no backticks
|
|
2256
|
-
2. Use the LUBM ontology prefix: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
2257
|
-
3. Common LUBM classes: Professor, GraduateStudent, UndergraduateStudent, Course, Department, University
|
|
2258
|
-
4. Common LUBM properties: name, advisor, teacherOf, takesCourse, memberOf, subOrganizationOf, worksFor, researchInterest, publicationAuthor
|
|
2259
|
-
|
|
2260
|
-
EXAMPLES:
|
|
2261
|
-
Q: "Find all professors"
|
|
2262
|
-
A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
2263
|
-
SELECT ?x WHERE { ?x a ub:Professor }
|
|
2264
|
-
|
|
2265
|
-
Q: "How many courses are there?"
|
|
2266
|
-
A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
2267
|
-
SELECT (COUNT(?x) AS ?count) WHERE { ?x a ub:Course }
|
|
2268
|
-
|
|
2269
|
-
Q: "Find students and their advisors"
|
|
2270
|
-
A: PREFIX ub: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#>
|
|
2271
|
-
SELECT ?student ?advisor WHERE { ?student ub:advisor ?advisor }
|
|
2272
|
-
|
|
2273
|
-
Now generate a SPARQL query for the following question. Output ONLY the SPARQL query, nothing else:`
|
|
2274
|
-
|
|
2275
|
-
if (this.model.includes('claude') || this.model.includes('anthropic')) {
|
|
2276
|
-
return this._callAnthropic(systemPrompt, prompt)
|
|
2277
|
-
} else if (this.model.includes('gpt') || this.model.includes('openai')) {
|
|
2278
|
-
return this._callOpenAI(systemPrompt, prompt)
|
|
2279
|
-
} else {
|
|
2280
|
-
throw new Error(`Unknown model: ${this.model}. Supported: claude-sonnet-4, gpt-4o, mock`)
|
|
2281
|
-
}
|
|
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)
|
|
2282
1190
|
}
|
|
1191
|
+
}
|
|
2283
1192
|
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
_lastLlmResponse = null
|
|
1193
|
+
// ============================================================================
|
|
1194
|
+
// GOVERNANCE LAYER
|
|
1195
|
+
// ============================================================================
|
|
2288
1196
|
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
1197
|
+
/**
|
|
1198
|
+
* GovernancePolicy - Defines capabilities and limits for agent execution
|
|
1199
|
+
*/
|
|
1200
|
+
class GovernancePolicy {
|
|
1201
|
+
constructor(config = {}) {
|
|
1202
|
+
this.capabilities = new Set(config.capabilities || ['ReadKG', 'ExecuteTool'])
|
|
1203
|
+
this.limits = {
|
|
1204
|
+
maxMemoryMB: config.maxMemoryMB || 256,
|
|
1205
|
+
maxExecutionTimeMs: config.maxExecutionTimeMs || 60000,
|
|
1206
|
+
maxToolCalls: config.maxToolCalls || 100
|
|
2297
1207
|
}
|
|
1208
|
+
this.auditEnabled = config.auditEnabled !== false
|
|
1209
|
+
}
|
|
2298
1210
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
model: modelId,
|
|
2303
|
-
max_tokens: 1024,
|
|
2304
|
-
system: systemPrompt,
|
|
2305
|
-
messages: [{ role: 'user', content: userPrompt }]
|
|
2306
|
-
})
|
|
2307
|
-
|
|
2308
|
-
const response = await httpRequest('https://api.anthropic.com/v1/messages', {
|
|
2309
|
-
method: 'POST',
|
|
2310
|
-
headers: {
|
|
2311
|
-
'Content-Type': 'application/json',
|
|
2312
|
-
'x-api-key': apiKey,
|
|
2313
|
-
'anthropic-version': '2023-06-01'
|
|
2314
|
-
},
|
|
2315
|
-
body: requestBody,
|
|
2316
|
-
timeout: 30000
|
|
2317
|
-
})
|
|
1211
|
+
hasCapability(cap) {
|
|
1212
|
+
return this.capabilities.has(cap)
|
|
1213
|
+
}
|
|
2318
1214
|
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
1215
|
+
addCapability(cap) {
|
|
1216
|
+
this.capabilities.add(cap)
|
|
1217
|
+
}
|
|
2322
1218
|
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
1219
|
+
removeCapability(cap) {
|
|
1220
|
+
this.capabilities.delete(cap)
|
|
1221
|
+
}
|
|
2326
1222
|
|
|
2327
|
-
|
|
1223
|
+
checkLimits(usage) {
|
|
2328
1224
|
return {
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
1225
|
+
memoryOk: (usage.memoryMB || 0) <= this.limits.maxMemoryMB,
|
|
1226
|
+
timeOk: (usage.executionTimeMs || 0) <= this.limits.maxExecutionTimeMs,
|
|
1227
|
+
toolCallsOk: (usage.toolCalls || 0) <= this.limits.maxToolCalls
|
|
2332
1228
|
}
|
|
2333
1229
|
}
|
|
1230
|
+
}
|
|
2334
1231
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* GovernanceEngine - Enforces policies and maintains audit trail
|
|
1234
|
+
*/
|
|
1235
|
+
class GovernanceEngine {
|
|
1236
|
+
constructor(policy) {
|
|
1237
|
+
this.policy = policy
|
|
1238
|
+
this.auditLog = []
|
|
1239
|
+
this.usage = { memoryMB: 0, executionTimeMs: 0, toolCalls: 0 }
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
authorize(action, args) {
|
|
1243
|
+
const requiredCap = this._actionToCapability(action)
|
|
1244
|
+
const authorized = this.policy.hasCapability(requiredCap)
|
|
1245
|
+
|
|
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
|
+
})
|
|
2343
1254
|
}
|
|
2344
1255
|
|
|
2345
|
-
|
|
1256
|
+
return { authorized, reason: authorized ? 'OK' : `Missing capability: ${requiredCap}` }
|
|
1257
|
+
}
|
|
1258
|
+
|
|
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
|
+
}
|
|
2346
1264
|
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
{ role: 'system', content: systemPrompt },
|
|
2351
|
-
{ role: 'user', content: userPrompt }
|
|
2352
|
-
],
|
|
2353
|
-
max_tokens: 1024,
|
|
2354
|
-
temperature: 0.1
|
|
2355
|
-
})
|
|
1265
|
+
checkLimits() {
|
|
1266
|
+
return this.policy.checkLimits(this.usage)
|
|
1267
|
+
}
|
|
2356
1268
|
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
'Content-Type': 'application/json',
|
|
2361
|
-
'Authorization': `Bearer ${apiKey}`
|
|
2362
|
-
},
|
|
2363
|
-
body: requestBody,
|
|
2364
|
-
timeout: 30000
|
|
2365
|
-
})
|
|
1269
|
+
getAuditLog() {
|
|
1270
|
+
return this.auditLog
|
|
1271
|
+
}
|
|
2366
1272
|
|
|
2367
|
-
|
|
2368
|
-
|
|
1273
|
+
_actionToCapability(action) {
|
|
1274
|
+
const mapping = {
|
|
1275
|
+
'query': 'ReadKG',
|
|
1276
|
+
'insert': 'WriteKG',
|
|
1277
|
+
'delete': 'WriteKG',
|
|
1278
|
+
'execute_tool': 'ExecuteTool',
|
|
1279
|
+
'use_embeddings': 'UseEmbeddings'
|
|
2369
1280
|
}
|
|
1281
|
+
return mapping[action] || 'ExecuteTool'
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
2370
1284
|
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
1285
|
+
// ============================================================================
|
|
1286
|
+
// AGENT SCOPE
|
|
1287
|
+
// ============================================================================
|
|
2374
1288
|
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
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
|
|
2380
1310
|
}
|
|
2381
1311
|
}
|
|
2382
1312
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
*/
|
|
2386
|
-
_cleanSparqlResponse(text) {
|
|
2387
|
-
// Remove markdown code blocks
|
|
2388
|
-
let clean = text.replace(/```sparql\n?/gi, '').replace(/```sql\n?/gi, '').replace(/```\n?/g, '')
|
|
2389
|
-
// Remove leading/trailing whitespace
|
|
2390
|
-
clean = clean.trim()
|
|
2391
|
-
// If it starts with "SPARQL:" or similar, remove it
|
|
2392
|
-
clean = clean.replace(/^sparql:\s*/i, '')
|
|
2393
|
-
return clean
|
|
1313
|
+
isGraphAllowed(graphUri) {
|
|
1314
|
+
return this.namespace.allowedGraphs.some(g => graphUri.startsWith(g))
|
|
2394
1315
|
}
|
|
2395
1316
|
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
const response = await httpRequest(url, {
|
|
2403
|
-
method: 'POST',
|
|
2404
|
-
headers: {
|
|
2405
|
-
'Content-Type': 'application/sparql-query',
|
|
2406
|
-
Accept: 'application/json'
|
|
2407
|
-
},
|
|
2408
|
-
body: sparql,
|
|
2409
|
-
timeout: this.timeout
|
|
2410
|
-
})
|
|
2411
|
-
|
|
2412
|
-
if (response.status !== 200) {
|
|
2413
|
-
throw new Error(`SPARQL execution failed: ${response.status} - ${response.data}`)
|
|
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})`)
|
|
2414
1322
|
}
|
|
1323
|
+
}
|
|
2415
1324
|
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
// Handle different response formats:
|
|
2419
|
-
// 1. W3C SPARQL JSON: {"results": {"bindings": [...]}}
|
|
2420
|
-
// 2. rust-kgdb native: {"results": [{"?var": "value"}], "execution_time_ms": N}
|
|
2421
|
-
if (data.results && Array.isArray(data.results)) {
|
|
2422
|
-
// rust-kgdb native format
|
|
2423
|
-
return data.results.map(row => ({
|
|
2424
|
-
bindings: Object.fromEntries(
|
|
2425
|
-
Object.entries(row).map(([k, v]) => {
|
|
2426
|
-
// Strip leading ? from variable names and typed literal suffix
|
|
2427
|
-
const cleanKey = k.startsWith('?') ? k.slice(1) : k
|
|
2428
|
-
let cleanVal = v
|
|
2429
|
-
// Handle typed literals: "11"^^<http://www.w3.org/2001/XMLSchema#integer>
|
|
2430
|
-
if (typeof v === 'string' && v.includes('^^<')) {
|
|
2431
|
-
cleanVal = v.split('^^')[0].replace(/^"|"$/g, '')
|
|
2432
|
-
}
|
|
2433
|
-
return [cleanKey, cleanVal]
|
|
2434
|
-
})
|
|
2435
|
-
)
|
|
2436
|
-
}))
|
|
2437
|
-
} else if (data.results && data.results.bindings) {
|
|
2438
|
-
// W3C SPARQL JSON format
|
|
2439
|
-
return data.results.bindings.map(b => ({
|
|
2440
|
-
bindings: Object.fromEntries(
|
|
2441
|
-
Object.entries(b).map(([k, v]) => [k, v.value])
|
|
2442
|
-
)
|
|
2443
|
-
}))
|
|
2444
|
-
}
|
|
2445
|
-
return data
|
|
2446
|
-
} catch (e) {
|
|
2447
|
-
throw new Error(`Failed to parse SPARQL results: ${e.message}`)
|
|
2448
|
-
}
|
|
1325
|
+
getScopedUri(localName) {
|
|
1326
|
+
return `${this.namespace.graphUri}${this.namespace.prefix}:${localName}`
|
|
2449
1327
|
}
|
|
2450
1328
|
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
1329
|
+
getUsage() {
|
|
1330
|
+
return {
|
|
1331
|
+
name: this.name,
|
|
1332
|
+
toolCalls: this.toolCalls,
|
|
1333
|
+
maxToolCalls: this.limits.maxToolCalls,
|
|
1334
|
+
...this.usage
|
|
1335
|
+
}
|
|
2456
1336
|
}
|
|
2457
1337
|
|
|
2458
1338
|
/**
|
|
2459
|
-
*
|
|
1339
|
+
* Track resource usage
|
|
1340
|
+
* @param {string} resource - Resource type (toolCalls, graphQueries, bytesProcessed)
|
|
1341
|
+
* @param {number} amount - Amount to add to usage
|
|
2460
1342
|
*/
|
|
2461
|
-
|
|
2462
|
-
|
|
1343
|
+
trackUsage(resource, amount = 1) {
|
|
1344
|
+
if (this.usage.hasOwnProperty(resource)) {
|
|
1345
|
+
this.usage[resource] += amount
|
|
1346
|
+
} else {
|
|
1347
|
+
this.usage[resource] = amount
|
|
1348
|
+
}
|
|
2463
1349
|
}
|
|
2464
1350
|
|
|
2465
1351
|
/**
|
|
2466
|
-
*
|
|
1352
|
+
* Get remaining resources before limits are exceeded
|
|
2467
1353
|
*/
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
return response.status === 200
|
|
2474
|
-
} catch {
|
|
2475
|
-
return false
|
|
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
|
|
2476
1359
|
}
|
|
2477
1360
|
}
|
|
2478
1361
|
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
1362
|
+
/**
|
|
1363
|
+
* Check if resource limit has been exceeded
|
|
1364
|
+
* @param {string} resource - Resource type to check
|
|
1365
|
+
*/
|
|
1366
|
+
isLimitExceeded(resource) {
|
|
1367
|
+
const remaining = this.getRemainingResources()
|
|
1368
|
+
return remaining[resource] !== undefined && remaining[resource] <= 0
|
|
2485
1369
|
}
|
|
2486
1370
|
}
|
|
2487
1371
|
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
* KEY COMPARISON:
|
|
2492
|
-
* - "Vanilla LLM" = Raw LLM output WITHOUT HyperMind cleaning
|
|
2493
|
-
* - "HyperMind Agent" = LLM output WITH typed tools, cleaning, validation
|
|
2494
|
-
*
|
|
2495
|
-
* This shows the TRUE value of HyperMind by comparing:
|
|
2496
|
-
* 1. How often raw LLM output has syntax issues (markdown, backticks, etc)
|
|
2497
|
-
* 2. How HyperMind fixes these issues with _cleanSparqlResponse()
|
|
2498
|
-
*/
|
|
2499
|
-
async function runHyperMindBenchmark(endpoint, model, options = {}) {
|
|
2500
|
-
const testSuite = options.testIndices
|
|
2501
|
-
? LUBM_TEST_SUITE.filter(t => options.testIndices.includes(t.index))
|
|
2502
|
-
: LUBM_TEST_SUITE
|
|
2503
|
-
|
|
2504
|
-
const results = []
|
|
2505
|
-
let rawSyntaxSuccess = 0 // Vanilla LLM: raw output passes validation
|
|
2506
|
-
let hypermindSyntaxSuccess = 0 // HyperMind: cleaned output passes validation
|
|
2507
|
-
let executionSuccess = 0 // Actually executed against cluster
|
|
2508
|
-
let typeErrorsCaught = 0
|
|
2509
|
-
let totalLatency = 0
|
|
2510
|
-
let cleaningRequired = 0 // How many times cleaning was needed
|
|
2511
|
-
|
|
2512
|
-
// Determine provider details
|
|
2513
|
-
const providerInfo = model.includes('claude')
|
|
2514
|
-
? { name: 'Anthropic', modelId: 'claude-sonnet-4-20250514', api: 'https://api.anthropic.com/v1/messages' }
|
|
2515
|
-
: model.includes('gpt')
|
|
2516
|
-
? { name: 'OpenAI', modelId: 'gpt-4o', api: 'https://api.openai.com/v1/chat/completions' }
|
|
2517
|
-
: { name: 'Mock (Pattern Matching)', modelId: 'mock', api: 'N/A' }
|
|
2518
|
-
|
|
2519
|
-
console.log(`\n${'═'.repeat(80)}`)
|
|
2520
|
-
console.log(` HyperMind Agentic Framework Benchmark`)
|
|
2521
|
-
console.log(` Vanilla LLM vs HyperMind Agent Comparison`)
|
|
2522
|
-
console.log(`${'═'.repeat(80)}`)
|
|
2523
|
-
console.log()
|
|
2524
|
-
console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
|
|
2525
|
-
console.log(` │ BENCHMARK CONFIGURATION │`)
|
|
2526
|
-
console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
|
|
2527
|
-
console.log(` │ Dataset: LUBM (Lehigh University Benchmark) Ontology │`)
|
|
2528
|
-
console.log(` │ - 3,272 triples (LUBM-1: 1 university) │`)
|
|
2529
|
-
console.log(` │ - Classes: Professor, GraduateStudent, Course, Department │`)
|
|
2530
|
-
console.log(` │ - Properties: advisor, teacherOf, memberOf, worksFor │`)
|
|
2531
|
-
console.log(` │ │`)
|
|
2532
|
-
console.log(` │ LLM Provider: ${providerInfo.name.padEnd(60)}│`)
|
|
2533
|
-
console.log(` │ Model ID: ${providerInfo.modelId.padEnd(60)}│`)
|
|
2534
|
-
console.log(` │ API Endpoint: ${providerInfo.api.padEnd(60)}│`)
|
|
2535
|
-
console.log(` │ │`)
|
|
2536
|
-
console.log(` │ Task: Natural Language → SPARQL Query Generation │`)
|
|
2537
|
-
console.log(` │ Agent receives question, generates SPARQL, executes query │`)
|
|
2538
|
-
console.log(` │ │`)
|
|
2539
|
-
console.log(` │ Embeddings: NOT USED (this benchmark is NL-to-SPARQL, not semantic) │`)
|
|
2540
|
-
console.log(` │ Multi-Vector: NOT APPLICABLE │`)
|
|
2541
|
-
console.log(` │ │`)
|
|
2542
|
-
console.log(` │ K8s Cluster: ${endpoint.padEnd(60)}│`)
|
|
2543
|
-
console.log(` │ Tests: ${testSuite.length} LUBM queries (Easy: 3, Medium: 5, Hard: 4) │`)
|
|
2544
|
-
console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
|
|
2545
|
-
console.log()
|
|
2546
|
-
console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
|
|
2547
|
-
console.log(` │ AGENT CREATION │`)
|
|
2548
|
-
console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
|
|
2549
|
-
console.log(` │ Name: benchmark-agent │`)
|
|
2550
|
-
console.log(` │ Model: ${model.padEnd(62)}│`)
|
|
2551
|
-
console.log(` │ Tools: kg.sparql.query, kg.motif.find, kg.datalog.apply │`)
|
|
2552
|
-
console.log(` │ Tracing: enabled │`)
|
|
2553
|
-
console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
|
|
2554
|
-
console.log()
|
|
2555
|
-
console.log(` ┌──────────────────────────────────────────────────────────────────────────┐`)
|
|
2556
|
-
console.log(` │ 12 LUBM TEST QUERIES │`)
|
|
2557
|
-
console.log(` ├──────────────────────────────────────────────────────────────────────────┤`)
|
|
2558
|
-
for (const test of testSuite) {
|
|
2559
|
-
const q = `Q${test.index}: "${test.question}"`.slice(0, 72)
|
|
2560
|
-
console.log(` │ ${q.padEnd(74)}│`)
|
|
2561
|
-
}
|
|
2562
|
-
console.log(` └──────────────────────────────────────────────────────────────────────────┘`)
|
|
2563
|
-
console.log()
|
|
2564
|
-
console.log(`${'═'.repeat(80)}\n`)
|
|
2565
|
-
|
|
2566
|
-
// Spawn agent with HyperMind framework
|
|
2567
|
-
const agent = await HyperMindAgent.spawn({
|
|
2568
|
-
name: 'benchmark-agent',
|
|
2569
|
-
model: model,
|
|
2570
|
-
tools: ['kg.sparql.query', 'kg.motif.find', 'kg.datalog.apply'],
|
|
2571
|
-
endpoint: endpoint,
|
|
2572
|
-
timeout: options.timeout || 30000,
|
|
2573
|
-
tracing: true
|
|
2574
|
-
})
|
|
2575
|
-
|
|
2576
|
-
for (const test of testSuite) {
|
|
2577
|
-
const startTime = Date.now()
|
|
2578
|
-
console.log(`Test ${test.index}: ${test.question}`)
|
|
2579
|
-
console.log(` Difficulty: ${test.difficulty}`)
|
|
1372
|
+
// ============================================================================
|
|
1373
|
+
// EXPORTS
|
|
1374
|
+
// ============================================================================
|
|
2580
1375
|
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
const latency = Date.now() - startTime
|
|
2585
|
-
totalLatency += latency
|
|
2586
|
-
|
|
2587
|
-
// Track raw (vanilla) LLM success
|
|
2588
|
-
if (result.rawIsValid === true) {
|
|
2589
|
-
rawSyntaxSuccess++
|
|
2590
|
-
console.log(` 📝 Vanilla LLM: ✅ RAW OUTPUT VALID`)
|
|
2591
|
-
} else if (result.rawIsValid === false) {
|
|
2592
|
-
console.log(` 📝 Vanilla LLM: ❌ RAW OUTPUT INVALID (needs cleaning)`)
|
|
2593
|
-
cleaningRequired++
|
|
2594
|
-
}
|
|
1376
|
+
module.exports = {
|
|
1377
|
+
// Main Agent
|
|
1378
|
+
HyperMindAgent,
|
|
2595
1379
|
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
console.log(` SPARQL: ${result.sparql.slice(0, 60)}...`)
|
|
2603
|
-
}
|
|
2604
|
-
} else {
|
|
2605
|
-
// Check if this was a type error caught by framework
|
|
2606
|
-
if (result.error && result.error.includes('Type')) {
|
|
2607
|
-
typeErrorsCaught++
|
|
2608
|
-
console.log(` 🧠 HyperMind: ⚠️ TYPE ERROR CAUGHT`)
|
|
2609
|
-
} else {
|
|
2610
|
-
console.log(` 🧠 HyperMind: ❌ FAILED - ${result.error}`)
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
1380
|
+
// Supporting Classes
|
|
1381
|
+
MemoryManager,
|
|
1382
|
+
DatalogRuleSet,
|
|
1383
|
+
WasmSandbox,
|
|
1384
|
+
ExecutionTrace,
|
|
1385
|
+
ProofNode,
|
|
2613
1386
|
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
if (options.verbose) {
|
|
2617
|
-
console.log(` ↳ Raw had: ${result.rawSparql.includes('```') ? 'markdown' : 'formatting issues'}`)
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
1387
|
+
// Type System
|
|
1388
|
+
TypeId,
|
|
2620
1389
|
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
rawIsValid: result.rawIsValid,
|
|
2625
|
-
hypermindSuccess: result.success,
|
|
2626
|
-
executionSuccess: result.success,
|
|
2627
|
-
sparql: result.sparql,
|
|
2628
|
-
rawSparql: result.rawSparql,
|
|
2629
|
-
typeErrorsCaught: result.error?.includes('Type') ? 1 : 0,
|
|
2630
|
-
latencyMs: latency,
|
|
2631
|
-
error: result.error
|
|
2632
|
-
})
|
|
2633
|
-
} catch (error) {
|
|
2634
|
-
console.log(` ❌ ERROR: ${error.message}`)
|
|
2635
|
-
results.push({
|
|
2636
|
-
question: test.question,
|
|
2637
|
-
difficulty: test.difficulty,
|
|
2638
|
-
rawIsValid: false,
|
|
2639
|
-
hypermindSuccess: false,
|
|
2640
|
-
executionSuccess: false,
|
|
2641
|
-
typeErrorsCaught: 0,
|
|
2642
|
-
latencyMs: Date.now() - startTime,
|
|
2643
|
-
error: error.message
|
|
2644
|
-
})
|
|
2645
|
-
}
|
|
1390
|
+
// Agent State Machine
|
|
1391
|
+
AgentState,
|
|
1392
|
+
AgentRuntime,
|
|
2646
1393
|
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
const stats = {
|
|
2652
|
-
totalTests: testSuite.length,
|
|
2653
|
-
// Vanilla LLM stats (raw output without HyperMind)
|
|
2654
|
-
vanillaLlmSyntaxSuccess: rawSyntaxSuccess,
|
|
2655
|
-
vanillaLlmSyntaxRate: (rawSyntaxSuccess / testSuite.length) * 100,
|
|
2656
|
-
// HyperMind stats (with typed tools + cleaning)
|
|
2657
|
-
hypermindSyntaxSuccess: hypermindSyntaxSuccess,
|
|
2658
|
-
hypermindSyntaxRate: (hypermindSyntaxSuccess / testSuite.length) * 100,
|
|
2659
|
-
// Execution stats
|
|
2660
|
-
executionSuccess: executionSuccess,
|
|
2661
|
-
executionSuccessRate: (executionSuccess / testSuite.length) * 100,
|
|
2662
|
-
// Value metrics
|
|
2663
|
-
cleaningRequired: cleaningRequired,
|
|
2664
|
-
syntaxImprovement: hypermindSyntaxSuccess - rawSyntaxSuccess,
|
|
2665
|
-
typeErrorsCaught: typeErrorsCaught,
|
|
2666
|
-
avgLatencyMs: totalLatency / testSuite.length
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
// Print summary with clear comparison
|
|
2670
|
-
console.log(`${'═'.repeat(70)}`)
|
|
2671
|
-
console.log(` BENCHMARK RESULTS: Vanilla LLM vs HyperMind Agent`)
|
|
2672
|
-
console.log(`${'═'.repeat(70)}`)
|
|
2673
|
-
console.log()
|
|
2674
|
-
console.log(` ┌─────────────────────────────────────────────────────────────────┐`)
|
|
2675
|
-
console.log(` │ Metric │ Vanilla LLM │ HyperMind │ Δ Improve │`)
|
|
2676
|
-
console.log(` ├─────────────────────────────────────────────────────────────────┤`)
|
|
2677
|
-
console.log(` │ Syntax Valid │ ${stats.vanillaLlmSyntaxRate.toFixed(1).padStart(9)}% │ ${stats.hypermindSyntaxRate.toFixed(1).padStart(7)}% │ ${stats.syntaxImprovement > 0 ? '+' : ''}${stats.syntaxImprovement.toString().padStart(7)} │`)
|
|
2678
|
-
console.log(` │ Execution Success │ N/A │ ${stats.executionSuccessRate.toFixed(1).padStart(7)}% │ │`)
|
|
2679
|
-
console.log(` │ Avg Latency │ N/A │ ${stats.avgLatencyMs.toFixed(0).padStart(5)}ms │ │`)
|
|
2680
|
-
console.log(` └─────────────────────────────────────────────────────────────────┘`)
|
|
2681
|
-
console.log()
|
|
2682
|
-
console.log(` 📊 Summary:`)
|
|
2683
|
-
console.log(` - Total Tests: ${stats.totalTests}`)
|
|
2684
|
-
console.log(` - Times Cleaning Needed: ${stats.cleaningRequired} (${((stats.cleaningRequired/stats.totalTests)*100).toFixed(0)}%)`)
|
|
2685
|
-
console.log(` - Type Errors Caught: ${stats.typeErrorsCaught}`)
|
|
2686
|
-
if (stats.syntaxImprovement > 0) {
|
|
2687
|
-
console.log(` - HyperMind FIXED ${stats.syntaxImprovement} queries that Vanilla LLM failed!`)
|
|
2688
|
-
}
|
|
2689
|
-
console.log(`${'═'.repeat(70)}\n`)
|
|
2690
|
-
|
|
2691
|
-
// Save results if requested
|
|
2692
|
-
if (options.saveResults) {
|
|
2693
|
-
const fs = require('fs')
|
|
2694
|
-
const filename = `hypermind_benchmark_${model}_${Date.now()}.json`
|
|
2695
|
-
fs.writeFileSync(
|
|
2696
|
-
filename,
|
|
2697
|
-
JSON.stringify(
|
|
2698
|
-
{
|
|
2699
|
-
timestamp: new Date().toISOString(),
|
|
2700
|
-
model,
|
|
2701
|
-
endpoint,
|
|
2702
|
-
comparison: 'Vanilla LLM vs HyperMind Agent',
|
|
2703
|
-
stats,
|
|
2704
|
-
results
|
|
2705
|
-
},
|
|
2706
|
-
null,
|
|
2707
|
-
2
|
|
2708
|
-
)
|
|
2709
|
-
)
|
|
2710
|
-
console.log(`Results saved to: ${filename}`)
|
|
2711
|
-
}
|
|
1394
|
+
// Memory Tiers
|
|
1395
|
+
WorkingMemory,
|
|
1396
|
+
EpisodicMemory,
|
|
1397
|
+
LongTermMemory,
|
|
2712
1398
|
|
|
2713
|
-
|
|
2714
|
-
|
|
1399
|
+
// Governance Layer
|
|
1400
|
+
GovernancePolicy,
|
|
1401
|
+
GovernanceEngine,
|
|
2715
1402
|
|
|
2716
|
-
//
|
|
2717
|
-
|
|
2718
|
-
// Original HyperMind Agent API
|
|
2719
|
-
HyperMindAgent,
|
|
2720
|
-
runHyperMindBenchmark,
|
|
2721
|
-
getHyperMindBenchmarkSuite,
|
|
2722
|
-
validateSparqlSyntax,
|
|
2723
|
-
createPlanningContext,
|
|
2724
|
-
LUBM_TEST_SUITE,
|
|
2725
|
-
HYPERMIND_TOOLS,
|
|
2726
|
-
|
|
2727
|
-
// Architecture Components (v0.5.8+)
|
|
2728
|
-
TypeId, // Type system (Hindley-Milner + Refinement Types)
|
|
2729
|
-
TOOL_REGISTRY, // Typed tool morphisms
|
|
2730
|
-
LLMPlanner, // Natural language -> typed tool pipelines
|
|
2731
|
-
WasmSandbox, // WASM sandbox with capability-based security
|
|
2732
|
-
AgentBuilder, // Fluent builder for agent composition
|
|
2733
|
-
ComposedAgent, // Composed agent with sandbox execution
|
|
2734
|
-
|
|
2735
|
-
// Memory Layer (v0.5.13+) - GraphDB-Powered Agent Memory
|
|
2736
|
-
AgentState, // Agent lifecycle states (CREATED, READY, RUNNING, etc.)
|
|
2737
|
-
AgentRuntime, // Agent identity and state management
|
|
2738
|
-
WorkingMemory, // In-memory current context
|
|
2739
|
-
EpisodicMemory, // Execution history stored in GraphDB
|
|
2740
|
-
LongTermMemory, // Source-of-truth knowledge graph (read-only)
|
|
2741
|
-
MemoryManager, // Unified memory retrieval with weighted scoring
|
|
2742
|
-
|
|
2743
|
-
// Governance Layer (v0.5.13+) - Policy Engine & Capability Grants
|
|
2744
|
-
GovernancePolicy, // Access control and behavioral policies
|
|
2745
|
-
GovernanceEngine, // Policy enforcement and audit logging
|
|
2746
|
-
|
|
2747
|
-
// Scope Layer (v0.5.13+) - Namespace Isolation & Resource Limits
|
|
2748
|
-
AgentScope // Namespace boundaries and resource tracking
|
|
1403
|
+
// Scope Layer
|
|
1404
|
+
AgentScope
|
|
2749
1405
|
}
|