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,110 @@
|
|
|
1
|
+
"""Agent-facing surfaces, imported lazily to keep the client wheel light."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from rememberstack.model.client import ToolDescriptor as ToolDescriptor
|
|
11
|
+
from rememberstack.surfaces.cli import main as cli_main # noqa: F401
|
|
12
|
+
from rememberstack.surfaces.consumption_skill import (
|
|
13
|
+
ConsumptionSkillSurface as ConsumptionSkillSurface,
|
|
14
|
+
)
|
|
15
|
+
from rememberstack.surfaces.graph_queries import GraphQueries as GraphQueries
|
|
16
|
+
from rememberstack.surfaces.http_api import build_api as build_api
|
|
17
|
+
from rememberstack.surfaces.mcp import RecipeMcpServer as RecipeMcpServer
|
|
18
|
+
from rememberstack.surfaces.query_engine import QueryEngine as QueryEngine
|
|
19
|
+
from rememberstack.surfaces.recipe_executor import EXECUTABLE_OPS as EXECUTABLE_OPS
|
|
20
|
+
from rememberstack.surfaces.recipe_executor import (
|
|
21
|
+
RecipeExecutionError as RecipeExecutionError,
|
|
22
|
+
)
|
|
23
|
+
from rememberstack.surfaces.recipe_executor import RecipeExecutor as RecipeExecutor
|
|
24
|
+
from rememberstack.surfaces.recipe_surface import (
|
|
25
|
+
InvalidArgumentError as InvalidArgumentError,
|
|
26
|
+
)
|
|
27
|
+
from rememberstack.surfaces.recipe_surface import (
|
|
28
|
+
MissingArgumentError as MissingArgumentError,
|
|
29
|
+
)
|
|
30
|
+
from rememberstack.surfaces.recipe_surface import RecipeSurface as RecipeSurface
|
|
31
|
+
from rememberstack.surfaces.recipe_surface import (
|
|
32
|
+
UnknownRecipeError as UnknownRecipeError,
|
|
33
|
+
)
|
|
34
|
+
from rememberstack.surfaces.remote_mcp import (
|
|
35
|
+
RemoteRecipeMcpServer as RemoteRecipeMcpServer,
|
|
36
|
+
)
|
|
37
|
+
from rememberstack.surfaces.remote_mcp import serve_mcp_stdio as serve_mcp_stdio
|
|
38
|
+
from rememberstack.surfaces.sdk import MemoryApiError as MemoryApiError
|
|
39
|
+
from rememberstack.surfaces.sdk import MemoryClient as MemoryClient
|
|
40
|
+
|
|
41
|
+
_EXPORTS = {
|
|
42
|
+
"EXECUTABLE_OPS": ("rememberstack.surfaces.recipe_executor", "EXECUTABLE_OPS"),
|
|
43
|
+
"ConsumptionSkillSurface": (
|
|
44
|
+
"rememberstack.surfaces.consumption_skill",
|
|
45
|
+
"ConsumptionSkillSurface",
|
|
46
|
+
),
|
|
47
|
+
"GraphQueries": ("rememberstack.surfaces.graph_queries", "GraphQueries"),
|
|
48
|
+
"InvalidArgumentError": (
|
|
49
|
+
"rememberstack.surfaces.recipe_surface",
|
|
50
|
+
"InvalidArgumentError",
|
|
51
|
+
),
|
|
52
|
+
"MemoryApiError": ("rememberstack.surfaces.sdk", "MemoryApiError"),
|
|
53
|
+
"MemoryClient": ("rememberstack.surfaces.sdk", "MemoryClient"),
|
|
54
|
+
"MissingArgumentError": (
|
|
55
|
+
"rememberstack.surfaces.recipe_surface",
|
|
56
|
+
"MissingArgumentError",
|
|
57
|
+
),
|
|
58
|
+
"QueryEngine": ("rememberstack.surfaces.query_engine", "QueryEngine"),
|
|
59
|
+
"RecipeExecutionError": (
|
|
60
|
+
"rememberstack.surfaces.recipe_executor",
|
|
61
|
+
"RecipeExecutionError",
|
|
62
|
+
),
|
|
63
|
+
"RecipeExecutor": ("rememberstack.surfaces.recipe_executor", "RecipeExecutor"),
|
|
64
|
+
"RecipeMcpServer": ("rememberstack.surfaces.mcp", "RecipeMcpServer"),
|
|
65
|
+
"RecipeSurface": ("rememberstack.surfaces.recipe_surface", "RecipeSurface"),
|
|
66
|
+
"RemoteRecipeMcpServer": (
|
|
67
|
+
"rememberstack.surfaces.remote_mcp",
|
|
68
|
+
"RemoteRecipeMcpServer",
|
|
69
|
+
),
|
|
70
|
+
"ToolDescriptor": ("rememberstack.model.client", "ToolDescriptor"),
|
|
71
|
+
"UnknownRecipeError": (
|
|
72
|
+
"rememberstack.surfaces.recipe_surface",
|
|
73
|
+
"UnknownRecipeError",
|
|
74
|
+
),
|
|
75
|
+
"build_api": ("rememberstack.surfaces.http_api", "build_api"),
|
|
76
|
+
"cli_main": ("rememberstack.surfaces.cli", "main"),
|
|
77
|
+
"serve_mcp_stdio": ("rememberstack.surfaces.remote_mcp", "serve_mcp_stdio"),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
__all__ = (
|
|
81
|
+
"EXECUTABLE_OPS",
|
|
82
|
+
"ConsumptionSkillSurface",
|
|
83
|
+
"GraphQueries",
|
|
84
|
+
"InvalidArgumentError",
|
|
85
|
+
"MemoryApiError",
|
|
86
|
+
"MemoryClient",
|
|
87
|
+
"MissingArgumentError",
|
|
88
|
+
"QueryEngine",
|
|
89
|
+
"RecipeExecutionError",
|
|
90
|
+
"RecipeExecutor",
|
|
91
|
+
"RecipeMcpServer",
|
|
92
|
+
"RecipeSurface",
|
|
93
|
+
"RemoteRecipeMcpServer",
|
|
94
|
+
"ToolDescriptor",
|
|
95
|
+
"UnknownRecipeError",
|
|
96
|
+
"build_api",
|
|
97
|
+
"cli_main",
|
|
98
|
+
"serve_mcp_stdio",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def __getattr__(name: str) -> Any:
|
|
103
|
+
"""Load each surface only when callers request it."""
|
|
104
|
+
try:
|
|
105
|
+
module_name, attribute = _EXPORTS[name]
|
|
106
|
+
except KeyError as error:
|
|
107
|
+
raise AttributeError(name) from error
|
|
108
|
+
value = getattr(import_module(module_name), attribute)
|
|
109
|
+
globals()[name] = value
|
|
110
|
+
return value
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""The ``remember`` CLI: a dependency-light client plus optional local admin commands.
|
|
2
|
+
|
|
3
|
+
Query, ingest, connector management, and MCP all talk to the deployment HTTP
|
|
4
|
+
API. ``remember review``, ``remember budget``, and ``remember ops`` import the server extra
|
|
5
|
+
and connect to the spine.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from importlib import import_module
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import sys
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
from uuid import UUID
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
from pydantic import JsonValue
|
|
21
|
+
|
|
22
|
+
from rememberstack import __version__
|
|
23
|
+
from rememberstack.model.adjudication import ReviewDecisionError
|
|
24
|
+
from rememberstack.model.client import ConnectorCreate
|
|
25
|
+
from rememberstack.surfaces.remote_mcp import RemoteRecipeMcpServer
|
|
26
|
+
from rememberstack.surfaces.remote_mcp import serve_mcp_stdio
|
|
27
|
+
from rememberstack.surfaces.sdk import MemoryApiError
|
|
28
|
+
from rememberstack.surfaces.sdk import MemoryClient
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from rememberstack.spine.review import ReviewQueue
|
|
32
|
+
from rememberstack.spine.work_ledger import WorkLedger
|
|
33
|
+
|
|
34
|
+
_MERGE_VERDICTS = ("merge", "not_merge")
|
|
35
|
+
_TRIAGE_VERDICTS = ("restore_support", "invalidate_fact", "uncertain")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main(argv: list[str] | None = None) -> int:
|
|
39
|
+
"""The ``remember`` entry point; returns the process exit code."""
|
|
40
|
+
parser = _build_parser()
|
|
41
|
+
args = parser.parse_args(argv)
|
|
42
|
+
try:
|
|
43
|
+
if args.command == "review":
|
|
44
|
+
return _run_review(args)
|
|
45
|
+
if args.command == "budget":
|
|
46
|
+
return _run_budget(args)
|
|
47
|
+
if args.command == "ops":
|
|
48
|
+
return _run_ops(args)
|
|
49
|
+
if args.command == "query":
|
|
50
|
+
return _run_query(args)
|
|
51
|
+
if args.command == "ingest":
|
|
52
|
+
return _run_ingest(args)
|
|
53
|
+
if args.command == "connectors":
|
|
54
|
+
return _run_connectors(args)
|
|
55
|
+
if args.command == "mcp":
|
|
56
|
+
return _run_mcp()
|
|
57
|
+
except MemoryApiError as error:
|
|
58
|
+
print(f"error: {error}", file=sys.stderr)
|
|
59
|
+
return 1
|
|
60
|
+
parser.print_help()
|
|
61
|
+
return 2
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _run_review(args: argparse.Namespace) -> int:
|
|
65
|
+
"""Compose the optional local ReviewQueue over the spine."""
|
|
66
|
+
try:
|
|
67
|
+
from sqlalchemy import create_engine
|
|
68
|
+
|
|
69
|
+
from rememberstack.spine.review import ReviewQueue
|
|
70
|
+
from rememberstack.spine.settings import load_database_settings
|
|
71
|
+
except ModuleNotFoundError:
|
|
72
|
+
print(
|
|
73
|
+
"error: review commands require the 'rememberstack[server]' extra",
|
|
74
|
+
file=sys.stderr,
|
|
75
|
+
)
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
engine = create_engine(load_database_settings().sqlalchemy_url())
|
|
79
|
+
try:
|
|
80
|
+
queue = ReviewQueue(engine=engine)
|
|
81
|
+
if args.review_command == "list":
|
|
82
|
+
return _list(queue=queue, deployment_id=args.deployment)
|
|
83
|
+
return _decide(
|
|
84
|
+
queue=queue,
|
|
85
|
+
deployment_id=args.deployment,
|
|
86
|
+
review_id=args.review_id,
|
|
87
|
+
verdict=args.verdict,
|
|
88
|
+
reviewer=args.reviewer,
|
|
89
|
+
note=args.note,
|
|
90
|
+
)
|
|
91
|
+
finally:
|
|
92
|
+
engine.dispose()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _run_budget(args: argparse.Namespace) -> int:
|
|
96
|
+
"""Compose the local WorkLedger and print configured budget state."""
|
|
97
|
+
try:
|
|
98
|
+
from sqlalchemy import create_engine
|
|
99
|
+
|
|
100
|
+
from rememberstack.spine.settings import load_database_settings
|
|
101
|
+
from rememberstack.spine.work_ledger import WorkLedger
|
|
102
|
+
from rememberstack.spine.work_ledger import WorkLedgerSettings
|
|
103
|
+
except ModuleNotFoundError:
|
|
104
|
+
print(
|
|
105
|
+
"error: budget commands require the 'rememberstack[server]' extra",
|
|
106
|
+
file=sys.stderr,
|
|
107
|
+
)
|
|
108
|
+
return 1
|
|
109
|
+
|
|
110
|
+
engine = create_engine(load_database_settings().sqlalchemy_url())
|
|
111
|
+
try:
|
|
112
|
+
ledger = WorkLedger(engine=engine, settings=WorkLedgerSettings())
|
|
113
|
+
return _inspect_budgets(ledger=ledger, deployment_id=args.deployment)
|
|
114
|
+
finally:
|
|
115
|
+
engine.dispose()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _run_ops(args: argparse.Namespace) -> int:
|
|
119
|
+
"""Compose bounded local inspection, one-row replay, or an existing rebuild."""
|
|
120
|
+
try:
|
|
121
|
+
from rememberstack.model import ProcessingLane
|
|
122
|
+
from rememberstack.model import WorkLedgerError
|
|
123
|
+
|
|
124
|
+
operations_type = import_module(
|
|
125
|
+
"rememberstack.profiles.selfhost_operations"
|
|
126
|
+
).SelfHostOperations
|
|
127
|
+
except ModuleNotFoundError:
|
|
128
|
+
print(
|
|
129
|
+
"error: ops commands require the 'rememberstack[server]' extra",
|
|
130
|
+
file=sys.stderr,
|
|
131
|
+
)
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
operations = operations_type.from_settings()
|
|
135
|
+
try:
|
|
136
|
+
if args.ops_command == "inspect":
|
|
137
|
+
report = operations.inspect(deployment_id=args.deployment)
|
|
138
|
+
print(report.model_dump_json())
|
|
139
|
+
return 0
|
|
140
|
+
if args.ops_command == "replay":
|
|
141
|
+
replayed = operations.replay(
|
|
142
|
+
deployment_id=args.deployment,
|
|
143
|
+
processing_id=args.processing_id,
|
|
144
|
+
attempt_allowance=args.attempts,
|
|
145
|
+
lane=None if args.lane is None else ProcessingLane(args.lane),
|
|
146
|
+
not_before=args.not_before,
|
|
147
|
+
)
|
|
148
|
+
print(replayed.model_dump_json())
|
|
149
|
+
return 0
|
|
150
|
+
result = operations.rebuild(
|
|
151
|
+
plane=args.plane,
|
|
152
|
+
deployment_id=args.deployment,
|
|
153
|
+
snapshot_root=args.snapshot_root,
|
|
154
|
+
workdir=args.workdir,
|
|
155
|
+
version=args.version,
|
|
156
|
+
)
|
|
157
|
+
print(json.dumps(result, default=str, sort_keys=True))
|
|
158
|
+
return 0
|
|
159
|
+
except (WorkLedgerError, ValueError, OSError) as error:
|
|
160
|
+
print(f"error: {error}", file=sys.stderr)
|
|
161
|
+
return 1
|
|
162
|
+
finally:
|
|
163
|
+
operations.close()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _run_query(args: argparse.Namespace) -> int:
|
|
167
|
+
"""Run a query command through the typed remote SDK."""
|
|
168
|
+
with MemoryClient.from_settings() as client:
|
|
169
|
+
if args.query_command == "list":
|
|
170
|
+
for descriptor in client.recipes():
|
|
171
|
+
print(descriptor.model_dump_json())
|
|
172
|
+
return 0
|
|
173
|
+
try:
|
|
174
|
+
arguments = dict(_split_arg(pair) for pair in args.arg)
|
|
175
|
+
except ValueError as error:
|
|
176
|
+
print(f"error: {error}", file=sys.stderr)
|
|
177
|
+
return 2
|
|
178
|
+
print(
|
|
179
|
+
client.run_recipe(name=args.recipe, arguments=arguments).model_dump_json()
|
|
180
|
+
)
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _run_ingest(args: argparse.Namespace) -> int:
|
|
185
|
+
"""Push one local file to the deployment's E0 ingress."""
|
|
186
|
+
try:
|
|
187
|
+
with MemoryClient.from_settings() as client:
|
|
188
|
+
result = client.ingest(
|
|
189
|
+
args.file,
|
|
190
|
+
mime=args.mime,
|
|
191
|
+
title=args.title,
|
|
192
|
+
source_kind=args.source_kind,
|
|
193
|
+
source_ref=args.source_ref,
|
|
194
|
+
source_modified_at=args.source_modified_at,
|
|
195
|
+
versioning_mode=args.versioning_mode,
|
|
196
|
+
source_version_ref=args.source_version_ref,
|
|
197
|
+
)
|
|
198
|
+
except OSError as error:
|
|
199
|
+
print(f"error: could not read {args.file}: {error}", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
except ValueError as error:
|
|
202
|
+
print(f"error: {error}", file=sys.stderr)
|
|
203
|
+
return 2
|
|
204
|
+
print(result.model_dump_json())
|
|
205
|
+
return 0
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _run_connectors(args: argparse.Namespace) -> int:
|
|
209
|
+
"""Manage connector configuration on the deployment API."""
|
|
210
|
+
with MemoryClient.from_settings() as client:
|
|
211
|
+
if args.connector_command == "list":
|
|
212
|
+
for connector in client.connectors():
|
|
213
|
+
print(connector.model_dump_json())
|
|
214
|
+
return 0
|
|
215
|
+
if args.connector_command == "add":
|
|
216
|
+
try:
|
|
217
|
+
configuration: dict[str, JsonValue] = dict(
|
|
218
|
+
_split_arg(pair) for pair in args.config
|
|
219
|
+
)
|
|
220
|
+
connector = ConnectorCreate(
|
|
221
|
+
kind=args.kind,
|
|
222
|
+
name=args.name,
|
|
223
|
+
configuration=configuration,
|
|
224
|
+
credential_ref=args.credential_ref,
|
|
225
|
+
)
|
|
226
|
+
except ValueError as error:
|
|
227
|
+
print(f"error: {error}", file=sys.stderr)
|
|
228
|
+
return 2
|
|
229
|
+
result = client.add_connector(connector=connector)
|
|
230
|
+
elif args.connector_command == "pause":
|
|
231
|
+
result = client.pause_connector(connector_id=args.connector_id)
|
|
232
|
+
else:
|
|
233
|
+
result = client.connector_status(connector_id=args.connector_id)
|
|
234
|
+
print(result.model_dump_json())
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _run_mcp() -> int:
|
|
239
|
+
"""Expose the remote deployment recipe registry over MCP stdio."""
|
|
240
|
+
with MemoryClient.from_settings() as client:
|
|
241
|
+
return serve_mcp_stdio(server=RemoteRecipeMcpServer(client=client))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def query_list(*, client: httpx.Client) -> int:
|
|
245
|
+
"""Print recipes from an injected client (the parity-testable CLI seam)."""
|
|
246
|
+
try:
|
|
247
|
+
for descriptor in MemoryClient(client=client).recipes():
|
|
248
|
+
print(descriptor.model_dump_json())
|
|
249
|
+
except MemoryApiError as error:
|
|
250
|
+
print(f"error: {error}", file=sys.stderr)
|
|
251
|
+
return 1
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def query_run(*, client: httpx.Client, name: str, arg_pairs: list[str]) -> int:
|
|
256
|
+
"""Run one recipe through an injected client and print its envelope."""
|
|
257
|
+
try:
|
|
258
|
+
arguments = dict(_split_arg(pair) for pair in arg_pairs)
|
|
259
|
+
except ValueError as error:
|
|
260
|
+
print(f"error: {error}", file=sys.stderr)
|
|
261
|
+
return 2
|
|
262
|
+
try:
|
|
263
|
+
envelope = MemoryClient(client=client).run_recipe(
|
|
264
|
+
name=name, arguments=arguments
|
|
265
|
+
)
|
|
266
|
+
except MemoryApiError as error:
|
|
267
|
+
print(f"error: {error}", file=sys.stderr)
|
|
268
|
+
return 1
|
|
269
|
+
print(envelope.model_dump_json())
|
|
270
|
+
return 0
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _split_arg(pair: str) -> tuple[str, str]:
|
|
274
|
+
"""Split one ``key=value`` argument, or raise a clear error."""
|
|
275
|
+
key, separator, value = pair.partition("=")
|
|
276
|
+
if not separator or not key:
|
|
277
|
+
raise ValueError(f"argument {pair!r} is not key=value")
|
|
278
|
+
return key, value
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _list(*, queue: ReviewQueue, deployment_id: UUID) -> int:
|
|
282
|
+
"""Print open items ranked by expected impact, one JSON line each."""
|
|
283
|
+
for item in queue.pending(deployment_id=deployment_id):
|
|
284
|
+
print(
|
|
285
|
+
json.dumps(
|
|
286
|
+
{
|
|
287
|
+
"review_id": str(item.review_id),
|
|
288
|
+
"kind": item.item_kind,
|
|
289
|
+
"expected_impact": item.expected_impact,
|
|
290
|
+
"blast_radius": item.blast_radius,
|
|
291
|
+
"status": item.status,
|
|
292
|
+
"candidate": item.candidate,
|
|
293
|
+
},
|
|
294
|
+
default=str,
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _inspect_budgets(*, ledger: WorkLedger, deployment_id: UUID) -> int:
|
|
301
|
+
"""Print one current-window JSON record per configured deployment budget."""
|
|
302
|
+
for status in ledger.budget_status(deployment_id=deployment_id):
|
|
303
|
+
print(status.model_dump_json())
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _decide(
|
|
308
|
+
*,
|
|
309
|
+
queue: ReviewQueue,
|
|
310
|
+
deployment_id: UUID,
|
|
311
|
+
review_id: UUID,
|
|
312
|
+
verdict: str,
|
|
313
|
+
reviewer: str,
|
|
314
|
+
note: str | None,
|
|
315
|
+
) -> int:
|
|
316
|
+
"""Apply one verdict; the verdict picks the decision path by its name."""
|
|
317
|
+
try:
|
|
318
|
+
if verdict in _MERGE_VERDICTS:
|
|
319
|
+
events = queue.decide_merge(
|
|
320
|
+
deployment_id=deployment_id,
|
|
321
|
+
review_id=review_id,
|
|
322
|
+
verdict=verdict,
|
|
323
|
+
reviewer=reviewer,
|
|
324
|
+
note=note,
|
|
325
|
+
)
|
|
326
|
+
print(
|
|
327
|
+
json.dumps(
|
|
328
|
+
{"verdict": verdict, "merge_events": [str(e) for e in events]}
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
else:
|
|
332
|
+
queue.decide_support_withdrawn(
|
|
333
|
+
deployment_id=deployment_id,
|
|
334
|
+
review_id=review_id,
|
|
335
|
+
verdict=verdict,
|
|
336
|
+
reviewer=reviewer,
|
|
337
|
+
note=note,
|
|
338
|
+
)
|
|
339
|
+
print(json.dumps({"verdict": verdict}))
|
|
340
|
+
except ReviewDecisionError as error:
|
|
341
|
+
print(f"error: {error}", file=sys.stderr)
|
|
342
|
+
return 1
|
|
343
|
+
return 0
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
347
|
+
"""Build the client-first command grammar."""
|
|
348
|
+
parser = argparse.ArgumentParser(
|
|
349
|
+
prog="remember", description="RememberStack command-line interface"
|
|
350
|
+
)
|
|
351
|
+
parser.add_argument(
|
|
352
|
+
"--version", action="version", version=f"RememberStack {__version__}"
|
|
353
|
+
)
|
|
354
|
+
commands = parser.add_subparsers(dest="command")
|
|
355
|
+
|
|
356
|
+
review = commands.add_parser("review", help="the D24 local review queue")
|
|
357
|
+
review_commands = review.add_subparsers(dest="review_command", required=True)
|
|
358
|
+
listing = review_commands.add_parser("list", help="open items, impact-ranked")
|
|
359
|
+
listing.add_argument("--deployment", type=UUID, required=True)
|
|
360
|
+
decide = review_commands.add_parser("decide", help="apply one verdict")
|
|
361
|
+
decide.add_argument("review_id", type=UUID)
|
|
362
|
+
decide.add_argument("--deployment", type=UUID, required=True)
|
|
363
|
+
decide.add_argument(
|
|
364
|
+
"--verdict", required=True, choices=(*_MERGE_VERDICTS, *_TRIAGE_VERDICTS)
|
|
365
|
+
)
|
|
366
|
+
decide.add_argument("--reviewer", required=True)
|
|
367
|
+
decide.add_argument("--note", default=None)
|
|
368
|
+
|
|
369
|
+
budget = commands.add_parser("budget", help="inspect configured spend ceilings")
|
|
370
|
+
budget_commands = budget.add_subparsers(dest="budget_command", required=True)
|
|
371
|
+
inspect = budget_commands.add_parser(
|
|
372
|
+
"inspect", help="current spend, tier attribution, and parked work"
|
|
373
|
+
)
|
|
374
|
+
inspect.add_argument("--deployment", type=UUID, required=True)
|
|
375
|
+
|
|
376
|
+
ops = commands.add_parser(
|
|
377
|
+
"ops", help="inspect durable state, replay one DLQ row, or rebuild"
|
|
378
|
+
)
|
|
379
|
+
ops_commands = ops.add_subparsers(dest="ops_command", required=True)
|
|
380
|
+
ops_inspect = ops_commands.add_parser(
|
|
381
|
+
"inspect", help="bounded pipeline, DLQ, projection, and currency report"
|
|
382
|
+
)
|
|
383
|
+
ops_inspect.add_argument("--deployment", type=UUID, required=True)
|
|
384
|
+
replay = ops_commands.add_parser("replay", help="reopen one dead-letter row")
|
|
385
|
+
replay.add_argument("processing_id", type=UUID)
|
|
386
|
+
replay.add_argument("--deployment", type=UUID, required=True)
|
|
387
|
+
replay.add_argument(
|
|
388
|
+
"--attempts",
|
|
389
|
+
type=int,
|
|
390
|
+
default=1,
|
|
391
|
+
help="additional handler attempts to grant (default: 1)",
|
|
392
|
+
)
|
|
393
|
+
replay.add_argument("--lane", choices=("steady", "backfill"))
|
|
394
|
+
replay.add_argument("--not-before", type=datetime.fromisoformat)
|
|
395
|
+
rebuild = ops_commands.add_parser(
|
|
396
|
+
"rebuild", help="invoke the existing P2 or P3 full-rebuild path"
|
|
397
|
+
)
|
|
398
|
+
rebuild.add_argument("--plane", choices=("p2", "p3"), required=True)
|
|
399
|
+
rebuild.add_argument("--deployment", type=UUID, required=True)
|
|
400
|
+
rebuild.add_argument("--snapshot-root", type=Path, required=True)
|
|
401
|
+
rebuild.add_argument("--workdir", type=Path, required=True)
|
|
402
|
+
rebuild.add_argument("--version", required=True)
|
|
403
|
+
|
|
404
|
+
query = commands.add_parser("query", help="query deployment recipes")
|
|
405
|
+
query_commands = query.add_subparsers(dest="query_command", required=True)
|
|
406
|
+
query_commands.add_parser("list", help="list the remote recipe tools")
|
|
407
|
+
run = query_commands.add_parser("run", help="run one recipe by name")
|
|
408
|
+
run.add_argument("recipe", help="the recipe name (see `remember query list`)")
|
|
409
|
+
run.add_argument(
|
|
410
|
+
"--arg", action="append", default=[], metavar="KEY=VALUE", help="repeatable"
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
ingest = commands.add_parser("ingest", help="push a file through E0")
|
|
414
|
+
ingest.add_argument("file", type=Path)
|
|
415
|
+
ingest.add_argument("--mime")
|
|
416
|
+
ingest.add_argument("--title")
|
|
417
|
+
ingest.add_argument("--source-kind")
|
|
418
|
+
ingest.add_argument("--source-ref")
|
|
419
|
+
ingest.add_argument("--source-modified-at", type=datetime.fromisoformat)
|
|
420
|
+
ingest.add_argument(
|
|
421
|
+
"--versioning-mode", choices=("snapshot", "living"), default="snapshot"
|
|
422
|
+
)
|
|
423
|
+
ingest.add_argument("--source-version-ref")
|
|
424
|
+
|
|
425
|
+
connectors = commands.add_parser(
|
|
426
|
+
"connectors", help="manage deployment-side connectors"
|
|
427
|
+
)
|
|
428
|
+
connector_commands = connectors.add_subparsers(
|
|
429
|
+
dest="connector_command", required=True
|
|
430
|
+
)
|
|
431
|
+
connector_commands.add_parser("list", help="list connectors")
|
|
432
|
+
add = connector_commands.add_parser("add", help="add connector configuration")
|
|
433
|
+
add.add_argument("kind")
|
|
434
|
+
add.add_argument("--name", required=True)
|
|
435
|
+
add.add_argument("--config", action="append", default=[], metavar="KEY=VALUE")
|
|
436
|
+
add.add_argument("--credential-ref")
|
|
437
|
+
pause = connector_commands.add_parser("pause", help="pause a connector")
|
|
438
|
+
pause.add_argument("connector_id", type=UUID)
|
|
439
|
+
status = connector_commands.add_parser("status", help="show connector status")
|
|
440
|
+
status.add_argument("connector_id", type=UUID)
|
|
441
|
+
|
|
442
|
+
commands.add_parser("mcp", help="serve remote recipes over MCP stdio")
|
|
443
|
+
return parser
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
if __name__ == "__main__": # pragma: no cover - exercised via the entry point
|
|
447
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Deployment surface for rendering and publishing the D51 consumption skill."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from rememberstack.core import render_consumption_skill
|
|
8
|
+
from rememberstack.model import ConsumptionRecipe
|
|
9
|
+
from rememberstack.model import ConsumptionSkillContext
|
|
10
|
+
from rememberstack.model import PublishedMounts
|
|
11
|
+
from rememberstack.model import Recipe
|
|
12
|
+
from rememberstack.model import RenderedConsumptionSkill
|
|
13
|
+
from rememberstack.spine.consumption import ConsumptionCatalog
|
|
14
|
+
from rememberstack.spine.recipes import RecipeRegistry
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConsumptionSkillSurface:
|
|
18
|
+
"""Render the skill from one deployment's live registries and mounts."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
catalog: ConsumptionCatalog,
|
|
24
|
+
recipes: RecipeRegistry,
|
|
25
|
+
deployment_id: UUID,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Bind the renderer to one deployment and its two spine read models."""
|
|
28
|
+
self._catalog = catalog
|
|
29
|
+
self._recipes = recipes
|
|
30
|
+
self._deployment_id = deployment_id
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def deployment_id(self) -> UUID:
|
|
34
|
+
"""The one deployment this skill surface renders."""
|
|
35
|
+
return self._deployment_id
|
|
36
|
+
|
|
37
|
+
def render(
|
|
38
|
+
self, *, mounts: PublishedMounts | None = None
|
|
39
|
+
) -> RenderedConsumptionSkill:
|
|
40
|
+
"""Render the current deployment-specific skill without writing it."""
|
|
41
|
+
if mounts is not None and mounts.deployment_id != self._deployment_id:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"the mount set and consumption skill serve different deployments"
|
|
44
|
+
)
|
|
45
|
+
recipes = tuple(
|
|
46
|
+
ConsumptionRecipe(
|
|
47
|
+
name=recipe.name,
|
|
48
|
+
description=recipe.description,
|
|
49
|
+
output_grain=recipe.output_grain,
|
|
50
|
+
answer_intent=recipe.answer_intent,
|
|
51
|
+
)
|
|
52
|
+
for recipe in _latest_recipes(
|
|
53
|
+
recipes=self._recipes.active(deployment_id=self._deployment_id)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
return render_consumption_skill(
|
|
57
|
+
context=ConsumptionSkillContext(
|
|
58
|
+
deployment=self._catalog.deployment(deployment_id=self._deployment_id),
|
|
59
|
+
recipes=recipes,
|
|
60
|
+
mounts=mounts,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def publish(self, *, directory: Path, rendered: RenderedConsumptionSkill) -> Path:
|
|
65
|
+
"""Atomically publish one already-rendered ``SKILL.md`` artifact."""
|
|
66
|
+
if rendered.deployment_id != self._deployment_id:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"the rendered skill and publisher serve different deployments"
|
|
69
|
+
)
|
|
70
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
destination = directory / rendered.filename
|
|
72
|
+
staging = directory / f".{rendered.filename}.{uuid4().hex}.tmp"
|
|
73
|
+
staging.write_text(data=rendered.content, encoding="utf-8")
|
|
74
|
+
staging.replace(target=destination)
|
|
75
|
+
return destination
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _latest_recipes(*, recipes: tuple[Recipe, ...]) -> tuple[Recipe, ...]:
|
|
79
|
+
"""Keep one latest active recipe per name from registry-ordered rows."""
|
|
80
|
+
seen: set[str] = set()
|
|
81
|
+
latest: list[Recipe] = []
|
|
82
|
+
for recipe in recipes:
|
|
83
|
+
if recipe.name in seen:
|
|
84
|
+
continue
|
|
85
|
+
seen.add(recipe.name)
|
|
86
|
+
latest.append(recipe)
|
|
87
|
+
return tuple(latest)
|