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.
Files changed (165) hide show
  1. app/__init__.py +1 -0
  2. app/__main__.py +7 -0
  3. app/boot/__init__.py +1 -0
  4. app/boot/admin_db.py +88 -0
  5. app/boot/config.py +14 -0
  6. app/boot/create_policy.py +52 -0
  7. app/boot/db.py +70 -0
  8. app/boot/embeddings.py +55 -0
  9. app/boot/home.py +45 -0
  10. app/boot/migrations.py +61 -0
  11. app/boot/read_policy.py +179 -0
  12. app/boot/repos.py +15 -0
  13. app/boot/retrieval.py +3 -0
  14. app/boot/thresholds.py +19 -0
  15. app/boot/update_policy.py +34 -0
  16. app/boot/use_cases.py +22 -0
  17. app/config/__init__.py +1 -0
  18. app/config/defaults/create_policy.yaml +7 -0
  19. app/config/defaults/read_policy.yaml +25 -0
  20. app/config/defaults/runtime.yaml +10 -0
  21. app/config/defaults/thresholds.yaml +3 -0
  22. app/config/defaults/update_policy.yaml +5 -0
  23. app/config/loader.py +58 -0
  24. app/core/__init__.py +1 -0
  25. app/core/contracts/__init__.py +1 -0
  26. app/core/contracts/errors.py +29 -0
  27. app/core/contracts/requests.py +211 -0
  28. app/core/contracts/responses.py +15 -0
  29. app/core/entities/__init__.py +1 -0
  30. app/core/entities/associations.py +58 -0
  31. app/core/entities/episodes.py +66 -0
  32. app/core/entities/evidence.py +29 -0
  33. app/core/entities/facts.py +30 -0
  34. app/core/entities/guidance.py +47 -0
  35. app/core/entities/identity.py +48 -0
  36. app/core/entities/memory.py +34 -0
  37. app/core/entities/runtime_context.py +19 -0
  38. app/core/entities/session_state.py +31 -0
  39. app/core/entities/telemetry.py +152 -0
  40. app/core/entities/utility.py +14 -0
  41. app/core/interfaces/__init__.py +1 -0
  42. app/core/interfaces/clock.py +12 -0
  43. app/core/interfaces/config.py +28 -0
  44. app/core/interfaces/embeddings.py +12 -0
  45. app/core/interfaces/idgen.py +11 -0
  46. app/core/interfaces/repos.py +279 -0
  47. app/core/interfaces/retrieval.py +20 -0
  48. app/core/interfaces/session_state_store.py +33 -0
  49. app/core/interfaces/unit_of_work.py +50 -0
  50. app/core/policies/__init__.py +1 -0
  51. app/core/policies/_shared/__init__.py +1 -0
  52. app/core/policies/_shared/executor.py +132 -0
  53. app/core/policies/_shared/side_effects.py +9 -0
  54. app/core/policies/create_policy/__init__.py +1 -0
  55. app/core/policies/create_policy/pipeline.py +96 -0
  56. app/core/policies/read_policy/__init__.py +1 -0
  57. app/core/policies/read_policy/bm25.py +114 -0
  58. app/core/policies/read_policy/context_pack_builder.py +140 -0
  59. app/core/policies/read_policy/expansion.py +132 -0
  60. app/core/policies/read_policy/fusion_rrf.py +34 -0
  61. app/core/policies/read_policy/lexical_query.py +101 -0
  62. app/core/policies/read_policy/pipeline.py +93 -0
  63. app/core/policies/read_policy/scenario_lift.py +11 -0
  64. app/core/policies/read_policy/scoring.py +61 -0
  65. app/core/policies/read_policy/seed_retrieval.py +54 -0
  66. app/core/policies/read_policy/utility_prior.py +11 -0
  67. app/core/policies/update_policy/__init__.py +1 -0
  68. app/core/policies/update_policy/pipeline.py +80 -0
  69. app/core/use_cases/__init__.py +1 -0
  70. app/core/use_cases/build_guidance.py +85 -0
  71. app/core/use_cases/create_memory.py +26 -0
  72. app/core/use_cases/manage_session_state.py +159 -0
  73. app/core/use_cases/read_memory.py +21 -0
  74. app/core/use_cases/record_episode_sync_telemetry.py +19 -0
  75. app/core/use_cases/record_operation_telemetry.py +32 -0
  76. app/core/use_cases/sync_episode.py +162 -0
  77. app/core/use_cases/update_memory.py +40 -0
  78. app/migrations/__init__.py +1 -0
  79. app/migrations/env.py +65 -0
  80. app/migrations/versions/20260226_0001_initial_schema.py +232 -0
  81. app/migrations/versions/20260312_0002_add_hard_invariants.py +60 -0
  82. app/migrations/versions/20260312_0003_drop_create_confidence.py +40 -0
  83. app/migrations/versions/20260313_0004_episode_sync_hardening.py +71 -0
  84. app/migrations/versions/20260313_0005_evidence_episode_event_refs.py +45 -0
  85. app/migrations/versions/20260318_0006_usage_telemetry_schema.py +175 -0
  86. app/migrations/versions/20260319_0007_identity_session_guidance.py +49 -0
  87. app/migrations/versions/20260320_0008_instance_metadata_and_backup_safety.py +31 -0
  88. app/migrations/versions/__init__.py +1 -0
  89. app/periphery/__init__.py +1 -0
  90. app/periphery/admin/__init__.py +1 -0
  91. app/periphery/admin/backup.py +360 -0
  92. app/periphery/admin/destructive_guard.py +32 -0
  93. app/periphery/admin/doctor.py +192 -0
  94. app/periphery/admin/init.py +996 -0
  95. app/periphery/admin/instance_guard.py +211 -0
  96. app/periphery/admin/machine_state.py +354 -0
  97. app/periphery/admin/privileges.py +42 -0
  98. app/periphery/admin/repo_state.py +266 -0
  99. app/periphery/admin/restore.py +30 -0
  100. app/periphery/cli/__init__.py +1 -0
  101. app/periphery/cli/handlers.py +830 -0
  102. app/periphery/cli/hydration.py +119 -0
  103. app/periphery/cli/main.py +710 -0
  104. app/periphery/cli/presenter_json.py +10 -0
  105. app/periphery/cli/schema_validation.py +201 -0
  106. app/periphery/db/__init__.py +1 -0
  107. app/periphery/db/engine.py +10 -0
  108. app/periphery/db/models/__init__.py +1 -0
  109. app/periphery/db/models/associations.py +55 -0
  110. app/periphery/db/models/episodes.py +55 -0
  111. app/periphery/db/models/evidence.py +19 -0
  112. app/periphery/db/models/experiences.py +33 -0
  113. app/periphery/db/models/instance_metadata.py +17 -0
  114. app/periphery/db/models/memories.py +39 -0
  115. app/periphery/db/models/metadata.py +6 -0
  116. app/periphery/db/models/registry.py +18 -0
  117. app/periphery/db/models/telemetry.py +174 -0
  118. app/periphery/db/models/utility.py +19 -0
  119. app/periphery/db/models/views.py +154 -0
  120. app/periphery/db/repos/__init__.py +1 -0
  121. app/periphery/db/repos/relational/__init__.py +1 -0
  122. app/periphery/db/repos/relational/associations_repo.py +117 -0
  123. app/periphery/db/repos/relational/episodes_repo.py +188 -0
  124. app/periphery/db/repos/relational/evidence_repo.py +82 -0
  125. app/periphery/db/repos/relational/experiences_repo.py +41 -0
  126. app/periphery/db/repos/relational/memories_repo.py +99 -0
  127. app/periphery/db/repos/relational/read_policy_repo.py +202 -0
  128. app/periphery/db/repos/relational/telemetry_repo.py +161 -0
  129. app/periphery/db/repos/relational/utility_repo.py +30 -0
  130. app/periphery/db/repos/semantic/__init__.py +1 -0
  131. app/periphery/db/repos/semantic/keyword_retrieval_repo.py +63 -0
  132. app/periphery/db/repos/semantic/semantic_retrieval_repo.py +111 -0
  133. app/periphery/db/session.py +10 -0
  134. app/periphery/db/uow.py +75 -0
  135. app/periphery/embeddings/__init__.py +1 -0
  136. app/periphery/embeddings/local_provider.py +35 -0
  137. app/periphery/embeddings/query_vector_search.py +18 -0
  138. app/periphery/episodes/__init__.py +1 -0
  139. app/periphery/episodes/claude_code.py +387 -0
  140. app/periphery/episodes/codex.py +423 -0
  141. app/periphery/episodes/launcher.py +66 -0
  142. app/periphery/episodes/normalization.py +31 -0
  143. app/periphery/episodes/poller.py +299 -0
  144. app/periphery/episodes/source_discovery.py +66 -0
  145. app/periphery/episodes/tool_filter.py +165 -0
  146. app/periphery/identity/__init__.py +1 -0
  147. app/periphery/identity/claude_hook_install.py +67 -0
  148. app/periphery/identity/claude_runtime.py +83 -0
  149. app/periphery/identity/codex_runtime.py +32 -0
  150. app/periphery/identity/compatibility.py +38 -0
  151. app/periphery/identity/resolver.py +163 -0
  152. app/periphery/session_state/__init__.py +1 -0
  153. app/periphery/session_state/file_store.py +100 -0
  154. app/periphery/telemetry/__init__.py +33 -0
  155. app/periphery/telemetry/operation_summary.py +299 -0
  156. app/periphery/telemetry/session_selection.py +156 -0
  157. app/periphery/telemetry/sync_summary.py +65 -0
  158. app/periphery/validation/__init__.py +1 -0
  159. app/periphery/validation/integrity_validation.py +253 -0
  160. app/periphery/validation/semantic_validation.py +94 -0
  161. shellbrain-0.1.0.dist-info/METADATA +130 -0
  162. shellbrain-0.1.0.dist-info/RECORD +165 -0
  163. shellbrain-0.1.0.dist-info/WHEEL +5 -0
  164. shellbrain-0.1.0.dist-info/entry_points.txt +2 -0
  165. 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,7 @@
1
+ # This file defines default create-policy controls for deterministic side-effect planning.
2
+ gates:
3
+ - schema
4
+ - semantic
5
+ - integrity
6
+ defaults:
7
+ scope: repo
@@ -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
@@ -0,0 +1,3 @@
1
+ # This file defines threshold defaults for semantic and lexical retrieval gates.
2
+ semantic_threshold: 0.0
3
+ keyword_threshold: 0.0
@@ -0,0 +1,5 @@
1
+ # This file defines default update-policy controls for deterministic side-effect planning.
2
+ gates:
3
+ - schema
4
+ - semantic
5
+ - integrity
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