cognitive-engine 0.2.0__py3-none-any.whl

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.
Files changed (60) hide show
  1. cognitive_engine/__init__.py +7 -0
  2. cognitive_engine/adapters/__init__.py +2 -0
  3. cognitive_engine/adapters/plastic_numeric_adapter.py +141 -0
  4. cognitive_engine/api/__init__.py +2 -0
  5. cognitive_engine/api/service.py +41 -0
  6. cognitive_engine/compression/__init__.py +2 -0
  7. cognitive_engine/compression/knowledge_compressor.py +53 -0
  8. cognitive_engine/compression/semantic_compressor_v2.py +115 -0
  9. cognitive_engine/config/__init__.py +2 -0
  10. cognitive_engine/config/loader.py +44 -0
  11. cognitive_engine/config/schema.py +45 -0
  12. cognitive_engine/consolidation/__init__.py +2 -0
  13. cognitive_engine/consolidation/engine.py +25 -0
  14. cognitive_engine/consolidation/engine_v2.py +39 -0
  15. cognitive_engine/context/__init__.py +2 -0
  16. cognitive_engine/context/long_context.py +64 -0
  17. cognitive_engine/core/__init__.py +2 -0
  18. cognitive_engine/core/builder.py +154 -0
  19. cognitive_engine/core/engine.py +174 -0
  20. cognitive_engine/core/engine_v2.py +280 -0
  21. cognitive_engine/core/registry.py +29 -0
  22. cognitive_engine/core/types.py +346 -0
  23. cognitive_engine/interfaces/__init__.py +2 -0
  24. cognitive_engine/interfaces/base.py +181 -0
  25. cognitive_engine/memory/__init__.py +2 -0
  26. cognitive_engine/memory/graph_memory.py +165 -0
  27. cognitive_engine/memory/hybrid_memory.py +110 -0
  28. cognitive_engine/memory/project_memory.py +80 -0
  29. cognitive_engine/memory/stores.py +177 -0
  30. cognitive_engine/memory/vector_store.py +28 -0
  31. cognitive_engine/models/__init__.py +2 -0
  32. cognitive_engine/models/stable_core.py +79 -0
  33. cognitive_engine/modules/__init__.py +2 -0
  34. cognitive_engine/modules/importance_evaluator.py +96 -0
  35. cognitive_engine/modules/input_processing.py +78 -0
  36. cognitive_engine/modules/semantic_understanding.py +130 -0
  37. cognitive_engine/nlp/__init__.py +16 -0
  38. cognitive_engine/nlp/models.py +116 -0
  39. cognitive_engine/nlp/trainer.py +95 -0
  40. cognitive_engine/replay/__init__.py +2 -0
  41. cognitive_engine/replay/buffer.py +40 -0
  42. cognitive_engine/routing/__init__.py +2 -0
  43. cognitive_engine/routing/dynamic_router.py +45 -0
  44. cognitive_engine/routing/learned_router.py +165 -0
  45. cognitive_engine/specialists/__init__.py +2 -0
  46. cognitive_engine/specialists/runtime.py +97 -0
  47. cognitive_engine/stability/__init__.py +2 -0
  48. cognitive_engine/stability/governor.py +38 -0
  49. cognitive_engine/training/__init__.py +2 -0
  50. cognitive_engine/training/online_trainer.py +87 -0
  51. cognitive_engine/utils/__init__.py +2 -0
  52. cognitive_engine/utils/numeric.py +67 -0
  53. cognitive_engine/utils/seeding.py +13 -0
  54. cognitive_engine/utils/telemetry.py +39 -0
  55. cognitive_engine/utils/text.py +104 -0
  56. cognitive_engine/utils/visualization.py +87 -0
  57. cognitive_engine-0.2.0.dist-info/METADATA +91 -0
  58. cognitive_engine-0.2.0.dist-info/RECORD +60 -0
  59. cognitive_engine-0.2.0.dist-info/WHEEL +5 -0
  60. cognitive_engine-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,346 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+
