@effect-agent/storage-sqlite 0.1.0-beta.8 → 0.1.0-beta.80

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 (60) hide show
  1. package/dist/SqliteActivityStore.d.mts +14 -0
  2. package/dist/SqliteActivityStore.mjs +310 -0
  3. package/dist/SqliteActivityStore.mjs.map +1 -0
  4. package/dist/SqliteMessageDeliveryStore.d.mts +14 -0
  5. package/dist/SqliteMessageDeliveryStore.mjs +24 -0
  6. package/dist/SqliteMessageDeliveryStore.mjs.map +1 -0
  7. package/dist/SqliteScheduleStore.d.mts +14 -0
  8. package/dist/SqliteScheduleStore.mjs +255 -0
  9. package/dist/SqliteScheduleStore.mjs.map +1 -0
  10. package/dist/SqliteStorageConfig.d.mts +34 -0
  11. package/dist/SqliteStorageConfig.mjs +39 -0
  12. package/dist/SqliteStorageConfig.mjs.map +1 -0
  13. package/dist/SqliteStorageError-DVWeaWLm.d.mts +86 -0
  14. package/dist/SqliteStorageError.d.mts +2 -0
  15. package/dist/SqliteStorageError.mjs +150 -0
  16. package/dist/SqliteStorageError.mjs.map +1 -0
  17. package/dist/SqliteStorageFailpoint.d.mts +17 -0
  18. package/dist/SqliteStorageFailpoint.mjs +14 -0
  19. package/dist/SqliteStorageFailpoint.mjs.map +1 -0
  20. package/dist/SqliteStorageFailpointTesting.d.mts +15 -0
  21. package/dist/SqliteStorageFailpointTesting.mjs +19 -0
  22. package/dist/SqliteStorageFailpointTesting.mjs.map +1 -0
  23. package/dist/SqliteStorageVersion-BqmqX0CI.d.mts +11 -0
  24. package/dist/SqliteStorageVersion.d.mts +2 -0
  25. package/dist/SqliteStorageVersion.mjs +8 -0
  26. package/dist/SqliteStorageVersion.mjs.map +1 -0
  27. package/dist/SqliteSubmissionLedger.d.mts +23 -0
  28. package/dist/SqliteSubmissionLedger.mjs +1788 -0
  29. package/dist/SqliteSubmissionLedger.mjs.map +1 -0
  30. package/dist/SqliteSubscriptionStore.d.mts +13 -0
  31. package/dist/SqliteSubscriptionStore.mjs +28 -0
  32. package/dist/SqliteSubscriptionStore.mjs.map +1 -0
  33. package/dist/SqliteThreadStore.d.mts +49 -0
  34. package/dist/SqliteThreadStore.mjs +460 -0
  35. package/dist/SqliteThreadStore.mjs.map +1 -0
  36. package/dist/index.d.mts +11 -193
  37. package/dist/index.mjs +11 -3082
  38. package/dist/migrations-Dy5v0HGe.mjs +346 -0
  39. package/dist/migrations-Dy5v0HGe.mjs.map +1 -0
  40. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  41. package/dist/sqlite-journal-D9xFxCw-.mjs +998 -0
  42. package/dist/sqlite-journal-D9xFxCw-.mjs.map +1 -0
  43. package/package.json +1 -41
  44. package/src/SqliteActivityStore.ts +556 -0
  45. package/src/SqliteMessageDeliveryStore.ts +42 -0
  46. package/src/SqliteScheduleStore.ts +404 -0
  47. package/src/{sqlite-storage-config.ts → SqliteStorageConfig.ts} +1 -1
  48. package/src/{errors.ts → SqliteStorageError.ts} +10 -3
  49. package/src/SqliteStorageFailpoint.ts +23 -0
  50. package/src/{sqlite-storage-failpoint.ts → SqliteStorageFailpointTesting.ts} +7 -18
  51. package/src/SqliteStorageVersion.ts +2 -0
  52. package/src/{sqlite-ledger.ts → SqliteSubmissionLedger.ts} +857 -157
  53. package/src/SqliteSubscriptionStore.ts +59 -0
  54. package/src/{sqlite-conversation-store.ts → SqliteThreadStore.ts} +448 -288
  55. package/src/index.ts +10 -6
  56. package/src/internal/message-delivery-schema.ts +24 -0
  57. package/src/{migrations.ts → internal/migrations.ts} +156 -79
  58. package/src/internal/recovery-checkpoint-schema.ts +17 -0
  59. package/src/{sqlite-journal.ts → internal/sqlite-journal.ts} +752 -239
  60. package/dist/index.mjs.map +0 -1
