@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
@@ -1,4 +1,5 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { ThreadManager } from '../../../manager/thread/lifecycle.js'
2
3
  import { TenantIsolationError } from '../../../session/errors.js'
3
4
  import type { ActorRef } from '../../../session/hierarchy/actor.js'
4
5
  import {
@@ -8,6 +9,7 @@ import {
8
9
  } from '../../../session/workspace/git-worktree.js'
9
10
  import { WorkspaceBackendRegistry } from '../../../session/workspace/registry.js'
10
11
  import { InMemorySessionStore } from '../../../store/session/memory.js'
12
+ import { InMemoryThreadStore } from '../../../store/thread/memory.js'
11
13
  import type { AgentId, SessionId, TenantId, UserId } from '../../../types/ids/index.js'
12
14
  import { generateHandoffId } from '../../../utils/id.js'
13
15
  import type { HandoffAssignment } from '../assignment.js'
@@ -59,6 +61,7 @@ interface MockedHandoffEventSink extends HandoffEventSink {
59
61
 
60
62
  function buildDeps(
61
63
  store: InMemorySessionStore,
64
+ threadStore: InMemoryThreadStore,
62
65
  execOverride?: ExecFile,
63
66
  runResolver?: RunStatusResolver,
64
67
  ): { deps: SingleHandoffDeps; events: MockedHandoffEventSink; execCalls: string[] } {
@@ -84,29 +87,36 @@ function buildDeps(
84
87
  onBroadcastRollback: vi.fn<(ev: HandoffBroadcastRollbackEvent) => void>(),
85
88
  }
86
89
 
90
+ const threadManager = new ThreadManager({ threadStore, sessionStore: store })
87
91
  const deps: SingleHandoffDeps = {
88
92
  store,
89
93
  workspaceRegistry: registry,
90
94
  capacity: new DefaultCapacityValidator(store),
91
95
  events,
96
+ threadManager,
92
97
  ...(runResolver !== undefined && { runStatus: runResolver }),
93
98
  }
94
99
 
95
100
  return { deps, events, execCalls }
96
101
  }
97
102
 
98
- async function seedIdle(store: InMemorySessionStore) {
103
+ async function seedIdle(store: InMemorySessionStore, threadStore: InMemoryThreadStore) {
99
104
  const project = await store.createProject({ tenantId: tenant, name: 'p' }, tenant)
105
+ const thread = await threadStore.createThread(
106
+ { projectId: project.id, title: 'handoff-single-test' },
107
+ tenant,
108
+ )
100
109
  const session = await store.createSession(
101
- { projectId: project.id, currentActor: user('usr_source') },
110
+ { threadId: thread.id, projectId: project.id, currentActor: user('usr_source') },
102
111
  tenant,
103
112
  )
104
- return { project, session }
113
+ return { project, thread, session }
105
114
  }
106
115
 
107
116
  function buildAssignment(
108
117
  sourceSessionId: SessionId,
109
118
  projectId: Awaited<ReturnType<InMemorySessionStore['createProject']>>['id'],
119
+ threadId: Awaited<ReturnType<InMemoryThreadStore['createThread']>>['id'],
110
120
  expectedOwnerVersion: number,
111
121
  recipient: ActorRef = user('usr_target'),
112
122
  ): HandoffAssignment {
@@ -115,6 +125,7 @@ function buildAssignment(
115
125
  mode: 'single',
116
126
  sourceSessionId,
117
127
  tenantId: tenant,
128
+ threadId,
118
129
  projectId,
119
130
  sourceActor: user('usr_source'),
120
131
  recipientActor: recipient,
@@ -125,16 +136,18 @@ function buildAssignment(
125
136
 
126
137
  describe('executeSingleHandoff', () => {
127
138
  let store: InMemorySessionStore
139
+ let threadStore: InMemoryThreadStore
128
140
 
129
141
  beforeEach(() => {
130
142
  store = new InMemorySessionStore()
143
+ threadStore = new InMemoryThreadStore()
131
144
  })
132
145
 
133
146
  it('happy path: idle source → lock → commit → outcome populated + source mutated', async () => {
134
- const { project, session } = await seedIdle(store)
135
- const { deps, events } = buildDeps(store)
147
+ const { project, thread, session } = await seedIdle(store, threadStore)
148
+ const { deps, events } = buildDeps(store, threadStore)
136
149
 
137
- const assignment = buildAssignment(session.id, project.id, 0)
150
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
138
151
  const outcome = await executeSingleHandoff(deps, assignment, tenant)
139
152
 
140
153
  expect(outcome.assignmentId).toBe(assignment.id)
@@ -155,11 +168,11 @@ describe('executeSingleHandoff', () => {
155
168
  })
156
169
 
157
170
  it('rejects when source session is non-idle (active → HandoffLockRejected with active_run)', async () => {
158
- const { project, session } = await seedIdle(store)
171
+ const { project, thread, session } = await seedIdle(store, threadStore)
159
172
  await store.updateSession({ ...session, status: 'active' }, tenant)
160
173
 
161
- const { deps } = buildDeps(store)
162
- const assignment = buildAssignment(session.id, project.id, 0)
174
+ const { deps } = buildDeps(store, threadStore)
175
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
163
176
 
164
177
  try {
165
178
  await executeSingleHandoff(deps, assignment, tenant)
@@ -171,14 +184,14 @@ describe('executeSingleHandoff', () => {
171
184
  })
172
185
 
173
186
  it('rejects when Run resolver reports pending_hitl', async () => {
174
- const { project, session } = await seedIdle(store)
187
+ const { project, thread, session } = await seedIdle(store, threadStore)
175
188
  const resolver: RunStatusResolver = {
176
189
  async blockingRun() {
177
190
  return { reason: 'pending_hitl' }
178
191
  },
179
192
  }
180
- const { deps } = buildDeps(store, undefined, resolver)
181
- const assignment = buildAssignment(session.id, project.id, 0)
193
+ const { deps } = buildDeps(store, threadStore, undefined, resolver)
194
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
182
195
 
183
196
  try {
184
197
  await executeSingleHandoff(deps, assignment, tenant)
@@ -190,11 +203,11 @@ describe('executeSingleHandoff', () => {
190
203
  })
191
204
 
192
205
  it('rejects on tenant mismatch (TenantIsolationError)', async () => {
193
- const { project, session } = await seedIdle(store)
194
- const { deps } = buildDeps(store)
206
+ const { project, thread, session } = await seedIdle(store, threadStore)
207
+ const { deps } = buildDeps(store, threadStore)
195
208
  // Assignment tenant differs from the call-site tenant.
196
209
  const assignment: HandoffAssignment = {
197
- ...buildAssignment(session.id, project.id, 0),
210
+ ...buildAssignment(session.id, project.id, thread.id, 0),
198
211
  tenantId: otherTenant,
199
212
  }
200
213
  await expect(executeSingleHandoff(deps, assignment, otherTenant)).rejects.toBeInstanceOf(
@@ -203,13 +216,13 @@ describe('executeSingleHandoff', () => {
203
216
  })
204
217
 
205
218
  it('rejects on CAS mismatch (HandoffVersionConflict)', async () => {
206
- const { project, session } = await seedIdle(store)
207
- const { deps } = buildDeps(store)
219
+ const { project, thread, session } = await seedIdle(store, threadStore)
220
+ const { deps } = buildDeps(store, threadStore)
208
221
 
209
222
  // Simulate a concurrent bump: move ownerVersion to 1 before the assignment
210
223
  // with expectedOwnerVersion=0 is executed.
211
224
  await store.updateSession({ ...session, ownerVersion: 1 }, tenant)
212
- const assignment = buildAssignment(session.id, project.id, 0)
225
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
213
226
 
214
227
  try {
215
228
  await executeSingleHandoff(deps, assignment, tenant)
@@ -224,18 +237,22 @@ describe('executeSingleHandoff', () => {
224
237
  it('depth cap enforcement rejects with DelegationCapacityExceeded (dimension=depth)', async () => {
225
238
  // Build a chain so the handoff source already sits at max depth.
226
239
  const project = await store.createProject({ tenantId: tenant, name: 'p' }, tenant)
240
+ const thread = await threadStore.createThread(
241
+ { projectId: project.id, title: 'depth-cap' },
242
+ tenant,
243
+ )
227
244
  // Set a tight limit on the project via a second createProject? — no, the
228
245
  // store hardcodes defaults {4,8,10}. Build a depth-4 chain then attempt
229
246
  // handoff on depth-4 node (ancestry length 5 > 4).
230
247
  const root = await store.createSession(
231
- { projectId: project.id, currentActor: user('usr_source') },
248
+ { threadId: thread.id, projectId: project.id, currentActor: user('usr_source') },
232
249
  tenant,
233
250
  )
234
251
  let parent = root.id
235
252
  let tail: SessionId = root.id
236
253
  for (let i = 0; i < 4; i++) {
237
254
  const child = await store.createSession(
238
- { projectId: project.id, currentActor: user(`usr_${i}`) },
255
+ { threadId: thread.id, projectId: project.id, currentActor: user(`usr_${i}`) },
239
256
  tenant,
240
257
  )
241
258
  await store.createSubSession(
@@ -252,15 +269,15 @@ describe('executeSingleHandoff', () => {
252
269
  }
253
270
 
254
271
  // Source is `tail` at ancestry length 5 → depth-capacity with limit 4 rejects.
255
- const { deps } = buildDeps(store)
256
- const assignment = buildAssignment(tail, project.id, 0)
272
+ const { deps } = buildDeps(store, threadStore)
273
+ const assignment = buildAssignment(tail, project.id, thread.id, 0)
257
274
  await expect(executeSingleHandoff(deps, assignment, tenant)).rejects.toBeInstanceOf(
258
275
  DelegationCapacityExceeded,
259
276
  )
260
277
  })
261
278
 
262
279
  it('compensating revert: workspace provisioning failure reverts source to idle, version unchanged, onUnlocked fires', async () => {
263
- const { project, session } = await seedIdle(store)
280
+ const { project, thread, session } = await seedIdle(store, threadStore)
264
281
 
265
282
  // Fail only on `worktree add` but pass for everything else. Here we fail
266
283
  // the single worktree add.
@@ -270,8 +287,8 @@ describe('executeSingleHandoff', () => {
270
287
  }
271
288
  return okExec()
272
289
  }
273
- const { deps, events } = buildDeps(store, exec)
274
- const assignment = buildAssignment(session.id, project.id, 0)
290
+ const { deps, events } = buildDeps(store, threadStore, exec)
291
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
275
292
 
276
293
  await expect(executeSingleHandoff(deps, assignment, tenant)).rejects.toThrow(
277
294
  /Workspace backend git-worktree failed on create/,
@@ -290,7 +307,7 @@ describe('executeSingleHandoff', () => {
290
307
  })
291
308
 
292
309
  it('compensating revert: store.createSubSession failure still reverts + archives partial recipient', async () => {
293
- const { project, session } = await seedIdle(store)
310
+ const { project, thread, session } = await seedIdle(store, threadStore)
294
311
 
295
312
  // Monkey-patch createSubSession on the store to throw.
296
313
  const original = store.createSubSession.bind(store)
@@ -298,8 +315,8 @@ describe('executeSingleHandoff', () => {
298
315
  throw new Error('simulated createSubSession failure')
299
316
  }
300
317
 
301
- const { deps, events } = buildDeps(store)
302
- const assignment = buildAssignment(session.id, project.id, 0)
318
+ const { deps, events } = buildDeps(store, threadStore)
319
+ const assignment = buildAssignment(session.id, project.id, thread.id, 0)
303
320
 
304
321
  await expect(executeSingleHandoff(deps, assignment, tenant)).rejects.toThrow(
305
322
  /createSubSession failure/,
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { SessionId, TenantId } from '../../types/ids/index.js'
12
- import type { HandoffId, ProjectId, WorkspaceId } from '../../types/session/ids.js'
12
+ import type { HandoffId, ProjectId, ThreadId, WorkspaceId } from '../../types/session/ids.js'
13
13
  import type { ActorRef } from '../hierarchy/actor.js'
14
14
 
15
15
  /**
@@ -32,6 +32,18 @@ export interface HandoffAssignment {
32
32
  mode: HandoffMode
33
33
  sourceSessionId: SessionId
34
34
  tenantId: TenantId
35
+ /**
36
+ * Topic-layer scope the source session belongs to. Handoff recipients
37
+ * always land on the same Thread (cross-thread handoff is forbidden —
38
+ * a new actor taking over a conversation stays on the same topic).
39
+ * Validated against `source.threadId` at execute time.
40
+ */
41
+ threadId: ThreadId
42
+ /**
43
+ * Denormalized from the owning Thread. Kept alongside `threadId` as the
44
+ * Session record itself carries both (see `Session` JSDoc). Consistency
45
+ * validated against `source.projectId` at execute time.
46
+ */
35
47
  projectId: ProjectId
36
48
  /** The actor initiating the handoff (must be the source's current owner). */
37
49
  sourceActor: ActorRef
@@ -21,6 +21,7 @@
21
21
  * ```
22
22
  */
23
23
 
24
+ import type { ThreadManager } from '../../manager/thread/lifecycle.js'
24
25
  import type { SessionId, TenantId } from '../../types/ids/index.js'
25
26
  import type { SubSessionId } from '../../types/session/ids.js'
26
27
  import type { SessionStore } from '../../types/session/store.js'
@@ -41,6 +42,12 @@ export interface BroadcastHandoffDeps {
41
42
  capacity: CapacityValidator
42
43
  events: HandoffEventSink
43
44
  runStatus?: RunStatusResolver
45
+ /**
46
+ * Gate every recipient-session creation on the Thread being `'open'`.
47
+ * Added in Phase 2.6; checked once per broadcast (all recipients share
48
+ * a threadId by the fan-out invariant validated above).
49
+ */
50
+ threadManager: ThreadManager
44
51
  }
45
52
 
46
53
  /**
@@ -108,6 +115,9 @@ export async function executeBroadcastHandoff(
108
115
  if (a.expectedOwnerVersion !== first.expectedOwnerVersion) {
109
116
  throw new Error('executeBroadcastHandoff: all assignments must share expectedOwnerVersion')
110
117
  }
118
+ if (a.threadId !== first.threadId) {
119
+ throw new Error('executeBroadcastHandoff: all assignments must share threadId')
120
+ }
111
121
  if (a.projectId !== first.projectId) {
112
122
  throw new Error('executeBroadcastHandoff: all assignments must share projectId')
113
123
  }
@@ -130,6 +140,12 @@ export async function executeBroadcastHandoff(
130
140
  seen.add(key)
131
141
  }
132
142
 
143
+ // Thread archive gate (Phase 2.6) — runs BEFORE source load/capacity so an
144
+ // archived thread fails fastest with `ThreadClosedError`. All assignments
145
+ // share `threadId` by the shape validation above. Runs BEFORE the CAS
146
+ // lock so a denied fan-out leaves the source session untouched.
147
+ await deps.threadManager.requireOpen(first.threadId, tenantId)
148
+
133
149
  // 3. Load source + tenant check.
134
150
  const source = await deps.store.getSession(first.sourceSessionId, tenantId)
135
151
  if (!source) {
@@ -141,6 +157,11 @@ export async function executeBroadcastHandoff(
141
157
  resource: `session(${source.id})`,
142
158
  })
143
159
  }
160
+ if (source.threadId !== first.threadId) {
161
+ throw new Error(
162
+ `Assignment threadId ${first.threadId} does not match source threadId ${source.threadId}`,
163
+ )
164
+ }
144
165
  if (source.projectId !== first.projectId) {
145
166
  throw new Error(
146
167
  `Assignment projectId ${first.projectId} does not match source projectId ${source.projectId}`,
@@ -220,7 +241,11 @@ export async function executeBroadcastHandoff(
220
241
  worktreesProvisioned += 1
221
242
 
222
243
  const childSession = await deps.store.createSession(
223
- { projectId: source.projectId, currentActor: assignment.recipientActor },
244
+ {
245
+ threadId: source.threadId,
246
+ projectId: source.projectId,
247
+ currentActor: assignment.recipientActor,
248
+ },
224
249
  tenantId,
225
250
  )
226
251
  partial.createdSessionId = childSession.id
@@ -20,6 +20,7 @@
20
20
  * 9. Emit `onCommitted` with the new version.
21
21
  */
22
22
 
23
+ import type { ThreadManager } from '../../manager/thread/lifecycle.js'
23
24
  import type { SessionId, TenantId } from '../../types/ids/index.js'
24
25
  import type { SessionStore } from '../../types/session/store.js'
25
26
  import { TenantIsolationError } from '../errors.js'
@@ -64,6 +65,12 @@ export interface SingleHandoffDeps {
64
65
  capacity: CapacityValidator
65
66
  events: HandoffEventSink
66
67
  runStatus?: RunStatusResolver
68
+ /**
69
+ * Gate the recipient-session creation on the Thread being `'open'`.
70
+ * Added in Phase 2.6 to mirror spawn — a handoff into an archived
71
+ * Thread would otherwise undermine `ThreadManager.archive`.
72
+ */
73
+ threadManager: ThreadManager
67
74
  }
68
75
 
69
76
  /**
@@ -85,6 +92,12 @@ export async function executeSingleHandoff(
85
92
  })
86
93
  }
87
94
 
95
+ // Thread archive gate (Phase 2.6) — runs FIRST so an archived thread
96
+ // fails fastest with `ThreadClosedError` rather than a lock rejection or
97
+ // capacity error. Checked BEFORE the CAS lock so a denied handoff leaves
98
+ // the source session untouched.
99
+ await deps.threadManager.requireOpen(assignment.threadId, tenantId)
100
+
88
101
  // 1. Load source session + tenant check.
89
102
  const source = await deps.store.getSession(assignment.sourceSessionId, tenantId)
90
103
  if (!source) {
@@ -96,6 +109,11 @@ export async function executeSingleHandoff(
96
109
  resource: `session(${source.id})`,
97
110
  })
98
111
  }
112
+ if (source.threadId !== assignment.threadId) {
113
+ throw new Error(
114
+ `Assignment threadId ${assignment.threadId} does not match source session threadId ${source.threadId}`,
115
+ )
116
+ }
99
117
  if (source.projectId !== assignment.projectId) {
100
118
  throw new Error(
101
119
  `Assignment projectId ${assignment.projectId} does not match source session projectId ${source.projectId}`,
@@ -157,7 +175,11 @@ export async function executeSingleHandoff(
157
175
  provisionedWorkspace = await driver.create({ label: `handoff-${assignment.id}` })
158
176
 
159
177
  const recipientSession = await deps.store.createSession(
160
- { projectId: source.projectId, currentActor: assignment.recipientActor },
178
+ {
179
+ threadId: source.threadId,
180
+ projectId: source.projectId,
181
+ currentActor: assignment.recipientActor,
182
+ },
161
183
  tenantId,
162
184
  )
163
185
  createdSessionId = recipientSession.id
@@ -1,11 +1,18 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import type { RunStatus } from '../../../types/run/status.js'
3
- import type { ProjectId, SessionId, TenantId, UserId } from '../../../types/session/ids.js'
3
+ import type {
4
+ ProjectId,
5
+ SessionId,
6
+ TenantId,
7
+ ThreadId,
8
+ UserId,
9
+ } from '../../../types/session/ids.js'
4
10
  import type { ActorRef } from '../actor.js'
5
11
  import { type Session, type SessionStatus, deriveStatus } from '../session.js'
6
12
 
7
13
  const tenant = 'tnt_a' as TenantId
8
14
  const project = 'prj_a' as ProjectId
15
+ const thread = 'thd_a' as ThreadId
9
16
 
10
17
  function user(): ActorRef {
11
18
  return { kind: 'user', userId: 'usr_a' as UserId, tenantId: tenant }
@@ -14,6 +21,7 @@ function user(): ActorRef {
14
21
  function makeSession(status: SessionStatus): Session {
15
22
  return {
16
23
  id: 'ses_a' as SessionId,
24
+ threadId: thread,
17
25
  projectId: project,
18
26
  tenantId: tenant,
19
27
  status,
@@ -5,6 +5,7 @@ export type { ActorRef, SystemRoleId } from './actor.js'
5
5
  export type { Lineage } from './lineage.js'
6
6
  export type { Tenant } from './tenant.js'
7
7
  export type { Project, ProjectConfig } from './project.js'
8
+ export type { Thread, ThreadStatus } from './thread.js'
8
9
  export type { Session, SessionStatus } from './session.js'
9
10
  export { deriveStatus } from './session.js'
10
11
  export type {
@@ -1,6 +1,6 @@
1
1
  import type { SessionId, TenantId } from '../../types/ids/index.js'
2
2
  import type { RunStatus } from '../../types/run/status.js'
3
- import type { ProjectId, WorkspaceId } from '../../types/session/ids.js'
3
+ import type { ProjectId, ThreadId, WorkspaceId } from '../../types/session/ids.js'
4
4
  import type { ActorRef } from './actor.js'
5
5
 
6
6
  /**
@@ -21,15 +21,27 @@ export type SessionStatus =
21
21
  /**
22
22
  * Multi-turn work unit owned by exactly one {@link ActorRef} at a time.
23
23
  *
24
- * Fields derived from session-hierarchy.md §4.3:
24
+ * Scope identifiers:
25
+ * - `threadId` — the topic-level {@link Thread} this Session lives under.
26
+ * Set at creation, immutable; Sessions never move threads.
27
+ * - `projectId` — the {@link Project} the owning Thread belongs to.
28
+ * **Denormalized** from `thread.projectId` at creation time; immutable.
29
+ * Kept on the Session record for ergonomic access (Project-scoped
30
+ * consumers — handoff validators, archival, retention — would otherwise
31
+ * need a second round-trip to ThreadStore on every read). This is NOT
32
+ * a deprecated mirror of a fading field; it is a deliberate
33
+ * denormalization of structurally-immutable derived data.
34
+ *
35
+ * Other invariants (session-hierarchy.md §4.3):
25
36
  * - `previousActors` is append-only and publicly read-only; previous
26
- * owners cannot write to the session again (Decision #3).
37
+ * owners cannot write to the session again.
27
38
  * - `ownerVersion` is the CAS counter for handoff (§6.1 / §6.2 / §6.4).
28
39
  * - `workspaceId` is nullable for sessions whose workspace has not yet
29
40
  * been provisioned (or has been torn down during archival).
30
41
  */
31
42
  export interface Session {
32
43
  id: SessionId
44
+ threadId: ThreadId
33
45
  projectId: ProjectId
34
46
  tenantId: TenantId
35
47
  status: SessionStatus
@@ -0,0 +1,55 @@
1
+ import type { TenantId } from '../../types/ids/index.js'
2
+ import type { ProjectId, ThreadId } from '../../types/session/ids.js'
3
+
4
+ /**
5
+ * Lifecycle state of a Thread.
6
+ *
7
+ * - `open` — accepts new Sessions and new Runs under existing Sessions.
8
+ * - `archived` — read-only tombstone. No new Sessions may be created; existing
9
+ * Sessions remain navigable. Transitioning `open → archived` requires that
10
+ * no Session under the Thread is in a non-terminal state (guarded at the
11
+ * store level by listing + status fan-in).
12
+ *
13
+ * There is no `active` variant — Thread does NOT derive status from its child
14
+ * Sessions the way a Session does from its Runs. Thread is a pure container
15
+ * (Phase 0 decision B.1: Thread is container-only, no message stream, no
16
+ * fan-in). Its status is an explicit owner action.
17
+ */
18
+ export type ThreadStatus = 'open' | 'archived'
19
+
20
+ /**
21
+ * Topic-level container sitting between {@link ProjectId Project} and
22
+ * {@link import('../../types/session/ids.js').SessionId Session} in the
23
+ * five-layer hierarchy (Project → Thread → Session → SubSession → Run).
24
+ *
25
+ * A Thread groups together many Sessions that address the same coherent
26
+ * topic or line-of-work within a Project (e.g. "auth refactor", "billing
27
+ * incident"). Sessions under the same Thread share Project-level shared
28
+ * resources (memory, vaults, knowledge bases) but have independent actor
29
+ * state, handoff history, and Run streams.
30
+ *
31
+ * Design §4 (`docs.local/sessions/ses_001-hierarchy-redesign/design.md`):
32
+ * - Container only. No own message stream, no own Run stream. Messages
33
+ * live in Sessions (Phase 0 decision B.1).
34
+ * - `title` is a user-facing label. **Titles are NOT unique within a
35
+ * Project.** Callers disambiguate by {@link ThreadId}; the title is
36
+ * freeform display text. If a product surface needs uniqueness (e.g.
37
+ * a human-typed slug), that constraint lives at the API layer, not in
38
+ * the kernel.
39
+ * - `ownerVersion` is the CAS counter for mutations — `updateThread` and
40
+ * archival transitions require a matching version and reject
41
+ * {@link StaleThreadError} on mismatch. Mirrors the
42
+ * {@link import('./session.js').Session} handoff CAS pattern (§6.1).
43
+ * - No fan-in `deriveStatus()` helper — status is owner-managed, not
44
+ * Run-derived. This is the Thread-vs-Session contract boundary.
45
+ */
46
+ export interface Thread {
47
+ id: ThreadId
48
+ projectId: ProjectId
49
+ tenantId: TenantId
50
+ title: string
51
+ status: ThreadStatus
52
+ ownerVersion: number
53
+ createdAt: Date
54
+ updatedAt: Date
55
+ }
@@ -1,23 +1,18 @@
1
1
  /**
2
2
  * ID-prefix migration window — read-side compat for legacy `thd_*` IDs.
3
3
  *
4
- * Phase 1 already ships {@link parseThreadId} in `utils/id.ts` that accepts
5
- * either `thd_*` or `prj_*` silently. This module formalises the warning
6
- * emission path called out in session-hierarchy.md §13.3.1:
7
- *
8
- * | Version | Reader accepts | Writer emits | Legacy read behaviour |
9
- * |---------|---------------------|--------------|-------------------------------|
10
- * | 0.2.x | `thd_*` AND `prj_*` | `prj_*` only | emits `MigrationWarning` once |
11
- * | 0.3.x | `prj_*` only | `prj_*` only | rejects `StalePrefixError` |
12
- *
13
4
  * Consumers that touch raw legacy IDs (filesystem migrator, wire decoders,
14
5
  * CLI imports) route through {@link acceptLegacyThreadId} so the warning
15
6
  * signal is structured rather than ad-hoc console output (Convention #18).
16
7
  *
17
- * The `WINDOW_OPEN` constant is the single switch that flips this module
18
- * from soft-accept to hard-reject when 0.3.0 cuts. Convention #0: no silent
19
- * long-lived compat the window is explicit, dated, and fails closed when
20
- * the clock runs out.
8
+ * | Window state | Reader accepts | Writer emits | Legacy read behaviour |
9
+ * |--------------|---------------------|--------------|-------------------------------|
10
+ * | OPEN (now) | `thd_*` AND `prj_*` | `prj_*` only | emits `MigrationWarning` once |
11
+ * | CLOSED | `prj_*` only | `prj_*` only | rejects `StalePrefixError` |
12
+ *
13
+ * The {@link WINDOW_OPEN} constant is the single switch that flips this
14
+ * module from soft-accept to hard-reject. Convention #0: no silent
15
+ * long-lived compat — the window is explicit and fails closed when closed.
21
16
  */
22
17
 
23
18
  import type { ProjectId } from '../../types/session/ids.js'
@@ -10,7 +10,7 @@ import { WorkspaceBackendRegistry } from '../../../session/workspace/registry.js
10
10
  import { InMemorySessionStore } from '../../../store/session/memory.js'
11
11
  import type { AgentId, TenantId, UserId } from '../../../types/ids/index.js'
12
12
  import { createUserMessage } from '../../../types/message/index.js'
13
- import type { WorkspaceId } from '../../../types/session/ids.js'
13
+ import type { ThreadId, WorkspaceId } from '../../../types/session/ids.js'
14
14
  import {
15
15
  ArchivalManager,
16
16
  ArchiveNotConfiguredError,
@@ -18,6 +18,8 @@ import {
18
18
  } from '../archive.js'
19
19
  import { DiskArchiveBackend } from '../disk-backend.js'
20
20
 
21
+ const TEST_THREAD_ID = 'thd_test' as ThreadId
22
+
21
23
  const tenantA = 'tnt_alpha' as TenantId
22
24
 
23
25
  function stubLogger() {
@@ -43,11 +45,11 @@ function agentActor(tenantId: TenantId): ActorRef {
43
45
  async function seedIdleSubSession(store: InMemorySessionStore) {
44
46
  const project = await store.createProject({ tenantId: tenantA, name: 'p' }, tenantA)
45
47
  const parent = await store.createSession(
46
- { projectId: project.id, currentActor: userActor(tenantA) },
48
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor(tenantA) },
47
49
  tenantA,
48
50
  )
49
51
  const child = await store.createSession(
50
- { projectId: project.id, currentActor: agentActor(tenantA) },
52
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(tenantA) },
51
53
  tenantA,
52
54
  )
53
55
  const sub = await store.createSubSession(
@@ -3,7 +3,7 @@ import { TenantIsolationError } from '../../../session/errors.js'
3
3
  import type { ActorRef } from '../../../session/hierarchy/actor.js'
4
4
  import { InMemorySessionStore } from '../../../store/session/memory.js'
5
5
  import type { AgentId, SessionId, TenantId, UserId } from '../../../types/ids/index.js'
6
- import type { SummaryId } from '../../../types/session/ids.js'
6
+ import type { SummaryId, ThreadId } from '../../../types/session/ids.js'
7
7
  import type { DeliverableRef } from '../deliverable.js'
8
8
  import { SessionSummaryMaterializer } from '../materialize.js'
9
9
  import {
@@ -12,6 +12,8 @@ import {
12
12
  SessionAlreadySummarizedError,
13
13
  } from '../ref.js'
14
14
 
15
+ const TEST_THREAD_ID = 'thd_test' as ThreadId
16
+
15
17
  const tenantA = 'tnt_alpha' as TenantId
16
18
  const tenantB = 'tnt_beta' as TenantId
17
19
 
@@ -31,7 +33,7 @@ function makeSummaryIdGenerator(): () => SummaryId {
31
33
  async function seedActiveSession(store: InMemorySessionStore, tenantId: TenantId) {
32
34
  const project = await store.createProject({ tenantId, name: 'p1' }, tenantId)
33
35
  const session = await store.createSession(
34
- { projectId: project.id, currentActor: agentActor(tenantId) },
36
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(tenantId) },
35
37
  tenantId,
36
38
  )
37
39
  // Put the session into `active` so the materializer's status-flip behavior
@@ -232,7 +234,7 @@ describe('SessionSummaryMaterializer.materialize', () => {
232
234
  const store = new InMemorySessionStore()
233
235
  const project = await store.createProject({ tenantId: tenantA, name: 'p1' }, tenantA)
234
236
  const session = await store.createSession(
235
- { projectId: project.id, currentActor: userActor(tenantA) },
237
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: userActor(tenantA) },
236
238
  tenantA,
237
239
  )
238
240
  // session.status defaults to 'idle'
@@ -255,7 +257,7 @@ describe('SessionSummaryMaterializer.materialize', () => {
255
257
  const store = new InMemorySessionStore()
256
258
  const project = await store.createProject({ tenantId: tenantA, name: 'p1' }, tenantA)
257
259
  const session = await store.createSession(
258
- { projectId: project.id, currentActor: agentActor(tenantA) },
260
+ { threadId: TEST_THREAD_ID, projectId: project.id, currentActor: agentActor(tenantA) },
259
261
  tenantA,
260
262
  )
261
263
  await store.updateSession({ ...session, status: 'failed' }, tenantA)
@@ -10,9 +10,6 @@ export { InMemoryTaskStore } from './task/memory.js'
10
10
  export { DiskTaskStore } from './task/disk.js'
11
11
  export type { DiskTaskStoreConfig } from './task/disk.js'
12
12
 
13
- export { InMemoryConversationStore } from './conversation/memory.js'
14
- export type { InMemoryConversationStoreConfig } from './conversation/memory.js'
15
-
16
13
  export { InMemoryMemoryIndex } from './memory/index.js'
17
14
  export { InMemoryMemoryStore } from './memory/memory.js'
18
15
  export { DiskMemoryStore } from './memory/disk.js'