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 +95 -0
- memblock/async_memblock.py +263 -0
- memblock/block.py +111 -0
- memblock/cli.py +342 -0
- memblock/conflict.py +208 -0
- memblock/context.py +298 -0
- memblock/crypto.py +144 -0
- memblock/decay.py +151 -0
- memblock/dedup.py +104 -0
- memblock/embeddings.py +202 -0
- memblock/errors.py +53 -0
- memblock/extraction.py +387 -0
- memblock/graph.py +235 -0
- memblock/hooks.py +108 -0
- memblock/licensing.py +187 -0
- memblock/memblock.py +1013 -0
- memblock/migrations.py +372 -0
- memblock/ops.py +144 -0
- memblock/query.py +229 -0
- memblock/rerankers.py +248 -0
- memblock/schema.py +145 -0
- memblock/storage/__init__.py +9 -0
- memblock/storage/base.py +160 -0
- memblock/storage/postgresql.py +773 -0
- memblock/storage/sqlite.py +609 -0
- memblock/store.py +335 -0
- memblock/types.py +213 -0
- memblock-0.4.0.dist-info/METADATA +472 -0
- memblock-0.4.0.dist-info/RECORD +33 -0
- memblock-0.4.0.dist-info/WHEEL +5 -0
- memblock-0.4.0.dist-info/entry_points.txt +2 -0
- memblock-0.4.0.dist-info/licenses/LICENSE +38 -0
- memblock-0.4.0.dist-info/top_level.txt +1 -0
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})"
|