@namzu/sdk 0.2.0 → 0.4.0

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 (329) hide show
  1. package/CHANGELOG.md +74 -2
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +3 -2
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/SupervisorAgent.js +5 -2
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/bridge/a2a/index.d.ts +1 -1
  9. package/dist/bridge/a2a/index.d.ts.map +1 -1
  10. package/dist/bridge/a2a/index.js +1 -1
  11. package/dist/bridge/a2a/index.js.map +1 -1
  12. package/dist/bridge/a2a/message.d.ts +0 -2
  13. package/dist/bridge/a2a/message.d.ts.map +1 -1
  14. package/dist/bridge/a2a/message.js +0 -26
  15. package/dist/bridge/a2a/message.js.map +1 -1
  16. package/dist/bridge/a2a/task.d.ts +4 -3
  17. package/dist/bridge/a2a/task.d.ts.map +1 -1
  18. package/dist/bridge/a2a/task.js +4 -4
  19. package/dist/bridge/a2a/task.js.map +1 -1
  20. package/dist/contracts/api.d.ts +6 -38
  21. package/dist/contracts/api.d.ts.map +1 -1
  22. package/dist/contracts/ids.d.ts +1 -1
  23. package/dist/contracts/ids.d.ts.map +1 -1
  24. package/dist/contracts/index.d.ts +3 -5
  25. package/dist/contracts/index.d.ts.map +1 -1
  26. package/dist/contracts/index.js +1 -1
  27. package/dist/contracts/index.js.map +1 -1
  28. package/dist/contracts/schemas.d.ts +1 -31
  29. package/dist/contracts/schemas.d.ts.map +1 -1
  30. package/dist/contracts/schemas.js +1 -7
  31. package/dist/contracts/schemas.js.map +1 -1
  32. package/dist/index.d.ts +2 -7
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +5 -6
  35. package/dist/index.js.map +1 -1
  36. package/dist/manager/agent/__tests__/lifecycle.test.js +27 -13
  37. package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -1
  38. package/dist/manager/agent/lifecycle.d.ts +9 -0
  39. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  40. package/dist/manager/agent/lifecycle.js +93 -31
  41. package/dist/manager/agent/lifecycle.js.map +1 -1
  42. package/dist/manager/index.d.ts +2 -0
  43. package/dist/manager/index.d.ts.map +1 -1
  44. package/dist/manager/index.js +1 -0
  45. package/dist/manager/index.js.map +1 -1
  46. package/dist/manager/run/persistence.d.ts +3 -1
  47. package/dist/manager/run/persistence.d.ts.map +1 -1
  48. package/dist/manager/run/persistence.js +5 -0
  49. package/dist/manager/run/persistence.js.map +1 -1
  50. package/dist/manager/thread/__tests__/lifecycle.test.d.ts +2 -0
  51. package/dist/manager/thread/__tests__/lifecycle.test.d.ts.map +1 -0
  52. package/dist/manager/thread/__tests__/lifecycle.test.js +216 -0
  53. package/dist/manager/thread/__tests__/lifecycle.test.js.map +1 -0
  54. package/dist/manager/thread/lifecycle.d.ts +105 -0
  55. package/dist/manager/thread/lifecycle.d.ts.map +1 -0
  56. package/dist/manager/thread/lifecycle.js +186 -0
  57. package/dist/manager/thread/lifecycle.js.map +1 -0
  58. package/dist/rag/retriever.js +2 -2
  59. package/dist/registry/tool/execute.js +1 -1
  60. package/dist/registry/tool/execute.js.map +1 -1
  61. package/dist/runtime/query/__tests__/context.test.js +8 -7
  62. package/dist/runtime/query/__tests__/context.test.js.map +1 -1
  63. package/dist/runtime/query/context-cache.d.ts +3 -3
  64. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  65. package/dist/runtime/query/context-cache.js +2 -2
  66. package/dist/runtime/query/context-cache.js.map +1 -1
  67. package/dist/runtime/query/context.d.ts +12 -21
  68. package/dist/runtime/query/context.d.ts.map +1 -1
  69. package/dist/runtime/query/context.js +3 -1
  70. package/dist/runtime/query/context.js.map +1 -1
  71. package/dist/runtime/query/index.d.ts +13 -15
  72. package/dist/runtime/query/index.d.ts.map +1 -1
  73. package/dist/runtime/query/index.js +2 -1
  74. package/dist/runtime/query/index.js.map +1 -1
  75. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  76. package/dist/runtime/query/iteration/index.js +1 -1
  77. package/dist/runtime/query/iteration/index.js.map +1 -1
  78. package/dist/session/__tests__/integration/_fixtures.d.ts +11 -4
  79. package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -1
  80. package/dist/session/__tests__/integration/_fixtures.js +23 -6
  81. package/dist/session/__tests__/integration/_fixtures.js.map +1 -1
  82. package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
  83. package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
  84. package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
  85. package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
  86. package/dist/session/__tests__/integration/capacity-caps.test.js +13 -6
  87. package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -1
  88. package/dist/session/__tests__/integration/e2e-spawn.test.js +14 -2
  89. package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -1
  90. package/dist/session/__tests__/integration/event-stream-ordering.test.js +14 -7
  91. package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -1
  92. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +26 -14
  93. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -1
  94. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +30 -20
  95. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -1
  96. package/dist/session/__tests__/integration/handoff-single-e2e.test.js +25 -9
  97. package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -1
  98. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +11 -10
  99. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -1
  100. package/dist/session/__tests__/integration/prev-artifact-dag.test.js +5 -4
  101. package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -1
  102. package/dist/session/__tests__/integration/retention-archive.test.js +3 -2
  103. package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -1
  104. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
  105. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
  106. package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
  107. package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
  108. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +2 -1
  109. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -1
  110. package/dist/session/__tests__/integration/tenant-isolation.test.js +14 -5
  111. package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -1
  112. package/dist/session/errors.d.ts +79 -0
  113. package/dist/session/errors.d.ts.map +1 -1
  114. package/dist/session/errors.js +57 -0
  115. package/dist/session/errors.js.map +1 -1
  116. package/dist/session/handoff/__tests__/broadcast.test.js +49 -31
  117. package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -1
  118. package/dist/session/handoff/__tests__/capacity.test.js +21 -18
  119. package/dist/session/handoff/__tests__/capacity.test.js.map +1 -1
  120. package/dist/session/handoff/__tests__/single.test.js +39 -30
  121. package/dist/session/handoff/__tests__/single.test.js.map +1 -1
  122. package/dist/session/handoff/assignment.d.ts +13 -1
  123. package/dist/session/handoff/assignment.d.ts.map +1 -1
  124. package/dist/session/handoff/broadcast.d.ts +7 -0
  125. package/dist/session/handoff/broadcast.d.ts.map +1 -1
  126. package/dist/session/handoff/broadcast.js +16 -1
  127. package/dist/session/handoff/broadcast.js.map +1 -1
  128. package/dist/session/handoff/single.d.ts +7 -0
  129. package/dist/session/handoff/single.d.ts.map +1 -1
  130. package/dist/session/handoff/single.js +13 -1
  131. package/dist/session/handoff/single.js.map +1 -1
  132. package/dist/session/hierarchy/__tests__/session.test.js +2 -0
  133. package/dist/session/hierarchy/__tests__/session.test.js.map +1 -1
  134. package/dist/session/hierarchy/index.d.ts +1 -0
  135. package/dist/session/hierarchy/index.d.ts.map +1 -1
  136. package/dist/session/hierarchy/index.js.map +1 -1
  137. package/dist/session/hierarchy/session.d.ts +15 -3
  138. package/dist/session/hierarchy/session.d.ts.map +1 -1
  139. package/dist/session/hierarchy/session.js.map +1 -1
  140. package/dist/session/hierarchy/thread.d.ts +54 -0
  141. package/dist/session/hierarchy/thread.d.ts.map +1 -0
  142. package/dist/session/hierarchy/thread.js +2 -0
  143. package/dist/session/hierarchy/thread.js.map +1 -0
  144. package/dist/session/migration/id-prefix.d.ts +8 -13
  145. package/dist/session/migration/id-prefix.d.ts.map +1 -1
  146. package/dist/session/migration/id-prefix.js +8 -13
  147. package/dist/session/migration/id-prefix.js.map +1 -1
  148. package/dist/session/retention/__tests__/archive.test.js +3 -2
  149. package/dist/session/retention/__tests__/archive.test.js.map +1 -1
  150. package/dist/session/summary/__tests__/materialize.test.js +4 -3
  151. package/dist/session/summary/__tests__/materialize.test.js.map +1 -1
  152. package/dist/store/index.d.ts +0 -2
  153. package/dist/store/index.d.ts.map +1 -1
  154. package/dist/store/index.js +0 -1
  155. package/dist/store/index.js.map +1 -1
  156. package/dist/store/session/__tests__/disk.test.js +32 -5
  157. package/dist/store/session/__tests__/disk.test.js.map +1 -1
  158. package/dist/store/session/__tests__/memory.test.js +50 -9
  159. package/dist/store/session/__tests__/memory.test.js.map +1 -1
  160. package/dist/store/session/disk.d.ts +2 -1
  161. package/dist/store/session/disk.d.ts.map +1 -1
  162. package/dist/store/session/disk.js +61 -0
  163. package/dist/store/session/disk.js.map +1 -1
  164. package/dist/store/session/index.d.ts.map +1 -1
  165. package/dist/store/session/index.js +3 -4
  166. package/dist/store/session/index.js.map +1 -1
  167. package/dist/store/session/memory.d.ts +2 -1
  168. package/dist/store/session/memory.d.ts.map +1 -1
  169. package/dist/store/session/memory.js +13 -0
  170. package/dist/store/session/memory.js.map +1 -1
  171. package/dist/store/thread/disk.d.ts +41 -0
  172. package/dist/store/thread/disk.d.ts.map +1 -0
  173. package/dist/store/thread/disk.js +229 -0
  174. package/dist/store/thread/disk.js.map +1 -0
  175. package/dist/store/thread/index.d.ts +4 -0
  176. package/dist/store/thread/index.d.ts.map +1 -0
  177. package/dist/store/thread/index.js +6 -0
  178. package/dist/store/thread/index.js.map +1 -0
  179. package/dist/store/thread/memory.d.ts +23 -0
  180. package/dist/store/thread/memory.d.ts.map +1 -0
  181. package/dist/store/thread/memory.js +90 -0
  182. package/dist/store/thread/memory.js.map +1 -0
  183. package/dist/telemetry/runtime-accessors.d.ts +4 -0
  184. package/dist/telemetry/runtime-accessors.d.ts.map +1 -0
  185. package/dist/telemetry/runtime-accessors.js +17 -0
  186. package/dist/telemetry/runtime-accessors.js.map +1 -0
  187. package/dist/types/agent/base.d.ts +17 -21
  188. package/dist/types/agent/base.d.ts.map +1 -1
  189. package/dist/types/agent/factory.d.ts +8 -2
  190. package/dist/types/agent/factory.d.ts.map +1 -1
  191. package/dist/types/agent/task.d.ts +18 -11
  192. package/dist/types/agent/task.d.ts.map +1 -1
  193. package/dist/types/ids/index.d.ts +5 -9
  194. package/dist/types/ids/index.d.ts.map +1 -1
  195. package/dist/types/ids/index.js +4 -4
  196. package/dist/types/ids/index.js.map +1 -1
  197. package/dist/types/rag/retrieval.d.ts +4 -3
  198. package/dist/types/rag/retrieval.d.ts.map +1 -1
  199. package/dist/types/run/config.d.ts +6 -5
  200. package/dist/types/run/config.d.ts.map +1 -1
  201. package/dist/types/run/metadata.d.ts +5 -18
  202. package/dist/types/run/metadata.d.ts.map +1 -1
  203. package/dist/types/session/ids.d.ts +4 -13
  204. package/dist/types/session/ids.d.ts.map +1 -1
  205. package/dist/types/session/ids.js +3 -6
  206. package/dist/types/session/ids.js.map +1 -1
  207. package/dist/types/session/index.d.ts +1 -1
  208. package/dist/types/session/index.d.ts.map +1 -1
  209. package/dist/types/session/store.d.ts +32 -10
  210. package/dist/types/session/store.d.ts.map +1 -1
  211. package/dist/types/session/store.js +3 -8
  212. package/dist/types/session/store.js.map +1 -1
  213. package/dist/types/thread/index.d.ts +2 -0
  214. package/dist/types/thread/index.d.ts.map +1 -0
  215. package/dist/types/thread/index.js +5 -0
  216. package/dist/types/thread/index.js.map +1 -0
  217. package/dist/types/thread/store.d.ts +86 -0
  218. package/dist/types/thread/store.d.ts.map +1 -0
  219. package/dist/types/thread/store.js +22 -0
  220. package/dist/types/thread/store.js.map +1 -0
  221. package/dist/utils/id.d.ts +1 -12
  222. package/dist/utils/id.d.ts.map +1 -1
  223. package/dist/utils/id.js +3 -23
  224. package/dist/utils/id.js.map +1 -1
  225. package/package.json +11 -20
  226. package/src/agents/ReactiveAgent.ts +3 -2
  227. package/src/agents/SupervisorAgent.ts +5 -2
  228. package/src/bridge/a2a/index.ts +0 -1
  229. package/src/bridge/a2a/message.ts +0 -32
  230. package/src/bridge/a2a/task.ts +8 -7
  231. package/src/contracts/api.ts +6 -42
  232. package/src/contracts/ids.ts +1 -1
  233. package/src/contracts/index.ts +2 -8
  234. package/src/contracts/schemas.ts +1 -8
  235. package/src/index.ts +3 -15
  236. package/src/manager/agent/__tests__/lifecycle.test.ts +34 -13
  237. package/src/manager/agent/lifecycle.ts +114 -35
  238. package/src/manager/index.ts +3 -0
  239. package/src/manager/run/persistence.ts +7 -1
  240. package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
  241. package/src/manager/thread/lifecycle.ts +217 -0
  242. package/src/rag/retriever.ts +2 -2
  243. package/src/registry/tool/execute.ts +1 -1
  244. package/src/runtime/query/__tests__/context.test.ts +9 -8
  245. package/src/runtime/query/context-cache.ts +4 -4
  246. package/src/runtime/query/context.ts +15 -22
  247. package/src/runtime/query/index.ts +16 -17
  248. package/src/runtime/query/iteration/index.ts +1 -1
  249. package/src/session/__tests__/integration/_fixtures.ts +36 -8
  250. package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
  251. package/src/session/__tests__/integration/capacity-caps.test.ts +13 -6
  252. package/src/session/__tests__/integration/e2e-spawn.test.ts +20 -2
  253. package/src/session/__tests__/integration/event-stream-ordering.test.ts +14 -7
  254. package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +39 -13
  255. package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +54 -19
  256. package/src/session/__tests__/integration/handoff-single-e2e.test.ts +40 -9
  257. package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +13 -10
  258. package/src/session/__tests__/integration/prev-artifact-dag.test.ts +12 -5
  259. package/src/session/__tests__/integration/retention-archive.test.ts +5 -3
  260. package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
  261. package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +4 -2
  262. package/src/session/__tests__/integration/tenant-isolation.test.ts +16 -6
  263. package/src/session/errors.ts +89 -0
  264. package/src/session/handoff/__tests__/broadcast.test.ts +56 -28
  265. package/src/session/handoff/__tests__/capacity.test.ts +26 -20
  266. package/src/session/handoff/__tests__/single.test.ts +45 -28
  267. package/src/session/handoff/assignment.ts +13 -1
  268. package/src/session/handoff/broadcast.ts +26 -1
  269. package/src/session/handoff/single.ts +23 -1
  270. package/src/session/hierarchy/__tests__/session.test.ts +9 -1
  271. package/src/session/hierarchy/index.ts +1 -0
  272. package/src/session/hierarchy/session.ts +15 -3
  273. package/src/session/hierarchy/thread.ts +55 -0
  274. package/src/session/migration/id-prefix.ts +8 -13
  275. package/src/session/retention/__tests__/archive.test.ts +5 -3
  276. package/src/session/summary/__tests__/materialize.test.ts +6 -4
  277. package/src/store/index.ts +0 -3
  278. package/src/store/session/__tests__/disk.test.ts +57 -6
  279. package/src/store/session/__tests__/memory.test.ts +84 -9
  280. package/src/store/session/disk.ts +57 -1
  281. package/src/store/session/index.ts +3 -4
  282. package/src/store/session/memory.ts +13 -1
  283. package/src/store/thread/disk.ts +261 -0
  284. package/src/store/thread/index.ts +7 -0
  285. package/src/store/thread/memory.ts +104 -0
  286. package/src/telemetry/runtime-accessors.ts +19 -0
  287. package/src/types/agent/base.ts +17 -21
  288. package/src/types/agent/factory.ts +8 -3
  289. package/src/types/agent/task.ts +19 -11
  290. package/src/types/ids/index.ts +8 -15
  291. package/src/types/rag/retrieval.ts +4 -3
  292. package/src/types/run/config.ts +6 -5
  293. package/src/types/run/metadata.ts +5 -18
  294. package/src/types/session/ids.ts +4 -15
  295. package/src/types/session/index.ts +1 -2
  296. package/src/types/session/store.ts +34 -11
  297. package/src/types/thread/index.ts +5 -0
  298. package/src/types/thread/store.ts +92 -0
  299. package/src/utils/id.ts +3 -24
  300. package/dist/provider/telemetry/setup.d.ts +0 -19
  301. package/dist/provider/telemetry/setup.d.ts.map +0 -1
  302. package/dist/provider/telemetry/setup.js +0 -102
  303. package/dist/provider/telemetry/setup.js.map +0 -1
  304. package/dist/store/conversation/memory.d.ts +0 -43
  305. package/dist/store/conversation/memory.d.ts.map +0 -1
  306. package/dist/store/conversation/memory.js +0 -108
  307. package/dist/store/conversation/memory.js.map +0 -1
  308. package/dist/telemetry/index.d.ts +0 -6
  309. package/dist/telemetry/index.d.ts.map +0 -1
  310. package/dist/telemetry/index.js +0 -4
  311. package/dist/telemetry/index.js.map +0 -1
  312. package/dist/telemetry/metrics.d.ts +0 -8
  313. package/dist/telemetry/metrics.d.ts.map +0 -1
  314. package/dist/telemetry/metrics.js +0 -53
  315. package/dist/telemetry/metrics.js.map +0 -1
  316. package/dist/types/conversation/index.d.ts +0 -14
  317. package/dist/types/conversation/index.d.ts.map +0 -1
  318. package/dist/types/conversation/index.js +0 -2
  319. package/dist/types/conversation/index.js.map +0 -1
  320. package/dist/types/telemetry/index.d.ts +0 -10
  321. package/dist/types/telemetry/index.d.ts.map +0 -1
  322. package/dist/types/telemetry/index.js +0 -2
  323. package/dist/types/telemetry/index.js.map +0 -1
  324. package/src/provider/telemetry/setup.ts +0 -125
  325. package/src/store/conversation/memory.ts +0 -144
  326. package/src/telemetry/index.ts +0 -14
  327. package/src/telemetry/metrics.ts +0 -69
  328. package/src/types/conversation/index.ts +0 -15
  329. package/src/types/telemetry/index.ts +0 -10
