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,312 @@
1
+ """The handler-registration model and the worker runner (WP-0.3, D12/D52/D67).
2
+
3
+ A stage handler transforms one claimed unit of work and names its chain
4
+ follow-ups; the runner is the single exception boundary (core value 6): a
5
+ handler failure is logged with its full traceback, recorded on the row, and
6
+ either retried with backoff or dead-lettered — it never disappears.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from datetime import UTC
11
+ import logging
12
+ import time
13
+ import traceback
14
+ from typing import Protocol
15
+ from typing import runtime_checkable
16
+ from uuid import UUID
17
+
18
+ from pydantic import BaseModel
19
+ from pydantic import ConfigDict
20
+
21
+ from rememberstack.model import BudgetParked
22
+ from rememberstack.model import ClaimedWork
23
+ from rememberstack.model import EnqueueWork
24
+ from rememberstack.model import HandlerAlreadyRegisteredError
25
+ from rememberstack.model import NonRetryableHandlerError
26
+ from rememberstack.model import PipelineStage
27
+ from rememberstack.model import ProcessingLane
28
+ from rememberstack.model import ProviderCallUsage
29
+ from rememberstack.model import QueueRoute
30
+ from rememberstack.model import RecordCall
31
+ from rememberstack.model import RunResultOutcome
32
+ from rememberstack.model import TelemetryAttribute
33
+ from rememberstack.model import TelemetryEvent
34
+ from rememberstack.model import UnknownStageHandlerError
35
+ from rememberstack.ports.cost_meter import CostMeterPort
36
+ from rememberstack.ports.queue import TaskQueuePort
37
+ from rememberstack.ports.telemetry import TelemetryPort
38
+ from rememberstack.spine.work_ledger import WorkLedger
39
+
40
+ _logger = logging.getLogger(__name__)
41
+
42
+
43
+ class HandlerOutcome(BaseModel):
44
+ """What a successful handler produced: the chain follow-ups to enqueue."""
45
+
46
+ model_config = ConfigDict(frozen=True, extra="forbid")
47
+
48
+ follow_up: tuple[EnqueueWork, ...] = ()
49
+
50
+
51
+ @runtime_checkable
52
+ class StageHandler(Protocol):
53
+ """One stage's transformation over one claimed unit of work."""
54
+
55
+ def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
56
+ """Process the claimed work and return its chain follow-ups.
57
+
58
+ Raise `NonRetryableHandlerError` for permanent failures (the work
59
+ dead-letters immediately); any other exception is a retryable failure.
60
+ """
61
+ ...
62
+
63
+
64
+ class _LedgerCostMeter:
65
+ """Bind provider accounting to one claimed processing attempt."""
66
+
67
+ def __init__(self, *, ledger: WorkLedger, processing_id: UUID) -> None:
68
+ """Bind every call record to the authoritative running row."""
69
+ self._ledger = ledger
70
+ self._processing_id = processing_id
71
+
72
+ def record(
73
+ self, *, call_key: str, tier: str | None, usage: ProviderCallUsage
74
+ ) -> None:
75
+ """Persist one provider-reported call through the spine attribution path."""
76
+ self._ledger.record_call(
77
+ call=RecordCall(
78
+ processing_id=self._processing_id,
79
+ call_key=call_key,
80
+ model_name=usage.model_name,
81
+ tier=tier,
82
+ tokens_in=usage.tokens_in,
83
+ tokens_out=usage.tokens_out,
84
+ cost_usd=usage.cost_usd,
85
+ latency_ms=usage.latency_ms,
86
+ )
87
+ )
88
+
89
+
90
+ class RunResult(BaseModel):
91
+ """What one runner pass did: which row it ran and how the attempt ended."""
92
+
93
+ model_config = ConfigDict(frozen=True, extra="forbid")
94
+
95
+ processing_id: UUID | None
96
+ outcome: RunResultOutcome
97
+
98
+
99
+ class HandlerRegistry:
100
+ """Exactly one handler per stage; registration conflicts are typed errors."""
101
+
102
+ def __init__(self) -> None:
103
+ """Start empty; stages register at composition time (profiles own wiring)."""
104
+ self._handlers: dict[PipelineStage, StageHandler] = {}
105
+
106
+ def register(self, *, stage: PipelineStage, handler: StageHandler) -> None:
107
+ """Bind a handler to a stage; a second binding for the stage is an error."""
108
+ if stage in self._handlers:
109
+ raise HandlerAlreadyRegisteredError(
110
+ f"stage {stage} already has a registered handler"
111
+ )
112
+ self._handlers[stage] = handler
113
+
114
+ def handler_for(self, *, stage: PipelineStage) -> StageHandler:
115
+ """Return the stage's handler; an unregistered stage is a typed error."""
116
+ handler = self._handlers.get(stage)
117
+ if handler is None:
118
+ raise UnknownStageHandlerError(f"no handler registered for stage {stage}")
119
+ return handler
120
+
121
+
122
+ class Worker:
123
+ """Claims due work on one route and runs its handler behind one error boundary."""
124
+
125
+ def __init__(
126
+ self,
127
+ *,
128
+ ledger: WorkLedger,
129
+ registry: HandlerRegistry,
130
+ queue: TaskQueuePort | None = None,
131
+ telemetry: TelemetryPort | None = None,
132
+ ) -> None:
133
+ """Bind the runner to its ledger, handler registry, and optional queue port.
134
+
135
+ When a queue port is provided, scheduled retries are re-announced
136
+ through it (packaging §3: retry paths call the port in both profiles).
137
+ """
138
+ self._ledger = ledger
139
+ self._registry = registry
140
+ self._queue = queue
141
+ self._telemetry = telemetry
142
+
143
+ def run_one(
144
+ self, *, deployment_id: UUID, stage: PipelineStage, lane: ProcessingLane | None
145
+ ) -> RunResult:
146
+ """Claim and run at most one due unit of work on the route.
147
+
148
+ This is the worker's single catch-to-log boundary (core value 6): the
149
+ full traceback is logged and recorded on the row; a
150
+ `NonRetryableHandlerError` dead-letters immediately, anything else
151
+ retries with backoff until the attempt limit dead-letters it.
152
+ """
153
+ started_ns = time.monotonic_ns()
154
+ handler = self._registry.handler_for(stage=stage) # before any claim:
155
+ # an unregistered stage must never strand a claimed row as running.
156
+ claimed = self._ledger.claim_one(
157
+ deployment_id=deployment_id, stage=stage, lane=lane
158
+ )
159
+ if claimed is None:
160
+ return RunResult(processing_id=None, outcome=RunResultOutcome.NO_WORK)
161
+ if isinstance(claimed, BudgetParked):
162
+ if self._queue is not None:
163
+ self._queue.announce(
164
+ processing_id=claimed.processing_id,
165
+ route_snapshot=QueueRoute(
166
+ deployment_id=deployment_id, stage=stage, lane=lane
167
+ ),
168
+ not_before_snapshot=claimed.resume_at,
169
+ )
170
+ self._export_event(
171
+ event=_worker_event(
172
+ deployment_id=deployment_id,
173
+ processing_id=claimed.processing_id,
174
+ stage=stage,
175
+ lane=lane,
176
+ attempt=None,
177
+ outcome=RunResultOutcome.BUDGET_PARKED,
178
+ started_ns=started_ns,
179
+ )
180
+ )
181
+ return RunResult(
182
+ processing_id=claimed.processing_id,
183
+ outcome=RunResultOutcome.BUDGET_PARKED,
184
+ )
185
+ meter = _LedgerCostMeter(
186
+ ledger=self._ledger, processing_id=claimed.processing_id
187
+ )
188
+ try:
189
+ outcome = handler.handle(work=claimed, meter=meter)
190
+ except NonRetryableHandlerError as exception:
191
+ _logger.exception(
192
+ "non-retryable failure in stage %s for %s",
193
+ claimed.stage,
194
+ claimed.processing_id,
195
+ )
196
+ self._ledger.fail(
197
+ processing_id=claimed.processing_id,
198
+ error=traceback.format_exc(),
199
+ retryable=False,
200
+ )
201
+ self._export_exception(
202
+ event=_worker_event(
203
+ deployment_id=claimed.deployment_id,
204
+ processing_id=claimed.processing_id,
205
+ stage=claimed.stage,
206
+ lane=claimed.lane,
207
+ attempt=claimed.attempt,
208
+ outcome=RunResultOutcome.DEAD_LETTERED,
209
+ started_ns=started_ns,
210
+ ),
211
+ exception=exception,
212
+ )
213
+ return RunResult(
214
+ processing_id=claimed.processing_id,
215
+ outcome=RunResultOutcome.DEAD_LETTERED,
216
+ )
217
+ except Exception as exception:
218
+ _logger.exception(
219
+ "retryable failure in stage %s for %s",
220
+ claimed.stage,
221
+ claimed.processing_id,
222
+ )
223
+ retry_at = self._ledger.fail(
224
+ processing_id=claimed.processing_id,
225
+ error=traceback.format_exc(),
226
+ retryable=True,
227
+ )
228
+ result_outcome = (
229
+ RunResultOutcome.RETRY_SCHEDULED
230
+ if retry_at is not None
231
+ else RunResultOutcome.DEAD_LETTERED
232
+ )
233
+ if retry_at is not None and self._queue is not None:
234
+ self._queue.announce(
235
+ processing_id=claimed.processing_id,
236
+ route_snapshot=QueueRoute(
237
+ deployment_id=deployment_id, stage=stage, lane=lane
238
+ ),
239
+ not_before_snapshot=retry_at,
240
+ )
241
+ self._export_exception(
242
+ event=_worker_event(
243
+ deployment_id=claimed.deployment_id,
244
+ processing_id=claimed.processing_id,
245
+ stage=claimed.stage,
246
+ lane=claimed.lane,
247
+ attempt=claimed.attempt,
248
+ outcome=result_outcome,
249
+ started_ns=started_ns,
250
+ ),
251
+ exception=exception,
252
+ )
253
+ return RunResult(
254
+ processing_id=claimed.processing_id, outcome=result_outcome
255
+ )
256
+ self._ledger.complete(
257
+ processing_id=claimed.processing_id, follow_up=outcome.follow_up
258
+ )
259
+ self._export_event(
260
+ event=_worker_event(
261
+ deployment_id=claimed.deployment_id,
262
+ processing_id=claimed.processing_id,
263
+ stage=claimed.stage,
264
+ lane=claimed.lane,
265
+ attempt=claimed.attempt,
266
+ outcome=RunResultOutcome.SUCCEEDED,
267
+ started_ns=started_ns,
268
+ )
269
+ )
270
+ return RunResult(
271
+ processing_id=claimed.processing_id, outcome=RunResultOutcome.SUCCEEDED
272
+ )
273
+
274
+ def _export_event(self, *, event: TelemetryEvent) -> None:
275
+ """Export one completed transition when telemetry is configured."""
276
+ if self._telemetry is not None:
277
+ self._telemetry.export_event(event=event)
278
+
279
+ def _export_exception(
280
+ self, *, event: TelemetryEvent, exception: BaseException
281
+ ) -> None:
282
+ """Export the original exception object without hiding exporter failure."""
283
+ if self._telemetry is not None:
284
+ self._telemetry.export_exception(event=event, exception=exception)
285
+
286
+
287
+ def _worker_event(
288
+ *,
289
+ deployment_id: UUID,
290
+ processing_id: UUID,
291
+ stage: PipelineStage,
292
+ lane: ProcessingLane | None,
293
+ attempt: int | None,
294
+ outcome: RunResultOutcome,
295
+ started_ns: int,
296
+ ) -> TelemetryEvent:
297
+ """Build the one stable provider-neutral worker event vocabulary."""
298
+ return TelemetryEvent(
299
+ name="worker.run",
300
+ occurred_at=datetime.now(UTC),
301
+ attributes=(
302
+ TelemetryAttribute(name="deployment_id", value=str(deployment_id)),
303
+ TelemetryAttribute(name="processing_id", value=str(processing_id)),
304
+ TelemetryAttribute(name="stage", value=stage.value),
305
+ TelemetryAttribute(name="lane", value=None if lane is None else lane.value),
306
+ TelemetryAttribute(name="attempt", value=attempt),
307
+ TelemetryAttribute(name="outcome", value=outcome.value),
308
+ TelemetryAttribute(
309
+ name="duration_ms", value=(time.monotonic_ns() - started_ns) / 1e6
310
+ ),
311
+ ),
312
+ )