@effect-agent/storage-sqlite 0.1.0-beta.38 → 0.1.0-beta.40

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.
@@ -0,0 +1,95 @@
1
+ import { Context, Effect, Layer, Schema } from "effect";
2
+ //#region src/errors.d.ts
3
+ declare const SqliteStorageCompatibilityError_base: Schema.Class<SqliteStorageCompatibilityError, Schema.TaggedStruct<"SqliteStorageCompatibilityError", {
4
+ readonly actualVersion: Schema.Int;
5
+ readonly message: Schema.String;
6
+ readonly supportedVersion: Schema.Int;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ /** The SQLite file uses a private-development format this adapter cannot read. */
9
+ declare class SqliteStorageCompatibilityError extends SqliteStorageCompatibilityError_base {}
10
+ declare const SqliteStorageCorruptionError_base: Schema.Class<SqliteStorageCorruptionError, Schema.TaggedStruct<"SqliteStorageCorruptionError", {
11
+ readonly message: Schema.String;
12
+ readonly rowKey: Schema.String;
13
+ readonly table: Schema.String;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ /** Stored bytes failed the current Schema and cannot be used as recovery truth. */
16
+ declare class SqliteStorageCorruptionError extends SqliteStorageCorruptionError_base {}
17
+ declare const SqliteStorageError_base: Schema.Class<SqliteStorageError, Schema.TaggedStruct<"SqliteStorageError", {
18
+ readonly cause: Schema.optionalKey<Schema.Defect>;
19
+ readonly message: Schema.String;
20
+ readonly operation: Schema.String;
21
+ }>, import("effect/Cause").YieldableError>;
22
+ /** SQLite infrastructure failed while opening or operating the store. */
23
+ declare class SqliteStorageError extends SqliteStorageError_base {}
24
+ declare const SqliteLedgerError_base: Schema.Class<SqliteLedgerError, Schema.TaggedStruct<"SqliteLedgerError", {
25
+ readonly cause: Schema.optionalKey<Schema.Defect>;
26
+ readonly message: Schema.String;
27
+ readonly operation: Schema.String;
28
+ }>, import("effect/Cause").YieldableError>;
29
+ /**
30
+ * SQLite infrastructure failed while operating the Submission Ledger. Surfaces at the
31
+ * SubmissionLedger port as the typed `LedgerError` with this error preserved as its cause,
32
+ * so the adapter-level tag is never erased.
33
+ */
34
+ declare class SqliteLedgerError extends SqliteLedgerError_base {}
35
+ declare const SqliteAppendConflict_base: Schema.Class<SqliteAppendConflict, Schema.TaggedStruct<"SqliteAppendConflict", {
36
+ readonly message: Schema.String;
37
+ readonly reason: Schema.Literals<readonly ["batch-digest", "record-identity", "tail"]>;
38
+ readonly actualTailSequence: Schema.optionalKey<Schema.brand<Schema.Natural, "@effect-agent/thread/CanonicalSequence">>;
39
+ readonly actualTailDigest: Schema.optionalKey<Schema.String>;
40
+ }>, import("effect/Cause").YieldableError>;
41
+ /**
42
+ * A canonical batch retry conflicts with existing append state. Tail conflicts carry the
43
+ * actual committed tail as a diagnostic resume hint.
44
+ */
45
+ declare class SqliteAppendConflict extends SqliteAppendConflict_base {}
46
+ declare const SqliteFenceRejected_base: Schema.Class<SqliteFenceRejected, Schema.TaggedStruct<"SqliteFenceRejected", {
47
+ readonly actualEpoch: Schema.brand<Schema.Natural, "@effect-agent/thread/ProducerEpoch">;
48
+ readonly message: Schema.String;
49
+ readonly producerEpoch: Schema.brand<Schema.Natural, "@effect-agent/thread/ProducerEpoch">;
50
+ }>, import("effect/Cause").YieldableError>;
51
+ /**
52
+ * A producer epoch does not match the Thread's current writer registration. Appends
53
+ * require the exact registered epoch, so both older and newer unregistered epochs are fenced;
54
+ * a newer epoch takes over by materializing first.
55
+ */
56
+ declare class SqliteFenceRejected extends SqliteFenceRejected_base {}
57
+ declare const SqliteWriteContention_base: Schema.Class<SqliteWriteContention, Schema.TaggedStruct<"SqliteWriteContention", {
58
+ readonly cause: Schema.optionalKey<Schema.Defect>;
59
+ readonly message: Schema.String;
60
+ readonly operation: Schema.String;
61
+ }>, import("effect/Cause").YieldableError>;
62
+ /**
63
+ * A write transaction could not acquire the SQLite write lock within the configured busy
64
+ * timeout (SQLITE_BUSY / SQLITE_LOCKED). Another transiently coexisting owner is writing;
65
+ * the operation did not mutate canonical state and is safe to retry.
66
+ */
67
+ declare class SqliteWriteContention extends SqliteWriteContention_base {}
68
+ declare const SqliteCheckpointConflict_base: Schema.Class<SqliteCheckpointConflict, Schema.TaggedStruct<"SqliteCheckpointConflict", {
69
+ readonly message: Schema.String;
70
+ }>, import("effect/Cause").YieldableError>;
71
+ /** A checkpoint conflicts with a previously stored checkpoint at the same offset. */
72
+ declare class SqliteCheckpointConflict extends SqliteCheckpointConflict_base {}
73
+ declare const SqliteStorageFailpointLocation: Schema.Literals<readonly ["materialize:before", "materialize:after", "append:before", "append:after-batch-insert", "append:after-record-insert", "append:after-tail-update", "append:after", "export:after-thread-read", "save-checkpoint:before", "save-checkpoint:after", "ledger:admit:before", "ledger:admit:after", "ledger:mark-ready:before", "ledger:mark-ready:after", "ledger:claim:before", "ledger:claim:after", "ledger:mark-input-applied:before", "ledger:mark-input-applied:after", "ledger:renew:before", "ledger:renew:after", "ledger:reserve-settlement:before", "ledger:reserve-settlement:after", "ledger:finalize-settlement:before", "ledger:finalize-settlement:after", "ledger:request-abort:before", "ledger:request-abort:after", "ledger:release:before", "ledger:release:after", "ledger:claim-joining:before", "ledger:claim-joining:after", "ledger:mark-joined:before", "ledger:mark-joined:after", "ledger:revert-joining:before", "ledger:revert-joining:after", "ledger:suspend:before", "ledger:suspend:after", "ledger:approval-decision:before", "ledger:approval-decision:after", "ledger:mark-unknown:before", "ledger:mark-unknown:after", "ledger:unknown-resolution:before", "ledger:unknown-resolution:after", "ledger:child-reservation:before", "ledger:child-reservation:after", "ledger:child-attach:before", "ledger:child-attach:after", "ledger:child-release-pending:before", "ledger:child-release-pending:after", "ledger:child-release:before", "ledger:child-release:after", "ledger:child-settled:before", "ledger:child-settled:after"]>;
74
+ type SqliteStorageFailpointLocation = typeof SqliteStorageFailpointLocation.Type;
75
+ declare const SqliteStorageFailpointError_base: Schema.Class<SqliteStorageFailpointError, Schema.TaggedStruct<"SqliteStorageFailpointError", {
76
+ readonly location: Schema.Literals<readonly ["materialize:before", "materialize:after", "append:before", "append:after-batch-insert", "append:after-record-insert", "append:after-tail-update", "append:after", "export:after-thread-read", "save-checkpoint:before", "save-checkpoint:after", "ledger:admit:before", "ledger:admit:after", "ledger:mark-ready:before", "ledger:mark-ready:after", "ledger:claim:before", "ledger:claim:after", "ledger:mark-input-applied:before", "ledger:mark-input-applied:after", "ledger:renew:before", "ledger:renew:after", "ledger:reserve-settlement:before", "ledger:reserve-settlement:after", "ledger:finalize-settlement:before", "ledger:finalize-settlement:after", "ledger:request-abort:before", "ledger:request-abort:after", "ledger:release:before", "ledger:release:after", "ledger:claim-joining:before", "ledger:claim-joining:after", "ledger:mark-joined:before", "ledger:mark-joined:after", "ledger:revert-joining:before", "ledger:revert-joining:after", "ledger:suspend:before", "ledger:suspend:after", "ledger:approval-decision:before", "ledger:approval-decision:after", "ledger:mark-unknown:before", "ledger:mark-unknown:after", "ledger:unknown-resolution:before", "ledger:unknown-resolution:after", "ledger:child-reservation:before", "ledger:child-reservation:after", "ledger:child-attach:before", "ledger:child-attach:after", "ledger:child-release-pending:before", "ledger:child-release-pending:after", "ledger:child-release:before", "ledger:child-release:after", "ledger:child-settled:before", "ledger:child-settled:after"]>;
77
+ }>, import("effect/Cause").YieldableError>;
78
+ /** Deterministic test-only fault or pause injected at a SQLite operation boundary. */
79
+ declare class SqliteStorageFailpointError extends SqliteStorageFailpointError_base {
80
+ get message(): string;
81
+ }
82
+ //#endregion
83
+ //#region src/sqlite-storage-failpoint.d.ts
84
+ type SqliteStorageFailpointHandler = (location: SqliteStorageFailpointLocation) => Effect.Effect<void, SqliteStorageFailpointError>;
85
+ declare const SqliteStorageFailpoint_base: Context.ServiceClass<SqliteStorageFailpoint, "@effect-agent/storage-sqlite/SqliteStorageFailpoint", {
86
+ readonly hit: SqliteStorageFailpointHandler;
87
+ }>;
88
+ /** Explicit fault-injection authority used at SQLite operation boundaries. */
89
+ declare class SqliteStorageFailpoint extends SqliteStorageFailpoint_base {
90
+ /** Production default: no fault injection. */
91
+ static readonly layer: Layer.Layer<SqliteStorageFailpoint, never, never>;
92
+ }
93
+ //#endregion
94
+ export { SqliteFenceRejected as a, SqliteStorageCorruptionError as c, SqliteStorageFailpointLocation as d, SqliteWriteContention as f, SqliteCheckpointConflict as i, SqliteStorageError as l, SqliteStorageFailpointHandler as n, SqliteLedgerError as o, SqliteAppendConflict as r, SqliteStorageCompatibilityError as s, SqliteStorageFailpoint as t, SqliteStorageFailpointError as u };
95
+ //# sourceMappingURL=sqlite-storage-failpoint-Cr0SXflP.d.mts.map
@@ -0,0 +1,12 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ //#region src/sqlite-storage-failpoint.ts
3
+ const noFailpoint = () => Effect.void;
4
+ /** Explicit fault-injection authority used at SQLite operation boundaries. */
5
+ var SqliteStorageFailpoint = class extends Context.Service()("@effect-agent/storage-sqlite/SqliteStorageFailpoint") {
6
+ /** Production default: no fault injection. */
7
+ static layer = Layer.succeed(this)({ hit: noFailpoint });
8
+ };
9
+ //#endregion
10
+ export { SqliteStorageFailpoint as t };
11
+
12
+ //# sourceMappingURL=sqlite-storage-failpoint-DIwWk5dw.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-storage-failpoint-DIwWk5dw.mjs","names":[],"sources":["../src/sqlite-storage-failpoint.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\n\nimport type { SqliteStorageFailpointError, SqliteStorageFailpointLocation } from \"./errors.ts\";\n\nexport type SqliteStorageFailpointHandler = (\n location: SqliteStorageFailpointLocation,\n) => Effect.Effect<void, SqliteStorageFailpointError>;\n\nconst noFailpoint: SqliteStorageFailpointHandler = () => Effect.void;\n\n/** Explicit fault-injection authority used at SQLite operation boundaries. */\nexport class SqliteStorageFailpoint extends Context.Service<\n SqliteStorageFailpoint,\n {\n readonly hit: SqliteStorageFailpointHandler;\n }\n>()(\"@effect-agent/storage-sqlite/SqliteStorageFailpoint\") {\n /** Production default: no fault injection. */\n static readonly layer = Layer.succeed(this)({ hit: noFailpoint });\n}\n"],"mappings":";;AAQA,MAAM,oBAAmD,OAAO;;AAGhE,IAAa,yBAAb,cAA4C,QAAQ,QAKlD,CAAC,CAAC,qDAAqD,CAAC,CAAC;;CAEzD,OAAgB,QAAQ,MAAM,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC;AAClE"}
@@ -0,0 +1,15 @@
1
+ import { n as SqliteStorageFailpointHandler, t as SqliteStorageFailpoint } from "./sqlite-storage-failpoint-Cr0SXflP.mjs";
2
+ import { Context, Effect, Layer } from "effect";
3
+ //#region src/sqlite-storage-failpoint-testing.d.ts
4
+ declare const SqliteStorageFailpointTestControl_base: Context.ServiceClass<SqliteStorageFailpointTestControl, "@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl", {
5
+ readonly clear: Effect.Effect<void>;
6
+ readonly setHandler: (handler: SqliteStorageFailpointHandler) => Effect.Effect<void>;
7
+ }>;
8
+ /** Test-only control for replacing the active SQLite failpoint handler. */
9
+ declare class SqliteStorageFailpointTestControl extends SqliteStorageFailpointTestControl_base {
10
+ /** Reusable test Layer with a control service backed by the same handler Ref. */
11
+ static readonly layer: Layer.Layer<SqliteStorageFailpoint | SqliteStorageFailpointTestControl, never, never>;
12
+ }
13
+ //#endregion
14
+ export { SqliteStorageFailpointTestControl };
15
+ //# sourceMappingURL=testing.d.mts.map
@@ -0,0 +1,19 @@
1
+ import { t as SqliteStorageFailpoint } from "./sqlite-storage-failpoint-DIwWk5dw.mjs";
2
+ import { Context, Effect, Layer, Ref } from "effect";
3
+ //#region src/sqlite-storage-failpoint-testing.ts
4
+ const noFailpoint = () => Effect.void;
5
+ /** Test-only control for replacing the active SQLite failpoint handler. */
6
+ var SqliteStorageFailpointTestControl = class SqliteStorageFailpointTestControl extends Context.Service()("@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl") {
7
+ /** Reusable test Layer with a control service backed by the same handler Ref. */
8
+ static layer = Layer.effectContext(Effect.gen(function* () {
9
+ const handler = yield* Ref.make(noFailpoint);
10
+ return Context.make(SqliteStorageFailpoint, SqliteStorageFailpoint.of({ hit: (location) => Ref.get(handler).pipe(Effect.flatMap((current) => current(location))) })).pipe(Context.add(SqliteStorageFailpointTestControl, SqliteStorageFailpointTestControl.of({
11
+ clear: Ref.set(handler, noFailpoint),
12
+ setHandler: (next) => Ref.set(handler, next)
13
+ })));
14
+ }));
15
+ };
16
+ //#endregion
17
+ export { SqliteStorageFailpointTestControl };
18
+
19
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/sqlite-storage-failpoint-testing.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref } from \"effect\";\n\nimport {\n SqliteStorageFailpoint,\n type SqliteStorageFailpointHandler,\n} from \"./sqlite-storage-failpoint.ts\";\n\nconst noFailpoint: SqliteStorageFailpointHandler = () => Effect.void;\n\n/** Test-only control for replacing the active SQLite failpoint handler. */\nexport class SqliteStorageFailpointTestControl extends Context.Service<\n SqliteStorageFailpointTestControl,\n {\n readonly clear: Effect.Effect<void>;\n readonly setHandler: (handler: SqliteStorageFailpointHandler) => Effect.Effect<void>;\n }\n>()(\"@effect-agent/storage-sqlite/SqliteStorageFailpointTestControl\") {\n /** Reusable test Layer with a control service backed by the same handler Ref. */\n static readonly layer = Layer.effectContext(\n Effect.gen(function* () {\n const handler = yield* Ref.make<SqliteStorageFailpointHandler>(noFailpoint);\n return Context.make(\n SqliteStorageFailpoint,\n SqliteStorageFailpoint.of({\n hit: (location) => Ref.get(handler).pipe(Effect.flatMap((current) => current(location))),\n }),\n ).pipe(\n Context.add(\n SqliteStorageFailpointTestControl,\n SqliteStorageFailpointTestControl.of({\n clear: Ref.set(handler, noFailpoint),\n setHandler: (next) => Ref.set(handler, next),\n }),\n ),\n );\n }),\n );\n}\n"],"mappings":";;;AAOA,MAAM,oBAAmD,OAAO;;AAGhE,IAAa,oCAAb,MAAa,0CAA0C,QAAQ,QAM7D,CAAC,CAAC,gEAAgE,CAAC,CAAC;;CAEpE,OAAgB,QAAQ,MAAM,cAC5B,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,KAAoC,WAAW;EAC1E,OAAO,QAAQ,KACb,wBACA,uBAAuB,GAAG,EACxB,MAAM,aAAa,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,SAAS,YAAY,QAAQ,QAAQ,CAAC,CAAC,EACzF,CAAC,CACH,CAAC,CAAC,KACA,QAAQ,IACN,mCACA,kCAAkC,GAAG;GACnC,OAAO,IAAI,IAAI,SAAS,WAAW;GACnC,aAAa,SAAS,IAAI,IAAI,SAAS,IAAI;EAC7C,CAAC,CACH,CACF;CACF,CAAC,CACH;AACF"}
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@effect-agent/storage-sqlite",
3
- "version": "0.1.0-beta.38",
3
+ "version": "0.1.0-beta.40",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/index.d.mts",
7
7
  "default": "./dist/index.mjs"
