@namzu/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/CHANGELOG.md +74 -2
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +3 -2
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/SupervisorAgent.js +5 -2
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/bridge/a2a/index.d.ts +1 -1
  9. package/dist/bridge/a2a/index.d.ts.map +1 -1
  10. package/dist/bridge/a2a/index.js +1 -1
  11. package/dist/bridge/a2a/index.js.map +1 -1
  12. package/dist/bridge/a2a/message.d.ts +0 -2
  13. package/dist/bridge/a2a/message.d.ts.map +1 -1
  14. package/dist/bridge/a2a/message.js +0 -26
  15. package/dist/bridge/a2a/message.js.map +1 -1
  16. package/dist/bridge/a2a/task.d.ts +4 -3
  17. package/dist/bridge/a2a/task.d.ts.map +1 -1
  18. package/dist/bridge/a2a/task.js +4 -4
  19. package/dist/bridge/a2a/task.js.map +1 -1
  20. package/dist/contracts/api.d.ts +6 -38
  21. package/dist/contracts/api.d.ts.map +1 -1
  22. package/dist/contracts/ids.d.ts +1 -1
  23. package/dist/contracts/ids.d.ts.map +1 -1
  24. package/dist/contracts/index.d.ts +3 -5
  25. package/dist/contracts/index.d.ts.map +1 -1
  26. package/dist/contracts/index.js +1 -1
  27. package/dist/contracts/index.js.map +1 -1
  28. package/dist/contracts/schemas.d.ts +1 -31
  29. package/dist/contracts/schemas.d.ts.map +1 -1
  30. package/dist/contracts/schemas.js +1 -7
  31. package/dist/contracts/schemas.js.map +1 -1
  32. package/dist/index.d.ts +2 -7
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +5 -6
  35. package/dist/index.js.map +1 -1
  36. package/dist/manager/agent/__tests__/lifecycle.test.js +27 -13
  37. package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -1
  38. package/dist/manager/agent/lifecycle.d.ts +9 -0
  39. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  40. package/dist/manager/agent/lifecycle.js +93 -31
  41. package/dist/manager/agent/lifecycle.js.map +1 -1
  42. package/dist/manager/index.d.ts +2 -0
  43. package/dist/manager/index.d.ts.map +1 -1
  44. package/dist/manager/index.js +1 -0
  45. package/dist/manager/index.js.map +1 -1
  46. package/dist/manager/run/persistence.d.ts +3 -1
  47. package/dist/manager/run/persistence.d.ts.map +1 -1
  48. package/dist/manager/run/persistence.js +5 -0
  49. package/dist/manager/run/persistence.js.map +1 -1
  50. package/dist/manager/thread/__tests__/lifecycle.test.d.ts +2 -0
  51. package/dist/manager/thread/__tests__/lifecycle.test.d.ts.map +1 -0
  52. package/dist/manager/thread/__tests__/lifecycle.test.js +216 -0
  53. package/dist/manager/thread/__tests__/lifecycle.test.js.map +1 -0
  54. package/dist/manager/thread/lifecycle.d.ts +105 -0
  55. package/dist/manager/thread/lifecycle.d.ts.map +1 -0
  56. package/dist/manager/thread/lifecycle.js +186 -0
  57. package/dist/manager/thread/lifecycle.js.map +1 -0
  58. package/dist/rag/retriever.js +2 -2
  59. package/dist/registry/tool/execute.js +1 -1
  60. package/dist/registry/tool/execute.js.map +1 -1
  61. package/dist/runtime/query/__tests__/context.test.js +8 -7
  62. package/dist/runtime/query/__tests__/context.test.js.map +1 -1
  63. package/dist/runtime/query/context-cache.d.ts +3 -3
  64. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  65. package/dist/runtime/query/context-cache.js +2 -2
  66. package/dist/runtime/query/context-cache.js.map +1 -1
  67. package/dist/runtime/query/context.d.ts +12 -21
  68. package/dist/runtime/query/context.d.ts.map +1 -1
  69. package/dist/runtime/query/context.js +3 -1
  70. package/dist/runtime/query/context.js.map +1 -1
  71. package/dist/runtime/query/index.d.ts +13 -15
  72. package/dist/runtime/query/index.d.ts.map +1 -1
  73. package/dist/runtime/query/index.js +2 -1
  74. package/dist/runtime/query/index.js.map +1 -1
  75. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  76. package/dist/runtime/query/iteration/index.js +1 -1
  77. package/dist/runtime/query/iteration/index.js.map +1 -1
  78. package/dist/session/__tests__/integration/_fixtures.d.ts +11 -4
  79. package/dist/session/__tests__/integration/_fixtures.d.ts.map +1 -1
  80. package/dist/session/__tests__/integration/_fixtures.js +23 -6
  81. package/dist/session/__tests__/integration/_fixtures.js.map +1 -1
  82. package/dist/session/__tests__/integration/archive-gate.test.d.ts +15 -0
  83. package/dist/session/__tests__/integration/archive-gate.test.d.ts.map +1 -0
  84. package/dist/session/__tests__/integration/archive-gate.test.js +214 -0
  85. package/dist/session/__tests__/integration/archive-gate.test.js.map +1 -0
  86. package/dist/session/__tests__/integration/capacity-caps.test.js +13 -6
  87. package/dist/session/__tests__/integration/capacity-caps.test.js.map +1 -1
  88. package/dist/session/__tests__/integration/e2e-spawn.test.js +14 -2
  89. package/dist/session/__tests__/integration/e2e-spawn.test.js.map +1 -1
  90. package/dist/session/__tests__/integration/event-stream-ordering.test.js +14 -7
  91. package/dist/session/__tests__/integration/event-stream-ordering.test.js.map +1 -1
  92. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js +26 -14
  93. package/dist/session/__tests__/integration/handoff-broadcast-e2e.test.js.map +1 -1
  94. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js +30 -20
  95. package/dist/session/__tests__/integration/handoff-illegal-transition.test.js.map +1 -1
  96. package/dist/session/__tests__/integration/handoff-single-e2e.test.js +25 -9
  97. package/dist/session/__tests__/integration/handoff-single-e2e.test.js.map +1 -1
  98. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js +11 -10
  99. package/dist/session/__tests__/integration/hierarchy-lifecycle.test.js.map +1 -1
  100. package/dist/session/__tests__/integration/prev-artifact-dag.test.js +5 -4
  101. package/dist/session/__tests__/integration/prev-artifact-dag.test.js.map +1 -1
  102. package/dist/session/__tests__/integration/retention-archive.test.js +3 -2
  103. package/dist/session/__tests__/integration/retention-archive.test.js.map +1 -1
  104. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts +26 -0
  105. package/dist/session/__tests__/integration/spawn-rollback.test.d.ts.map +1 -0
  106. package/dist/session/__tests__/integration/spawn-rollback.test.js +236 -0
  107. package/dist/session/__tests__/integration/spawn-rollback.test.js.map +1 -0
  108. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js +2 -1
  109. package/dist/session/__tests__/integration/summary-materialization-e2e.test.js.map +1 -1
  110. package/dist/session/__tests__/integration/tenant-isolation.test.js +14 -5
  111. package/dist/session/__tests__/integration/tenant-isolation.test.js.map +1 -1
  112. package/dist/session/errors.d.ts +79 -0
  113. package/dist/session/errors.d.ts.map +1 -1
  114. package/dist/session/errors.js +57 -0
  115. package/dist/session/errors.js.map +1 -1
  116. package/dist/session/handoff/__tests__/broadcast.test.js +49 -31
  117. package/dist/session/handoff/__tests__/broadcast.test.js.map +1 -1
  118. package/dist/session/handoff/__tests__/capacity.test.js +21 -18
  119. package/dist/session/handoff/__tests__/capacity.test.js.map +1 -1
  120. package/dist/session/handoff/__tests__/single.test.js +39 -30
  121. package/dist/session/handoff/__tests__/single.test.js.map +1 -1
  122. package/dist/session/handoff/assignment.d.ts +13 -1
  123. package/dist/session/handoff/assignment.d.ts.map +1 -1
  124. package/dist/session/handoff/broadcast.d.ts +7 -0
  125. package/dist/session/handoff/broadcast.d.ts.map +1 -1
  126. package/dist/session/handoff/broadcast.js +16 -1
  127. package/dist/session/handoff/broadcast.js.map +1 -1
  128. package/dist/session/handoff/single.d.ts +7 -0
  129. package/dist/session/handoff/single.d.ts.map +1 -1
  130. package/dist/session/handoff/single.js +13 -1
  131. package/dist/session/handoff/single.js.map +1 -1
  132. package/dist/session/hierarchy/__tests__/session.test.js +2 -0
  133. package/dist/session/hierarchy/__tests__/session.test.js.map +1 -1
  134. package/dist/session/hierarchy/index.d.ts +1 -0
  135. package/dist/session/hierarchy/index.d.ts.map +1 -1
  136. package/dist/session/hierarchy/index.js.map +1 -1
  137. package/dist/session/hierarchy/session.d.ts +15 -3
  138. package/dist/session/hierarchy/session.d.ts.map +1 -1
  139. package/dist/session/hierarchy/session.js.map +1 -1
  140. package/dist/session/hierarchy/thread.d.ts +54 -0
  141. package/dist/session/hierarchy/thread.d.ts.map +1 -0
  142. package/dist/session/hierarchy/thread.js +2 -0
  143. package/dist/session/hierarchy/thread.js.map +1 -0
  144. package/dist/session/migration/id-prefix.d.ts +8 -13
  145. package/dist/session/migration/id-prefix.d.ts.map +1 -1
  146. package/dist/session/migration/id-prefix.js +8 -13
  147. package/dist/session/migration/id-prefix.js.map +1 -1
  148. package/dist/session/retention/__tests__/archive.test.js +3 -2
  149. package/dist/session/retention/__tests__/archive.test.js.map +1 -1
  150. package/dist/session/summary/__tests__/materialize.test.js +4 -3
  151. package/dist/session/summary/__tests__/materialize.test.js.map +1 -1
  152. package/dist/store/index.d.ts +0 -2
  153. package/dist/store/index.d.ts.map +1 -1
  154. package/dist/store/index.js +0 -1
  155. package/dist/store/index.js.map +1 -1
  156. package/dist/store/session/__tests__/disk.test.js +32 -5
  157. package/dist/store/session/__tests__/disk.test.js.map +1 -1
  158. package/dist/store/session/__tests__/memory.test.js +50 -9
  159. package/dist/store/session/__tests__/memory.test.js.map +1 -1
  160. package/dist/store/session/disk.d.ts +2 -1
  161. package/dist/store/session/disk.d.ts.map +1 -1
  162. package/dist/store/session/disk.js +61 -0
  163. package/dist/store/session/disk.js.map +1 -1
  164. package/dist/store/session/index.d.ts.map +1 -1
  165. package/dist/store/session/index.js +3 -4
  166. package/dist/store/session/index.js.map +1 -1
  167. package/dist/store/session/memory.d.ts +2 -1
  168. package/dist/store/session/memory.d.ts.map +1 -1
  169. package/dist/store/session/memory.js +13 -0
  170. package/dist/store/session/memory.js.map +1 -1
  171. package/dist/store/thread/disk.d.ts +41 -0
  172. package/dist/store/thread/disk.d.ts.map +1 -0
  173. package/dist/store/thread/disk.js +229 -0
  174. package/dist/store/thread/disk.js.map +1 -0
  175. package/dist/store/thread/index.d.ts +4 -0
  176. package/dist/store/thread/index.d.ts.map +1 -0
  177. package/dist/store/thread/index.js +6 -0
  178. package/dist/store/thread/index.js.map +1 -0
  179. package/dist/store/thread/memory.d.ts +23 -0
  180. package/dist/store/thread/memory.d.ts.map +1 -0
  181. package/dist/store/thread/memory.js +90 -0
  182. package/dist/store/thread/memory.js.map +1 -0
  183. package/dist/telemetry/runtime-accessors.d.ts +4 -0
  184. package/dist/telemetry/runtime-accessors.d.ts.map +1 -0
  185. package/dist/telemetry/runtime-accessors.js +17 -0
  186. package/dist/telemetry/runtime-accessors.js.map +1 -0
  187. package/dist/types/agent/base.d.ts +17 -21
  188. package/dist/types/agent/base.d.ts.map +1 -1
  189. package/dist/types/agent/factory.d.ts +8 -2
  190. package/dist/types/agent/factory.d.ts.map +1 -1
  191. package/dist/types/agent/task.d.ts +18 -11
  192. package/dist/types/agent/task.d.ts.map +1 -1
  193. package/dist/types/ids/index.d.ts +5 -9
  194. package/dist/types/ids/index.d.ts.map +1 -1
  195. package/dist/types/ids/index.js +4 -4
  196. package/dist/types/ids/index.js.map +1 -1
  197. package/dist/types/rag/retrieval.d.ts +4 -3
  198. package/dist/types/rag/retrieval.d.ts.map +1 -1
  199. package/dist/types/run/config.d.ts +6 -5
  200. package/dist/types/run/config.d.ts.map +1 -1
  201. package/dist/types/run/metadata.d.ts +5 -18
  202. package/dist/types/run/metadata.d.ts.map +1 -1
  203. package/dist/types/session/ids.d.ts +4 -13
  204. package/dist/types/session/ids.d.ts.map +1 -1
  205. package/dist/types/session/ids.js +3 -6
  206. package/dist/types/session/ids.js.map +1 -1
  207. package/dist/types/session/index.d.ts +1 -1
  208. package/dist/types/session/index.d.ts.map +1 -1
  209. package/dist/types/session/store.d.ts +32 -10
  210. package/dist/types/session/store.d.ts.map +1 -1
  211. package/dist/types/session/store.js +3 -8
  212. package/dist/types/session/store.js.map +1 -1
  213. package/dist/types/thread/index.d.ts +2 -0
  214. package/dist/types/thread/index.d.ts.map +1 -0
  215. package/dist/types/thread/index.js +5 -0
  216. package/dist/types/thread/index.js.map +1 -0
  217. package/dist/types/thread/store.d.ts +86 -0
  218. package/dist/types/thread/store.d.ts.map +1 -0
  219. package/dist/types/thread/store.js +22 -0
  220. package/dist/types/thread/store.js.map +1 -0
  221. package/dist/utils/id.d.ts +1 -12
  222. package/dist/utils/id.d.ts.map +1 -1
  223. package/dist/utils/id.js +3 -23
  224. package/dist/utils/id.js.map +1 -1
  225. package/package.json +11 -20
  226. package/src/agents/ReactiveAgent.ts +3 -2
  227. package/src/agents/SupervisorAgent.ts +5 -2
  228. package/src/bridge/a2a/index.ts +0 -1
  229. package/src/bridge/a2a/message.ts +0 -32
  230. package/src/bridge/a2a/task.ts +8 -7
  231. package/src/contracts/api.ts +6 -42
  232. package/src/contracts/ids.ts +1 -1
  233. package/src/contracts/index.ts +2 -8
  234. package/src/contracts/schemas.ts +1 -8
  235. package/src/index.ts +3 -15
  236. package/src/manager/agent/__tests__/lifecycle.test.ts +34 -13
  237. package/src/manager/agent/lifecycle.ts +114 -35
  238. package/src/manager/index.ts +3 -0
  239. package/src/manager/run/persistence.ts +7 -1
  240. package/src/manager/thread/__tests__/lifecycle.test.ts +286 -0
  241. package/src/manager/thread/lifecycle.ts +217 -0
  242. package/src/rag/retriever.ts +2 -2
  243. package/src/registry/tool/execute.ts +1 -1
  244. package/src/runtime/query/__tests__/context.test.ts +9 -8
  245. package/src/runtime/query/context-cache.ts +4 -4
  246. package/src/runtime/query/context.ts +15 -22
  247. package/src/runtime/query/index.ts +16 -17
  248. package/src/runtime/query/iteration/index.ts +1 -1
  249. package/src/session/__tests__/integration/_fixtures.ts +36 -8
  250. package/src/session/__tests__/integration/archive-gate.test.ts +288 -0
  251. package/src/session/__tests__/integration/capacity-caps.test.ts +13 -6
  252. package/src/session/__tests__/integration/e2e-spawn.test.ts +20 -2
  253. package/src/session/__tests__/integration/event-stream-ordering.test.ts +14 -7
  254. package/src/session/__tests__/integration/handoff-broadcast-e2e.test.ts +39 -13
  255. package/src/session/__tests__/integration/handoff-illegal-transition.test.ts +54 -19
  256. package/src/session/__tests__/integration/handoff-single-e2e.test.ts +40 -9
  257. package/src/session/__tests__/integration/hierarchy-lifecycle.test.ts +13 -10
  258. package/src/session/__tests__/integration/prev-artifact-dag.test.ts +12 -5
  259. package/src/session/__tests__/integration/retention-archive.test.ts +5 -3
  260. package/src/session/__tests__/integration/spawn-rollback.test.ts +313 -0
  261. package/src/session/__tests__/integration/summary-materialization-e2e.test.ts +4 -2
  262. package/src/session/__tests__/integration/tenant-isolation.test.ts +16 -6
  263. package/src/session/errors.ts +89 -0
  264. package/src/session/handoff/__tests__/broadcast.test.ts +56 -28
  265. package/src/session/handoff/__tests__/capacity.test.ts +26 -20
  266. package/src/session/handoff/__tests__/single.test.ts +45 -28
  267. package/src/session/handoff/assignment.ts +13 -1
  268. package/src/session/handoff/broadcast.ts +26 -1
  269. package/src/session/handoff/single.ts +23 -1
  270. package/src/session/hierarchy/__tests__/session.test.ts +9 -1
  271. package/src/session/hierarchy/index.ts +1 -0
  272. package/src/session/hierarchy/session.ts +15 -3
  273. package/src/session/hierarchy/thread.ts +55 -0
  274. package/src/session/migration/id-prefix.ts +8 -13
  275. package/src/session/retention/__tests__/archive.test.ts +5 -3
  276. package/src/session/summary/__tests__/materialize.test.ts +6 -4
  277. package/src/store/index.ts +0 -3
  278. package/src/store/session/__tests__/disk.test.ts +57 -6
  279. package/src/store/session/__tests__/memory.test.ts +84 -9
  280. package/src/store/session/disk.ts +57 -1
  281. package/src/store/session/index.ts +3 -4
  282. package/src/store/session/memory.ts +13 -1
  283. package/src/store/thread/disk.ts +261 -0
  284. package/src/store/thread/index.ts +7 -0
  285. package/src/store/thread/memory.ts +104 -0
  286. package/src/telemetry/runtime-accessors.ts +19 -0
  287. package/src/types/agent/base.ts +17 -21
  288. package/src/types/agent/factory.ts +8 -3
  289. package/src/types/agent/task.ts +19 -11
  290. package/src/types/ids/index.ts +8 -15
  291. package/src/types/rag/retrieval.ts +4 -3
  292. package/src/types/run/config.ts +6 -5
  293. package/src/types/run/metadata.ts +5 -18
  294. package/src/types/session/ids.ts +4 -15
  295. package/src/types/session/index.ts +1 -2
  296. package/src/types/session/store.ts +34 -11
  297. package/src/types/thread/index.ts +5 -0
  298. package/src/types/thread/store.ts +92 -0
  299. package/src/utils/id.ts +3 -24
  300. package/dist/provider/telemetry/setup.d.ts +0 -19
  301. package/dist/provider/telemetry/setup.d.ts.map +0 -1
  302. package/dist/provider/telemetry/setup.js +0 -102
  303. package/dist/provider/telemetry/setup.js.map +0 -1
  304. package/dist/store/conversation/memory.d.ts +0 -43
  305. package/dist/store/conversation/memory.d.ts.map +0 -1
  306. package/dist/store/conversation/memory.js +0 -108
  307. package/dist/store/conversation/memory.js.map +0 -1
  308. package/dist/telemetry/index.d.ts +0 -6
  309. package/dist/telemetry/index.d.ts.map +0 -1
  310. package/dist/telemetry/index.js +0 -4
  311. package/dist/telemetry/index.js.map +0 -1
  312. package/dist/telemetry/metrics.d.ts +0 -8
  313. package/dist/telemetry/metrics.d.ts.map +0 -1
  314. package/dist/telemetry/metrics.js +0 -53
  315. package/dist/telemetry/metrics.js.map +0 -1
  316. package/dist/types/conversation/index.d.ts +0 -14
  317. package/dist/types/conversation/index.d.ts.map +0 -1
  318. package/dist/types/conversation/index.js +0 -2
  319. package/dist/types/conversation/index.js.map +0 -1
  320. package/dist/types/telemetry/index.d.ts +0 -10
  321. package/dist/types/telemetry/index.d.ts.map +0 -1
  322. package/dist/types/telemetry/index.js +0 -2
  323. package/dist/types/telemetry/index.js.map +0 -1
  324. package/src/provider/telemetry/setup.ts +0 -125
  325. package/src/store/conversation/memory.ts +0 -144
  326. package/src/telemetry/index.ts +0 -14
  327. package/src/telemetry/metrics.ts +0 -69
  328. package/src/types/conversation/index.ts +0 -15
  329. package/src/types/telemetry/index.ts +0 -10
