rememberstack 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.
Files changed (186) hide show
  1. rememberstack/__init__.py +9 -0
  2. rememberstack/adapters/__init__.py +42 -0
  3. rememberstack/adapters/codex_writer.py +221 -0
  4. rememberstack/adapters/markitdown_converter.py +42 -0
  5. rememberstack/adapters/openrouter.py +136 -0
  6. rememberstack/adapters/selfhost/__init__.py +54 -0
  7. rememberstack/adapters/selfhost/forget.py +66 -0
  8. rememberstack/adapters/selfhost/git.py +374 -0
  9. rememberstack/adapters/selfhost/lance.py +328 -0
  10. rememberstack/adapters/selfhost/minio.py +279 -0
  11. rememberstack/adapters/selfhost/mounts.py +249 -0
  12. rememberstack/adapters/selfhost/object_store.py +130 -0
  13. rememberstack/adapters/selfhost/projection.py +80 -0
  14. rememberstack/adapters/selfhost/queue.py +137 -0
  15. rememberstack/adapters/selfhost/telemetry.py +45 -0
  16. rememberstack/adapters/selfhost/watcher.py +70 -0
  17. rememberstack/adapters/testing/__init__.py +15 -0
  18. rememberstack/adapters/testing/cost_meter.py +13 -0
  19. rememberstack/adapters/testing/model_provider.py +83 -0
  20. rememberstack/adapters/testing/queue.py +43 -0
  21. rememberstack/adapters/testing/telemetry.py +22 -0
  22. rememberstack/client.py +19 -0
  23. rememberstack/core/__init__.py +127 -0
  24. rememberstack/core/blockizer.py +189 -0
  25. rememberstack/core/chunker.py +216 -0
  26. rememberstack/core/consumption_skill.py +275 -0
  27. rememberstack/core/conversion.py +76 -0
  28. rememberstack/core/core_manifest.py +598 -0
  29. rememberstack/core/extension_packs.py +124 -0
  30. rememberstack/core/forget.py +17 -0
  31. rememberstack/core/knowledge_authored.py +276 -0
  32. rememberstack/core/knowledge_compile.py +215 -0
  33. rememberstack/core/knowledge_fact_sheet.py +210 -0
  34. rememberstack/core/knowledge_hashing.py +68 -0
  35. rememberstack/core/knowledge_planner.py +64 -0
  36. rememberstack/core/knowledge_writer.py +175 -0
  37. rememberstack/core/ranking.py +200 -0
  38. rememberstack/core/recipe_linter.py +149 -0
  39. rememberstack/core/section_snap.py +209 -0
  40. rememberstack/core/storage_routing.py +27 -0
  41. rememberstack/eval/__init__.py +53 -0
  42. rememberstack/eval/consumption.py +141 -0
  43. rememberstack/eval/contradiction.py +184 -0
  44. rememberstack/eval/harness.py +136 -0
  45. rememberstack/eval/lifecycle.py +400 -0
  46. rememberstack/eval/operational_scale.py +49 -0
  47. rememberstack/eval/resolution.py +255 -0
  48. rememberstack/eval/retrieval_spikes.py +50 -0
  49. rememberstack/eval/skeleton.py +231 -0
  50. rememberstack/llm/__init__.py +1 -0
  51. rememberstack/model/__init__.py +589 -0
  52. rememberstack/model/adjudication.py +100 -0
  53. rememberstack/model/auth.py +27 -0
  54. rememberstack/model/blocks.py +30 -0
  55. rememberstack/model/chunks.py +190 -0
  56. rememberstack/model/claims.py +162 -0
  57. rememberstack/model/client.py +98 -0
  58. rememberstack/model/clustering.py +54 -0
  59. rememberstack/model/component_version.py +124 -0
  60. rememberstack/model/consumption.py +88 -0
  61. rememberstack/model/conversion.py +31 -0
  62. rememberstack/model/deployment.py +53 -0
  63. rememberstack/model/documents.py +168 -0
  64. rememberstack/model/envelope.py +513 -0
  65. rememberstack/model/evaluation.py +72 -0
  66. rememberstack/model/forget.py +143 -0
  67. rememberstack/model/git.py +13 -0
  68. rememberstack/model/knowledge.py +840 -0
  69. rememberstack/model/knowledge_authored.py +325 -0
  70. rememberstack/model/knowledge_planner.py +431 -0
  71. rememberstack/model/lifecycle.py +42 -0
  72. rememberstack/model/model_provider.py +78 -0
  73. rememberstack/model/mounts.py +24 -0
  74. rememberstack/model/object_store.py +21 -0
  75. rememberstack/model/operational_scale.py +59 -0
  76. rememberstack/model/operations.py +153 -0
  77. rememberstack/model/processing.py +228 -0
  78. rememberstack/model/queue.py +73 -0
  79. rememberstack/model/recipes.py +83 -0
  80. rememberstack/model/relations.py +79 -0
  81. rememberstack/model/resolution.py +83 -0
  82. rememberstack/model/retrieval_spikes.py +62 -0
  83. rememberstack/model/sections.py +120 -0
  84. rememberstack/model/telemetry.py +30 -0
  85. rememberstack/ports/__init__.py +29 -0
  86. rememberstack/ports/auth.py +16 -0
  87. rememberstack/ports/connector.py +23 -0
  88. rememberstack/ports/cost_meter.py +17 -0
  89. rememberstack/ports/forget.py +20 -0
  90. rememberstack/ports/git.py +20 -0
  91. rememberstack/ports/model_provider.py +28 -0
  92. rememberstack/ports/mounts.py +16 -0
  93. rememberstack/ports/object_store.py +27 -0
  94. rememberstack/ports/p1_index.py +92 -0
  95. rememberstack/ports/purge.py +93 -0
  96. rememberstack/ports/queue.py +23 -0
  97. rememberstack/ports/telemetry.py +21 -0
  98. rememberstack/profiles/__init__.py +22 -0
  99. rememberstack/profiles/selfhost.py +324 -0
  100. rememberstack/profiles/selfhost_forget.py +158 -0
  101. rememberstack/profiles/selfhost_operations.py +95 -0
  102. rememberstack/py.typed +1 -0
  103. rememberstack/spine/__init__.py +93 -0
  104. rememberstack/spine/admission.py +26 -0
  105. rememberstack/spine/backfill.py +168 -0
  106. rememberstack/spine/catalog_contract.py +742 -0
  107. rememberstack/spine/chunk_catalog.py +237 -0
  108. rememberstack/spine/claim_catalog.py +298 -0
  109. rememberstack/spine/clustering.py +740 -0
  110. rememberstack/spine/component_versions.py +208 -0
  111. rememberstack/spine/consumption.py +81 -0
  112. rememberstack/spine/deployment_bootstrap.py +445 -0
  113. rememberstack/spine/document_catalog.py +621 -0
  114. rememberstack/spine/entity_registry.py +205 -0
  115. rememberstack/spine/extension_packs.py +220 -0
  116. rememberstack/spine/fact_catalog.py +571 -0
  117. rememberstack/spine/forget.py +1753 -0
  118. rememberstack/spine/knowledge.py +5467 -0
  119. rememberstack/spine/lifecycle.py +1071 -0
  120. rememberstack/spine/migrations/__init__.py +1 -0
  121. rememberstack/spine/migrations/_helpers.py +153 -0
  122. rememberstack/spine/migrations/env.py +58 -0
  123. rememberstack/spine/migrations/script.py.mako +27 -0
  124. rememberstack/spine/migrations/versions/__init__.py +1 -0
  125. rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
  126. rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
  127. rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
  128. rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
  129. rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
  130. rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
  131. rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
  132. rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
  133. rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
  134. rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
  135. rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
  136. rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
  137. rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
  138. rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
  139. rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
  140. rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
  141. rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
  142. rememberstack/spine/observation_adjudication.py +778 -0
  143. rememberstack/spine/operations.py +298 -0
  144. rememberstack/spine/projection.py +662 -0
  145. rememberstack/spine/recipes.py +276 -0
  146. rememberstack/spine/resolver.py +763 -0
  147. rememberstack/spine/review.py +650 -0
  148. rememberstack/spine/settings.py +22 -0
  149. rememberstack/spine/supersession.py +510 -0
  150. rememberstack/spine/sync.py +128 -0
  151. rememberstack/spine/work_ledger.py +816 -0
  152. rememberstack/surfaces/__init__.py +110 -0
  153. rememberstack/surfaces/cli.py +447 -0
  154. rememberstack/surfaces/consumption_skill.py +87 -0
  155. rememberstack/surfaces/graph_queries.py +698 -0
  156. rememberstack/surfaces/http_api.py +377 -0
  157. rememberstack/surfaces/mcp.py +67 -0
  158. rememberstack/surfaces/query_engine.py +1591 -0
  159. rememberstack/surfaces/recipe_executor.py +185 -0
  160. rememberstack/surfaces/recipe_surface.py +219 -0
  161. rememberstack/surfaces/remote_mcp.py +133 -0
  162. rememberstack/surfaces/sdk.py +324 -0
  163. rememberstack/workers/__init__.py +155 -0
  164. rememberstack/workers/base.py +312 -0
  165. rememberstack/workers/e0.py +577 -0
  166. rememberstack/workers/e1.py +425 -0
  167. rememberstack/workers/e2.py +525 -0
  168. rememberstack/workers/e3.py +434 -0
  169. rememberstack/workers/forget.py +299 -0
  170. rememberstack/workers/knowledge_authored.py +146 -0
  171. rememberstack/workers/knowledge_driver.py +735 -0
  172. rememberstack/workers/knowledge_fact_sheet.py +123 -0
  173. rememberstack/workers/knowledge_planner.py +325 -0
  174. rememberstack/workers/knowledge_writer.py +393 -0
  175. rememberstack/workers/operations.py +42 -0
  176. rememberstack/workers/p1.py +234 -0
  177. rememberstack/workers/p2.py +513 -0
  178. rememberstack/workers/p2_analytics.py +276 -0
  179. rememberstack/workers/p3.py +673 -0
  180. rememberstack/workers/reconcile.py +485 -0
  181. rememberstack/workers/sync.py +168 -0
  182. rememberstack-0.1.0.dist-info/METADATA +213 -0
  183. rememberstack-0.1.0.dist-info/RECORD +186 -0
  184. rememberstack-0.1.0.dist-info/WHEEL +4 -0
  185. rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
  186. rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,276 @@