8
+ },
9
+ "./testing": {
10
+ "types": "./dist/testing.d.mts",
11
+ "default": "./dist/testing.mjs"
8
12
  }
9
13
  },
10
14
  "dependencies": {
11
- "@effect-agent/session": "0.1.0-beta.38",
15
+ "@effect-agent/core": "0.1.0-beta.40",
16
+ "@effect-agent/thread": "0.1.0-beta.40",
12
17
  "@effect/platform-node": "4.0.0-rc.111",
13
- "@effect/sql-sqlite-node": "4.0.0-rc.111",
14
- "effect": "4.0.0-rc.111"
18
+ "@effect/sql-sqlite-node": "4.0.0-rc.111"
15
19
  },
16
- "description": "Node SQLite Conversation store and submission ledger adapters for the Effect Agent durable runtime.",
20
+ "peerDependencies": {
21
+ "effect": "^4.0.0-rc.111"
22
+ },
23
+ "description": "Node SQLite persistence adapters for the Effect Agent durable runtime and optional memory lifecycle.",
17
24
  "license": "MIT",
18
25
  "repository": {
19
26
  "type": "git",
@@ -35,7 +42,8 @@
35
42
  },
36
43
  "devDependencies": {
37
44
  "@effect/vitest": "4.0.0-rc.111",
45
+ "effect": "4.0.0-rc.111",
38
46
  "typescript": "7.0.2",
39
- "vite-plus": "0.2.6"
47
+ "vite-plus": "0.3.0"
40
48
  }
