stratagate-dsh 0.2.15 → 0.2.16

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.16 - 2026-08-21
4
+
5
+ - Isolate open tails, Block sealing, decay, and automatic Block context by DSH session while keeping Events and Elements project-scoped for cross-session recall.
6
+ - Migrate SQLite storage to schema v5 with optional thread ownership on raw messages and Blocks; pre-v5 Blocks remain unowned archival provenance instead of being injected into new sessions.
7
+
3
8
  ## 0.2.15 - 2026-08-21
4
9
 
5
10
  - Disable reasoning for internal structured memory workers because the current DSH adapters do not map `tool_choice` to the provider request.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # StrataGate for DeepSeek Harness
2
2
 
3
- [English](README.md) · [简体中文](README.zh-CN.md)
3
+ [English](README.md) · [简体中文](docs/README.zh-CN.md)
4
4
 
5
5
  Automatic, local-first cross-session memory for DeepSeek Harness. StrataGate remembers user preferences, project decisions, completed conversations, and tool results, then checks recalled evidence and can expand it back to the original messages before the agent answers. No separate memory server is required.
6
6
 
@@ -70,9 +70,9 @@ Removing the plugin does not delete that database.
70
70
  - Subagent turns are not ingested by default; subagents in the same project can still read project memory.
71
71
  - Each DSH turn has a durable ingestion receipt, so replay or retry cannot store it twice.
72
72
  - StrataGate performs the existing Block summarization, Event extraction, Element projection, search, Evidence Gate, and use-only reinforcement.
73
- - Before every main-model call, the plugin injects the complete open tail, each sealed Block at its current decay-pointer level, and up to four activated Events plus four active Element facts.
73
+ - Before every main-model call, the plugin injects only the current session's open tail and sealed Blocks, plus up to four project-scoped activated Events and four active Element facts. Blocks remain persisted as source evidence, but they are never automatically carried into another session.
74
74
 
75
- Activated memory uses the current human message plus the latest two open-tail turns as its query. Existing BM25 search remains the lexical relevance gate; pinned and safety memory are the only exceptions. Existing memory weights provide a second ranking, and RRF fuses the relevance and weight rankings. The activated section has a fixed budget of about 900 tokens, so it does not grow with the database.
75
+ Activated memory uses the current human message plus the latest two open-tail turns from the current session as its query. Existing BM25 search remains the lexical relevance gate; pinned and safety memory are the only exceptions. Existing memory weights provide a second ranking, and RRF fuses the relevance and weight rankings. The activated section has a fixed budget of about 900 tokens, so it does not grow with the database.
76
76
 
77
77
  Automatic context contains only compact Event and fact fields and is explicitly marked as historical background rather than instructions. Building it never calls `recordMemoryUse`, increments `mentionCount`, or changes `lastAdoptedTurn`. The existing `memory_*` tools remain available for deeper, evidence-gated retrieval and are the only path to adoption reinforcement.
78
78
 
package/dist/index.js CHANGED
@@ -358,7 +358,7 @@ function rrfRank(rankings) {
358
358
  }
359
359
 
360
360
  // ../../src/storage.ts
