memblock 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.
memblock/__init__.py ADDED
@@ -0,0 +1,95 @@
1
+ # MemBlock SDK — Typed block tree + knowledge graph memory for AI agents
2
+ """
3
+ MemBlock: Structured memory SDK for AI agents.
4
+
5
+ Usage:
6
+ from memblock import MemBlock, BlockType, SourceType
7
+
8
+ mem = MemBlock(storage="sqlite:///./memory.db")
9
+ block = mem.store(content="User prefers Python", type=BlockType.PREFERENCE)
10
+ mem.link(block.id, other_block.id, relation="supports")
11
+ results = mem.query(type=BlockType.PREFERENCE)
12
+ context = mem.build_context(query="what does the user prefer?", token_budget=4000)
13
+ mem.verify() # check tamper detection
14
+ """
15
+
16
+ __version__ = "0.4.0"
17
+
18
+ from memblock.block import Block
19
+ from memblock.context import ContextBuilder
20
+ from memblock.crypto import CryptoLayer, CryptoLayerWithPassphrase
21
+ from memblock.decay import DecayEngine
22
+ from memblock.dedup import DuplicatePolicy
23
+ from memblock.errors import (
24
+ BlockNotFoundError,
25
+ ConflictResolutionError,
26
+ DuplicateBlockError,
27
+ EncryptionError,
28
+ ExtractionError,
29
+ LicenseError,
30
+ MemBlockError,
31
+ MigrationError,
32
+ StorageError,
33
+ ValidationError,
34
+ )
35
+ from memblock.graph import GraphIndex
36
+ from memblock.hooks import EventType, HookManager
37
+ from memblock.ops import OpLog, TamperReport
38
+ from memblock.query import QueryEngine
39
+ from memblock.schema import BlockSchema, SchemaValidationError
40
+ from memblock.storage.base import StorageAdapter
41
+ from memblock.storage.sqlite import SQLiteAdapter
42
+ from memblock.store import BlockStore
43
+ from memblock.types import (
44
+ BlockMetadata,
45
+ BlockType,
46
+ Edge,
47
+ EdgeRelation,
48
+ EncryptionLevel,
49
+ OpAction,
50
+ Operation,
51
+ SourceType,
52
+ )
53
+
54
+ from memblock.memblock import MemBlock
55
+ from memblock.async_memblock import AsyncMemBlock
56
+
57
+ __all__ = [
58
+ "AsyncMemBlock",
59
+ "MemBlock",
60
+ "Block",
61
+ "BlockMetadata",
62
+ "BlockNotFoundError",
63
+ "BlockSchema",
64
+ "BlockStore",
65
+ "BlockType",
66
+ "ConflictResolutionError",
67
+ "ContextBuilder",
68
+ "CryptoLayer",
69
+ "CryptoLayerWithPassphrase",
70
+ "DecayEngine",
71
+ "DuplicateBlockError",
72
+ "DuplicatePolicy",
73
+ "Edge",
74
+ "EdgeRelation",
75
+ "EncryptionError",
76
+ "EncryptionLevel",
77
+ "EventType",
78
+ "ExtractionError",
79
+ "GraphIndex",
80
+ "HookManager",
81
+ "LicenseError",
82
+ "MemBlockError",
83
+ "MigrationError",
84
+ "OpAction",
85
+ "OpLog",
86
+ "Operation",
87
+ "QueryEngine",
88
+ "SchemaValidationError",
89
+ "SourceType",
90
+ "SQLiteAdapter",
91
+ "StorageAdapter",
92
+ "StorageError",
93
+ "TamperReport",
94
+ "ValidationError",
95
+ ]
@@ -0,0 +1,263 @@
1
+ """AsyncMemBlock — async wrapper around MemBlock for use in async frameworks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ from memblock.block import Block
9
+ from memblock.memblock import MemBlock
10
+ from memblock.ops import TamperReport
11
+ from memblock.types import (
12
+ BlockType,
13
+ EdgeRelation,
14
+ EncryptionLevel,
15
+ SourceType,
16
+ )
17
+
18
+
19
+ class AsyncMemBlock:
20
+ """
21
+ Async-compatible wrapper around MemBlock.
22
+
23
+ All I/O methods are wrapped with asyncio.to_thread() so they don't
24
+ block the event loop. Uses the same MemBlock instance internally.
25
+
26
+ Usage:
27
+ async with AsyncMemBlock(storage="sqlite:///./memory.db") as mem:
28
+ block = await mem.store("User prefers Python", type=BlockType.PREFERENCE)
29
+ results = await mem.query(type=BlockType.PREFERENCE)
30
+ context = await mem.build_context(query="user preferences")
31
+ """
32
+
33
+ def __init__(self, **kwargs: Any) -> None:
34
+ """
35
+ Initialize AsyncMemBlock with the same parameters as MemBlock.
36
+
37
+ All constructor args are passed directly to MemBlock.
38
+ """
39
+ self._mem = MemBlock(**kwargs)
40
+
41
+ @property
42
+ def sync(self) -> MemBlock:
43
+ """Access the underlying sync MemBlock instance."""
44
+ return self._mem
45
+
46
+ # ─── Store Operations ─────────────────────────────────────────────────
47
+
48
+ async def store(
49
+ self,
50
+ content: str,
51
+ type: BlockType = BlockType.FACT,
52
+ confidence: float = 1.0,
53
+ source: SourceType = SourceType.EXPLICIT,
54
+ tags: list[str] | None = None,
55
+ parent_id: str | None = None,
56
+ encryption_level: EncryptionLevel = EncryptionLevel.NONE,
57
+ decay_rate: float = 0.01,
58
+ ttl: int | None = None,
59
+ session_id: str | None = None,
60
+ org_id: str | None = None,
61
+ project_id: str | None = None,
62
+ agent_id: str | None = None,
63
+ metadata: dict[str, Any] | None = None,
64
+ ) -> Block:
65
+ """Store a new memory block (async)."""
66
+ return await asyncio.to_thread(
67
+ self._mem.store,
68
+ content=content,
69
+ type=type,
70
+ confidence=confidence,
71
+ source=source,
72
+ tags=tags,
73
+ parent_id=parent_id,
74
+ encryption_level=encryption_level,
75
+ decay_rate=decay_rate,
76
+ ttl=ttl,
77
+ session_id=session_id,
78
+ org_id=org_id,
79
+ project_id=project_id,
80
+ agent_id=agent_id,
81
+ metadata=metadata,
82
+ )
83
+
84
+ async def get(self, block_id: str, decrypt: bool = True) -> Block | None:
85
+ """Retrieve a block by ID (async)."""
86
+ return await asyncio.to_thread(self._mem.get, block_id, decrypt)
87
+
88
+ async def update(self, block_id: str, **updates: Any) -> Block | None:
89
+ """Update a block's fields (async)."""
90
+ return await asyncio.to_thread(self._mem.update, block_id, **updates)
91
+
92
+ async def delete(self, block_id: str, cascade: bool = False) -> bool:
93
+ """Soft-delete a block (async)."""
94
+ return await asyncio.to_thread(self._mem.delete, block_id, cascade)
95
+
96
+ # ─── Graph Operations ─────────────────────────────────────────────────
97
+
98
+ async def link(
99
+ self,
100
+ source_id: str,
101
+ target_id: str,
102
+ relation: EdgeRelation | str = EdgeRelation.RELATED_TO,
103
+ weight: float = 1.0,
104
+ ) -> None:
105
+ """Create a relationship between two blocks (async)."""
106
+ await asyncio.to_thread(self._mem.link, source_id, target_id, relation, weight)
107
+
108
+ async def unlink(
109
+ self,
110
+ source_id: str,
111
+ target_id: str,
112
+ relation: EdgeRelation | str | None = None,
113
+ ) -> int:
114
+ """Remove relationship(s) between two blocks (async)."""
115
+ return await asyncio.to_thread(self._mem.unlink, source_id, target_id, relation)
116
+
117
+ async def neighbors(self, block_id: str, relation: EdgeRelation | None = None) -> list[Block]:
118
+ """Get blocks directly connected to a block (async)."""
119
+ return await asyncio.to_thread(self._mem.neighbors, block_id, relation)
120
+
121
+ async def traverse(self, block_id: str, max_depth: int = 3) -> list[Block]:
122
+ """Walk the graph from a block (async)."""
123
+ return await asyncio.to_thread(self._mem.traverse, block_id, max_depth)
124
+
125
+ # ─── Query ────────────────────────────────────────────────────────────
126
+
127
+ async def query(
128
+ self,
129
+ type: BlockType | None = None,
130
+ tags: list[str] | None = None,
131
+ text_search: str | None = None,
132
+ related_to: str | None = None,
133
+ min_confidence: float = 0.0,
134
+ sort_by: str = "relevance",
135
+ limit: int = 10,
136
+ semantic: bool = True,
137
+ session_id: str | None = None,
138
+ org_id: str | None = None,
139
+ project_id: str | None = None,
140
+ agent_id: str | None = None,
141
+ metadata_filters: dict[str, Any] | None = None,
142
+ ) -> list[Block]:
143
+ """Query memory blocks with structured filters (async)."""
144
+ return await asyncio.to_thread(
145
+ self._mem.query,
146
+ type=type,
147
+ tags=tags,
148
+ text_search=text_search,
149
+ related_to=related_to,
150
+ min_confidence=min_confidence,
151
+ sort_by=sort_by,
152
+ limit=limit,
153
+ semantic=semantic,
154
+ session_id=session_id,
155
+ org_id=org_id,
156
+ project_id=project_id,
157
+ agent_id=agent_id,
158
+ metadata_filters=metadata_filters,
159
+ )
160
+
161
+ # ─── Context Builder ──────────────────────────────────────────────────
162
+
163
+ async def build_context(
164
+ self,
165
+ query: str | None = None,
166
+ token_budget: int = 4000,
167
+ strategy: str = "relevance",
168
+ include_metadata: bool = True,
169
+ session_id: str | None = None,
170
+ org_id: str | None = None,
171
+ project_id: str | None = None,
172
+ agent_id: str | None = None,
173
+ metadata_filters: dict[str, Any] | None = None,
174
+ ) -> str:
175
+ """Build LLM-ready context from relevant memory blocks (async)."""
176
+ return await asyncio.to_thread(
177
+ self._mem.build_context,
178
+ query=query,
179
+ token_budget=token_budget,
180
+ strategy=strategy,
181
+ include_metadata=include_metadata,
182
+ session_id=session_id,
183
+ org_id=org_id,
184
+ project_id=project_id,
185
+ agent_id=agent_id,
186
+ metadata_filters=metadata_filters,
187
+ )
188
+
189
+ # ─── Extraction ───────────────────────────────────────────────────────
190
+
191
+ async def extract(self, conversation: str, **kwargs: Any) -> Any:
192
+ """Auto-extract memory blocks from a conversation (async)."""
193
+ return await asyncio.to_thread(self._mem.extract, conversation, **kwargs)
194
+
195
+ async def extract_messages(self, messages: list[dict[str, str]], **kwargs: Any) -> Any:
196
+ """Auto-extract from a list of message dicts (async)."""
197
+ return await asyncio.to_thread(self._mem.extract_messages, messages, **kwargs)
198
+
199
+ # ─── Session Operations ────────────────────────────────────────────────
200
+
201
+ async def get_sessions(self) -> list[str]:
202
+ """Get all distinct session IDs (async)."""
203
+ return await asyncio.to_thread(self._mem.get_sessions)
204
+
205
+ async def get_session_history(self, session_id: str, limit: int = 100) -> list[Block]:
206
+ """Get blocks for a specific session (async)."""
207
+ return await asyncio.to_thread(self._mem.get_session_history, session_id, limit)
208
+
209
+ # ─── Integrity ────────────────────────────────────────────────────────
210
+
211
+ async def verify(self) -> TamperReport:
212
+ """Verify the integrity of the operation log (async)."""
213
+ return await asyncio.to_thread(self._mem.verify)
214
+
215
+ # ─── Decay & Maintenance ──────────────────────────────────────────────
216
+
217
+ async def prune(self, min_strength: float = 0.1) -> list[Block]:
218
+ """Remove decayed memories below the strength threshold (async)."""
219
+ return await asyncio.to_thread(self._mem.prune, min_strength)
220
+
221
+ async def strongest(self, limit: int = 10) -> list[tuple[Block, float]]:
222
+ """Get the strongest memories (async)."""
223
+ return await asyncio.to_thread(self._mem.strongest, limit)
224
+
225
+ async def weakest(self, limit: int = 10) -> list[tuple[Block, float]]:
226
+ """Get the weakest memories (async)."""
227
+ return await asyncio.to_thread(self._mem.weakest, limit)
228
+
229
+ # ─── Hooks ────────────────────────────────────────────────────────────
230
+
231
+ def on(self, event: str, callback: Any) -> None:
232
+ """Register a callback for a memory lifecycle event."""
233
+ self._mem.on(event, callback)
234
+
235
+ # ─── Properties ───────────────────────────────────────────────────────
236
+
237
+ @property
238
+ def has_embeddings(self) -> bool:
239
+ """Whether embedding-based semantic search is enabled."""
240
+ return self._mem.has_embeddings
241
+
242
+ async def stats(self) -> dict[str, Any]:
243
+ """Get statistics about the memory store (async)."""
244
+ return await asyncio.to_thread(self._mem.stats)
245
+
246
+ async def export_markdown(self) -> str:
247
+ """Export all memories as human-readable markdown (async)."""
248
+ return await asyncio.to_thread(self._mem.export_markdown)
249
+
250
+ # ─── Lifecycle ────────────────────────────────────────────────────────
251
+
252
+ async def close(self) -> None:
253
+ """Close the storage connection (async)."""
254
+ await asyncio.to_thread(self._mem.close)
255
+
256
+ async def __aenter__(self) -> AsyncMemBlock:
257
+ return self
258
+
259
+ async def __aexit__(self, *args: Any) -> None:
260
+ await self.close()
261
+
262
+ def __repr__(self) -> str:
263
+ return f"Async{repr(self._mem)}"
memblock/block.py ADDED
@@ -0,0 +1,111 @@
1
+ """Block model — the core unit of memory in MemBlock."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from memblock.types import (
9
+ BlockMetadata,
10
+ BlockType,
11
+ Edge,
12
+ EncryptionLevel,
13
+ generate_block_id,
14
+ )
15
+
16
+
17
+ @dataclass
18
+ class Block:
19
+ """
20
+ A single memory block — the fundamental unit of the MemBlock system.
21
+
22
+ Each block represents a piece of knowledge (fact, preference, event, entity,
23
+ or relation) with associated metadata, graph edges, and version tracking.
24
+ """
25
+
26
+ # Identity
27
+ id: str = field(default_factory=generate_block_id)
28
+ type: BlockType = BlockType.FACT
29
+ content: str = ""
30
+
31
+ # Metadata
32
+ metadata: BlockMetadata = field(default_factory=BlockMetadata)
33
+
34
+ # Encryption
35
+ encryption_level: EncryptionLevel = EncryptionLevel.NONE
36
+ encrypted: bool = False
37
+
38
+ # Graph edges (relationships to other blocks)
39
+ edges: list[Edge] = field(default_factory=list)
40
+
41
+ # Tree structure
42
+ parent_id: str | None = None
43
+ children_ids: list[str] = field(default_factory=list)
44
+
45
+ # Versioning & integrity
46
+ version: int = 1
47
+ op_hash: str = "" # hash from operation log for tamper detection
48
+
49
+ # Tags for categorization
50
+ tags: list[str] = field(default_factory=list)
51
+
52
+ # Content hash for deduplication
53
+ content_hash: str = ""
54
+
55
+ # Soft delete
56
+ deleted: bool = False
57
+
58
+ def to_dict(self) -> dict[str, Any]:
59
+ """Serialize block to dictionary."""
60
+ return {
61
+ "id": self.id,
62
+ "type": self.type.value,
63
+ "content": self.content,
64
+ "metadata": self.metadata.to_dict(),
65
+ "encryption_level": self.encryption_level.value,
66
+ "encrypted": self.encrypted,
67
+ "edges": [e.to_dict() for e in self.edges],
68
+ "parent_id": self.parent_id,
69
+ "children_ids": self.children_ids,
70
+ "version": self.version,
71
+ "op_hash": self.op_hash,
72
+ "tags": self.tags,
73
+ "content_hash": self.content_hash,
74
+ "deleted": self.deleted,
75
+ }
76
+
77
+ @classmethod
78
+ def from_dict(cls, data: dict[str, Any]) -> Block:
79
+ """Deserialize block from dictionary."""
80
+ return cls(
81
+ id=data["id"],
82
+ type=BlockType(data["type"]),
83
+ content=data.get("content", ""),
84
+ metadata=BlockMetadata.from_dict(data["metadata"]) if "metadata" in data else BlockMetadata(),
85
+ encryption_level=EncryptionLevel(data.get("encryption_level", "none")),
86
+ encrypted=data.get("encrypted", False),
87
+ edges=[Edge.from_dict(e) for e in data.get("edges", [])],
88
+ parent_id=data.get("parent_id"),
89
+ children_ids=data.get("children_ids", []),
90
+ version=data.get("version", 1),
91
+ op_hash=data.get("op_hash", ""),
92
+ tags=data.get("tags", []),
93
+ content_hash=data.get("content_hash", ""),
94
+ deleted=data.get("deleted", False),
95
+ )
96
+
97
+ def to_snapshot(self) -> dict[str, Any]:
98
+ """Lightweight export for context building — no internal state."""
99
+ return {
100
+ "id": self.id,
101
+ "type": self.type.value,
102
+ "content": self.content,
103
+ "confidence": self.metadata.confidence,
104
+ "source": self.metadata.source.value,
105
+ "tags": self.tags,
106
+ "edge_count": len(self.edges),
107
+ }
108
+
109
+ def __repr__(self) -> str:
110
+ content_preview = self.content[:50] + "..." if len(self.content) > 50 else self.content
111
+ return f"Block(id={self.id!r}, type={self.type.value}, content={content_preview!r})"