neural-memory 0.2.0__py3-none-any.whl → 0.4.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.
- neural_memory/__init__.py +1 -1
- neural_memory/cli/main.py +201 -0
- neural_memory/engine/lifecycle.py +233 -0
- neural_memory/mcp/server.py +1 -1
- neural_memory/server/app.py +68 -0
- neural_memory/server/static/index.html +403 -0
- neural_memory/storage/sqlite_store.py +16 -0
- {neural_memory-0.2.0.dist-info → neural_memory-0.4.0.dist-info}/METADATA +1 -1
- {neural_memory-0.2.0.dist-info → neural_memory-0.4.0.dist-info}/RECORD +12 -10
- {neural_memory-0.2.0.dist-info → neural_memory-0.4.0.dist-info}/WHEEL +0 -0
- {neural_memory-0.2.0.dist-info → neural_memory-0.4.0.dist-info}/entry_points.txt +0 -0
- {neural_memory-0.2.0.dist-info → neural_memory-0.4.0.dist-info}/licenses/LICENSE +0 -0
neural_memory/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from neural_memory.core.synapse import Direction, Synapse, SynapseType
|
|
|
13
13
|
from neural_memory.engine.encoder import EncodingResult, MemoryEncoder
|
|
14
14
|
from neural_memory.engine.retrieval import DepthLevel, ReflexPipeline, RetrievalResult
|
|
15
15
|
|
|
16
|
-
__version__ = "0.
|
|
16
|
+
__version__ = "0.4.0"
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"__version__",
|
neural_memory/cli/main.py
CHANGED
|
@@ -2768,6 +2768,207 @@ def prompt(
|
|
|
2768
2768
|
typer.echo(text)
|
|
2769
2769
|
|
|
2770
2770
|
|
|
2771
|
+
@app.command(name="export")
|
|
2772
|
+
def export_brain_cmd(
|
|
2773
|
+
output: Annotated[
|
|
2774
|
+
str, typer.Argument(help="Output file path (e.g., my-brain.json)")
|
|
2775
|
+
],
|
|
2776
|
+
brain: Annotated[
|
|
2777
|
+
str | None, typer.Option("--brain", "-b", help="Brain to export (default: current)")
|
|
2778
|
+
] = None,
|
|
2779
|
+
) -> None:
|
|
2780
|
+
"""Export brain to JSON file for backup or sharing.
|
|
2781
|
+
|
|
2782
|
+
Examples:
|
|
2783
|
+
nmem export backup.json # Export current brain
|
|
2784
|
+
nmem export work.json -b work # Export specific brain
|
|
2785
|
+
"""
|
|
2786
|
+
from pathlib import Path
|
|
2787
|
+
|
|
2788
|
+
from neural_memory.unified_config import get_config, get_shared_storage
|
|
2789
|
+
|
|
2790
|
+
async def _export() -> None:
|
|
2791
|
+
config = get_config()
|
|
2792
|
+
brain_name = brain or config.current_brain
|
|
2793
|
+
storage = await get_shared_storage(brain_name)
|
|
2794
|
+
|
|
2795
|
+
snapshot = await storage.export_brain(brain_name)
|
|
2796
|
+
|
|
2797
|
+
output_path = Path(output)
|
|
2798
|
+
export_data = {
|
|
2799
|
+
"brain_id": snapshot.brain_id,
|
|
2800
|
+
"brain_name": snapshot.brain_name,
|
|
2801
|
+
"exported_at": snapshot.exported_at.isoformat(),
|
|
2802
|
+
"version": snapshot.version,
|
|
2803
|
+
"neurons": snapshot.neurons,
|
|
2804
|
+
"synapses": snapshot.synapses,
|
|
2805
|
+
"fibers": snapshot.fibers,
|
|
2806
|
+
"config": snapshot.config,
|
|
2807
|
+
"metadata": snapshot.metadata,
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
output_path.write_text(json.dumps(export_data, indent=2, default=str))
|
|
2811
|
+
|
|
2812
|
+
typer.echo(f"Exported brain '{brain_name}' to {output_path}")
|
|
2813
|
+
typer.echo(f" Neurons: {len(snapshot.neurons)}")
|
|
2814
|
+
typer.echo(f" Synapses: {len(snapshot.synapses)}")
|
|
2815
|
+
typer.echo(f" Fibers: {len(snapshot.fibers)}")
|
|
2816
|
+
|
|
2817
|
+
asyncio.run(_export())
|
|
2818
|
+
|
|
2819
|
+
|
|
2820
|
+
@app.command(name="import")
|
|
2821
|
+
def import_brain_cmd(
|
|
2822
|
+
input_file: Annotated[
|
|
2823
|
+
str, typer.Argument(help="Input file path (e.g., my-brain.json)")
|
|
2824
|
+
],
|
|
2825
|
+
brain: Annotated[
|
|
2826
|
+
str | None, typer.Option("--brain", "-b", help="Target brain name (default: from file)")
|
|
2827
|
+
] = None,
|
|
2828
|
+
merge: Annotated[
|
|
2829
|
+
bool, typer.Option("--merge", "-m", help="Merge with existing brain")
|
|
2830
|
+
] = False,
|
|
2831
|
+
) -> None:
|
|
2832
|
+
"""Import brain from JSON file.
|
|
2833
|
+
|
|
2834
|
+
Examples:
|
|
2835
|
+
nmem import backup.json # Import as original brain name
|
|
2836
|
+
nmem import backup.json -b new # Import as 'new' brain
|
|
2837
|
+
nmem import backup.json --merge # Merge into existing brain
|
|
2838
|
+
"""
|
|
2839
|
+
from pathlib import Path
|
|
2840
|
+
|
|
2841
|
+
from neural_memory.core.brain import BrainSnapshot
|
|
2842
|
+
from neural_memory.unified_config import get_shared_storage
|
|
2843
|
+
|
|
2844
|
+
async def _import() -> None:
|
|
2845
|
+
input_path = Path(input_file)
|
|
2846
|
+
if not input_path.exists():
|
|
2847
|
+
typer.echo(f"Error: File not found: {input_path}", err=True)
|
|
2848
|
+
raise typer.Exit(1)
|
|
2849
|
+
|
|
2850
|
+
data = json.loads(input_path.read_text())
|
|
2851
|
+
|
|
2852
|
+
brain_name = brain or data.get("brain_name", "imported")
|
|
2853
|
+
storage = await get_shared_storage(brain_name)
|
|
2854
|
+
|
|
2855
|
+
snapshot = BrainSnapshot(
|
|
2856
|
+
brain_id=data.get("brain_id", brain_name),
|
|
2857
|
+
brain_name=data["brain_name"],
|
|
2858
|
+
exported_at=datetime.fromisoformat(data["exported_at"]),
|
|
2859
|
+
version=data["version"],
|
|
2860
|
+
neurons=data["neurons"],
|
|
2861
|
+
synapses=data["synapses"],
|
|
2862
|
+
fibers=data["fibers"],
|
|
2863
|
+
config=data["config"],
|
|
2864
|
+
metadata=data.get("metadata", {}),
|
|
2865
|
+
)
|
|
2866
|
+
|
|
2867
|
+
imported_id = await storage.import_brain(snapshot, brain_name)
|
|
2868
|
+
|
|
2869
|
+
typer.echo(f"Imported brain '{brain_name}' from {input_path}")
|
|
2870
|
+
typer.echo(f" Neurons: {len(snapshot.neurons)}")
|
|
2871
|
+
typer.echo(f" Synapses: {len(snapshot.synapses)}")
|
|
2872
|
+
typer.echo(f" Fibers: {len(snapshot.fibers)}")
|
|
2873
|
+
|
|
2874
|
+
asyncio.run(_import())
|
|
2875
|
+
|
|
2876
|
+
|
|
2877
|
+
@app.command()
|
|
2878
|
+
def serve(
|
|
2879
|
+
host: Annotated[
|
|
2880
|
+
str, typer.Option("--host", "-h", help="Host to bind to")
|
|
2881
|
+
] = "127.0.0.1",
|
|
2882
|
+
port: Annotated[
|
|
2883
|
+
int, typer.Option("--port", "-p", help="Port to bind to")
|
|
2884
|
+
] = 8000,
|
|
2885
|
+
reload: Annotated[
|
|
2886
|
+
bool, typer.Option("--reload", "-r", help="Enable auto-reload for development")
|
|
2887
|
+
] = False,
|
|
2888
|
+
) -> None:
|
|
2889
|
+
"""Run the NeuralMemory API server.
|
|
2890
|
+
|
|
2891
|
+
Examples:
|
|
2892
|
+
nmem serve # Run on localhost:8000
|
|
2893
|
+
nmem serve -p 9000 # Run on port 9000
|
|
2894
|
+
nmem serve --host 0.0.0.0 # Expose to network
|
|
2895
|
+
nmem serve --reload # Development mode
|
|
2896
|
+
"""
|
|
2897
|
+
try:
|
|
2898
|
+
import uvicorn
|
|
2899
|
+
except ImportError:
|
|
2900
|
+
typer.echo("Error: uvicorn not installed. Run: pip install neural-memory[server]", err=True)
|
|
2901
|
+
raise typer.Exit(1)
|
|
2902
|
+
|
|
2903
|
+
typer.echo(f"Starting NeuralMemory API server on http://{host}:{port}")
|
|
2904
|
+
typer.echo(f" UI: http://{host}:{port}/ui")
|
|
2905
|
+
typer.echo(f" Docs: http://{host}:{port}/docs")
|
|
2906
|
+
|
|
2907
|
+
uvicorn.run(
|
|
2908
|
+
"neural_memory.server.app:create_app",
|
|
2909
|
+
host=host,
|
|
2910
|
+
port=port,
|
|
2911
|
+
reload=reload,
|
|
2912
|
+
factory=True,
|
|
2913
|
+
)
|
|
2914
|
+
|
|
2915
|
+
|
|
2916
|
+
@app.command()
|
|
2917
|
+
def decay(
|
|
2918
|
+
brain: Annotated[
|
|
2919
|
+
str | None, typer.Option("--brain", "-b", help="Brain to apply decay to")
|
|
2920
|
+
] = None,
|
|
2921
|
+
dry_run: Annotated[
|
|
2922
|
+
bool, typer.Option("--dry-run", "-n", help="Preview changes without applying")
|
|
2923
|
+
] = False,
|
|
2924
|
+
prune_threshold: Annotated[
|
|
2925
|
+
float, typer.Option("--prune", "-p", help="Prune below this activation level")
|
|
2926
|
+
] = 0.01,
|
|
2927
|
+
) -> None:
|
|
2928
|
+
"""Apply memory decay to simulate forgetting.
|
|
2929
|
+
|
|
2930
|
+
Memories that haven't been accessed recently will have their
|
|
2931
|
+
activation levels reduced following the Ebbinghaus forgetting curve.
|
|
2932
|
+
|
|
2933
|
+
Examples:
|
|
2934
|
+
nmem decay # Apply decay to current brain
|
|
2935
|
+
nmem decay -b work # Apply to specific brain
|
|
2936
|
+
nmem decay --dry-run # Preview without changes
|
|
2937
|
+
nmem decay --prune 0.05 # More aggressive pruning
|
|
2938
|
+
"""
|
|
2939
|
+
from neural_memory.engine.lifecycle import DecayManager
|
|
2940
|
+
from neural_memory.unified_config import get_config, get_shared_storage
|
|
2941
|
+
|
|
2942
|
+
async def _decay() -> None:
|
|
2943
|
+
config = get_config()
|
|
2944
|
+
brain_name = brain or config.current_brain
|
|
2945
|
+
|
|
2946
|
+
typer.echo(f"Applying decay to brain '{brain_name}'...")
|
|
2947
|
+
if dry_run:
|
|
2948
|
+
typer.echo("(dry run - no changes will be saved)")
|
|
2949
|
+
|
|
2950
|
+
storage = await get_shared_storage(brain_name)
|
|
2951
|
+
|
|
2952
|
+
manager = DecayManager(
|
|
2953
|
+
decay_rate=config.brain.decay_rate,
|
|
2954
|
+
prune_threshold=prune_threshold,
|
|
2955
|
+
)
|
|
2956
|
+
|
|
2957
|
+
report = await manager.apply_decay(storage, dry_run=dry_run)
|
|
2958
|
+
|
|
2959
|
+
typer.echo("")
|
|
2960
|
+
typer.echo(report.summary())
|
|
2961
|
+
|
|
2962
|
+
if report.neurons_pruned > 0 or report.synapses_pruned > 0:
|
|
2963
|
+
typer.echo("")
|
|
2964
|
+
typer.echo(
|
|
2965
|
+
f"Pruned {report.neurons_pruned} neurons and "
|
|
2966
|
+
f"{report.synapses_pruned} synapses below threshold {prune_threshold}"
|
|
2967
|
+
)
|
|
2968
|
+
|
|
2969
|
+
asyncio.run(_decay())
|
|
2970
|
+
|
|
2971
|
+
|
|
2771
2972
|
@app.command()
|
|
2772
2973
|
def version() -> None:
|
|
2773
2974
|
"""Show version information."""
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Memory lifecycle management - decay, reinforcement, compression.
|
|
2
|
+
|
|
3
|
+
Implements the Ebbinghaus forgetting curve for natural memory decay
|
|
4
|
+
and reinforcement for frequently accessed memories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from neural_memory.storage.base import NeuralStorage
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class DecayReport:
|
|
20
|
+
"""Report of decay operation results."""
|
|
21
|
+
|
|
22
|
+
neurons_processed: int = 0
|
|
23
|
+
neurons_decayed: int = 0
|
|
24
|
+
neurons_pruned: int = 0
|
|
25
|
+
synapses_processed: int = 0
|
|
26
|
+
synapses_decayed: int = 0
|
|
27
|
+
synapses_pruned: int = 0
|
|
28
|
+
duration_ms: float = 0.0
|
|
29
|
+
reference_time: datetime = field(default_factory=datetime.now)
|
|
30
|
+
|
|
31
|
+
def summary(self) -> str:
|
|
32
|
+
"""Generate human-readable summary."""
|
|
33
|
+
lines = [
|
|
34
|
+
f"Decay Report ({self.reference_time.strftime('%Y-%m-%d %H:%M')})",
|
|
35
|
+
f" Neurons: {self.neurons_decayed}/{self.neurons_processed} decayed, {self.neurons_pruned} pruned",
|
|
36
|
+
f" Synapses: {self.synapses_decayed}/{self.synapses_processed} decayed, {self.synapses_pruned} pruned",
|
|
37
|
+
f" Duration: {self.duration_ms:.1f}ms",
|
|
38
|
+
]
|
|
39
|
+
return "\n".join(lines)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DecayManager:
|
|
43
|
+
"""Manage memory decay using Ebbinghaus forgetting curve.
|
|
44
|
+
|
|
45
|
+
Decay formula: retention = e^(-decay_rate * days_since_access)
|
|
46
|
+
|
|
47
|
+
Memories that haven't been accessed recently will have their
|
|
48
|
+
activation levels reduced. Memories below the prune threshold
|
|
49
|
+
can be marked as dormant or removed.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
decay_rate: float = 0.1,
|
|
55
|
+
prune_threshold: float = 0.01,
|
|
56
|
+
min_age_days: float = 1.0,
|
|
57
|
+
):
|
|
58
|
+
"""Initialize decay manager.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
decay_rate: Rate of decay per day (0.1 = 10% per day)
|
|
62
|
+
prune_threshold: Activation level below which to prune
|
|
63
|
+
min_age_days: Minimum age before applying decay
|
|
64
|
+
"""
|
|
65
|
+
self.decay_rate = decay_rate
|
|
66
|
+
self.prune_threshold = prune_threshold
|
|
67
|
+
self.min_age_days = min_age_days
|
|
68
|
+
|
|
69
|
+
async def apply_decay(
|
|
70
|
+
self,
|
|
71
|
+
storage: NeuralStorage,
|
|
72
|
+
reference_time: datetime | None = None,
|
|
73
|
+
dry_run: bool = False,
|
|
74
|
+
) -> DecayReport:
|
|
75
|
+
"""Apply decay to all neurons and synapses in storage.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
storage: Storage instance to apply decay to
|
|
79
|
+
reference_time: Reference time for decay calculation (default: now)
|
|
80
|
+
dry_run: If True, calculate but don't save changes
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
DecayReport with statistics
|
|
84
|
+
"""
|
|
85
|
+
import time
|
|
86
|
+
|
|
87
|
+
start_time = time.perf_counter()
|
|
88
|
+
reference_time = reference_time or datetime.now()
|
|
89
|
+
report = DecayReport(reference_time=reference_time)
|
|
90
|
+
|
|
91
|
+
# Get all neuron states
|
|
92
|
+
states = await storage.get_all_neuron_states()
|
|
93
|
+
report.neurons_processed = len(states)
|
|
94
|
+
|
|
95
|
+
for state in states:
|
|
96
|
+
if state.last_activated is None:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
# Calculate time since last activation
|
|
100
|
+
time_diff = reference_time - state.last_activated
|
|
101
|
+
days_elapsed = time_diff.total_seconds() / 86400
|
|
102
|
+
|
|
103
|
+
# Skip if too recent
|
|
104
|
+
if days_elapsed < self.min_age_days:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# Calculate decay
|
|
108
|
+
decay_factor = math.exp(-self.decay_rate * days_elapsed)
|
|
109
|
+
new_level = state.activation_level * decay_factor
|
|
110
|
+
|
|
111
|
+
if new_level < state.activation_level:
|
|
112
|
+
report.neurons_decayed += 1
|
|
113
|
+
|
|
114
|
+
if new_level < self.prune_threshold:
|
|
115
|
+
report.neurons_pruned += 1
|
|
116
|
+
new_level = 0.0
|
|
117
|
+
|
|
118
|
+
if not dry_run:
|
|
119
|
+
# Update the neuron state
|
|
120
|
+
updated_state = state.with_activation(new_level)
|
|
121
|
+
updated_state = type(state)(
|
|
122
|
+
neuron_id=state.neuron_id,
|
|
123
|
+
activation_level=new_level,
|
|
124
|
+
access_frequency=state.access_frequency,
|
|
125
|
+
last_activated=state.last_activated,
|
|
126
|
+
decay_rate=state.decay_rate,
|
|
127
|
+
created_at=state.created_at,
|
|
128
|
+
)
|
|
129
|
+
await storage.update_neuron_state(updated_state)
|
|
130
|
+
|
|
131
|
+
# Get all synapses and apply decay
|
|
132
|
+
synapses = await storage.get_all_synapses()
|
|
133
|
+
report.synapses_processed = len(synapses)
|
|
134
|
+
|
|
135
|
+
for synapse in synapses:
|
|
136
|
+
if synapse.last_activated is None:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
time_diff = reference_time - synapse.last_activated
|
|
140
|
+
days_elapsed = time_diff.total_seconds() / 86400
|
|
141
|
+
|
|
142
|
+
if days_elapsed < self.min_age_days:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# Decay synapse weight
|
|
146
|
+
decay_factor = math.exp(-self.decay_rate * days_elapsed)
|
|
147
|
+
new_weight = synapse.weight * decay_factor
|
|
148
|
+
|
|
149
|
+
if new_weight < synapse.weight:
|
|
150
|
+
report.synapses_decayed += 1
|
|
151
|
+
|
|
152
|
+
if new_weight < self.prune_threshold:
|
|
153
|
+
report.synapses_pruned += 1
|
|
154
|
+
if not dry_run:
|
|
155
|
+
# Could delete synapse here, but just set weight to 0 for now
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
if not dry_run and new_weight >= self.prune_threshold:
|
|
159
|
+
decayed_synapse = synapse.decay(decay_factor)
|
|
160
|
+
await storage.update_synapse(decayed_synapse)
|
|
161
|
+
|
|
162
|
+
report.duration_ms = (time.perf_counter() - start_time) * 1000
|
|
163
|
+
return report
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ReinforcementManager:
|
|
167
|
+
"""Strengthen frequently accessed memory paths.
|
|
168
|
+
|
|
169
|
+
When memories are accessed, their activation levels and
|
|
170
|
+
synapse weights are increased (reinforced).
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
reinforcement_delta: float = 0.05,
|
|
176
|
+
max_activation: float = 1.0,
|
|
177
|
+
max_weight: float = 1.0,
|
|
178
|
+
):
|
|
179
|
+
"""Initialize reinforcement manager.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
reinforcement_delta: Amount to increase on each access
|
|
183
|
+
max_activation: Maximum activation level
|
|
184
|
+
max_weight: Maximum synapse weight
|
|
185
|
+
"""
|
|
186
|
+
self.reinforcement_delta = reinforcement_delta
|
|
187
|
+
self.max_activation = max_activation
|
|
188
|
+
self.max_weight = max_weight
|
|
189
|
+
|
|
190
|
+
async def reinforce(
|
|
191
|
+
self,
|
|
192
|
+
storage: NeuralStorage,
|
|
193
|
+
neuron_ids: list[str],
|
|
194
|
+
synapse_ids: list[str] | None = None,
|
|
195
|
+
) -> int:
|
|
196
|
+
"""Reinforce accessed neurons and synapses.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
storage: Storage instance
|
|
200
|
+
neuron_ids: List of accessed neuron IDs
|
|
201
|
+
synapse_ids: Optional list of accessed synapse IDs
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Number of items reinforced
|
|
205
|
+
"""
|
|
206
|
+
reinforced = 0
|
|
207
|
+
|
|
208
|
+
for neuron_id in neuron_ids:
|
|
209
|
+
state = await storage.get_neuron_state(neuron_id)
|
|
210
|
+
if state:
|
|
211
|
+
new_level = min(
|
|
212
|
+
state.activation_level + self.reinforcement_delta,
|
|
213
|
+
self.max_activation,
|
|
214
|
+
)
|
|
215
|
+
activated_state = state.activate(new_level - state.activation_level)
|
|
216
|
+
await storage.update_neuron_state(activated_state)
|
|
217
|
+
reinforced += 1
|
|
218
|
+
|
|
219
|
+
if synapse_ids:
|
|
220
|
+
for synapse_id in synapse_ids:
|
|
221
|
+
synapse = await storage.get_synapse(synapse_id)
|
|
222
|
+
if synapse:
|
|
223
|
+
new_weight = min(
|
|
224
|
+
synapse.weight + self.reinforcement_delta,
|
|
225
|
+
self.max_weight,
|
|
226
|
+
)
|
|
227
|
+
reinforced_synapse = synapse.reinforce(
|
|
228
|
+
new_weight - synapse.weight
|
|
229
|
+
)
|
|
230
|
+
await storage.update_synapse(reinforced_synapse)
|
|
231
|
+
reinforced += 1
|
|
232
|
+
|
|
233
|
+
return reinforced
|
neural_memory/mcp/server.py
CHANGED
|
@@ -603,7 +603,7 @@ async def handle_message(server: MCPServer, message: dict[str, Any]) -> dict[str
|
|
|
603
603
|
"id": msg_id,
|
|
604
604
|
"result": {
|
|
605
605
|
"protocolVersion": "2024-11-05",
|
|
606
|
-
"serverInfo": {"name": "neural-memory", "version": "0.
|
|
606
|
+
"serverInfo": {"name": "neural-memory", "version": "0.4.0"},
|
|
607
607
|
"capabilities": {"tools": {}, "resources": {}},
|
|
608
608
|
},
|
|
609
609
|
}
|
neural_memory/server/app.py
CHANGED
|
@@ -4,15 +4,21 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import AsyncGenerator
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from fastapi import FastAPI
|
|
9
10
|
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
from fastapi.responses import FileResponse
|
|
12
|
+
from fastapi.staticfiles import StaticFiles
|
|
10
13
|
|
|
11
14
|
from neural_memory import __version__
|
|
12
15
|
from neural_memory.server.models import HealthResponse
|
|
13
16
|
from neural_memory.server.routes import brain_router, memory_router, sync_router
|
|
14
17
|
from neural_memory.storage.memory_store import InMemoryStorage
|
|
15
18
|
|
|
19
|
+
# Static files directory
|
|
20
|
+
STATIC_DIR = Path(__file__).parent / "static"
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
@asynccontextmanager
|
|
18
24
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
@@ -90,8 +96,70 @@ def create_app(
|
|
|
90
96
|
"version": __version__,
|
|
91
97
|
"docs": "/docs",
|
|
92
98
|
"health": "/health",
|
|
99
|
+
"ui": "/ui",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Graph visualization API
|
|
103
|
+
@app.get("/api/graph", tags=["visualization"])
|
|
104
|
+
async def get_graph_data() -> dict:
|
|
105
|
+
"""Get graph data for visualization."""
|
|
106
|
+
from neural_memory.unified_config import get_shared_storage
|
|
107
|
+
|
|
108
|
+
storage = await get_shared_storage()
|
|
109
|
+
|
|
110
|
+
# Get all data
|
|
111
|
+
neurons = await storage.find_neurons()
|
|
112
|
+
synapses = await storage.get_all_synapses()
|
|
113
|
+
fibers = await storage.get_fibers(limit=1000)
|
|
114
|
+
|
|
115
|
+
stats = {
|
|
116
|
+
"neuron_count": len(neurons),
|
|
117
|
+
"synapse_count": len(synapses),
|
|
118
|
+
"fiber_count": len(fibers),
|
|
93
119
|
}
|
|
94
120
|
|
|
121
|
+
return {
|
|
122
|
+
"neurons": [
|
|
123
|
+
{
|
|
124
|
+
"id": n.id,
|
|
125
|
+
"type": n.type.value,
|
|
126
|
+
"content": n.content,
|
|
127
|
+
"metadata": n.metadata,
|
|
128
|
+
}
|
|
129
|
+
for n in neurons
|
|
130
|
+
],
|
|
131
|
+
"synapses": [
|
|
132
|
+
{
|
|
133
|
+
"id": s.id,
|
|
134
|
+
"source_id": s.source_id,
|
|
135
|
+
"target_id": s.target_id,
|
|
136
|
+
"type": s.type.value,
|
|
137
|
+
"weight": s.weight,
|
|
138
|
+
"direction": s.direction.value,
|
|
139
|
+
}
|
|
140
|
+
for s in synapses
|
|
141
|
+
],
|
|
142
|
+
"fibers": [
|
|
143
|
+
{
|
|
144
|
+
"id": f.id,
|
|
145
|
+
"summary": f.summary,
|
|
146
|
+
"neuron_count": len(f.neuron_ids) if f.neuron_ids else 0,
|
|
147
|
+
}
|
|
148
|
+
for f in fibers
|
|
149
|
+
],
|
|
150
|
+
"stats": stats,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# UI endpoint
|
|
154
|
+
@app.get("/ui", tags=["visualization"])
|
|
155
|
+
async def ui() -> FileResponse:
|
|
156
|
+
"""Serve the visualization UI."""
|
|
157
|
+
return FileResponse(STATIC_DIR / "index.html")
|
|
158
|
+
|
|
159
|
+
# Mount static files (for potential future assets)
|
|
160
|
+
if STATIC_DIR.exists():
|
|
161
|
+
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
|
|
162
|
+
|
|
95
163
|
return app
|
|
96
164
|
|
|
97
165
|
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>NeuralMemory - Brain Visualization</title>
|
|
7
|
+
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
16
|
+
background: #1a1a2e;
|
|
17
|
+
color: #eee;
|
|
18
|
+
}
|
|
19
|
+
.container {
|
|
20
|
+
display: flex;
|
|
21
|
+
height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
.sidebar {
|
|
24
|
+
width: 300px;
|
|
25
|
+
background: #16213e;
|
|
26
|
+
padding: 20px;
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
}
|
|
29
|
+
.main {
|
|
30
|
+
flex: 1;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
}
|
|
34
|
+
.header {
|
|
35
|
+
background: #0f3460;
|
|
36
|
+
padding: 15px 20px;
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: center;
|
|
40
|
+
}
|
|
41
|
+
.header h1 {
|
|
42
|
+
font-size: 1.5rem;
|
|
43
|
+
color: #e94560;
|
|
44
|
+
}
|
|
45
|
+
.stats {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 20px;
|
|
48
|
+
}
|
|
49
|
+
.stat {
|
|
50
|
+
text-align: center;
|
|
51
|
+
}
|
|
52
|
+
.stat-value {
|
|
53
|
+
font-size: 1.5rem;
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
color: #e94560;
|
|
56
|
+
}
|
|
57
|
+
.stat-label {
|
|
58
|
+
font-size: 0.8rem;
|
|
59
|
+
color: #aaa;
|
|
60
|
+
}
|
|
61
|
+
#graph {
|
|
62
|
+
flex: 1;
|
|
63
|
+
background: #1a1a2e;
|
|
64
|
+
}
|
|
65
|
+
.sidebar h2 {
|
|
66
|
+
color: #e94560;
|
|
67
|
+
margin-bottom: 15px;
|
|
68
|
+
font-size: 1.1rem;
|
|
69
|
+
}
|
|
70
|
+
.sidebar h3 {
|
|
71
|
+
color: #aaa;
|
|
72
|
+
margin: 15px 0 10px;
|
|
73
|
+
font-size: 0.9rem;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
}
|
|
76
|
+
.brain-select {
|
|
77
|
+
width: 100%;
|
|
78
|
+
padding: 10px;
|
|
79
|
+
background: #0f3460;
|
|
80
|
+
border: 1px solid #e94560;
|
|
81
|
+
color: #eee;
|
|
82
|
+
border-radius: 5px;
|
|
83
|
+
margin-bottom: 15px;
|
|
84
|
+
}
|
|
85
|
+
.btn {
|
|
86
|
+
width: 100%;
|
|
87
|
+
padding: 10px;
|
|
88
|
+
background: #e94560;
|
|
89
|
+
border: none;
|
|
90
|
+
color: white;
|
|
91
|
+
border-radius: 5px;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
margin-bottom: 10px;
|
|
94
|
+
font-size: 0.9rem;
|
|
95
|
+
}
|
|
96
|
+
.btn:hover {
|
|
97
|
+
background: #ff6b6b;
|
|
98
|
+
}
|
|
99
|
+
.btn-secondary {
|
|
100
|
+
background: #0f3460;
|
|
101
|
+
border: 1px solid #e94560;
|
|
102
|
+
}
|
|
103
|
+
.btn-secondary:hover {
|
|
104
|
+
background: #16213e;
|
|
105
|
+
}
|
|
106
|
+
.node-info {
|
|
107
|
+
background: #0f3460;
|
|
108
|
+
padding: 15px;
|
|
109
|
+
border-radius: 5px;
|
|
110
|
+
margin-top: 15px;
|
|
111
|
+
}
|
|
112
|
+
.node-info p {
|
|
113
|
+
margin: 5px 0;
|
|
114
|
+
font-size: 0.85rem;
|
|
115
|
+
}
|
|
116
|
+
.node-info .label {
|
|
117
|
+
color: #aaa;
|
|
118
|
+
}
|
|
119
|
+
.node-info .value {
|
|
120
|
+
color: #eee;
|
|
121
|
+
}
|
|
122
|
+
.legend {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-wrap: wrap;
|
|
125
|
+
gap: 10px;
|
|
126
|
+
margin-top: 15px;
|
|
127
|
+
}
|
|
128
|
+
.legend-item {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 5px;
|
|
132
|
+
font-size: 0.75rem;
|
|
133
|
+
}
|
|
134
|
+
.legend-color {
|
|
135
|
+
width: 12px;
|
|
136
|
+
height: 12px;
|
|
137
|
+
border-radius: 50%;
|
|
138
|
+
}
|
|
139
|
+
.loading {
|
|
140
|
+
display: flex;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
align-items: center;
|
|
143
|
+
height: 100%;
|
|
144
|
+
font-size: 1.2rem;
|
|
145
|
+
color: #aaa;
|
|
146
|
+
}
|
|
147
|
+
.error {
|
|
148
|
+
color: #ff6b6b;
|
|
149
|
+
padding: 10px;
|
|
150
|
+
background: rgba(255,107,107,0.1);
|
|
151
|
+
border-radius: 5px;
|
|
152
|
+
margin-top: 10px;
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<div class="container">
|
|
158
|
+
<aside class="sidebar">
|
|
159
|
+
<h2>NeuralMemory</h2>
|
|
160
|
+
|
|
161
|
+
<select id="brainSelect" class="brain-select">
|
|
162
|
+
<option value="">Loading brains...</option>
|
|
163
|
+
</select>
|
|
164
|
+
|
|
165
|
+
<button class="btn" onclick="loadGraph()">Refresh Graph</button>
|
|
166
|
+
<button class="btn btn-secondary" onclick="resetView()">Reset View</button>
|
|
167
|
+
|
|
168
|
+
<h3>Statistics</h3>
|
|
169
|
+
<div class="stats" style="flex-direction: column; gap: 10px;">
|
|
170
|
+
<div class="stat" style="text-align: left;">
|
|
171
|
+
<span class="stat-label">Neurons: </span>
|
|
172
|
+
<span class="stat-value" id="neuronCount">0</span>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="stat" style="text-align: left;">
|
|
175
|
+
<span class="stat-label">Synapses: </span>
|
|
176
|
+
<span class="stat-value" id="synapseCount">0</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="stat" style="text-align: left;">
|
|
179
|
+
<span class="stat-label">Fibers: </span>
|
|
180
|
+
<span class="stat-value" id="fiberCount">0</span>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<h3>Selected Node</h3>
|
|
185
|
+
<div class="node-info" id="nodeInfo">
|
|
186
|
+
<p><span class="label">Click a node to see details</span></p>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<h3>Legend</h3>
|
|
190
|
+
<div class="legend">
|
|
191
|
+
<div class="legend-item">
|
|
192
|
+
<div class="legend-color" style="background: #e94560;"></div>
|
|
193
|
+
<span>Concept</span>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="legend-item">
|
|
196
|
+
<div class="legend-color" style="background: #4ecdc4;"></div>
|
|
197
|
+
<span>Entity</span>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="legend-item">
|
|
200
|
+
<div class="legend-color" style="background: #ffe66d;"></div>
|
|
201
|
+
<span>Time</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="legend-item">
|
|
204
|
+
<div class="legend-color" style="background: #95e1d3;"></div>
|
|
205
|
+
<span>Action</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="legend-item">
|
|
208
|
+
<div class="legend-color" style="background: #f38181;"></div>
|
|
209
|
+
<span>State</span>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="legend-item">
|
|
212
|
+
<div class="legend-color" style="background: #aa96da;"></div>
|
|
213
|
+
<span>Other</span>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</aside>
|
|
217
|
+
|
|
218
|
+
<main class="main">
|
|
219
|
+
<header class="header">
|
|
220
|
+
<h1>Brain Visualization</h1>
|
|
221
|
+
<div class="stats">
|
|
222
|
+
<div class="stat">
|
|
223
|
+
<div class="stat-value" id="visibleNodes">0</div>
|
|
224
|
+
<div class="stat-label">Visible Nodes</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="stat">
|
|
227
|
+
<div class="stat-value" id="visibleEdges">0</div>
|
|
228
|
+
<div class="stat-label">Visible Edges</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</header>
|
|
232
|
+
<div id="graph">
|
|
233
|
+
<div class="loading">Loading graph...</div>
|
|
234
|
+
</div>
|
|
235
|
+
</main>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<script>
|
|
239
|
+
const API_BASE = window.location.origin;
|
|
240
|
+
let network = null;
|
|
241
|
+
let allNodes = [];
|
|
242
|
+
let allEdges = [];
|
|
243
|
+
|
|
244
|
+
const COLORS = {
|
|
245
|
+
concept: '#e94560',
|
|
246
|
+
entity: '#4ecdc4',
|
|
247
|
+
time: '#ffe66d',
|
|
248
|
+
action: '#95e1d3',
|
|
249
|
+
state: '#f38181',
|
|
250
|
+
spatial: '#45b7d1',
|
|
251
|
+
sensory: '#96ceb4',
|
|
252
|
+
intent: '#dda0dd',
|
|
253
|
+
default: '#aa96da'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
async function loadBrains() {
|
|
257
|
+
try {
|
|
258
|
+
// For now, use default brain from unified config
|
|
259
|
+
const select = document.getElementById('brainSelect');
|
|
260
|
+
select.innerHTML = '<option value="default">default</option>';
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('Error loading brains:', error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function loadGraph() {
|
|
267
|
+
const graphContainer = document.getElementById('graph');
|
|
268
|
+
graphContainer.innerHTML = '<div class="loading">Loading graph...</div>';
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const response = await fetch(`${API_BASE}/api/graph`);
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(`HTTP ${response.status}`);
|
|
274
|
+
}
|
|
275
|
+
const data = await response.json();
|
|
276
|
+
|
|
277
|
+
// Update stats
|
|
278
|
+
document.getElementById('neuronCount').textContent = data.stats.neuron_count;
|
|
279
|
+
document.getElementById('synapseCount').textContent = data.stats.synapse_count;
|
|
280
|
+
document.getElementById('fiberCount').textContent = data.stats.fiber_count;
|
|
281
|
+
|
|
282
|
+
// Convert to vis.js format
|
|
283
|
+
allNodes = data.neurons.map(n => ({
|
|
284
|
+
id: n.id,
|
|
285
|
+
label: truncate(n.content, 20),
|
|
286
|
+
title: n.content,
|
|
287
|
+
color: COLORS[n.type] || COLORS.default,
|
|
288
|
+
type: n.type,
|
|
289
|
+
content: n.content,
|
|
290
|
+
metadata: n.metadata
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
allEdges = data.synapses.map(s => ({
|
|
294
|
+
id: s.id,
|
|
295
|
+
from: s.source_id,
|
|
296
|
+
to: s.target_id,
|
|
297
|
+
label: s.type,
|
|
298
|
+
title: `${s.type} (weight: ${s.weight.toFixed(2)})`,
|
|
299
|
+
arrows: s.direction === 'uni' ? 'to' : 'to, from',
|
|
300
|
+
width: Math.max(1, s.weight * 3),
|
|
301
|
+
color: { color: '#555', highlight: '#e94560' }
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
renderGraph();
|
|
305
|
+
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error('Error loading graph:', error);
|
|
308
|
+
graphContainer.innerHTML = `<div class="error">Error loading graph: ${error.message}<br>Make sure the server is running with UI enabled.</div>`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function renderGraph() {
|
|
313
|
+
const container = document.getElementById('graph');
|
|
314
|
+
container.innerHTML = '';
|
|
315
|
+
|
|
316
|
+
const nodes = new vis.DataSet(allNodes);
|
|
317
|
+
const edges = new vis.DataSet(allEdges);
|
|
318
|
+
|
|
319
|
+
document.getElementById('visibleNodes').textContent = allNodes.length;
|
|
320
|
+
document.getElementById('visibleEdges').textContent = allEdges.length;
|
|
321
|
+
|
|
322
|
+
const options = {
|
|
323
|
+
nodes: {
|
|
324
|
+
shape: 'dot',
|
|
325
|
+
size: 16,
|
|
326
|
+
font: {
|
|
327
|
+
size: 12,
|
|
328
|
+
color: '#eee'
|
|
329
|
+
},
|
|
330
|
+
borderWidth: 2,
|
|
331
|
+
shadow: true
|
|
332
|
+
},
|
|
333
|
+
edges: {
|
|
334
|
+
font: {
|
|
335
|
+
size: 10,
|
|
336
|
+
color: '#888',
|
|
337
|
+
strokeWidth: 0
|
|
338
|
+
},
|
|
339
|
+
smooth: {
|
|
340
|
+
type: 'continuous'
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
physics: {
|
|
344
|
+
stabilization: {
|
|
345
|
+
iterations: 100
|
|
346
|
+
},
|
|
347
|
+
barnesHut: {
|
|
348
|
+
gravitationalConstant: -2000,
|
|
349
|
+
springLength: 150
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
interaction: {
|
|
353
|
+
hover: true,
|
|
354
|
+
tooltipDelay: 200
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
network = new vis.Network(container, { nodes, edges }, options);
|
|
359
|
+
|
|
360
|
+
network.on('selectNode', function(params) {
|
|
361
|
+
if (params.nodes.length > 0) {
|
|
362
|
+
const nodeId = params.nodes[0];
|
|
363
|
+
const node = allNodes.find(n => n.id === nodeId);
|
|
364
|
+
showNodeInfo(node);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
network.on('deselectNode', function() {
|
|
369
|
+
document.getElementById('nodeInfo').innerHTML =
|
|
370
|
+
'<p><span class="label">Click a node to see details</span></p>';
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function showNodeInfo(node) {
|
|
375
|
+
if (!node) return;
|
|
376
|
+
|
|
377
|
+
const infoDiv = document.getElementById('nodeInfo');
|
|
378
|
+
infoDiv.innerHTML = `
|
|
379
|
+
<p><span class="label">Type:</span> <span class="value">${node.type}</span></p>
|
|
380
|
+
<p><span class="label">Content:</span> <span class="value">${node.content}</span></p>
|
|
381
|
+
<p><span class="label">ID:</span> <span class="value" style="font-size:0.7rem">${node.id}</span></p>
|
|
382
|
+
`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resetView() {
|
|
386
|
+
if (network) {
|
|
387
|
+
network.fit();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function truncate(str, len) {
|
|
392
|
+
if (!str) return '';
|
|
393
|
+
return str.length > len ? str.substring(0, len) + '...' : str;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Initialize
|
|
397
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
398
|
+
loadBrains();
|
|
399
|
+
loadGraph();
|
|
400
|
+
});
|
|
401
|
+
</script>
|
|
402
|
+
</body>
|
|
403
|
+
</html>
|
|
@@ -396,6 +396,18 @@ class SQLiteStorage(NeuralStorage):
|
|
|
396
396
|
created_at=datetime.fromisoformat(row["created_at"]),
|
|
397
397
|
)
|
|
398
398
|
|
|
399
|
+
async def get_all_neuron_states(self) -> list[NeuronState]:
|
|
400
|
+
"""Get all neuron states for current brain."""
|
|
401
|
+
conn = self._ensure_conn()
|
|
402
|
+
brain_id = self._get_brain_id()
|
|
403
|
+
|
|
404
|
+
async with conn.execute(
|
|
405
|
+
"SELECT * FROM neuron_states WHERE brain_id = ?",
|
|
406
|
+
(brain_id,),
|
|
407
|
+
) as cursor:
|
|
408
|
+
rows = await cursor.fetchall()
|
|
409
|
+
return [self._row_to_neuron_state(row) for row in rows]
|
|
410
|
+
|
|
399
411
|
# ========== Synapse Operations ==========
|
|
400
412
|
|
|
401
413
|
async def add_synapse(self, synapse: Synapse) -> str:
|
|
@@ -486,6 +498,10 @@ class SQLiteStorage(NeuralStorage):
|
|
|
486
498
|
rows = await cursor.fetchall()
|
|
487
499
|
return [self._row_to_synapse(row) for row in rows]
|
|
488
500
|
|
|
501
|
+
async def get_all_synapses(self) -> list[Synapse]:
|
|
502
|
+
"""Get all synapses for current brain."""
|
|
503
|
+
return await self.get_synapses()
|
|
504
|
+
|
|
489
505
|
async def update_synapse(self, synapse: Synapse) -> None:
|
|
490
506
|
conn = self._ensure_conn()
|
|
491
507
|
brain_id = self._get_brain_id()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: neural-memory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Reflex-based memory system for AI agents - retrieval through activation, not search
|
|
5
5
|
Project-URL: Homepage, https://github.com/nhadaututtheky/neural-memory
|
|
6
6
|
Project-URL: Documentation, https://github.com/nhadaututtheky/neural-memory#readme
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
neural_memory/__init__.py,sha256=
|
|
1
|
+
neural_memory/__init__.py,sha256=XsRi9r3Wk-633WlAO3kU5l40vUonLN1DfLNuJ5BhQL4,970
|
|
2
2
|
neural_memory/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
neural_memory/unified_config.py,sha256=BySYLNJmnBcz9A83kKztJ53ShkScJ5qOVqBNGQls0Rs,10208
|
|
4
4
|
neural_memory/cli/__init__.py,sha256=4srWgYwiGcs-mG6dm91gRbYCN-om2hfkuxd-fYkgIGI,409
|
|
5
5
|
neural_memory/cli/__main__.py,sha256=TnhrDVCqsaagmqua7QH1yaP9YXLNRvLxQ0pFrMxDYRw,132
|
|
6
6
|
neural_memory/cli/config.py,sha256=3kuYeWyTJms_2H_xAgx0qXEN1FbaYwvcU0oKPdiGO2Y,5612
|
|
7
|
-
neural_memory/cli/main.py,sha256=
|
|
7
|
+
neural_memory/cli/main.py,sha256=XdfbuDpYPY6s5Uu8fTW1gWBf4cqjAiLAA75oDcu-xMk,101845
|
|
8
8
|
neural_memory/cli/storage.py,sha256=xiIk9Nmygw2G-nBDuRaSh6ZY6Ccu3zX_1l_eyeOlIsM,6044
|
|
9
9
|
neural_memory/cli/tui.py,sha256=3D0uhQD2LpP7caBOns7EeOsx-JsORl4-DyzSsS1HN64,16335
|
|
10
10
|
neural_memory/core/__init__.py,sha256=j9oPfDRg6n3Jr1OwwjY1OufH1zEPcL5aZol23R8vik0,1181
|
|
@@ -18,6 +18,7 @@ neural_memory/core/synapse.py,sha256=esfb0kNKwyYw0Ps68ao7IfFxf9thyML24WJJoLgnQjM
|
|
|
18
18
|
neural_memory/engine/__init__.py,sha256=bBuk-gqynBG_ebwB1scGu52PE6LjZw5fNOpL9zTe4wE,472
|
|
19
19
|
neural_memory/engine/activation.py,sha256=wTecU_j5FBe3LgXAvPNnR6Nji-fsLeLsANezIBW0KaI,11221
|
|
20
20
|
neural_memory/engine/encoder.py,sha256=uk3NvThLntXszzlbR0nO2GiPFikJ4th4TKkZf4NueyY,12716
|
|
21
|
+
neural_memory/engine/lifecycle.py,sha256=zhNdokjc8uz6YmT6Qm4zsgHhVZBN9TZg7aM3UzG_CIo,7908
|
|
21
22
|
neural_memory/engine/retrieval.py,sha256=0IjyDVDnUJCdZo6umgjYIBElVbTggV45zGxRzYoQryg,13809
|
|
22
23
|
neural_memory/extraction/__init__.py,sha256=XTuBnuAv0uWXs6LOUEQMoPBsBxVih93EvBaY1Vb_OCk,854
|
|
23
24
|
neural_memory/extraction/entities.py,sha256=62To2Z-DbPFidOLn2pjY6tN10LmAxk_7vDyhTtxinso,13430
|
|
@@ -27,30 +28,31 @@ neural_memory/extraction/temporal.py,sha256=uESwr-NWTMNiRJHDryKBdSS3swnkp_FBwjUw
|
|
|
27
28
|
neural_memory/mcp/__init__.py,sha256=8qo502ZfwryBv5htRaUJWsk3q9tbZX6TBtjmO6H0iTA,332
|
|
28
29
|
neural_memory/mcp/__main__.py,sha256=BETnISNHCDaqFhvOo51fSV1DcB2yMu9iefu-b6mA0bk,137
|
|
29
30
|
neural_memory/mcp/prompt.py,sha256=PTTWJ5ZxSPFq42ojU7UH8DKqgJxepg8ZMZmj9xYcaKQ,5003
|
|
30
|
-
neural_memory/mcp/server.py,sha256=
|
|
31
|
+
neural_memory/mcp/server.py,sha256=c4k3jFcmAUDDBZ71kk1y9q0XRgjTaZWtd3n7FWW-fMk,26121
|
|
31
32
|
neural_memory/safety/__init__.py,sha256=25_pdeXu-UI3dNsPYiCsC3Rvq26ma-tCd3h1ow7Ny3w,679
|
|
32
33
|
neural_memory/safety/freshness.py,sha256=nwUdybjsDK_8uwa_mJRPflx0XGZXVwDwJCw-LEVyh9c,6748
|
|
33
34
|
neural_memory/safety/sensitive.py,sha256=3dCv951D1JOMo6V4vu11d0OxUFhxCITZ2w05HhJ3CtA,9359
|
|
34
35
|
neural_memory/server/__init__.py,sha256=-VB8CldyBAzD10NK0aFBKMhUkHwR4aJHn05g_4X8xYQ,114
|
|
35
|
-
neural_memory/server/app.py,sha256=
|
|
36
|
+
neural_memory/server/app.py,sha256=nd2fKFeOGs2KuOsARsxkZO4duD2D75Pz1Bw7v6lake4,4791
|
|
36
37
|
neural_memory/server/dependencies.py,sha256=gcUsxihGs7VrhZjmqzA4VwytTNkQE9IMswQnf5t-fnA,923
|
|
37
38
|
neural_memory/server/models.py,sha256=HDrqIMamc2c4bf2yrsVgTGDQZcO27ASCjeVJAdOWpso,4259
|
|
38
39
|
neural_memory/server/routes/__init__.py,sha256=dkZBeETlaGDW1uetueQWhCRgzfUGr64T_ELkDUWFi1c,310
|
|
39
40
|
neural_memory/server/routes/brain.py,sha256=ej7N5jU8SKTC4jkX-uXEmSojONd3q0EJjgjJwEVKXXc,6389
|
|
40
41
|
neural_memory/server/routes/memory.py,sha256=A-A8PF-aX7L8RhEN_zn0Gno09oI1IUnYadEZ7B5YcC8,5076
|
|
41
42
|
neural_memory/server/routes/sync.py,sha256=5nvnuu8EakOQ-iNEBztSehvbwQDpdT5YwMP8zy3RBco,12747
|
|
43
|
+
neural_memory/server/static/index.html,sha256=iOtVm8LgHVij5FxRBKBWwokMLdzbZjs3pvITEtMwWUk,13098
|
|
42
44
|
neural_memory/storage/__init__.py,sha256=jiVLlyvj_Yn-E24O_edNQmyYhNsUNmT8OMvvrvxll9c,542
|
|
43
45
|
neural_memory/storage/base.py,sha256=LxoiYseiNP4zOpK__lYW7eGkUiNw7tuLDPX_vH_dZNY,10565
|
|
44
46
|
neural_memory/storage/factory.py,sha256=kQYTDuPG3LTW8lPrzn556DDci3d8uEHfWUgn0uox3E4,10567
|
|
45
47
|
neural_memory/storage/memory_store.py,sha256=cY5Gtw1_oDyOzDVuIQTPZ-q0javFb2t2gr1lFC_a_Vo,31669
|
|
46
48
|
neural_memory/storage/shared_store.py,sha256=wI33TY40c-_7sCIRIn63mLzfSXHgvdhGUnrj4fMK2tg,23372
|
|
47
|
-
neural_memory/storage/sqlite_store.py,sha256=
|
|
49
|
+
neural_memory/storage/sqlite_store.py,sha256=5JeCtETIPi0CQPwfCcl7T7vy7g4X2HxkPjsbhY9RZDY,60922
|
|
48
50
|
neural_memory/sync/__init__.py,sha256=epKpdAsHzwUYXrWawlHPckSEgK5CZYb-B29AAcdTnVk,162
|
|
49
51
|
neural_memory/sync/client.py,sha256=kiKqRlteXVe9P-IzgayV7bwdT9Ovu2UgV_XJtEUIbxQ,13421
|
|
50
52
|
neural_memory/utils/__init__.py,sha256=vZM_jKQ_BUUd6jXtz9BGq5A1d-kFRMcMXt96XVhRv2M,135
|
|
51
53
|
neural_memory/utils/config.py,sha256=jgDaHksEibusC2gub1I_USUBOJyP6gBHcfgS7RkL4dc,3086
|
|
52
|
-
neural_memory-0.
|
|
53
|
-
neural_memory-0.
|
|
54
|
-
neural_memory-0.
|
|
55
|
-
neural_memory-0.
|
|
56
|
-
neural_memory-0.
|
|
54
|
+
neural_memory-0.4.0.dist-info/METADATA,sha256=TOi8D4Yu2i8Wt_VEZQ49rX8xNoErvf2pcOmG1s_ybZQ,10305
|
|
55
|
+
neural_memory-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
56
|
+
neural_memory-0.4.0.dist-info/entry_points.txt,sha256=u0ZGaAD6uU98g0Wheyj1WjrSchm_zlAkyzz1HRbKOnE,121
|
|
57
|
+
neural_memory-0.4.0.dist-info/licenses/LICENSE,sha256=uuCGDPgkW8shclBRpQNK5I0T97ZQy9HHolwo9Qr3Bbc,1082
|
|
58
|
+
neural_memory-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|