361
- var STRATAGATE_STORAGE_SCHEMA_VERSION = 4;
361
+ var STRATAGATE_STORAGE_SCHEMA_VERSION = 5;
362
362
  var StorageConflictError = class extends Error {
363
363
  constructor(namespace, expectedRevision, actualRevision) {
364
364
  super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
@@ -399,6 +399,11 @@ function normalizeSnapshot(value) {
399
399
  ...structuredClone(value),
400
400
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
401
401
  };
402
+ } else if (schemaVersion === 4) {
403
+ snapshot = {
404
+ ...structuredClone(value),
405
+ schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
406
+ };
402
407
  } else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
403
408
  snapshot = structuredClone(value);
404
409
  } else {
@@ -594,6 +599,7 @@ CREATE TABLE IF NOT EXISTS memory_spaces (
594
599
  CREATE TABLE IF NOT EXISTS blocks (
595
600
  namespace TEXT NOT NULL,
596
601
  id TEXT NOT NULL,
602
+ thread_id TEXT,
597
603
  sequence INTEGER NOT NULL,
598
604
  start_turn INTEGER NOT NULL,
599
605
  end_turn INTEGER NOT NULL,
@@ -618,6 +624,7 @@ CREATE TABLE IF NOT EXISTS messages (
618
624
  namespace TEXT NOT NULL,
619
625
  id TEXT NOT NULL,
620
626
  block_id TEXT,
627
+ thread_id TEXT,
621
628
  position INTEGER NOT NULL,
622
629
  role TEXT NOT NULL,
623
630
  content TEXT NOT NULL,
@@ -781,6 +788,10 @@ CREATE TABLE IF NOT EXISTS ingestion_receipts (
781
788
  FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
782
789
  ) STRICT;
783
790
  `;
791
+ var THREAD_INDEXES = `
792
+ CREATE INDEX IF NOT EXISTS messages_thread_idx ON messages(namespace, thread_id, position);
793
+ CREATE INDEX IF NOT EXISTS blocks_thread_idx ON blocks(namespace, thread_id, sequence);
794
+ `;
784
795
  function parseJson(value, label) {
785
796
  try {
786
797
  return JSON.parse(value);
@@ -828,7 +839,7 @@ var SqliteStorage = class {
828
839
  throw new Error(`Unsupported stored StrataGate schema: ${space.schema_version}`);
829
840
  }
830
841
  const messageRows = this.database.prepare(`
831
- SELECT id, block_id, position, role, content, created_at, tool_calls_json
842
+ SELECT id, block_id, thread_id, position, role, content, created_at, tool_calls_json
832
843
  FROM messages WHERE namespace = ? ORDER BY block_id, position
833
844
  `).all(key);
834
845
  const openTail = [];
@@ -839,6 +850,7 @@ var SqliteStorage = class {
839
850
  role: row.role,
840
851
  content: row.content,
841
852
  createdAt: row.created_at,
853
+ ...row.thread_id ? { threadId: row.thread_id } : {},
842
854
  ...row.tool_calls_json ? { toolCalls: parseJson(row.tool_calls_json, "messages.tool_calls_json") } : {}
843
855
  };
844
856
  if (row.block_id === null) openTail.push(message);
@@ -853,6 +865,7 @@ var SqliteStorage = class {
853
865
  `).all(key);
854
866
  const blocks = blockRows.map((row) => ({
855
867
  id: row.id,
868
+ ...row.thread_id ? { threadId: row.thread_id } : {},
856
869
  sequence: row.sequence,
857
870
  startTurn: row.start_turn,
858
871
  endTurn: row.end_turn,
@@ -1099,11 +1112,12 @@ var SqliteStorage = class {
1099
1112
  }
1100
1113
  const insertBlock = this.database.prepare(`
1101
1114
  INSERT INTO blocks (
1102
- namespace, id, sequence, start_turn, end_turn, created_at, should_extract,
1115
+ namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
1103
1116
  l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
1104
1117
  pointer_current_level, pointer_anchor_level, pointer_anchor_turn, last_lifted_at
1105
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1118
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1106
1119
  ON CONFLICT (namespace, id) DO UPDATE SET
1120
+ thread_id = excluded.thread_id,
1107
1121
  sequence = excluded.sequence,
1108
1122
  start_turn = excluded.start_turn,
1109
1123
  end_turn = excluded.end_turn,
@@ -1124,6 +1138,7 @@ var SqliteStorage = class {
1124
1138
  insertBlock.run(
1125
1139
  namespace,
1126
1140
  block.id,
1141
+ block.threadId ?? null,
1127
1142
  block.sequence,
1128
1143
  block.startTurn,
1129
1144
  block.endTurn,
@@ -1143,10 +1158,11 @@ var SqliteStorage = class {
1143
1158
  }
1144
1159
  const insertMessage = this.database.prepare(`
1145
1160
  INSERT INTO messages (
1146
- namespace, id, block_id, position, role, content, created_at, tool_calls_json
1147
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1161
+ namespace, id, block_id, thread_id, position, role, content, created_at, tool_calls_json
1162
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1148
1163
  ON CONFLICT (namespace, id) DO UPDATE SET
1149
1164
  block_id = excluded.block_id,
1165
+ thread_id = excluded.thread_id,
1150
1166
  position = excluded.position,
1151
1167
  role = excluded.role,
1152
1168
  content = excluded.content,
@@ -1159,6 +1175,7 @@ var SqliteStorage = class {
1159
1175
  namespace,
1160
1176
  message.id,
1161
1177
  blockId,
1178
+ message.threadId ?? null,
1162
1179
  position,
1163
1180
  message.role,
1164
1181
  message.content,
@@ -1402,9 +1419,10 @@ var SqliteStorage = class {
1402
1419
  if (version === 0) {
1403
1420
  this.immediateTransaction(() => {
1404
1421
  this.database.exec(SCHEMA);
1422
+ this.database.exec(THREAD_INDEXES);
1405
1423
  this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
1406
1424
  });
1407
- } else if (version === 1 || version === 2 || version === 3) {
1425
+ } else if (version === 1 || version === 2 || version === 3 || version === 4) {
1408
1426
  this.immediateTransaction(() => {
1409
1427
  if (version === 1) {
1410
1428
  const receiptColumns2 = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
@@ -1417,11 +1435,21 @@ var SqliteStorage = class {
1417
1435
  this.database.exec("ALTER TABLE usage_receipts ADD COLUMN audit_json TEXT NOT NULL DEFAULT '{}'");
1418
1436
  }
1419
1437
  this.database.exec(SCHEMA);
1438
+ const blockColumns = this.database.prepare("PRAGMA table_info('blocks')").all();
1439
+ if (!blockColumns.some(({ name: name2 }) => name2 === "thread_id")) {
1440
+ this.database.exec("ALTER TABLE blocks ADD COLUMN thread_id TEXT");
1441
+ }
1442
+ const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
1443
+ if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
1444
+ this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
1445
+ }
1446
+ this.database.exec(THREAD_INDEXES);
1420
1447
  this.database.prepare("UPDATE memory_spaces SET schema_version = ? WHERE schema_version < ?").run(STRATAGATE_STORAGE_SCHEMA_VERSION, STRATAGATE_STORAGE_SCHEMA_VERSION);
1421
1448
  this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
1422
1449
  });
1423
1450
  } else if (version === STRATAGATE_STORAGE_SCHEMA_VERSION) {
1424
1451
  this.database.exec(SCHEMA);
1452
+ this.database.exec(THREAD_INDEXES);
1425
1453
  }
1426
1454
  this.assertSchemaVersion();
1427
1455
  }
@@ -1634,8 +1662,9 @@ var StrataGate = class _StrataGate {
1634
1662
  listElements() {
1635
1663
  return this.elements;
1636
1664
  }
1637
- listOpenTail() {
1638
- return this.openTail;
1665
+ listOpenTail(threadId) {
1666
+ if (threadId === void 0) return this.openTail;
1667
+ return this.openTail.filter((message) => message.threadId === threadId);
1639
1668
  }
1640
1669
  listExtractionJobs() {
1641
1670
  return [...this.extractionJobs.values()];
@@ -1685,12 +1714,17 @@ var StrataGate = class _StrataGate {
1685
1714
  if (input.receiptId !== void 0 && !receiptId) {
1686
1715
  throw new TypeError("Turn receiptId must not be empty");
1687
1716
  }
1717
+ const threadId = input.threadId?.trim();
1718
+ if (input.threadId !== void 0 && !threadId) {
1719
+ throw new TypeError("Turn threadId must not be empty");
1720
+ }
1688
1721
  const createdAt = toUtc8Iso(input.createdAt ?? this.now());
1689
1722
  const userMessage = {
1690
1723
  id: this.idFactory("msg"),
1691
1724
  role: "user",
1692
1725
  content: input.user,
1693
1726
  createdAt,
1727
+ ...threadId ? { threadId } : {},
1694
1728
  ...input.userToolCalls ? { toolCalls: input.userToolCalls } : {}
1695
1729
  };
1696
1730
  const assistantMessage = {
@@ -1698,6 +1732,7 @@ var StrataGate = class _StrataGate {
1698
1732
  role: "assistant",
1699
1733
  content: input.assistant,
1700
1734
  createdAt,
1735
+ ...threadId ? { threadId } : {},
1701
1736
  ...input.assistantToolCalls ? { toolCalls: input.assistantToolCalls } : {}
1702
1737
  };
1703
1738
  const appended = await this.commitMutation(() => {
@@ -1711,11 +1746,11 @@ var StrataGate = class _StrataGate {
1711
1746
  if (options.deferProcessing === true) {
1712
1747
  return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
1713
1748
  }
1714
- if (this.openTail.filter((message) => message.role === "user").length < this.blockTurnSize) {
1749
+ if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
1715
1750
  const projectedElements2 = await this.projectEligibleElements() ?? [];
1716
1751
  return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
1717
1752
  }
1718
- const sealedBlock = await this.sealOpenTail();
1753
+ const sealedBlock = await this.sealOpenTail(threadId);
1719
1754
  const extractedEvents = await this.extractEligibleBlock() ?? [];
1720
1755
  const projectedElements = await this.projectEligibleElements() ?? [];
1721
1756
  return { sealedBlock, extractedEvents, projectedElements };
@@ -1724,8 +1759,10 @@ var StrataGate = class _StrataGate {
1724
1759
  const sealedBlocks = [];
1725
1760
  const extractedEvents = [];
1726
1761
  const projectedElements = [];
1727
- while (this.openTail.filter((message) => message.role === "user").length >= this.blockTurnSize) {
1728
- sealedBlocks.push(await this.sealOpenTail());
1762
+ while (true) {
1763
+ const sealable = this.nextSealableThread();
1764
+ if (sealable === null) break;
1765
+ sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
1729
1766
  extractedEvents.push(...await this.extractEligibleBlock() ?? []);
1730
1767
  projectedElements.push(...await this.projectEligibleElements() ?? []);
1731
1768
  }
@@ -1736,7 +1773,7 @@ var StrataGate = class _StrataGate {
1736
1773
  projectedElements.push(...await this.projectEligibleElements() ?? []);
1737
1774
  }
1738
1775
  if (options.retrySkipped === true) {
1739
- const skippedBlockIds = this.blocks.filter((block, index) => index < this.blocks.length - 1 && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
1776
+ const skippedBlockIds = this.blocks.filter((block) => this.nextBlockInThread(block) !== null && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
1740
1777
  for (const blockId of skippedBlockIds) {
1741
1778
  const extracted = await this.extractEligibleBlock({ blockId, includeSkipped: true });
1742
1779
  if (extracted === null) continue;
@@ -1955,12 +1992,15 @@ var StrataGate = class _StrataGate {
1955
1992
  }
1956
1993
  return hits;
1957
1994
  }
1958
- getBlockContext() {
1959
- return this.blocks.map((block) => {
1960
- const level = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, this.currentTurn);
1995
+ getBlockContext(threadId) {
1996
+ const blocks = threadId === void 0 ? this.blocks : this.blocks.filter((block) => block.threadId === threadId);
1997
+ return blocks.map((block) => {
1998
+ const currentTurn = block.threadId === void 0 ? this.currentTurn : this.threadTurn(block.threadId);
1999
+ const level = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, currentTurn);
1961
2000
  block.pointerCurrentLevel = level;
1962
2001
  return {
1963
2002
  id: block.id,
2003
+ ...block.threadId ? { threadId: block.threadId } : {},
1964
2004
  turnRange: [block.startTurn, block.endTurn],
1965
2005
  level,
1966
2006
  label: blockLevelLabel(level),
@@ -1972,14 +2012,16 @@ var StrataGate = class _StrataGate {
1972
2012
  return this.commitMutation(() => {
1973
2013
  const block = this.blocks.find((candidate) => candidate.id === id);
1974
2014
  if (!block) throw new Error(`Unknown block: ${id}`);
1975
- const current = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, this.currentTurn);
2015
+ const currentTurn = block.threadId === void 0 ? this.currentTurn : this.threadTurn(block.threadId);
2016
+ const current = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, currentTurn);
1976
2017
  const level = normalizeBlockLevel(target, current);
1977
2018
  block.pointerCurrentLevel = level;
1978
2019
  block.pointerAnchorLevel = level;
1979
- block.pointerAnchorTurn = this.currentTurn;
2020
+ block.pointerAnchorTurn = currentTurn;
1980
2021
  block.lastLiftedAt = toUtc8Iso(this.now());
1981
2022
  return {
1982
2023
  id: block.id,
2024
+ ...block.threadId ? { threadId: block.threadId } : {},
1983
2025
  turnRange: [block.startTurn, block.endTurn],
1984
2026
  level,
1985
2027
  label: blockLevelLabel(level),
@@ -2129,36 +2171,67 @@ var StrataGate = class _StrataGate {
2129
2171
  this.elementProjectionJobs.set(job.id, job);
2130
2172
  return job;
2131
2173
  }
2132
- pendingBlockMessages() {
2174
+ threadOpenTail(threadId) {
2175
+ return this.openTail.filter((message) => message.threadId === threadId);
2176
+ }
2177
+ threadBlocks(threadId) {
2178
+ return this.blocks.filter((block) => block.threadId === threadId);
2179
+ }
2180
+ threadTurn(threadId) {
2181
+ const sealedTurns = this.threadBlocks(threadId).reduce((total, block) => total + block.l5Raw.filter((message) => message.role === "user").length, 0);
2182
+ return sealedTurns + this.threadOpenTail(threadId).filter((message) => message.role === "user").length;
2183
+ }
2184
+ nextSealableThread() {
2185
+ const counts = [];
2186
+ for (const message of this.openTail) {
2187
+ if (message.role !== "user") continue;
2188
+ let entry = counts.find((candidate) => candidate.threadId === message.threadId);
2189
+ if (!entry) {
2190
+ entry = { threadId: message.threadId, users: 0 };
2191
+ counts.push(entry);
2192
+ }
2193
+ entry.users += 1;
2194
+ if (entry.users >= this.blockTurnSize) return { threadId: entry.threadId };
2195
+ }
2196
+ return null;
2197
+ }
2198
+ nextBlockInThread(block) {
2199
+ const index = this.blocks.indexOf(block);
2200
+ return this.blocks.slice(index + 1).find((candidate) => candidate.threadId === block.threadId) ?? null;
2201
+ }
2202
+ pendingBlockMessages(threadId) {
2203
+ const messages = this.threadOpenTail(threadId);
2133
2204
  let users = 0;
2134
- let end = this.openTail.length;
2135
- for (const [index, message] of this.openTail.entries()) {
2205
+ let end = messages.length;
2206
+ for (const [index, message] of messages.entries()) {
2136
2207
  if (message.role !== "user") continue;
2137
2208
  users += 1;
2138
2209
  if (users !== this.blockTurnSize) continue;
2139
- const nextUserOffset = this.openTail.slice(index + 1).findIndex((candidate) => candidate.role === "user");
2140
- end = nextUserOffset === -1 ? this.openTail.length : index + 1 + nextUserOffset;
2210
+ const nextUserOffset = messages.slice(index + 1).findIndex((candidate) => candidate.role === "user");
2211
+ end = nextUserOffset === -1 ? messages.length : index + 1 + nextUserOffset;
2141
2212
  break;
2142
2213
  }
2143
- return this.openTail.slice(0, end);
2214
+ return messages.slice(0, end);
2144
2215
  }
2145
- async sealOpenTail() {
2146
- const raw = this.pendingBlockMessages();
2216
+ async sealOpenTail(threadId) {
2217
+ const raw = this.pendingBlockMessages(threadId);
2147
2218
  if (raw.filter((message) => message.role === "user").length < this.blockTurnSize) {
2148
2219
  throw new Error("Open tail does not contain enough turns to seal a block");
2149
2220
  }
2150
2221
  const generated = this.summarizer ? await this.summarizer(raw) : defaultSummary(raw);
2151
2222
  const deterministic = deterministicBlockLayers(raw);
2152
2223
  const sequence = this.blocks.length + 1;
2153
- const startTurn = this.blocks.at(-1)?.endTurn !== void 0 ? (this.blocks.at(-1)?.endTurn ?? 0) + 1 : 1;
2224
+ const previous = this.threadBlocks(threadId).at(-1);
2225
+ const startTurn = previous ? previous.endTurn + 1 : 1;
2154
2226
  const endTurn = startTurn + this.blockTurnSize - 1;
2155
2227
  return this.commitMutation(() => {
2156
- const currentRaw = this.pendingBlockMessages();
2228
+ const currentRaw = this.pendingBlockMessages(threadId);
2157
2229
  if (!sameIds(currentRaw.map((message) => message.id), raw.map((message) => message.id))) {
2158
2230
  throw new Error("Open tail changed while the block summary was being prepared");
2159
2231
  }
2160
2232
  const block = {
2161
2233
  id: this.idFactory("blk"),
2234
+ ...threadId ? { threadId } : {},
2162
2235
  sequence,
2163
2236
  startTurn,
2164
2237
  endTurn,
@@ -2174,23 +2247,26 @@ var StrataGate = class _StrataGate {
2174
2247
  pointerAnchorTurn: endTurn,
2175
2248
  lastLiftedAt: null
2176
2249
  };
2177
- this.openTail.splice(0, raw.length);
2250
+ const sealedIds = new Set(raw.map((message) => message.id));
2251
+ const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
2252
+ this.openTail.splice(0, this.openTail.length, ...remaining);
2178
2253
  this.blocks.push(block);
2179
2254
  return block;
2180
2255
  });
2181
2256
  }
2182
2257
  async extractEligibleBlock(options = {}) {
2183
2258
  if (!this.extractor || this.blocks.length < 2) return null;
2184
- const targetIndex = this.blocks.findIndex((block, index) => {
2185
- if (index >= this.blocks.length - 1 || !block.shouldExtract) return false;
2259
+ const target = this.blocks.find((block) => {
2260
+ if (this.nextBlockInThread(block) === null || !block.shouldExtract) return false;
2186
2261
  if (options.blockId !== void 0 && block.id !== options.blockId) return false;
2187
2262
  const status = this.extractionJobs.get(block.id)?.status;
2188
2263
  return status === void 0 || status === "failed" || options.includeSkipped === true && status === "skipped";
2189
2264
  });
2190
- if (targetIndex < 0) return null;
2191
- const target = this.blocks[targetIndex];
2192
- const next = this.blocks[targetIndex + 1];
2193
- if (!target || !next) return null;
2265
+ if (!target) return null;
2266
+ const threadBlocks = this.threadBlocks(target.threadId);
2267
+ const targetIndex = threadBlocks.indexOf(target);
2268
+ const next = threadBlocks[targetIndex + 1];
2269
+ if (!next) return null;
2194
2270
  const existing = this.extractionJobs.get(target.id);
2195
2271
  await this.commitMutation(() => {
2196
2272
  const currentStatus = this.extractionJobs.get(target.id)?.status;
@@ -2209,7 +2285,7 @@ var StrataGate = class _StrataGate {
2209
2285
  let result;
2210
2286
  try {
2211
2287
  result = await this.extractor({
2212
- previous: this.blocks[targetIndex - 1] ?? null,
2288
+ previous: threadBlocks[targetIndex - 1] ?? null,
2213
2289
  target,
2214
2290
  next,
2215
2291
  timeline: this.events.map((event) => ({ id: event.id, title: event.title, temporal: event.temporal }))
@@ -2950,6 +3026,7 @@ var TurnFolder = class {
2950
3026
  return {
2951
3027
  user: pending.user.join("\n\n"),
2952
3028
  assistant: pending.assistant.join("\n\n") || reasonLabel(event.data.reason),
3029
+ threadId: sessionId,
2953
3030
  assistantToolCalls: [...pending.tools.values()],
2954
3031
  createdAt: toUtc8Iso(event.time),
2955
3032
  receiptId: `dsh:${sessionId}:turn:${event.data.turn}`
@@ -3045,7 +3122,7 @@ var StrataGateRuntime = class {
3045
3122
  }
3046
3123
  async blocks(session) {
3047
3124
  await this.flush();
3048
- const results = (await this.space(session)).getBlockContext();
3125
+ const results = (await this.space(session)).getBlockContext(String(session.id));
3049
3126
  return this.batch(session, results.map((result) => ({
3050
3127
  ref: `block:${result.id}:level:${result.level}`,
3051
3128
  target: { eventIds: [], elementIds: [] }
@@ -3167,7 +3244,8 @@ var StrataGateRuntime = class {
3167
3244
  async buildAutoContext(session) {
3168
3245
  await this.flush();
3169
3246
  const memory = await this.space(session);
3170
- const openTail = memory.listOpenTail();
3247
+ const threadId = String(session.id);
3248
+ const openTail = memory.listOpenTail(threadId);
3171
3249
  const activationQuery = [currentUserMessage(session), renderMessages(recentTurns(openTail, 2))].filter(Boolean).join("\n\n");
3172
3250
  const [eventHits, elementHits] = activationQuery ? await Promise.all([
3173
3251
  memory.searchEvents(activationQuery, { limit: 20 }),
@@ -3180,7 +3258,7 @@ var StrataGateRuntime = class {
3180
3258
  openTail.length > 0 ? renderMessages(openTail) : "(open tail is empty)",
3181
3259
  "",
3182
3260
  "[Decayed memory blocks]",
3183
- renderBlocks2(memory.getBlockContext()),
3261
+ renderBlocks2(memory.getBlockContext(threadId)),
3184
3262
  "",
3185
3263
  renderActivatedMemory(events, elements)
3186
3264
  ].join("\n");