@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
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Integration — Thread archive gate enforced at session-creation ingress
3
+ * sites (Phase 2.6).
4
+ *
5
+ * The Phase 2.5 commit flagged this as a known gap:
6
+ * > spawn and handoff paths do not yet invoke `ThreadManager.requireOpen`
7
+ * > before creating a child session. Until they do, the archive invariant
8
+ * > is best-effort.
9
+ *
10
+ * Phase 2.6 wires `requireOpen` into `AgentManager.provisionSpawn` + both
11
+ * handoff flows. These tests drive the archived-thread-then-attempt-creation
12
+ * scenarios end-to-end against the real stack.
13
+ */
14
+
15
+ import { describe, expect, it, vi } from 'vitest'
16
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
17
+ import { InMemorySessionStore } from '../../../store/session/memory.js'
18
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
19
+ import type { AgentId, RunId, UserId } from '../../../types/ids/index.js'
20
+ import { generateHandoffId } from '../../../utils/id.js'
21
+ import { ThreadClosedError } from '../../errors.js'
22
+ import type { HandoffAssignment } from '../../handoff/assignment.js'
23
+ import { type BroadcastHandoffDeps, executeBroadcastHandoff } from '../../handoff/broadcast.js'
24
+ import { DefaultCapacityValidator } from '../../handoff/capacity.js'
25
+ import type { HandoffEventSink } from '../../handoff/events.js'
26
+ import { type SingleHandoffDeps, executeSingleHandoff } from '../../handoff/single.js'
27
+ import type { ActorRef } from '../../hierarchy/actor.js'
28
+ import { GitWorktreeDriver } from '../../workspace/git-worktree.js'
29
+ import { WorkspaceBackendRegistry } from '../../workspace/registry.js'
30
+ import {
31
+ DEFAULT_TENANT,
32
+ buildAgent,
33
+ buildDefinition,
34
+ buildHarness,
35
+ buildSendMessageOptions,
36
+ buildTaskContext,
37
+ okExec,
38
+ seedActiveParent,
39
+ stubLogger,
40
+ userActor,
41
+ } from './_fixtures.js'
42
+
43
+ describe('Integration — archive gate (Phase 2.6)', () => {
44
+ it('AgentManager spawn: rejects with ThreadClosedError when parent thread is archived', async () => {
45
+ const harness = buildHarness()
46
+ const { project, thread, session, actor } = await seedActiveParent(harness)
47
+
48
+ // Archive the thread via the manager so the invariant is enforced — no
49
+ // in-flight sessions under this thread, so archive succeeds.
50
+ await harness.store.updateSession({ ...session, status: 'idle' }, DEFAULT_TENANT)
51
+ await harness.threadManager.archive(thread.id, DEFAULT_TENANT)
52
+ // Flip session back to active so the spawn's capacity path reaches
53
+ // provisionSpawn (the archive gate should still reject).
54
+ await harness.store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
55
+
56
+ harness.registry.register(buildDefinition(buildAgent('worker')))
57
+
58
+ await expect(
59
+ harness.manager.sendMessage(
60
+ buildSendMessageOptions({
61
+ agentId: 'worker',
62
+ parentSessionId: session.id,
63
+ projectId: project.id,
64
+ tenantId: DEFAULT_TENANT,
65
+ parentActor: actor,
66
+ }),
67
+ buildTaskContext({
68
+ sessionId: session.id,
69
+ projectId: project.id,
70
+ threadId: thread.id,
71
+ tenantId: DEFAULT_TENANT,
72
+ parentActor: actor,
73
+ }),
74
+ ),
75
+ ).rejects.toBeInstanceOf(ThreadClosedError)
76
+
77
+ // Archive invariant held: no new child sessions under the archived thread.
78
+ const underThread = await harness.store.listSessions(thread.id, DEFAULT_TENANT)
79
+ expect(underThread).toHaveLength(1)
80
+ expect(underThread[0]?.id).toBe(session.id)
81
+ })
82
+
83
+ it('Single handoff: rejects with ThreadClosedError when thread is archived (before CAS lock)', async () => {
84
+ const store = new InMemorySessionStore()
85
+ const threadStore = new InMemoryThreadStore()
86
+ const project = await store.createProject(
87
+ { tenantId: DEFAULT_TENANT, name: 'archive-single' },
88
+ DEFAULT_TENANT,
89
+ )
90
+ const thread = await threadStore.createThread(
91
+ { projectId: project.id, title: 'archive-single' },
92
+ DEFAULT_TENANT,
93
+ )
94
+ const sourceActor: ActorRef = {
95
+ kind: 'user',
96
+ userId: 'usr_source' as UserId,
97
+ tenantId: DEFAULT_TENANT,
98
+ }
99
+ const session = await store.createSession(
100
+ { threadId: thread.id, projectId: project.id, currentActor: sourceActor },
101
+ DEFAULT_TENANT,
102
+ )
103
+
104
+ // Archive the thread directly — session is idle, so archive precondition
105
+ // holds via listSessions.
106
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
107
+ await threadManager.archive(thread.id, DEFAULT_TENANT)
108
+
109
+ const driver = new GitWorktreeDriver({
110
+ repoRoot: '/repo',
111
+ logger: stubLogger(),
112
+ execFile: async () => okExec(),
113
+ })
114
+ const workspaceRegistry = new WorkspaceBackendRegistry()
115
+ workspaceRegistry.register(driver)
116
+
117
+ const events: HandoffEventSink = {
118
+ onLocked: vi.fn(),
119
+ onUnlocked: vi.fn(),
120
+ onCommitted: vi.fn(),
121
+ onBroadcastRollback: vi.fn(),
122
+ }
123
+ const deps: SingleHandoffDeps = {
124
+ store,
125
+ workspaceRegistry,
126
+ capacity: new DefaultCapacityValidator(store),
127
+ events,
128
+ threadManager,
129
+ }
130
+
131
+ const assignment: HandoffAssignment = {
132
+ id: generateHandoffId(),
133
+ mode: 'single',
134
+ sourceSessionId: session.id,
135
+ tenantId: DEFAULT_TENANT,
136
+ threadId: thread.id,
137
+ projectId: project.id,
138
+ sourceActor,
139
+ recipientActor: userActor('usr_target'),
140
+ expectedOwnerVersion: 0,
141
+ createdAt: new Date('2026-04-19'),
142
+ }
143
+
144
+ await expect(executeSingleHandoff(deps, assignment, DEFAULT_TENANT)).rejects.toBeInstanceOf(
145
+ ThreadClosedError,
146
+ )
147
+
148
+ // Critical: source session must still be idle (lock never acquired).
149
+ const reloaded = await store.getSession(session.id, DEFAULT_TENANT)
150
+ expect(reloaded?.status).toBe('idle')
151
+ expect(reloaded?.ownerVersion).toBe(0)
152
+ expect(events.onLocked).not.toHaveBeenCalled()
153
+ })
154
+
155
+ it('Broadcast handoff: rejects with ThreadClosedError when thread is archived (no CAS, no worktrees)', async () => {
156
+ const store = new InMemorySessionStore()
157
+ const threadStore = new InMemoryThreadStore()
158
+ const project = await store.createProject(
159
+ { tenantId: DEFAULT_TENANT, name: 'archive-bc' },
160
+ DEFAULT_TENANT,
161
+ )
162
+ const thread = await threadStore.createThread(
163
+ { projectId: project.id, title: 'archive-bc' },
164
+ DEFAULT_TENANT,
165
+ )
166
+ const source = await store.createSession(
167
+ { threadId: thread.id, projectId: project.id, currentActor: userActor('usr_source') },
168
+ DEFAULT_TENANT,
169
+ )
170
+
171
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
172
+ await threadManager.archive(thread.id, DEFAULT_TENANT)
173
+
174
+ let worktreeAdds = 0
175
+ const driver = new GitWorktreeDriver({
176
+ repoRoot: '/repo',
177
+ logger: stubLogger(),
178
+ execFile: async (_file, args) => {
179
+ if (args.includes('add')) worktreeAdds += 1
180
+ return okExec()
181
+ },
182
+ })
183
+ const workspaceRegistry = new WorkspaceBackendRegistry()
184
+ workspaceRegistry.register(driver)
185
+
186
+ const events: HandoffEventSink = {
187
+ onLocked: vi.fn(),
188
+ onUnlocked: vi.fn(),
189
+ onCommitted: vi.fn(),
190
+ onBroadcastRollback: vi.fn(),
191
+ }
192
+ const deps: BroadcastHandoffDeps = {
193
+ store,
194
+ workspaceRegistry,
195
+ capacity: new DefaultCapacityValidator(store),
196
+ events,
197
+ threadManager,
198
+ }
199
+
200
+ const assignments: HandoffAssignment[] = [userActor('usr_b'), userActor('usr_c')].map(
201
+ (recipientActor) => ({
202
+ id: generateHandoffId(),
203
+ mode: 'broadcast' as const,
204
+ sourceSessionId: source.id,
205
+ tenantId: DEFAULT_TENANT,
206
+ threadId: thread.id,
207
+ projectId: project.id,
208
+ sourceActor: userActor('usr_source'),
209
+ recipientActor,
210
+ expectedOwnerVersion: 0,
211
+ broadcastId: 'bc_archive',
212
+ createdAt: new Date('2026-04-19'),
213
+ }),
214
+ )
215
+
216
+ await expect(executeBroadcastHandoff(deps, assignments, DEFAULT_TENANT)).rejects.toBeInstanceOf(
217
+ ThreadClosedError,
218
+ )
219
+
220
+ // Source never locked, no worktrees provisioned, no rollback event —
221
+ // the archive gate short-circuited before any side-effect landed.
222
+ const reloaded = await store.getSession(source.id, DEFAULT_TENANT)
223
+ expect(reloaded?.status).toBe('idle')
224
+ expect(reloaded?.ownerVersion).toBe(0)
225
+ expect(events.onLocked).not.toHaveBeenCalled()
226
+ expect(events.onBroadcastRollback).not.toHaveBeenCalled()
227
+ expect(worktreeAdds).toBe(0)
228
+ })
229
+
230
+ it('Phase 2.6 closure: post-archive spawn via AgentManager rejects (kernel ingress path gated)', async () => {
231
+ // End-to-end proof that the production ingress path honors the
232
+ // archive status. Direct `SessionStore.createSession` / `updateSession`
233
+ // remain ungated at the store layer (by design — the store has no
234
+ // cross-store awareness), so this test exercises the manager path,
235
+ // not the store boundary.
236
+ const harness = buildHarness()
237
+ const { project, thread, session } = await seedActiveParent(harness)
238
+
239
+ await harness.store.updateSession({ ...session, status: 'idle' }, DEFAULT_TENANT)
240
+ await harness.threadManager.archive(thread.id, DEFAULT_TENANT)
241
+
242
+ // Attempt to spawn directly via context threading (bypass the fixture
243
+ // builder to prove the gate trips from AgentManager, not from the
244
+ // fixture).
245
+ harness.registry.register(buildDefinition(buildAgent('leaker')))
246
+
247
+ const childActor: ActorRef = {
248
+ kind: 'agent',
249
+ agentId: 'leaker' as AgentId,
250
+ tenantId: DEFAULT_TENANT,
251
+ }
252
+
253
+ // Flip session to active again so capacity checks pass and the
254
+ // archive-gate is what fails — not an earlier guard.
255
+ await harness.store.updateSession({ ...session, status: 'active' }, DEFAULT_TENANT)
256
+
257
+ await expect(
258
+ harness.manager.sendMessage(
259
+ {
260
+ agentId: 'leaker',
261
+ input: { messages: [], workingDirectory: '/tmp' },
262
+ parentSessionId: session.id,
263
+ tenantId: DEFAULT_TENANT,
264
+ projectId: project.id,
265
+ parentActor: childActor,
266
+ },
267
+ {
268
+ parentRunId: 'run_post_archive' as RunId,
269
+ parentAgentId: 'supervisor',
270
+ parentAbortController: new AbortController(),
271
+ depth: 0,
272
+ budgetTracker: { total: 10_000, remaining: 10_000 },
273
+ tenantId: DEFAULT_TENANT,
274
+ threadId: thread.id,
275
+ sessionId: session.id,
276
+ projectId: project.id,
277
+ parentActor: childActor,
278
+ },
279
+ ),
280
+ ).rejects.toBeInstanceOf(ThreadClosedError)
281
+
282
+ // The gate tripped before any observable side effect — listSessions
283
+ // still shows only the original seeded session, not a smuggled child.
284
+ const underThread = await harness.store.listSessions(thread.id, DEFAULT_TENANT)
285
+ expect(underThread).toHaveLength(1)
286
+ expect(underThread[0]?.id).toBe(session.id)
287
+ })
288
+ })
@@ -28,14 +28,18 @@ import {
28
28
  describe('Integration — capacity caps at spawn sites', () => {
29
29
  it('spawn at depth 4 accepted; depth 5 rejected (default maxDelegationDepth=4)', async () => {
30
30
  const harness = buildHarness()
31
- const { project } = await seedActiveParent(harness)
31
+ const { project, thread } = await seedActiveParent(harness)
32
32
 
33
33
  // Build a depth-4 ancestry chain under the project. Each layer flips to
34
34
  // `active` so it is a legal spawn parent via the real AgentManager.
35
35
  const chainActors: ActorRef[] = [userActor('usr_root')]
36
36
  let parentSessionId = (
37
37
  await harness.store.createSession(
38
- { projectId: project.id, currentActor: chainActors[0] ?? userActor('usr_root') },
38
+ {
39
+ threadId: thread.id,
40
+ projectId: project.id,
41
+ currentActor: chainActors[0] ?? userActor('usr_root'),
42
+ },
39
43
  DEFAULT_TENANT,
40
44
  )
41
45
  ).id
@@ -44,7 +48,7 @@ describe('Integration — capacity caps at spawn sites', () => {
44
48
 
45
49
  for (let i = 0; i < 4; i++) {
46
50
  const child = await harness.store.createSession(
47
- { projectId: project.id, currentActor: agentActor(`agt_${i}`) },
51
+ { threadId: thread.id, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
48
52
  DEFAULT_TENANT,
49
53
  )
50
54
  await harness.store.createSubSession(
@@ -68,6 +72,7 @@ describe('Integration — capacity caps at spawn sites', () => {
68
72
  const context = buildTaskContext({
69
73
  sessionId: tail,
70
74
  projectId: project.id,
75
+ threadId: thread.id,
71
76
  tenantId: DEFAULT_TENANT,
72
77
  parentActor: userActor('usr_root'),
73
78
  })
@@ -86,12 +91,12 @@ describe('Integration — capacity caps at spawn sites', () => {
86
91
 
87
92
  it('spawn at width 8 accepted; 9th sibling rejected (default maxDelegationWidth=8)', async () => {
88
93
  const harness = buildHarness()
89
- const { project, session, actor } = await seedActiveParent(harness)
94
+ const { project, thread, session, actor } = await seedActiveParent(harness)
90
95
 
91
96
  // Seed 8 existing children directly through the store.
92
97
  for (let i = 0; i < 8; i++) {
93
98
  const child = await harness.store.createSession(
94
- { projectId: project.id, currentActor: agentActor(`agt_${i}`) },
99
+ { threadId: thread.id, projectId: project.id, currentActor: agentActor(`agt_${i}`) },
95
100
  DEFAULT_TENANT,
96
101
  )
97
102
  await harness.store.createSubSession(
@@ -110,6 +115,7 @@ describe('Integration — capacity caps at spawn sites', () => {
110
115
  const context = buildTaskContext({
111
116
  sessionId: session.id,
112
117
  projectId: project.id,
118
+ threadId: thread.id,
113
119
  tenantId: DEFAULT_TENANT,
114
120
  parentActor: actor,
115
121
  })
@@ -133,7 +139,7 @@ describe('Integration — capacity caps at spawn sites', () => {
133
139
  // DefaultCapacityValidator(store) so it walks the actual persisted
134
140
  // parent chain — confirms the wiring rather than a direct unit call.
135
141
  const harness = buildHarness()
136
- const { project, session, actor } = await seedActiveParent(harness)
142
+ const { project, thread, session, actor } = await seedActiveParent(harness)
137
143
 
138
144
  harness.registry.register(buildDefinition(buildAgent('worker')))
139
145
 
@@ -142,6 +148,7 @@ describe('Integration — capacity caps at spawn sites', () => {
142
148
  const context = buildTaskContext({
143
149
  sessionId: session.id,
144
150
  projectId: project.id,
151
+ threadId: thread.id,
145
152
  tenantId: DEFAULT_TENANT,
146
153
  parentActor: actor,
147
154
  })
@@ -18,8 +18,10 @@
18
18
  import { describe, expect, it } from 'vitest'
19
19
  import { EMPTY_TOKEN_USAGE } from '../../../constants/limits.js'
20
20
  import { AgentManager } from '../../../manager/agent/lifecycle.js'
21
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
21
22
  import { AgentRegistry } from '../../../registry/agent/definitions.js'
22
23
  import { InMemorySessionStore } from '../../../store/session/memory.js'
24
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
23
25
  import type {
24
26
  AgentCapabilities,
25
27
  AgentInput,
@@ -93,7 +95,12 @@ function buildDefinition(agent: Agent<BaseAgentConfig, BaseAgentResult>): AgentD
93
95
  describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
94
96
  it('emits the §10.5 event sequence with lineage + schemaVersion stamping', async () => {
95
97
  const store = new InMemorySessionStore()
98
+ const threadStore = new InMemoryThreadStore()
96
99
  const project = await store.createProject({ tenantId: tenant, name: 'e2e-project' }, tenant)
100
+ const thread = await threadStore.createThread(
101
+ { projectId: project.id, title: 'e2e-spawn' },
102
+ tenant,
103
+ )
97
104
 
98
105
  const userActor: ActorRef = {
99
106
  kind: 'user',
@@ -102,7 +109,7 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
102
109
  }
103
110
 
104
111
  const parentSession = await store.createSession(
105
- { projectId: project.id, currentActor: userActor },
112
+ { threadId: thread.id, projectId: project.id, currentActor: userActor },
106
113
  tenant,
107
114
  )
108
115
  // Parent Run in flight — session active while the spawn is happening.
@@ -117,11 +124,13 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
117
124
  const registry = new AgentRegistry()
118
125
  registry.register(buildDefinition(buildAgent('worker')))
119
126
 
127
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
120
128
  const manager = new AgentManager(registry, undefined, {
121
129
  sessionStore: store,
122
130
  summaryMaterializer: materializer,
123
131
  workspaceRegistry: new WorkspaceBackendRegistry(),
124
132
  capacity: new DefaultCapacityValidator(store),
133
+ threadManager,
125
134
  })
126
135
 
127
136
  const capturedEvents: RunEvent[] = []
@@ -136,6 +145,7 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
136
145
  depth: 0,
137
146
  budgetTracker: { total: 100_000, remaining: 100_000 },
138
147
  tenantId: tenant,
148
+ threadId: thread.id,
139
149
  sessionId: parentSession.id,
140
150
  projectId: project.id,
141
151
  parentActor: userActor,
@@ -212,7 +222,12 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
212
222
 
213
223
  it('closes the parent→child message gap: parent can re-ingest child content via drill + summary', async () => {
214
224
  const store = new InMemorySessionStore()
225
+ const threadStore = new InMemoryThreadStore()
215
226
  const project = await store.createProject({ tenantId: tenant, name: 'e2e-gap' }, tenant)
227
+ const thread = await threadStore.createThread(
228
+ { projectId: project.id, title: 'e2e-gap' },
229
+ tenant,
230
+ )
216
231
 
217
232
  const userActor: ActorRef = {
218
233
  kind: 'user',
@@ -220,7 +235,7 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
220
235
  tenantId: tenant,
221
236
  }
222
237
  const parentSession = await store.createSession(
223
- { projectId: project.id, currentActor: userActor },
238
+ { threadId: thread.id, projectId: project.id, currentActor: userActor },
224
239
  tenant,
225
240
  )
226
241
  await store.updateSession({ ...parentSession, status: 'active' }, tenant)
@@ -234,11 +249,13 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
234
249
  const registry = new AgentRegistry()
235
250
  registry.register(buildDefinition(buildAgent('worker')))
236
251
 
252
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
237
253
  const manager = new AgentManager(registry, undefined, {
238
254
  sessionStore: store,
239
255
  summaryMaterializer: materializer,
240
256
  workspaceRegistry: new WorkspaceBackendRegistry(),
241
257
  capacity: new DefaultCapacityValidator(store),
258
+ threadManager,
242
259
  })
243
260
 
244
261
  const task = await manager.sendMessage(
@@ -257,6 +274,7 @@ describe('E2E — SubSession spawn → kernel summary → parent drill', () => {
257
274
  depth: 0,
258
275
  budgetTracker: { total: 10_000, remaining: 10_000 },
259
276
  tenantId: tenant,
277
+ threadId: thread.id,
260
278
  sessionId: parentSession.id,
261
279
  projectId: project.id,
262
280
  parentActor: userActor,
@@ -34,7 +34,7 @@ import {
34
34
  describe('Integration — event stream ordering + lineage + schemaVersion', () => {
35
35
  it('every sub-session RunEvent carries schemaVersion: 2', async () => {
36
36
  const harness = buildHarness()
37
- const { project, session, actor } = await seedActiveParent(harness)
37
+ const { project, thread, session, actor } = await seedActiveParent(harness)
38
38
  harness.registry.register(buildDefinition(buildAgent('worker')))
39
39
 
40
40
  const captured: RunEvent[] = []
@@ -49,6 +49,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
49
49
  buildTaskContext({
50
50
  sessionId: session.id,
51
51
  projectId: project.id,
52
+ threadId: thread.id,
52
53
  tenantId: DEFAULT_TENANT,
53
54
  parentActor: actor,
54
55
  }),
@@ -73,7 +74,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
73
74
 
74
75
  it('every sub-session event carries lineage { parentSessionId, rootSessionId, depth }', async () => {
75
76
  const harness = buildHarness()
76
- const { project, session, actor } = await seedActiveParent(harness)
77
+ const { project, thread, session, actor } = await seedActiveParent(harness)
77
78
  harness.registry.register(buildDefinition(buildAgent('worker')))
78
79
 
79
80
  const captured: RunEvent[] = []
@@ -88,6 +89,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
88
89
  buildTaskContext({
89
90
  sessionId: session.id,
90
91
  projectId: project.id,
92
+ threadId: thread.id,
91
93
  tenantId: DEFAULT_TENANT,
92
94
  parentActor: actor,
93
95
  }),
@@ -116,7 +118,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
116
118
 
117
119
  it('3-deep delegation: rootSessionId identical across tree; depth ascends 1→2→3', async () => {
118
120
  const harness = buildHarness()
119
- const { project, session, actor } = await seedActiveParent(harness)
121
+ const { project, thread, session, actor } = await seedActiveParent(harness)
120
122
 
121
123
  // Wire a cascading agent: level-1 child spawns level-2 via its own
122
124
  // sendMessage. We hand a reference to the manager into the child agent
@@ -129,7 +131,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
129
131
  'mid',
130
132
  async (_input: AgentInput, config: BaseAgentConfig): Promise<BaseAgentResult> => {
131
133
  // Spawn a level-2 child from inside the mid-agent's run.
132
- if (!config.sessionId || !config.projectId || !config.tenantId) {
134
+ if (!config.sessionId || !config.threadId || !config.projectId || !config.tenantId) {
133
135
  throw new Error('mid agent missing session scoping')
134
136
  }
135
137
  // Flip child session to active so it is a legal spawn parent.
@@ -154,6 +156,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
154
156
  depth: 1,
155
157
  budgetTracker: { total: 10_000, remaining: 10_000 },
156
158
  tenantId: config.tenantId,
159
+ threadId: config.threadId,
157
160
  sessionId: childSessionId,
158
161
  projectId: config.projectId,
159
162
  parentActor: { kind: 'agent', agentId: 'mid' as never, tenantId: config.tenantId },
@@ -192,6 +195,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
192
195
  buildTaskContext({
193
196
  sessionId: session.id,
194
197
  projectId: project.id,
198
+ threadId: thread.id,
195
199
  tenantId: DEFAULT_TENANT,
196
200
  parentActor: actor,
197
201
  }),
@@ -237,7 +241,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
237
241
  // its own listener — the outer listener does NOT see the nested
238
242
  // listener's events (no cross-contamination).
239
243
  const harness = buildHarness()
240
- const { project, session, actor } = await seedActiveParent(harness)
244
+ const { project, thread, session, actor } = await seedActiveParent(harness)
241
245
 
242
246
  const outerCaptured: RunEvent[] = []
243
247
  const nestedCaptured: RunEvent[] = []
@@ -246,7 +250,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
246
250
  const midAgent = buildAgentCustom(
247
251
  'mid',
248
252
  async (_input: AgentInput, config: BaseAgentConfig): Promise<BaseAgentResult> => {
249
- if (!config.sessionId || !config.projectId || !config.tenantId) {
253
+ if (!config.sessionId || !config.threadId || !config.projectId || !config.tenantId) {
250
254
  throw new Error('mid missing scope')
251
255
  }
252
256
  const cs = await harness.store.getSession(config.sessionId, config.tenantId)
@@ -273,6 +277,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
273
277
  depth: 1,
274
278
  budgetTracker: { total: 10_000, remaining: 10_000 },
275
279
  tenantId: config.tenantId,
280
+ threadId: config.threadId,
276
281
  sessionId: config.sessionId,
277
282
  projectId: config.projectId,
278
283
  parentActor: {
@@ -313,6 +318,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
313
318
  buildTaskContext({
314
319
  sessionId: session.id,
315
320
  projectId: project.id,
321
+ threadId: thread.id,
316
322
  tenantId: DEFAULT_TENANT,
317
323
  parentActor: actor,
318
324
  }),
@@ -349,7 +355,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
349
355
  // therefore inherit the envelope even though they have no lineage in
350
356
  // their own type definition.
351
357
  const harness = buildHarness()
352
- const { project, session, actor } = await seedActiveParent(harness)
358
+ const { project, thread, session, actor } = await seedActiveParent(harness)
353
359
 
354
360
  const leafAgent = buildAgentCustom(
355
361
  'leaf-emit',
@@ -385,6 +391,7 @@ describe('Integration — event stream ordering + lineage + schemaVersion', () =
385
391
  buildTaskContext({
386
392
  sessionId: session.id,
387
393
  projectId: project.id,
394
+ threadId: thread.id,
388
395
  tenantId: DEFAULT_TENANT,
389
396
  parentActor: actor,
390
397
  }),