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.
Files changed (75) hide show
  1. rootmemory/__init__.py +84 -0
  2. rootmemory/api/__init__.py +25 -0
  3. rootmemory/api/agents.py +38 -0
  4. rootmemory/api/beliefs.py +66 -0
  5. rootmemory/api/claims.py +87 -0
  6. rootmemory/api/deps.py +21 -0
  7. rootmemory/api/errors.py +36 -0
  8. rootmemory/api/graph.py +32 -0
  9. rootmemory/api/invalidation.py +47 -0
  10. rootmemory/api/observations.py +46 -0
  11. rootmemory/api/promotion.py +37 -0
  12. rootmemory/api/provenance.py +88 -0
  13. rootmemory/api/security.py +53 -0
  14. rootmemory/cli.py +96 -0
  15. rootmemory/client.py +219 -0
  16. rootmemory/config.py +64 -0
  17. rootmemory/db/__init__.py +0 -0
  18. rootmemory/db/base.py +42 -0
  19. rootmemory/db/migrations/env.py +59 -0
  20. rootmemory/db/migrations/script.py.mako +26 -0
  21. rootmemory/db/migrations/versions/d336d5cf7fb9_initial_schema.py +260 -0
  22. rootmemory/db/session.py +138 -0
  23. rootmemory/errors.py +27 -0
  24. rootmemory/integrations/__init__.py +16 -0
  25. rootmemory/integrations/async_client.py +268 -0
  26. rootmemory/integrations/langchain_tools.py +237 -0
  27. rootmemory/integrations/langgraph_memory.py +179 -0
  28. rootmemory/integrations/langgraph_store.py +402 -0
  29. rootmemory/logging_config.py +30 -0
  30. rootmemory/main.py +133 -0
  31. rootmemory/models/__init__.py +22 -0
  32. rootmemory/models/agent.py +30 -0
  33. rootmemory/models/audit.py +51 -0
  34. rootmemory/models/belief.py +39 -0
  35. rootmemory/models/claim.py +52 -0
  36. rootmemory/models/decision.py +38 -0
  37. rootmemory/models/edge.py +43 -0
  38. rootmemory/models/enums.py +104 -0
  39. rootmemory/models/node_ref.py +42 -0
  40. rootmemory/models/observation.py +51 -0
  41. rootmemory/py.typed +0 -0
  42. rootmemory/repositories/__init__.py +19 -0
  43. rootmemory/repositories/agent_repo.py +44 -0
  44. rootmemory/repositories/audit_repo.py +43 -0
  45. rootmemory/repositories/belief_repo.py +63 -0
  46. rootmemory/repositories/claim_repo.py +102 -0
  47. rootmemory/repositories/decision_repo.py +76 -0
  48. rootmemory/repositories/edge_repo.py +77 -0
  49. rootmemory/repositories/observation_repo.py +76 -0
  50. rootmemory/schemas/__init__.py +52 -0
  51. rootmemory/schemas/agent.py +26 -0
  52. rootmemory/schemas/belief.py +49 -0
  53. rootmemory/schemas/claim.py +54 -0
  54. rootmemory/schemas/common.py +27 -0
  55. rootmemory/schemas/observation.py +69 -0
  56. rootmemory/schemas/promotion.py +50 -0
  57. rootmemory/schemas/provenance.py +51 -0
  58. rootmemory/services/__init__.py +35 -0
  59. rootmemory/services/belief_service.py +161 -0
  60. rootmemory/services/contradiction_service.py +148 -0
  61. rootmemory/services/decision_service.py +65 -0
  62. rootmemory/services/graph_service.py +316 -0
  63. rootmemory/services/independence_service.py +159 -0
  64. rootmemory/services/invalidation_service.py +164 -0
  65. rootmemory/services/normalization_service.py +234 -0
  66. rootmemory/services/promotion_service.py +318 -0
  67. rootmemory/services/provenance_service.py +282 -0
  68. rootmemory/services/scoring_service.py +53 -0
  69. rootmemory/static/index.html +866 -0
  70. rootmemory-0.1.0.dist-info/METADATA +266 -0
  71. rootmemory-0.1.0.dist-info/RECORD +75 -0
  72. rootmemory-0.1.0.dist-info/WHEEL +5 -0
  73. rootmemory-0.1.0.dist-info/entry_points.txt +2 -0
  74. rootmemory-0.1.0.dist-info/licenses/LICENSE +21 -0
  75. 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 ###
@@ -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"]