@@ -10,9 +10,11 @@
10
10
  */
11
11
 
12
12
  import { describe, expect, it, vi } from 'vitest'
13
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
13
14
  import { InMemorySessionStore } from '../../../store/session/memory.js'
15
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
14
16
  import type { SessionId } from '../../../types/ids/index.js'
15
- import type { ProjectId } from '../../../types/session/ids.js'
17
+ import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
16
18
  import { generateHandoffId } from '../../../utils/id.js'
17
19
  import type { HandoffAssignment } from '../../handoff/assignment.js'
18
20
  import { type BroadcastHandoffDeps, executeBroadcastHandoff } from '../../handoff/broadcast.js'
@@ -26,6 +28,7 @@ import { DEFAULT_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
26
28
 
27
29
  function buildDeps(
28
30
  store: InMemorySessionStore,
31
+ threadStore: InMemoryThreadStore,
29
32
  execOverride?: ExecFile,
30
33
  ): {
31
34
  deps: BroadcastHandoffDeps
@@ -54,6 +57,7 @@ function buildDeps(
54
57
  workspaceRegistry,
55
58
  capacity: new DefaultCapacityValidator(store),
56
59
  events: sink,
60
+ threadManager: new ThreadManager({ threadStore, sessionStore: store }),
57
61
  },
58
62
  events: { ...sink, onBroadcastRollback },
59
63
  }
@@ -62,6 +66,7 @@ function buildDeps(
62
66
  function buildAssignments(
63
67
  sourceSessionId: SessionId,
64
68
  projectId: ProjectId,
69
+ threadId: ThreadId,
65
70
  recipients: ActorRef[],
66
71
  broadcastId = 'bc_integration',
67
72
  expectedOwnerVersion = 0,
@@ -71,6 +76,7 @@ function buildAssignments(
71
76
  mode: 'broadcast' as const,
72
77
  sourceSessionId,
73
78
  tenantId: DEFAULT_TENANT,
79
+ threadId,
74
80
  projectId,
75
81
  sourceActor: userActor('usr_source'),
76
82
  recipientActor,
@@ -83,12 +89,17 @@ function buildAssignments(
83
89
  describe('Integration — broadcast handoff E2E', () => {
84
90
  it('happy: 3-recipient fan-out → each recipient has isolated worktree + source reaches awaiting_merge', async () => {
85
91
  const store = new InMemorySessionStore()
92
+ const threadStore = new InMemoryThreadStore()
86
93
  const project = await store.createProject(
87
94
  { tenantId: DEFAULT_TENANT, name: 'bc-happy' },
88
95
  DEFAULT_TENANT,
89
96
  )
97
+ const thread = await threadStore.createThread(
98
+ { projectId: project.id, title: 'bc-happy' },
99
+ DEFAULT_TENANT,
100
+ )
90
101
  const source = await store.createSession(
91
- { projectId: project.id, currentActor: userActor('usr_source') },
102
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
92
103
  DEFAULT_TENANT,
93
104
  )
94
105
 
@@ -103,10 +114,10 @@ describe('Integration — broadcast handoff E2E', () => {
103
114
  }
104
115
  return okExec()
105
116
  }
106
- const { deps } = buildDeps(store, exec)
117
+ const { deps } = buildDeps(store, threadStore, exec)
107
118
 
108
119
  const recipients = [userActor('usr_bob'), userActor('usr_carol'), userActor('usr_dan')]
109
- const assignments = buildAssignments(source.id, project.id, recipients)
120
+ const assignments = buildAssignments(source.id, project.id, thread.id, recipients)
110
121
 
111
122
  const outcomes = await executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)
112
123
  expect(outcomes).toHaveLength(3)
@@ -128,12 +139,17 @@ describe('Integration — broadcast handoff E2E', () => {
128
139
 
129
140
  it('rollback on 2nd-recipient provisioning failure: zero orphan records, partialState accurate', async () => {
130
141
  const store = new InMemorySessionStore()
142
+ const threadStore = new InMemoryThreadStore()
131
143
  const project = await store.createProject(
132
144
  { tenantId: DEFAULT_TENANT, name: 'bc-rb' },
133
145
  DEFAULT_TENANT,
134
146
  )
147
+ const thread = await threadStore.createThread(
148
+ { projectId: project.id, title: 'bc-rb' },
149
+ DEFAULT_TENANT,
150
+ )
135
151
  const source = await store.createSession(
136
- { projectId: project.id, currentActor: userActor('usr_source') },
152
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
137
153
  DEFAULT_TENANT,
138
154
  )
139
155
 
@@ -145,9 +161,9 @@ describe('Integration — broadcast handoff E2E', () => {
145
161
  }
146
162
  return okExec()
147
163
  }
148
- const { deps, events } = buildDeps(store, exec)
164
+ const { deps, events } = buildDeps(store, threadStore, exec)
149
165
 
150
- const assignments = buildAssignments(source.id, project.id, [
166
+ const assignments = buildAssignments(source.id, project.id, thread.id, [
151
167
  userActor('usr_b'),
152
168
  userActor('usr_c'),
153
169
  userActor('usr_d'),
@@ -186,18 +202,23 @@ describe('Integration — broadcast handoff E2E', () => {
186
202
 
187
203
  it('source transitions to awaiting_merge + retains currentActor as coordinator (§5.4)', async () => {
188
204
  const store = new InMemorySessionStore()
205
+ const threadStore = new InMemoryThreadStore()
189
206
  const project = await store.createProject(
190
207
  { tenantId: DEFAULT_TENANT, name: 'coord' },
191
208
  DEFAULT_TENANT,
192
209
  )
210
+ const thread = await threadStore.createThread(
211
+ { projectId: project.id, title: 'coord' },
212
+ DEFAULT_TENANT,
213
+ )
193
214
  const coordinator = userActor('usr_source')
194
215
  const source = await store.createSession(
195
- { projectId: project.id, currentActor: coordinator },
216
+ { threadId: thread.id, projectId: project.id, currentActor: coordinator },
196
217
  DEFAULT_TENANT,
197
218
  )
198
219
 
199
- const { deps } = buildDeps(store)
200
- const assignments = buildAssignments(source.id, project.id, [
220
+ const { deps } = buildDeps(store, threadStore)
221
+ const assignments = buildAssignments(source.id, project.id, thread.id, [
201
222
  userActor('usr_b'),
202
223
  userActor('usr_c'),
203
224
  ])
@@ -213,12 +234,17 @@ describe('Integration — broadcast handoff E2E', () => {
213
234
 
214
235
  it('all recipients get isolated worktrees — zero path collisions even under N=8', async () => {
215
236
  const store = new InMemorySessionStore()
237
+ const threadStore = new InMemoryThreadStore()
216
238
  const project = await store.createProject(
217
239
  { tenantId: DEFAULT_TENANT, name: 'iso' },
218
240
  DEFAULT_TENANT,
219
241
  )
242
+ const thread = await threadStore.createThread(
243
+ { projectId: project.id, title: 'iso' },
244
+ DEFAULT_TENANT,
245
+ )
220
246
  const source = await store.createSession(
221
- { projectId: project.id, currentActor: userActor('usr_source') },
247
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
222
248
  DEFAULT_TENANT,
223
249
  )
224
250
 
@@ -233,10 +259,10 @@ describe('Integration — broadcast handoff E2E', () => {
233
259
  }
234
260
  return okExec()
235
261
  }
236
- const { deps } = buildDeps(store, exec)
262
+ const { deps } = buildDeps(store, threadStore, exec)
237
263
 
238
264
  const recipients = Array.from({ length: 8 }, (_, i) => userActor(`usr_${i}`))
239
- const assignments = buildAssignments(source.id, project.id, recipients)
265
+ const assignments = buildAssignments(source.id, project.id, thread.id, recipients)
240
266
 
241
267
  const outcomes = await executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)
242
268
  expect(outcomes).toHaveLength(8)
@@ -16,7 +16,10 @@
16
16
  */
17
17
 
18
18
  import { describe, expect, it } from 'vitest'
19
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
19
20
  import { InMemorySessionStore } from '../../../store/session/memory.js'
21
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
22
+ import type { ThreadId } from '../../../types/session/ids.js'
20
23
  import { generateHandoffId } from '../../../utils/id.js'
21
24
  import type { HandoffAssignment } from '../../handoff/assignment.js'
22
25
  import { DefaultCapacityValidator } from '../../handoff/capacity.js'
@@ -31,7 +34,11 @@ import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
31
34
  import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
32
35
  import { DEFAULT_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
33
36
 
34
- function buildDeps(store: InMemorySessionStore, runStatus?: RunStatusResolver): SingleHandoffDeps {
37
+ function buildDeps(
38
+ store: InMemorySessionStore,
39
+ threadStore: InMemoryThreadStore,
40
+ runStatus?: RunStatusResolver,
41
+ ): SingleHandoffDeps {
35
42
  const driver = new GitWorktreeDriver({
36
43
  repoRoot: '/repo',
37
44
  logger: stubLogger(),
@@ -47,31 +54,38 @@ function buildDeps(store: InMemorySessionStore, runStatus?: RunStatusResolver):
47
54
  workspaceRegistry,
48
55
  capacity: new DefaultCapacityValidator(store),
49
56
  events,
57
+ threadManager: new ThreadManager({ threadStore, sessionStore: store }),
50
58
  ...(runStatus !== undefined && { runStatus }),
51
59
  }
52
60
  }
53
61
 
54
- async function seedIdleSession(store: InMemorySessionStore) {
62
+ async function seedIdleSession(store: InMemorySessionStore, threadStore: InMemoryThreadStore) {
55
63
  const project = await store.createProject(
56
64
  { tenantId: DEFAULT_TENANT, name: 'illegal' },
57
65
  DEFAULT_TENANT,
58
66
  )
67
+ const thread = await threadStore.createThread(
68
+ { projectId: project.id, title: 'illegal' },
69
+ DEFAULT_TENANT,
70
+ )
59
71
  const session = await store.createSession(
60
- { projectId: project.id, currentActor: userActor('usr_source') },
72
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
61
73
  DEFAULT_TENANT,
62
74
  )
63
- return { project, session }
75
+ return { project, thread, session }
64
76
  }
65
77
 
66
78
  function buildAssignment(
67
79
  sourceSessionId: Awaited<ReturnType<InMemorySessionStore['createSession']>>['id'],
68
80
  projectId: Awaited<ReturnType<InMemorySessionStore['createProject']>>['id'],
81
+ threadId: ThreadId,
69
82
  ): HandoffAssignment {
70
83
  return {
71
84
  id: generateHandoffId(),
72
85
  mode: 'single',
73
86
  sourceSessionId,
74
87
  tenantId: DEFAULT_TENANT,
88
+ threadId,
75
89
  projectId,
76
90
  sourceActor: userActor('usr_source'),
77
91
  recipientActor: userActor('usr_target'),
@@ -83,13 +97,14 @@ function buildAssignment(
83
97
  describe('Integration — illegal handoff transitions (§5.1)', () => {
84
98
  it('running Run → HandoffLockRejected { reason: active_run }', async () => {
85
99
  const store = new InMemorySessionStore()
86
- const { project, session } = await seedIdleSession(store)
87
- const deps = buildDeps(store, {
100
+ const threadStore = new InMemoryThreadStore()
101
+ const { project, thread, session } = await seedIdleSession(store, threadStore)
102
+ const deps = buildDeps(store, threadStore, {
88
103
  async blockingRun() {
89
104
  return { reason: 'active_run' }
90
105
  },
91
106
  })
92
- const assignment = buildAssignment(session.id, project.id)
107
+ const assignment = buildAssignment(session.id, project.id, thread.id)
93
108
 
94
109
  try {
95
110
  await executeSingleHandoff(deps, assignment, DEFAULT_TENANT)
@@ -107,15 +122,20 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
107
122
 
108
123
  it('awaiting_hitl → HandoffLockRejected { reason: pending_hitl }', async () => {
109
124
  const store = new InMemorySessionStore()
110
- const { project, session } = await seedIdleSession(store)
111
- const deps = buildDeps(store, {
125
+ const threadStore = new InMemoryThreadStore()
126
+ const { project, thread, session } = await seedIdleSession(store, threadStore)
127
+ const deps = buildDeps(store, threadStore, {
112
128
  async blockingRun() {
113
129
  return { reason: 'pending_hitl' }
114
130
  },
115
131
  })
116
132
 
117
133
  try {
118
- await executeSingleHandoff(deps, buildAssignment(session.id, project.id), DEFAULT_TENANT)
134
+ await executeSingleHandoff(
135
+ deps,
136
+ buildAssignment(session.id, project.id, thread.id),
137
+ DEFAULT_TENANT,
138
+ )
119
139
  expect.fail('expected HandoffLockRejected')
120
140
  } catch (err) {
121
141
  expect(err).toBeInstanceOf(HandoffLockRejected)
@@ -129,29 +149,39 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
129
149
  // `pending_hitl` for both. This keeps the lock-rejection enum
130
150
  // conservative (no new reason variant for a sub-state).
131
151
  const store = new InMemorySessionStore()
132
- const { project, session } = await seedIdleSession(store)
133
- const deps = buildDeps(store, {
152
+ const threadStore = new InMemoryThreadStore()
153
+ const { project, thread, session } = await seedIdleSession(store, threadStore)
154
+ const deps = buildDeps(store, threadStore, {
134
155
  async blockingRun() {
135
156
  return { reason: 'pending_hitl' }
136
157
  },
137
158
  })
138
159
 
139
160
  await expect(
140
- executeSingleHandoff(deps, buildAssignment(session.id, project.id), DEFAULT_TENANT),
161
+ executeSingleHandoff(
162
+ deps,
163
+ buildAssignment(session.id, project.id, thread.id),
164
+ DEFAULT_TENANT,
165
+ ),
141
166
  ).rejects.toBeInstanceOf(HandoffLockRejected)
142
167
  })
143
168
 
144
169
  it('awaiting_subsession → HandoffLockRejected { reason: pending_subsession }', async () => {
145
170
  const store = new InMemorySessionStore()
146
- const { project, session } = await seedIdleSession(store)
147
- const deps = buildDeps(store, {
171
+ const threadStore = new InMemoryThreadStore()
172
+ const { project, thread, session } = await seedIdleSession(store, threadStore)
173
+ const deps = buildDeps(store, threadStore, {
148
174
  async blockingRun() {
149
175
  return { reason: 'pending_subsession' }
150
176
  },
151
177
  })
152
178
 
153
179
  try {
154
- await executeSingleHandoff(deps, buildAssignment(session.id, project.id), DEFAULT_TENANT)
180
+ await executeSingleHandoff(
181
+ deps,
182
+ buildAssignment(session.id, project.id, thread.id),
183
+ DEFAULT_TENANT,
184
+ )
155
185
  expect.fail('expected HandoffLockRejected')
156
186
  } catch (err) {
157
187
  expect(err).toBeInstanceOf(HandoffLockRejected)
@@ -163,17 +193,22 @@ describe('Integration — illegal handoff transitions (§5.1)', () => {
163
193
  // When the session itself is already non-idle (e.g. `active`), the lock
164
194
  // rejection fires from the status check — no RunStatusResolver invoked.
165
195
  const store = new InMemorySessionStore()
166
- const { project, session } = await seedIdleSession(store)
196
+ const threadStore = new InMemoryThreadStore()
197
+ const { project, thread, session } = await seedIdleSession(store, threadStore)
167
198
  await store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
168
199
 
169
- const deps = buildDeps(store, {
200
+ const deps = buildDeps(store, threadStore, {
170
201
  async blockingRun() {
171
202
  return null // resolver would allow, but status guard trips first
172
203
  },
173
204
  })
174
205
 
175
206
  await expect(
176
- executeSingleHandoff(deps, buildAssignment(session.id, project.id), DEFAULT_TENANT),
207
+ executeSingleHandoff(
208
+ deps,
209
+ buildAssignment(session.id, project.id, thread.id),
210
+ DEFAULT_TENANT,
211
+ ),
177
212
  ).rejects.toBeInstanceOf(HandoffLockRejected)
178
213
  })
179
214
  })
@@ -13,7 +13,9 @@
13
13
  */
14
14
 
15
15
  import { describe, expect, it, vi } from 'vitest'
16
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
16
17
  import { InMemorySessionStore } from '../../../store/session/memory.js'
18
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
17
19
  import type { TenantId } from '../../../types/ids/index.js'
18
20
  import { generateHandoffId } from '../../../utils/id.js'
19
21
  import { TenantIsolationError } from '../../errors.js'
@@ -26,7 +28,10 @@ import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
26
28
  import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
27
29
  import { DEFAULT_TENANT, OTHER_TENANT, okExec, stubLogger, userActor } from './_fixtures.js'
28
30
 
29
- function buildHandoffDeps(store: InMemorySessionStore): {
31
+ function buildHandoffDeps(
32
+ store: InMemorySessionStore,
33
+ threadStore: InMemoryThreadStore,
34
+ ): {
30
35
  deps: SingleHandoffDeps
31
36
  updateCalls: Array<{ status?: string; ownerVersion?: number }>
32
37
  } {
@@ -52,12 +57,14 @@ function buildHandoffDeps(store: InMemorySessionStore): {
52
57
  return originalUpdate(session, tenantId)
53
58
  }
54
59
 
60
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
55
61
  return {
56
62
  deps: {
57
63
  store,
58
64
  workspaceRegistry,
59
65
  capacity: new DefaultCapacityValidator(store),
60
66
  events: sink,
67
+ threadManager,
61
68
  },
62
69
  updateCalls,
63
70
  }
@@ -66,23 +73,29 @@ function buildHandoffDeps(store: InMemorySessionStore): {
66
73
  describe('Integration — single-recipient handoff E2E', () => {
67
74
  it('idle → locked → commit: source previousActors grew + ownerVersion bumped atomically', async () => {
68
75
  const store = new InMemorySessionStore()
76
+ const threadStore = new InMemoryThreadStore()
69
77
  const project = await store.createProject(
70
78
  { tenantId: DEFAULT_TENANT, name: 'ho' },
71
79
  DEFAULT_TENANT,
72
80
  )
81
+ const thread = await threadStore.createThread(
82
+ { projectId: project.id, title: 'ho' },
83
+ DEFAULT_TENANT,
84
+ )
73
85
  const sourceActor = userActor('usr_source')
74
86
  const recipientActor = userActor('usr_target')
75
87
  const session = await store.createSession(
76
- { projectId: project.id, currentActor: sourceActor },
88
+ { threadId: thread.id, projectId: project.id, currentActor: sourceActor },
77
89
  DEFAULT_TENANT,
78
90
  )
79
91
 
80
- const { deps, updateCalls } = buildHandoffDeps(store)
92
+ const { deps, updateCalls } = buildHandoffDeps(store, threadStore)
81
93
  const assignment: HandoffAssignment = {
82
94
  id: generateHandoffId(),
83
95
  mode: 'single',
84
96
  sourceSessionId: session.id,
85
97
  tenantId: DEFAULT_TENANT,
98
+ threadId: thread.id,
86
99
  projectId: project.id,
87
100
  sourceActor,
88
101
  recipientActor,
@@ -115,21 +128,27 @@ describe('Integration — single-recipient handoff E2E', () => {
115
128
 
116
129
  it('cross-tenant assignment rejects at entry (TenantIsolationError)', async () => {
117
130
  const store = new InMemorySessionStore()
131
+ const threadStore = new InMemoryThreadStore()
118
132
  const project = await store.createProject(
119
133
  { tenantId: DEFAULT_TENANT, name: 'ct' },
120
134
  DEFAULT_TENANT,
121
135
  )
136
+ const thread = await threadStore.createThread(
137
+ { projectId: project.id, title: 'ct' },
138
+ DEFAULT_TENANT,
139
+ )
122
140
  const session = await store.createSession(
123
- { projectId: project.id, currentActor: userActor('usr_source') },
141
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
124
142
  DEFAULT_TENANT,
125
143
  )
126
144
 
127
- const { deps } = buildHandoffDeps(store)
145
+ const { deps } = buildHandoffDeps(store, threadStore)
128
146
  const assignment: HandoffAssignment = {
129
147
  id: generateHandoffId(),
130
148
  mode: 'single',
131
149
  sourceSessionId: session.id,
132
150
  tenantId: OTHER_TENANT,
151
+ threadId: thread.id,
133
152
  projectId: project.id,
134
153
  sourceActor: userActor('usr_source', OTHER_TENANT),
135
154
  recipientActor: userActor('usr_target', OTHER_TENANT),
@@ -144,21 +163,27 @@ describe('Integration — single-recipient handoff E2E', () => {
144
163
 
145
164
  it('source-owned workspace provisioned for recipient', async () => {
146
165
  const store = new InMemorySessionStore()
166
+ const threadStore = new InMemoryThreadStore()
147
167
  const project = await store.createProject(
148
168
  { tenantId: DEFAULT_TENANT, name: 'wsp' },
149
169
  DEFAULT_TENANT,
150
170
  )
171
+ const thread = await threadStore.createThread(
172
+ { projectId: project.id, title: 'wsp' },
173
+ DEFAULT_TENANT,
174
+ )
151
175
  const source = await store.createSession(
152
- { projectId: project.id, currentActor: userActor('usr_source') },
176
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
153
177
  DEFAULT_TENANT,
154
178
  )
155
179
 
156
- const { deps } = buildHandoffDeps(store)
180
+ const { deps } = buildHandoffDeps(store, threadStore)
157
181
  const assignment: HandoffAssignment = {
158
182
  id: generateHandoffId(),
159
183
  mode: 'single',
160
184
  sourceSessionId: source.id,
161
185
  tenantId: DEFAULT_TENANT,
186
+ threadId: thread.id,
162
187
  projectId: project.id,
163
188
  sourceActor: userActor('usr_source'),
164
189
  recipientActor: userActor('usr_target'),
@@ -190,21 +215,27 @@ describe('Integration — single-recipient handoff E2E', () => {
190
215
  it('denormalized tenantId stamped on Session + SubSession records', async () => {
191
216
  const _tenantType: TenantId = DEFAULT_TENANT
192
217
  const store = new InMemorySessionStore()
218
+ const threadStore = new InMemoryThreadStore()
193
219
  const project = await store.createProject(
194
220
  { tenantId: DEFAULT_TENANT, name: 'denorm' },
195
221
  DEFAULT_TENANT,
196
222
  )
223
+ const thread = await threadStore.createThread(
224
+ { projectId: project.id, title: 'denorm' },
225
+ DEFAULT_TENANT,
226
+ )
197
227
  const source = await store.createSession(
198
- { projectId: project.id, currentActor: userActor('usr_source') },
228
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
199
229
  DEFAULT_TENANT,
200
230
  )
201
- const { deps } = buildHandoffDeps(store)
231
+ const { deps } = buildHandoffDeps(store, threadStore)
202
232
 
203
233
  const assignment: HandoffAssignment = {
204
234
  id: generateHandoffId(),
205
235
  mode: 'single',
206
236
  sourceSessionId: source.id,
207
237
  tenantId: DEFAULT_TENANT,
238
+ threadId: thread.id,
208
239
  projectId: project.id,
209
240
  sourceActor: userActor('usr_source'),
210
241
  recipientActor: userActor('usr_target'),
@@ -10,9 +10,12 @@
10
10
  */
11
11
 
12
12
  import { describe, expect, it } from 'vitest'
13
+ import type { ThreadId } from '../../../types/session/ids.js'
13
14
  import { TenantIsolationError } from '../../errors.js'
14
15
  import { DEFAULT_TENANT, agentActor, buildHarness, userActor } from './_fixtures.js'
15
16
 
17
+ const TEST_THREAD_ID = 'thd_test' as ThreadId
18
+
16
19
  describe('Integration — hierarchy lifecycle', () => {
17
20
  it('creates Tenant → Project → Session → SubSession with properly branded IDs', async () => {
18
21
  const { store } = buildHarness()
@@ -23,7 +26,7 @@ describe('Integration — hierarchy lifecycle', () => {
23
26
  expect(project.tenantId.startsWith('tnt_')).toBe(true)
24
27
 
25
28
  const session = await store.createSession(
26
- { projectId: project.id, currentActor: userActor('usr_a') },
29
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
27
30
  tenant,
28
31
  )
29
32
  expect(session.id.startsWith('ses_')).toBe(true)
@@ -34,7 +37,7 @@ describe('Integration — hierarchy lifecycle', () => {
34
37
  expect(session.previousActors).toEqual([])
35
38
 
36
39
  const childSession = await store.createSession(
37
- { projectId: project.id, currentActor: agentActor('agt_worker') },
40
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_worker') },
38
41
  tenant,
39
42
  )
40
43
  const subSession = await store.createSubSession(
@@ -59,15 +62,15 @@ describe('Integration — hierarchy lifecycle', () => {
59
62
 
60
63
  const project = await store.createProject({ tenantId: tenant, name: 'drill' }, tenant)
61
64
  const parent = await store.createSession(
62
- { projectId: project.id, currentActor: userActor('usr_root') },
65
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_root') },
63
66
  tenant,
64
67
  )
65
68
  const childA = await store.createSession(
66
- { projectId: project.id, currentActor: agentActor('agt_a') },
69
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
67
70
  tenant,
68
71
  )
69
72
  const childB = await store.createSession(
70
- { projectId: project.id, currentActor: agentActor('agt_b') },
73
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_b') },
71
74
  tenant,
72
75
  )
73
76
  await store.createSubSession(
@@ -120,7 +123,7 @@ describe('Integration — hierarchy lifecycle', () => {
120
123
  const userC = userActor('usr_c')
121
124
 
122
125
  const session = await store.createSession(
123
- { projectId: project.id, currentActor: userA },
126
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userA },
124
127
  tenant,
125
128
  )
126
129
 
@@ -154,11 +157,11 @@ describe('Integration — hierarchy lifecycle', () => {
154
157
 
155
158
  const project = await store.createProject({ tenantId: tenant, name: 'cycle' }, tenant)
156
159
  const sA = await store.createSession(
157
- { projectId: project.id, currentActor: userActor('usr_a') },
160
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
158
161
  tenant,
159
162
  )
160
163
  const sB = await store.createSession(
161
- { projectId: project.id, currentActor: userActor('usr_b') },
164
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_b') },
162
165
  tenant,
163
166
  )
164
167
 
@@ -195,11 +198,11 @@ describe('Integration — hierarchy lifecycle', () => {
195
198
 
196
199
  const project = await store.createProject({ tenantId: tenant, name: 'lifecycle' }, tenant)
197
200
  const parent = await store.createSession(
198
- { projectId: project.id, currentActor: userActor('usr_a') },
201
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
199
202
  tenant,
200
203
  )
201
204
  const child = await store.createSession(
202
- { projectId: project.id, currentActor: agentActor('agt_a') },
205
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
203
206
  tenant,
204
207
  )
205
208
  const sub = await store.createSubSession(
@@ -14,7 +14,12 @@
14
14
  import { describe, expect, it } from 'vitest'
15
15
  import { InMemorySessionStore } from '../../../store/session/memory.js'
16
16
  import type { SessionId } from '../../../types/ids/index.js'
17
- import type { DeliverableId, SubSessionId, SummaryId } from '../../../types/session/ids.js'
17
+ import type {
18
+ DeliverableId,
19
+ SubSessionId,
20
+ SummaryId,
21
+ ThreadId,
22
+ } from '../../../types/session/ids.js'
18
23
  import {
19
24
  ArtifactRefCycleError,
20
25
  type InterventionChainLoader,
@@ -24,6 +29,8 @@ import {
24
29
  import type { DeliverableRef, SessionSummaryDeliverable } from '../../summary/deliverable.js'
25
30
  import { DEFAULT_TENANT, agentActor, userActor } from './_fixtures.js'
26
31
 
32
+ const TEST_THREAD_ID = 'thd_test' as ThreadId
33
+
27
34
  /**
28
35
  * Build a live loader pointing at a real InMemorySessionStore. Each node
29
36
  * resolves via `findParentSubSession`-style lookup on the store's sub-session
@@ -68,7 +75,7 @@ async function buildLinearChain(
68
75
  let previous: SessionId | null = null
69
76
  for (let i = 0; i < length; i++) {
70
77
  const s = await store.createSession(
71
- { projectId: project.id, currentActor: agentActor(`agt_${i}`) },
78
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
72
79
  DEFAULT_TENANT,
73
80
  )
74
81
  if (previous) {
@@ -260,15 +267,15 @@ describe('Integration — prevArtifactRef DAG against real store', () => {
260
267
  DEFAULT_TENANT,
261
268
  )
262
269
  const sA = await store.createSession(
263
- { projectId: project.id, currentActor: agentActor('agt_a') },
270
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_a') },
264
271
  DEFAULT_TENANT,
265
272
  )
266
273
  const sB = await store.createSession(
267
- { projectId: project.id, currentActor: agentActor('agt_b') },
274
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_b') },
268
275
  DEFAULT_TENANT,
269
276
  )
270
277
  const sC = await store.createSession(
271
- { projectId: project.id, currentActor: agentActor('agt_c') },
278
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_c') },
272
279
  DEFAULT_TENANT,
273
280
  )
274
281
 
@@ -15,24 +15,26 @@ import { join } from 'node:path'
15
15
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
16
16
  import { InMemorySessionStore } from '../../../store/session/memory.js'
17
17
  import { createUserMessage } from '../../../types/message/index.js'
18
- import type { WorkspaceId } from '../../../types/session/ids.js'
18
+ import type { ThreadId, WorkspaceId } from '../../../types/session/ids.js'
19
19
  import { ArchivalManager, ArchiveNotConfiguredError } from '../../retention/archive.js'
20
20
  import { DiskArchiveBackend } from '../../retention/disk-backend.js'
21
21
  import type { WorkspaceRef } from '../../workspace/ref.js'
22
22
  import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
23
23
  import { DEFAULT_TENANT, agentActor, userActor } from './_fixtures.js'
24
24
 
25
+ const TEST_THREAD_ID = 'thd_test' as ThreadId
26
+
25
27
  async function seedIdleSubSession(store: InMemorySessionStore) {
26
28
  const project = await store.createProject(
27
29
  { tenantId: DEFAULT_TENANT, name: 'archive' },
28
30
  DEFAULT_TENANT,
29
31
  )
30
32
  const parent = await store.createSession(
31
- { projectId: project.id, currentActor: userActor('usr_a') },
33
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor('usr_a') },
32
34
  DEFAULT_TENANT,
33
35
  )
34
36
  const child = await store.createSession(
35
- { projectId: project.id, currentActor: agentActor('agt_w') },
37
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor('agt_w') },
36
38
  DEFAULT_TENANT,
37
39
  )
38
40
  const sub = await store.createSubSession(