@@ -45,7 +45,7 @@ import type {
45
45
  } from '../../session/summary/ref.js'
46
46
  import type { MessageId, SessionId, TenantId } from '../../types/ids/index.js'
47
47
  import type { Message } from '../../types/message/index.js'
48
- import type { ProjectId, SubSessionId, SummaryId } from '../../types/session/ids.js'
48
+ import type { ProjectId, SubSessionId, SummaryId, ThreadId } from '../../types/session/ids.js'
49
49
  import type {
50
50
  CreateProjectParams,
51
51
  CreateSessionParams,
@@ -82,6 +82,7 @@ interface PersistedProject {
82
82
 
83
83
  interface PersistedSession {
84
84
  id: SessionId
85
+ threadId: ThreadId
85
86
  projectId: ProjectId
86
87
  tenantId: TenantId
87
88
  status: Session['status']
@@ -221,6 +222,7 @@ export class DiskSessionStore implements SessionStore {
221
222
  const now = new Date()
222
223
  const session: Session = {
223
224
  id: generateSessionId(),
225
+ threadId: params.threadId,
224
226
  projectId: params.projectId,
225
227
  tenantId,
226
228
  status: 'idle',
@@ -251,6 +253,58 @@ export class DiskSessionStore implements SessionStore {
251
253
  return deserializeSession(raw)
252
254
  }
253
255
 
256
+ async listSessions(threadId: ThreadId, tenantId: TenantId): Promise<readonly Session[]> {
257
+ // Walk projects/*/sessions/* and filter on the persisted record. Sessions
258
+ // don't live under a thread-scoped path in the current layout — the
259
+ // denormalized `threadId` on every session.json is the authority. Matches
260
+ // DiskThreadStore.listThreads in scan semantics.
261
+ //
262
+ // Cost: O(all sessions across all projects in the root) per call. The
263
+ // MVP disk store prioritizes simplicity over index freshness, matching
264
+ // `buildLinkageView` / `locateSession` which use the same pattern. A
265
+ // production driver would maintain a threadId → sessionIds secondary
266
+ // index populated on createSession / deleteSession. Acceptable for
267
+ // ThreadManager archive/delete today because those operations are
268
+ // admin-initiated and infrequent.
269
+ const projectsDir = join(this.rootDir, 'projects')
270
+ let projectDirs: string[]
271
+ try {
272
+ projectDirs = await readdir(projectsDir)
273
+ } catch (err) {
274
+ const code = (err as NodeJS.ErrnoException).code
275
+ if (code === 'ENOENT') return []
276
+ throw err
277
+ }
278
+
279
+ const results: Session[] = []
280
+ for (const rawProject of projectDirs) {
281
+ if (!rawProject.startsWith('prj_')) continue
282
+ const sessionsRoot = join(projectsDir, rawProject, 'sessions')
283
+ let sessionDirs: string[]
284
+ try {
285
+ sessionDirs = await readdir(sessionsRoot)
286
+ } catch {
287
+ continue
288
+ }
289
+ for (const rawSessionId of sessionDirs) {
290
+ if (!rawSessionId.startsWith('ses_')) continue
291
+ const path = join(sessionsRoot, rawSessionId)
292
+ const raw = await readJson<PersistedSession>(join(path, 'session.json'))
293
+ if (!raw) continue
294
+ if (raw.tenantId !== tenantId) continue
295
+ if (raw.threadId !== threadId) continue
296
+ results.push(deserializeSession(raw))
297
+ this.sessionIndex.set(raw.id, {
298
+ sessionId: raw.id,
299
+ projectId: rawProject as ProjectId,
300
+ path,
301
+ })
302
+ }
303
+ }
304
+ results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
305
+ return results
306
+ }
307
+
254
308
  async updateSession(session: Session, tenantId: TenantId): Promise<void> {
255
309
  const located = await this.locateSession(session.id)
256
310
  if (!located) {
@@ -785,6 +839,7 @@ function deserializeProject(p: PersistedProject): Project {
785
839
  function serializeSession(s: Session): PersistedSession {
786
840
  return {
787
841
  id: s.id,
842
+ threadId: s.threadId,
788
843
  projectId: s.projectId,
789
844
  tenantId: s.tenantId,
790
845
  status: s.status,
@@ -800,6 +855,7 @@ function serializeSession(s: Session): PersistedSession {
800
855
  function deserializeSession(s: PersistedSession): Session {
801
856
  return {
802
857
  id: s.id,
858
+ threadId: s.threadId,
803
859
  projectId: s.projectId,
804
860
  tenantId: s.tenantId,
805
861
  status: s.status,
@@ -1,9 +1,8 @@
1
1
  // Sub-barrel for the session-scoped persistence module (Convention #4).
2
2
  //
3
- // `SessionStore` replaces the legacy `ConversationStore`; messages are scoped
4
- // to a `SessionId` (not a bare thread) and every accessor carries explicit
5
- // `TenantId` per session-hierarchy.md §12.1. Concrete implementations live
6
- // in sibling files; re-export them here so consumers import via
3
+ // Messages are scoped to a `SessionId` and every accessor carries explicit
4
+ // `TenantId` (Convention #17). Concrete implementations live in sibling
5
+ // files; re-export them here so consumers import via
7
6
  // `../store/session/index.js`.
8
7
 
9
8
  export { InMemorySessionStore } from './memory.js'
@@ -16,7 +16,7 @@ import { SessionAlreadySummarizedError } from '../../session/summary/ref.js'
16
16
  import type { SessionSummaryRef } from '../../session/summary/ref.js'
17
17
  import type { MessageId, SessionId, TenantId } from '../../types/ids/index.js'
18
18
  import type { Message } from '../../types/message/index.js'
19
- import type { ProjectId, SubSessionId } from '../../types/session/ids.js'
19
+ import type { ProjectId, SubSessionId, ThreadId } from '../../types/session/ids.js'
20
20
  import type {
21
21
  CreateProjectParams,
22
22
  CreateSessionParams,
@@ -118,6 +118,7 @@ export class InMemorySessionStore implements SessionStore {
118
118
  const now = new Date()
119
119
  const session: Session = {
120
120
  id: generateSessionId(),
121
+ threadId: params.threadId,
121
122
  projectId: params.projectId,
122
123
  tenantId,
123
124
  status: 'idle',
@@ -139,6 +140,17 @@ export class InMemorySessionStore implements SessionStore {
139
140
  return record.session
140
141
  }
141
142
 
143
+ async listSessions(threadId: ThreadId, tenantId: TenantId): Promise<readonly Session[]> {
144
+ const matches: Session[] = []
145
+ for (const record of this.sessions.values()) {
146
+ if (record.tenantId !== tenantId) continue
147
+ if (record.session.threadId !== threadId) continue
148
+ matches.push(record.session)
149
+ }
150
+ matches.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
151
+ return matches
152
+ }
153
+
142
154
  async updateSession(session: Session, tenantId: TenantId): Promise<void> {
143
155
  const record = this.sessions.get(session.id)
144
156
  if (!record) {
@@ -0,0 +1,261 @@
1
+ /**
2
+ * DiskThreadStore — filesystem-backed implementation of {@link ThreadStore}.
3
+ *
4
+ * Every mutation is write-tmp-rename (Convention #8). Layout uses the Phase 2
5
+ * intermediate shape:
6
+ *
7
+ * {rootDir}/projects/{projectId}/threads/{threadId}/
8
+ * thread.json
9
+ *
10
+ * Sessions stay under `projects/{projectId}/sessions/{sessionId}/...` rather
11
+ * than nesting under `threads/{threadId}/` — the denormalized `threadId` on
12
+ * each session record (Phase 2.4 decision) makes thread-scoped queries
13
+ * addressable without path-level nesting, and keeps Project-scoped consumers
14
+ * (handoff, retention, archival) a single-directory scan. Phase 6 collapses
15
+ * `projects/{projectId}/` to `.namzu/` as part of `namzu init` folder binding.
16
+ *
17
+ * Tenant scoping is enforced through the JSON payload (`tenantId` field on
18
+ * every record), not the path — cross-tenant reads reject with
19
+ * {@link TenantIsolationError} (Convention #17).
20
+ */
21
+
22
+ import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from 'node:fs/promises'
23
+ import { join } from 'node:path'
24
+ import { StaleThreadError, TenantIsolationError } from '../../session/errors.js'
25
+ import type { Thread, ThreadStatus } from '../../session/hierarchy/thread.js'
26
+ import type { TenantId } from '../../types/ids/index.js'
27
+ import type { ProjectId, ThreadId } from '../../types/session/ids.js'
28
+ import type { CreateThreadParams, ThreadStore } from '../../types/thread/store.js'
29
+ import { generateThreadId } from '../../utils/id.js'
30
+
31
+ /** Config for {@link DiskThreadStore}. `rootDir` is absolute. */
32
+ export interface DiskThreadStoreConfig {
33
+ rootDir: string
34
+ }
35
+
36
+ interface PersistedThread {
37
+ id: ThreadId
38
+ projectId: ProjectId
39
+ tenantId: TenantId
40
+ title: string
41
+ status: ThreadStatus
42
+ ownerVersion: number
43
+ createdAt: string
44
+ updatedAt: string
45
+ }
46
+
47
+ /** Index of threadId → (projectId, path). Lazy; populated on create / lookup. */
48
+ interface ThreadIndexEntry {
49
+ threadId: ThreadId
50
+ projectId: ProjectId
51
+ path: string
52
+ }
53
+
54
+ export class DiskThreadStore implements ThreadStore {
55
+ private readonly rootDir: string
56
+ private readonly threadIndex = new Map<ThreadId, ThreadIndexEntry>()
57
+
58
+ constructor(config: DiskThreadStoreConfig) {
59
+ this.rootDir = config.rootDir
60
+ }
61
+
62
+ async createThread(params: CreateThreadParams, tenantId: TenantId): Promise<Thread> {
63
+ const now = new Date()
64
+ const thread: Thread = {
65
+ id: generateThreadId(),
66
+ projectId: params.projectId,
67
+ tenantId,
68
+ title: params.title,
69
+ status: 'open',
70
+ ownerVersion: 0,
71
+ createdAt: now,
72
+ updatedAt: now,
73
+ }
74
+ const dir = join(this.rootDir, 'projects', params.projectId, 'threads', thread.id)
75
+ await mkdir(dir, { recursive: true })
76
+ await atomicWriteJson(join(dir, 'thread.json'), serializeThread(thread))
77
+ this.threadIndex.set(thread.id, {
78
+ threadId: thread.id,
79
+ projectId: params.projectId,
80
+ path: dir,
81
+ })
82
+ return thread
83
+ }
84
+
85
+ async getThread(threadId: ThreadId, tenantId: TenantId): Promise<Thread | null> {
86
+ const located = await this.locateThread(threadId)
87
+ if (!located) return null
88
+ const raw = await readJson<PersistedThread>(join(located.path, 'thread.json'))
89
+ if (!raw) return null
90
+ this.assertTenant(raw.tenantId, tenantId, `thread(${threadId})`)
91
+ return deserializeThread(raw)
92
+ }
93
+
94
+ async updateThread(thread: Thread, tenantId: TenantId): Promise<void> {
95
+ if (thread.tenantId !== tenantId) {
96
+ throw new TenantIsolationError({
97
+ requested: tenantId,
98
+ resource: `thread(${thread.id}) payload`,
99
+ })
100
+ }
101
+ const located = await this.locateThread(thread.id)
102
+ if (!located) {
103
+ throw new Error(`Thread ${thread.id} not found`)
104
+ }
105
+ const existing = await readJson<PersistedThread>(join(located.path, 'thread.json'))
106
+ if (!existing) {
107
+ throw new Error(`Thread ${thread.id} not found`)
108
+ }
109
+ this.assertTenant(existing.tenantId, tenantId, `thread(${thread.id})`)
110
+
111
+ // CAS on ownerVersion. On mismatch, caller must re-read + re-apply +
112
+ // retry. No silent overwrite (Convention #0).
113
+ if (thread.ownerVersion !== existing.ownerVersion) {
114
+ throw new StaleThreadError({
115
+ threadId: thread.id,
116
+ expectedVersion: thread.ownerVersion,
117
+ actualVersion: existing.ownerVersion,
118
+ })
119
+ }
120
+
121
+ const updated: Thread = {
122
+ ...thread,
123
+ ownerVersion: existing.ownerVersion + 1,
124
+ updatedAt: new Date(),
125
+ }
126
+ await atomicWriteJson(join(located.path, 'thread.json'), serializeThread(updated))
127
+ }
128
+
129
+ async deleteThread(threadId: ThreadId, tenantId: TenantId): Promise<void> {
130
+ const located = await this.locateThread(threadId)
131
+ if (!located) return // Idempotent: missing = no-op.
132
+ const existing = await readJson<PersistedThread>(join(located.path, 'thread.json'))
133
+ if (!existing) return
134
+ this.assertTenant(existing.tenantId, tenantId, `thread(${threadId})`)
135
+
136
+ // The store does NOT enforce "no attached sessions" here — that is a
137
+ // cross-store precondition owned by ThreadManager. This keeps the
138
+ // boundary clean; ThreadStore has no awareness of SessionStore layout.
139
+ await rm(located.path, { recursive: true, force: true })
140
+ this.threadIndex.delete(threadId)
141
+ }
142
+
143
+ async listThreads(projectId: ProjectId, tenantId: TenantId): Promise<readonly Thread[]> {
144
+ const threadsDir = join(this.rootDir, 'projects', projectId, 'threads')
145
+ let entries: string[]
146
+ try {
147
+ entries = await readdir(threadsDir)
148
+ } catch (err) {
149
+ const code = (err as NodeJS.ErrnoException).code
150
+ if (code === 'ENOENT') return []
151
+ throw err
152
+ }
153
+
154
+ const results: Thread[] = []
155
+ for (const entry of entries) {
156
+ if (!entry.startsWith('thd_')) continue
157
+ const path = join(threadsDir, entry)
158
+ const raw = await readJson<PersistedThread>(join(path, 'thread.json'))
159
+ if (!raw) continue
160
+ // Cross-tenant records in the same directory are skipped silently —
161
+ // the listing is scoped to the caller's tenant. Mismatch is not an
162
+ // isolation violation because the caller never requested the record.
163
+ if (raw.tenantId !== tenantId) continue
164
+ results.push(deserializeThread(raw))
165
+ this.threadIndex.set(raw.id, { threadId: raw.id, projectId, path })
166
+ }
167
+ results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
168
+ return results
169
+ }
170
+
171
+ private async locateThread(threadId: ThreadId): Promise<ThreadIndexEntry | null> {
172
+ const cached = this.threadIndex.get(threadId)
173
+ if (cached) return cached
174
+
175
+ // Walk projects/* looking for the thread dir. Cost is bounded by number
176
+ // of projects (usually 1) × number of threads per project.
177
+ const projectsDir = join(this.rootDir, 'projects')
178
+ let projectDirs: string[]
179
+ try {
180
+ projectDirs = await readdir(projectsDir)
181
+ } catch (err) {
182
+ const code = (err as NodeJS.ErrnoException).code
183
+ if (code === 'ENOENT') return null
184
+ throw err
185
+ }
186
+
187
+ for (const rawProject of projectDirs) {
188
+ if (!rawProject.startsWith('prj_')) continue
189
+ const threadPath = join(projectsDir, rawProject, 'threads', threadId)
190
+ const raw = await readJson<PersistedThread>(join(threadPath, 'thread.json'))
191
+ if (raw?.id === threadId) {
192
+ const entry: ThreadIndexEntry = {
193
+ threadId,
194
+ projectId: rawProject as ProjectId,
195
+ path: threadPath,
196
+ }
197
+ this.threadIndex.set(threadId, entry)
198
+ return entry
199
+ }
200
+ }
201
+ return null
202
+ }
203
+
204
+ private assertTenant(owning: TenantId, requested: TenantId, resource: string): void {
205
+ if (owning !== requested) {
206
+ throw new TenantIsolationError({ requested, resource })
207
+ }
208
+ }
209
+ }
210
+
211
+ // Serialization -----------------------------------------------------------
212
+
213
+ function serializeThread(thread: Thread): PersistedThread {
214
+ return {
215
+ id: thread.id,
216
+ projectId: thread.projectId,
217
+ tenantId: thread.tenantId,
218
+ title: thread.title,
219
+ status: thread.status,
220
+ ownerVersion: thread.ownerVersion,
221
+ createdAt: thread.createdAt.toISOString(),
222
+ updatedAt: thread.updatedAt.toISOString(),
223
+ }
224
+ }
225
+
226
+ function deserializeThread(raw: PersistedThread): Thread {
227
+ return {
228
+ id: raw.id,
229
+ projectId: raw.projectId,
230
+ tenantId: raw.tenantId,
231
+ title: raw.title,
232
+ status: raw.status,
233
+ ownerVersion: raw.ownerVersion,
234
+ createdAt: new Date(raw.createdAt),
235
+ updatedAt: new Date(raw.updatedAt),
236
+ }
237
+ }
238
+
239
+ // FS helpers -----------------------------------------------------------------
240
+
241
+ async function readJson<T>(path: string): Promise<T | null> {
242
+ try {
243
+ const raw = await readFile(path, 'utf-8')
244
+ return JSON.parse(raw) as T
245
+ } catch (err) {
246
+ const code = (err as NodeJS.ErrnoException).code
247
+ if (code === 'ENOENT') return null
248
+ throw err
249
+ }
250
+ }
251
+
252
+ async function atomicWriteJson(filePath: string, value: unknown): Promise<void> {
253
+ const tempPath = `${filePath}.tmp`
254
+ try {
255
+ await writeFile(tempPath, JSON.stringify(value, null, 2), 'utf-8')
256
+ await rename(tempPath, filePath)
257
+ } catch (err) {
258
+ await unlink(tempPath).catch(() => undefined)
259
+ throw err
260
+ }
261
+ }
@@ -0,0 +1,7 @@
1
+ // Sub-barrel for the Thread persistence module (Convention #4).
2
+ // Concrete implementations live in sibling files; re-export them here so
3
+ // consumers import via `../store/thread/index.js`.
4
+
5
+ export { InMemoryThreadStore } from './memory.js'
6
+ export { DiskThreadStore } from './disk.js'
7
+ export type { DiskThreadStoreConfig } from './disk.js'
@@ -0,0 +1,104 @@
1
+ /**
2
+ * InMemoryThreadStore — reference in-memory implementation of
3
+ * {@link ThreadStore}.
4
+ *
5
+ * Mirrors the write-time CAS contract of the disk store: every
6
+ * `updateThread` compares the supplied `ownerVersion` against the persisted
7
+ * copy and rejects with `StaleThreadError` on mismatch. Convention #17:
8
+ * cross-tenant access throws `TenantIsolationError` with no fallback.
9
+ */
10
+
11
+ import { StaleThreadError, TenantIsolationError } from '../../session/errors.js'
12
+ import type { Thread } from '../../session/hierarchy/thread.js'
13
+ import type { TenantId } from '../../types/ids/index.js'
14
+ import type { ProjectId, ThreadId } from '../../types/session/ids.js'
15
+ import type { CreateThreadParams, ThreadStore } from '../../types/thread/store.js'
16
+ import { generateThreadId } from '../../utils/id.js'
17
+
18
+ interface ThreadRecord {
19
+ tenantId: TenantId
20
+ thread: Thread
21
+ }
22
+
23
+ export class InMemoryThreadStore implements ThreadStore {
24
+ private readonly threads = new Map<ThreadId, ThreadRecord>()
25
+
26
+ async createThread(params: CreateThreadParams, tenantId: TenantId): Promise<Thread> {
27
+ const now = new Date()
28
+ const thread: Thread = {
29
+ id: generateThreadId(),
30
+ projectId: params.projectId,
31
+ tenantId,
32
+ title: params.title,
33
+ status: 'open',
34
+ ownerVersion: 0,
35
+ createdAt: now,
36
+ updatedAt: now,
37
+ }
38
+ this.threads.set(thread.id, { tenantId, thread })
39
+ return thread
40
+ }
41
+
42
+ async getThread(threadId: ThreadId, tenantId: TenantId): Promise<Thread | null> {
43
+ const record = this.threads.get(threadId)
44
+ if (!record) return null
45
+ this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`)
46
+ return record.thread
47
+ }
48
+
49
+ async updateThread(thread: Thread, tenantId: TenantId): Promise<void> {
50
+ if (thread.tenantId !== tenantId) {
51
+ throw new TenantIsolationError({
52
+ requested: tenantId,
53
+ resource: `thread(${thread.id}) payload`,
54
+ })
55
+ }
56
+ const existing = this.threads.get(thread.id)
57
+ if (!existing) {
58
+ throw new Error(`Thread ${thread.id} not found`)
59
+ }
60
+ this.assertTenant(existing.tenantId, tenantId, `thread(${thread.id})`)
61
+
62
+ // CAS on ownerVersion — supplied version must match persisted exactly.
63
+ // Any drift means another writer already advanced the record; the caller
64
+ // must re-read + re-apply + retry.
65
+ if (thread.ownerVersion !== existing.thread.ownerVersion) {
66
+ throw new StaleThreadError({
67
+ threadId: thread.id,
68
+ expectedVersion: thread.ownerVersion,
69
+ actualVersion: existing.thread.ownerVersion,
70
+ })
71
+ }
72
+
73
+ const updated: Thread = {
74
+ ...thread,
75
+ ownerVersion: existing.thread.ownerVersion + 1,
76
+ updatedAt: new Date(),
77
+ }
78
+ this.threads.set(thread.id, { tenantId, thread: updated })
79
+ }
80
+
81
+ async deleteThread(threadId: ThreadId, tenantId: TenantId): Promise<void> {
82
+ const record = this.threads.get(threadId)
83
+ if (!record) return // Idempotent: missing = no-op.
84
+ this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`)
85
+ this.threads.delete(threadId)
86
+ }
87
+
88
+ async listThreads(projectId: ProjectId, tenantId: TenantId): Promise<readonly Thread[]> {
89
+ const matches: Thread[] = []
90
+ for (const { tenantId: ownerTenant, thread } of this.threads.values()) {
91
+ if (ownerTenant !== tenantId) continue
92
+ if (thread.projectId !== projectId) continue
93
+ matches.push(thread)
94
+ }
95
+ matches.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
96
+ return matches
97
+ }
98
+
99
+ private assertTenant(owning: TenantId, requested: TenantId, resource: string): void {
100
+ if (owning !== requested) {
101
+ throw new TenantIsolationError({ requested, resource })
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,19 @@
1
+ // SDK-internal tracer/meter readers. Not re-exported by the root barrel.
2
+ //
3
+ // These wrap `@opentelemetry/api` globals so SDK internal call sites
4
+ // (runtime/query, runtime/query/iteration, registry/tool/execute) have a
5
+ // single place to resolve the active tracer/meter. When `@namzu/telemetry`
6
+ // is registered, its `registerTelemetry()` mutates the api globals and
7
+ // these readers pick up the real providers; without registration, they
8
+ // return the no-op defaults and every span/meter write is silently
9
+ // discarded — standard OTEL library behavior.
10
+
11
+ import { type Meter, type Tracer, metrics, trace } from '@opentelemetry/api'
12
+
13
+ export function getTracer(): Tracer {
14
+ return trace.getTracer('namzu')
15
+ }
16
+
17
+ export function getMeter(): Meter {
18
+ return metrics.getMeter('namzu')
19
+ }
@@ -1,10 +1,10 @@
1
1
  import type { AgentStatus, CostInfo, TokenUsage } from '../common/index.js'
2
- import type { RunId, SessionId, TenantId, ThreadId } from '../ids/index.js'
2
+ import type { RunId, SessionId, TenantId } from '../ids/index.js'
3
3
  import type { InvocationState } from '../invocation/index.js'
4
4
  import type { Message } from '../message/index.js'
5
5
  import type { PermissionMode } from '../permission/index.js'
6
6
  import type { StopReason } from '../run/stop-reason.js'
7
- import type { ProjectId } from '../session/ids.js'
7
+ import type { ProjectId, ThreadId } from '../session/ids.js'
8
8
  import type { TaskStore } from '../task/index.js'
9
9
  import type { ToolAvailability } from '../tool/index.js'
10
10
 
@@ -24,29 +24,25 @@ export interface BaseAgentConfig {
24
24
  env?: Record<string, string>
25
25
 
26
26
  /**
27
- * @deprecated Use `projectId`. Kept as a migration-window mirror; when both
28
- * are present `projectId` wins. See session-hierarchy.md §13.1.
27
+ * Long-lived goal scope for the run. Required at runtime — agents reject
28
+ * configs missing this (`'X requires sessionId, projectId, and tenantId
29
+ * in config'`).
30
+ *
31
+ * Kept optional at the TYPE level because {@link AgentManager} stamps
32
+ * this field AFTER `configBuilder` returns (manager/agent/lifecycle.ts).
33
+ * Tightening to required is a separate task alongside
34
+ * `AgentFactoryOptions` carrying the triple.
29
35
  */
30
- threadId?: ThreadId
36
+ projectId?: ProjectId
31
37
 
32
38
  /**
33
- * Long-lived goal scope for the run. Required at runtime in 0.2.0 per
34
- * session-hierarchy.md §12.1`{@link ReactiveAgent}`, `{@link
35
- * SupervisorAgent}`, etc. reject configs missing this (`'X requires
36
- * sessionId, projectId, and tenantId in config (§12.1)'`).
37
- *
38
- * Kept optional at the TYPE level during the 0.2.x migration window
39
- * because {@link AgentManager} stamps this field AFTER `configBuilder`
40
- * returns (manager/agent/lifecycle.ts:228–230). That stamping path is
41
- * how every `@namzu/agents` configBuilder currently gets its tenant /
42
- * session / project triple; flipping the type to required without first
43
- * updating every {@link AgentFactoryOptions} consumer (which does not
44
- * carry these fields) would be a gratuitous downstream break.
45
- *
46
- * Tightening to required is Phase 9 Known Delta #6. The type-level flip
47
- * lands in 0.3.0 alongside `AgentFactoryOptions` gaining the triple.
39
+ * Topic the run belongs to. Optional at the TYPE level for the same
40
+ * reason as `projectId` — {@link AgentManager} stamps this field after
41
+ * `configBuilder` returns so `configBuilder` implementations do not
42
+ * need to be updated before this tightens. Tightening to required
43
+ * lands with the `AgentFactoryOptions` triple refactor.
48
44
  */
49
- projectId?: ProjectId
45
+ threadId?: ThreadId
50
46
 
51
47
  /** Session under which the run executes. See `projectId` for the tightening plan. */
52
48
  sessionId?: SessionId
@@ -14,7 +14,14 @@ export interface AgentDefinition {
14
14
  }
15
15
 
16
16
  export interface AgentFactoryOptions {
17
- apiKey: string
17
+ /**
18
+ * API key for providers that authenticate via key (OpenAI, Anthropic,
19
+ * OpenRouter). Optional because BYO-provider flows (Bedrock IAM, custom
20
+ * `ProviderRegistry.create(...)`) resolve credentials outside this object.
21
+ * `configBuilder` implementations should treat an absent `apiKey` as the
22
+ * BYO signal and use the provider passed via the agent config instead.
23
+ */
24
+ apiKey?: string
18
25
  model?: string
19
26
  workingDirectory?: string
20
27
  tokenBudget?: number
@@ -39,8 +46,6 @@ export interface AgentFactoryOptions {
39
46
 
40
47
  taskRouter?: TaskRouterConfig
41
48
 
42
- threadId?: string
43
-
44
49
  runId?: string
45
50
 
46
51
  parentRunId?: string
@@ -4,7 +4,7 @@ import type { TokenUsage } from '../common/index.js'
4
4
  import type { RunId, SessionId, TaskId, TenantId } from '../ids/index.js'
5
5
  import type { Message } from '../message/index.js'
6
6
  import type { RunEventListener } from '../run/events.js'
7
- import type { ProjectId } from '../session/ids.js'
7
+ import type { ProjectId, ThreadId } from '../session/ids.js'
8
8
  import type { AgentInput, BaseAgentConfig, BaseAgentResult } from './base.js'
9
9
  import type { Agent } from './core.js'
10
10
  import type { AgentFactoryOptions } from './factory.js'
@@ -23,15 +23,10 @@ export function isTerminalAgentTaskState(state: AgentTaskState): boolean {
23
23
  }
24
24
 
25
25
  /**
26
- * Context carried into {@link AgentManager.sendMessage}. Phase 6 promotes
27
- * `tenantId`, `sessionId`, `projectId`, and `parentActor` to required fields
28
- * the spawn path is the ingress point for the session hierarchy; callers must
29
- * provide the full scoping set (session-hierarchy.md §12.1 required-callsite
30
- * matrix).
31
- *
32
- * `threadId` was removed — `projectId` owns the root scope. The deprecated
33
- * alias `type ThreadId = ProjectId` still compiles for consumers transitioning
34
- * off the name, but the shape no longer exposes a separate slot.
26
+ * Context carried into {@link AgentManager.sendMessage}. `tenantId`,
27
+ * `threadId`, `sessionId`, `projectId`, and `parentActor` are required —
28
+ * the spawn path is the ingress point for the session hierarchy; callers
29
+ * must provide the full scoping set.
35
30
  */
36
31
  export interface AgentTaskContext {
37
32
  parentRunId: RunId
@@ -49,13 +44,26 @@ export interface AgentTaskContext {
49
44
  /** Isolation boundary. Required per session-hierarchy.md §12.1. */
50
45
  tenantId: TenantId
51
46
 
47
+ /**
48
+ * Topic the current task belongs to. Required in 0.3.0 — spawn copies
49
+ * this onto the child session without a second ThreadStore round-trip,
50
+ * and gates creation on {@link ThreadManager.requireOpen}. Children
51
+ * inherit the parent's `threadId` verbatim; cross-thread spawn is
52
+ * forbidden by design (a delegated sub-agent stays on the same topic).
53
+ */
54
+ threadId: ThreadId
55
+
52
56
  /**
53
57
  * Parent session under which any sub-agent spawn is recorded. Required
54
58
  * in 0.2.0; a spawn cannot be attributed without it.
55
59
  */
56
60
  sessionId: SessionId
57
61
 
58
- /** Long-lived goal scope (replaces `threadId`). Required. */
62
+ /**
63
+ * Long-lived goal scope. Required. Denormalized from the owning Thread
64
+ * (see {@link Thread}) — structurally immutable per Phase 2.4 decision
65
+ * (sessions never cross threads, threads never cross projects).
66
+ */
59
67
  projectId: ProjectId
60
68
 
61
69
  /**