41
49
  }
package/src/errors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CanonicalSequence, ProducerEpoch } from "@effect-agent/session";
1
+ import { CanonicalSequence, ProducerEpoch } from "@effect-agent/thread";
2
2
  import { Schema } from "effect";
3
3
 
4
4
  /** The SQLite file uses a private-development format this adapter cannot read. */
@@ -60,7 +60,7 @@ export class SqliteAppendConflict extends Schema.TaggedError<SqliteAppendConflic
60
60
  ) {}
61
61
 
62
62
  /**
63
- * A producer epoch does not match the Conversation's current writer registration. Appends
63
+ * A producer epoch does not match the Thread's current writer registration. Appends
64
64
  * require the exact registered epoch, so both older and newer unregistered epochs are fenced;
65
65
  * a newer epoch takes over by materializing first.
66
66
  */
@@ -103,7 +103,7 @@ export const SqliteStorageFailpointLocation = Schema.Literals([
103
103
  "append:after-record-insert",
104
104
  "append:after-tail-update",
105
105
  "append:after",
106
- "export:after-conversation-read",
106
+ "export:after-thread-read",
107
107
  "save-checkpoint:before",
108
108
  "save-checkpoint:after",
109
109
  "ledger:admit:before",
package/src/index.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export * from "./errors.ts";
2
- export * from "./migrations.ts";
3
- export * from "./sqlite-conversation-store.ts";
2
+ export { CurrentSqliteStorageVersion } from "./migrations.ts";
3
+ export * from "./sqlite-activity-store.ts";
4
+ export * from "./sqlite-thread-store.ts";
4
5
  export * from "./sqlite-ledger.ts";
6
+ export * from "./sqlite-memory-store.ts";
5
7
  export * from "./sqlite-schedule-store.ts";
8
+ export * from "./sqlite-subscription-store.ts";
6
9
  export * from "./sqlite-storage-config.ts";
7
10
  export * from "./sqlite-storage-failpoint.ts";
package/src/migrations.ts CHANGED
@@ -2,15 +2,16 @@ 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 = 5;
5
+ export const CurrentSqliteStorageVersion = 7;
6
6
 
7
+ /** Initialize empty storage with the complete current schema. */
7
8
  export const sqliteMigrations = SqliteMigrator.fromRecord({
8
- "1_current_persistent_conversation_foundation": Effect.gen(function* () {
9
+ "1_current_thread_storage": Effect.gen(function* () {
9
10
  const sql = yield* SqlClient.SqlClient;
10
11
 
11
12
  yield* sql`
12
- CREATE TABLE effect_agent_conversations (
13
- conversation_id TEXT PRIMARY KEY NOT NULL,
13
+ CREATE TABLE effect_agent_threads (
14
+ thread_id TEXT PRIMARY KEY NOT NULL,
14
15
  created_at TEXT NOT NULL,
15
16
  tail_sequence INTEGER NOT NULL,
16
17
  tail_digest TEXT NOT NULL,
@@ -20,64 +21,59 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
20
21
 
21
22
  yield* sql`
22
23
  CREATE TABLE effect_agent_canonical_batches (
23
- conversation_id TEXT NOT NULL,
24
+ thread_id TEXT NOT NULL,
24
25
  batch_id TEXT NOT NULL,
25
26
  first_sequence INTEGER NOT NULL,
26
27
  last_sequence INTEGER NOT NULL,
27
28
  batch_digest TEXT NOT NULL,
28
29
  tail_digest TEXT NOT NULL,
29
30
  batch_json TEXT NOT NULL,
30
- PRIMARY KEY (conversation_id, batch_id),
31
- FOREIGN KEY (conversation_id)
32
- REFERENCES effect_agent_conversations(conversation_id)
31
+ PRIMARY KEY (thread_id, batch_id),
32
+ FOREIGN KEY (thread_id)
33
+ REFERENCES effect_agent_threads(thread_id)
33
34
  ON DELETE RESTRICT
34
35
  )
35
36
  `.withoutTransform;
36
37
 
37
38
  yield* sql`
38
39
  CREATE TABLE effect_agent_canonical_records (
39
- conversation_id TEXT NOT NULL,
40
+ thread_id TEXT NOT NULL,
40
41
  sequence INTEGER NOT NULL,
41
42
  record_id TEXT NOT NULL,
42
43
  batch_id TEXT NOT NULL,
43
44
  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)
45
+ PRIMARY KEY (thread_id, sequence),
46
+ UNIQUE (thread_id, record_id),
47
+ FOREIGN KEY (thread_id, batch_id)
48
+ REFERENCES effect_agent_canonical_batches(thread_id, batch_id)
48
49
  ON DELETE RESTRICT
49
50
  )
50
51
  `.withoutTransform;
51
52
 
52
53
  yield* sql`
53
54
  CREATE INDEX effect_agent_canonical_records_batch
54
- ON effect_agent_canonical_records (conversation_id, batch_id, sequence)
55
+ ON effect_agent_canonical_records (thread_id, batch_id, sequence)
55
56
  `.withoutTransform;
56
57
 
57
58
  yield* sql`
58
59
  CREATE TABLE effect_agent_checkpoints (
59
- conversation_id TEXT NOT NULL,
60
+ thread_id TEXT NOT NULL,
60
61
  through_sequence INTEGER NOT NULL,
61
62
  tail_digest TEXT NOT NULL,
62
63
  checkpoint_json TEXT NOT NULL,
63
- PRIMARY KEY (conversation_id, through_sequence),
64
- FOREIGN KEY (conversation_id)
65
- REFERENCES effect_agent_conversations(conversation_id)
64
+ PRIMARY KEY (thread_id, through_sequence),
65
+ FOREIGN KEY (thread_id)
66
+ REFERENCES effect_agent_threads(thread_id)
66
67
  ON DELETE RESTRICT
67
68
  )
68
69
  `.withoutTransform;
69
70
 
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.
71
+ // Admission rows exist before Thread materialization (durability §4), so
72
+ // thread_id intentionally carries no foreign key into effect_agent_threads.
77
73
  yield* sql`
78
74
  CREATE TABLE effect_agent_submissions (
79
75
  submission_id TEXT PRIMARY KEY NOT NULL,
80
- conversation_id TEXT NOT NULL,
76
+ thread_id TEXT NOT NULL,
81
77
  queue_sequence INTEGER NOT NULL,
82
78
  principal TEXT NOT NULL,
83
79
  idempotency_key TEXT NOT NULL,
@@ -93,8 +89,15 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
93
89
  ready_at TEXT,
94
90
  input_applied_record_id TEXT,
95
91
  input_applied_sequence INTEGER,
96
- UNIQUE (conversation_id, principal, idempotency_key),
97
- UNIQUE (conversation_id, queue_sequence)
92
+ joined_host_submission_id TEXT,
93
+ suspended_reason_json TEXT,
94
+ suspended_at TEXT,
95
+ unknown_reason TEXT,
96
+ unknown_tool_call_ids_json TEXT,
97
+ parent_submission_id TEXT,
98
+ parent_tool_call_id TEXT,
99
+ UNIQUE (thread_id, principal, idempotency_key),
100
+ UNIQUE (thread_id, queue_sequence)
98
101
  )
99
102
  `.withoutTransform;
100
103
 
@@ -116,7 +119,7 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
116
119
  CREATE TABLE effect_agent_attempts (
117
120
  attempt_id TEXT PRIMARY KEY NOT NULL,
118
121
  submission_id TEXT NOT NULL,
119
- conversation_id TEXT NOT NULL,
122
+ thread_id TEXT NOT NULL,
120
123
  owner_producer_id TEXT NOT NULL,
121
124
  producer_epoch INTEGER NOT NULL,
122
125
  claimed_at TEXT NOT NULL,
@@ -155,42 +158,6 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
155
158
  )
156
159
  `.withoutTransform;
157
160
 
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
161
  yield* sql`
195
162
  CREATE INDEX effect_agent_submissions_joined_host
196
163
  ON effect_agent_submissions (joined_host_submission_id)
@@ -226,21 +193,8 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
226
193
  )
227
194
  `.withoutTransform;
228
195
 
229
- yield* sql`PRAGMA user_version = 3`.withoutTransform;
230
- }),
231
- "4_durable_subagents": Effect.gen(function* () {
232
- const sql = yield* SqlClient.SqlClient;
233
-
234
196
  // Durable attached children (spec §12, SUB-004): a child Submission records its immutable
235
197
  // 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
198
  yield* sql`
245
199
  CREATE INDEX effect_agent_submissions_parent
246
200
  ON effect_agent_submissions (parent_submission_id)
@@ -270,11 +224,6 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
270
224
  )
271
225
  `.withoutTransform;
272
226
 
273
- yield* sql`PRAGMA user_version = 4`.withoutTransform;
274
- }),
275
- "5_durable_schedules": Effect.gen(function* () {
276
- const sql = yield* SqlClient.SqlClient;
277
-
278
227
  // record_json is authoritative. The remaining columns support owner keyset paging and
279
228
  // deadline queries without decoding unrelated future schedules.
280
229
  yield* sql`
@@ -300,6 +249,79 @@ export const sqliteMigrations = SqliteMigrator.fromRecord({
300
249
  WHERE deadline_at_millis IS NOT NULL
301
250
  `.withoutTransform;
302
251
 
303
- yield* sql`PRAGMA user_version = 5`.withoutTransform;
252
+ yield* sql`
253
+ CREATE TABLE effect_agent_subscription_sequences (
254
+ tenant_id TEXT NOT NULL,
255
+ source_address TEXT NOT NULL,
256
+ sequence INTEGER NOT NULL,
257
+ event_scan_cursor TEXT NOT NULL,
258
+ delivery_scan_cursor TEXT NOT NULL,
259
+ recovery_scan_cursor INTEGER NOT NULL,
260
+ PRIMARY KEY (tenant_id, source_address)
261
+ )
262
+ `.withoutTransform;
263
+ yield* sql`
264
+ CREATE TABLE effect_agent_subscriptions (
265
+ tenant_id TEXT NOT NULL,
266
+ source_address TEXT NOT NULL,
267
+ owner_id TEXT NOT NULL,
268
+ subscription_id TEXT NOT NULL,
269
+ ordinal INTEGER NOT NULL,
270
+ source_name TEXT NOT NULL,
271
+ source_version TEXT NOT NULL,
272
+ matching_key TEXT NOT NULL,
273
+ state TEXT NOT NULL,
274
+ expires_at_millis INTEGER NOT NULL,
275
+ recovery_at_millis INTEGER,
276
+ record_json TEXT NOT NULL,
277
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id),
278
+ UNIQUE (tenant_id, source_address, ordinal)
279
+ )
280
+ `.withoutTransform;
281
+ yield* sql`CREATE INDEX effect_agent_subscriptions_owner ON effect_agent_subscriptions (tenant_id, source_address, owner_id, ordinal)`
282
+ .withoutTransform;
283
+ yield* sql`CREATE INDEX effect_agent_subscriptions_candidates ON effect_agent_subscriptions (tenant_id, source_address, source_name, source_version, matching_key, ordinal)`
284
+ .withoutTransform;
285
+ 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`
286
+ .withoutTransform;
287
+ yield* sql`
288
+ CREATE TABLE effect_agent_subscription_events (
289
+ tenant_id TEXT NOT NULL,
290
+ source_address TEXT NOT NULL,
291
+ event_id TEXT NOT NULL,
292
+ source_name TEXT NOT NULL,
293
+ source_version TEXT NOT NULL,
294
+ matching_key TEXT NOT NULL,
295
+ payload_digest TEXT NOT NULL,
296
+ cutoff INTEGER NOT NULL,
297
+ cursor INTEGER NOT NULL,
298
+ routing_complete INTEGER NOT NULL,
299
+ next_attempt_at_millis INTEGER NOT NULL,
300
+ record_json TEXT NOT NULL,
301
+ PRIMARY KEY (tenant_id, source_address, event_id)
302
+ )
303
+ `.withoutTransform;
304
+ 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)`
305
+ .withoutTransform;
306
+ yield* sql`
307
+ CREATE TABLE effect_agent_subscription_deliveries (
308
+ tenant_id TEXT NOT NULL,
309
+ source_address TEXT NOT NULL,
310
+ owner_id TEXT NOT NULL,
311
+ subscription_id TEXT NOT NULL,
312
+ event_id TEXT NOT NULL,
313
+ delivery_key TEXT NOT NULL,
314
+ state TEXT NOT NULL,
315
+ next_attempt_at_millis INTEGER NOT NULL,
316
+ record_json TEXT NOT NULL,
317
+ PRIMARY KEY (tenant_id, source_address, owner_id, subscription_id, event_id),
318
+ UNIQUE (tenant_id, source_address, delivery_key)
319
+ )
320
+ `.withoutTransform;
321
+ 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)`
322
+ .withoutTransform;
323
+ yield* sql`CREATE INDEX effect_agent_subscription_deliveries_registration ON effect_agent_subscription_deliveries (tenant_id, source_address, owner_id, subscription_id, delivery_key)`
324
+ .withoutTransform;
325
+ yield* sql`PRAGMA user_version = 7`.withoutTransform;
304
326
  }),
305
327
  });