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,123 @@
1
+ """Zero-LLM compiler for deterministic ``kind='fact_sheet'`` pages."""
2
+
3
+ from collections.abc import Callable
4
+ from datetime import datetime
5
+ from datetime import UTC
6
+ from typing import Final
7
+ from uuid import uuid4
8
+
9
+ from pydantic import TypeAdapter
10
+
11
+ from rememberstack.core import compose_knowledge_page
12
+ from rememberstack.core import knowledge_content_hash
13
+ from rememberstack.core import knowledge_inputs_hash
14
+ from rememberstack.core import knowledge_summary_hash
15
+ from rememberstack.core import render_knowledge_fact_sheet
16
+ from rememberstack.model import KnowledgeCompilationWrite
17
+ from rememberstack.model import KnowledgeCompileContext
18
+ from rememberstack.model import KnowledgePageCompileOutput
19
+ from rememberstack.model import KnowledgePageCompileRequest
20
+ from rememberstack.model import UTCDateTime
21
+ from rememberstack.spine.knowledge import KnowledgeControlPlane
22
+
23
+ KNOWLEDGE_FACT_SHEET_VERSION: Final = "k-fact-sheet-2026.07"
24
+ """Hash-visible deterministic renderer version."""
25
+
26
+ _UTC_ADAPTER = TypeAdapter(UTCDateTime)
27
+
28
+
29
+ def _utc_now() -> datetime:
30
+ """Return the current aware UTC timestamp for the machine footer."""
31
+ return datetime.now(tz=UTC)
32
+
33
+
34
+ class KnowledgeFactSheetCompileError(ValueError):
35
+ """A non-fact-sheet artifact reached the deterministic compiler."""
36
+
37
+
38
+ class KnowledgeFactSheetCompiler:
39
+ """Compile exact rule-selected facts without invoking a model provider."""
40
+
41
+ def __init__(
42
+ self,
43
+ *,
44
+ control_plane: KnowledgeControlPlane,
45
+ clock: Callable[[], datetime] = _utc_now,
46
+ ) -> None:
47
+ """Bind the renderer to the authoritative fact query and an injectable clock."""
48
+ self._control_plane = control_plane
49
+ self._clock = clock
50
+
51
+ def compile_page(
52
+ self, *, request: KnowledgePageCompileRequest
53
+ ) -> KnowledgePageCompileOutput:
54
+ """Return one complete fact-sheet-only page and honest compile metadata."""
55
+ artifact = request.artifact
56
+ if artifact.artifact_kind != "fact_sheet":
57
+ raise KnowledgeFactSheetCompileError(
58
+ "deterministic compiler requires artifact kind 'fact_sheet'"
59
+ )
60
+ child_summary_hashes = tuple(
61
+ knowledge_summary_hash(summary=summary)
62
+ for summary in request.child_summaries.values()
63
+ )
64
+ shared_model_summary_hash = (
65
+ None
66
+ if request.shared_model_summary is None
67
+ else knowledge_summary_hash(summary=request.shared_model_summary)
68
+ )
69
+ snapshot = self._control_plane.fact_sheet_snapshot(
70
+ artifact_id=artifact.artifact_id,
71
+ context=KnowledgeCompileContext(
72
+ curation_hash=request.curation_hash,
73
+ shared_model_summary_hash=shared_model_summary_hash,
74
+ writer_version=KNOWLEDGE_FACT_SHEET_VERSION,
75
+ ),
76
+ child_summary_hashes=child_summary_hashes,
77
+ )
78
+ excluded_relations = {
79
+ target.relation_id
80
+ for target in request.exclusions
81
+ if target.relation_id is not None
82
+ }
83
+ render_snapshot = snapshot.model_copy(
84
+ update={
85
+ "facts": tuple(
86
+ fact
87
+ for fact in snapshot.facts
88
+ if fact.kind != "relation" or fact.fact_id not in excluded_relations
89
+ )
90
+ }
91
+ )
92
+ rendered = render_knowledge_fact_sheet(
93
+ snapshot=render_snapshot,
94
+ compiled_at=_UTC_ADAPTER.validate_python(self._clock()),
95
+ citation_count=0,
96
+ )
97
+ markdown = compose_knowledge_page(
98
+ prose_markdown=None, fact_sheet_markdown=rendered.markdown
99
+ )
100
+ candidate_count = len(snapshot.input_snapshot.facts) + len(
101
+ snapshot.input_snapshot.claims
102
+ )
103
+ summary = (
104
+ f"Fact sheet for {artifact.git_path}: "
105
+ f"{rendered.current_relation_count} current relations, "
106
+ f"{rendered.observation_count} observations, and "
107
+ f"{rendered.contradiction_group_count} open contradiction groups."
108
+ )
109
+ return KnowledgePageCompileOutput(
110
+ compilation=KnowledgeCompilationWrite(
111
+ compilation_id=uuid4(),
112
+ deployment_id=artifact.deployment_id,
113
+ artifact_id=artifact.artifact_id,
114
+ inputs_hash=knowledge_inputs_hash(snapshot=snapshot.input_snapshot),
115
+ candidate_count=candidate_count,
116
+ uncited_count=candidate_count,
117
+ citations=(),
118
+ writer_version=KNOWLEDGE_FACT_SHEET_VERSION,
119
+ page_summary=summary,
120
+ content_hash=knowledge_content_hash(markdown=markdown),
121
+ ),
122
+ markdown=markdown,
123
+ )
@@ -0,0 +1,325 @@
1
+ """Transcripted stock-harness planner and independent reflection worker."""
2
+
3
+ from collections.abc import Callable
4
+ from datetime import datetime
5
+ from datetime import UTC
6
+ from decimal import Decimal
7
+ import json
8
+ from pathlib import PurePosixPath
9
+ import traceback
10
+ from typing import Final
11
+ from typing import Protocol
12
+ from uuid import UUID
13
+ from uuid import uuid4
14
+
15
+ from pydantic import Field
16
+ from pydantic import field_validator
17
+ from pydantic import TypeAdapter
18
+ from pydantic_settings import BaseSettings
19
+ from pydantic_settings import SettingsConfigDict
20
+
21
+ from rememberstack.core import knowledge_planning_input_hash
22
+ from rememberstack.core import knowledge_summary_hash
23
+ from rememberstack.core import primary_knowledge_plan_trigger
24
+ from rememberstack.model import KnowledgeAgentSessionResult
25
+ from rememberstack.model import KnowledgePlanDecisionResult
26
+ from rememberstack.model import KnowledgePlannerSessionRequest
27
+ from rememberstack.model import KnowledgePlanningSnapshot
28
+ from rememberstack.model import KnowledgePlanProposal
29
+ from rememberstack.model import KnowledgePlanRunKind
30
+ from rememberstack.model import KnowledgePlanRunStatus
31
+ from rememberstack.model import KnowledgePlanRunWrite
32
+ from rememberstack.model import ObjectKey
33
+ from rememberstack.ports import MountPublisherPort
34
+ from rememberstack.ports import ObjectStorePort
35
+ from rememberstack.spine.knowledge import KnowledgeControlPlane
36
+
37
+ KNOWLEDGE_PLANNER_VERSION: Final = "k-planner-2026.07"
38
+
39
+ _PLANNER_PROMPT: Final = """You maintain Plane-K structure, never page content.
40
+ Your working directory is the declared output/ surface. Read ../INSTRUCTIONS.md,
41
+ ../input/planning_snapshot.json, ../input/decision_schema.json, and the read-only memory
42
+ mount locations in ../context/memory_mounts.json. Do not use the internet, initialize git,
43
+ commit, edit a page body, or write outside the declared output.
44
+
45
+ Write exactly decisions.json in the current directory (archived as output/decisions.json):
46
+ one JSON array of zero or more schema-valid structural proposals. Use only create_page,
47
+ split_page, merge_pages, move_page, retire_page, adjust_rule, or convert_kind. Every created
48
+ page must be compiled and have a complete mechanical rule set. Base decisions only on the
49
+ supplied orphan, overflow, community, and writer-suggestion inputs. The deterministic driver
50
+ computes impact, applies the automatic band, and owns every file and database mutation. Your
51
+ output proposes; it never takes action.
52
+ """
53
+
54
+ _REFLECTION_PROMPT: Final = """You are the independent Plane-K reflection seat.
55
+ Your working directory is the declared output/ surface. Read ../INSTRUCTIONS.md,
56
+ ../input/planning_snapshot.json, ../input/decision_schema.json, and the read-only memory
57
+ mount locations in ../context/memory_mounts.json. Inspect the compiled tree and health
58
+ metrics for orphan volume, page sizes, uncited rates, and navigation problems. Do not use the
59
+ internet, initialize git, commit, edit any page, or write outside the declared output.
60
+
61
+ Write exactly decisions.json in the current directory (archived as output/decisions.json):
62
+ one JSON array of zero or more schema-valid structural proposals. Use only create_page,
63
+ split_page, merge_pages, move_page, retire_page, adjust_rule, or convert_kind. Every proposal
64
+ is review-band by contract. Your output proposes structural changes only; the deterministic
65
+ driver owns every mutation and commit.
66
+ """
67
+
68
+ _PROPOSALS_ADAPTER = TypeAdapter(tuple[KnowledgePlanProposal, ...])
69
+
70
+
71
+ def _utc_now() -> datetime:
72
+ """Return the current aware UTC time for injectable worker bookkeeping."""
73
+ return datetime.now(tz=UTC)
74
+
75
+
76
+ class KnowledgePlannerSettings(BaseSettings):
77
+ """Settings-owned model split, timeout, band, and transcript namespace."""
78
+
79
+ model_config = SettingsConfigDict(env_prefix="REMEMBERSTACK_K_PLANNER_")
80
+
81
+ planner_model: str = Field(min_length=1)
82
+ planner_model_family: str = Field(min_length=1)
83
+ reflection_model: str = Field(min_length=1)
84
+ reflection_model_family: str = Field(min_length=1)
85
+ timeout_seconds: int = Field(gt=0)
86
+ auto_apply_max_expected_impact: Decimal = Field(ge=Decimal("0"))
87
+ transcript_prefix: str = Field(min_length=1)
88
+
89
+ @field_validator("reflection_model_family")
90
+ @classmethod
91
+ def require_independent_reflection_family(cls, value: str, info: object) -> str:
92
+ """Bind D53 producer/checker independence in deploy-time settings."""
93
+ data = getattr(info, "data", {})
94
+ planner_family = data.get("planner_model_family")
95
+ if planner_family is not None and value.casefold() == planner_family.casefold():
96
+ raise ValueError(
97
+ "reflection must use a different model family than planner"
98
+ )
99
+ return value
100
+
101
+ @field_validator("transcript_prefix")
102
+ @classmethod
103
+ def require_safe_transcript_prefix(cls, value: str) -> str:
104
+ """Keep immutable transcripts under one normalized relative prefix."""
105
+ path = PurePosixPath(value)
106
+ if path.is_absolute() or ".." in path.parts or str(path) != value:
107
+ raise ValueError("transcript_prefix must be a normalized relative path")
108
+ return value.rstrip("/")
109
+
110
+
111
+ class KnowledgePlannerError(RuntimeError):
112
+ """A planner or reflection session failed its declared-output contract."""
113
+
114
+
115
+ class KnowledgePlannerSession(Protocol):
116
+ """The generic stock-harness seam used by both structural seats."""
117
+
118
+ def run_session(
119
+ self, *, request: KnowledgePlannerSessionRequest
120
+ ) -> KnowledgeAgentSessionResult:
121
+ """Return raw declared files and the complete process transcript."""
122
+ ...
123
+
124
+
125
+ class KnowledgePlannerWorker:
126
+ """Run one decisions-only planner or independent reflection session."""
127
+
128
+ def __init__(
129
+ self,
130
+ *,
131
+ control_plane: KnowledgeControlPlane,
132
+ agent_session: KnowledgePlannerSession,
133
+ transcript_store: ObjectStorePort,
134
+ mount_publisher: MountPublisherPort,
135
+ settings: KnowledgePlannerSettings,
136
+ clock: Callable[[], datetime] = _utc_now,
137
+ ) -> None:
138
+ """Bind the harness, immutable ledger, mounts, and deterministic policy."""
139
+ self._control_plane = control_plane
140
+ self._agent_session = agent_session
141
+ self._transcript_store = transcript_store
142
+ self._mount_publisher = mount_publisher
143
+ self._settings = settings
144
+ self._clock = clock
145
+
146
+ def run_planner(
147
+ self, *, snapshot: KnowledgePlanningSnapshot
148
+ ) -> tuple[KnowledgePlanDecisionResult, ...]:
149
+ """Run the producing structural planner only when a trigger is present."""
150
+ return self._run(snapshot=snapshot, run_kind=KnowledgePlanRunKind.PLANNER)
151
+
152
+ def run_reflection(
153
+ self, *, snapshot: KnowledgePlanningSnapshot
154
+ ) -> tuple[KnowledgePlanDecisionResult, ...]:
155
+ """Run the cross-family checker seat with review-only consequences."""
156
+ return self._run(snapshot=snapshot, run_kind=KnowledgePlanRunKind.REFLECTION)
157
+
158
+ def _run(
159
+ self, *, snapshot: KnowledgePlanningSnapshot, run_kind: KnowledgePlanRunKind
160
+ ) -> tuple[KnowledgePlanDecisionResult, ...]:
161
+ """Archive first, parse second, then ask the control plane to route effects."""
162
+ run_id = uuid4()
163
+ session_id = uuid4()
164
+ input_hash = knowledge_planning_input_hash(snapshot=snapshot)
165
+ trigger = primary_knowledge_plan_trigger(snapshot=snapshot, run_kind=run_kind)
166
+ prompt, model = self._seat_configuration(run_kind=run_kind)
167
+ component_version = _planner_component_version(
168
+ settings=self._settings, run_kind=run_kind, prompt=prompt
169
+ )
170
+ transcript_uri: str | None = None
171
+ result: KnowledgeAgentSessionResult | None = None
172
+ try:
173
+ mounts = self._mount_publisher.publish(deployment_id=snapshot.deployment_id)
174
+ if mounts.deployment_id != snapshot.deployment_id:
175
+ raise KnowledgePlannerError("mount publisher crossed deployments")
176
+ result = self._agent_session.run_session(
177
+ request=KnowledgePlannerSessionRequest(
178
+ session_id=session_id,
179
+ model=model,
180
+ prompt=prompt,
181
+ timeout_seconds=self._settings.timeout_seconds,
182
+ input_files={
183
+ "INSTRUCTIONS.md": prompt,
184
+ "input/planning_snapshot.json": (
185
+ f"{snapshot.model_dump_json(indent=2)}\n"
186
+ ),
187
+ "input/decision_schema.json": (
188
+ f"{json.dumps(_PROPOSALS_ADAPTER.json_schema(), indent=2, sort_keys=True)}\n"
189
+ ),
190
+ "context/memory_mounts.json": (
191
+ f"{mounts.model_dump_json(indent=2)}\n"
192
+ ),
193
+ },
194
+ mounts=mounts,
195
+ )
196
+ )
197
+ if result.session_id != session_id:
198
+ raise KnowledgePlannerError("planner returned a different session ID")
199
+ transcript_uri = self._archive_transcript(
200
+ snapshot=snapshot,
201
+ run_kind=run_kind,
202
+ session_id=session_id,
203
+ transcript=result.transcript,
204
+ )
205
+ if result.timed_out:
206
+ raise KnowledgePlannerError("planner session timed out")
207
+ if result.exit_code != 0:
208
+ raise KnowledgePlannerError(
209
+ f"planner session exited with status {result.exit_code}"
210
+ )
211
+ raw_decisions = result.output_files.get("output/decisions.json")
212
+ if raw_decisions is None:
213
+ raise KnowledgePlannerError("planner omitted output/decisions.json")
214
+ try:
215
+ proposals = _PROPOSALS_ADAPTER.validate_json(raw_decisions)
216
+ except ValueError as error:
217
+ raise KnowledgePlannerError(
218
+ "planner decisions violate the typed contract"
219
+ ) from error
220
+ return self._control_plane.record_plan_proposals(
221
+ run=KnowledgePlanRunWrite(
222
+ run_id=run_id,
223
+ deployment_id=snapshot.deployment_id,
224
+ scope_id=snapshot.scope_id,
225
+ run_kind=run_kind,
226
+ trigger=trigger,
227
+ component_version=component_version,
228
+ input_hash=input_hash,
229
+ session_transcript_uri=transcript_uri,
230
+ status=KnowledgePlanRunStatus.SUCCEEDED,
231
+ tokens=result.tokens,
232
+ cost_usd=_cost_decimal(result=result),
233
+ ),
234
+ proposals=proposals,
235
+ auto_apply_max_expected_impact=(
236
+ self._settings.auto_apply_max_expected_impact
237
+ ),
238
+ )
239
+ except Exception as error:
240
+ failure_trace = traceback.format_exc()
241
+ if transcript_uri is None:
242
+ try:
243
+ transcript_uri = self._archive_transcript(
244
+ snapshot=snapshot,
245
+ run_kind=run_kind,
246
+ session_id=session_id,
247
+ transcript=failure_trace,
248
+ )
249
+ except Exception as transcript_error:
250
+ raise ExceptionGroup(
251
+ "planner failure and transcript archival both failed",
252
+ (error, transcript_error),
253
+ ) from None
254
+ try:
255
+ self._control_plane.record_plan_run_failure(
256
+ run=KnowledgePlanRunWrite(
257
+ run_id=run_id,
258
+ deployment_id=snapshot.deployment_id,
259
+ scope_id=snapshot.scope_id,
260
+ run_kind=run_kind,
261
+ trigger=trigger,
262
+ component_version=component_version,
263
+ input_hash=input_hash,
264
+ session_transcript_uri=transcript_uri,
265
+ status=KnowledgePlanRunStatus.FAILED,
266
+ failure=failure_trace,
267
+ tokens=None if result is None else result.tokens,
268
+ cost_usd=(
269
+ None if result is None else _cost_decimal(result=result)
270
+ ),
271
+ )
272
+ )
273
+ except Exception as ledger_error:
274
+ raise ExceptionGroup(
275
+ "planner execution and failure-ledger recording both failed",
276
+ (error, ledger_error),
277
+ ) from None
278
+ raise
279
+
280
+ def _seat_configuration(self, *, run_kind: KnowledgePlanRunKind) -> tuple[str, str]:
281
+ """Select the settings-owned producer or checker model."""
282
+ if run_kind is KnowledgePlanRunKind.REFLECTION:
283
+ return _REFLECTION_PROMPT, self._settings.reflection_model
284
+ return _PLANNER_PROMPT, self._settings.planner_model
285
+
286
+ def _archive_transcript(
287
+ self,
288
+ *,
289
+ snapshot: KnowledgePlanningSnapshot,
290
+ run_kind: KnowledgePlanRunKind,
291
+ session_id: UUID,
292
+ transcript: str,
293
+ ) -> str:
294
+ """Persist the complete session before inspecting any declared output."""
295
+ key = ObjectKey(
296
+ f"{self._settings.transcript_prefix}/{snapshot.deployment_id}/"
297
+ f"{run_kind.value}/{self._clock().date().isoformat()}/{session_id}.json"
298
+ )
299
+ self._transcript_store.write_bytes(key=key, content=transcript.encode("utf-8"))
300
+ return key.root
301
+
302
+
303
+ def _planner_component_version(
304
+ *, settings: KnowledgePlannerSettings, run_kind: KnowledgePlanRunKind, prompt: str
305
+ ) -> str:
306
+ """Make model, family, prompt, timeout, and band policy ledger-visible."""
307
+ if run_kind is KnowledgePlanRunKind.REFLECTION:
308
+ model = settings.reflection_model
309
+ family = settings.reflection_model_family
310
+ else:
311
+ model = settings.planner_model
312
+ family = settings.planner_model_family
313
+ fingerprint = knowledge_summary_hash(
314
+ summary=(
315
+ f"{run_kind.value}\n{prompt}\n{model}\n{family}\n"
316
+ f"{settings.timeout_seconds}\n"
317
+ f"{settings.auto_apply_max_expected_impact}"
318
+ )
319
+ )
320
+ return f"{KNOWLEDGE_PLANNER_VERSION}:{run_kind.value}:{fingerprint[:16]}"
321
+
322
+
323
+ def _cost_decimal(*, result: KnowledgeAgentSessionResult) -> Decimal | None:
324
+ """Convert adapter-native floating metering without binary artifacts."""
325
+ return None if result.cost_usd is None else Decimal(str(result.cost_usd))