11
+ @dataclass
12
+ class ProcessedInput:
13
+ raw_input: Any
14
+ modality: str
15
+ normalized_text: str = ""
16
+ tokens: List[str] = field(default_factory=list)
17
+ token_tensor: Optional[torch.Tensor] = None
18
+ attention_mask: Optional[torch.Tensor] = None
19
+ numeric_tensor: Optional[torch.Tensor] = None
20
+ operation: Optional[str] = None
21
+ metadata: Dict[str, Any] = field(default_factory=dict)
22
+
23
+
24
+ @dataclass
25
+ class SemanticConcept:
26
+ label: str
27
+ weight: float
28
+ source: str
29
+ attributes: Dict[str, Any] = field(default_factory=dict)
30
+
31
+
32
+ @dataclass
33
+ class SemanticState:
34
+ raw_input: Any
35
+ modality: str
36
+ sequence_embedding: Optional[torch.Tensor]
37
+ pooled_embedding: torch.Tensor
38
+ intent: str
39
+ entities: List[str]
40
+ concepts: List[SemanticConcept]
41
+ concept_graph_edges: List[Tuple[str, str, float]]
42
+ compressed_context: str
43
+ metadata: Dict[str, Any] = field(default_factory=dict)
44
+
45
+
46
+ @dataclass
47
+ class ImportanceAssessment:
48
+ novelty: float
49
+ utility: float
50
+ frequency: float
51
+ coherence: float
52
+ correction_signal: float
53
+ redundancy: float
54
+ future_relevance: float
55
+ contradiction_risk: float
56
+ importance_score: float
57
+ confidence_score: float
58
+ learning_priority: float
59
+ action: str
60
+ rationale: str
61
+
62
+
63
+ @dataclass
64
+ class CompressedKnowledge:
65
+ record_id: str
66
+ source_type: str
67
+ summary: str
68
+ concepts: List[str]
69
+ relations: List[Tuple[str, str, str]]
70
+ embedding: np.ndarray
71
+ importance: float
72
+ confidence: float
73
+ provenance: Dict[str, Any]
74
+ metadata: Dict[str, Any] = field(default_factory=dict)
75
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
76
+ reinforced_count: int = 1
77
+
78
+
79
+ @dataclass
80
+ class MemoryQuery:
81
+ embedding: np.ndarray
82
+ top_k: int = 5
83
+ modality: Optional[str] = None
84
+ intent: Optional[str] = None
85
+ concepts: List[str] = field(default_factory=list)
86
+
87
+
88
+ @dataclass
89
+ class MemoryBundle:
90
+ short_term: List[CompressedKnowledge] = field(default_factory=list)
91
+ working_memory: Dict[str, Any] = field(default_factory=dict)
92
+ semantic_long_term: List[CompressedKnowledge] = field(default_factory=list)
93
+ episodic: List[CompressedKnowledge] = field(default_factory=list)
94
+
95
+
96
+ @dataclass
97
+ class RoutingDecision:
98
+ active_modules: List[str]
99
+ consult_memories: List[str]
100
+ update_memory: bool
101
+ engage_plasticity: bool
102
+ compute_budget: float
103
+ rationale: str
104
+
105
+
106
+ @dataclass
107
+ class CoreInference:
108
+ prediction: Any
109
+ confidence: float
110
+ explanation: str
111
+ hidden_state: Optional[torch.Tensor] = None
112
+ artifacts: Dict[str, Any] = field(default_factory=dict)
113
+
114
+
115
+ @dataclass
116
+ class ReplaySample:
117
+ sample_id: str
118
+ payload: Dict[str, Any]
119
+ target: Any
120
+ priority: float
121
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
122
+
123
+
124
+ @dataclass
125
+ class TraceEvent:
126
+ stage: str
127
+ message: str
128
+ payload: Dict[str, Any] = field(default_factory=dict)
129
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
130
+
131
+
132
+ @dataclass
133
+ class EngineResponse:
134
+ text: str
135
+ semantic_state: SemanticState
136
+ importance: ImportanceAssessment
137
+ routing: RoutingDecision
138
+ memory_bundle: MemoryBundle
139
+ inference: CoreInference
140
+ traces: List[TraceEvent]
141
+ learning_applied: bool
142
+
143
+
144
+ @dataclass
145
+ class NumericBatch:
146
+ features: torch.Tensor
147
+ operation_ids: torch.Tensor
148
+ targets: Optional[torch.Tensor] = None
149
+ metadata: Dict[str, Any] = field(default_factory=dict)
150
+
151
+
152
+ @dataclass
153
+ class TrainingMetrics:
154
+ epoch: int
155
+ loss: float
156
+ mae: float
157
+ accuracy: float
158
+ replay_ratio: float
159
+ plastic_norm: float
160
+ stable_drift: float
161
+
162
+
163
+ @dataclass
164
+ class ConsolidationReport:
165
+ merged_records: int
166
+ pruned_records: int
167
+ replay_reweighted: int
168
+ notes: str
169
+
170
+
171
+ @dataclass
172
+ class KnowledgeTriple:
173
+ subject: str
174
+ relation: str
175
+ object: str
176
+ confidence: float = 0.7
177
+ evidence: List[str] = field(default_factory=list)
178
+
179
+
180
+ @dataclass
181
+ class GraphNode:
182
+ node_id: str
183
+ node_type: str
184
+ label: str
185
+ namespace: str = ""
186
+ project_id: str = "global"
187
+ confidence: float = 0.7
188
+ version: int = 1
189
+ metadata: Dict[str, Any] = field(default_factory=dict)
190
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
191
+ valid_from: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
192
+ valid_to: Optional[datetime] = None
193
+
194
+
195
+ @dataclass
196
+ class GraphEdge:
197
+ edge_id: str
198
+ source_id: str
199
+ target_id: str
200
+ relation: str
201
+ weight: float = 1.0
202
+ confidence: float = 0.7
203
+ evidence_ids: List[str] = field(default_factory=list)
204
+ metadata: Dict[str, Any] = field(default_factory=dict)
205
+
206
+
207
+ @dataclass
208
+ class GraphPatch:
209
+ nodes: List[GraphNode] = field(default_factory=list)
210
+ edges: List[GraphEdge] = field(default_factory=list)
211
+ source: str = "unknown"
212
+
213
+
214
+ @dataclass
215
+ class GraphQuery:
216
+ seeds: List[str] = field(default_factory=list)
217
+ relation_filter: List[str] = field(default_factory=list)
218
+ project_id: Optional[str] = None
219
+ max_depth: int = 2
220
+ top_k: int = 12
221
+
222
+
223
+ @dataclass
224
+ class GraphSubgraph:
225
+ nodes: List[GraphNode] = field(default_factory=list)
226
+ edges: List[GraphEdge] = field(default_factory=list)
227
+ rationale: str = ""
228
+
229
+
230
+ @dataclass
231
+ class ProcedureMemory:
232
+ procedure_id: str
233
+ title: str
234
+ steps: List[str]
235
+ domains: List[str]
236
+ confidence: float
237
+ evidence_ids: List[str] = field(default_factory=list)
238
+
239
+
240
+ @dataclass
241
+ class ProjectMemoryRecord:
242
+ project_id: str
243
+ root_path: str
244
+ files_indexed: int
245
+ modules: List[str]
246
+ dependencies: List[str]
247
+ tests: List[str]
248
+ commands: List[str] = field(default_factory=list)
249
+ metadata: Dict[str, Any] = field(default_factory=dict)
250
+
251
+
252
+ @dataclass
253
+ class PreferenceRecord:
254
+ key: str
255
+ value: str
256
+ confidence: float
257
+ source: str
258
+ updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
259
+
260
+
261
+ @dataclass
262
+ class EvidenceRecord:
263
+ evidence_id: str
264
+ source: str
265
+ quote: str
266
+ confidence: float
267
+ metadata: Dict[str, Any] = field(default_factory=dict)
268
+
269
+
270
+ @dataclass
271
+ class SemanticStateV2(SemanticState):
272
+ code_symbols: List[str] = field(default_factory=list)
273
+ graph_candidates: List[GraphPatch] = field(default_factory=list)
274
+ uncertainty: float = 0.0
275
+ modality_features: Dict[str, Any] = field(default_factory=dict)
276
+
277
+
278
+ @dataclass
279
+ class MemoryQueryV2(MemoryQuery):
280
+ project_id: Optional[str] = None
281
+ include_graph: bool = True
282
+ include_procedures: bool = True
283
+ include_project: bool = True
284
+ include_preferences: bool = True
285
+ graph_query: Optional[GraphQuery] = None
286
+
287
+
288
+ @dataclass
289
+ class MemoryBundleV2(MemoryBundle):
290
+ procedural: List[ProcedureMemory] = field(default_factory=list)
291
+ project: List[ProjectMemoryRecord] = field(default_factory=list)
292
+ graph_subgraph: Optional[GraphSubgraph] = None
293
+ preferences: List[PreferenceRecord] = field(default_factory=list)
294
+ evidence: List[EvidenceRecord] = field(default_factory=list)
295
+
296
+
297
+ @dataclass
298
+ class RoutingDecisionV2(RoutingDecision):
299
+ selected_specialists: List[str] = field(default_factory=list)
300
+ memory_plan: Dict[str, float] = field(default_factory=dict)
301
+ tool_plan: List[str] = field(default_factory=list)
302
+ context_budget: int = 8192
303
+ learning_action: str = "observe"
304
+ consolidation_action: str = "none"
305
+ confidence: float = 0.5
306
+ gate_scores: Dict[str, float] = field(default_factory=dict)
307
+ fallback_plan: str = "heuristic_router"
308
+
309
+
310
+ @dataclass
311
+ class CompressionBundle:
312
+ compressed_knowledge: List[CompressedKnowledge] = field(default_factory=list)
313
+ triples: List[KnowledgeTriple] = field(default_factory=list)
314
+ graph_patch: GraphPatch = field(default_factory=GraphPatch)
315
+ replay_samples: List[ReplaySample] = field(default_factory=list)
316
+ validation_requests: List[str] = field(default_factory=list)
317
+ preferences: List[PreferenceRecord] = field(default_factory=list)
318
+ procedures: List[ProcedureMemory] = field(default_factory=list)
319
+
320
+
321
+ @dataclass
322
+ class ContextPackage:
323
+ prompt: str
324
+ evidence_ids: List[str]
325
+ token_estimate: int
326
+ sections: Dict[str, str] = field(default_factory=dict)
327
+
328
+
329
+ @dataclass
330
+ class StabilityDecision:
331
+ approved: bool
332
+ confidence: float
333
+ risk_score: float
334
+ reasons: List[str] = field(default_factory=list)
335
+
336
+
337
+ @dataclass
338
+ class EngineResponseV2(EngineResponse):
339
+ routing_v2: Optional[RoutingDecisionV2] = None
340
+ memory_bundle_v2: Optional[MemoryBundleV2] = None
341
+ context_package: Optional[ContextPackage] = None
342
+ stability_decision: Optional[StabilityDecision] = None
343
+
344
+
345
+ def tensor_to_numpy(tensor: torch.Tensor) -> np.ndarray:
346
+ return tensor.detach().cpu().numpy()
@@ -0,0 +1,2 @@
1
+ """Public interfaces and contracts."""
2
+
@@ -0,0 +1,181 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, Iterable, List, Optional
5
+
6
+ from cognitive_engine.core.types import (
7
+ CompressedKnowledge,
8
+ ConsolidationReport,
9
+ CoreInference,
10
+ ImportanceAssessment,
11
+ MemoryBundle,
12
+ MemoryQuery,
13
+ NumericBatch,
14
+ ProcessedInput,
15
+ ReplaySample,
16
+ RoutingDecision,
17
+ RoutingDecisionV2,
18
+ SemanticState,
19
+ SemanticStateV2,
20
+ TraceEvent,
21
+ )
22
+
23
+
24
+ class ConfigurableComponent(ABC):
25
+ name: str
26
+
27
+ @abstractmethod
28
+ def describe(self) -> Dict[str, Any]:
29
+ raise NotImplementedError
30
+
31
+
32
+ class InputProcessor(ConfigurableComponent, ABC):
33
+ @abstractmethod
34
+ def supports(self, payload: Any) -> bool:
35
+ raise NotImplementedError
36
+
37
+ @abstractmethod
38
+ def process(self, payload: Any) -> ProcessedInput:
39
+ raise NotImplementedError
40
+
41
+
42
+ class SemanticEncoder(ConfigurableComponent, ABC):
43
+ @abstractmethod
44
+ def encode(self, processed: ProcessedInput) -> SemanticState:
45
+ raise NotImplementedError
46
+
47
+
48
+ class ImportanceEvaluator(ConfigurableComponent, ABC):
49
+ @abstractmethod
50
+ def evaluate(self, semantic_state: SemanticState, memory_bundle: MemoryBundle) -> ImportanceAssessment:
51
+ raise NotImplementedError
52
+
53
+
54
+ class KnowledgeCompressor(ConfigurableComponent, ABC):
55
+ @abstractmethod
56
+ def compress(self, semantic_state: SemanticState, importance: ImportanceAssessment) -> CompressedKnowledge:
57
+ raise NotImplementedError
58
+
59
+
60
+ class MemoryStore(ConfigurableComponent, ABC):
61
+ @abstractmethod
62
+ def write(self, knowledge: CompressedKnowledge) -> None:
63
+ raise NotImplementedError
64
+
65
+ @abstractmethod
66
+ def search(self, query: MemoryQuery) -> List[CompressedKnowledge]:
67
+ raise NotImplementedError
68
+
69
+ @abstractmethod
70
+ def snapshot(self) -> List[CompressedKnowledge]:
71
+ raise NotImplementedError
72
+
73
+
74
+ class WorkingMemory(ConfigurableComponent, ABC):
75
+ @abstractmethod
76
+ def update(self, semantic_state: SemanticState) -> Dict[str, Any]:
77
+ raise NotImplementedError
78
+
79
+ @abstractmethod
80
+ def snapshot(self) -> Dict[str, Any]:
81
+ raise NotImplementedError
82
+
83
+
84
+ class Router(ConfigurableComponent, ABC):
85
+ @abstractmethod
86
+ def route(self, processed: ProcessedInput, semantic_state: Optional[SemanticState] = None) -> RoutingDecision:
87
+ raise NotImplementedError
88
+
89
+
90
+ class StableCore(ConfigurableComponent, ABC):
91
+ @abstractmethod
92
+ def infer(
93
+ self,
94
+ semantic_state: SemanticState,
95
+ memory_bundle: MemoryBundle,
96
+ routing: RoutingDecision,
97
+ plastic_output: Optional[CoreInference] = None,
98
+ ) -> CoreInference:
99
+ raise NotImplementedError
100
+
101
+
102
+ class PlasticLearner(ConfigurableComponent, ABC):
103
+ @abstractmethod
104
+ def predict(self, semantic_state: SemanticState) -> CoreInference:
105
+ raise NotImplementedError
106
+
107
+ @abstractmethod
108
+ def train_step(self, batch: NumericBatch, replay_batch: Optional[NumericBatch] = None) -> Dict[str, float]:
109
+ raise NotImplementedError
110
+
111
+ @abstractmethod
112
+ def parameter_drift(self) -> float:
113
+ raise NotImplementedError
114
+
115
+
116
+ class ReplayBuffer(ConfigurableComponent, ABC):
117
+ @abstractmethod
118
+ def add(self, sample: ReplaySample) -> None:
119
+ raise NotImplementedError
120
+
121
+ @abstractmethod
122
+ def sample(self, batch_size: int) -> List[ReplaySample]:
123
+ raise NotImplementedError
124
+
125
+ @abstractmethod
126
+ def __len__(self) -> int:
127
+ raise NotImplementedError
128
+
129
+
130
+ class Consolidator(ConfigurableComponent, ABC):
131
+ @abstractmethod
132
+ def run(self) -> ConsolidationReport:
133
+ raise NotImplementedError
134
+
135
+
136
+ class TelemetrySink(ConfigurableComponent, ABC):
137
+ @abstractmethod
138
+ def emit(self, event: TraceEvent) -> None:
139
+ raise NotImplementedError
140
+
141
+ @abstractmethod
142
+ def flush(self) -> None:
143
+ raise NotImplementedError
144
+
145
+
146
+ class LearnedRouter(Router, ABC):
147
+ @abstractmethod
148
+ def route_v2(self, semantic_state: SemanticStateV2, runtime_context: Dict[str, Any]) -> RoutingDecisionV2:
149
+ raise NotImplementedError
150
+
151
+
152
+ class GraphMemoryStore(ConfigurableComponent, ABC):
153
+ @abstractmethod
154
+ def apply_patch(self, patch: Any) -> None:
155
+ raise NotImplementedError
156
+
157
+ @abstractmethod
158
+ def query_subgraph(self, query: Any) -> Any:
159
+ raise NotImplementedError
160
+
161
+
162
+ class Specialist(ConfigurableComponent, ABC):
163
+ @abstractmethod
164
+ def can_handle(self, semantic_state: SemanticStateV2) -> float:
165
+ raise NotImplementedError
166
+
167
+ @abstractmethod
168
+ def prepare_context(self, semantic_state: SemanticStateV2, memory_bundle: Any) -> Dict[str, Any]:
169
+ raise NotImplementedError
170
+
171
+
172
+ class LongContextManager(ConfigurableComponent, ABC):
173
+ @abstractmethod
174
+ def compose(self, request: Dict[str, Any]) -> Any:
175
+ raise NotImplementedError
176
+
177
+
178
+ class StabilityGovernor(ConfigurableComponent, ABC):
179
+ @abstractmethod
180
+ def validate_update(self, update: Dict[str, Any]) -> Any:
181
+ raise NotImplementedError
@@ -0,0 +1,2 @@
1
+ """Hierarchical memory subsystems."""
2
+
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict
4
+ from typing import Any, Dict, List
5
+ from uuid import uuid4
6
+
7
+ import networkx as nx
8
+
9
+ from cognitive_engine.core.types import GraphEdge, GraphNode, GraphPatch, GraphQuery, GraphSubgraph
10
+ from cognitive_engine.interfaces.base import GraphMemoryStore
11
+
12
+
13
+ class CognitiveGraphMemory(GraphMemoryStore):
14
+ name = "cognitive_graph_memory"
15
+
16
+ def __init__(self) -> None:
17
+ self.graph = nx.MultiDiGraph()
18
+ self.nodes: Dict[str, GraphNode] = {}
19
+ self.edges: Dict[str, GraphEdge] = {}
20
+ self.label_index: Dict[str, str] = {}
21
+
22
+ def describe(self) -> Dict[str, Any]:
23
+ return {"name": self.name, "nodes": len(self.nodes), "edges": len(self.edges)}
24
+
25
+ def upsert_node(
26
+ self,
27
+ label: str,
28
+ node_type: str,
29
+ namespace: str = "",
30
+ project_id: str = "global",
31
+ confidence: float = 0.75,
32
+ metadata: Dict[str, Any] | None = None,
33
+ ) -> GraphNode:
34
+ key = self._key(label, node_type, namespace, project_id)
35
+ existing_id = self.label_index.get(key)
36
+ if existing_id:
37
+ node = self.nodes[existing_id]
38
+ node.confidence = max(node.confidence, confidence)
39
+ node.version += 1
40
+ node.metadata.update(metadata or {})
41
+ self.graph.nodes[existing_id].update(asdict(node))
42
+ return node
43
+
44
+ node = GraphNode(
45
+ node_id=str(uuid4()),
46
+ node_type=node_type,
47
+ label=label,
48
+ namespace=namespace,
49
+ project_id=project_id,
50
+ confidence=confidence,
51
+ metadata=metadata or {},
52
+ )
53
+ self.nodes[node.node_id] = node
54
+ self.label_index[key] = node.node_id
55
+ self.graph.add_node(node.node_id, **asdict(node))
56
+ return node
57
+
58
+ def upsert_edge(
59
+ self,
60
+ source: GraphNode,
61
+ target: GraphNode,
62
+ relation: str,
63
+ weight: float = 1.0,
64
+ confidence: float = 0.75,
65
+ evidence_ids: List[str] | None = None,
66
+ metadata: Dict[str, Any] | None = None,
67
+ ) -> GraphEdge:
68
+ for edge_id, edge in self.edges.items():
69
+ if edge.source_id == source.node_id and edge.target_id == target.node_id and edge.relation == relation:
70
+ edge.weight = max(edge.weight, weight)
71
+ edge.confidence = max(edge.confidence, confidence)
72
+ edge.evidence_ids = list(dict.fromkeys(edge.evidence_ids + (evidence_ids or [])))
73
+ edge.metadata.update(metadata or {})
74
+ self.graph[source.node_id][target.node_id][edge_id].update(asdict(edge))
75
+ return edge
76
+
77
+ edge = GraphEdge(
78
+ edge_id=str(uuid4()),
79
+ source_id=source.node_id,
80
+ target_id=target.node_id,
81
+ relation=relation,
82
+ weight=weight,
83
+ confidence=confidence,
84
+ evidence_ids=evidence_ids or [],
85
+ metadata=metadata or {},
86
+ )
87
+ self.edges[edge.edge_id] = edge
88
+ self.graph.add_edge(source.node_id, target.node_id, key=edge.edge_id, **asdict(edge))
89
+ return edge
90
+
91
+ def apply_patch(self, patch: GraphPatch) -> None:
92
+ id_map: Dict[str, GraphNode] = {}
93
+ for node in patch.nodes:
94
+ upserted = self.upsert_node(
95
+ label=node.label,
96
+ node_type=node.node_type,
97
+ namespace=node.namespace,
98
+ project_id=node.project_id,
99
+ confidence=node.confidence,
100
+ metadata=node.metadata,
101
+ )
102
+ id_map[node.node_id] = upserted
103
+ for edge in patch.edges:
104
+ source = id_map.get(edge.source_id) or self.nodes.get(edge.source_id)
105
+ target = id_map.get(edge.target_id) or self.nodes.get(edge.target_id)
106
+ if source and target:
107
+ self.upsert_edge(source, target, edge.relation, edge.weight, edge.confidence, edge.evidence_ids, edge.metadata)
108
+
109
+ def query_subgraph(self, query: GraphQuery) -> GraphSubgraph:
110
+ if not self.nodes:
111
+ return GraphSubgraph(rationale="Graph is empty.")
112
+
113
+ seeds = self._resolve_seeds(query)
114
+ visited = set(seeds)
115
+ frontier = set(seeds)
116
+ for _ in range(max(query.max_depth, 0)):
117
+ next_frontier = set()
118
+ for node_id in frontier:
119
+ neighbors = set(self.graph.successors(node_id)) | set(self.graph.predecessors(node_id))
120
+ next_frontier.update(neighbors)
121
+ next_frontier -= visited
122
+ visited.update(next_frontier)
123
+ frontier = next_frontier
124
+
125
+ nodes = [self.nodes[node_id] for node_id in visited if self._matches_project(self.nodes[node_id], query)]
126
+ node_ids = {node.node_id for node in nodes}
127
+ edges = [
128
+ edge
129
+ for edge in self.edges.values()
130
+ if edge.source_id in node_ids
131
+ and edge.target_id in node_ids
132
+ and (not query.relation_filter or edge.relation in query.relation_filter)
133
+ ]
134
+ nodes = nodes[: query.top_k]
135
+ node_ids = {node.node_id for node in nodes}
136
+ edges = [edge for edge in edges if edge.source_id in node_ids and edge.target_id in node_ids]
137
+ return GraphSubgraph(nodes=nodes, edges=edges, rationale=f"Expanded {len(seeds)} seed nodes to {len(nodes)} nodes.")
138
+
139
+ def snapshot(self) -> Dict[str, Any]:
140
+ type_counts: Dict[str, int] = {}
141
+ relation_counts: Dict[str, int] = {}
142
+ for node in self.nodes.values():
143
+ type_counts[node.node_type] = type_counts.get(node.node_type, 0) + 1
144
+ for edge in self.edges.values():
145
+ relation_counts[edge.relation] = relation_counts.get(edge.relation, 0) + 1
146
+ return {"nodes": len(self.nodes), "edges": len(self.edges), "node_types": type_counts, "relations": relation_counts}
147
+
148
+ def _resolve_seeds(self, query: GraphQuery) -> List[str]:
149
+ if not query.seeds:
150
+ return list(self.nodes)[: query.top_k]
151
+ resolved = []
152
+ normalized_seeds = [seed.lower() for seed in query.seeds]
153
+ for node_id, node in self.nodes.items():
154
+ label = node.label.lower()
155
+ if any(seed in label or label in seed for seed in normalized_seeds):
156
+ if self._matches_project(node, query):
157
+ resolved.append(node_id)
158
+ return resolved[: query.top_k] or list(self.nodes)[: min(query.top_k, len(self.nodes))]
159
+
160
+ def _matches_project(self, node: GraphNode, query: GraphQuery) -> bool:
161
+ return query.project_id is None or node.project_id in {query.project_id, "global"}
162
+
163
+ def _key(self, label: str, node_type: str, namespace: str, project_id: str) -> str:
164
+ return f"{project_id}:{namespace}:{node_type}:{label}".lower()
165
+