package/src/index.ts CHANGED
@@ -1,6 +1,10 @@
1
- export * from "./errors.ts";
2
- export * from "./migrations.ts";
3
- export * from "./sqlite-conversation-store.ts";
4
- export * from "./sqlite-ledger.ts";
5
- export * from "./sqlite-storage-config.ts";
6
- export * from "./sqlite-storage-failpoint.ts";
1
+ export * as SqliteMessageDeliveryStore from "./SqliteMessageDeliveryStore.ts";
2
+ export * as SqliteActivityStore from "./SqliteActivityStore.ts";
3
+ export * as SqliteScheduleStore from "./SqliteScheduleStore.ts";
4
+ export * as SqliteStorageConfig from "./SqliteStorageConfig.ts";
5
+ export * as SqliteStorageError from "./SqliteStorageError.ts";
6
+ export * as SqliteStorageFailpoint from "./SqliteStorageFailpoint.ts";
7
+ export * as SqliteStorageVersion from "./SqliteStorageVersion.ts";
8
+ export * as SqliteSubmissionLedger from "./SqliteSubmissionLedger.ts";
9
+ export * as SqliteSubscriptionStore from "./SqliteSubscriptionStore.ts";
10
+ export * as SqliteThreadStore from "./SqliteThreadStore.ts";
@@ -0,0 +1,24 @@
1
+ import { Effect } from "effect";
2
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
3
+
4
+ /** Additive storage owned by this adapter; creation participates in its version transaction. */
5
+ export const createMessageDeliveryTables = Effect.gen(function* () {
6
+ const sql = yield* SqlClient.SqlClient;
7
+
8
+ yield* sql`
9
+ CREATE TABLE effect_agent_message_deliveries (
10
+ owner_thread_id TEXT NOT NULL,
11
+ message_id TEXT NOT NULL,
12
+ version INTEGER NOT NULL,
13
+ state TEXT NOT NULL,
14
+ deadline_at_millis INTEGER,
15
+ record_json TEXT NOT NULL,
16
+ PRIMARY KEY (owner_thread_id, message_id)
17
+ )
18
+ `.withoutTransform;
19
+ yield* sql`
20
+ CREATE INDEX effect_agent_message_deliveries_due
21
+ ON effect_agent_message_deliveries (deadline_at_millis, owner_thread_id, message_id)
22
+ WHERE deadline_at_millis IS NOT NULL
23
+ `.withoutTransform;
24
+ });
@@ -2,15 +2,27 @@ import { SqliteMigrator } from "@effect/sql-sqlite-node";
2
2
  import { Effect } from "effect";
3
3
  import * as SqlClient from "effect/unstable/sql/SqlClient";
4
4
 
5
- export const CurrentSqliteStorageVersion = 4;
5
+ import { createMessageDeliveryTables } from "./message-delivery-schema.ts";
6
+ import { createRecoveryCheckpointTable } from "./recovery-checkpoint-schema.ts";
6
7
 
