rust-kgdb 0.5.12 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +253 -0
- package/README.md +170 -0
- package/examples/fraud-detection-agent.js +157 -1
- package/examples/fraud-memory-hypergraph.js +658 -0
- package/examples/underwriting-agent.js +142 -2
- package/hypermind-agent.js +1199 -1
- package/index.d.ts +735 -0
- package/index.js +24 -0
- package/package.json +8 -3
package/hypermind-agent.js
CHANGED
|
@@ -488,6 +488,1189 @@ class WasmSandbox {
|
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
+
// ============================================================================
|
|
492
|
+
// MEMORY LAYER (GraphDB-Powered Agent Memory System)
|
|
493
|
+
// ============================================================================
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* AgentState - Lifecycle states for agent runtime
|
|
497
|
+
*/
|
|
498
|
+
const AgentState = {
|
|
499
|
+
CREATED: 'CREATED',
|
|
500
|
+
READY: 'READY',
|
|
501
|
+
RUNNING: 'RUNNING',
|
|
502
|
+
PAUSED: 'PAUSED',
|
|
503
|
+
COMPLETED: 'COMPLETED',
|
|
504
|
+
FAILED: 'FAILED'
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* AgentRuntime - Core agent identity and state management
|
|
509
|
+
*
|
|
510
|
+
* Provides:
|
|
511
|
+
* - Unique agent identity (UUID)
|
|
512
|
+
* - Lifecycle state management
|
|
513
|
+
* - Memory layer orchestration
|
|
514
|
+
* - Execution context tracking
|
|
515
|
+
*/
|
|
516
|
+
class AgentRuntime {
|
|
517
|
+
constructor(config = {}) {
|
|
518
|
+
// Agent Identity
|
|
519
|
+
this.id = config.id || `agent_${crypto.randomUUID()}`
|
|
520
|
+
this.name = config.name || 'unnamed-agent'
|
|
521
|
+
this.version = config.version || '1.0.0'
|
|
522
|
+
this.createdAt = new Date().toISOString()
|
|
523
|
+
|
|
524
|
+
// State Management
|
|
525
|
+
this.state = AgentState.CREATED
|
|
526
|
+
this.stateHistory = [{
|
|
527
|
+
state: AgentState.CREATED,
|
|
528
|
+
timestamp: this.createdAt,
|
|
529
|
+
reason: 'Agent initialized'
|
|
530
|
+
}]
|
|
531
|
+
|
|
532
|
+
// Memory Configuration
|
|
533
|
+
this.memoryConfig = {
|
|
534
|
+
workingMemoryCapacity: config.workingMemoryCapacity || 100,
|
|
535
|
+
episodicRetentionDays: config.episodicRetentionDays || 30,
|
|
536
|
+
retrievalWeights: config.retrievalWeights || {
|
|
537
|
+
recency: 0.3, // α - time decay
|
|
538
|
+
relevance: 0.5, // β - semantic similarity
|
|
539
|
+
importance: 0.2 // γ - access frequency
|
|
540
|
+
},
|
|
541
|
+
recencyDecayRate: config.recencyDecayRate || 0.995 // per hour
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Execution Context
|
|
545
|
+
this.currentExecution = null
|
|
546
|
+
this.executionCount = 0
|
|
547
|
+
this.lastActiveAt = this.createdAt
|
|
548
|
+
|
|
549
|
+
// Metrics
|
|
550
|
+
this.metrics = {
|
|
551
|
+
totalExecutions: 0,
|
|
552
|
+
successfulExecutions: 0,
|
|
553
|
+
failedExecutions: 0,
|
|
554
|
+
totalMemoryRetrievals: 0,
|
|
555
|
+
avgExecutionTimeMs: 0
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
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
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!validTransitions[this.state]?.includes(newState)) {
|
|
573
|
+
throw new Error(`Invalid state transition: ${this.state} -> ${newState}`)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
this.state = newState
|
|
577
|
+
this.stateHistory.push({
|
|
578
|
+
state: newState,
|
|
579
|
+
timestamp: new Date().toISOString(),
|
|
580
|
+
reason
|
|
581
|
+
})
|
|
582
|
+
this.lastActiveAt = new Date().toISOString()
|
|
583
|
+
|
|
584
|
+
return this
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Mark agent as ready for execution
|
|
589
|
+
*/
|
|
590
|
+
ready() {
|
|
591
|
+
return this.transitionTo(AgentState.READY, 'Agent initialized and ready')
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Start an execution
|
|
596
|
+
*/
|
|
597
|
+
startExecution(prompt) {
|
|
598
|
+
this.transitionTo(AgentState.RUNNING, `Starting execution: ${prompt.slice(0, 50)}...`)
|
|
599
|
+
this.currentExecution = {
|
|
600
|
+
id: `exec_${Date.now()}_${crypto.randomUUID().slice(0, 8)}`,
|
|
601
|
+
prompt,
|
|
602
|
+
startTime: Date.now(),
|
|
603
|
+
steps: []
|
|
604
|
+
}
|
|
605
|
+
this.executionCount++
|
|
606
|
+
return this.currentExecution.id
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Complete current execution
|
|
611
|
+
*/
|
|
612
|
+
completeExecution(result, success = true) {
|
|
613
|
+
if (!this.currentExecution) return
|
|
614
|
+
|
|
615
|
+
this.currentExecution.endTime = Date.now()
|
|
616
|
+
this.currentExecution.durationMs = this.currentExecution.endTime - this.currentExecution.startTime
|
|
617
|
+
this.currentExecution.result = result
|
|
618
|
+
this.currentExecution.success = success
|
|
619
|
+
|
|
620
|
+
// Update metrics
|
|
621
|
+
this.metrics.totalExecutions++
|
|
622
|
+
if (success) {
|
|
623
|
+
this.metrics.successfulExecutions++
|
|
624
|
+
} else {
|
|
625
|
+
this.metrics.failedExecutions++
|
|
626
|
+
}
|
|
627
|
+
this.metrics.avgExecutionTimeMs =
|
|
628
|
+
(this.metrics.avgExecutionTimeMs * (this.metrics.totalExecutions - 1) +
|
|
629
|
+
this.currentExecution.durationMs) / this.metrics.totalExecutions
|
|
630
|
+
|
|
631
|
+
const execution = this.currentExecution
|
|
632
|
+
this.currentExecution = null
|
|
633
|
+
this.transitionTo(AgentState.READY, `Execution completed: ${success ? 'success' : 'failed'}`)
|
|
634
|
+
|
|
635
|
+
return execution
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Get agent identity info
|
|
640
|
+
*/
|
|
641
|
+
getIdentity() {
|
|
642
|
+
return {
|
|
643
|
+
id: this.id,
|
|
644
|
+
name: this.name,
|
|
645
|
+
version: this.version,
|
|
646
|
+
createdAt: this.createdAt,
|
|
647
|
+
state: this.state,
|
|
648
|
+
executionCount: this.executionCount,
|
|
649
|
+
lastActiveAt: this.lastActiveAt
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Get runtime metrics
|
|
655
|
+
*/
|
|
656
|
+
getMetrics() {
|
|
657
|
+
return {
|
|
658
|
+
...this.metrics,
|
|
659
|
+
uptime: Date.now() - new Date(this.createdAt).getTime(),
|
|
660
|
+
currentState: this.state
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* WorkingMemory - Current execution context (in-memory)
|
|
667
|
+
*
|
|
668
|
+
* Fast, ephemeral memory for the current task:
|
|
669
|
+
* - Current conversation context
|
|
670
|
+
* - Active bindings and variables
|
|
671
|
+
* - Tool execution results (recent)
|
|
672
|
+
* - Scratchpad for reasoning
|
|
673
|
+
*/
|
|
674
|
+
class WorkingMemory {
|
|
675
|
+
constructor(capacity = 100) {
|
|
676
|
+
this.capacity = capacity
|
|
677
|
+
this.context = [] // Current conversation/task context
|
|
678
|
+
this.bindings = new Map() // Variable bindings
|
|
679
|
+
this.scratchpad = new Map() // Temporary reasoning space
|
|
680
|
+
this.toolResults = [] // Recent tool execution results
|
|
681
|
+
this.focus = null // Current focus entity/topic
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Add item to working memory context
|
|
686
|
+
*/
|
|
687
|
+
addContext(item) {
|
|
688
|
+
this.context.push({
|
|
689
|
+
...item,
|
|
690
|
+
timestamp: item.timestamp || Date.now(),
|
|
691
|
+
id: item.id !== undefined ? item.id : `ctx_${Date.now()}`
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
// Evict oldest if over capacity
|
|
695
|
+
while (this.context.length > this.capacity) {
|
|
696
|
+
this.context.shift()
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return this
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Set a variable binding
|
|
704
|
+
*/
|
|
705
|
+
setBinding(key, value) {
|
|
706
|
+
this.bindings.set(key, {
|
|
707
|
+
value,
|
|
708
|
+
timestamp: Date.now(),
|
|
709
|
+
accessCount: 0
|
|
710
|
+
})
|
|
711
|
+
return this
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Get a variable binding
|
|
716
|
+
*/
|
|
717
|
+
getBinding(key) {
|
|
718
|
+
const binding = this.bindings.get(key)
|
|
719
|
+
if (binding) {
|
|
720
|
+
binding.accessCount++
|
|
721
|
+
return binding.value
|
|
722
|
+
}
|
|
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
|
+
|
|
737
|
+
// Keep only recent results
|
|
738
|
+
if (this.toolResults.length > 20) {
|
|
739
|
+
this.toolResults.shift()
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return this
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Set current focus
|
|
747
|
+
*/
|
|
748
|
+
setFocus(entity) {
|
|
749
|
+
this.focus = {
|
|
750
|
+
entity,
|
|
751
|
+
timestamp: Date.now()
|
|
752
|
+
}
|
|
753
|
+
return this
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Write to scratchpad
|
|
758
|
+
*/
|
|
759
|
+
scratch(key, value) {
|
|
760
|
+
this.scratchpad.set(key, value)
|
|
761
|
+
return this
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Read from scratchpad
|
|
766
|
+
*/
|
|
767
|
+
readScratch(key) {
|
|
768
|
+
return this.scratchpad.get(key)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Get recent context items
|
|
773
|
+
*/
|
|
774
|
+
getRecentContext(limit = 10) {
|
|
775
|
+
return this.context.slice(-limit)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Clear working memory
|
|
780
|
+
*/
|
|
781
|
+
clear() {
|
|
782
|
+
this.context = []
|
|
783
|
+
this.bindings.clear()
|
|
784
|
+
this.scratchpad.clear()
|
|
785
|
+
this.toolResults = []
|
|
786
|
+
this.focus = null
|
|
787
|
+
return this
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Export working memory state
|
|
792
|
+
*/
|
|
793
|
+
export() {
|
|
794
|
+
return {
|
|
795
|
+
context: this.context,
|
|
796
|
+
bindings: Object.fromEntries(this.bindings),
|
|
797
|
+
scratchpad: Object.fromEntries(this.scratchpad),
|
|
798
|
+
toolResults: this.toolResults,
|
|
799
|
+
focus: this.focus
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* EpisodicMemory - Execution history stored in GraphDB
|
|
806
|
+
*
|
|
807
|
+
* Persistent memory of agent executions:
|
|
808
|
+
* - Past prompts and responses
|
|
809
|
+
* - Tool invocation history
|
|
810
|
+
* - Success/failure outcomes
|
|
811
|
+
* - Temporal relationships
|
|
812
|
+
*/
|
|
813
|
+
class EpisodicMemory {
|
|
814
|
+
constructor(graphDb, namespace = 'http://hypermind.ai/episodic/') {
|
|
815
|
+
this.graphDb = graphDb
|
|
816
|
+
this.namespace = namespace
|
|
817
|
+
this.episodeCount = 0
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Store an execution episode
|
|
822
|
+
*/
|
|
823
|
+
async storeEpisode(agentId, execution) {
|
|
824
|
+
const episodeUri = `${this.namespace}episode/${execution.id}`
|
|
825
|
+
const timestamp = new Date().toISOString()
|
|
826
|
+
|
|
827
|
+
// Build episode triples
|
|
828
|
+
const triples = `
|
|
829
|
+
@prefix ep: <${this.namespace}> .
|
|
830
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
831
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
832
|
+
|
|
833
|
+
<${episodeUri}> rdf:type ep:Episode ;
|
|
834
|
+
ep:agentId "${agentId}" ;
|
|
835
|
+
ep:executionId "${execution.id}" ;
|
|
836
|
+
ep:prompt "${this._escapeForTtl(execution.prompt)}" ;
|
|
837
|
+
ep:timestamp "${timestamp}"^^xsd:dateTime ;
|
|
838
|
+
ep:durationMs "${execution.durationMs || 0}"^^xsd:integer ;
|
|
839
|
+
ep:success "${execution.success}"^^xsd:boolean .
|
|
840
|
+
`
|
|
841
|
+
|
|
842
|
+
// Store in GraphDB
|
|
843
|
+
if (this.graphDb && typeof this.graphDb.loadTtl === 'function') {
|
|
844
|
+
await this.graphDb.loadTtl(triples, `${this.namespace}episodes`)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
this.episodeCount++
|
|
848
|
+
|
|
849
|
+
return {
|
|
850
|
+
episodeUri,
|
|
851
|
+
timestamp,
|
|
852
|
+
stored: true
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Store a tool invocation within an episode
|
|
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
|
+
`
|
|
872
|
+
|
|
873
|
+
if (this.graphDb && typeof this.graphDb.loadTtl === 'function') {
|
|
874
|
+
await this.graphDb.loadTtl(triples, `${this.namespace}invocations`)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return invocationUri
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Retrieve episodes for an agent
|
|
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
|
+
`
|
|
904
|
+
|
|
905
|
+
if (this.graphDb && typeof this.graphDb.querySelect === 'function') {
|
|
906
|
+
try {
|
|
907
|
+
const results = await this.graphDb.querySelect(query)
|
|
908
|
+
return results.map(r => ({
|
|
909
|
+
episode: r.bindings?.episode || r.episode,
|
|
910
|
+
prompt: r.bindings?.prompt || r.prompt,
|
|
911
|
+
timestamp: r.bindings?.timestamp || r.timestamp,
|
|
912
|
+
durationMs: parseInt(r.bindings?.durationMs || r.durationMs || '0'),
|
|
913
|
+
success: (r.bindings?.success || r.success) === 'true'
|
|
914
|
+
}))
|
|
915
|
+
} catch (e) {
|
|
916
|
+
// GraphDB not available, return empty
|
|
917
|
+
return []
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return []
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Get episodes similar to a prompt (using embeddings if available)
|
|
926
|
+
*/
|
|
927
|
+
async getSimilarEpisodes(prompt, k = 5, threshold = 0.7) {
|
|
928
|
+
// This will be enhanced when EmbeddingService is integrated
|
|
929
|
+
// For now, return recent episodes as fallback
|
|
930
|
+
return this.getEpisodes('*', { limit: k })
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Calculate recency score with exponential decay
|
|
935
|
+
*/
|
|
936
|
+
calculateRecencyScore(timestamp, decayRate = 0.995) {
|
|
937
|
+
const hoursElapsed = (Date.now() - new Date(timestamp).getTime()) / (1000 * 60 * 60)
|
|
938
|
+
return Math.pow(decayRate, hoursElapsed)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Escape string for Turtle format
|
|
943
|
+
*/
|
|
944
|
+
_escapeForTtl(str) {
|
|
945
|
+
if (!str) return ''
|
|
946
|
+
return str
|
|
947
|
+
.replace(/\\/g, '\\\\')
|
|
948
|
+
.replace(/"/g, '\\"')
|
|
949
|
+
.replace(/\n/g, '\\n')
|
|
950
|
+
.replace(/\r/g, '\\r')
|
|
951
|
+
.replace(/\t/g, '\\t')
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get episode count
|
|
956
|
+
*/
|
|
957
|
+
getEpisodeCount() {
|
|
958
|
+
return this.episodeCount
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* LongTermMemory - Source-of-truth Knowledge Graph
|
|
964
|
+
*
|
|
965
|
+
* Read-only access to the ingestion graph (source of truth):
|
|
966
|
+
* - Domain ontologies and schemas
|
|
967
|
+
* - Factual knowledge from data ingestion
|
|
968
|
+
* - Business rules and constraints
|
|
969
|
+
* - Clear separation from agent memory
|
|
970
|
+
*/
|
|
971
|
+
class LongTermMemory {
|
|
972
|
+
constructor(graphDb, config = {}) {
|
|
973
|
+
this.graphDb = graphDb
|
|
974
|
+
this.sourceGraphUri = config.sourceGraphUri || 'http://hypermind.ai/source/'
|
|
975
|
+
this.agentGraphUri = config.agentGraphUri || 'http://hypermind.ai/agent/'
|
|
976
|
+
this.readOnly = config.readOnly !== false // Default to read-only
|
|
977
|
+
this.accessLog = []
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Query the source-of-truth graph (read-only)
|
|
982
|
+
*/
|
|
983
|
+
async querySource(sparql) {
|
|
984
|
+
this._logAccess('query', sparql)
|
|
985
|
+
|
|
986
|
+
// Ensure query targets source graph
|
|
987
|
+
const graphQuery = sparql.includes('FROM') ? sparql :
|
|
988
|
+
sparql.replace('WHERE', `FROM <${this.sourceGraphUri}> WHERE`)
|
|
989
|
+
|
|
990
|
+
if (this.graphDb && typeof this.graphDb.querySelect === 'function') {
|
|
991
|
+
try {
|
|
992
|
+
return await this.graphDb.querySelect(graphQuery)
|
|
993
|
+
} catch (e) {
|
|
994
|
+
return []
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return []
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Get entity facts from source graph
|
|
1003
|
+
*/
|
|
1004
|
+
async getEntityFacts(entityUri, limit = 50) {
|
|
1005
|
+
const query = `
|
|
1006
|
+
SELECT ?p ?o WHERE {
|
|
1007
|
+
<${entityUri}> ?p ?o .
|
|
1008
|
+
} LIMIT ${limit}
|
|
1009
|
+
`
|
|
1010
|
+
return this.querySource(query)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Get entities by type from source graph
|
|
1015
|
+
*/
|
|
1016
|
+
async getEntitiesByType(typeUri, limit = 100) {
|
|
1017
|
+
const query = `
|
|
1018
|
+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
1019
|
+
SELECT ?entity WHERE {
|
|
1020
|
+
?entity rdf:type <${typeUri}> .
|
|
1021
|
+
} LIMIT ${limit}
|
|
1022
|
+
`
|
|
1023
|
+
return this.querySource(query)
|
|
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
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Get schema/ontology information
|
|
1049
|
+
*/
|
|
1050
|
+
async getSchema(prefix = null) {
|
|
1051
|
+
const query = `
|
|
1052
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
1053
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
1054
|
+
|
|
1055
|
+
SELECT ?class ?property ?domain ?range WHERE {
|
|
1056
|
+
{
|
|
1057
|
+
?class a owl:Class .
|
|
1058
|
+
} UNION {
|
|
1059
|
+
?property a owl:ObjectProperty .
|
|
1060
|
+
OPTIONAL { ?property rdfs:domain ?domain }
|
|
1061
|
+
OPTIONAL { ?property rdfs:range ?range }
|
|
1062
|
+
} UNION {
|
|
1063
|
+
?property a owl:DatatypeProperty .
|
|
1064
|
+
OPTIONAL { ?property rdfs:domain ?domain }
|
|
1065
|
+
OPTIONAL { ?property rdfs:range ?range }
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
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
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
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
|
+
return {
|
|
1100
|
+
source: this.sourceGraphUri,
|
|
1101
|
+
agent: this.agentGraphUri,
|
|
1102
|
+
separation: 'Source graph is read-only truth from ingestion. Agent graph is agent-writable memory.'
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* MemoryManager - Unified memory retrieval with weighted scoring
|
|
1109
|
+
*
|
|
1110
|
+
* Orchestrates all memory layers with intelligent retrieval:
|
|
1111
|
+
* - Score = α × Recency + β × Relevance + γ × Importance
|
|
1112
|
+
* - α = 0.3 (time decay), β = 0.5 (semantic similarity), γ = 0.2 (access frequency)
|
|
1113
|
+
*/
|
|
1114
|
+
class MemoryManager {
|
|
1115
|
+
constructor(runtime, config = {}) {
|
|
1116
|
+
this.runtime = runtime
|
|
1117
|
+
|
|
1118
|
+
// Initialize memory layers
|
|
1119
|
+
this.working = new WorkingMemory(config.workingCapacity || 100)
|
|
1120
|
+
this.episodic = new EpisodicMemory(config.graphDb, config.episodicNamespace)
|
|
1121
|
+
this.longTerm = new LongTermMemory(config.graphDb, {
|
|
1122
|
+
sourceGraphUri: config.sourceGraphUri,
|
|
1123
|
+
agentGraphUri: config.agentGraphUri
|
|
1124
|
+
})
|
|
1125
|
+
|
|
1126
|
+
// Retrieval weights
|
|
1127
|
+
this.weights = config.weights || {
|
|
1128
|
+
recency: 0.3,
|
|
1129
|
+
relevance: 0.5,
|
|
1130
|
+
importance: 0.2
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Access tracking for importance scoring
|
|
1134
|
+
this.accessCounts = new Map()
|
|
1135
|
+
this.retrievalHistory = []
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Unified memory retrieval with weighted scoring
|
|
1140
|
+
*/
|
|
1141
|
+
async retrieve(query, options = {}) {
|
|
1142
|
+
const results = {
|
|
1143
|
+
working: [],
|
|
1144
|
+
episodic: [],
|
|
1145
|
+
longTerm: [],
|
|
1146
|
+
combined: []
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// 1. Working Memory (always fast, in-memory)
|
|
1150
|
+
results.working = this._searchWorkingMemory(query)
|
|
1151
|
+
|
|
1152
|
+
// 2. Episodic Memory (past executions)
|
|
1153
|
+
if (options.includeEpisodic !== false) {
|
|
1154
|
+
const episodes = await this.episodic.getEpisodes(this.runtime.id, { limit: 20 })
|
|
1155
|
+
results.episodic = this._scoreEpisodicResults(episodes, query)
|
|
1156
|
+
}
|
|
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
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Get memory statistics
|
|
1320
|
+
*/
|
|
1321
|
+
getStats() {
|
|
1322
|
+
return {
|
|
1323
|
+
working: {
|
|
1324
|
+
contextSize: this.working.context.length,
|
|
1325
|
+
bindingsCount: this.working.bindings.size,
|
|
1326
|
+
toolResultsCount: this.working.toolResults.length
|
|
1327
|
+
},
|
|
1328
|
+
episodic: {
|
|
1329
|
+
episodeCount: this.episodic.getEpisodeCount()
|
|
1330
|
+
},
|
|
1331
|
+
longTerm: {
|
|
1332
|
+
accessLogSize: this.longTerm.accessLog.length
|
|
1333
|
+
},
|
|
1334
|
+
retrieval: {
|
|
1335
|
+
totalRetrievals: this.retrievalHistory.length,
|
|
1336
|
+
uniqueAccessedItems: this.accessCounts.size
|
|
1337
|
+
},
|
|
1338
|
+
weights: this.weights
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Clear working memory (episodic and long-term persist)
|
|
1344
|
+
*/
|
|
1345
|
+
clearWorking() {
|
|
1346
|
+
this.working.clear()
|
|
1347
|
+
return this
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// ============================================================================
|
|
1352
|
+
// GOVERNANCE LAYER (Policy Engine & Capability Grants)
|
|
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
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Check if capability is granted
|
|
1398
|
+
*/
|
|
1399
|
+
hasCapability(capability) {
|
|
1400
|
+
return this.capabilities.has(capability)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Grant a capability
|
|
1405
|
+
*/
|
|
1406
|
+
grantCapability(capability) {
|
|
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
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* GovernanceEngine - Enforces policies and manages capability grants
|
|
1444
|
+
*/
|
|
1445
|
+
class GovernanceEngine {
|
|
1446
|
+
constructor(policy = null) {
|
|
1447
|
+
this.policy = policy || new GovernancePolicy()
|
|
1448
|
+
this.auditLog = []
|
|
1449
|
+
this.denials = []
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Authorize an action
|
|
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
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// Check resource limits
|
|
1471
|
+
if (action.resource && action.current !== undefined) {
|
|
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
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// Check constraints
|
|
1480
|
+
if (action.type === 'externalApi' && !this.policy.constraints.allowExternalApi) {
|
|
1481
|
+
result.reason = 'External API access not allowed'
|
|
1482
|
+
this._logDenial(action, result.reason)
|
|
1483
|
+
return result
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
result.allowed = true
|
|
1487
|
+
result.reason = 'Authorized'
|
|
1488
|
+
this._logAudit(action, result)
|
|
1489
|
+
|
|
1490
|
+
return result
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Log audit entry
|
|
1495
|
+
*/
|
|
1496
|
+
_logAudit(action, result) {
|
|
1497
|
+
if (this.policy.audit.logAllToolCalls ||
|
|
1498
|
+
(this.policy.audit.logMemoryAccess && action.type === 'memory') ||
|
|
1499
|
+
(this.policy.audit.logGraphQueries && action.type === 'graphQuery')) {
|
|
1500
|
+
this.auditLog.push({
|
|
1501
|
+
action,
|
|
1502
|
+
result,
|
|
1503
|
+
timestamp: new Date().toISOString()
|
|
1504
|
+
})
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* Log denial
|
|
1510
|
+
*/
|
|
1511
|
+
_logDenial(action, reason) {
|
|
1512
|
+
this.denials.push({
|
|
1513
|
+
action,
|
|
1514
|
+
reason,
|
|
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)
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/**
|
|
1534
|
+
* Update policy
|
|
1535
|
+
*/
|
|
1536
|
+
setPolicy(policy) {
|
|
1537
|
+
this.policy = policy
|
|
1538
|
+
return this
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// ============================================================================
|
|
1543
|
+
// SCOPE LAYER (Namespace Isolation & Resource Limits)
|
|
1544
|
+
// ============================================================================
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* AgentScope - Defines namespace and resource boundaries for agents
|
|
1548
|
+
*/
|
|
1549
|
+
class AgentScope {
|
|
1550
|
+
constructor(config = {}) {
|
|
1551
|
+
this.id = config.id || `scope_${crypto.randomUUID()}`
|
|
1552
|
+
this.name = config.name || 'default-scope'
|
|
1553
|
+
|
|
1554
|
+
// Namespace configuration
|
|
1555
|
+
this.namespace = config.namespace || {
|
|
1556
|
+
prefix: 'http://hypermind.ai/agent/',
|
|
1557
|
+
graphUri: `http://hypermind.ai/agent/${this.id}/`,
|
|
1558
|
+
allowedGraphs: ['*'], // '*' means all, or list specific graph URIs
|
|
1559
|
+
deniedGraphs: []
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Resource limits
|
|
1563
|
+
this.resources = {
|
|
1564
|
+
maxMemoryMB: config.maxMemoryMB || 256,
|
|
1565
|
+
maxExecutionTimeMs: config.maxExecutionTimeMs || 60000,
|
|
1566
|
+
maxToolCalls: config.maxToolCalls || 100,
|
|
1567
|
+
maxGraphQueries: config.maxGraphQueries || 1000,
|
|
1568
|
+
maxEpisodes: config.maxEpisodes || 10000
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Current usage tracking
|
|
1572
|
+
this.usage = {
|
|
1573
|
+
memoryMB: 0,
|
|
1574
|
+
executionTimeMs: 0,
|
|
1575
|
+
toolCalls: 0,
|
|
1576
|
+
graphQueries: 0,
|
|
1577
|
+
episodes: 0
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Isolation settings
|
|
1581
|
+
this.isolation = {
|
|
1582
|
+
shareMemory: config.shareMemory || false,
|
|
1583
|
+
shareEpisodes: config.shareEpisodes || false,
|
|
1584
|
+
parentScope: config.parentScope || null
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Check if graph access is allowed
|
|
1590
|
+
*/
|
|
1591
|
+
isGraphAllowed(graphUri) {
|
|
1592
|
+
// Check denied first
|
|
1593
|
+
if (this.namespace.deniedGraphs.includes(graphUri)) {
|
|
1594
|
+
return false
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Check allowed
|
|
1598
|
+
if (this.namespace.allowedGraphs.includes('*')) {
|
|
1599
|
+
return true
|
|
1600
|
+
}
|
|
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
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* Track resource usage
|
|
1612
|
+
*/
|
|
1613
|
+
trackUsage(resource, amount) {
|
|
1614
|
+
if (this.usage[resource] !== undefined) {
|
|
1615
|
+
this.usage[resource] += amount
|
|
1616
|
+
}
|
|
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
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Get remaining resources
|
|
1630
|
+
*/
|
|
1631
|
+
getRemainingResources() {
|
|
1632
|
+
const remaining = {}
|
|
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)
|
|
1638
|
+
}
|
|
1639
|
+
return remaining
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Reset usage counters
|
|
1644
|
+
*/
|
|
1645
|
+
resetUsage() {
|
|
1646
|
+
for (const key of Object.keys(this.usage)) {
|
|
1647
|
+
this.usage[key] = 0
|
|
1648
|
+
}
|
|
1649
|
+
return this
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* Get scoped graph URI for agent
|
|
1654
|
+
*/
|
|
1655
|
+
getScopedGraphUri(suffix = '') {
|
|
1656
|
+
return `${this.namespace.graphUri}${suffix}`
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/**
|
|
1660
|
+
* Export scope configuration
|
|
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
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
491
1674
|
// ============================================================================
|
|
492
1675
|
// AGENT BUILDER (Fluent Composition Pattern)
|
|
493
1676
|
// ============================================================================
|
|
@@ -1547,5 +2730,20 @@ module.exports = {
|
|
|
1547
2730
|
LLMPlanner, // Natural language -> typed tool pipelines
|
|
1548
2731
|
WasmSandbox, // WASM sandbox with capability-based security
|
|
1549
2732
|
AgentBuilder, // Fluent builder for agent composition
|
|
1550
|
-
ComposedAgent
|
|
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
|
|
1551
2749
|
}
|