1
+ """Graph analytics on the freshly built snapshot (p2 §7, D11 → D72).
2
+
3
+ PageRank (salience prior), k-core (hub-ness), weakly-connected components
4
+ (ER-health signal), and **Louvain community detection** all run natively in
5
+ the engine on the snapshot the rebuild just validated — Louvain included,
6
+ which D72 corrected after verifying it live on the deployed build (the
7
+ vendored survey behind D11 said it was absent, so the original plan carried
8
+ an external igraph pass; the simpler mechanism removes it).
9
+
10
+ Results are written **back to Postgres** and never reprojected into the
11
+ graph's node tables: analytics are graph-DERIVED, so loading them into the
12
+ projection would be circular (D6). Community *labels* — the K1 navigation
13
+ aid — are an optional batched micro-LLM call over each community's top
14
+ members by PageRank; without a provider the communities are unlabeled,
15
+ which costs navigation polish and nothing load-bearing.
16
+ """
17
+
18
+ from typing import cast
19
+ from typing import Final
20
+ from uuid import NAMESPACE_URL
21
+ from uuid import UUID
22
+ from uuid import uuid5
23
+
24
+ import ladybug
25
+ from pydantic import BaseModel
26
+ from pydantic import ConfigDict
27
+ from pydantic import Field
28
+ from pydantic_settings import BaseSettings
29
+ from pydantic_settings import SettingsConfigDict
30
+
31
+ from rememberstack.model import ModelRequest
32
+ from rememberstack.ports.model_provider import ModelProviderPort
33
+ from rememberstack.spine.projection import ProjectionCatalog
34
+
35
+ COMMUNITY_DETECTOR_VERSION: Final = "p2-communities-2026.07:louvain-native"
36
+ """The analytics pass's component version (D12); the algorithm is part of
37
+ the identity because a detector swap changes every assignment."""
38
+
39
+ _PROJECTION: Final = "analytics_graph"
40
+ _LABEL_MEMBERS: Final = 8
41
+ """How many top-PageRank members the labeling prompt sees per community."""
42
+
43
+ _LABEL_PROMPT: Final = (
44
+ "Each numbered line lists the most central members of one cluster in a"
45
+ " knowledge graph. Give EVERY cluster a short topic label (2-5 words) an"
46
+ " agent could navigate by, returning the cluster's number with its"
47
+ " label.\n\n{clusters}"
48
+ )
49
+
50
+
51
+ class CommunityLabelItem(BaseModel):
52
+ """One cluster's label, keyed by its position in the batched prompt."""
53
+
54
+ model_config = ConfigDict(frozen=True, extra="ignore")
55
+
56
+ index: int = Field(default=-1)
57
+ label: str = Field(default="")
58
+
59
+
60
+ class CommunityLabels(BaseModel):
61
+ """The batched labeling call's structured output (p2 §7)."""
62
+
63
+ model_config = ConfigDict(frozen=True, extra="ignore")
64
+
65
+ labels: tuple[CommunityLabelItem, ...] = ()
66
+
67
+
68
+ class AnalyticsSettings(BaseSettings):
69
+ """The analytics pass's knobs (D70 port config; D22 numbers)."""
70
+
71
+ model_config = SettingsConfigDict(env_prefix="REMEMBERSTACK_P2_ANALYTICS_")
72
+
73
+ label_model: str = Field(default="openai/gpt-5.6-luna")
74
+ min_community_size_to_label: int = Field(default=3, ge=1)
75
+
76
+
77
+ class GraphAnalyticsWorker:
78
+ """Compute snapshot analytics natively; write assignments to Postgres."""
79
+
80
+ def __init__(
81
+ self,
82
+ *,
83
+ catalog: ProjectionCatalog,
84
+ model_provider: ModelProviderPort | None = None,
85
+ settings: AnalyticsSettings | None = None,
86
+ ) -> None:
87
+ """Bind to the spine and (optionally) the labeling model seat."""
88
+ self._catalog = catalog
89
+ self._model_provider = model_provider
90
+ self._settings = settings or AnalyticsSettings()
91
+
92
+ def compute(
93
+ self, *, snapshot_id: UUID, connection: ladybug.Connection
94
+ ) -> tuple[tuple[dict[str, object], ...], tuple[dict[str, object], ...]]:
95
+ """Run every algorithm on the snapshot; return rows, write nothing.
96
+
97
+ Computing and writing are deliberately separate (Codex review): the
98
+ algorithms need the writer's live graph, but their rows must not
99
+ outlive a snapshot that later fails validation or upload — so the
100
+ caller persists them only after the snapshot publishes.
101
+ """
102
+ connection.execute("INSTALL algo")
103
+ connection.execute("LOAD algo")
104
+ connection.execute(
105
+ # analytics measure CURRENT connectivity: the snapshot
106
+ # deliberately retains invalidated and expired edges for
107
+ # transaction-time as-of (D69), but a withdrawn fact must not
108
+ # inflate centrality or fuse two communities (Codex review).
109
+ # The engine's filtered projection applies this per edge.
110
+ f"CALL PROJECT_GRAPH('{_PROJECTION}', ['Entity'],"
111
+ " {'RELATES': 'r.invalidated_at IS NULL AND (r.valid_until IS NULL"
112
+ " OR r.valid_until > current_timestamp())'})"
113
+ )
114
+ pagerank = _algorithm_scores(connection, algorithm="PAGE_RANK")
115
+ k_core = _algorithm_scores(connection, algorithm="K_CORE_DECOMPOSITION")
116
+ components = _algorithm_scores(
117
+ connection, algorithm="WEAKLY_CONNECTED_COMPONENTS"
118
+ )
119
+ louvain = _algorithm_scores(connection, algorithm="LOUVAIN")
120
+ degrees = _degrees(connection)
121
+ names = _names(connection)
122
+
123
+ members: dict[object, list[UUID]] = {}
124
+ for entity_id, group in louvain.items():
125
+ members.setdefault(group, []).append(entity_id)
126
+ communities: list[dict[str, object]] = []
127
+ community_ids: dict[object, UUID] = {}
128
+ labels = self._labels(members=members, pagerank=pagerank, names=names)
129
+ for group, group_members in sorted(
130
+ members.items(), key=lambda item: str(item[0])
131
+ ):
132
+ # the id derives from the MEMBER SET, never the engine's group
133
+ # label: Louvain's numbering is order-dependent, so a re-run
134
+ # could otherwise reuse an id for a different community
135
+ # (Codex review). Same members ⇒ same id, always.
136
+ fingerprint = ",".join(sorted(str(member) for member in group_members))
137
+ community_id = uuid5(
138
+ NAMESPACE_URL, f"rememberstack:community:{snapshot_id}:{fingerprint}"
139
+ )
140
+ community_ids[group] = community_id
141
+ communities.append(
142
+ {
143
+ "community_id": community_id,
144
+ "label": labels.get(group),
145
+ "size": len(group_members),
146
+ "algorithm": "louvain",
147
+ }
148
+ )
149
+ metrics: tuple[dict[str, object], ...] = tuple(
150
+ {
151
+ "entity_id": entity_id,
152
+ "community_id": community_ids.get(louvain.get(entity_id)),
153
+ "pagerank": float(cast("float", pagerank.get(entity_id, 0.0))),
154
+ "degree": int(cast("int", degrees.get(entity_id, 0))),
155
+ "k_core": int(cast("int", k_core.get(entity_id, 0))),
156
+ "component_id": uuid5(
157
+ NAMESPACE_URL,
158
+ f"rememberstack:component:{snapshot_id}:{components.get(entity_id)}",
159
+ ),
160
+ }
161
+ for entity_id in names
162
+ )
163
+ return tuple(communities), metrics
164
+
165
+ def persist(
166
+ self,
167
+ *,
168
+ deployment_id: UUID,
169
+ snapshot_id: UUID,
170
+ communities: tuple[dict[str, object], ...],
171
+ metrics: tuple[dict[str, object], ...],
172
+ ) -> dict[str, int]:
173
+ """Write a PUBLISHED snapshot's analytics back to Postgres (D6)."""
174
+ self._catalog.record_graph_analytics(
175
+ deployment_id=deployment_id,
176
+ snapshot_id=snapshot_id,
177
+ communities=communities,
178
+ metrics=metrics,
179
+ detector_version=COMMUNITY_DETECTOR_VERSION,
180
+ label_model=(
181
+ self._settings.label_model if self._model_provider is not None else None
182
+ ),
183
+ )
184
+ return {"communities": len(communities), "entities": len(metrics)}
185
+
186
+ def _labels(
187
+ self,
188
+ *,
189
+ members: dict[object, list[UUID]],
190
+ pagerank: dict[UUID, object],
191
+ names: dict[UUID, str],
192
+ ) -> dict[object, str]:
193
+ """Short navigation labels for every eligible community — ONE call.
194
+
195
+ Batched by contract (p2 §7): community count must not multiply
196
+ rebuild latency, and thousands of small clusters must not become
197
+ thousands of sequential calls (Codex review). Skipped entirely
198
+ without a model seat; a failed call yields no labels rather than a
199
+ failed rebuild — labels are navigation aids, nothing load-bearing
200
+ reads them.
201
+ """
202
+ if self._model_provider is None:
203
+ return {}
204
+ eligible = {
205
+ group: group_members
206
+ for group, group_members in members.items()
207
+ if len(group_members) >= self._settings.min_community_size_to_label
208
+ }
209
+ if not eligible:
210
+ return {}
211
+ ordered = sorted(eligible, key=str)
212
+ blocks: list[str] = []
213
+ for index, group in enumerate(ordered):
214
+ central = sorted(
215
+ eligible[group],
216
+ key=lambda entity: float(cast("float", pagerank.get(entity, 0.0))),
217
+ reverse=True,
218
+ )[:_LABEL_MEMBERS]
219
+ listing = ", ".join(names.get(entity, "") for entity in central)
220
+ blocks.append(f"{index}. {listing}")
221
+ try:
222
+ response = self._model_provider.generate(
223
+ request=ModelRequest(
224
+ model=self._settings.label_model,
225
+ prompt=_LABEL_PROMPT.format(clusters="\n".join(blocks)),
226
+ ),
227
+ response_type=CommunityLabels,
228
+ )
229
+ except Exception: # noqa: BLE001 — a label never fails the analytics
230
+ return {}
231
+ return {
232
+ ordered[item.index]: item.label
233
+ for item in response.output.labels
234
+ if 0 <= item.index < len(ordered) and item.label
235
+ }
236
+
237
+
238
+ def _algorithm_scores(
239
+ connection: ladybug.Connection, *, algorithm: str
240
+ ) -> dict[UUID, object]:
241
+ """One algorithm's per-entity output, keyed by entity id."""
242
+ result = connection.execute(f"CALL {algorithm}('{_PROJECTION}') RETURN *")
243
+ assert isinstance(result, ladybug.QueryResult)
244
+ scores: dict[UUID, object] = {}
245
+ while result.has_next():
246
+ row = cast("list[object]", result.get_next())
247
+ node = cast("dict[str, object]", row[0])
248
+ scores[cast("UUID", node["id"])] = row[1]
249
+ return scores
250
+
251
+
252
+ def _degrees(connection: ladybug.Connection) -> dict[UUID, int]:
253
+ """Relation degree per entity (undirected: the blast-radius input)."""
254
+ result = connection.execute(
255
+ "MATCH (e:Entity) OPTIONAL MATCH (e)-[r:RELATES]-()"
256
+ " WHERE r.invalidated_at IS NULL"
257
+ " AND (r.valid_until IS NULL OR r.valid_until > current_timestamp())"
258
+ " RETURN e.id, count(r)"
259
+ )
260
+ assert isinstance(result, ladybug.QueryResult)
261
+ degrees: dict[UUID, int] = {}
262
+ while result.has_next():
263
+ row = cast("list[object]", result.get_next())
264
+ degrees[cast("UUID", row[0])] = cast("int", row[1])
265
+ return degrees
266
+
267
+
268
+ def _names(connection: ladybug.Connection) -> dict[UUID, str]:
269
+ """Every emitted entity's canonical name (the labeling input)."""
270
+ result = connection.execute("MATCH (e:Entity) RETURN e.id, e.name")
271
+ assert isinstance(result, ladybug.QueryResult)
272
+ names: dict[UUID, str] = {}
273
+ while result.has_next():
274
+ row = cast("list[object]", result.get_next())
275
+ names[cast("UUID", row[0])] = cast("str", row[1])
276
+ return names