8
+ export const CurrentSqliteStorageVersion = 11;
9
+
10
+ /** Index only outstanding obligations, ordered by the recovery scan's stable cursor. */
11
+ export const createNonterminalIndex = Effect.gen(function* () {
12
+ const sql = yield* SqlClient.SqlClient;
13
+
14
+ yield* sql`CREATE INDEX effect_agent_submissions_nonterminal ON effect_agent_submissions (thread_id, queue_sequence) WHERE state <> 'settled'`
15
+ .withoutTransform;
16
+ });
17
+
18
+ /** Initialize empty storage with the complete current schema. */
7
19
  export const sqliteMigrations = SqliteMigrator.fromRecord({
8
- "1_current_persistent_conversation_foundation": Effect.gen(function* () {
20
+ "1_current_thread_storage": Effect.gen(function* () {
9
21
  const sql = yield* SqlClient.SqlClient;
10
22
 
11
23
  yield* sql`
12
- CREATE TABLE effect_agent_conversations (
13
- conversation_id TEXT PRIMARY KEY NOT NULL,
24
+ CREATE TABLE effect_agent_threads (
25
+ thread_id TEXT PRIMARY KEY NOT NULL,
14
26
  created_at TEXT NOT NULL,
15
27
  tail_sequence INTEGER NOT NULL,
16
28
  tail_digest TEXT NOT NULL,
@@ -20,64 +32,59 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
20
32
 
21
33
  yield* sql`
22
34
  CREATE TABLE effect_agent_canonical_batches (
23
- conversation_id TEXT NOT NULL,
35
+ thread_id TEXT NOT NULL,
24
36
  batch_id TEXT NOT NULL,
25
37
  first_sequence INTEGER NOT NULL,
26
38
  last_sequence INTEGER NOT NULL,
27
39
  batch_digest TEXT NOT NULL,
28
40
  tail_digest TEXT NOT NULL,
29
41
  batch_json TEXT NOT NULL,
30
- PRIMARY KEY (conversation_id, batch_id),
31
- FOREIGN KEY (conversation_id)
32
- REFERENCES effect_agent_conversations(conversation_id)
42
+ PRIMARY KEY (thread_id, batch_id),
43
+ FOREIGN KEY (thread_id)
44
+ REFERENCES effect_agent_threads(thread_id)
33
45
  ON DELETE RESTRICT
34
46
  )
35
47
  `.withoutTransform;
36
48
 
37
49
  yield* sql`
38
50
  CREATE TABLE effect_agent_canonical_records (
39
- conversation_id TEXT NOT NULL,
51
+ thread_id TEXT NOT NULL,
40
52
  sequence INTEGER NOT NULL,
41
53
  record_id TEXT NOT NULL,
42
54
  batch_id TEXT NOT NULL,
43
55
  record_json TEXT NOT NULL,
44
- PRIMARY KEY (conversation_id, sequence),
45
- UNIQUE (conversation_id, record_id),
46
- FOREIGN KEY (conversation_id, batch_id)
47
- REFERENCES effect_agent_canonical_batches(conversation_id, batch_id)
56
+ PRIMARY KEY (thread_id, sequence),
57
+ UNIQUE (thread_id, record_id),
58
+ FOREIGN KEY (thread_id, batch_id)
59
+ REFERENCES effect_agent_canonical_batches(thread_id, batch_id)
48
60
  ON DELETE RESTRICT
49
61
  )
50
62
  `.withoutTransform;
51
63
 
52
64
  yield* sql`
53
65
  CREATE INDEX effect_agent_canonical_records_batch
54
- ON effect_agent_canonical_records (conversation_id, batch_id, sequence)
66
+ ON effect_agent_canonical_records (thread_id, batch_id, sequence)
55
67
  `.withoutTransform;
56
68
 
57
69
  yield* sql`
58
70
  CREATE TABLE effect_agent_checkpoints (
59
- conversation_id TEXT NOT NULL,
71
+ thread_id TEXT NOT NULL,
60
72
  through_sequence INTEGER NOT NULL,
61
73
  tail_digest TEXT NOT NULL,
62
74
  checkpoint_json TEXT NOT NULL,
63
- PRIMARY KEY (conversation_id, through_sequence),
64
- FOREIGN KEY (conversation_id)
65
- REFERENCES effect_agent_conversations(conversation_id)
75
+ PRIMARY KEY (thread_id, through_sequence),
76
+ FOREIGN KEY (thread_id)
77
+ REFERENCES effect_agent_threads(thread_id)
66
78
  ON DELETE RESTRICT
67
79
  )
68
80
  `.withoutTransform;
69
81
 
70
- yield* sql`PRAGMA user_version = 1`.withoutTransform;
71
- }),
72
- "2_durable_submission_ledger": Effect.gen(function* () {
73
- const sql = yield* SqlClient.SqlClient;
74
-
75
- // Admission rows exist before Conversation materialization (durability §4), so
76
- // conversation_id intentionally carries no foreign key into effect_agent_conversations.
82
+ // Admission rows exist before Thread materialization (durability §4), so
83
+ // thread_id intentionally carries no foreign key into effect_agent_threads.
77
84
  yield* sql`
78
85
  CREATE TABLE effect_agent_submissions (
79
86
  submission_id TEXT PRIMARY KEY NOT NULL,
80
- conversation_id TEXT NOT NULL,
87
+ thread_id TEXT NOT NULL,
81
88
  queue_sequence INTEGER NOT NULL,
82
89
  principal TEXT NOT NULL,
83
90
  idempotency_key TEXT NOT NULL,
@@ -93,11 +100,27 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
93
100
  ready_at TEXT,
94
101
  input_applied_record_id TEXT,
95
102
  input_applied_sequence INTEGER,
96
- UNIQUE (conversation_id, principal, idempotency_key),
97
- UNIQUE (conversation_id, queue_sequence)
103
+ joined_host_submission_id TEXT,
104
+ suspended_reason_json TEXT,
105
+ suspended_at TEXT,
106
+ unknown_reason TEXT,
107
+ unknown_tool_call_ids_json TEXT,
108
+ parent_submission_id TEXT,
109
+ parent_tool_call_id TEXT,
110
+ admission_group TEXT,
111
+ admission_fence_json TEXT,
112
+ worker_admission_json TEXT,
113
+ message_admission_json TEXT,
114
+ UNIQUE (thread_id, principal, idempotency_key),
115
+ UNIQUE (thread_id, queue_sequence)
98
116
  )
99
117
  `.withoutTransform;
100
118
 
119
+ yield* sql`
120
+ CREATE INDEX effect_agent_submissions_group
121
+ ON effect_agent_submissions (thread_id, admission_group, state)
122
+ `.withoutTransform;
123
+
101
124
  yield* sql`
102
125
  CREATE TABLE effect_agent_submission_ownership (
103
126
  submission_id TEXT PRIMARY KEY NOT NULL,
@@ -116,7 +139,7 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
116
139
  CREATE TABLE effect_agent_attempts (
117
140
  attempt_id TEXT PRIMARY KEY NOT NULL,
118
141
  submission_id TEXT NOT NULL,
119
- conversation_id TEXT NOT NULL,
142
+ thread_id TEXT NOT NULL,
120
143
  owner_producer_id TEXT NOT NULL,
121
144
  producer_epoch INTEGER NOT NULL,
122
145
  claimed_at TEXT NOT NULL,
@@ -155,42 +178,6 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
155
178
  )
156
179
  `.withoutTransform;
157
180
 
158
- yield* sql`PRAGMA user_version = 2`.withoutTransform;
159
- }),
160
- "3_durable_tools_and_joined_input": Effect.gen(function* () {
161
- const sql = yield* SqlClient.SqlClient;
162
-
163
- // Joined queued input (plan §2.5, DUR-016): a joining/joined Submission records which host
164
- // Run claimed it. The canonical input marker reuses input_applied_record_id/_sequence
165
- // because the joined input IS the Submission's own canonical `input:{sid}` record.
166
- yield* sql`
167
- ALTER TABLE effect_agent_submissions
168
- ADD COLUMN joined_host_submission_id TEXT
169
- `.withoutTransform;
170
-
171
- // Durable approval suspension (plan §2.6): the reason is the Schema-encoded
172
- // SuspensionReason union so later suspension families stay additive.
173
- yield* sql`
174
- ALTER TABLE effect_agent_submissions
175
- ADD COLUMN suspended_reason_json TEXT
176
- `.withoutTransform;
177
- yield* sql`
178
- ALTER TABLE effect_agent_submissions
179
- ADD COLUMN suspended_at TEXT
180
- `.withoutTransform;
181
-
182
- // Unknown Outcome marking (DUR-009/DUR-017): the marked open Tool Call identities are
183
- // stored so `recordUnknownResolution` can reopen the lane only when every marked call
184
- // has a durable resolution intent.
185
- yield* sql`
186
- ALTER TABLE effect_agent_submissions
187
- ADD COLUMN unknown_reason TEXT
188
- `.withoutTransform;
189
- yield* sql`
190
- ALTER TABLE effect_agent_submissions
191
- ADD COLUMN unknown_tool_call_ids_json TEXT
192
- `.withoutTransform;
193
-
194
181
  yield* sql`
195
182
  CREATE INDEX effect_agent_submissions_joined_host
196
183
  ON effect_agent_submissions (joined_host_submission_id)
@@ -226,21 +213,8 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
226
213
  )
227
214
  `.withoutTransform;
228
215
 
229
- yield* sql`PRAGMA user_version = 3`.withoutTransform;
230
- }),
231
- "4_durable_subagents": Effect.gen(function* () {
232
- const sql = yield* SqlClient.SqlClient;
233
-
234
216
  // Durable attached children (spec §12, SUB-004): a child Submission records its immutable
235
217
  // parent linkage at admission; the parent-side index serves the recovery attachment view.
236
- yield* sql`
237
- ALTER TABLE effect_agent_submissions
238
- ADD COLUMN parent_submission_id TEXT
239
- `.withoutTransform;
240
- yield* sql`
241
- ALTER TABLE effect_agent_submissions
242
- ADD COLUMN parent_tool_call_id TEXT
243
- `.withoutTransform;
244
218
  yield* sql`
245
219
  CREATE INDEX effect_agent_submissions_parent
246
220
  ON effect_agent_submissions (parent_submission_id)
@@ -270,6 +244,109 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
270
244
  )
271
245
  `.withoutTransform;
272
246
 
273
- yield* sql`PRAGMA user_version = 4`.withoutTransform;
247
+ // record_json is authoritative. The remaining columns support owner keyset paging and
248
+ // deadline queries without decoding unrelated future schedules.
249
+ yield* sql`
250
+ CREATE TABLE effect_agent_schedules (
251
+ tenant_id TEXT NOT NULL,
252
+ owner_id TEXT NOT NULL,
253
+ schedule_id TEXT NOT NULL,
254
+ deadline_at_millis INTEGER,
255
+ record_json TEXT NOT NULL,
256
+ PRIMARY KEY (tenant_id, owner_id, schedule_id)
257
+ )
258
+ `.withoutTransform;
259
+
260
+ yield* sql`
261
+ CREATE INDEX effect_agent_schedules_deadline
262
+ ON effect_agent_schedules (deadline_at_millis, tenant_id, owner_id, schedule_id)
263
+ WHERE deadline_at_millis IS NOT NULL
264
+ `.withoutTransform;
265
+
266
+ yield* sql`
267
+ CREATE INDEX effect_agent_schedules_owner_deadline
268
+ ON effect_agent_schedules (tenant_id, owner_id, deadline_at_millis, schedule_id)
269
+ WHERE deadline_at_millis IS NOT NULL
270
+ `.withoutTransform;
271
+
272
+ yield* sql`
273
+ CREATE TABLE effect_agent_subscription_sequences (
274
+ tenant_id TEXT NOT NULL,
275
+ source_address TEXT NOT NULL,
276
+ sequence INTEGER NOT NULL,
277
+ event_scan_cursor TEXT NOT NULL,
278
+ delivery_scan_cursor TEXT NOT NULL,
279
+ recovery_scan_cursor INTEGER NOT NULL,
280
+ PRIMARY KEY (tenant_id, source_address)
281
+ )
282
+ `.withoutTransform;
283
+ yield* sql`
284
+ CREATE TABLE effect_agent_subscriptions (
285
+ tenant_id TEXT NOT NULL,
286
+ source_address TEXT NOT NULL,
287
+ owner_id TEXT NOT NULL,
288
+ subscription_id TEXT NOT NULL,
289
+ ordinal INTEGER NOT NULL,
290
+ source_name TEXT NOT NULL,
291
+ source_version TEXT NOT NULL,
292
+ matching_key TEXT NOT NULL,
293
+ state TEXT NOT NULL,
294
+ expires_at_millis INTEGER,
295
+ recovery_at_millis INTEGER,
296
+ recovery_present INTEGER NOT NULL DEFAULT 0,
297
+ record_json TEXT NOT NULL,
298
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id),
299
+ UNIQUE (tenant_id, source_address, ordinal)
300
+ )
301
+ `.withoutTransform;
302
+ yield* sql`CREATE INDEX effect_agent_subscriptions_owner ON effect_agent_subscriptions (tenant_id, source_address, owner_id, ordinal)`
303
+ .withoutTransform;
304
+ yield* sql`CREATE INDEX effect_agent_subscriptions_candidates ON effect_agent_subscriptions (tenant_id, source_address, source_name, source_version, matching_key, ordinal)`
305
+ .withoutTransform;
306
+ yield* sql`CREATE INDEX effect_agent_subscriptions_recovery ON effect_agent_subscriptions (tenant_id, source_address, recovery_at_millis, ordinal) WHERE recovery_at_millis IS NOT NULL`
307
+ .withoutTransform;
308
+ yield* sql`
309
+ CREATE TABLE effect_agent_subscription_events (
310
+ tenant_id TEXT NOT NULL,
311
+ source_address TEXT NOT NULL,
312
+ event_id TEXT NOT NULL,
313
+ source_name TEXT NOT NULL,
314
+ source_version TEXT NOT NULL,
315
+ matching_key TEXT NOT NULL,
316
+ payload_digest TEXT NOT NULL,
317
+ cutoff INTEGER NOT NULL,
318
+ cursor INTEGER NOT NULL,
319
+ routing_complete INTEGER NOT NULL,
320
+ tombstone INTEGER NOT NULL DEFAULT 0,
321
+ next_attempt_at_millis INTEGER NOT NULL,
322
+ record_json TEXT NOT NULL,
323
+ PRIMARY KEY (tenant_id, source_address, event_id)
324
+ )
325
+ `.withoutTransform;
326
+ yield* sql`CREATE INDEX effect_agent_subscription_events_pending ON effect_agent_subscription_events (tenant_id, source_address, routing_complete, next_attempt_at_millis, event_id)`
327
+ .withoutTransform;
328
+ yield* sql`
329
+ CREATE TABLE effect_agent_subscription_deliveries (
330
+ tenant_id TEXT NOT NULL,
331
+ source_address TEXT NOT NULL,
332
+ owner_id TEXT NOT NULL,
333
+ subscription_id TEXT NOT NULL,
334
+ event_id TEXT NOT NULL,
335
+ delivery_key TEXT NOT NULL,
336
+ state TEXT NOT NULL,
337
+ next_attempt_at_millis INTEGER NOT NULL,
338
+ record_json TEXT NOT NULL,
339
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id, event_id),
340
+ UNIQUE (tenant_id, source_address, delivery_key)
341
+ )
342
+ `.withoutTransform;
343
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_pending ON effect_agent_subscription_deliveries (tenant_id, source_address, state, next_attempt_at_millis, delivery_key)`
344
+ .withoutTransform;
345
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_registration ON effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, delivery_key)`
346
+ .withoutTransform;
347
+ yield* createNonterminalIndex;
348
+ yield* createMessageDeliveryTables;
349
+ yield* createRecoveryCheckpointTable;
350
+ yield* sql`PRAGMA user_version = 11`.withoutTransform;
274
351
  }),
275
352
  });
@@ -0,0 +1,17 @@
1
+ import { Effect } from "effect";
2
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
3
+
4
+ /** One disposable recovery snapshot per Thread, independent of generic projections. */
5
+ export const createRecoveryCheckpointTable = Effect.gen(function* () {
6
+ const sql = yield* SqlClient.SqlClient;
7
+
8
+ yield* sql`
9
+ CREATE TABLE effect_agent_recovery_checkpoints (
10
+ thread_id TEXT PRIMARY KEY NOT NULL,
11
+ through_sequence INTEGER NOT NULL,
12
+ tail_digest TEXT NOT NULL,
13
+ checkpoint_json TEXT NOT NULL,
14
+ FOREIGN KEY (thread_id) REFERENCES effect_agent_threads(thread_id) ON DELETE RESTRICT
15
+ )
16
+ `.withoutTransform;
17
+ });