memex-python 0.13.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.
- memex/__init__.py +336 -0
- memex/_time.py +26 -0
- memex/_uuid.py +62 -0
- memex/bulk.py +138 -0
- memex/commands.py +75 -0
- memex/envelope.py +69 -0
- memex/errors.py +51 -0
- memex/factories.py +97 -0
- memex/graph.py +30 -0
- memex/integrity.py +317 -0
- memex/intent.py +318 -0
- memex/models.py +271 -0
- memex/query.py +435 -0
- memex/reducer.py +151 -0
- memex/replay.py +144 -0
- memex/retrieval.py +266 -0
- memex/schemas.py +67 -0
- memex/serialization.py +47 -0
- memex/stats.py +71 -0
- memex/store.py +222 -0
- memex/task.py +361 -0
- memex/transplant.py +480 -0
- memex_python-0.13.0.dist-info/METADATA +150 -0
- memex_python-0.13.0.dist-info/RECORD +26 -0
- memex_python-0.13.0.dist-info/WHEEL +4 -0
- memex_python-0.13.0.dist-info/licenses/LICENSE +190 -0
memex/intent.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Intent graph — active goals with a status machine.
|
|
2
|
+
|
|
3
|
+
``active ⇄ paused → completed / cancelled``. Invalid transitions raise
|
|
4
|
+
``InvalidIntentTransitionError``. Same command → reducer → events pattern as the
|
|
5
|
+
memory graph, with its own reducer (``apply_intent_command``).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Annotated, Any, Literal, NamedTuple
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
|
|
14
|
+
|
|
15
|
+
from ._uuid import uuid7
|
|
16
|
+
from .errors import MemexError
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"IntentStatus",
|
|
20
|
+
"Intent",
|
|
21
|
+
"IntentState",
|
|
22
|
+
"IntentCommand",
|
|
23
|
+
"IntentLifecycleEvent",
|
|
24
|
+
"IntentFilter",
|
|
25
|
+
"IntentResult",
|
|
26
|
+
"IntentNotFoundError",
|
|
27
|
+
"DuplicateIntentError",
|
|
28
|
+
"InvalidIntentTransitionError",
|
|
29
|
+
"create_intent_state",
|
|
30
|
+
"create_intent",
|
|
31
|
+
"apply_intent_command",
|
|
32
|
+
"get_intents",
|
|
33
|
+
"get_intent_by_id",
|
|
34
|
+
"get_child_intents",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
IntentStatus = Literal["active", "paused", "completed", "cancelled"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Intent(BaseModel):
|
|
41
|
+
model_config = ConfigDict(frozen=True)
|
|
42
|
+
|
|
43
|
+
id: str
|
|
44
|
+
parent_id: str | None = None
|
|
45
|
+
label: str
|
|
46
|
+
description: str | None = None
|
|
47
|
+
|
|
48
|
+
priority: float = Field(ge=0, le=1)
|
|
49
|
+
owner: str
|
|
50
|
+
status: IntentStatus
|
|
51
|
+
|
|
52
|
+
context: dict[str, Any] | None = None
|
|
53
|
+
root_memory_ids: list[str] | None = None
|
|
54
|
+
|
|
55
|
+
meta: dict[str, Any] | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class IntentState:
|
|
60
|
+
intents: dict[str, Intent] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_intent_state() -> IntentState:
|
|
64
|
+
return IntentState(intents={})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_intent(
|
|
68
|
+
*,
|
|
69
|
+
label: str,
|
|
70
|
+
priority: float,
|
|
71
|
+
owner: str,
|
|
72
|
+
id: str | None = None,
|
|
73
|
+
parent_id: str | None = None,
|
|
74
|
+
description: str | None = None,
|
|
75
|
+
status: IntentStatus | None = None,
|
|
76
|
+
context: dict[str, Any] | None = None,
|
|
77
|
+
root_memory_ids: list[str] | None = None,
|
|
78
|
+
meta: dict[str, Any] | None = None,
|
|
79
|
+
) -> Intent:
|
|
80
|
+
return Intent(
|
|
81
|
+
id=id if id is not None else uuid7(),
|
|
82
|
+
parent_id=parent_id,
|
|
83
|
+
label=label,
|
|
84
|
+
description=description,
|
|
85
|
+
priority=priority,
|
|
86
|
+
owner=owner,
|
|
87
|
+
status=status if status is not None else "active",
|
|
88
|
+
context=context,
|
|
89
|
+
root_memory_ids=root_memory_ids,
|
|
90
|
+
meta=meta,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Commands
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class IntentCreate(BaseModel):
|
|
100
|
+
type: Literal["intent.create"] = "intent.create"
|
|
101
|
+
intent: Intent
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class IntentUpdate(BaseModel):
|
|
105
|
+
type: Literal["intent.update"] = "intent.update"
|
|
106
|
+
intent_id: str
|
|
107
|
+
partial: dict[str, Any]
|
|
108
|
+
author: str
|
|
109
|
+
reason: str | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class IntentComplete(BaseModel):
|
|
113
|
+
type: Literal["intent.complete"] = "intent.complete"
|
|
114
|
+
intent_id: str
|
|
115
|
+
author: str
|
|
116
|
+
reason: str | None = None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class IntentCancel(BaseModel):
|
|
120
|
+
type: Literal["intent.cancel"] = "intent.cancel"
|
|
121
|
+
intent_id: str
|
|
122
|
+
author: str
|
|
123
|
+
reason: str | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class IntentPause(BaseModel):
|
|
127
|
+
type: Literal["intent.pause"] = "intent.pause"
|
|
128
|
+
intent_id: str
|
|
129
|
+
author: str
|
|
130
|
+
reason: str | None = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class IntentResume(BaseModel):
|
|
134
|
+
type: Literal["intent.resume"] = "intent.resume"
|
|
135
|
+
intent_id: str
|
|
136
|
+
author: str
|
|
137
|
+
reason: str | None = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
IntentCommand = Annotated[
|
|
141
|
+
IntentCreate | IntentUpdate | IntentComplete | IntentCancel | IntentPause | IntentResume,
|
|
142
|
+
Field(discriminator="type"),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
_INTENT_COMMAND_ADAPTER: TypeAdapter[IntentCommand] = TypeAdapter(IntentCommand)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
IntentEventType = Literal[
|
|
149
|
+
"intent.created",
|
|
150
|
+
"intent.updated",
|
|
151
|
+
"intent.completed",
|
|
152
|
+
"intent.cancelled",
|
|
153
|
+
"intent.paused",
|
|
154
|
+
"intent.resumed",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class IntentLifecycleEvent(BaseModel):
|
|
159
|
+
namespace: Literal["intent"] = "intent"
|
|
160
|
+
type: IntentEventType
|
|
161
|
+
intent: Intent
|
|
162
|
+
cause_type: str
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class IntentResult(NamedTuple):
|
|
166
|
+
state: IntentState
|
|
167
|
+
events: list[IntentLifecycleEvent]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Errors
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class IntentNotFoundError(MemexError):
|
|
176
|
+
def __init__(self, intent_id: str) -> None:
|
|
177
|
+
super().__init__(f"Intent not found: {intent_id}")
|
|
178
|
+
self.intent_id = intent_id
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class DuplicateIntentError(MemexError):
|
|
182
|
+
def __init__(self, intent_id: str) -> None:
|
|
183
|
+
super().__init__(f"Intent already exists: {intent_id}")
|
|
184
|
+
self.intent_id = intent_id
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class InvalidIntentTransitionError(MemexError):
|
|
188
|
+
def __init__(self, intent_id: str, from_status: str, to_status: str) -> None:
|
|
189
|
+
super().__init__(f"Invalid intent transition: {intent_id} from {from_status} to {to_status}")
|
|
190
|
+
self.intent_id = intent_id
|
|
191
|
+
self.from_status = from_status
|
|
192
|
+
self.to_status = to_status
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Reducer
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _set_status(
|
|
201
|
+
state: IntentState,
|
|
202
|
+
intent_id: str,
|
|
203
|
+
target: IntentStatus,
|
|
204
|
+
valid_from: tuple[IntentStatus, ...],
|
|
205
|
+
cause_type: str,
|
|
206
|
+
event_type: IntentEventType,
|
|
207
|
+
) -> IntentResult:
|
|
208
|
+
existing = state.intents.get(intent_id)
|
|
209
|
+
if existing is None:
|
|
210
|
+
raise IntentNotFoundError(intent_id)
|
|
211
|
+
if existing.status not in valid_from:
|
|
212
|
+
raise InvalidIntentTransitionError(intent_id, existing.status, target)
|
|
213
|
+
updated = existing.model_copy(update={"status": target})
|
|
214
|
+
intents = {**state.intents, intent_id: updated}
|
|
215
|
+
return IntentResult(
|
|
216
|
+
IntentState(intents),
|
|
217
|
+
[IntentLifecycleEvent(type=event_type, intent=updated, cause_type=cause_type)],
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def apply_intent_command(state: IntentState, cmd: IntentCommand | dict[str, Any]) -> IntentResult:
|
|
222
|
+
command = cmd if isinstance(cmd, BaseModel) else _INTENT_COMMAND_ADAPTER.validate_python(cmd)
|
|
223
|
+
|
|
224
|
+
match command:
|
|
225
|
+
case IntentCreate(intent=intent):
|
|
226
|
+
if intent.id in state.intents:
|
|
227
|
+
raise DuplicateIntentError(intent.id)
|
|
228
|
+
intents = {**state.intents, intent.id: intent}
|
|
229
|
+
return IntentResult(
|
|
230
|
+
IntentState(intents),
|
|
231
|
+
[IntentLifecycleEvent(type="intent.created", intent=intent, cause_type="intent.create")],
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
case IntentUpdate(intent_id=intent_id, partial=partial):
|
|
235
|
+
existing = state.intents.get(intent_id)
|
|
236
|
+
if existing is None:
|
|
237
|
+
raise IntentNotFoundError(intent_id)
|
|
238
|
+
update = {k: v for k, v in partial.items() if k not in ("id", "status")}
|
|
239
|
+
updated = existing.model_copy(update=update)
|
|
240
|
+
intents = {**state.intents, intent_id: updated}
|
|
241
|
+
return IntentResult(
|
|
242
|
+
IntentState(intents),
|
|
243
|
+
[IntentLifecycleEvent(type="intent.updated", intent=updated, cause_type="intent.update")],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
case IntentComplete(intent_id=intent_id):
|
|
247
|
+
return _set_status(state, intent_id, "completed", ("active", "paused"), "intent.complete", "intent.completed")
|
|
248
|
+
|
|
249
|
+
case IntentCancel(intent_id=intent_id):
|
|
250
|
+
return _set_status(state, intent_id, "cancelled", ("active", "paused"), "intent.cancel", "intent.cancelled")
|
|
251
|
+
|
|
252
|
+
case IntentPause(intent_id=intent_id):
|
|
253
|
+
return _set_status(state, intent_id, "paused", ("active",), "intent.pause", "intent.paused")
|
|
254
|
+
|
|
255
|
+
case IntentResume(intent_id=intent_id):
|
|
256
|
+
return _set_status(state, intent_id, "active", ("paused",), "intent.resume", "intent.resumed")
|
|
257
|
+
|
|
258
|
+
case _: # pragma: no cover
|
|
259
|
+
raise TypeError(f"Unknown intent command: {command!r}")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# Query
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class IntentFilter(BaseModel):
|
|
268
|
+
owner: str | None = None
|
|
269
|
+
status: IntentStatus | None = None
|
|
270
|
+
statuses: list[IntentStatus] | None = None
|
|
271
|
+
min_priority: float | None = None
|
|
272
|
+
has_memory_id: str | None = None
|
|
273
|
+
parent_id: str | None = None
|
|
274
|
+
is_root: bool | None = None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _coerce_intent_filter(f: IntentFilter | dict[str, Any] | None) -> IntentFilter | None:
|
|
278
|
+
if f is None or isinstance(f, IntentFilter):
|
|
279
|
+
return f
|
|
280
|
+
return IntentFilter.model_validate(f)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_intents(state: IntentState, filter: IntentFilter | dict[str, Any] | None = None) -> list[Intent]:
|
|
284
|
+
f = _coerce_intent_filter(filter)
|
|
285
|
+
if f is None:
|
|
286
|
+
return list(state.intents.values())
|
|
287
|
+
|
|
288
|
+
results: list[Intent] = []
|
|
289
|
+
for intent in state.intents.values():
|
|
290
|
+
if f.owner is not None and intent.owner != f.owner:
|
|
291
|
+
continue
|
|
292
|
+
if f.status is not None and intent.status != f.status:
|
|
293
|
+
continue
|
|
294
|
+
if f.statuses is not None and intent.status not in f.statuses:
|
|
295
|
+
continue
|
|
296
|
+
if f.min_priority is not None and intent.priority < f.min_priority:
|
|
297
|
+
continue
|
|
298
|
+
if f.has_memory_id is not None:
|
|
299
|
+
if not intent.root_memory_ids or f.has_memory_id not in intent.root_memory_ids:
|
|
300
|
+
continue
|
|
301
|
+
if f.parent_id is not None and intent.parent_id != f.parent_id:
|
|
302
|
+
continue
|
|
303
|
+
if f.is_root is not None:
|
|
304
|
+
has_parent = intent.parent_id is not None
|
|
305
|
+
if f.is_root and has_parent:
|
|
306
|
+
continue
|
|
307
|
+
if not f.is_root and not has_parent:
|
|
308
|
+
continue
|
|
309
|
+
results.append(intent)
|
|
310
|
+
return results
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_intent_by_id(state: IntentState, id: str) -> Intent | None:
|
|
314
|
+
return state.intents.get(id)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def get_child_intents(state: IntentState, parent_id: str) -> list[Intent]:
|
|
318
|
+
return get_intents(state, {"parent_id": parent_id})
|
memex/models.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Pydantic models for the memory graph.
|
|
2
|
+
|
|
3
|
+
Design notes (see PLAN.md):
|
|
4
|
+
- Entities (`MemoryItem`, `Edge`) are ``frozen`` — immutability is enforced by
|
|
5
|
+
the type system; a "merge" produces a new instance via ``model_copy(update=)``.
|
|
6
|
+
- Numeric score bounds use ``Field(ge=0, le=1)`` so construction validates them
|
|
7
|
+
(D2: validation is always on; use ``Model.model_construct`` to bypass).
|
|
8
|
+
- Open string unions (kind / source_kind / edge kind / namespace) are plain
|
|
9
|
+
``str``; the ``Known*`` ``Literal`` aliases document the canonical values.
|
|
10
|
+
- ``from`` / ``not`` / ``or`` are Python keywords, so the corresponding fields
|
|
11
|
+
are ``from_`` / ``not_`` / ``or_`` with JSON aliases. ``populate_by_name``
|
|
12
|
+
lets you pass either the field name or the alias.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Generic, Literal, TypeVar
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Open-string type families (known values documented via Literal aliases)
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
KnownMemoryKind = Literal[
|
|
26
|
+
"observation",
|
|
27
|
+
"assertion",
|
|
28
|
+
"assumption",
|
|
29
|
+
"hypothesis",
|
|
30
|
+
"derivation",
|
|
31
|
+
"simulation",
|
|
32
|
+
"policy",
|
|
33
|
+
"trait",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
KnownSourceKind = Literal[
|
|
37
|
+
"user_explicit",
|
|
38
|
+
"observed",
|
|
39
|
+
"derived_deterministic",
|
|
40
|
+
"agent_inferred",
|
|
41
|
+
"simulated",
|
|
42
|
+
"imported",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
KnownEdgeKind = Literal[
|
|
46
|
+
"DERIVED_FROM",
|
|
47
|
+
"CONTRADICTS",
|
|
48
|
+
"SUPPORTS",
|
|
49
|
+
"ABOUT",
|
|
50
|
+
"SUPERSEDES",
|
|
51
|
+
"ALIAS",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
KnownNamespace = Literal[
|
|
55
|
+
"memory",
|
|
56
|
+
"task",
|
|
57
|
+
"agent",
|
|
58
|
+
"tool",
|
|
59
|
+
"net",
|
|
60
|
+
"app",
|
|
61
|
+
"chat",
|
|
62
|
+
"system",
|
|
63
|
+
"debug",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
LifecycleEventType = Literal[
|
|
67
|
+
"memory.created",
|
|
68
|
+
"memory.updated",
|
|
69
|
+
"memory.retracted",
|
|
70
|
+
"edge.created",
|
|
71
|
+
"edge.updated",
|
|
72
|
+
"edge.retracted",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
SortField = Literal["authority", "conviction", "importance", "recency"]
|
|
76
|
+
DecayInterval = Literal["hour", "day", "week"]
|
|
77
|
+
DecayType = Literal["exponential", "linear", "step"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# MemoryItem (core node)
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MemoryItem(BaseModel):
|
|
86
|
+
model_config = ConfigDict(frozen=True)
|
|
87
|
+
|
|
88
|
+
id: str
|
|
89
|
+
scope: str
|
|
90
|
+
kind: str
|
|
91
|
+
content: dict[str, Any]
|
|
92
|
+
|
|
93
|
+
author: str
|
|
94
|
+
source_kind: str
|
|
95
|
+
parents: list[str] | None = None
|
|
96
|
+
|
|
97
|
+
authority: float = Field(ge=0, le=1)
|
|
98
|
+
conviction: float | None = Field(default=None, ge=0, le=1)
|
|
99
|
+
importance: float | None = Field(default=None, ge=0, le=1)
|
|
100
|
+
|
|
101
|
+
created_at: int | None = None
|
|
102
|
+
|
|
103
|
+
intent_id: str | None = None
|
|
104
|
+
task_id: str | None = None
|
|
105
|
+
|
|
106
|
+
meta: dict[str, Any] | None = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# Edge
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Edge(BaseModel):
|
|
115
|
+
model_config = ConfigDict(frozen=True, populate_by_name=True)
|
|
116
|
+
|
|
117
|
+
edge_id: str
|
|
118
|
+
from_: str = Field(alias="from")
|
|
119
|
+
to: str
|
|
120
|
+
kind: str
|
|
121
|
+
|
|
122
|
+
weight: float | None = Field(default=None, ge=0, le=1)
|
|
123
|
+
|
|
124
|
+
author: str
|
|
125
|
+
source_kind: str
|
|
126
|
+
authority: float = Field(ge=0, le=1)
|
|
127
|
+
active: bool
|
|
128
|
+
|
|
129
|
+
meta: dict[str, Any] | None = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# Event envelope (generic over its payload)
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
T = TypeVar("T")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class EventEnvelope(BaseModel, Generic[T]):
|
|
140
|
+
id: str
|
|
141
|
+
namespace: str
|
|
142
|
+
type: str
|
|
143
|
+
ts: str
|
|
144
|
+
trace_id: str | None = None
|
|
145
|
+
payload: T
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Scoring / decay configuration
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class DecayConfig(BaseModel):
|
|
154
|
+
rate: float = Field(ge=0, le=1)
|
|
155
|
+
interval: str # DecayInterval; runtime-checked in query.compute_decay_multiplier
|
|
156
|
+
type: str # DecayType; runtime-checked in query.compute_decay_multiplier
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ScoreWeights(BaseModel):
|
|
160
|
+
# Weights are multipliers, intentionally unbounded.
|
|
161
|
+
authority: float | None = None
|
|
162
|
+
conviction: float | None = None
|
|
163
|
+
importance: float | None = None
|
|
164
|
+
decay: DecayConfig | None = None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ScoredItem(BaseModel):
|
|
168
|
+
# Not frozen: surface_contradictions annotates `contradicted_by` in place.
|
|
169
|
+
item: MemoryItem
|
|
170
|
+
score: float
|
|
171
|
+
contradicted_by: list[MemoryItem] | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Filters
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class Range(BaseModel):
|
|
180
|
+
min: float | None = None
|
|
181
|
+
max: float | None = None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ScoreRanges(BaseModel):
|
|
185
|
+
authority: Range | None = None
|
|
186
|
+
conviction: Range | None = None
|
|
187
|
+
importance: Range | None = None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ParentsFilter(BaseModel):
|
|
191
|
+
includes: str | None = None
|
|
192
|
+
includes_any: list[str] | None = None
|
|
193
|
+
includes_all: list[str] | None = None
|
|
194
|
+
count: Range | None = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class DecayFilter(BaseModel):
|
|
198
|
+
config: DecayConfig
|
|
199
|
+
min: float = Field(ge=0, le=1)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class CreatedFilter(BaseModel):
|
|
203
|
+
before: int | None = None
|
|
204
|
+
after: int | None = None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class MemoryFilter(BaseModel):
|
|
208
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
209
|
+
|
|
210
|
+
ids: list[str] | None = None
|
|
211
|
+
scope: str | None = None
|
|
212
|
+
scope_prefix: str | None = None
|
|
213
|
+
author: str | None = None
|
|
214
|
+
kind: str | None = None
|
|
215
|
+
source_kind: str | None = None
|
|
216
|
+
|
|
217
|
+
range: ScoreRanges | None = None
|
|
218
|
+
|
|
219
|
+
intent_id: str | None = None
|
|
220
|
+
intent_ids: list[str] | None = None
|
|
221
|
+
task_id: str | None = None
|
|
222
|
+
task_ids: list[str] | None = None
|
|
223
|
+
|
|
224
|
+
has_parent: str | None = None
|
|
225
|
+
is_root: bool | None = None
|
|
226
|
+
parents: ParentsFilter | None = None
|
|
227
|
+
|
|
228
|
+
decay: DecayFilter | None = None
|
|
229
|
+
created: CreatedFilter | None = None
|
|
230
|
+
|
|
231
|
+
not_: MemoryFilter | None = Field(default=None, alias="not")
|
|
232
|
+
meta: dict[str, Any] | None = None
|
|
233
|
+
meta_has: list[str] | None = None
|
|
234
|
+
or_: list[MemoryFilter] | None = Field(default=None, alias="or")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class SortOption(BaseModel):
|
|
238
|
+
field: str # SortField; runtime-checked in query.get_sort_value
|
|
239
|
+
order: Literal["asc", "desc"]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class QueryOptions(BaseModel):
|
|
243
|
+
sort: SortOption | list[SortOption] | None = None
|
|
244
|
+
limit: int | None = Field(default=None, ge=0)
|
|
245
|
+
offset: int | None = Field(default=None, ge=0)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class EdgeFilter(BaseModel):
|
|
249
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
250
|
+
|
|
251
|
+
from_: str | None = Field(default=None, alias="from")
|
|
252
|
+
to: str | None = None
|
|
253
|
+
kind: str | None = None
|
|
254
|
+
min_weight: float | None = None
|
|
255
|
+
active_only: bool | None = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# Memory lifecycle event (emitted by the reducer)
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class MemoryLifecycleEvent(BaseModel):
|
|
264
|
+
namespace: Literal["memory"] = "memory"
|
|
265
|
+
type: LifecycleEventType
|
|
266
|
+
item: MemoryItem | None = None
|
|
267
|
+
edge: Edge | None = None
|
|
268
|
+
cause_type: str | None = None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
MemoryFilter.model_rebuild()
|