hb-cortex-memory 0.1.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.
- cortex_memory/__init__.py +126 -0
- cortex_memory/_textutil.py +83 -0
- cortex_memory/assembly.py +335 -0
- cortex_memory/db.py +25 -0
- cortex_memory/domains.py +158 -0
- cortex_memory/dreaming.py +568 -0
- cortex_memory/dreaming_prompts.py +55 -0
- cortex_memory/dtos.py +260 -0
- cortex_memory/embedding.py +58 -0
- cortex_memory/enums.py +78 -0
- cortex_memory/episodic_tree.py +466 -0
- cortex_memory/experience_tree.py +230 -0
- cortex_memory/graph.py +409 -0
- cortex_memory/ingestion.py +224 -0
- cortex_memory/intelligence_tree.py +275 -0
- cortex_memory/knowledge_tree.py +483 -0
- cortex_memory/models.py +240 -0
- cortex_memory/prompts.py +21 -0
- cortex_memory/providers.py +156 -0
- cortex_memory/providers_reference.py +121 -0
- cortex_memory/py.typed +0 -0
- cortex_memory/schema.py +43 -0
- cortex_memory/scope_policy.py +53 -0
- cortex_memory/service.py +1196 -0
- hb_cortex_memory-0.1.0.dist-info/METADATA +168 -0
- hb_cortex_memory-0.1.0.dist-info/RECORD +29 -0
- hb_cortex_memory-0.1.0.dist-info/WHEEL +5 -0
- hb_cortex_memory-0.1.0.dist-info/licenses/LICENSE +201 -0
- hb_cortex_memory-0.1.0.dist-info/top_level.txt +1 -0
cortex_memory/dtos.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.dtos — CORTEX data-transfer objects.
|
|
3
|
+
|
|
4
|
+
Tree-level CRUD/list shapes, node summaries, viewport, content pages, node
|
|
5
|
+
creates, checkpoint writes, recurse requests; the ``Provenance`` block attached
|
|
6
|
+
to knowledge writes; and the ``GoalNode`` goal-decomposition unit. Moved out of
|
|
7
|
+
the host ``schemas/cortex.py`` (Phase 12 `04`); the host re-exports them.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field as dataclass_field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
from uuid import UUID
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from cortex_memory.enums import CortexNodeType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Provenance
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SourceType(str, Enum):
|
|
28
|
+
TOOL = "tool"
|
|
29
|
+
USER_UPLOAD = "user_upload"
|
|
30
|
+
REFLECTION = "reflection"
|
|
31
|
+
DREAMING = "dreaming"
|
|
32
|
+
EXTERNAL_LINK = "external_link"
|
|
33
|
+
MANUAL = "manual"
|
|
34
|
+
CONTEXT_SOURCE = "context_source"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Per-source-type default trust scores.
|
|
38
|
+
DEFAULT_TRUST_BY_SOURCE: Dict[str, float] = {
|
|
39
|
+
SourceType.USER_UPLOAD.value: 1.0,
|
|
40
|
+
SourceType.MANUAL.value: 0.9,
|
|
41
|
+
SourceType.DREAMING.value: 0.8,
|
|
42
|
+
SourceType.TOOL.value: 0.7,
|
|
43
|
+
SourceType.REFLECTION.value: 0.6,
|
|
44
|
+
SourceType.CONTEXT_SOURCE.value: 0.6,
|
|
45
|
+
SourceType.EXTERNAL_LINK.value: 0.4,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Provenance(BaseModel):
|
|
50
|
+
"""Typed source-tracking block attached to CORTEX knowledge writes."""
|
|
51
|
+
|
|
52
|
+
source_type: SourceType
|
|
53
|
+
tool_id: Optional[str] = None
|
|
54
|
+
url: Optional[str] = None
|
|
55
|
+
upload_ref: Optional[str] = None
|
|
56
|
+
fetched_at: Optional[datetime] = None
|
|
57
|
+
trust_score: Optional[float] = None
|
|
58
|
+
run_id: Optional[UUID] = None
|
|
59
|
+
step_id: Optional[str] = None
|
|
60
|
+
notes: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
def effective_trust_score(self) -> float:
|
|
63
|
+
if self.trust_score is not None:
|
|
64
|
+
return max(0.0, min(1.0, float(self.trust_score)))
|
|
65
|
+
return float(DEFAULT_TRUST_BY_SOURCE.get(self.source_type.value, 0.5))
|
|
66
|
+
|
|
67
|
+
def to_source_ref(self) -> Dict[str, Any]:
|
|
68
|
+
"""Serialise into the CortexNode.source_ref JSON blob."""
|
|
69
|
+
return {
|
|
70
|
+
"source_type": self.source_type.value,
|
|
71
|
+
"tool_id": self.tool_id,
|
|
72
|
+
"url": self.url,
|
|
73
|
+
"upload_ref": self.upload_ref,
|
|
74
|
+
"fetched_at": self.fetched_at.isoformat() if self.fetched_at else None,
|
|
75
|
+
"trust_score": self.effective_trust_score(),
|
|
76
|
+
"run_id": str(self.run_id) if self.run_id else None,
|
|
77
|
+
"step_id": self.step_id,
|
|
78
|
+
"notes": self.notes,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_source_ref(cls, raw: Optional[Dict[str, Any]]) -> Optional["Provenance"]:
|
|
83
|
+
if not raw or "source_type" not in raw:
|
|
84
|
+
return None
|
|
85
|
+
try:
|
|
86
|
+
fetched_at = raw.get("fetched_at")
|
|
87
|
+
if isinstance(fetched_at, str):
|
|
88
|
+
try:
|
|
89
|
+
fetched_at = datetime.fromisoformat(fetched_at)
|
|
90
|
+
except Exception:
|
|
91
|
+
fetched_at = None
|
|
92
|
+
return cls(
|
|
93
|
+
source_type=SourceType(raw["source_type"]),
|
|
94
|
+
tool_id=raw.get("tool_id"),
|
|
95
|
+
url=raw.get("url"),
|
|
96
|
+
upload_ref=raw.get("upload_ref"),
|
|
97
|
+
fetched_at=fetched_at,
|
|
98
|
+
trust_score=raw.get("trust_score"),
|
|
99
|
+
run_id=UUID(raw["run_id"]) if raw.get("run_id") else None,
|
|
100
|
+
step_id=raw.get("step_id"),
|
|
101
|
+
notes=raw.get("notes"),
|
|
102
|
+
)
|
|
103
|
+
except Exception:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Tree / node DTOs
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CortexTreeCreate(BaseModel):
|
|
113
|
+
entity_id: UUID
|
|
114
|
+
task_description: str
|
|
115
|
+
max_children: int = 12
|
|
116
|
+
page_size_tokens: int = 8000
|
|
117
|
+
context_budget_pct: int = 40
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class CortexTreeResponse(BaseModel):
|
|
121
|
+
id: UUID
|
|
122
|
+
entity_id: UUID
|
|
123
|
+
task_description: Optional[str]
|
|
124
|
+
status: str
|
|
125
|
+
total_nodes: int = 0
|
|
126
|
+
root_node_id: Optional[str] = None
|
|
127
|
+
output_root_id: Optional[str] = None
|
|
128
|
+
resume_cursor_id: Optional[str] = None
|
|
129
|
+
max_children: int = 12
|
|
130
|
+
created_at: Optional[datetime] = None
|
|
131
|
+
last_active_at: Optional[datetime] = None
|
|
132
|
+
|
|
133
|
+
class Config:
|
|
134
|
+
from_attributes = True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CortexTreeListResponse(BaseModel):
|
|
138
|
+
id: UUID
|
|
139
|
+
entity_id: UUID
|
|
140
|
+
task_description: Optional[str]
|
|
141
|
+
status: str
|
|
142
|
+
total_nodes: int = 0
|
|
143
|
+
created_at: Optional[datetime] = None
|
|
144
|
+
last_active_at: Optional[datetime] = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class CortexNodeSummary(BaseModel):
|
|
148
|
+
id: str
|
|
149
|
+
title: str
|
|
150
|
+
summary: Optional[str]
|
|
151
|
+
status: str
|
|
152
|
+
node_type: str
|
|
153
|
+
sibling_order: int = 0
|
|
154
|
+
depth: int = 0
|
|
155
|
+
content_tokens: int = 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class CortexViewportResponse(BaseModel):
|
|
159
|
+
current_node: CortexNodeSummary
|
|
160
|
+
children: List[CortexNodeSummary]
|
|
161
|
+
parent: Optional[CortexNodeSummary] = None
|
|
162
|
+
breadcrumb: List[Dict[str, str]]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class CortexNodeContentResponse(BaseModel):
|
|
166
|
+
node_id: str
|
|
167
|
+
title: str
|
|
168
|
+
content: str
|
|
169
|
+
page: int
|
|
170
|
+
total_pages: int
|
|
171
|
+
content_tokens: int
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class CortexNodeCreate(BaseModel):
|
|
175
|
+
parent_id: UUID
|
|
176
|
+
node_type: CortexNodeType
|
|
177
|
+
title: str
|
|
178
|
+
content: Optional[str] = None
|
|
179
|
+
summary: Optional[str] = None
|
|
180
|
+
status: str = "complete"
|
|
181
|
+
source_ref: Optional[Dict[str, Any]] = None
|
|
182
|
+
metadata_extra: Optional[Dict[str, Any]] = None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class CortexCheckpointCreate(BaseModel):
|
|
186
|
+
progress_summary: str
|
|
187
|
+
key_facts: List[str] = []
|
|
188
|
+
next_steps: List[str] = []
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class CortexRecurseRequest(BaseModel):
|
|
192
|
+
node_id: UUID
|
|
193
|
+
task: str
|
|
194
|
+
result_slot: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class CortexNodeDetailResponse(BaseModel):
|
|
198
|
+
id: str
|
|
199
|
+
tree_id: str
|
|
200
|
+
parent_id: Optional[str] = None
|
|
201
|
+
node_type: str
|
|
202
|
+
title: str
|
|
203
|
+
summary: Optional[str]
|
|
204
|
+
content_tokens: int = 0
|
|
205
|
+
status: str
|
|
206
|
+
depth: int = 0
|
|
207
|
+
sibling_order: int = 0
|
|
208
|
+
source_ref: Optional[Dict[str, Any]] = None
|
|
209
|
+
metadata_extra: Optional[Dict[str, Any]] = None
|
|
210
|
+
created_at: Optional[datetime] = None
|
|
211
|
+
updated_at: Optional[datetime] = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# GoalNode — unit of the goal-decomposition tree.
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class GoalNode:
|
|
221
|
+
"""A single node in the goal-decomposition tree."""
|
|
222
|
+
|
|
223
|
+
goal: str
|
|
224
|
+
depth: int = 0
|
|
225
|
+
confidence: float = 1.0
|
|
226
|
+
parent: Optional["GoalNode"] = dataclass_field(default=None, repr=False)
|
|
227
|
+
children: List["GoalNode"] = dataclass_field(default_factory=list)
|
|
228
|
+
result: Optional[str] = None
|
|
229
|
+
status: str = "pending"
|
|
230
|
+
|
|
231
|
+
def is_leaf(self) -> bool:
|
|
232
|
+
return len(self.children) == 0
|
|
233
|
+
|
|
234
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
235
|
+
return {
|
|
236
|
+
"goal": self.goal,
|
|
237
|
+
"depth": self.depth,
|
|
238
|
+
"confidence": self.confidence,
|
|
239
|
+
"status": self.status,
|
|
240
|
+
"result": self.result,
|
|
241
|
+
"children": [c.to_dict() for c in self.children],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
__all__ = [
|
|
246
|
+
"SourceType",
|
|
247
|
+
"DEFAULT_TRUST_BY_SOURCE",
|
|
248
|
+
"Provenance",
|
|
249
|
+
"CortexTreeCreate",
|
|
250
|
+
"CortexTreeResponse",
|
|
251
|
+
"CortexTreeListResponse",
|
|
252
|
+
"CortexNodeSummary",
|
|
253
|
+
"CortexViewportResponse",
|
|
254
|
+
"CortexNodeContentResponse",
|
|
255
|
+
"CortexNodeCreate",
|
|
256
|
+
"CortexCheckpointCreate",
|
|
257
|
+
"CortexRecurseRequest",
|
|
258
|
+
"CortexNodeDetailResponse",
|
|
259
|
+
"GoalNode",
|
|
260
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.embedding — CORTEX node/query embedding helpers.
|
|
3
|
+
|
|
4
|
+
Thin CORTEX-specific wrappers over an injected ``EmbeddingProvider``: select the
|
|
5
|
+
text to embed for a node, run the provider, and store the vector. Keeping this
|
|
6
|
+
node-aware logic in the package (rather than in the host's ``EmbeddingService``)
|
|
7
|
+
is what lets the domain services be host-free — they call these with whatever
|
|
8
|
+
provider the host injected.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, List, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def embed_query(provider: Any, text: str) -> Optional[List[float]]:
|
|
16
|
+
"""Embed a single query string; ``None`` if no provider or no result."""
|
|
17
|
+
if provider is None or not text:
|
|
18
|
+
return None
|
|
19
|
+
res = await provider.embed([text])
|
|
20
|
+
if res.vectors and res.vectors[0]:
|
|
21
|
+
return list(res.vectors[0])
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def embed_texts(provider: Any, texts: List[str]) -> tuple[List[Optional[List[float]]], str]:
|
|
26
|
+
"""Embed a batch; returns (vectors, model_name). Missing → None entries."""
|
|
27
|
+
if provider is None or not texts:
|
|
28
|
+
return [None] * len(texts), ""
|
|
29
|
+
res = await provider.embed(list(texts))
|
|
30
|
+
vectors: List[Optional[List[float]]] = [
|
|
31
|
+
list(v) if v else None for v in res.vectors
|
|
32
|
+
]
|
|
33
|
+
return vectors, res.model
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def embed_node(provider: Any, node: Any) -> bool:
|
|
37
|
+
"""Generate + store an embedding for a CortexNode.
|
|
38
|
+
|
|
39
|
+
Uses ``node.summary``, then ``node.title``, then ``node.content`` (truncated).
|
|
40
|
+
Sets ``node.embedding`` / ``node.embedding_model``. Returns True on success.
|
|
41
|
+
"""
|
|
42
|
+
if provider is None:
|
|
43
|
+
return False
|
|
44
|
+
text_to_embed = node.summary or node.title
|
|
45
|
+
if not text_to_embed and node.content:
|
|
46
|
+
text_to_embed = node.content[:2000]
|
|
47
|
+
if not text_to_embed:
|
|
48
|
+
return False
|
|
49
|
+
res = await provider.embed([text_to_embed])
|
|
50
|
+
vec = res.vectors[0] if res.vectors and res.vectors[0] else None
|
|
51
|
+
if vec:
|
|
52
|
+
node.embedding = list(vec)
|
|
53
|
+
node.embedding_model = res.model or ""
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = ["embed_query", "embed_texts", "embed_node"]
|
cortex_memory/enums.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.enums — CORTEX domain enums.
|
|
3
|
+
|
|
4
|
+
Moved out of the host (Phase 12 `04`). These are the canonical definitions; the
|
|
5
|
+
host re-exports ``CortexNodeType`` (and the others, via ``cortex_models``) for
|
|
6
|
+
backward compatibility.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CortexTreeStatus(str, enum.Enum):
|
|
14
|
+
ACTIVE = "active"
|
|
15
|
+
SUSPENDED = "suspended"
|
|
16
|
+
COMPLETE = "complete"
|
|
17
|
+
ARCHIVED = "archived"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CortexNodeType(str, enum.Enum):
|
|
21
|
+
# --- v1 types ---
|
|
22
|
+
ROOT = "root"
|
|
23
|
+
KNOWLEDGE = "knowledge" # Ingested from a document (PageIndex-derived)
|
|
24
|
+
FINDING = "finding" # Written by the agent during execution
|
|
25
|
+
TASK = "task" # A sub-task to be executed
|
|
26
|
+
OUTPUT = "output" # A section of the output document
|
|
27
|
+
CHECKPOINT = "checkpoint" # A compacted state snapshot
|
|
28
|
+
# --- v2 types ---
|
|
29
|
+
GROUP = "group" # Re-clustering group container
|
|
30
|
+
DOCUMENT = "document" # Represents an ingested document
|
|
31
|
+
SECTION = "section" # A section/chapter within a document
|
|
32
|
+
CHUNK = "chunk" # Leaf-level text chunk with embedding
|
|
33
|
+
OBSERVATION = "observation" # Experience: specific observation
|
|
34
|
+
PATTERN = "pattern" # Experience: recurring pattern
|
|
35
|
+
SUGGESTION = "suggestion" # Experience: suggested approach
|
|
36
|
+
INSTRUCTION = "instruction" # Intelligence: distilled actionable rule
|
|
37
|
+
STRATEGY = "strategy" # Intelligence: high-level strategic approach
|
|
38
|
+
PREFERENCE = "preference" # Intelligence: user/entity preference
|
|
39
|
+
EPISODE = "episode" # Episodic: single execution episode record
|
|
40
|
+
EPISODE_GROUP = "episode_group" # Episodic: grouped episodes
|
|
41
|
+
# --- agent-loop types ---
|
|
42
|
+
SNAPSHOT = "snapshot" # AgentState snapshot written each loop iteration
|
|
43
|
+
HEALTH_RECORD = "health_record" # Critic StepHealthRecord
|
|
44
|
+
HEALTH_ROOT = "health_root" # Container node for a run's health records
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CortexNodeStatus(str, enum.Enum):
|
|
48
|
+
PENDING = "pending"
|
|
49
|
+
ACTIVE = "active"
|
|
50
|
+
COMPLETE = "complete"
|
|
51
|
+
SUMMARISED = "summarised"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MemoryDomain(str, enum.Enum):
|
|
55
|
+
"""Which memory domain a CORTEX tree belongs to."""
|
|
56
|
+
KNOWLEDGE = "knowledge"
|
|
57
|
+
EXPERIENCE = "experience"
|
|
58
|
+
INTELLIGENCE = "intelligence"
|
|
59
|
+
EPISODIC = "episodic"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ScopeLevel(str, enum.Enum):
|
|
63
|
+
"""Hierarchical scope level for memory inheritance."""
|
|
64
|
+
APP = "app" # L0: Platform-wide
|
|
65
|
+
PARTNER = "partner" # L1: Partner organization
|
|
66
|
+
TENANT = "tenant" # L2: Company/tenant
|
|
67
|
+
USER = "user" # L3: End-user
|
|
68
|
+
ENTITY = "entity" # L4: Agent/entity
|
|
69
|
+
RUNTIME = "runtime" # L5: Single execution run
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"CortexTreeStatus",
|
|
74
|
+
"CortexNodeType",
|
|
75
|
+
"CortexNodeStatus",
|
|
76
|
+
"MemoryDomain",
|
|
77
|
+
"ScopeLevel",
|
|
78
|
+
]
|