@namzu/sdk 0.2.0 → 0.3.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 (297) hide show
  1. package/CHANGELOG.md +53 -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 -3
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -3
  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/runtime/query/__tests__/context.test.js +8 -7
  60. package/dist/runtime/query/__tests__/context.test.js.map +1 -1
  61. package/dist/runtime/query/context-cache.d.ts +3 -3
  62. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  63. package/dist/runtime/query/context-cache.js +2 -2
  64. package/dist/runtime/query/context-cache.js.map +1 -1
  65. package/dist/runtime/query/context.d.ts +12 -21
  66. package/dist/runtime/query/context.d.ts.map +1 -1
  67. package/dist/runtime/query/context.js +3 -1
  68. package/dist/runtime/query/context.js.map +1 -1
  69. package/dist/runtime/query/index.d.ts +13 -15
  70. package/dist/runtime/query/index.d.ts.map +1 -1
  71. package/dist/runtime/query/index.js +1 -0
  72. package/dist/runtime/query/index.js.map +1 -1
  73. package/dist/session/__tests__/integration/_fixtures.d.ts +11 -4
  74. package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -1
  75. package/dist/session/__tests__/integration/_fixtures.js +23 -6
  76. package/dist/session/__tests__/integration/_fixtures.js.map +1 -1
  77. package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
  78. package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
  79. package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
  80. package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
  81. package/dist/session/__tests__/integration/capacity-caps.test.js +13 -6
  82. package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -1
  83. package/dist/session/__tests__/integration/e2e-spawn.test.js +14 -2
  84. package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -1
  85. package/dist/session/__tests__/integration/event-stream-ordering.test.js +14 -7
  86. package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -1
  87. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +26 -14
  88. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -1
  89. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +30 -20
  90. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -1
  91. package/dist/session/__tests__/integration/handoff-single-e2e.test.js +25 -9
  92. package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -1
  93. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +11 -10
  94. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -1
  95. package/dist/session/__tests__/integration/prev-artifact-dag.test.js +5 -4
  96. package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -1
  97. package/dist/session/__tests__/integration/retention-archive.test.js +3 -2
  98. package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -1
  99. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
  100. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
  101. package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
  102. package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
  103. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +2 -1
  104. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -1
  105. package/dist/session/__tests__/integration/tenant-isolation.test.js +14 -5
  106. package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -1
  107. package/dist/session/errors.d.ts +79 -0
  108. package/dist/session/errors.d.ts.map +1 -1
  109. package/dist/session/errors.js +57 -0
  110. package/dist/session/errors.js.map +1 -1
  111. package/dist/session/handoff/__tests__/broadcast.test.js +49 -31
  112. package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -1
  113. package/dist/session/handoff/__tests__/capacity.test.js +21 -18
  114. package/dist/session/handoff/__tests__/capacity.test.js.map +1 -1
  115. package/dist/session/handoff/__tests__/single.test.js +39 -30
  116. package/dist/session/handoff/__tests__/single.test.js.map +1 -1
  117. package/dist/session/handoff/assignment.d.ts +13 -1
  118. package/dist/session/handoff/assignment.d.ts.map +1 -1
  119. package/dist/session/handoff/broadcast.d.ts +7 -0
  120. package/dist/session/handoff/broadcast.d.ts.map +1 -1
  121. package/dist/session/handoff/broadcast.js +16 -1
  122. package/dist/session/handoff/broadcast.js.map +1 -1
  123. package/dist/session/handoff/single.d.ts +7 -0
  124. package/dist/session/handoff/single.d.ts.map +1 -1
  125. package/dist/session/handoff/single.js +13 -1
  126. package/dist/session/handoff/single.js.map +1 -1
  127. package/dist/session/hierarchy/__tests__/session.test.js +2 -0
  128. package/dist/session/hierarchy/__tests__/session.test.js.map +1 -1
  129. package/dist/session/hierarchy/index.d.ts +1 -0
  130. package/dist/session/hierarchy/index.d.ts.map +1 -1
  131. package/dist/session/hierarchy/index.js.map +1 -1
  132. package/dist/session/hierarchy/session.d.ts +15 -3
  133. package/dist/session/hierarchy/session.d.ts.map +1 -1
  134. package/dist/session/hierarchy/session.js.map +1 -1
  135. package/dist/session/hierarchy/thread.d.ts +54 -0
  136. package/dist/session/hierarchy/thread.d.ts.map +1 -0
  137. package/dist/session/hierarchy/thread.js +2 -0
  138. package/dist/session/hierarchy/thread.js.map +1 -0
  139. package/dist/session/migration/id-prefix.d.ts +8 -13
  140. package/dist/session/migration/id-prefix.d.ts.map +1 -1
  141. package/dist/session/migration/id-prefix.js +8 -13
  142. package/dist/session/migration/id-prefix.js.map +1 -1
  143. package/dist/session/retention/__tests__/archive.test.js +3 -2
  144. package/dist/session/retention/__tests__/archive.test.js.map +1 -1
  145. package/dist/session/summary/__tests__/materialize.test.js +4 -3
  146. package/dist/session/summary/__tests__/materialize.test.js.map +1 -1
  147. package/dist/store/index.d.ts +0 -2
  148. package/dist/store/index.d.ts.map +1 -1
  149. package/dist/store/index.js +0 -1
  150. package/dist/store/index.js.map +1 -1
  151. package/dist/store/session/__tests__/disk.test.js +32 -5
  152. package/dist/store/session/__tests__/disk.test.js.map +1 -1
  153. package/dist/store/session/__tests__/memory.test.js +50 -9
  154. package/dist/store/session/__tests__/memory.test.js.map +1 -1
  155. package/dist/store/session/disk.d.ts +2 -1
  156. package/dist/store/session/disk.d.ts.map +1 -1
  157. package/dist/store/session/disk.js +61 -0
  158. package/dist/store/session/disk.js.map +1 -1
  159. package/dist/store/session/index.d.ts.map +1 -1
  160. package/dist/store/session/index.js +3 -4
  161. package/dist/store/session/index.js.map +1 -1
  162. package/dist/store/session/memory.d.ts +2 -1
  163. package/dist/store/session/memory.d.ts.map +1 -1
  164. package/dist/store/session/memory.js +13 -0
  165. package/dist/store/session/memory.js.map +1 -1
  166. package/dist/store/thread/disk.d.ts +41 -0
  167. package/dist/store/thread/disk.d.ts.map +1 -0
  168. package/dist/store/thread/disk.js +229 -0
  169. package/dist/store/thread/disk.js.map +1 -0
  170. package/dist/store/thread/index.d.ts +4 -0
  171. package/dist/store/thread/index.d.ts.map +1 -0
  172. package/dist/store/thread/index.js +6 -0
  173. package/dist/store/thread/index.js.map +1 -0
  174. package/dist/store/thread/memory.d.ts +23 -0
  175. package/dist/store/thread/memory.d.ts.map +1 -0
  176. package/dist/store/thread/memory.js +90 -0
  177. package/dist/store/thread/memory.js.map +1 -0
  178. package/dist/types/agent/base.d.ts +17 -21
  179. package/dist/types/agent/base.d.ts.map +1 -1
  180. package/dist/types/agent/factory.d.ts +8 -2
  181. package/dist/types/agent/factory.d.ts.map +1 -1
  182. package/dist/types/agent/task.d.ts +18 -11
  183. package/dist/types/agent/task.d.ts.map +1 -1
  184. package/dist/types/ids/index.d.ts +5 -9
  185. package/dist/types/ids/index.d.ts.map +1 -1
  186. package/dist/types/ids/index.js +4 -4
  187. package/dist/types/ids/index.js.map +1 -1
  188. package/dist/types/rag/retrieval.d.ts +4 -3
  189. package/dist/types/rag/retrieval.d.ts.map +1 -1
  190. package/dist/types/run/config.d.ts +6 -5
  191. package/dist/types/run/config.d.ts.map +1 -1
  192. package/dist/types/run/metadata.d.ts +5 -18
  193. package/dist/types/run/metadata.d.ts.map +1 -1
  194. package/dist/types/session/ids.d.ts +4 -13
  195. package/dist/types/session/ids.d.ts.map +1 -1
  196. package/dist/types/session/ids.js +3 -6
  197. package/dist/types/session/ids.js.map +1 -1
  198. package/dist/types/session/index.d.ts +1 -1
  199. package/dist/types/session/index.d.ts.map +1 -1
  200. package/dist/types/session/store.d.ts +32 -10
  201. package/dist/types/session/store.d.ts.map +1 -1
  202. package/dist/types/session/store.js +3 -8
  203. package/dist/types/session/store.js.map +1 -1
  204. package/dist/types/thread/index.d.ts +2 -0
  205. package/dist/types/thread/index.d.ts.map +1 -0
  206. package/dist/types/thread/index.js +5 -0
  207. package/dist/types/thread/index.js.map +1 -0
  208. package/dist/types/thread/store.d.ts +86 -0
  209. package/dist/types/thread/store.d.ts.map +1 -0
  210. package/dist/types/thread/store.js +22 -0
  211. package/dist/types/thread/store.js.map +1 -0
  212. package/dist/utils/id.d.ts +1 -12
  213. package/dist/utils/id.d.ts.map +1 -1
  214. package/dist/utils/id.js +3 -23
  215. package/dist/utils/id.js.map +1 -1
  216. package/package.json +6 -11
  217. package/src/agents/ReactiveAgent.ts +3 -2
  218. package/src/agents/SupervisorAgent.ts +5 -2
  219. package/src/bridge/a2a/index.ts +0 -1
  220. package/src/bridge/a2a/message.ts +0 -32
  221. package/src/bridge/a2a/task.ts +8 -7
  222. package/src/contracts/api.ts +6 -42
  223. package/src/contracts/ids.ts +1 -1
  224. package/src/contracts/index.ts +2 -8
  225. package/src/contracts/schemas.ts +1 -8
  226. package/src/index.ts +0 -4
  227. package/src/manager/agent/__tests__/lifecycle.test.ts +34 -13
  228. package/src/manager/agent/lifecycle.ts +114 -35
  229. package/src/manager/index.ts +3 -0
  230. package/src/manager/run/persistence.ts +7 -1
  231. package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
  232. package/src/manager/thread/lifecycle.ts +217 -0
  233. package/src/rag/retriever.ts +2 -2
  234. package/src/runtime/query/__tests__/context.test.ts +9 -8
  235. package/src/runtime/query/context-cache.ts +4 -4
  236. package/src/runtime/query/context.ts +15 -22
  237. package/src/runtime/query/index.ts +15 -16
  238. package/src/session/__tests__/integration/_fixtures.ts +36 -8
  239. package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
  240. package/src/session/__tests__/integration/capacity-caps.test.ts +13 -6
  241. package/src/session/__tests__/integration/e2e-spawn.test.ts +20 -2
  242. package/src/session/__tests__/integration/event-stream-ordering.test.ts +14 -7
  243. package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +39 -13
  244. package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +54 -19
  245. package/src/session/__tests__/integration/handoff-single-e2e.test.ts +40 -9
  246. package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +13 -10
  247. package/src/session/__tests__/integration/prev-artifact-dag.test.ts +12 -5
  248. package/src/session/__tests__/integration/retention-archive.test.ts +5 -3
  249. package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
  250. package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +4 -2
  251. package/src/session/__tests__/integration/tenant-isolation.test.ts +16 -6
  252. package/src/session/errors.ts +89 -0
  253. package/src/session/handoff/__tests__/broadcast.test.ts +56 -28
  254. package/src/session/handoff/__tests__/capacity.test.ts +26 -20
  255. package/src/session/handoff/__tests__/single.test.ts +45 -28
  256. package/src/session/handoff/assignment.ts +13 -1
  257. package/src/session/handoff/broadcast.ts +26 -1
  258. package/src/session/handoff/single.ts +23 -1
  259. package/src/session/hierarchy/__tests__/session.test.ts +9 -1
  260. package/src/session/hierarchy/index.ts +1 -0
  261. package/src/session/hierarchy/session.ts +15 -3
  262. package/src/session/hierarchy/thread.ts +55 -0
  263. package/src/session/migration/id-prefix.ts +8 -13
  264. package/src/session/retention/__tests__/archive.test.ts +5 -3
  265. package/src/session/summary/__tests__/materialize.test.ts +6 -4
  266. package/src/store/index.ts +0 -3
  267. package/src/store/session/__tests__/disk.test.ts +57 -6
  268. package/src/store/session/__tests__/memory.test.ts +84 -9
  269. package/src/store/session/disk.ts +57 -1
  270. package/src/store/session/index.ts +3 -4
  271. package/src/store/session/memory.ts +13 -1
  272. package/src/store/thread/disk.ts +261 -0
  273. package/src/store/thread/index.ts +7 -0
  274. package/src/store/thread/memory.ts +104 -0
  275. package/src/types/agent/base.ts +17 -21
  276. package/src/types/agent/factory.ts +8 -3
  277. package/src/types/agent/task.ts +19 -11
  278. package/src/types/ids/index.ts +8 -15
  279. package/src/types/rag/retrieval.ts +4 -3
  280. package/src/types/run/config.ts +6 -5
  281. package/src/types/run/metadata.ts +5 -18
  282. package/src/types/session/ids.ts +4 -15
  283. package/src/types/session/index.ts +1 -2
  284. package/src/types/session/store.ts +34 -11
  285. package/src/types/thread/index.ts +5 -0
  286. package/src/types/thread/store.ts +92 -0
  287. package/src/utils/id.ts +3 -24
  288. package/dist/store/conversation/memory.d.ts +0 -43
  289. package/dist/store/conversation/memory.d.ts.map +0 -1
  290. package/dist/store/conversation/memory.js +0 -108
  291. package/dist/store/conversation/memory.js.map +0 -1
  292. package/dist/types/conversation/index.d.ts +0 -14
  293. package/dist/types/conversation/index.d.ts.map +0 -1
  294. package/dist/types/conversation/index.js +0 -2
  295. package/dist/types/conversation/index.js.map +0 -1
  296. package/src/store/conversation/memory.ts +0 -144
  297. package/src/types/conversation/index.ts +0 -15
