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/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
+ ]