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
@@ -0,0 +1,10 @@
1
+ """This module defines deterministic JSON presentation helpers for CLI responses."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ def render(payload: dict[str, Any]) -> str:
8
+ """This function renders payloads as deterministic compact JSON output."""
9
+
10
+ return json.dumps(payload, separators=(",", ":"))
@@ -0,0 +1,201 @@
1
+ """CLI schema-validation helpers for strict request contracts."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from pydantic import Field, ValidationError, field_validator
6
+
7
+ from app.core.contracts.errors import ErrorCode, ErrorDetail
8
+ from app.core.contracts.requests import (
9
+ BatchUtilityVoteItem,
10
+ EpisodeEventsRequest,
11
+ MemoryBatchUpdateRequest,
12
+ MemoryCreateLinks,
13
+ MemoryCreateRequest,
14
+ MemoryReadRequest,
15
+ MemoryUpdateRequest,
16
+ StrictBaseModel,
17
+ UpdatePayload,
18
+ UtilityVoteUpdate,
19
+ )
20
+
21
+
22
+ class AgentReadRequest(StrictBaseModel):
23
+ """Agent-facing read payload with config-only retrieval knobs removed."""
24
+
25
+ op: Literal["read"] = "read"
26
+ repo_id: str | None = None
27
+ mode: Literal["ambient", "targeted"] | None = None
28
+ query: str = Field(min_length=1)
29
+ include_global: bool | None = None
30
+ kinds: (
31
+ list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None
32
+ ) = None
33
+ limit: int | None = Field(default=None, ge=1, le=100)
34
+
35
+ @field_validator("kinds")
36
+ @classmethod
37
+ def _validate_kinds_unique(
38
+ cls,
39
+ value: list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None,
40
+ ) -> list[Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]] | None:
41
+ """This validator enforces unique kinds filters for agent read requests."""
42
+
43
+ if value is None:
44
+ return value
45
+ if len(value) != len(set(value)):
46
+ raise ValueError("kinds must be unique")
47
+ return value
48
+
49
+
50
+ class AgentCreateBody(StrictBaseModel):
51
+ """Agent-facing create payload with transport fields removed."""
52
+
53
+ text: str
54
+ scope: Literal["repo", "global"] | None = None
55
+ kind: Literal["problem", "solution", "failed_tactic", "fact", "preference", "change"]
56
+ rationale: str | None = None
57
+ links: MemoryCreateLinks = Field(default_factory=MemoryCreateLinks)
58
+ evidence_refs: list[str] = Field(min_length=1)
59
+
60
+ @field_validator("evidence_refs")
61
+ @classmethod
62
+ def _validate_evidence_unique(cls, value: list[str]) -> list[str]:
63
+ """This validator enforces unique evidence references for agent create requests."""
64
+
65
+ if len(value) != len(set(value)):
66
+ raise ValueError("evidence_refs must be unique")
67
+ return value
68
+
69
+
70
+ class AgentCreateRequest(StrictBaseModel):
71
+ """Agent-facing create payload with repo/op transport details removed."""
72
+
73
+ memory: AgentCreateBody
74
+
75
+
76
+ class AgentEventsRequest(StrictBaseModel):
77
+ """Agent-facing events payload with transport fields removed."""
78
+
79
+ limit: int | None = Field(default=None, ge=1, le=100)
80
+
81
+
82
+ class AgentUpdateRequest(StrictBaseModel):
83
+ """Agent-facing update payload with repo/op transport details removed."""
84
+
85
+ memory_id: str
86
+ update: UpdatePayload
87
+
88
+
89
+ class AgentBatchUpdateItem(StrictBaseModel):
90
+ """Agent-facing batch utility item with transport fields removed."""
91
+
92
+ memory_id: str
93
+ update: UtilityVoteUpdate
94
+
95
+
96
+ class AgentBatchUpdateRequest(StrictBaseModel):
97
+ """Agent-facing batch utility update payload."""
98
+
99
+ updates: list[AgentBatchUpdateItem] = Field(min_length=1)
100
+
101
+
102
+ def _format_validation_errors(exc: ValidationError) -> list[ErrorDetail]:
103
+ """Convert Pydantic validation errors into contract error details."""
104
+
105
+ details: list[ErrorDetail] = []
106
+ for item in exc.errors():
107
+ path = ".".join(str(segment) for segment in item.get("loc", ()))
108
+ message = item.get("msg", "Schema validation failed")
109
+ if path in {"memory.evidence_refs", "update.evidence_refs"} and item.get("type") in {
110
+ "too_short",
111
+ "missing",
112
+ }:
113
+ message = "evidence_refs must include at least one stored episode event id; query events first"
114
+ details.append(
115
+ ErrorDetail(
116
+ code=ErrorCode.SCHEMA_ERROR,
117
+ message=message,
118
+ field=path or None,
119
+ )
120
+ )
121
+ return details
122
+
123
+
124
+ def validate_create_schema(payload: dict[str, Any]) -> tuple[AgentCreateRequest | None, list[ErrorDetail]]:
125
+ """Validate and parse agent create payloads into the simplified create contract."""
126
+
127
+ try:
128
+ return AgentCreateRequest.model_validate(payload), []
129
+ except ValidationError as exc:
130
+ return None, _format_validation_errors(exc)
131
+
132
+
133
+ def validate_read_schema(payload: dict[str, Any]) -> tuple[AgentReadRequest | None, list[ErrorDetail]]:
134
+ """Validate and parse agent read payloads into the simplified read contract."""
135
+
136
+ try:
137
+ return AgentReadRequest.model_validate(payload), []
138
+ except ValidationError as exc:
139
+ return None, _format_validation_errors(exc)
140
+
141
+
142
+ def validate_events_schema(payload: dict[str, Any]) -> tuple[AgentEventsRequest | None, list[ErrorDetail]]:
143
+ """Validate and parse agent events payloads into the simplified events contract."""
144
+
145
+ try:
146
+ return AgentEventsRequest.model_validate(payload), []
147
+ except ValidationError as exc:
148
+ return None, _format_validation_errors(exc)
149
+
150
+
151
+ def validate_internal_read_contract(payload: dict[str, Any]) -> tuple[MemoryReadRequest | None, list[ErrorDetail]]:
152
+ """Validate one hydrated read payload against the full internal read contract."""
153
+
154
+ try:
155
+ return MemoryReadRequest.model_validate(payload), []
156
+ except ValidationError as exc:
157
+ return None, _format_validation_errors(exc)
158
+
159
+
160
+ def validate_internal_events_contract(
161
+ payload: dict[str, Any],
162
+ ) -> tuple[EpisodeEventsRequest | None, list[ErrorDetail]]:
163
+ """Validate one hydrated events payload against the full internal events contract."""
164
+
165
+ try:
166
+ return EpisodeEventsRequest.model_validate(payload), []
167
+ except ValidationError as exc:
168
+ return None, _format_validation_errors(exc)
169
+
170
+
171
+ def validate_internal_create_contract(payload: dict[str, Any]) -> tuple[MemoryCreateRequest | None, list[ErrorDetail]]:
172
+ """Validate one hydrated create payload against the full internal create contract."""
173
+
174
+ try:
175
+ return MemoryCreateRequest.model_validate(payload), []
176
+ except ValidationError as exc:
177
+ return None, _format_validation_errors(exc)
178
+
179
+
180
+ def validate_update_schema(payload: dict[str, Any]) -> tuple[AgentUpdateRequest | None, list[ErrorDetail]]:
181
+ """Validate and parse agent update payloads into the simplified update contract."""
182
+
183
+ try:
184
+ if "updates" in payload:
185
+ return AgentBatchUpdateRequest.model_validate(payload), []
186
+ return AgentUpdateRequest.model_validate(payload), []
187
+ except ValidationError as exc:
188
+ return None, _format_validation_errors(exc)
189
+
190
+
191
+ def validate_internal_update_contract(
192
+ payload: dict[str, Any],
193
+ ) -> tuple[MemoryUpdateRequest | MemoryBatchUpdateRequest | None, list[ErrorDetail]]:
194
+ """Validate one hydrated update payload against the full internal update contract."""
195
+
196
+ try:
197
+ if "updates" in payload:
198
+ return MemoryBatchUpdateRequest.model_validate(payload), []
199
+ return MemoryUpdateRequest.model_validate(payload), []
200
+ except ValidationError as exc:
201
+ return None, _format_validation_errors(exc)
@@ -0,0 +1 @@
1
+ """This package contains database models, repositories, and transaction wiring."""
@@ -0,0 +1,10 @@
1
+ """This module defines PostgreSQL engine construction helpers for application boot wiring."""
2
+
3
+ from sqlalchemy import create_engine
4
+ from sqlalchemy.engine import Engine
5
+
6
+
7
+ def get_engine(dsn: str) -> Engine:
8
+ """This function creates a SQLAlchemy engine for the provided PostgreSQL DSN."""
9
+
10
+ return create_engine(dsn, future=True)
@@ -0,0 +1 @@
1
+ """This package defines SQLAlchemy Core table and view metadata."""
@@ -0,0 +1,55 @@
1
+ """This module defines SQLAlchemy Core tables for association edges and observations."""
2
+
3
+ from sqlalchemy import CheckConstraint, Column, Float, ForeignKey, Integer, String, Table, UniqueConstraint
4
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ association_edges = Table(
10
+ "association_edges",
11
+ metadata,
12
+ Column("id", String, primary_key=True),
13
+ Column("repo_id", String, nullable=False),
14
+ Column("from_memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
15
+ Column("to_memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
16
+ Column("relation_type", String, nullable=False),
17
+ Column("source_mode", String, nullable=False),
18
+ Column("state", String, nullable=False),
19
+ Column("strength", Float, nullable=False, default=0.0),
20
+ Column("obs_count", Integer, nullable=False, default=0),
21
+ Column("positive_obs", Integer, nullable=False, default=0),
22
+ Column("negative_obs", Integer, nullable=False, default=0),
23
+ Column("salience_sum", Float, nullable=False, default=0.0),
24
+ Column("last_reinforced_at", TIMESTAMP(timezone=True)),
25
+ Column("last_used_at", TIMESTAMP(timezone=True)),
26
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
27
+ Column("updated_at", TIMESTAMP(timezone=True), nullable=False),
28
+ CheckConstraint("from_memory_id <> to_memory_id", name="ck_association_edges_no_self_loop"),
29
+ UniqueConstraint("repo_id", "from_memory_id", "to_memory_id", "relation_type", name="uq_association_edges_pair"),
30
+ )
31
+
32
+ association_observations = Table(
33
+ "association_observations",
34
+ metadata,
35
+ Column("id", String, primary_key=True),
36
+ Column("repo_id", String, nullable=False),
37
+ Column("edge_id", String, ForeignKey("association_edges.id", ondelete="CASCADE")),
38
+ Column("from_memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
39
+ Column("to_memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
40
+ Column("relation_type", String, nullable=False),
41
+ Column("source", String, nullable=False),
42
+ Column("problem_id", String, ForeignKey("memories.id", ondelete="SET NULL")),
43
+ Column("episode_id", String, ForeignKey("episodes.id", ondelete="SET NULL")),
44
+ Column("valence", Float, nullable=False),
45
+ Column("salience", Float, nullable=False, default=0.5),
46
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
47
+ CheckConstraint("from_memory_id <> to_memory_id", name="ck_association_observations_no_self_loop"),
48
+ )
49
+
50
+ association_edge_evidence = Table(
51
+ "association_edge_evidence",
52
+ metadata,
53
+ Column("edge_id", String, ForeignKey("association_edges.id", ondelete="CASCADE"), primary_key=True),
54
+ Column("evidence_id", String, ForeignKey("evidence_refs.id", ondelete="CASCADE"), primary_key=True),
55
+ )
@@ -0,0 +1,55 @@
1
+ """This module defines SQLAlchemy Core tables for episodes, events, and session transfers."""
2
+
3
+ from sqlalchemy import CheckConstraint, Column, ForeignKey, Integer, String, Table, Text, UniqueConstraint
4
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ episodes = Table(
10
+ "episodes",
11
+ metadata,
12
+ Column("id", String, primary_key=True),
13
+ Column("repo_id", String, nullable=False),
14
+ Column("host_app", String, nullable=False),
15
+ Column("thread_id", String),
16
+ Column("title", String),
17
+ Column("objective", String),
18
+ Column("status", String, nullable=False),
19
+ Column("started_at", TIMESTAMP(timezone=True), nullable=False),
20
+ Column("ended_at", TIMESTAMP(timezone=True)),
21
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
22
+ UniqueConstraint("repo_id", "thread_id", name="uq_episodes_repo_thread"),
23
+ CheckConstraint("ended_at IS NULL OR ended_at >= started_at", name="ck_episodes_ended_after_started"),
24
+ )
25
+
26
+ episode_events = Table(
27
+ "episode_events",
28
+ metadata,
29
+ Column("id", String, primary_key=True),
30
+ Column("episode_id", String, ForeignKey("episodes.id", ondelete="CASCADE"), nullable=False),
31
+ Column("seq", Integer, nullable=False),
32
+ Column("host_event_key", String, nullable=False),
33
+ Column("source", String, nullable=False),
34
+ Column("content", Text, nullable=False),
35
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
36
+ CheckConstraint("seq > 0", name="ck_episode_events_seq_positive"),
37
+ UniqueConstraint("episode_id", "seq", name="uq_episode_events_seq"),
38
+ UniqueConstraint("episode_id", "host_event_key", name="uq_episode_events_host_event_key"),
39
+ )
40
+
41
+ session_transfers = Table(
42
+ "session_transfers",
43
+ metadata,
44
+ Column("id", String, primary_key=True),
45
+ Column("repo_id", String, nullable=False),
46
+ Column("from_episode_id", String, ForeignKey("episodes.id", ondelete="CASCADE"), nullable=False),
47
+ Column("to_episode_id", String, ForeignKey("episodes.id", ondelete="CASCADE"), nullable=False),
48
+ Column("event_id", String, ForeignKey("episode_events.id", ondelete="CASCADE"), nullable=False),
49
+ Column("transfer_kind", String, nullable=False),
50
+ Column("rationale", Text),
51
+ Column("transferred_by", String),
52
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
53
+ CheckConstraint("from_episode_id <> to_episode_id", name="ck_session_transfers_distinct_episodes"),
54
+ UniqueConstraint("from_episode_id", "to_episode_id", "event_id", "transfer_kind", name="uq_session_transfers_transfer"),
55
+ )
@@ -0,0 +1,19 @@
1
+ """This module defines SQLAlchemy Core tables for evidence reference records."""
2
+
3
+ from sqlalchemy import Column, ForeignKey, String, Table, UniqueConstraint
4
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ evidence_refs = Table(
10
+ "evidence_refs",
11
+ metadata,
12
+ Column("id", String, primary_key=True),
13
+ Column("repo_id", String, nullable=False),
14
+ Column("ref", String, nullable=False),
15
+ Column("episode_event_id", String, ForeignKey("episode_events.id"), nullable=True),
16
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
17
+ UniqueConstraint("repo_id", "ref", name="uq_evidence_repo_ref"),
18
+ UniqueConstraint("repo_id", "episode_event_id", name="uq_evidence_repo_episode_event"),
19
+ )
@@ -0,0 +1,33 @@
1
+ """This module defines SQLAlchemy Core tables for problem attempts and fact updates."""
2
+
3
+ from sqlalchemy import CheckConstraint, Column, ForeignKey, String, Table, UniqueConstraint
4
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ problem_attempts = Table(
10
+ "problem_attempts",
11
+ metadata,
12
+ Column("problem_id", String, ForeignKey("memories.id", ondelete="CASCADE"), primary_key=True),
13
+ Column("attempt_id", String, ForeignKey("memories.id", ondelete="CASCADE"), primary_key=True),
14
+ Column("role", String, nullable=False),
15
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
16
+ CheckConstraint("problem_id <> attempt_id", name="ck_problem_attempts_distinct_memories"),
17
+ )
18
+
19
+ fact_updates = Table(
20
+ "fact_updates",
21
+ metadata,
22
+ Column("id", String, primary_key=True),
23
+ Column("old_fact_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
24
+ Column("change_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
25
+ Column("new_fact_id", String, ForeignKey("memories.id", ondelete="CASCADE"), nullable=False),
26
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
27
+ CheckConstraint("old_fact_id <> new_fact_id", name="ck_fact_updates_distinct_fact_endpoints"),
28
+ CheckConstraint(
29
+ "change_id <> old_fact_id AND change_id <> new_fact_id",
30
+ name="ck_fact_updates_change_id_distinct",
31
+ ),
32
+ UniqueConstraint("old_fact_id", "change_id", "new_fact_id", name="uq_fact_updates_chain"),
33
+ )
@@ -0,0 +1,17 @@
1
+ """SQLAlchemy Core table for instance classification and safety metadata."""
2
+
3
+ from sqlalchemy import Column, String, Table, Text
4
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ instance_metadata = Table(
10
+ "instance_metadata",
11
+ metadata,
12
+ Column("instance_id", String, primary_key=True),
13
+ Column("instance_mode", String, nullable=False),
14
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
15
+ Column("created_by", String, nullable=False),
16
+ Column("notes", Text, nullable=True),
17
+ )
@@ -0,0 +1,39 @@
1
+ """This module defines SQLAlchemy Core tables for memories and shellbrain embeddings."""
2
+
3
+ from pgvector.sqlalchemy import Vector
4
+ from sqlalchemy import Boolean, CheckConstraint, Column, ForeignKey, Integer, String, Table, Text, UniqueConstraint
5
+ from sqlalchemy.dialects.postgresql import TIMESTAMP
6
+
7
+ from app.periphery.db.models.metadata import metadata
8
+
9
+
10
+ memories = Table(
11
+ "memories",
12
+ metadata,
13
+ Column("id", String, primary_key=True),
14
+ Column("repo_id", String, nullable=False),
15
+ Column("scope", String, nullable=False),
16
+ Column("kind", String, nullable=False),
17
+ Column("text", Text, nullable=False),
18
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
19
+ Column("archived", Boolean, nullable=False, default=False),
20
+ )
21
+
22
+ memory_embeddings = Table(
23
+ "memory_embeddings",
24
+ metadata,
25
+ Column("memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), primary_key=True),
26
+ Column("model", String, nullable=False),
27
+ Column("dim", Integer, nullable=False),
28
+ Column("vector", Vector(), nullable=False),
29
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False),
30
+ CheckConstraint("dim > 0", name="ck_memory_embeddings_dim_positive"),
31
+ )
32
+
33
+ memory_evidence = Table(
34
+ "memory_evidence",
35
+ metadata,
36
+ Column("memory_id", String, ForeignKey("memories.id", ondelete="CASCADE"), primary_key=True),
37
+ Column("evidence_id", String, ForeignKey("evidence_refs.id", ondelete="CASCADE"), primary_key=True),
38
+ UniqueConstraint("memory_id", "evidence_id", name="uq_memory_evidence_pair"),
39
+ )
@@ -0,0 +1,6 @@
1
+ """This module defines shared SQLAlchemy metadata and naming conventions for DB models."""
2
+
3
+ from sqlalchemy import MetaData
4
+
5
+
6
+ metadata = MetaData()
@@ -0,0 +1,18 @@
1
+ """This module imports all SQLAlchemy table modules so metadata is fully registered."""
2
+
3
+ from app.periphery.db.models import (
4
+ associations,
5
+ episodes,
6
+ evidence,
7
+ experiences,
8
+ instance_metadata,
9
+ memories,
10
+ telemetry,
11
+ utility,
12
+ )
13
+ from app.periphery.db.models.metadata import metadata
14
+
15
+
16
+ _ = (associations, episodes, evidence, experiences, instance_metadata, memories, telemetry, utility)
17
+
18
+ target_metadata = metadata
@@ -0,0 +1,174 @@
1
+ """SQLAlchemy Core tables for low-overhead usage telemetry storage."""
2
+
3
+ from sqlalchemy import Boolean, Column, ForeignKey, Index, Integer, String, Table, Text, text
4
+ from sqlalchemy.dialects.postgresql import JSONB, TIMESTAMP
5
+
6
+ from app.periphery.db.models.metadata import metadata
7
+
8
+
9
+ operation_invocations = Table(
10
+ "operation_invocations",
11
+ metadata,
12
+ Column("id", String, primary_key=True),
13
+ Column("command", String, nullable=False),
14
+ Column("repo_id", String, nullable=False),
15
+ Column("repo_root", Text, nullable=False),
16
+ Column("no_sync", Boolean, nullable=False, server_default=text("FALSE")),
17
+ Column("caller_id", String),
18
+ Column("caller_trust_level", String),
19
+ Column("identity_failure_code", String),
20
+ Column("selected_host_app", String),
21
+ Column("selected_host_session_key", String),
22
+ Column("selected_thread_id", String),
23
+ Column("selected_episode_id", String),
24
+ Column("matching_candidate_count", Integer, nullable=False, server_default=text("0")),
25
+ Column("selection_ambiguous", Boolean, nullable=False, server_default=text("FALSE")),
26
+ Column("outcome", String, nullable=False),
27
+ Column("error_stage", String),
28
+ Column("error_code", String),
29
+ Column("error_message", Text),
30
+ Column("total_latency_ms", Integer, nullable=False),
31
+ Column("poller_start_attempted", Boolean, nullable=False, server_default=text("FALSE")),
32
+ Column("poller_started", Boolean, nullable=False, server_default=text("FALSE")),
33
+ Column("guidance_codes", JSONB, nullable=False, server_default=text("'[]'::jsonb")),
34
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()")),
35
+ )
36
+
37
+ read_invocation_summaries = Table(
38
+ "read_invocation_summaries",
39
+ metadata,
40
+ Column(
41
+ "invocation_id",
42
+ String,
43
+ ForeignKey("operation_invocations.id", ondelete="CASCADE"),
44
+ primary_key=True,
45
+ ),
46
+ Column("query_text", Text, nullable=False),
47
+ Column("mode", String, nullable=False),
48
+ Column("requested_limit", Integer),
49
+ Column("effective_limit", Integer, nullable=False),
50
+ Column("include_global", Boolean),
51
+ Column("kinds_filter", JSONB),
52
+ Column("direct_count", Integer, nullable=False),
53
+ Column("explicit_related_count", Integer, nullable=False),
54
+ Column("implicit_related_count", Integer, nullable=False),
55
+ Column("total_returned", Integer, nullable=False),
56
+ Column("zero_results", Boolean, nullable=False),
57
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()")),
58
+ )
59
+
60
+ read_result_items = Table(
61
+ "read_result_items",
62
+ metadata,
63
+ Column(
64
+ "invocation_id",
65
+ String,
66
+ ForeignKey("operation_invocations.id", ondelete="CASCADE"),
67
+ primary_key=True,
68
+ ),
69
+ Column("ordinal", Integer, primary_key=True),
70
+ Column("memory_id", String, nullable=False),
71
+ Column("kind", String, nullable=False),
72
+ Column("section", String, nullable=False),
73
+ Column("priority", Integer, nullable=False),
74
+ Column("why_included", String, nullable=False),
75
+ Column("anchor_memory_id", String),
76
+ Column("relation_type", String),
77
+ )
78
+
79
+ write_invocation_summaries = Table(
80
+ "write_invocation_summaries",
81
+ metadata,
82
+ Column(
83
+ "invocation_id",
84
+ String,
85
+ ForeignKey("operation_invocations.id", ondelete="CASCADE"),
86
+ primary_key=True,
87
+ ),
88
+ Column("operation_command", String, nullable=False),
89
+ Column("target_memory_id", String, nullable=False),
90
+ Column("target_kind", String),
91
+ Column("update_type", String),
92
+ Column("scope", String),
93
+ Column("evidence_ref_count", Integer, nullable=False, server_default=text("0")),
94
+ Column("planned_effect_count", Integer, nullable=False),
95
+ Column("created_memory_count", Integer, nullable=False, server_default=text("0")),
96
+ Column("archived_memory_count", Integer, nullable=False, server_default=text("0")),
97
+ Column("utility_observation_count", Integer, nullable=False, server_default=text("0")),
98
+ Column("association_effect_count", Integer, nullable=False, server_default=text("0")),
99
+ Column("fact_update_count", Integer, nullable=False, server_default=text("0")),
100
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()")),
101
+ )
102
+
103
+ write_effect_items = Table(
104
+ "write_effect_items",
105
+ metadata,
106
+ Column(
107
+ "invocation_id",
108
+ String,
109
+ ForeignKey("operation_invocations.id", ondelete="CASCADE"),
110
+ primary_key=True,
111
+ ),
112
+ Column("ordinal", Integer, primary_key=True),
113
+ Column("effect_type", String, nullable=False),
114
+ Column("repo_id", String, nullable=False),
115
+ Column("primary_memory_id", String),
116
+ Column("secondary_memory_id", String),
117
+ Column("params_json", JSONB, nullable=False, server_default=text("'{}'::jsonb")),
118
+ )
119
+
120
+ episode_sync_runs = Table(
121
+ "episode_sync_runs",
122
+ metadata,
123
+ Column("id", String, primary_key=True),
124
+ Column("source", String, nullable=False),
125
+ Column("invocation_id", String, ForeignKey("operation_invocations.id", ondelete="SET NULL")),
126
+ Column("repo_id", String, nullable=False),
127
+ Column("host_app", String, nullable=False),
128
+ Column("host_session_key", String, nullable=False),
129
+ Column("thread_id", String, nullable=False),
130
+ Column("episode_id", String),
131
+ Column("transcript_path", Text),
132
+ Column("outcome", String, nullable=False),
133
+ Column("error_stage", String),
134
+ Column("error_message", Text),
135
+ Column("duration_ms", Integer, nullable=False),
136
+ Column("imported_event_count", Integer, nullable=False, server_default=text("0")),
137
+ Column("total_event_count", Integer, nullable=False, server_default=text("0")),
138
+ Column("user_event_count", Integer, nullable=False, server_default=text("0")),
139
+ Column("assistant_event_count", Integer, nullable=False, server_default=text("0")),
140
+ Column("tool_event_count", Integer, nullable=False, server_default=text("0")),
141
+ Column("system_event_count", Integer, nullable=False, server_default=text("0")),
142
+ Column("created_at", TIMESTAMP(timezone=True), nullable=False, server_default=text("NOW()")),
143
+ )
144
+
145
+ episode_sync_tool_types = Table(
146
+ "episode_sync_tool_types",
147
+ metadata,
148
+ Column(
149
+ "sync_run_id",
150
+ String,
151
+ ForeignKey("episode_sync_runs.id", ondelete="CASCADE"),
152
+ primary_key=True,
153
+ ),
154
+ Column("tool_type", String, primary_key=True),
155
+ Column("event_count", Integer, nullable=False),
156
+ )
157
+
158
+ Index("idx_operation_invocations_repo_created_at", operation_invocations.c.repo_id, operation_invocations.c.created_at)
159
+ Index("idx_operation_invocations_command_created_at", operation_invocations.c.command, operation_invocations.c.created_at)
160
+ Index(
161
+ "idx_operation_invocations_thread_created_at",
162
+ operation_invocations.c.selected_thread_id,
163
+ operation_invocations.c.created_at,
164
+ postgresql_where=operation_invocations.c.selected_thread_id.is_not(None),
165
+ )
166
+ Index("idx_read_result_items_memory_invocation", read_result_items.c.memory_id, read_result_items.c.invocation_id)
167
+ Index(
168
+ "idx_write_effect_items_repo_effect_invocation",
169
+ write_effect_items.c.repo_id,
170
+ write_effect_items.c.effect_type,
171
+ write_effect_items.c.invocation_id,
172
+ )
173
+ Index("idx_episode_sync_runs_repo_host_created_at", episode_sync_runs.c.repo_id, episode_sync_runs.c.host_app, episode_sync_runs.c.created_at)
174
+ Index("idx_episode_sync_runs_thread_created_at", episode_sync_runs.c.thread_id, episode_sync_runs.c.created_at)