rust-kgdb 0.5.11 → 0.5.13

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.
@@ -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 // Composed agent with sandbox execution
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
  }