hindsight-api 0.3.0__py3-none-any.whl → 0.4.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 (74) hide show
  1. hindsight_api/admin/cli.py +59 -0
  2. hindsight_api/alembic/versions/h3c4d5e6f7g8_mental_models_v4.py +112 -0
  3. hindsight_api/alembic/versions/i4d5e6f7g8h9_delete_opinions.py +41 -0
  4. hindsight_api/alembic/versions/j5e6f7g8h9i0_mental_model_versions.py +95 -0
  5. hindsight_api/alembic/versions/k6f7g8h9i0j1_add_directive_subtype.py +58 -0
  6. hindsight_api/alembic/versions/l7g8h9i0j1k2_add_worker_columns.py +109 -0
  7. hindsight_api/alembic/versions/m8h9i0j1k2l3_mental_model_id_to_text.py +41 -0
  8. hindsight_api/alembic/versions/n9i0j1k2l3m4_learnings_and_pinned_reflections.py +134 -0
  9. hindsight_api/alembic/versions/o0j1k2l3m4n5_migrate_mental_models_data.py +113 -0
  10. hindsight_api/alembic/versions/p1k2l3m4n5o6_new_knowledge_architecture.py +194 -0
  11. hindsight_api/alembic/versions/q2l3m4n5o6p7_fix_mental_model_fact_type.py +50 -0
  12. hindsight_api/alembic/versions/r3m4n5o6p7q8_add_reflect_response_to_reflections.py +47 -0
  13. hindsight_api/alembic/versions/s4n5o6p7q8r9_add_consolidated_at_to_memory_units.py +53 -0
  14. hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py +134 -0
  15. hindsight_api/alembic/versions/u6p7q8r9s0t1_mental_models_text_id.py +41 -0
  16. hindsight_api/alembic/versions/v7q8r9s0t1u2_add_max_tokens_to_mental_models.py +50 -0
  17. hindsight_api/api/http.py +1119 -93
  18. hindsight_api/api/mcp.py +11 -191
  19. hindsight_api/config.py +145 -45
  20. hindsight_api/engine/consolidation/__init__.py +5 -0
  21. hindsight_api/engine/consolidation/consolidator.py +859 -0
  22. hindsight_api/engine/consolidation/prompts.py +69 -0
  23. hindsight_api/engine/cross_encoder.py +114 -9
  24. hindsight_api/engine/directives/__init__.py +5 -0
  25. hindsight_api/engine/directives/models.py +37 -0
  26. hindsight_api/engine/embeddings.py +102 -5
  27. hindsight_api/engine/interface.py +32 -13
  28. hindsight_api/engine/llm_wrapper.py +505 -43
  29. hindsight_api/engine/memory_engine.py +2090 -1089
  30. hindsight_api/engine/mental_models/__init__.py +14 -0
  31. hindsight_api/engine/mental_models/models.py +53 -0
  32. hindsight_api/engine/reflect/__init__.py +18 -0
  33. hindsight_api/engine/reflect/agent.py +933 -0
  34. hindsight_api/engine/reflect/models.py +109 -0
  35. hindsight_api/engine/reflect/observations.py +186 -0
  36. hindsight_api/engine/reflect/prompts.py +483 -0
  37. hindsight_api/engine/reflect/tools.py +437 -0
  38. hindsight_api/engine/reflect/tools_schema.py +250 -0
  39. hindsight_api/engine/response_models.py +130 -4
  40. hindsight_api/engine/retain/bank_utils.py +79 -201
  41. hindsight_api/engine/retain/fact_extraction.py +81 -48
  42. hindsight_api/engine/retain/fact_storage.py +5 -8
  43. hindsight_api/engine/retain/link_utils.py +5 -8
  44. hindsight_api/engine/retain/orchestrator.py +1 -55
  45. hindsight_api/engine/retain/types.py +2 -2
  46. hindsight_api/engine/search/graph_retrieval.py +2 -2
  47. hindsight_api/engine/search/link_expansion_retrieval.py +164 -29
  48. hindsight_api/engine/search/mpfp_retrieval.py +1 -1
  49. hindsight_api/engine/search/retrieval.py +14 -14
  50. hindsight_api/engine/search/think_utils.py +41 -140
  51. hindsight_api/engine/search/trace.py +0 -1
  52. hindsight_api/engine/search/tracer.py +2 -5
  53. hindsight_api/engine/search/types.py +0 -3
  54. hindsight_api/engine/task_backend.py +112 -196
  55. hindsight_api/engine/utils.py +0 -151
  56. hindsight_api/extensions/__init__.py +10 -1
  57. hindsight_api/extensions/builtin/tenant.py +5 -1
  58. hindsight_api/extensions/operation_validator.py +81 -4
  59. hindsight_api/extensions/tenant.py +26 -0
  60. hindsight_api/main.py +16 -5
  61. hindsight_api/mcp_local.py +12 -53
  62. hindsight_api/mcp_tools.py +494 -0
  63. hindsight_api/models.py +0 -2
  64. hindsight_api/worker/__init__.py +11 -0
  65. hindsight_api/worker/main.py +296 -0
  66. hindsight_api/worker/poller.py +486 -0
  67. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/METADATA +12 -6
  68. hindsight_api-0.4.0.dist-info/RECORD +112 -0
  69. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/entry_points.txt +1 -0
  70. hindsight_api/engine/retain/observation_regeneration.py +0 -254
  71. hindsight_api/engine/search/observation_utils.py +0 -125
  72. hindsight_api/engine/search/scoring.py +0 -159
  73. hindsight_api-0.3.0.dist-info/RECORD +0 -82
  74. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,113 @@