@@ -11,29 +11,27 @@ import {
11
11
  import { DefaultPathBuilder, type PathBuilder } from '../../session/workspace/path-builder.js'
12
12
  import { ActivityStore } from '../../store/activity/memory.js'
13
13
  import { type ActivityTrackingConfig, resolveActivityTracking } from '../../types/activity/index.js'
14
- import type { RunId, SessionId, TenantId, ThreadId } from '../../types/ids/index.js'
14
+ import type { RunId, SessionId, TenantId } from '../../types/ids/index.js'
15
15
  import type { Message } from '../../types/message/index.js'
16
16
  import type { PermissionMode } from '../../types/permission/index.js'
17
17
  import type { LLMProvider } from '../../types/provider/index.js'
18
18
  import type { AgentRunConfig } from '../../types/run/index.js'
19
- import type { ProjectId } from '../../types/session/ids.js'
19
+ import type { ProjectId, ThreadId } from '../../types/session/ids.js'
20
20
  import type { ModelPricing } from '../../utils/cost.js'
21
21
  import { generateRunId } from '../../utils/id.js'
22
22
  import { type Logger, getRootLogger } from '../../utils/logger.js'
23
23
 
24
24
  /**
25
- * Config accepted by {@link RunContextFactory.build}. Phase 6 promotes
26
- * `sessionId`, `projectId`, and `tenantId` to required — runs are scoped
27
- * under a Session within a Project within a Tenant (session-hierarchy.md
28
- * §12.1). `threadId` is retained only as a deprecated compat alias of
29
- * `projectId` — consumers can still pass it, but no new path layout honors it.
25
+ * Config accepted by {@link RunContextFactory.build}. `sessionId`,
26
+ * `threadId`, `projectId`, and `tenantId` are required — runs carry the full
27
+ * five-layer scope (Tenant Project Thread Session → Run) per
28
+ * Convention #17.
30
29
  *
31
30
  * `pathBuilder` is optional; when absent a {@link DefaultPathBuilder} is
32
- * constructed against `{workingDirectory}/.namzu` — no more hardcoded
33
- * `.namzu/threads` path.
31
+ * constructed against `{workingDirectory}/.namzu`.
34
32
  *
35
- * Phase 7 adds `filesystemMigrator` + `migrationSink`. These are also
36
- * optional; when absent a {@link DefaultFilesystemMigrator} wired to the
33
+ * `filesystemMigrator` + `migrationSink` are optional; when absent a
34
+ * {@link DefaultFilesystemMigrator} wired to the
37
35
  * {@link NOOP_FILESYSTEM_MIGRATION_SINK} is used. Migration runs once per
38
36
  * process via {@link RunContextFactory.ensureMigrated}; the static `build`
39
37
  * method stays synchronous so existing call sites are not broken — async
@@ -51,6 +49,7 @@ export interface RunContextConfig {
51
49
  signal?: AbortSignal
52
50
 
53
51
  sessionId: SessionId
52
+ threadId: ThreadId
54
53
  projectId: ProjectId
55
54
  tenantId: TenantId
56
55
 
@@ -73,21 +72,13 @@ export interface RunContextConfig {
73
72
  depth?: number
74
73
  }
75
74
 
76
- /**
77
- * Result of {@link RunContextFactory.build}. `threadId` remains as a
78
- * deprecated read-only mirror of `projectId` for consumers still referencing
79
- * the old name — scheduled for removal in 0.3.0 (session-hierarchy.md §13.1).
80
- */
75
+ /** Result of {@link RunContextFactory.build}. */
81
76
  export interface RunContext {
82
77
  runId: RunId
83
78
  sessionId: SessionId
79
+ threadId: ThreadId
84
80
  projectId: ProjectId
85
81
  tenantId: TenantId
86
- /**
87
- * @deprecated Mirrors `projectId` — remove when callers migrate off the
88
- * legacy name.
89
- */
90
- threadId: ThreadId
91
82
  runMgr: RunPersistence
92
83
  activityStore: ActivityStore
93
84
  planManager: PlanManager
@@ -156,6 +147,7 @@ export class RunContextFactory {
156
147
  agent: config.agentName,
157
148
  runId,
158
149
  sessionId: config.sessionId,
150
+ threadId: config.threadId,
159
151
  projectId: config.projectId,
160
152
  tenantId: config.tenantId,
161
153
  })
@@ -170,6 +162,7 @@ export class RunContextFactory {
170
162
  pricing: config.pricing,
171
163
  log,
172
164
  sessionId: config.sessionId,
165
+ threadId: config.threadId,
173
166
  tenantId: config.tenantId,
174
167
  projectId: config.projectId,
175
168
  parentRunId: config.parentRunId,
@@ -183,9 +176,9 @@ export class RunContextFactory {
183
176
  return {
184
177
  runId,
185
178
  sessionId: config.sessionId,
179
+ threadId: config.threadId,
186
180
  projectId: config.projectId,
187
181
  tenantId: config.tenantId,
188
- threadId: config.projectId as ThreadId,
189
182
  runMgr,
190
183
  activityStore,
191
184
  planManager,
@@ -21,7 +21,7 @@ import {
21
21
  type ResumeHandler,
22
22
  autoApproveHandler,
23
23
  } from '../../types/hitl/index.js'
24
- import type { RunId, SessionId, TenantId, ThreadId } from '../../types/ids/index.js'
24
+ import type { RunId, SessionId, TenantId } from '../../types/ids/index.js'
25
25
  import type { InvocationState } from '../../types/invocation/index.js'
26
26
  import { type Message, createSystemMessage } from '../../types/message/index.js'
27
27
  import type { AgentPersona } from '../../types/persona/index.js'
@@ -29,7 +29,7 @@ import type { LLMProvider } from '../../types/provider/index.js'
29
29
  import type { TaskRouterConfig } from '../../types/router/index.js'
30
30
  import type { AgentRun, AgentRunConfig, RunEvent, RunEventListener } from '../../types/run/index.js'
31
31
  import type { Sandbox, SandboxProvider } from '../../types/sandbox/index.js'
32
- import type { ProjectId } from '../../types/session/ids.js'
32
+ import type { ProjectId, ThreadId } from '../../types/session/ids.js'
33
33
  import type { Skill } from '../../types/skills/index.js'
34
34
  import type { TaskStore } from '../../types/task/index.js'
35
35
  import type { ToolRegistryContract } from '../../types/tool/index.js'
@@ -67,30 +67,28 @@ export interface QueryParams {
67
67
  resumeHandler: ResumeHandler
68
68
  resumeFromCheckpoint?: CheckpointId
69
69
 
70
+ /** Session scope for the run. Required — every run is attributed to a Session. */
71
+ sessionId: SessionId
72
+
70
73
  /**
71
- * Session scope for the run. Required in 0.2.0 — every run is attributed to
72
- * a Session (session-hierarchy.md §12.1).
74
+ * Topic the Session lives under. Required in 0.3.0 — every run carries
75
+ * the full five-layer scope (Tenant → Project → Thread → Session →
76
+ * Run). Denormalized from `session.threadId`; callers build this
77
+ * alongside `sessionId` so the query pipeline never needs a second
78
+ * SessionStore round-trip to recover it.
73
79
  */
74
- sessionId: SessionId
80
+ threadId: ThreadId
75
81
 
76
82
  /** Long-lived goal scope for the run. Required. */
77
83
  projectId: ProjectId
78
84
 
79
- /** Isolation boundary. Required. */
85
+ /** Isolation boundary (Convention #17). Required. */
80
86
  tenantId: TenantId
81
87
 
82
- /**
83
- * @deprecated Pass `projectId` instead. When both are present, `projectId`
84
- * wins. During the 0.2.x migration window a caller supplying only
85
- * `threadId` must also supply `projectId` — the kernel no longer infers
86
- * `projectId` from a bare `threadId` on the QueryParams shape.
87
- */
88
- threadId?: ThreadId
89
-
90
88
  /**
91
89
  * Optional path layout override. Defaults to a {@link DefaultPathBuilder}
92
- * rooted at `{workingDirectory}/.namzu` (§13.4). Phase 7 wires first-call
93
- * filesystem migration onto this same entry point.
90
+ * rooted at `{workingDirectory}/.namzu`. First-call filesystem migration
91
+ * runs on this same entry point.
94
92
  */
95
93
  pathBuilder?: PathBuilder
96
94
 
@@ -158,6 +156,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Agen
158
156
  messages: params.messages,
159
157
  signal: params.signal,
160
158
  sessionId: params.sessionId,
159
+ threadId: params.threadId,
161
160
  projectId: params.projectId,
162
161
  tenantId: params.tenantId,
163
162
  pathBuilder: params.pathBuilder,
@@ -15,8 +15,10 @@
15
15
  import { vi } from 'vitest'
16
16
  import { EMPTY_TOKEN_USAGE } from '../../../constants/limits.js'
17
17
  import { AgentManager } from '../../../manager/agent/lifecycle.js'
18
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
18
19
  import { AgentRegistry } from '../../../registry/agent/definitions.js'
19
20
  import { InMemorySessionStore } from '../../../store/session/memory.js'
21
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
20
22
  import type {
21
23
  AgentCapabilities,
22
24
  AgentInput,
@@ -28,7 +30,7 @@ import type { AgentDefinition } from '../../../types/agent/factory.js'
28
30
  import type { AgentTaskContext, SendMessageOptions } from '../../../types/agent/task.js'
29
31
  import type { AgentId, RunId, SessionId, TenantId, UserId } from '../../../types/ids/index.js'
30
32
  import { createAssistantMessage } from '../../../types/message/index.js'
31
- import type { ProjectId, SummaryId } from '../../../types/session/ids.js'
33
+ import type { ProjectId, SummaryId, ThreadId } from '../../../types/session/ids.js'
32
34
  import { ZERO_COST } from '../../../utils/cost.js'
33
35
  import { DefaultCapacityValidator } from '../../handoff/capacity.js'
34
36
  import type { ActorRef } from '../../hierarchy/actor.js'
@@ -149,6 +151,8 @@ export function buildDefinition(agent: Agent<BaseAgentConfig, BaseAgentResult>):
149
151
 
150
152
  export interface IntegrationHarness {
151
153
  readonly store: InMemorySessionStore
154
+ readonly threadStore: InMemoryThreadStore
155
+ readonly threadManager: ThreadManager
152
156
  readonly registry: AgentRegistry
153
157
  readonly manager: AgentManager
154
158
  readonly materializer: SessionSummaryMaterializer
@@ -178,6 +182,7 @@ export interface IntegrationHarnessOptions {
178
182
  export function buildHarness(options: IntegrationHarnessOptions = {}): IntegrationHarness {
179
183
  const tenantId = options.tenantId ?? DEFAULT_TENANT
180
184
  const store = new InMemorySessionStore()
185
+ const threadStore = new InMemoryThreadStore()
181
186
 
182
187
  const workspaceRegistry = new WorkspaceBackendRegistry()
183
188
  if (options.withWorktreeDriver !== false) {
@@ -200,25 +205,42 @@ export function buildHarness(options: IntegrationHarnessOptions = {}): Integrati
200
205
  })
201
206
 
202
207
  const capacity = new DefaultCapacityValidator(store)
208
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
203
209
  const registry = new AgentRegistry()
204
210
  const manager = new AgentManager(registry, undefined, {
205
211
  sessionStore: store,
206
212
  summaryMaterializer: materializer,
207
213
  workspaceRegistry,
208
214
  capacity,
215
+ threadManager,
209
216
  })
210
217
 
211
- return { store, registry, manager, materializer, workspaceRegistry, capacity, tenantId }
218
+ return {
219
+ store,
220
+ threadStore,
221
+ threadManager,
222
+ registry,
223
+ manager,
224
+ materializer,
225
+ workspaceRegistry,
226
+ capacity,
227
+ tenantId,
228
+ }
212
229
  }
213
230
 
214
231
  /**
215
- * Seeds a Tenant → Project → Session triple and flips the session into
216
- * `active` so it is a legal spawn parent. Returns the project + active
217
- * session for the caller to drive spawns against.
232
+ * Seeds a Tenant → Project → Thread → Session quadruple and flips the session
233
+ * into `active` so it is a legal spawn parent. Returns the project, thread,
234
+ * and active session for the caller to drive spawns against.
218
235
  */
219
236
  export async function seedActiveParent(
220
237
  harness: IntegrationHarness,
221
- options?: { actor?: ActorRef; projectName?: string; tenantId?: TenantId },
238
+ options?: {
239
+ actor?: ActorRef
240
+ projectName?: string
241
+ tenantId?: TenantId
242
+ threadTitle?: string
243
+ },
222
244
  ) {
223
245
  const tenantId = options?.tenantId ?? harness.tenantId
224
246
  const actor: ActorRef = options?.actor ?? userActor('usr_root', tenantId)
@@ -226,12 +248,16 @@ export async function seedActiveParent(
226
248
  { tenantId, name: options?.projectName ?? 'integration-project' },
227
249
  tenantId,
228
250
  )
251
+ const thread = await harness.threadStore.createThread(
252
+ { projectId: project.id, title: options?.threadTitle ?? 'default' },
253
+ tenantId,
254
+ )
229
255
  const session = await harness.store.createSession(
230
- { projectId: project.id, currentActor: actor },
256
+ { threadId: thread.id, projectId: project.id, currentActor: actor },
231
257
  tenantId,
232
258
  )
233
259
  await harness.store.updateSession({ ...session, status: 'active' as Session['status'] }, tenantId)
234
- return { project, session, actor }
260
+ return { project, thread, session, actor }
235
261
  }
236
262
 
237
263
  /**
@@ -242,6 +268,7 @@ export async function seedActiveParent(
242
268
  export function buildTaskContext(params: {
243
269
  sessionId: SessionId
244
270
  projectId: ProjectId
271
+ threadId: ThreadId
245
272
  tenantId: TenantId
246
273
  parentActor: ActorRef
247
274
  depth?: number
@@ -258,6 +285,7 @@ export function buildTaskContext(params: {
258
285
  remaining: params.budget ?? 100_000,
259
286
  },
260
287
  tenantId: params.tenantId,
288
+ threadId: params.threadId,
261
289
  sessionId: params.sessionId,
262
290
  projectId: params.projectId,
263
291
  parentActor: params.parentActor,
@@ -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
  })