rootmemory 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.
- rootmemory/__init__.py +84 -0
- rootmemory/api/__init__.py +25 -0
- rootmemory/api/agents.py +38 -0
- rootmemory/api/beliefs.py +66 -0
- rootmemory/api/claims.py +87 -0
- rootmemory/api/deps.py +21 -0
- rootmemory/api/errors.py +36 -0
- rootmemory/api/graph.py +32 -0
- rootmemory/api/invalidation.py +47 -0
- rootmemory/api/observations.py +46 -0
- rootmemory/api/promotion.py +37 -0
- rootmemory/api/provenance.py +88 -0
- rootmemory/api/security.py +53 -0
- rootmemory/cli.py +96 -0
- rootmemory/client.py +219 -0
- rootmemory/config.py +64 -0
- rootmemory/db/__init__.py +0 -0
- rootmemory/db/base.py +42 -0
- rootmemory/db/migrations/env.py +59 -0
- rootmemory/db/migrations/script.py.mako +26 -0
- rootmemory/db/migrations/versions/d336d5cf7fb9_initial_schema.py +260 -0
- rootmemory/db/session.py +138 -0
- rootmemory/errors.py +27 -0
- rootmemory/integrations/__init__.py +16 -0
- rootmemory/integrations/async_client.py +268 -0
- rootmemory/integrations/langchain_tools.py +237 -0
- rootmemory/integrations/langgraph_memory.py +179 -0
- rootmemory/integrations/langgraph_store.py +402 -0
- rootmemory/logging_config.py +30 -0
- rootmemory/main.py +133 -0
- rootmemory/models/__init__.py +22 -0
- rootmemory/models/agent.py +30 -0
- rootmemory/models/audit.py +51 -0
- rootmemory/models/belief.py +39 -0
- rootmemory/models/claim.py +52 -0
- rootmemory/models/decision.py +38 -0
- rootmemory/models/edge.py +43 -0
- rootmemory/models/enums.py +104 -0
- rootmemory/models/node_ref.py +42 -0
- rootmemory/models/observation.py +51 -0
- rootmemory/py.typed +0 -0
- rootmemory/repositories/__init__.py +19 -0
- rootmemory/repositories/agent_repo.py +44 -0
- rootmemory/repositories/audit_repo.py +43 -0
- rootmemory/repositories/belief_repo.py +63 -0
- rootmemory/repositories/claim_repo.py +102 -0
- rootmemory/repositories/decision_repo.py +76 -0
- rootmemory/repositories/edge_repo.py +77 -0
- rootmemory/repositories/observation_repo.py +76 -0
- rootmemory/schemas/__init__.py +52 -0
- rootmemory/schemas/agent.py +26 -0
- rootmemory/schemas/belief.py +49 -0
- rootmemory/schemas/claim.py +54 -0
- rootmemory/schemas/common.py +27 -0
- rootmemory/schemas/observation.py +69 -0
- rootmemory/schemas/promotion.py +50 -0
- rootmemory/schemas/provenance.py +51 -0
- rootmemory/services/__init__.py +35 -0
- rootmemory/services/belief_service.py +161 -0
- rootmemory/services/contradiction_service.py +148 -0
- rootmemory/services/decision_service.py +65 -0
- rootmemory/services/graph_service.py +316 -0
- rootmemory/services/independence_service.py +159 -0
- rootmemory/services/invalidation_service.py +164 -0
- rootmemory/services/normalization_service.py +234 -0
- rootmemory/services/promotion_service.py +318 -0
- rootmemory/services/provenance_service.py +282 -0
- rootmemory/services/scoring_service.py +53 -0
- rootmemory/static/index.html +866 -0
- rootmemory-0.1.0.dist-info/METADATA +266 -0
- rootmemory-0.1.0.dist-info/RECORD +75 -0
- rootmemory-0.1.0.dist-info/WHEEL +5 -0
- rootmemory-0.1.0.dist-info/entry_points.txt +2 -0
- rootmemory-0.1.0.dist-info/licenses/LICENSE +21 -0
- rootmemory-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""initial schema
|
|
2
|
+
|
|
3
|
+
Revision ID: d336d5cf7fb9
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-09-08 18:58:54.903233
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
from alembic import op
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
revision: str = 'd336d5cf7fb9'
|
|
16
|
+
down_revision: str | None = None
|
|
17
|
+
branch_labels: str | Sequence[str] | None = None
|
|
18
|
+
depends_on: str | Sequence[str] | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
23
|
+
op.create_table('agents',
|
|
24
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
25
|
+
sa.Column('name', sa.String(length=200), nullable=False),
|
|
26
|
+
sa.Column('role', sa.String(length=200), nullable=False),
|
|
27
|
+
sa.Column('description', sa.String(length=2000), nullable=False),
|
|
28
|
+
sa.Column('trust_score', sa.Float(), nullable=False),
|
|
29
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
30
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
31
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_agents'))
|
|
32
|
+
)
|
|
33
|
+
with op.batch_alter_table('agents', schema=None) as batch_op:
|
|
34
|
+
batch_op.create_index(batch_op.f('ix_agents_name'), ['name'], unique=True)
|
|
35
|
+
|
|
36
|
+
op.create_table('claims',
|
|
37
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
38
|
+
sa.Column('claim_key', sa.String(length=300), nullable=False),
|
|
39
|
+
sa.Column('canonical_text', sa.Text(), nullable=False),
|
|
40
|
+
sa.Column('status', sa.String(length=20), nullable=False),
|
|
41
|
+
sa.Column('confidence', sa.Float(), nullable=False),
|
|
42
|
+
sa.Column('independent_support_count', sa.Integer(), nullable=False),
|
|
43
|
+
sa.Column('independent_contradiction_count', sa.Integer(), nullable=False),
|
|
44
|
+
sa.Column('promoted_at', sa.DateTime(timezone=True), nullable=True),
|
|
45
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
46
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
47
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_claims'))
|
|
48
|
+
)
|
|
49
|
+
with op.batch_alter_table('claims', schema=None) as batch_op:
|
|
50
|
+
batch_op.create_index(batch_op.f('ix_claims_claim_key'), ['claim_key'], unique=True)
|
|
51
|
+
batch_op.create_index(batch_op.f('ix_claims_status'), ['status'], unique=False)
|
|
52
|
+
|
|
53
|
+
op.create_table('provenance_edges',
|
|
54
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
55
|
+
sa.Column('from_node_id', sa.Uuid(), nullable=False),
|
|
56
|
+
sa.Column('from_node_type', sa.String(length=20), nullable=False),
|
|
57
|
+
sa.Column('to_node_id', sa.Uuid(), nullable=False),
|
|
58
|
+
sa.Column('to_node_type', sa.String(length=20), nullable=False),
|
|
59
|
+
sa.Column('edge_type', sa.String(length=20), nullable=False),
|
|
60
|
+
sa.Column('weight', sa.Float(), nullable=False),
|
|
61
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
62
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
63
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_provenance_edges')),
|
|
64
|
+
sa.UniqueConstraint('from_node_id', 'to_node_id', 'edge_type', name='from_node_id_to_node_id_edge_type')
|
|
65
|
+
)
|
|
66
|
+
with op.batch_alter_table('provenance_edges', schema=None) as batch_op:
|
|
67
|
+
batch_op.create_index('ix_provenance_edges_edge_type', ['edge_type'], unique=False)
|
|
68
|
+
batch_op.create_index('ix_provenance_edges_from_node_id', ['from_node_id'], unique=False)
|
|
69
|
+
batch_op.create_index('ix_provenance_edges_to_node_id', ['to_node_id'], unique=False)
|
|
70
|
+
|
|
71
|
+
op.create_table('repair_reports',
|
|
72
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
73
|
+
sa.Column('trigger_node_id', sa.Uuid(), nullable=False),
|
|
74
|
+
sa.Column('trigger_node_type', sa.String(length=20), nullable=False),
|
|
75
|
+
sa.Column('trigger_reason', sa.String(length=2000), nullable=False),
|
|
76
|
+
sa.Column('affected_beliefs', sa.JSON(), nullable=False),
|
|
77
|
+
sa.Column('affected_claims', sa.JSON(), nullable=False),
|
|
78
|
+
sa.Column('affected_decisions', sa.JSON(), nullable=False),
|
|
79
|
+
sa.Column('details', sa.JSON(), nullable=False),
|
|
80
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
81
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_repair_reports'))
|
|
82
|
+
)
|
|
83
|
+
with op.batch_alter_table('repair_reports', schema=None) as batch_op:
|
|
84
|
+
batch_op.create_index(batch_op.f('ix_repair_reports_trigger_node_id'), ['trigger_node_id'], unique=False)
|
|
85
|
+
|
|
86
|
+
op.create_table('beliefs',
|
|
87
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
88
|
+
sa.Column('agent_id', sa.Uuid(), nullable=False),
|
|
89
|
+
sa.Column('claim_text', sa.Text(), nullable=False),
|
|
90
|
+
sa.Column('normalized_claim_key', sa.String(length=300), nullable=False),
|
|
91
|
+
sa.Column('confidence', sa.Float(), nullable=False),
|
|
92
|
+
sa.Column('epistemic_status', sa.String(length=20), nullable=False),
|
|
93
|
+
sa.Column('visibility', sa.String(length=20), nullable=False),
|
|
94
|
+
sa.Column('llm_generated', sa.Boolean(), nullable=False),
|
|
95
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
96
|
+
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
97
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
98
|
+
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('fk_beliefs_agent_id_agents')),
|
|
99
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_beliefs'))
|
|
100
|
+
)
|
|
101
|
+
with op.batch_alter_table('beliefs', schema=None) as batch_op:
|
|
102
|
+
batch_op.create_index(batch_op.f('ix_beliefs_agent_id'), ['agent_id'], unique=False)
|
|
103
|
+
batch_op.create_index(batch_op.f('ix_beliefs_epistemic_status'), ['epistemic_status'], unique=False)
|
|
104
|
+
batch_op.create_index(batch_op.f('ix_beliefs_normalized_claim_key'), ['normalized_claim_key'], unique=False)
|
|
105
|
+
batch_op.create_index(batch_op.f('ix_beliefs_visibility'), ['visibility'], unique=False)
|
|
106
|
+
|
|
107
|
+
op.create_table('decisions',
|
|
108
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
109
|
+
sa.Column('agent_id', sa.Uuid(), nullable=False),
|
|
110
|
+
sa.Column('decision_type', sa.String(length=100), nullable=False),
|
|
111
|
+
sa.Column('content', sa.Text(), nullable=False),
|
|
112
|
+
sa.Column('status', sa.String(length=20), nullable=False),
|
|
113
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
114
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
115
|
+
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], name=op.f('fk_decisions_agent_id_agents')),
|
|
116
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_decisions'))
|
|
117
|
+
)
|
|
118
|
+
with op.batch_alter_table('decisions', schema=None) as batch_op:
|
|
119
|
+
batch_op.create_index(batch_op.f('ix_decisions_agent_id'), ['agent_id'], unique=False)
|
|
120
|
+
batch_op.create_index(batch_op.f('ix_decisions_decision_type'), ['decision_type'], unique=False)
|
|
121
|
+
batch_op.create_index(batch_op.f('ix_decisions_status'), ['status'], unique=False)
|
|
122
|
+
|
|
123
|
+
op.create_table('observations',
|
|
124
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
125
|
+
sa.Column('source_type', sa.String(length=100), nullable=False),
|
|
126
|
+
sa.Column('source_uri', sa.String(length=1000), nullable=True),
|
|
127
|
+
sa.Column('source_actor', sa.String(length=300), nullable=True),
|
|
128
|
+
sa.Column('content', sa.Text(), nullable=False),
|
|
129
|
+
sa.Column('content_hash', sa.String(length=64), nullable=False),
|
|
130
|
+
sa.Column('timestamp', sa.DateTime(timezone=True), nullable=False),
|
|
131
|
+
sa.Column('created_by_agent_id', sa.Uuid(), nullable=True),
|
|
132
|
+
sa.Column('validity_status', sa.String(length=20), nullable=False),
|
|
133
|
+
sa.Column('validity_reason', sa.String(length=2000), nullable=True),
|
|
134
|
+
sa.Column('reliability_score', sa.Float(), nullable=False),
|
|
135
|
+
sa.Column('source_family_id', sa.String(length=200), nullable=True),
|
|
136
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
137
|
+
sa.Column('meta', sa.JSON(), nullable=False),
|
|
138
|
+
sa.ForeignKeyConstraint(['created_by_agent_id'], ['agents.id'], name=op.f('fk_observations_created_by_agent_id_agents')),
|
|
139
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_observations'))
|
|
140
|
+
)
|
|
141
|
+
with op.batch_alter_table('observations', schema=None) as batch_op:
|
|
142
|
+
batch_op.create_index(batch_op.f('ix_observations_content_hash'), ['content_hash'], unique=False)
|
|
143
|
+
batch_op.create_index(batch_op.f('ix_observations_created_by_agent_id'), ['created_by_agent_id'], unique=False)
|
|
144
|
+
batch_op.create_index(batch_op.f('ix_observations_source_family_id'), ['source_family_id'], unique=False)
|
|
145
|
+
batch_op.create_index(batch_op.f('ix_observations_source_type'), ['source_type'], unique=False)
|
|
146
|
+
batch_op.create_index(batch_op.f('ix_observations_validity_status'), ['validity_status'], unique=False)
|
|
147
|
+
|
|
148
|
+
op.create_table('promotion_audits',
|
|
149
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
150
|
+
sa.Column('claim_id', sa.Uuid(), nullable=False),
|
|
151
|
+
sa.Column('evaluated_at', sa.DateTime(timezone=True), nullable=False),
|
|
152
|
+
sa.Column('supporting_belief_count', sa.Integer(), nullable=False),
|
|
153
|
+
sa.Column('agreeing_agent_count', sa.Integer(), nullable=False),
|
|
154
|
+
sa.Column('independent_support_count', sa.Integer(), nullable=False),
|
|
155
|
+
sa.Column('contradiction_count', sa.Integer(), nullable=False),
|
|
156
|
+
sa.Column('independent_contradiction_count', sa.Integer(), nullable=False),
|
|
157
|
+
sa.Column('aggregate_confidence', sa.Float(), nullable=False),
|
|
158
|
+
sa.Column('result', sa.String(length=20), nullable=False),
|
|
159
|
+
sa.Column('reasons', sa.JSON(), nullable=False),
|
|
160
|
+
sa.Column('policy_version', sa.String(length=50), nullable=False),
|
|
161
|
+
sa.Column('policy_snapshot', sa.JSON(), nullable=False),
|
|
162
|
+
sa.ForeignKeyConstraint(['claim_id'], ['claims.id'], name=op.f('fk_promotion_audits_claim_id_claims')),
|
|
163
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_promotion_audits'))
|
|
164
|
+
)
|
|
165
|
+
with op.batch_alter_table('promotion_audits', schema=None) as batch_op:
|
|
166
|
+
batch_op.create_index(batch_op.f('ix_promotion_audits_claim_id'), ['claim_id'], unique=False)
|
|
167
|
+
batch_op.create_index(batch_op.f('ix_promotion_audits_result'), ['result'], unique=False)
|
|
168
|
+
|
|
169
|
+
op.create_table('claim_belief_links',
|
|
170
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
171
|
+
sa.Column('claim_id', sa.Uuid(), nullable=False),
|
|
172
|
+
sa.Column('belief_id', sa.Uuid(), nullable=False),
|
|
173
|
+
sa.Column('role', sa.String(length=20), nullable=False),
|
|
174
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
175
|
+
sa.ForeignKeyConstraint(['belief_id'], ['beliefs.id'], name=op.f('fk_claim_belief_links_belief_id_beliefs')),
|
|
176
|
+
sa.ForeignKeyConstraint(['claim_id'], ['claims.id'], name=op.f('fk_claim_belief_links_claim_id_claims')),
|
|
177
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_claim_belief_links')),
|
|
178
|
+
sa.UniqueConstraint('claim_id', 'belief_id', 'role', name='claim_id_belief_id_role')
|
|
179
|
+
)
|
|
180
|
+
with op.batch_alter_table('claim_belief_links', schema=None) as batch_op:
|
|
181
|
+
batch_op.create_index(batch_op.f('ix_claim_belief_links_belief_id'), ['belief_id'], unique=False)
|
|
182
|
+
batch_op.create_index(batch_op.f('ix_claim_belief_links_claim_id'), ['claim_id'], unique=False)
|
|
183
|
+
batch_op.create_index(batch_op.f('ix_claim_belief_links_role'), ['role'], unique=False)
|
|
184
|
+
|
|
185
|
+
op.create_table('decision_dependencies',
|
|
186
|
+
sa.Column('id', sa.Uuid(), nullable=False),
|
|
187
|
+
sa.Column('decision_id', sa.Uuid(), nullable=False),
|
|
188
|
+
sa.Column('claim_id', sa.Uuid(), nullable=False),
|
|
189
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
190
|
+
sa.ForeignKeyConstraint(['claim_id'], ['claims.id'], name=op.f('fk_decision_dependencies_claim_id_claims')),
|
|
191
|
+
sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], name=op.f('fk_decision_dependencies_decision_id_decisions')),
|
|
192
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_decision_dependencies')),
|
|
193
|
+
sa.UniqueConstraint('decision_id', 'claim_id', name='decision_id_claim_id')
|
|
194
|
+
)
|
|
195
|
+
with op.batch_alter_table('decision_dependencies', schema=None) as batch_op:
|
|
196
|
+
batch_op.create_index(batch_op.f('ix_decision_dependencies_claim_id'), ['claim_id'], unique=False)
|
|
197
|
+
batch_op.create_index(batch_op.f('ix_decision_dependencies_decision_id'), ['decision_id'], unique=False)
|
|
198
|
+
|
|
199
|
+
# ### end Alembic commands ###
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def downgrade() -> None:
|
|
203
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
204
|
+
with op.batch_alter_table('decision_dependencies', schema=None) as batch_op:
|
|
205
|
+
batch_op.drop_index(batch_op.f('ix_decision_dependencies_decision_id'))
|
|
206
|
+
batch_op.drop_index(batch_op.f('ix_decision_dependencies_claim_id'))
|
|
207
|
+
|
|
208
|
+
op.drop_table('decision_dependencies')
|
|
209
|
+
with op.batch_alter_table('claim_belief_links', schema=None) as batch_op:
|
|
210
|
+
batch_op.drop_index(batch_op.f('ix_claim_belief_links_role'))
|
|
211
|
+
batch_op.drop_index(batch_op.f('ix_claim_belief_links_claim_id'))
|
|
212
|
+
batch_op.drop_index(batch_op.f('ix_claim_belief_links_belief_id'))
|
|
213
|
+
|
|
214
|
+
op.drop_table('claim_belief_links')
|
|
215
|
+
with op.batch_alter_table('promotion_audits', schema=None) as batch_op:
|
|
216
|
+
batch_op.drop_index(batch_op.f('ix_promotion_audits_result'))
|
|
217
|
+
batch_op.drop_index(batch_op.f('ix_promotion_audits_claim_id'))
|
|
218
|
+
|
|
219
|
+
op.drop_table('promotion_audits')
|
|
220
|
+
with op.batch_alter_table('observations', schema=None) as batch_op:
|
|
221
|
+
batch_op.drop_index(batch_op.f('ix_observations_validity_status'))
|
|
222
|
+
batch_op.drop_index(batch_op.f('ix_observations_source_type'))
|
|
223
|
+
batch_op.drop_index(batch_op.f('ix_observations_source_family_id'))
|
|
224
|
+
batch_op.drop_index(batch_op.f('ix_observations_created_by_agent_id'))
|
|
225
|
+
batch_op.drop_index(batch_op.f('ix_observations_content_hash'))
|
|
226
|
+
|
|
227
|
+
op.drop_table('observations')
|
|
228
|
+
with op.batch_alter_table('decisions', schema=None) as batch_op:
|
|
229
|
+
batch_op.drop_index(batch_op.f('ix_decisions_status'))
|
|
230
|
+
batch_op.drop_index(batch_op.f('ix_decisions_decision_type'))
|
|
231
|
+
batch_op.drop_index(batch_op.f('ix_decisions_agent_id'))
|
|
232
|
+
|
|
233
|
+
op.drop_table('decisions')
|
|
234
|
+
with op.batch_alter_table('beliefs', schema=None) as batch_op:
|
|
235
|
+
batch_op.drop_index(batch_op.f('ix_beliefs_visibility'))
|
|
236
|
+
batch_op.drop_index(batch_op.f('ix_beliefs_normalized_claim_key'))
|
|
237
|
+
batch_op.drop_index(batch_op.f('ix_beliefs_epistemic_status'))
|
|
238
|
+
batch_op.drop_index(batch_op.f('ix_beliefs_agent_id'))
|
|
239
|
+
|
|
240
|
+
op.drop_table('beliefs')
|
|
241
|
+
with op.batch_alter_table('repair_reports', schema=None) as batch_op:
|
|
242
|
+
batch_op.drop_index(batch_op.f('ix_repair_reports_trigger_node_id'))
|
|
243
|
+
|
|
244
|
+
op.drop_table('repair_reports')
|
|
245
|
+
with op.batch_alter_table('provenance_edges', schema=None) as batch_op:
|
|
246
|
+
batch_op.drop_index('ix_provenance_edges_to_node_id')
|
|
247
|
+
batch_op.drop_index('ix_provenance_edges_from_node_id')
|
|
248
|
+
batch_op.drop_index('ix_provenance_edges_edge_type')
|
|
249
|
+
|
|
250
|
+
op.drop_table('provenance_edges')
|
|
251
|
+
with op.batch_alter_table('claims', schema=None) as batch_op:
|
|
252
|
+
batch_op.drop_index(batch_op.f('ix_claims_status'))
|
|
253
|
+
batch_op.drop_index(batch_op.f('ix_claims_claim_key'))
|
|
254
|
+
|
|
255
|
+
op.drop_table('claims')
|
|
256
|
+
with op.batch_alter_table('agents', schema=None) as batch_op:
|
|
257
|
+
batch_op.drop_index(batch_op.f('ix_agents_name'))
|
|
258
|
+
|
|
259
|
+
op.drop_table('agents')
|
|
260
|
+
# ### end Alembic commands ###
|
rootmemory/db/session.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Engine/session management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Generator, Iterator
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import Engine, create_engine, event
|
|
9
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
10
|
+
from sqlalchemy.pool import StaticPool
|
|
11
|
+
|
|
12
|
+
from rootmemory.config import get_settings
|
|
13
|
+
from rootmemory.db.base import Base
|
|
14
|
+
|
|
15
|
+
_engine: Engine | None = None
|
|
16
|
+
_session_factory: sessionmaker[Session] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_engine(database_url: str, echo: bool = False) -> Engine:
|
|
20
|
+
"""Create an engine with the dialect-specific settings this app needs."""
|
|
21
|
+
is_sqlite = database_url.startswith("sqlite")
|
|
22
|
+
kwargs: dict[str, object] = {"echo": echo, "future": True}
|
|
23
|
+
if is_sqlite:
|
|
24
|
+
kwargs["connect_args"] = {"check_same_thread": False}
|
|
25
|
+
if ":memory:" in database_url:
|
|
26
|
+
# Every connection to an in-memory SQLite database gets its own
|
|
27
|
+
# empty database, so the whole app must share one connection.
|
|
28
|
+
kwargs["poolclass"] = StaticPool
|
|
29
|
+
engine = create_engine(database_url, **kwargs)
|
|
30
|
+
if is_sqlite:
|
|
31
|
+
# Foreign keys are off by default on SQLite; the integrity rules in
|
|
32
|
+
# spec section 46 depend on them being enforced.
|
|
33
|
+
@event.listens_for(engine, "connect")
|
|
34
|
+
def _fk_pragma(dbapi_connection, _record): # type: ignore[no-untyped-def]
|
|
35
|
+
cursor = dbapi_connection.cursor()
|
|
36
|
+
cursor.execute("PRAGMA foreign_keys=ON")
|
|
37
|
+
cursor.close()
|
|
38
|
+
|
|
39
|
+
return engine
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_engine() -> Engine:
|
|
43
|
+
global _engine
|
|
44
|
+
if _engine is None:
|
|
45
|
+
settings = get_settings()
|
|
46
|
+
_engine = build_engine(settings.database_url, settings.sql_echo)
|
|
47
|
+
return _engine
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_isolated_session(database_url: str = "sqlite+pysqlite:///:memory:") -> Session:
|
|
51
|
+
"""A session on a private, freshly created database.
|
|
52
|
+
|
|
53
|
+
Used by the test-suite and by the experiment harness, where every scenario
|
|
54
|
+
must start from an empty graph without disturbing the process-wide engine.
|
|
55
|
+
"""
|
|
56
|
+
import rootmemory.models # noqa: F401 (register mappers)
|
|
57
|
+
|
|
58
|
+
engine = build_engine(database_url)
|
|
59
|
+
Base.metadata.create_all(bind=engine)
|
|
60
|
+
return sessionmaker(bind=engine, expire_on_commit=False, future=True)()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_session_factory() -> sessionmaker[Session]:
|
|
64
|
+
global _session_factory
|
|
65
|
+
if _session_factory is None:
|
|
66
|
+
_session_factory = sessionmaker(bind=get_engine(), expire_on_commit=False, future=True)
|
|
67
|
+
return _session_factory
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def init_db() -> None:
|
|
71
|
+
"""Create tables for local/dev/test use.
|
|
72
|
+
|
|
73
|
+
Production schema changes go through Alembic; this is the zero-infrastructure
|
|
74
|
+
path used by the demo and the test-suite.
|
|
75
|
+
"""
|
|
76
|
+
import rootmemory.models # noqa: F401 (register mappers)
|
|
77
|
+
|
|
78
|
+
Base.metadata.create_all(bind=get_engine())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@contextmanager
|
|
82
|
+
def session_scope() -> Iterator[Session]:
|
|
83
|
+
"""Transactional scope for scripts and services outside the API."""
|
|
84
|
+
session = get_session_factory()()
|
|
85
|
+
try:
|
|
86
|
+
yield session
|
|
87
|
+
session.commit()
|
|
88
|
+
except Exception:
|
|
89
|
+
session.rollback()
|
|
90
|
+
raise
|
|
91
|
+
finally:
|
|
92
|
+
session.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@contextmanager
|
|
96
|
+
def open_memory(
|
|
97
|
+
database_url: str = "sqlite+pysqlite:///rootmemory.db",
|
|
98
|
+
create_tables: bool = True,
|
|
99
|
+
) -> Iterator[Session]:
|
|
100
|
+
"""Open a memory on its own database, in one line.
|
|
101
|
+
|
|
102
|
+
The library front door for embedding RootMemory in another application::
|
|
103
|
+
|
|
104
|
+
with open_memory("sqlite+pysqlite:///memory.db") as session:
|
|
105
|
+
memory = RootMemory(session)
|
|
106
|
+
|
|
107
|
+
Uses a private engine, so it never disturbs process-wide configuration and
|
|
108
|
+
two memories can be open at once. Commits on clean exit, rolls back on an
|
|
109
|
+
exception.
|
|
110
|
+
"""
|
|
111
|
+
import rootmemory.models # noqa: F401 (register mappers)
|
|
112
|
+
|
|
113
|
+
engine = build_engine(database_url)
|
|
114
|
+
if create_tables:
|
|
115
|
+
Base.metadata.create_all(bind=engine)
|
|
116
|
+
|
|
117
|
+
session = sessionmaker(bind=engine, expire_on_commit=False, future=True)()
|
|
118
|
+
try:
|
|
119
|
+
yield session
|
|
120
|
+
session.commit()
|
|
121
|
+
except Exception:
|
|
122
|
+
session.rollback()
|
|
123
|
+
raise
|
|
124
|
+
finally:
|
|
125
|
+
session.close()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_db() -> Generator[Session, None, None]:
|
|
129
|
+
"""FastAPI dependency."""
|
|
130
|
+
session = get_session_factory()()
|
|
131
|
+
try:
|
|
132
|
+
yield session
|
|
133
|
+
session.commit()
|
|
134
|
+
except Exception:
|
|
135
|
+
session.rollback()
|
|
136
|
+
raise
|
|
137
|
+
finally:
|
|
138
|
+
session.close()
|
rootmemory/errors.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Domain errors, mapped to HTTP status codes in app/api/errors.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RootMemoryError(Exception):
|
|
7
|
+
"""Base class for all domain errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NodeNotFoundError(RootMemoryError):
|
|
11
|
+
"""A referenced observation, belief, claim, decision or agent does not exist."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProvenanceRequiredError(RootMemoryError):
|
|
15
|
+
"""A belief was submitted without any causal parent (spec section 16)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProvenanceCycleError(RootMemoryError):
|
|
19
|
+
"""The requested edge would make a belief depend on itself."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ImmutableRecordError(RootMemoryError):
|
|
23
|
+
"""An attempt was made to mutate an append-only record."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PromotionForbiddenError(RootMemoryError):
|
|
27
|
+
"""Shared/confirmed state may only be reached through the PromotionService."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Adapters that plug the memory layer into agent frameworks.
|
|
2
|
+
|
|
3
|
+
Nothing in ``app/`` outside this package imports a framework, and nothing in
|
|
4
|
+
this package is imported unless you ask for it. Each adapter imports its
|
|
5
|
+
framework lazily and raises a readable error if it is not installed, so the
|
|
6
|
+
core stays dependency-free.
|
|
7
|
+
|
|
8
|
+
from rootmemory.integrations import AsyncRootMemory # any async code
|
|
9
|
+
from rootmemory.integrations.langchain_tools import build_tools # LangChain / any
|
|
10
|
+
# tool-calling agent
|
|
11
|
+
from rootmemory.integrations.langgraph_memory import remembering, MemoryState
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from rootmemory.integrations.async_client import AsyncRootMemory
|
|
15
|
+
|
|
16
|
+
__all__ = ["AsyncRootMemory"]
|