adi-super-memory 0.3.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.
- adi_super_memory/__init__.py +1 -0
- adi_super_memory/adapters/__init__.py +1 -0
- adi_super_memory/adapters/langchain.py +9 -0
- adi_super_memory/backends.py +26 -0
- adi_super_memory/core.py +80 -0
- adi_super_memory/distillers.py +39 -0
- adi_super_memory/embeddings.py +27 -0
- adi_super_memory/policy.py +16 -0
- adi_super_memory/py.typed +0 -0
- adi_super_memory/retrieval.py +17 -0
- adi_super_memory/robotics/__init__.py +1 -0
- adi_super_memory/robotics/runtime.py +6 -0
- adi_super_memory/safety/__init__.py +2 -0
- adi_super_memory/safety/modes.py +22 -0
- adi_super_memory/safety/redaction.py +7 -0
- adi_super_memory/stm.py +21 -0
- adi_super_memory/types.py +16 -0
- adi_super_memory-0.3.0.dist-info/METADATA +20 -0
- adi_super_memory-0.3.0.dist-info/RECORD +21 -0
- adi_super_memory-0.3.0.dist-info/WHEEL +5 -0
- adi_super_memory-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .core import SuperMemory\nfrom .policy import MemoryPolicy\n
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .langchain import LangChainMemoryAdapter\n
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class LangChainMemoryAdapter:
|
|
2
|
+
def __init__(self, super_memory, key: str = "history"):
|
|
3
|
+
self.mem=super_memory; self.key=key
|
|
4
|
+
@property
|
|
5
|
+
def memory_variables(self): return [self.key]
|
|
6
|
+
def load_memory_variables(self, inputs): return {self.key: self.mem.stm.messages()}
|
|
7
|
+
def save_context(self, inputs, outputs):
|
|
8
|
+
self.mem.stm.add("user", str(inputs))
|
|
9
|
+
self.mem.stm.add("assistant", str(outputs))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Iterable, List, Optional, Tuple
|
|
3
|
+
import threading
|
|
4
|
+
from .types import MemoryItem
|
|
5
|
+
|
|
6
|
+
class Backend(ABC):
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def upsert(self, item: MemoryItem, embedding: List[float]) -> None: ...
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def iter_items(self, tenant: str, kinds: Optional[List[str]] = None) -> Iterable[Tuple[MemoryItem, List[float]]]: ...
|
|
11
|
+
|
|
12
|
+
class InMemoryBackend(Backend):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._lock = threading.RLock()
|
|
15
|
+
self._store: Dict[str, Tuple[MemoryItem, List[float]]] = {}
|
|
16
|
+
|
|
17
|
+
def upsert(self, item: MemoryItem, embedding: List[float]) -> None:
|
|
18
|
+
with self._lock:
|
|
19
|
+
self._store[item.id] = (item, embedding)
|
|
20
|
+
|
|
21
|
+
def iter_items(self, tenant: str, kinds: Optional[List[str]] = None):
|
|
22
|
+
with self._lock:
|
|
23
|
+
for item, emb in self._store.values():
|
|
24
|
+
if item.tenant != tenant: continue
|
|
25
|
+
if kinds and item.kind not in kinds: continue
|
|
26
|
+
yield item, emb
|
adi_super_memory/core.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone, timedelta
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
import uuid, hashlib, json
|
|
5
|
+
|
|
6
|
+
from .stm import MessageSTM
|
|
7
|
+
from .policy import MemoryPolicy
|
|
8
|
+
from .embeddings import HashEmbeddingProvider, EmbeddingProvider
|
|
9
|
+
from .backends import Backend, InMemoryBackend
|
|
10
|
+
from .types import MemoryItem
|
|
11
|
+
from .retrieval import cosine, recency_weight
|
|
12
|
+
from .distillers import Distiller, DeterministicDistiller
|
|
13
|
+
from .safety.modes import EnterpriseSafetyMode, SafetyMode
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SuperMemory:
|
|
17
|
+
backend: Backend = InMemoryBackend()
|
|
18
|
+
policy: MemoryPolicy = MemoryPolicy.default()
|
|
19
|
+
embedder: EmbeddingProvider = HashEmbeddingProvider()
|
|
20
|
+
tenant: str = "default"
|
|
21
|
+
safety: SafetyMode = EnterpriseSafetyMode.default()
|
|
22
|
+
|
|
23
|
+
def __post_init__(self):
|
|
24
|
+
self.stm = MessageSTM()
|
|
25
|
+
self._distiller: Distiller = DeterministicDistiller()
|
|
26
|
+
self._audit_last = "0"*64
|
|
27
|
+
self._items: Dict[str, MemoryItem] = {}
|
|
28
|
+
self._emb: Dict[str, List[float]] = {}
|
|
29
|
+
|
|
30
|
+
def set_distiller(self, distiller: Distiller):
|
|
31
|
+
self._distiller = distiller
|
|
32
|
+
|
|
33
|
+
def _audit(self, action: str, item_id: str):
|
|
34
|
+
body = json.dumps({"t": self.tenant, "a": action, "id": item_id, "prev": self._audit_last}, sort_keys=True).encode("utf-8")
|
|
35
|
+
self._audit_last = hashlib.sha256(body).hexdigest()
|
|
36
|
+
|
|
37
|
+
def audit_last_hash(self) -> str:
|
|
38
|
+
return self._audit_last
|
|
39
|
+
|
|
40
|
+
def add_event(self, actor: str, subkind: str, text: str, tags: Optional[List[str]] = None, score: float = 0.0):
|
|
41
|
+
return self._add("event", actor, subkind.capitalize(), text, tags or [], score, {"subkind": subkind})
|
|
42
|
+
|
|
43
|
+
def add_knowledge(self, namespace: str, title: str, content: str, tags: Optional[List[str]] = None):
|
|
44
|
+
return self._add("knowledge", "system", title, content, tags or [], 0.0, {"namespace": namespace})
|
|
45
|
+
|
|
46
|
+
def add_super(self, title: str, text: str, tags: Optional[List[str]] = None, score: float = 0.0):
|
|
47
|
+
return self._add("super", "system", title, text, tags or [], score, {})
|
|
48
|
+
|
|
49
|
+
def _add(self, kind: str, actor: str, title: str, text: str, tags: List[str], score: float, md: Dict[str,str]):
|
|
50
|
+
now = datetime.now(timezone.utc)
|
|
51
|
+
title = self.safety.redaction(title)
|
|
52
|
+
text = self.safety.redaction(text)
|
|
53
|
+
item = MemoryItem(id=str(uuid.uuid4()), created_at=now, kind=kind, actor=actor, title=title, text=text, tags=tags, metadata=md, score=float(score), tenant=self.tenant)
|
|
54
|
+
emb = self.embedder.embed([title + "\n" + text + "\n" + " ".join(tags)])[0]
|
|
55
|
+
self.backend.upsert(item, emb)
|
|
56
|
+
self._audit(f"upsert:{kind}", item.id)
|
|
57
|
+
return item
|
|
58
|
+
|
|
59
|
+
def recall(self, query: str, top_k: int = 8, kinds: Optional[List[str]] = None):
|
|
60
|
+
kinds = kinds or ["event","knowledge","super"]
|
|
61
|
+
qemb = self.embedder.embed([query])[0]
|
|
62
|
+
scored=[]
|
|
63
|
+
for item, emb in self.backend.iter_items(self.tenant, kinds=kinds):
|
|
64
|
+
sim = cosine(qemb, emb)
|
|
65
|
+
rec = recency_weight(item.created_at, self.policy.recency_half_life_days)
|
|
66
|
+
scored.append((0.7*sim+0.2*rec+0.1*float(item.score), item))
|
|
67
|
+
scored.sort(key=lambda x: x[0], reverse=True)
|
|
68
|
+
return [i for _, i in scored[:top_k]]
|
|
69
|
+
|
|
70
|
+
def distill(self, window_days: int = 30):
|
|
71
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=window_days)
|
|
72
|
+
events=[]
|
|
73
|
+
for item, _ in self.backend.iter_items(self.tenant, kinds=["event"]):
|
|
74
|
+
if item.created_at >= cutoff and (item.score >= self.policy.distill_min_score or item.metadata.get("subkind") == "outcome"):
|
|
75
|
+
events.append(item)
|
|
76
|
+
distilled = self._distiller.distill(events)
|
|
77
|
+
created=[]
|
|
78
|
+
for d in distilled:
|
|
79
|
+
created.append(self.add_super(d.get("title","Wisdom"), d.get("text",""), [d.get("tag","wisdom")], float(d.get("avg_score",0) or 0.0)).id)
|
|
80
|
+
return {"source_events": len(events), "created_super_memories": len(created), "super_memory_ids": created}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
from .types import MemoryItem
|
|
4
|
+
|
|
5
|
+
class Distiller(ABC):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def distill(self, events: List[MemoryItem]) -> List[Dict[str,str]]: ...
|
|
8
|
+
|
|
9
|
+
class DeterministicDistiller(Distiller):
|
|
10
|
+
def distill(self, events: List[MemoryItem]) -> List[Dict[str,str]]:
|
|
11
|
+
by_tag = {}
|
|
12
|
+
for e in events:
|
|
13
|
+
for t in (e.tags or ["untagged"]):
|
|
14
|
+
by_tag.setdefault(t, []).append(e)
|
|
15
|
+
out=[]
|
|
16
|
+
for tag, items in by_tag.items():
|
|
17
|
+
avg = sum(float(x.score) for x in items)/max(len(items),1)
|
|
18
|
+
out.append({"title": f"Wisdom: {tag}", "text": f"avg_score={avg:.2f} over {len(items)} events", "tag": tag, "avg_score": f"{avg:.3f}", "examples": ""})
|
|
19
|
+
return out
|
|
20
|
+
|
|
21
|
+
class OpenAIChatDistiller(Distiller):
|
|
22
|
+
def __init__(self, model: str = "gpt-4.1-mini", api_key=None):
|
|
23
|
+
self.model=model; self.api_key=api_key
|
|
24
|
+
def distill(self, events: List[MemoryItem]) -> List[Dict[str,str]]:
|
|
25
|
+
try:
|
|
26
|
+
from openai import OpenAI
|
|
27
|
+
except Exception as e:
|
|
28
|
+
raise RuntimeError("Install OpenAI extras: pip install 'adi-super-memory[llm]'") from e
|
|
29
|
+
client = OpenAI(api_key=self.api_key)
|
|
30
|
+
lines=[f"- score={e.score:.2f} tags={','.join(e.tags)}: {e.text[:200]}" for e in events[:80]]
|
|
31
|
+
prompt="Return JSON array of heuristics (title,text,tag,avg_score,examples).\nEVENTS:\n" + "\n".join(lines)
|
|
32
|
+
resp = client.chat.completions.create(model=self.model, messages=[{"role":"user","content":prompt}], temperature=0.2)
|
|
33
|
+
import json as _json
|
|
34
|
+
content = resp.choices[0].message.content or "[]"
|
|
35
|
+
try:
|
|
36
|
+
data = _json.loads(content)
|
|
37
|
+
return data if isinstance(data, list) else []
|
|
38
|
+
except Exception:
|
|
39
|
+
return [{"title":"Wisdom: distilled","text":content[:1500],"tag":"distilled","avg_score":"0.0","examples":""}]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List
|
|
3
|
+
import hashlib, math
|
|
4
|
+
|
|
5
|
+
class EmbeddingProvider(ABC):
|
|
6
|
+
@property
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def dim(self) -> int: ...
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def embed(self, texts: List[str]) -> List[List[float]]: ...
|
|
11
|
+
|
|
12
|
+
class HashEmbeddingProvider(EmbeddingProvider):
|
|
13
|
+
def __init__(self, dim: int = 256): self._dim = dim
|
|
14
|
+
@property
|
|
15
|
+
def dim(self) -> int: return self._dim
|
|
16
|
+
def _one(self, text: str) -> List[float]:
|
|
17
|
+
b = text.encode("utf-8", errors="ignore")
|
|
18
|
+
seed = hashlib.sha256(b).digest()
|
|
19
|
+
out = b""; cur = seed
|
|
20
|
+
while len(out) < self._dim:
|
|
21
|
+
cur = hashlib.sha256(cur + seed).digest()
|
|
22
|
+
out += cur
|
|
23
|
+
v = [((out[i]-128)/128.0) for i in range(self._dim)]
|
|
24
|
+
n = math.sqrt(sum(x*x for x in v)) or 1.0
|
|
25
|
+
return [x/n for x in v]
|
|
26
|
+
def embed(self, texts: List[str]) -> List[List[float]]:
|
|
27
|
+
return [self._one(t) for t in texts]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
RedactionFn = Callable[[str], str]
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class MemoryPolicy:
|
|
9
|
+
episodic_ttl: Optional[timedelta] = timedelta(days=30)
|
|
10
|
+
recency_half_life_days: float = 14.0
|
|
11
|
+
distill_min_score: float = 0.6
|
|
12
|
+
redaction: Optional[RedactionFn] = None
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def default() -> "MemoryPolicy":
|
|
16
|
+
return MemoryPolicy()
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import List
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
def cosine(a: List[float], b: List[float]) -> float:
|
|
6
|
+
dot=na=nb=0.0
|
|
7
|
+
for x,y in zip(a,b):
|
|
8
|
+
dot += x*y; na += x*x; nb += y*y
|
|
9
|
+
d = math.sqrt(na)*math.sqrt(nb)
|
|
10
|
+
return (dot/d) if d else 0.0
|
|
11
|
+
|
|
12
|
+
def recency_weight(created_at: datetime, half_life_days: float) -> float:
|
|
13
|
+
now = datetime.now(timezone.utc)
|
|
14
|
+
if created_at.tzinfo is None:
|
|
15
|
+
created_at = created_at.replace(tzinfo=timezone.utc)
|
|
16
|
+
age_days = max((now-created_at).total_seconds(),0.0)/86400.0
|
|
17
|
+
return math.pow(0.5, age_days/half_life_days) if half_life_days>0 else 1.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .runtime import RealTimePolicy\n
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from .redaction import default_redactor
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True)
|
|
5
|
+
class SafetyMode:
|
|
6
|
+
name: str
|
|
7
|
+
redaction: callable
|
|
8
|
+
allow_external_network: bool
|
|
9
|
+
allow_cloud_models: bool
|
|
10
|
+
require_signed_audit: bool
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class EnterpriseSafetyMode(SafetyMode):
|
|
14
|
+
@staticmethod
|
|
15
|
+
def default():
|
|
16
|
+
return EnterpriseSafetyMode("enterprise", default_redactor, True, True, True)
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class GovernmentSafetyMode(SafetyMode):
|
|
20
|
+
@staticmethod
|
|
21
|
+
def default():
|
|
22
|
+
return GovernmentSafetyMode("government", default_redactor, False, False, True)
|
adi_super_memory/stm.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class MessageSTM:
|
|
7
|
+
_kv: Dict[str, Any] = field(default_factory=dict)
|
|
8
|
+
_messages: List[Dict[str, str]] = field(default_factory=list)
|
|
9
|
+
_lock: threading.RLock = field(default_factory=threading.RLock)
|
|
10
|
+
|
|
11
|
+
def set(self, key: str, value: Any) -> None:
|
|
12
|
+
with self._lock: self._kv[key] = value
|
|
13
|
+
|
|
14
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
15
|
+
with self._lock: return self._kv.get(key, default)
|
|
16
|
+
|
|
17
|
+
def add(self, role: str, content: str) -> None:
|
|
18
|
+
with self._lock: self._messages.append({"role": role, "content": content})
|
|
19
|
+
|
|
20
|
+
def messages(self, limit: int = 50) -> List[Dict[str, str]]:
|
|
21
|
+
with self._lock: return list(self._messages[-limit:])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class MemoryItem:
|
|
7
|
+
id: str
|
|
8
|
+
created_at: datetime
|
|
9
|
+
kind: str
|
|
10
|
+
actor: str
|
|
11
|
+
title: str
|
|
12
|
+
text: str
|
|
13
|
+
tags: List[str] = field(default_factory=list)
|
|
14
|
+
metadata: Dict[str,str] = field(default_factory=dict)
|
|
15
|
+
score: float = 0.0
|
|
16
|
+
tenant: str = "default"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adi-super-memory
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: ADI Super Memory: STM + LTM + distilled Super Memory with adapters and safety modes.
|
|
5
|
+
Author: Adi's American Soft LLC
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pydantic>=2.0; python_version >= "3.9"
|
|
10
|
+
Provides-Extra: server
|
|
11
|
+
Requires-Dist: fastapi>=0.110; extra == "server"
|
|
12
|
+
Requires-Dist: uvicorn>=0.23; extra == "server"
|
|
13
|
+
Provides-Extra: llm
|
|
14
|
+
Requires-Dist: openai>=1.0.0; extra == "llm"
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
17
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
18
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
19
|
+
|
|
20
|
+
# adi-super-memory v0.3 (PyPI-ready)\n
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
adi_super_memory/__init__.py,sha256=lGC9AL8vmW7GzrEb83eeEXm8p0v2gBK7iDNZygRsMko,65
|
|
2
|
+
adi_super_memory/backends.py,sha256=iplPvVfx67IDp8RsXBHOi3vGAA6DmGIogd_ER9RUTMg,1010
|
|
3
|
+
adi_super_memory/core.py,sha256=iyOK3bLgzhLY3DvHy7Ypow0a9U7BRezRmXpl6_hpaZs,4003
|
|
4
|
+
adi_super_memory/distillers.py,sha256=sW_DAJjG9L_K47EwjVHita49SwtiBsqoBzvaQzBuxCo,1927
|
|
5
|
+
adi_super_memory/embeddings.py,sha256=96OQaldgeOY3UUFvMfseebTjaI2o8CXuSUioqvwZvJg,951
|
|
6
|
+
adi_super_memory/policy.py,sha256=NxVXwxvpftJKvpP0UNhuDH6LF_z2KN1EvGgqtM_Wnk4,449
|
|
7
|
+
adi_super_memory/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
adi_super_memory/retrieval.py,sha256=riy6Y2xML294fsZb21-vrpOPRClWugjPgRuWQRtpj2g,634
|
|
9
|
+
adi_super_memory/stm.py,sha256=5xNYtAddsDkoyPhTemaWSHJQdHWZMXNnNK6grtHZZcw,789
|
|
10
|
+
adi_super_memory/types.py,sha256=BH6iwnBpPZgbz4rV-YArlrajAzezWZpxJT6wJOEB5AE,398
|
|
11
|
+
adi_super_memory/adapters/__init__.py,sha256=ppoqcdxFAikKxRmIcxapkNwsent6pKbI9LethDSLbh8,47
|
|
12
|
+
adi_super_memory/adapters/langchain.py,sha256=Y4wlpv69zqfj6N_RD1UxcSCJjd15z3A1MDpSoH-MyNk,429
|
|
13
|
+
adi_super_memory/robotics/__init__.py,sha256=EOMHWilw1ZuQwmTN1yvX7fYpQbkW_ZOBSiMECC7souM,37
|
|
14
|
+
adi_super_memory/robotics/runtime.py,sha256=KY7PNre9Tw3EpQK2sqT942ZQw071Wakw-rZgRy0EdGM,179
|
|
15
|
+
adi_super_memory/safety/__init__.py,sha256=ILKFalih5cUX0FohFH412nUgBydloOBtyOWmfc5Wp4Q,102
|
|
16
|
+
adi_super_memory/safety/modes.py,sha256=uKa29ihC_d-WpW2d03coF3lq_c0HuUEexvYJG8vTXnk,626
|
|
17
|
+
adi_super_memory/safety/redaction.py,sha256=vtBrXkr4wohrfyytfipEyRfjGBIxDEdSPeneldzxLZI,267
|
|
18
|
+
adi_super_memory-0.3.0.dist-info/METADATA,sha256=IB_g23hxsiUSZvD5_BwAsoshOgJG6zWffxB5xGrlI8c,715
|
|
19
|
+
adi_super_memory-0.3.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
20
|
+
adi_super_memory-0.3.0.dist-info/top_level.txt,sha256=6n6xha3RX2TEyVBrdWp959fyizdkEULt677xSL4lZOw,17
|
|
21
|
+
adi_super_memory-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
adi_super_memory
|