1
+ """migrate_mental_models_data
2
+
3
+ Revision ID: o0j1k2l3m4n5
4
+ Revises: n9i0j1k2l3m4
5
+ Create Date: 2026-01-21 00:00:00.000000
6
+
7
+ This migration:
8
+ 1. Migrates existing 'pinned' mental models to the new 'pinned_reflections' table
9
+ 2. Migrates existing 'learned' mental models to the new 'learnings' table
10
+ 3. Deletes non-directive mental models (structural, emergent, pinned, learned)
11
+ 4. Drops the mental_model_versions table (no longer used)
12
+ 5. Adds a CHECK constraint that only 'directive' subtype is allowed
13
+ """
14
+
15
+ from collections.abc import Sequence
16
+
17
+ from alembic import context, op
18
+
19
+ # revision identifiers, used by Alembic.
20
+ revision: str = "o0j1k2l3m4n5"
21
+ down_revision: str | Sequence[str] | None = "n9i0j1k2l3m4"
22
+ branch_labels: str | Sequence[str] | None = None
23
+ depends_on: str | Sequence[str] | None = None
24
+
25
+
26
+ def _get_schema_prefix() -> str:
27
+ """Get schema prefix for table names (required for multi-tenant support)."""
28
+ schema = context.config.get_main_option("target_schema")
29
+ return f'"{schema}".' if schema else ""
30
+
31
+
32
+ def upgrade() -> None:
33
+ """Migrate data and clean up old mental models."""
34
+ schema = _get_schema_prefix()
35
+
36
+ # 1. Migrate 'pinned' mental models to pinned_reflections
37
+ # For pinned models, the first observation's content becomes the pinned reflection content
38
+ op.execute(f"""
39
+ INSERT INTO {schema}pinned_reflections (bank_id, name, source_query, content, tags, created_at)
40
+ SELECT
41
+ bank_id,
42
+ name,
43
+ description AS source_query,
44
+ COALESCE(
45
+ observations->'observations'->0->>'content',
46
+ description,
47
+ ''
48
+ ) AS content,
49
+ tags,
50
+ created_at
51
+ FROM {schema}mental_models
52
+ WHERE subtype = 'pinned'
53
+ ON CONFLICT DO NOTHING
54
+ """)
55
+
56
+ # 2. Migrate 'learned' mental models to learnings
57
+ # Each observation in a learned model becomes a separate learning
58
+ op.execute(f"""
59
+ INSERT INTO {schema}learnings (bank_id, text, proof_count, tags, created_at)
60
+ SELECT
61
+ mm.bank_id,
62
+ obs->>'content' AS text,
63
+ GREATEST(1, COALESCE(jsonb_array_length(obs->'evidence'), 1)) AS proof_count,
64
+ mm.tags,
65
+ mm.created_at
66
+ FROM {schema}mental_models mm,
67
+ LATERAL jsonb_array_elements(mm.observations->'observations') AS obs
68
+ WHERE mm.subtype = 'learned'
69
+ AND obs->>'content' IS NOT NULL
70
+ AND obs->>'content' != ''
71
+ ON CONFLICT DO NOTHING
72
+ """)
73
+
74
+ # 3. Delete all non-directive mental models (they've been migrated or are obsolete)
75
+ op.execute(f"""
76
+ DELETE FROM {schema}mental_models
77
+ WHERE subtype != 'directive'
78
+ """)
79
+
80
+ # 4. Drop the mental_model_versions table (no longer used)
81
+ op.execute(f"DROP TABLE IF EXISTS {schema}mental_model_versions CASCADE")
82
+
83
+ # 5. Drop old constraints and add new one that only allows 'directive'
84
+ op.execute(f"ALTER TABLE {schema}mental_models DROP CONSTRAINT IF EXISTS ck_mental_models_subtype")
85
+ op.execute(f"""
86
+ ALTER TABLE {schema}mental_models
87
+ ADD CONSTRAINT ck_mental_models_subtype CHECK (subtype = 'directive')
88
+ """)
89
+
90
+
91
+ def downgrade() -> None:
92
+ """Reverse the migration (data migration is one-way, so this just removes constraints)."""
93
+ schema = _get_schema_prefix()
94
+
95
+ # Remove the directive-only constraint
96
+ op.execute(f"ALTER TABLE {schema}mental_models DROP CONSTRAINT IF EXISTS ck_mental_models_subtype")
97
+
98
+ # Re-create mental_model_versions table
99
+ op.execute(f"""
100
+ CREATE TABLE IF NOT EXISTS {schema}mental_model_versions (
101
+ id SERIAL PRIMARY KEY,
102
+ bank_id VARCHAR(64) NOT NULL,
103
+ model_id VARCHAR(128) NOT NULL,
104
+ version INT NOT NULL,
105
+ observations JSONB NOT NULL,
106
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
107
+ )
108
+ """)
109
+ op.execute(
110
+ f"CREATE INDEX IF NOT EXISTS idx_mm_versions_lookup ON {schema}mental_model_versions(bank_id, model_id, version DESC)"
111
+ )
112
+
113
+ # Note: Data migration cannot be reversed - pinned_reflections and learnings data remains
@@ -0,0 +1,194 @@
1
+ """new_knowledge_architecture
2
+
3
+ Revision ID: p1k2l3m4n5o6
4
+ Revises: o0j1k2l3m4n5
5
+ Create Date: 2026-01-21 00:00:00.000000
6
+
7
+ This migration implements the new knowledge architecture:
8
+ 1. Drops the 'learnings' table (mental models are now in memory_units)
9
+ 2. Renames 'pinned_reflections' to 'reflections'
10
+ 3. Drops the 'mental_models' table completely
11
+ 4. Creates 'directives' table for hard rules
12
+ 5. Adds mental model support columns to 'memory_units' (proof_count, source_memory_ids, history)
13
+
14
+ The new architecture:
15
+ - Directives: Hard rules in their own table
16
+ - Mental Models: Stored in memory_units with fact_type='mental_model'
17
+ - Reflections: User-curated documents (renamed from pinned_reflections)
18
+ """
19
+
20
+ from collections.abc import Sequence
21
+
22
+ from alembic import context, op
23
+
24
+ # revision identifiers, used by Alembic.
25
+ revision: str = "p1k2l3m4n5o6"
26
+ down_revision: str | Sequence[str] | None = "o0j1k2l3m4n5"
27
+ branch_labels: str | Sequence[str] | None = None
28
+ depends_on: str | Sequence[str] | None = None
29
+
30
+
31
+ def _get_schema_prefix() -> str:
32
+ """Get schema prefix for table names (required for multi-tenant support)."""
33
+ schema = context.config.get_main_option("target_schema")
34
+ return f'"{schema}".' if schema else ""
35
+
36
+
37
+ def upgrade() -> None:
38
+ """Implement new knowledge architecture."""
39
+ schema = _get_schema_prefix()
40
+
41
+ # 1. Drop the learnings table (mental models will be in memory_units)
42
+ op.execute(f"DROP TABLE IF EXISTS {schema}learnings CASCADE")
43
+
44
+ # 2. Rename pinned_reflections to reflections
45
+ op.execute(f"ALTER TABLE IF EXISTS {schema}pinned_reflections RENAME TO reflections")
46
+
47
+ # Rename indexes for reflections
48
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_pinned_reflections_bank_id RENAME TO idx_reflections_bank_id")
49
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_pinned_reflections_embedding RENAME TO idx_reflections_embedding")
50
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_pinned_reflections_tags RENAME TO idx_reflections_tags")
51
+ op.execute(
52
+ f"ALTER INDEX IF EXISTS {schema}idx_pinned_reflections_text_search RENAME TO idx_reflections_text_search"
53
+ )
54
+
55
+ # Rename foreign key constraint
56
+ op.execute(f"""
57
+ ALTER TABLE {schema}reflections
58
+ DROP CONSTRAINT IF EXISTS fk_pinned_reflections_bank_id
59
+ """)
60
+ op.execute(f"""
61
+ ALTER TABLE {schema}reflections
62
+ ADD CONSTRAINT fk_reflections_bank_id
63
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
64
+ """)
65
+
66
+ # 3. Drop the mental_models table completely
67
+ op.execute(f"DROP TABLE IF EXISTS {schema}mental_models CASCADE")
68
+
69
+ # 4. Create directives table
70
+ op.execute(f"""
71
+ CREATE TABLE {schema}directives (
72
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
73
+ bank_id VARCHAR(64) NOT NULL,
74
+ name VARCHAR(256) NOT NULL,
75
+ content TEXT NOT NULL,
76
+ priority INT NOT NULL DEFAULT 0,
77
+ is_active BOOLEAN NOT NULL DEFAULT TRUE,
78
+ tags VARCHAR[] DEFAULT ARRAY[]::VARCHAR[],
79
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
80
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
81
+ )
82
+ """)
83
+
84
+ # Add foreign key and indexes for directives
85
+ op.execute(f"""
86
+ ALTER TABLE {schema}directives
87
+ ADD CONSTRAINT fk_directives_bank_id
88
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
89
+ """)
90
+ op.execute(f"CREATE INDEX idx_directives_bank_id ON {schema}directives(bank_id)")
91
+ op.execute(f"CREATE INDEX idx_directives_bank_active ON {schema}directives(bank_id, is_active)")
92
+ op.execute(f"CREATE INDEX idx_directives_tags ON {schema}directives USING GIN(tags)")
93
+
94
+ # 5. Add mental model support columns to memory_units
95
+ # proof_count: Number of memories that support this mental model
96
+ op.execute(f"""
97
+ ALTER TABLE {schema}memory_units
98
+ ADD COLUMN IF NOT EXISTS proof_count INT DEFAULT 1
99
+ """)
100
+
101
+ # source_memory_ids: Array of memory IDs that consolidated into this mental model
102
+ op.execute(f"""
103
+ ALTER TABLE {schema}memory_units
104
+ ADD COLUMN IF NOT EXISTS source_memory_ids UUID[] DEFAULT ARRAY[]::UUID[]
105
+ """)
106
+
107
+ # history: JSONB array tracking changes to mental models
108
+ op.execute(f"""
109
+ ALTER TABLE {schema}memory_units
110
+ ADD COLUMN IF NOT EXISTS history JSONB DEFAULT '[]'::jsonb
111
+ """)
112
+
113
+ # Add index for finding mental models
114
+ op.execute(f"""
115
+ CREATE INDEX IF NOT EXISTS idx_memory_units_mental_models
116
+ ON {schema}memory_units(bank_id, fact_type)
117
+ WHERE fact_type = 'mental_model'
118
+ """)
119
+
120
+ # 6. Update fact_type check constraint to include 'mental_model'
121
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
122
+ op.execute(f"""
123
+ ALTER TABLE {schema}memory_units
124
+ ADD CONSTRAINT memory_units_fact_type_check
125
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation', 'mental_model'))
126
+ """)
127
+
128
+
129
+ def downgrade() -> None:
130
+ """Reverse the migration."""
131
+ schema = _get_schema_prefix()
132
+
133
+ # Restore original fact_type check constraint (without 'mental_model')
134
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
135
+ op.execute(f"""
136
+ ALTER TABLE {schema}memory_units
137
+ ADD CONSTRAINT memory_units_fact_type_check
138
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation'))
139
+ """)
140
+
141
+ # Drop mental model columns from memory_units
142
+ op.execute(f"ALTER TABLE {schema}memory_units DROP COLUMN IF EXISTS proof_count")
143
+ op.execute(f"ALTER TABLE {schema}memory_units DROP COLUMN IF EXISTS source_memory_ids")
144
+ op.execute(f"ALTER TABLE {schema}memory_units DROP COLUMN IF EXISTS history")
145
+ op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_mental_models")
146
+
147
+ # Drop directives table
148
+ op.execute(f"DROP TABLE IF EXISTS {schema}directives CASCADE")
149
+
150
+ # Rename reflections back to pinned_reflections
151
+ op.execute(f"ALTER TABLE IF EXISTS {schema}reflections RENAME TO pinned_reflections")
152
+
153
+ # Restore indexes
154
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_bank_id RENAME TO idx_pinned_reflections_bank_id")
155
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_embedding RENAME TO idx_pinned_reflections_embedding")
156
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_tags RENAME TO idx_pinned_reflections_tags")
157
+ op.execute(
158
+ f"ALTER INDEX IF EXISTS {schema}idx_reflections_text_search RENAME TO idx_pinned_reflections_text_search"
159
+ )
160
+
161
+ # Restore foreign key
162
+ op.execute(f"""
163
+ ALTER TABLE {schema}pinned_reflections
164
+ DROP CONSTRAINT IF EXISTS fk_reflections_bank_id
165
+ """)
166
+ op.execute(f"""
167
+ ALTER TABLE {schema}pinned_reflections
168
+ ADD CONSTRAINT fk_pinned_reflections_bank_id
169
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
170
+ """)
171
+
172
+ # Re-create learnings table
173
+ op.execute(f"""
174
+ CREATE TABLE IF NOT EXISTS {schema}learnings (
175
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
176
+ bank_id VARCHAR(64) NOT NULL,
177
+ text TEXT NOT NULL,
178
+ proof_count INT NOT NULL DEFAULT 1,
179
+ history JSONB DEFAULT '[]'::jsonb,
180
+ mission_context VARCHAR(64),
181
+ pre_mission_change BOOLEAN DEFAULT FALSE,
182
+ embedding vector(384),
183
+ tags VARCHAR[] DEFAULT ARRAY[]::VARCHAR[],
184
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
185
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
186
+ )
187
+ """)
188
+ op.execute(f"""
189
+ ALTER TABLE {schema}learnings
190
+ ADD CONSTRAINT fk_learnings_bank_id
191
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
192
+ """)
193
+
194
+ # Note: mental_models table recreation is complex and would need separate handling
@@ -0,0 +1,50 @@
1
+ """fix_mental_model_fact_type
2
+
3
+ Revision ID: q2l3m4n5o6p7
4
+ Revises: p1k2l3m4n5o6
5
+ Create Date: 2026-01-21 13:30:00.000000
6
+
7
+ Fix the fact_type check constraint to include 'mental_model'.
8
+ This is a fix for p1k2l3m4n5o6 which should have included this change.
9
+ """
10
+
11
+ from collections.abc import Sequence
12
+
13
+ from alembic import context, op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "q2l3m4n5o6p7"
17
+ down_revision: str | Sequence[str] | None = "p1k2l3m4n5o6"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def _get_schema_prefix() -> str:
23
+ """Get schema prefix for table names (required for multi-tenant support)."""
24
+ schema = context.config.get_main_option("target_schema")
25
+ return f'"{schema}".' if schema else ""
26
+
27
+
28
+ def upgrade() -> None:
29
+ """Add 'mental_model' to the fact_type check constraint."""
30
+ schema = _get_schema_prefix()
31
+
32
+ # Drop the old constraint and add the new one with mental_model included
33
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
34
+ op.execute(f"""
35
+ ALTER TABLE {schema}memory_units
36
+ ADD CONSTRAINT memory_units_fact_type_check
37
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation', 'mental_model'))
38
+ """)
39
+
40
+
41
+ def downgrade() -> None:
42
+ """Remove 'mental_model' from the fact_type check constraint."""
43
+ schema = _get_schema_prefix()
44
+
45
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
46
+ op.execute(f"""
47
+ ALTER TABLE {schema}memory_units
48
+ ADD CONSTRAINT memory_units_fact_type_check
49
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation'))
50
+ """)
@@ -0,0 +1,47 @@
1
+ """Add reflect_response JSONB column to reflections
2
+
3
+ Revision ID: r3m4n5o6p7q8
4
+ Revises: q2l3m4n5o6p7
5
+ Create Date: 2026-01-21
6
+
7
+ This migration adds a reflect_response JSONB column to store the full
8
+ reflect API response payload, including based_on facts and trace data.
9
+
10
+ Note: Table was renamed from pinned_reflections to reflections in p1k2l3m4n5o6.
11
+ """
12
+
13
+ from collections.abc import Sequence
14
+
15
+ from alembic import context, op
16
+
17
+ revision: str = "r3m4n5o6p7q8"
18
+ down_revision: str | Sequence[str] | None = "q2l3m4n5o6p7"
19
+ branch_labels: str | Sequence[str] | None = None
20
+ depends_on: str | Sequence[str] | None = None
21
+
22
+
23
+ def _get_schema_prefix() -> str:
24
+ """Get schema prefix for table names (required for multi-tenant support)."""
25
+ schema = context.config.get_main_option("target_schema")
26
+ return f'"{schema}".' if schema else ""
27
+
28
+
29
+ def upgrade() -> None:
30
+ """Add reflect_response JSONB column to reflections."""
31
+ schema = _get_schema_prefix()
32
+
33
+ # Add reflect_response column to store the full reflect API response
34
+ op.execute(f"""
35
+ ALTER TABLE {schema}reflections
36
+ ADD COLUMN IF NOT EXISTS reflect_response JSONB
37
+ """)
38
+
39
+
40
+ def downgrade() -> None:
41
+ """Remove reflect_response column from reflections."""
42
+ schema = _get_schema_prefix()
43
+
44
+ op.execute(f"""
45
+ ALTER TABLE {schema}reflections
46
+ DROP COLUMN IF EXISTS reflect_response
47
+ """)
@@ -0,0 +1,53 @@
1
+ """Add consolidated_at column to memory_units for incremental consolidation tracking.
2
+
3
+ This allows consolidation to track progress at the memory level rather than
4
+ using a bank-level watermark. If consolidation crashes, already-processed
5
+ memories won't be reprocessed.
6
+
7
+ Revision ID: s4n5o6p7q8r9
8
+ Revises: r3m4n5o6p7q8
9
+ Create Date: 2025-01-22
10
+ """
11
+
12
+ from collections.abc import Sequence
13
+
14
+ from alembic import context, op
15
+
16
+ revision: str = "s4n5o6p7q8r9"
17
+ down_revision: str | Sequence[str] | None = "r3m4n5o6p7q8"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def _get_schema_prefix() -> str:
23
+ """Get schema prefix for table names (required for multi-tenant support)."""
24
+ schema = context.config.get_main_option("target_schema")
25
+ return f'"{schema}".' if schema else ""
26
+
27
+
28
+ def upgrade() -> None:
29
+ schema = _get_schema_prefix()
30
+
31
+ # Add consolidated_at column to memory_units
32
+ op.execute(
33
+ f"""
34
+ ALTER TABLE {schema}memory_units
35
+ ADD COLUMN IF NOT EXISTS consolidated_at TIMESTAMPTZ DEFAULT NULL
36
+ """
37
+ )
38
+
39
+ # Create index for efficient querying of unconsolidated memories
40
+ op.execute(
41
+ f"""
42
+ CREATE INDEX IF NOT EXISTS idx_memory_units_unconsolidated
43
+ ON {schema}memory_units (bank_id, created_at)
44
+ WHERE consolidated_at IS NULL AND fact_type IN ('experience', 'world')
45
+ """
46
+ )
47
+
48
+
49
+ def downgrade() -> None:
50
+ schema = _get_schema_prefix()
51
+
52
+ op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_unconsolidated")
53
+ op.execute(f"ALTER TABLE {schema}memory_units DROP COLUMN IF EXISTS consolidated_at")
@@ -0,0 +1,134 @@
1
+ """Rename mental_model fact_type to observation and reflections table to mental_models
2
+
3
+ Revision ID: t5o6p7q8r9s0
4
+ Revises: s4n5o6p7q8r9
5
+ Create Date: 2026-01-26
6
+
7
+ This migration implements the terminology rename:
8
+ 1. mental_model (fact_type in memory_units) -> observation
9
+ 2. reflections table -> mental_models table
10
+
11
+ The new terminology:
12
+ - Observations: Consolidated knowledge synthesized from facts (was mental_model)
13
+ - Mental Models: Stored reflect responses (was reflections)
14
+ """
15
+
16
+ from collections.abc import Sequence
17
+
18
+ from alembic import context, op
19
+
20
+ revision: str = "t5o6p7q8r9s0"
21
+ down_revision: str | Sequence[str] | None = "s4n5o6p7q8r9"
22
+ branch_labels: str | Sequence[str] | None = None
23
+ depends_on: str | Sequence[str] | None = None
24
+
25
+
26
+ def _get_schema_prefix() -> str:
27
+ """Get schema prefix for table names (required for multi-tenant support)."""
28
+ schema = context.config.get_main_option("target_schema")
29
+ return f'"{schema}".' if schema else ""
30
+
31
+
32
+ def upgrade() -> None:
33
+ """Rename mental_model -> observation and reflections -> mental_models."""
34
+ schema = _get_schema_prefix()
35
+
36
+ # 1. Update fact_type values: mental_model -> observation
37
+ op.execute(f"""
38
+ UPDATE {schema}memory_units
39
+ SET fact_type = 'observation'
40
+ WHERE fact_type = 'mental_model'
41
+ """)
42
+
43
+ # 2. Update the CHECK constraint - remove mental_model, keep observation
44
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
45
+ op.execute(f"""
46
+ ALTER TABLE {schema}memory_units
47
+ ADD CONSTRAINT memory_units_fact_type_check
48
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation'))
49
+ """)
50
+
51
+ # 3. Rename the index for observations (was for mental_models)
52
+ op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_mental_models")
53
+ op.execute(f"""
54
+ CREATE INDEX IF NOT EXISTS idx_memory_units_observations
55
+ ON {schema}memory_units(bank_id, fact_type)
56
+ WHERE fact_type = 'observation'
57
+ """)
58
+
59
+ # 4. Update the unconsolidated index to not filter by fact_type since observations
60
+ # are now the consolidated type
61
+ op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_unconsolidated")
62
+ op.execute(f"""
63
+ CREATE INDEX IF NOT EXISTS idx_memory_units_unconsolidated
64
+ ON {schema}memory_units (bank_id, created_at)
65
+ WHERE consolidated_at IS NULL AND fact_type IN ('experience', 'world')
66
+ """)
67
+
68
+ # 5. Rename reflections table to mental_models
69
+ op.execute(f"ALTER TABLE IF EXISTS {schema}reflections RENAME TO mental_models")
70
+
71
+ # 6. Rename indexes for mental_models (was reflections)
72
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_bank_id RENAME TO idx_mental_models_bank_id")
73
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_embedding RENAME TO idx_mental_models_embedding")
74
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_tags RENAME TO idx_mental_models_tags")
75
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_text_search RENAME TO idx_mental_models_text_search")
76
+
77
+ # 7. Rename foreign key constraint
78
+ op.execute(f"""
79
+ ALTER TABLE {schema}mental_models
80
+ DROP CONSTRAINT IF EXISTS fk_reflections_bank_id
81
+ """)
82
+ op.execute(f"""
83
+ ALTER TABLE {schema}mental_models
84
+ ADD CONSTRAINT fk_mental_models_bank_id
85
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
86
+ """)
87
+
88
+
89
+ def downgrade() -> None:
90
+ """Reverse: observation -> mental_model and mental_models -> reflections."""
91
+ schema = _get_schema_prefix()
92
+
93
+ # 1. Rename mental_models table back to reflections
94
+ op.execute(f"ALTER TABLE IF EXISTS {schema}mental_models RENAME TO reflections")
95
+
96
+ # 2. Rename indexes back
97
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_bank_id RENAME TO idx_reflections_bank_id")
98
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_embedding RENAME TO idx_reflections_embedding")
99
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_tags RENAME TO idx_reflections_tags")
100
+ op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_text_search RENAME TO idx_reflections_text_search")
101
+
102
+ # 3. Rename foreign key back
103
+ op.execute(f"""
104
+ ALTER TABLE {schema}reflections
105
+ DROP CONSTRAINT IF EXISTS fk_mental_models_bank_id
106
+ """)
107
+ op.execute(f"""
108
+ ALTER TABLE {schema}reflections
109
+ ADD CONSTRAINT fk_reflections_bank_id
110
+ FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE
111
+ """)
112
+
113
+ # 4. Update fact_type values: observation -> mental_model
114
+ op.execute(f"""
115
+ UPDATE {schema}memory_units
116
+ SET fact_type = 'mental_model'
117
+ WHERE fact_type = 'observation'
118
+ """)
119
+
120
+ # 5. Update the CHECK constraint back
121
+ op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check")
122
+ op.execute(f"""
123
+ ALTER TABLE {schema}memory_units
124
+ ADD CONSTRAINT memory_units_fact_type_check
125
+ CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation', 'mental_model'))
126
+ """)
127
+
128
+ # 6. Rename index back
129
+ op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_observations")
130
+ op.execute(f"""
131
+ CREATE INDEX IF NOT EXISTS idx_memory_units_mental_models
132
+ ON {schema}memory_units(bank_id, fact_type)
133
+ WHERE fact_type = 'mental_model'
134
+ """)
@@ -0,0 +1,41 @@
1
+ """Change mental_models.id from UUID to TEXT
2
+
3
+ Revision ID: u6p7q8r9s0t1
4
+ Revises: t5o6p7q8r9s0
5
+ Create Date: 2026-01-27
6
+
7
+ This migration changes the mental_models.id column from UUID to TEXT
8
+ to support user-defined text identifiers like 'team-communication' instead of UUIDs.
9
+ """
10
+
11
+ from collections.abc import Sequence
12
+
13
+ from alembic import context, op
14
+
15
+ revision: str = "u6p7q8r9s0t1"
16
+ down_revision: str | Sequence[str] | None = "t5o6p7q8r9s0"
17
+ branch_labels: str | Sequence[str] | None = None
18
+ depends_on: str | Sequence[str] | None = None
19
+
20
+
21
+ def _get_schema_prefix() -> str:
22
+ """Get schema prefix for table names (required for multi-tenant support)."""
23
+ schema = context.config.get_main_option("target_schema")
24
+ return f'"{schema}".' if schema else ""
25
+
26
+
27
+ def upgrade() -> None:
28
+ """Change mental_models.id from UUID to TEXT."""
29
+ schema = _get_schema_prefix()
30
+
31
+ # Change the id column type from UUID to TEXT
32
+ # Existing UUIDs will be converted to their string representation
33
+ op.execute(f"ALTER TABLE {schema}mental_models ALTER COLUMN id TYPE TEXT USING id::TEXT")
34
+
35
+
36
+ def downgrade() -> None:
37
+ """Revert mental_models.id from TEXT to UUID."""
38
+ schema = _get_schema_prefix()
39
+
40
+ # Note: This will fail if any id values are not valid UUIDs
41
+ op.execute(f"ALTER TABLE {schema}mental_models ALTER COLUMN id TYPE UUID USING id::UUID")
@@ -0,0 +1,50 @@
1
+ """Add max_tokens and trigger columns to mental_models
2
+
3
+ Revision ID: v7q8r9s0t1u2
4
+ Revises: u6p7q8r9s0t1
5
+ Create Date: 2026-01-27
6
+
7
+ This migration adds:
8
+ - max_tokens column: token limit for content generation during refresh
9
+ - trigger column: JSONB for trigger settings (e.g., refresh_after_consolidation)
10
+ """
11
+
12
+ from collections.abc import Sequence
13
+
14
+ from alembic import context, op
15
+
16
+ revision: str = "v7q8r9s0t1u2"
17
+ down_revision: str | Sequence[str] | None = "u6p7q8r9s0t1"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def _get_schema_prefix() -> str:
23
+ """Get schema prefix for table names (required for multi-tenant support)."""
24
+ schema = context.config.get_main_option("target_schema")
25
+ return f'"{schema}".' if schema else ""
26
+
27
+
28
+ def upgrade() -> None:
29
+ """Add max_tokens and trigger columns to mental_models."""
30
+ schema = _get_schema_prefix()
31
+
32
+ op.execute(f"""
33
+ ALTER TABLE {schema}mental_models
34
+ ADD COLUMN IF NOT EXISTS max_tokens INT NOT NULL DEFAULT 2048
35
+ """)
36
+
37
+ # trigger column stores trigger settings as JSONB
38
+ # Default: refresh_after_consolidation = false (not "real time")
39
+ op.execute(f"""
40
+ ALTER TABLE {schema}mental_models
41
+ ADD COLUMN IF NOT EXISTS trigger JSONB NOT NULL DEFAULT '{{"refresh_after_consolidation": false}}'::jsonb
42
+ """)
43
+
44
+
45
+ def downgrade() -> None:
46
+ """Remove max_tokens and trigger columns from mental_models."""
47
+ schema = _get_schema_prefix()
48
+
49
+ op.execute(f"ALTER TABLE {schema}mental_models DROP COLUMN IF EXISTS max_tokens")
50
+ op.execute(f"ALTER TABLE {schema}mental_models DROP COLUMN IF EXISTS trigger")