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.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- 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))
|