shellbrain 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.
- app/__init__.py +1 -0
- app/__main__.py +7 -0
- app/boot/__init__.py +1 -0
- app/boot/admin_db.py +88 -0
- app/boot/config.py +14 -0
- app/boot/create_policy.py +52 -0
- app/boot/db.py +70 -0
- app/boot/embeddings.py +55 -0
- app/boot/home.py +45 -0
- app/boot/migrations.py +61 -0
- app/boot/read_policy.py +179 -0
- app/boot/repos.py +15 -0
- app/boot/retrieval.py +3 -0
- app/boot/thresholds.py +19 -0
- app/boot/update_policy.py +34 -0
- app/boot/use_cases.py +22 -0
- app/config/__init__.py +1 -0
- app/config/defaults/create_policy.yaml +7 -0
- app/config/defaults/read_policy.yaml +25 -0
- app/config/defaults/runtime.yaml +10 -0
- app/config/defaults/thresholds.yaml +3 -0
- app/config/defaults/update_policy.yaml +5 -0
- app/config/loader.py +58 -0
- app/core/__init__.py +1 -0
- app/core/contracts/__init__.py +1 -0
- app/core/contracts/errors.py +29 -0
- app/core/contracts/requests.py +211 -0
- app/core/contracts/responses.py +15 -0
- app/core/entities/__init__.py +1 -0
- app/core/entities/associations.py +58 -0
- app/core/entities/episodes.py +66 -0
- app/core/entities/evidence.py +29 -0
- app/core/entities/facts.py +30 -0
- app/core/entities/guidance.py +47 -0
- app/core/entities/identity.py +48 -0
- app/core/entities/memory.py +34 -0
- app/core/entities/runtime_context.py +19 -0
- app/core/entities/session_state.py +31 -0
- app/core/entities/telemetry.py +152 -0
- app/core/entities/utility.py +14 -0
- app/core/interfaces/__init__.py +1 -0
- app/core/interfaces/clock.py +12 -0
- app/core/interfaces/config.py +28 -0
- app/core/interfaces/embeddings.py +12 -0
- app/core/interfaces/idgen.py +11 -0
- app/core/interfaces/repos.py +279 -0
- app/core/interfaces/retrieval.py +20 -0
- app/core/interfaces/session_state_store.py +33 -0
- app/core/interfaces/unit_of_work.py +50 -0
- app/core/policies/__init__.py +1 -0
- app/core/policies/_shared/__init__.py +1 -0
- app/core/policies/_shared/executor.py +132 -0
- app/core/policies/_shared/side_effects.py +9 -0
- app/core/policies/create_policy/__init__.py +1 -0
- app/core/policies/create_policy/pipeline.py +96 -0
- app/core/policies/read_policy/__init__.py +1 -0
- app/core/policies/read_policy/bm25.py +114 -0
- app/core/policies/read_policy/context_pack_builder.py +140 -0
- app/core/policies/read_policy/expansion.py +132 -0
- app/core/policies/read_policy/fusion_rrf.py +34 -0
- app/core/policies/read_policy/lexical_query.py +101 -0
- app/core/policies/read_policy/pipeline.py +93 -0
- app/core/policies/read_policy/scenario_lift.py +11 -0
- app/core/policies/read_policy/scoring.py +61 -0
- app/core/policies/read_policy/seed_retrieval.py +54 -0
- app/core/policies/read_policy/utility_prior.py +11 -0
- app/core/policies/update_policy/__init__.py +1 -0
- app/core/policies/update_policy/pipeline.py +80 -0
- app/core/use_cases/__init__.py +1 -0
- app/core/use_cases/build_guidance.py +85 -0
- app/core/use_cases/create_memory.py +26 -0
- app/core/use_cases/manage_session_state.py +159 -0
- app/core/use_cases/read_memory.py +21 -0
- app/core/use_cases/record_episode_sync_telemetry.py +19 -0
- app/core/use_cases/record_operation_telemetry.py +32 -0
- app/core/use_cases/sync_episode.py +162 -0
- app/core/use_cases/update_memory.py +40 -0
- app/migrations/__init__.py +1 -0
- app/migrations/env.py +65 -0
- app/migrations/versions/20260226_0001_initial_schema.py +232 -0
- app/migrations/versions/20260312_0002_add_hard_invariants.py +60 -0
- app/migrations/versions/20260312_0003_drop_create_confidence.py +40 -0
- app/migrations/versions/20260313_0004_episode_sync_hardening.py +71 -0
- app/migrations/versions/20260313_0005_evidence_episode_event_refs.py +45 -0
- app/migrations/versions/20260318_0006_usage_telemetry_schema.py +175 -0
- app/migrations/versions/20260319_0007_identity_session_guidance.py +49 -0
- app/migrations/versions/20260320_0008_instance_metadata_and_backup_safety.py +31 -0
- app/migrations/versions/__init__.py +1 -0
- app/periphery/__init__.py +1 -0
- app/periphery/admin/__init__.py +1 -0
- app/periphery/admin/backup.py +360 -0
- app/periphery/admin/destructive_guard.py +32 -0
- app/periphery/admin/doctor.py +192 -0
- app/periphery/admin/init.py +996 -0
- app/periphery/admin/instance_guard.py +211 -0
- app/periphery/admin/machine_state.py +354 -0
- app/periphery/admin/privileges.py +42 -0
- app/periphery/admin/repo_state.py +266 -0
- app/periphery/admin/restore.py +30 -0
- app/periphery/cli/__init__.py +1 -0
- app/periphery/cli/handlers.py +830 -0
- app/periphery/cli/hydration.py +119 -0
- app/periphery/cli/main.py +710 -0
- app/periphery/cli/presenter_json.py +10 -0
- app/periphery/cli/schema_validation.py +201 -0
- app/periphery/db/__init__.py +1 -0
- app/periphery/db/engine.py +10 -0
- app/periphery/db/models/__init__.py +1 -0
- app/periphery/db/models/associations.py +55 -0
- app/periphery/db/models/episodes.py +55 -0
- app/periphery/db/models/evidence.py +19 -0
- app/periphery/db/models/experiences.py +33 -0
- app/periphery/db/models/instance_metadata.py +17 -0
- app/periphery/db/models/memories.py +39 -0
- app/periphery/db/models/metadata.py +6 -0
- app/periphery/db/models/registry.py +18 -0
- app/periphery/db/models/telemetry.py +174 -0
- app/periphery/db/models/utility.py +19 -0
- app/periphery/db/models/views.py +154 -0
- app/periphery/db/repos/__init__.py +1 -0
- app/periphery/db/repos/relational/__init__.py +1 -0
- app/periphery/db/repos/relational/associations_repo.py +117 -0
- app/periphery/db/repos/relational/episodes_repo.py +188 -0
- app/periphery/db/repos/relational/evidence_repo.py +82 -0
- app/periphery/db/repos/relational/experiences_repo.py +41 -0
- app/periphery/db/repos/relational/memories_repo.py +99 -0
- app/periphery/db/repos/relational/read_policy_repo.py +202 -0
- app/periphery/db/repos/relational/telemetry_repo.py +161 -0
- app/periphery/db/repos/relational/utility_repo.py +30 -0
- app/periphery/db/repos/semantic/__init__.py +1 -0
- app/periphery/db/repos/semantic/keyword_retrieval_repo.py +63 -0
- app/periphery/db/repos/semantic/semantic_retrieval_repo.py +111 -0
- app/periphery/db/session.py +10 -0
- app/periphery/db/uow.py +75 -0
- app/periphery/embeddings/__init__.py +1 -0
- app/periphery/embeddings/local_provider.py +35 -0
- app/periphery/embeddings/query_vector_search.py +18 -0
- app/periphery/episodes/__init__.py +1 -0
- app/periphery/episodes/claude_code.py +387 -0
- app/periphery/episodes/codex.py +423 -0
- app/periphery/episodes/launcher.py +66 -0
- app/periphery/episodes/normalization.py +31 -0
- app/periphery/episodes/poller.py +299 -0
- app/periphery/episodes/source_discovery.py +66 -0
- app/periphery/episodes/tool_filter.py +165 -0
- app/periphery/identity/__init__.py +1 -0
- app/periphery/identity/claude_hook_install.py +67 -0
- app/periphery/identity/claude_runtime.py +83 -0
- app/periphery/identity/codex_runtime.py +32 -0
- app/periphery/identity/compatibility.py +38 -0
- app/periphery/identity/resolver.py +163 -0
- app/periphery/session_state/__init__.py +1 -0
- app/periphery/session_state/file_store.py +100 -0
- app/periphery/telemetry/__init__.py +33 -0
- app/periphery/telemetry/operation_summary.py +299 -0
- app/periphery/telemetry/session_selection.py +156 -0
- app/periphery/telemetry/sync_summary.py +65 -0
- app/periphery/validation/__init__.py +1 -0
- app/periphery/validation/integrity_validation.py +253 -0
- app/periphery/validation/semantic_validation.py +94 -0
- shellbrain-0.1.0.dist-info/METADATA +130 -0
- shellbrain-0.1.0.dist-info/RECORD +165 -0
- shellbrain-0.1.0.dist-info/WHEEL +5 -0
- shellbrain-0.1.0.dist-info/entry_points.txt +2 -0
- shellbrain-0.1.0.dist-info/top_level.txt +1 -0
app/boot/use_cases.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""This module defines boot-time helpers used by CLI handlers to obtain use-case dependencies."""
|
|
2
|
+
|
|
3
|
+
from app.boot.embeddings import get_embedding_model_name, get_embedding_provider
|
|
4
|
+
from app.boot.repos import get_uow
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_uow_factory():
|
|
8
|
+
"""This function returns a callable that creates fresh unit-of-work instances."""
|
|
9
|
+
|
|
10
|
+
return get_uow
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_embedding_provider_factory():
|
|
14
|
+
"""This function returns a callable that creates fresh embedding providers."""
|
|
15
|
+
|
|
16
|
+
return get_embedding_provider
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_embedding_model():
|
|
20
|
+
"""This function returns the model label that should be stored for embedding rows."""
|
|
21
|
+
|
|
22
|
+
return get_embedding_model_name()
|
app/config/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This package contains configuration loading adapters and defaults."""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file defines default read-policy controls for retrieval and context-pack assembly.
|
|
2
|
+
weights:
|
|
3
|
+
semantic: 1.0
|
|
4
|
+
keyword: 1.0
|
|
5
|
+
fusion:
|
|
6
|
+
k_rrf: 20
|
|
7
|
+
limits:
|
|
8
|
+
targeted: 8
|
|
9
|
+
ambient: 12
|
|
10
|
+
quotas:
|
|
11
|
+
targeted:
|
|
12
|
+
direct: 4
|
|
13
|
+
explicit: 3
|
|
14
|
+
implicit: 1
|
|
15
|
+
ambient:
|
|
16
|
+
direct: 4
|
|
17
|
+
explicit: 5
|
|
18
|
+
implicit: 3
|
|
19
|
+
expansion:
|
|
20
|
+
semantic_hops: 2
|
|
21
|
+
include_problem_links: true
|
|
22
|
+
include_fact_update_links: true
|
|
23
|
+
include_association_links: true
|
|
24
|
+
max_association_depth: 2
|
|
25
|
+
min_association_strength: 0.25
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# This file defines runtime defaults for database connectivity and execution mode.
|
|
2
|
+
database:
|
|
3
|
+
dsn_env: SHELLBRAIN_DB_DSN
|
|
4
|
+
admin_dsn_env: SHELLBRAIN_DB_ADMIN_DSN
|
|
5
|
+
cli:
|
|
6
|
+
default_mode: targeted
|
|
7
|
+
include_global: true
|
|
8
|
+
embeddings:
|
|
9
|
+
provider: sentence_transformers
|
|
10
|
+
model: all-MiniLM-L6-v2
|
app/config/loader.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""This module defines YAML-backed configuration loading for policy and runtime settings."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from app.core.interfaces.config import IConfigProvider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class YamlConfigProvider(IConfigProvider):
|
|
13
|
+
"""This class loads and serves configuration sections from YAML files."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, defaults_dir: Path) -> None:
|
|
16
|
+
"""This method loads all default YAML config files from a directory."""
|
|
17
|
+
|
|
18
|
+
self._defaults_dir = defaults_dir
|
|
19
|
+
self._read_policy = self._load_yaml("read_policy.yaml")
|
|
20
|
+
self._create_policy = self._load_yaml("create_policy.yaml")
|
|
21
|
+
self._update_policy = self._load_yaml("update_policy.yaml")
|
|
22
|
+
self._thresholds = self._load_yaml("thresholds.yaml")
|
|
23
|
+
self._runtime = self._load_yaml("runtime.yaml")
|
|
24
|
+
|
|
25
|
+
def _load_yaml(self, filename: str) -> dict[str, Any]:
|
|
26
|
+
"""This method parses a YAML file into a dictionary."""
|
|
27
|
+
|
|
28
|
+
path = self._defaults_dir / filename
|
|
29
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
30
|
+
data = yaml.safe_load(handle) or {}
|
|
31
|
+
if not isinstance(data, dict):
|
|
32
|
+
raise ValueError(f"Expected mapping in {path}")
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
def get_read_policy(self) -> dict[str, Any]:
|
|
36
|
+
"""This method returns read-policy configuration values."""
|
|
37
|
+
|
|
38
|
+
return deepcopy(self._read_policy)
|
|
39
|
+
|
|
40
|
+
def get_create_policy(self) -> dict[str, Any]:
|
|
41
|
+
"""This method returns create-policy configuration values."""
|
|
42
|
+
|
|
43
|
+
return deepcopy(self._create_policy)
|
|
44
|
+
|
|
45
|
+
def get_update_policy(self) -> dict[str, Any]:
|
|
46
|
+
"""This method returns update-policy configuration values."""
|
|
47
|
+
|
|
48
|
+
return deepcopy(self._update_policy)
|
|
49
|
+
|
|
50
|
+
def get_thresholds(self) -> dict[str, Any]:
|
|
51
|
+
"""This method returns threshold configuration values."""
|
|
52
|
+
|
|
53
|
+
return deepcopy(self._thresholds)
|
|
54
|
+
|
|
55
|
+
def get_runtime(self) -> dict[str, Any]:
|
|
56
|
+
"""This method returns runtime configuration values."""
|
|
57
|
+
|
|
58
|
+
return deepcopy(self._runtime)
|
app/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This package contains pure core domain logic and contracts."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This package defines strict request, response, and error contracts."""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""This module defines structured error contracts and canonical error codes."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorCode(str, Enum):
|
|
9
|
+
"""This enum defines canonical error codes across validation and execution layers."""
|
|
10
|
+
|
|
11
|
+
SCHEMA_ERROR = "schema_error"
|
|
12
|
+
SEMANTIC_ERROR = "semantic_error"
|
|
13
|
+
INTEGRITY_ERROR = "integrity_error"
|
|
14
|
+
NOT_FOUND = "not_found"
|
|
15
|
+
CONFLICT = "conflict"
|
|
16
|
+
INTERNAL_ERROR = "internal_error"
|
|
17
|
+
HOST_IDENTITY_UNAVAILABLE = "host_identity_unavailable"
|
|
18
|
+
HOST_IDENTITY_UNSUPPORTED = "host_identity_unsupported"
|
|
19
|
+
HOST_IDENTITY_DRIFTED = "host_identity_drifted"
|
|
20
|
+
HOST_HOOK_MISSING = "host_hook_missing"
|
|
21
|
+
TRANSCRIPT_SOURCE_NOT_FOUND = "transcript_source_not_found"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ErrorDetail(BaseModel):
|
|
25
|
+
"""This model defines a structured error payload entry."""
|
|
26
|
+
|
|
27
|
+
code: ErrorCode
|
|
28
|
+
message: str
|
|
29
|
+
field: str | None = None
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""This module defines strict internal request contracts for create, read, and update operations."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StrictBaseModel(BaseModel):
|
|
9
|
+
"""This base model enforces strict schemas by rejecting unknown fields."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(extra="forbid")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReadExpandRequest(StrictBaseModel):
|
|
15
|
+
"""This model defines expansion knobs for read requests."""
|
|
16
|
+
|
|
17
|
+
semantic_hops: int | None = Field(default=None, ge=0, le=3)
|
|
18
|
+
include_problem_links: bool | None = None
|
|
19
|
+
include_fact_update_links: bool | None = None
|
|
20
|
+
include_association_links: bool | None = None
|
|
21
|
+
max_association_depth: int | None = Field(default=None, ge=1, le=4)
|
|
22
|
+
min_association_strength: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MemoryReadRequest(StrictBaseModel):
|
|
26
|
+
"""This model defines the canonical read request payload."""
|
|
27
|
+
|
|
28
|
+
op: Literal["read"] = "read"
|
|
29
|
+
repo_id: str
|
|
30
|
+
mode: Literal["ambient", "targeted"]
|
|
31
|
+
query: str = Field(min_length=1)
|
|
32
|
+
include_global: bool | None = None
|
|
33
|
+
kinds: (
|
|
34
|
+
list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None
|
|
35
|
+
) = None
|
|
36
|
+
limit: int | None = Field(default=None, ge=1, le=100)
|
|
37
|
+
expand: ReadExpandRequest | None = None
|
|
38
|
+
|
|
39
|
+
@field_validator("kinds")
|
|
40
|
+
@classmethod
|
|
41
|
+
def _validate_kinds_unique(
|
|
42
|
+
cls,
|
|
43
|
+
value: list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None,
|
|
44
|
+
) -> list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None:
|
|
45
|
+
"""This validator enforces unique kinds filters for read requests."""
|
|
46
|
+
|
|
47
|
+
if value is None:
|
|
48
|
+
return value
|
|
49
|
+
if len(value) != len(set(value)):
|
|
50
|
+
raise ValueError("kinds must be unique")
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class EpisodeEventsRequest(StrictBaseModel):
|
|
55
|
+
"""This model defines the canonical episode-events request payload."""
|
|
56
|
+
|
|
57
|
+
op: Literal["events"] = "events"
|
|
58
|
+
repo_id: str
|
|
59
|
+
limit: int = Field(default=20, ge=1, le=100)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MemoryCreateAssociationLink(StrictBaseModel):
|
|
63
|
+
"""This model defines a typed explicit association link payload on create."""
|
|
64
|
+
|
|
65
|
+
to_memory_id: str
|
|
66
|
+
relation_type: Literal["depends_on", "associated_with"]
|
|
67
|
+
confidence: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
68
|
+
salience: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
69
|
+
rationale: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MemoryCreateLinks(StrictBaseModel):
|
|
73
|
+
"""This model defines optional link payloads on create requests."""
|
|
74
|
+
|
|
75
|
+
problem_id: str | None = None
|
|
76
|
+
related_memory_ids: list[str] = Field(default_factory=list)
|
|
77
|
+
associations: list[MemoryCreateAssociationLink] = Field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
@field_validator("related_memory_ids")
|
|
80
|
+
@classmethod
|
|
81
|
+
def _validate_related_unique(cls, value: list[str]) -> list[str]:
|
|
82
|
+
"""This validator enforces unique related-shellbrain references."""
|
|
83
|
+
|
|
84
|
+
if len(value) != len(set(value)):
|
|
85
|
+
raise ValueError("related_memory_ids must be unique")
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class MemoryCreateBody(StrictBaseModel):
|
|
90
|
+
"""This model defines create-body fields for immutable shellbrain records."""
|
|
91
|
+
|
|
92
|
+
text: str
|
|
93
|
+
scope: Literal["repo", "global"]
|
|
94
|
+
kind: Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]
|
|
95
|
+
rationale: str | None = None
|
|
96
|
+
links: MemoryCreateLinks = Field(default_factory=MemoryCreateLinks)
|
|
97
|
+
evidence_refs: list[str] = Field(min_length=1)
|
|
98
|
+
|
|
99
|
+
@field_validator("evidence_refs")
|
|
100
|
+
@classmethod
|
|
101
|
+
def _validate_evidence_unique(cls, value: list[str]) -> list[str]:
|
|
102
|
+
"""This validator enforces unique evidence references."""
|
|
103
|
+
|
|
104
|
+
if len(value) != len(set(value)):
|
|
105
|
+
raise ValueError("evidence_refs must be unique")
|
|
106
|
+
return value
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class MemoryCreateRequest(StrictBaseModel):
|
|
110
|
+
"""This model defines the canonical create request payload."""
|
|
111
|
+
|
|
112
|
+
op: Literal["create"] = "create"
|
|
113
|
+
repo_id: str
|
|
114
|
+
memory: MemoryCreateBody
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ArchiveStateUpdate(StrictBaseModel):
|
|
118
|
+
"""This model defines archive-state update payload fields."""
|
|
119
|
+
|
|
120
|
+
type: Literal["archive_state"]
|
|
121
|
+
archived: bool
|
|
122
|
+
rationale: str | None = None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class UtilityVoteUpdate(StrictBaseModel):
|
|
126
|
+
"""This model defines utility-vote update payload fields."""
|
|
127
|
+
|
|
128
|
+
type: Literal["utility_vote"]
|
|
129
|
+
problem_id: str
|
|
130
|
+
vote: float = Field(ge=-1.0, le=1.0)
|
|
131
|
+
rationale: str | None = None
|
|
132
|
+
evidence_refs: list[str] = Field(default_factory=list)
|
|
133
|
+
|
|
134
|
+
@field_validator("evidence_refs")
|
|
135
|
+
@classmethod
|
|
136
|
+
def _validate_evidence_unique(cls, value: list[str]) -> list[str]:
|
|
137
|
+
"""This validator enforces unique utility evidence references."""
|
|
138
|
+
|
|
139
|
+
if len(value) != len(set(value)):
|
|
140
|
+
raise ValueError("evidence_refs must be unique")
|
|
141
|
+
return value
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class FactUpdateLinkUpdate(StrictBaseModel):
|
|
145
|
+
"""This model defines fact-update-link payload fields."""
|
|
146
|
+
|
|
147
|
+
type: Literal["fact_update_link"]
|
|
148
|
+
old_fact_id: str
|
|
149
|
+
new_fact_id: str
|
|
150
|
+
rationale: str | None = None
|
|
151
|
+
evidence_refs: list[str] = Field(default_factory=list)
|
|
152
|
+
|
|
153
|
+
@field_validator("evidence_refs")
|
|
154
|
+
@classmethod
|
|
155
|
+
def _validate_evidence_unique(cls, value: list[str]) -> list[str]:
|
|
156
|
+
"""This validator enforces unique fact-update evidence references."""
|
|
157
|
+
|
|
158
|
+
if len(value) != len(set(value)):
|
|
159
|
+
raise ValueError("evidence_refs must be unique")
|
|
160
|
+
return value
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class AssociationLinkUpdate(StrictBaseModel):
|
|
164
|
+
"""This model defines association-link update payload fields."""
|
|
165
|
+
|
|
166
|
+
type: Literal["association_link"]
|
|
167
|
+
to_memory_id: str
|
|
168
|
+
relation_type: Literal["depends_on", "associated_with"]
|
|
169
|
+
confidence: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
170
|
+
salience: float | None = Field(default=None, ge=0.0, le=1.0)
|
|
171
|
+
rationale: str | None = None
|
|
172
|
+
evidence_refs: list[str] = Field(min_length=1)
|
|
173
|
+
|
|
174
|
+
@field_validator("evidence_refs")
|
|
175
|
+
@classmethod
|
|
176
|
+
def _validate_evidence_unique(cls, value: list[str]) -> list[str]:
|
|
177
|
+
"""This validator enforces unique association evidence references."""
|
|
178
|
+
|
|
179
|
+
if len(value) != len(set(value)):
|
|
180
|
+
raise ValueError("evidence_refs must be unique")
|
|
181
|
+
return value
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
UpdatePayload = Annotated[
|
|
185
|
+
ArchiveStateUpdate | UtilityVoteUpdate | FactUpdateLinkUpdate | AssociationLinkUpdate,
|
|
186
|
+
Field(discriminator="type"),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class MemoryUpdateRequest(StrictBaseModel):
|
|
191
|
+
"""This model defines the canonical update request payload."""
|
|
192
|
+
|
|
193
|
+
op: Literal["update"] = "update"
|
|
194
|
+
repo_id: str
|
|
195
|
+
memory_id: str
|
|
196
|
+
update: UpdatePayload
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class BatchUtilityVoteItem(StrictBaseModel):
|
|
200
|
+
"""One utility-vote update entry inside a batch update request."""
|
|
201
|
+
|
|
202
|
+
memory_id: str
|
|
203
|
+
update: UtilityVoteUpdate
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class MemoryBatchUpdateRequest(StrictBaseModel):
|
|
207
|
+
"""This model defines the canonical batch utility-update payload."""
|
|
208
|
+
|
|
209
|
+
op: Literal["update"] = "update"
|
|
210
|
+
repo_id: str
|
|
211
|
+
updates: list[BatchUtilityVoteItem] = Field(min_length=1)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""This module defines standardized response envelopes for operation results."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from app.core.contracts.errors import ErrorDetail
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OperationResult(BaseModel):
|
|
11
|
+
"""This model defines a deterministic response envelope for all operations."""
|
|
12
|
+
|
|
13
|
+
status: Literal["ok", "error"]
|
|
14
|
+
data: dict[str, Any] = Field(default_factory=dict)
|
|
15
|
+
errors: list[ErrorDetail] = Field(default_factory=list)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This package defines domain entities used throughout the core."""
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""This module defines formal association entities and association metadata enums."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AssociationRelationType(str, Enum):
|
|
8
|
+
"""This enum defines ratified formal association relation types."""
|
|
9
|
+
|
|
10
|
+
DEPENDS_ON = "depends_on"
|
|
11
|
+
ASSOCIATED_WITH = "associated_with"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AssociationSourceMode(str, Enum):
|
|
15
|
+
"""This enum defines whether an association comes from agent or implicit channels."""
|
|
16
|
+
|
|
17
|
+
AGENT = "agent"
|
|
18
|
+
IMPLICIT = "implicit"
|
|
19
|
+
MIXED = "mixed"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AssociationState(str, Enum):
|
|
23
|
+
"""This enum defines the lifecycle state of an association edge."""
|
|
24
|
+
|
|
25
|
+
TENTATIVE = "tentative"
|
|
26
|
+
CONFIRMED = "confirmed"
|
|
27
|
+
DEPRECATED = "deprecated"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(kw_only=True)
|
|
31
|
+
class AssociationEdge:
|
|
32
|
+
"""This dataclass models a formal association edge between two memories."""
|
|
33
|
+
|
|
34
|
+
id: str
|
|
35
|
+
repo_id: str
|
|
36
|
+
from_memory_id: str
|
|
37
|
+
to_memory_id: str
|
|
38
|
+
relation_type: AssociationRelationType
|
|
39
|
+
source_mode: AssociationSourceMode = AssociationSourceMode.AGENT
|
|
40
|
+
state: AssociationState = AssociationState.TENTATIVE
|
|
41
|
+
strength: float = 0.0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(kw_only=True)
|
|
45
|
+
class AssociationObservation:
|
|
46
|
+
"""This dataclass models an immutable reinforcement observation for associations."""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
repo_id: str
|
|
50
|
+
from_memory_id: str
|
|
51
|
+
to_memory_id: str
|
|
52
|
+
relation_type: AssociationRelationType
|
|
53
|
+
source: str
|
|
54
|
+
valence: float
|
|
55
|
+
salience: float = 0.5
|
|
56
|
+
edge_id: str | None = None
|
|
57
|
+
problem_id: str | None = None
|
|
58
|
+
episode_id: str | None = None
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""This module defines episodic session entities and transfer provenance entities."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EpisodeStatus(str, Enum):
|
|
9
|
+
"""This enum defines lifecycle status values for episodes."""
|
|
10
|
+
|
|
11
|
+
ACTIVE = "active"
|
|
12
|
+
CLOSED = "closed"
|
|
13
|
+
ARCHIVED = "archived"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EpisodeEventSource(str, Enum):
|
|
17
|
+
"""This enum defines allowed source values for episode events."""
|
|
18
|
+
|
|
19
|
+
USER = "user"
|
|
20
|
+
ASSISTANT = "assistant"
|
|
21
|
+
TOOL = "tool"
|
|
22
|
+
SYSTEM = "system"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(kw_only=True)
|
|
26
|
+
class Episode:
|
|
27
|
+
"""This dataclass models a work-session container record."""
|
|
28
|
+
|
|
29
|
+
id: str
|
|
30
|
+
repo_id: str
|
|
31
|
+
host_app: str
|
|
32
|
+
thread_id: str | None = None
|
|
33
|
+
title: str | None = None
|
|
34
|
+
objective: str | None = None
|
|
35
|
+
status: EpisodeStatus = EpisodeStatus.ACTIVE
|
|
36
|
+
started_at: datetime | None = None
|
|
37
|
+
ended_at: datetime | None = None
|
|
38
|
+
created_at: datetime | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(kw_only=True)
|
|
42
|
+
class EpisodeEvent:
|
|
43
|
+
"""This dataclass models a single immutable event inside an episode."""
|
|
44
|
+
|
|
45
|
+
id: str
|
|
46
|
+
episode_id: str
|
|
47
|
+
seq: int
|
|
48
|
+
host_event_key: str
|
|
49
|
+
source: EpisodeEventSource
|
|
50
|
+
content: str
|
|
51
|
+
created_at: datetime | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(kw_only=True)
|
|
55
|
+
class SessionTransfer:
|
|
56
|
+
"""This dataclass models immutable cross-session transfer provenance."""
|
|
57
|
+
|
|
58
|
+
id: str
|
|
59
|
+
repo_id: str
|
|
60
|
+
from_episode_id: str
|
|
61
|
+
to_episode_id: str
|
|
62
|
+
event_id: str
|
|
63
|
+
transfer_kind: str
|
|
64
|
+
rationale: str | None = None
|
|
65
|
+
transferred_by: str | None = None
|
|
66
|
+
created_at: datetime | None = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""This module defines evidence reference entities and evidence link entities."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(kw_only=True)
|
|
7
|
+
class EvidenceRef:
|
|
8
|
+
"""This dataclass models a canonical evidence reference entry."""
|
|
9
|
+
|
|
10
|
+
id: str
|
|
11
|
+
repo_id: str
|
|
12
|
+
ref: str
|
|
13
|
+
episode_event_id: str | None = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(kw_only=True)
|
|
17
|
+
class MemoryEvidenceLink:
|
|
18
|
+
"""This dataclass models a many-to-many link between shellbrain and evidence."""
|
|
19
|
+
|
|
20
|
+
memory_id: str
|
|
21
|
+
evidence_id: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(kw_only=True)
|
|
25
|
+
class AssociationEdgeEvidenceLink:
|
|
26
|
+
"""This dataclass models a many-to-many link between association edges and evidence."""
|
|
27
|
+
|
|
28
|
+
edge_id: str
|
|
29
|
+
evidence_id: str
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""This module defines fact-chain and problem-attempt linkage entities."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProblemAttemptRole(str, Enum):
|
|
8
|
+
"""This enum defines the role of an attempt linked to a problem."""
|
|
9
|
+
|
|
10
|
+
SOLUTION = "solution"
|
|
11
|
+
FAILED_TACTIC = "failed_tactic"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(kw_only=True)
|
|
15
|
+
class ProblemAttempt:
|
|
16
|
+
"""This dataclass models a direct problem-to-attempt link."""
|
|
17
|
+
|
|
18
|
+
problem_id: str
|
|
19
|
+
attempt_id: str
|
|
20
|
+
role: ProblemAttemptRole
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(kw_only=True)
|
|
24
|
+
class FactUpdate:
|
|
25
|
+
"""This dataclass models an immutable fact-update chain record."""
|
|
26
|
+
|
|
27
|
+
id: str
|
|
28
|
+
old_fact_id: str
|
|
29
|
+
change_id: str
|
|
30
|
+
new_fact_id: str
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Guidance entities used to nudge agents without adding new top-level commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, kw_only=True)
|
|
10
|
+
class PendingUtilityCandidate:
|
|
11
|
+
"""One memory that still appears to need a utility vote for the active problem."""
|
|
12
|
+
|
|
13
|
+
memory_id: str
|
|
14
|
+
kind: str
|
|
15
|
+
retrieval_count: int
|
|
16
|
+
last_seen_at: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, kw_only=True)
|
|
20
|
+
class GuidanceDecision:
|
|
21
|
+
"""One guidance item that may be attached to a successful operation result."""
|
|
22
|
+
|
|
23
|
+
code: str
|
|
24
|
+
severity: str
|
|
25
|
+
message: str
|
|
26
|
+
problem_id: str | None = None
|
|
27
|
+
memory_ids: list[str] = field(default_factory=list)
|
|
28
|
+
vote_scale_hint: dict[str, float] | None = None
|
|
29
|
+
setup_hint: str | None = None
|
|
30
|
+
|
|
31
|
+
def to_payload(self) -> dict[str, Any]:
|
|
32
|
+
"""Serialize one guidance decision into the public response shape."""
|
|
33
|
+
|
|
34
|
+
payload: dict[str, Any] = {
|
|
35
|
+
"code": self.code,
|
|
36
|
+
"severity": self.severity,
|
|
37
|
+
"message": self.message,
|
|
38
|
+
}
|
|
39
|
+
if self.problem_id is not None:
|
|
40
|
+
payload["problem_id"] = self.problem_id
|
|
41
|
+
if self.memory_ids:
|
|
42
|
+
payload["memory_ids"] = list(self.memory_ids)
|
|
43
|
+
if self.vote_scale_hint is not None:
|
|
44
|
+
payload["vote_scale_hint"] = dict(self.vote_scale_hint)
|
|
45
|
+
if self.setup_hint is not None:
|
|
46
|
+
payload["setup_hint"] = self.setup_hint
|
|
47
|
+
return payload
|