@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
@@ -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
  }),
@@ -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
  })