kernl 0.2.1 → 0.6.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 (267) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-check-types.log +4 -0
  3. package/CHANGELOG.md +138 -0
  4. package/LICENSE +1 -1
  5. package/dist/agent/__tests__/concurrency.test.d.ts +2 -0
  6. package/dist/agent/__tests__/concurrency.test.d.ts.map +1 -0
  7. package/dist/agent/__tests__/concurrency.test.js +152 -0
  8. package/dist/agent/__tests__/run.test.d.ts +2 -0
  9. package/dist/agent/__tests__/run.test.d.ts.map +1 -0
  10. package/dist/agent/__tests__/run.test.js +357 -0
  11. package/dist/agent/index.d.ts +1 -0
  12. package/dist/agent/index.d.ts.map +1 -0
  13. package/dist/agent.d.ts +32 -9
  14. package/dist/agent.d.ts.map +1 -1
  15. package/dist/agent.js +101 -14
  16. package/dist/api/__tests__/cursor-page.test.d.ts +2 -0
  17. package/dist/api/__tests__/cursor-page.test.d.ts.map +1 -0
  18. package/dist/api/__tests__/cursor-page.test.js +414 -0
  19. package/dist/api/__tests__/offset-page.test.d.ts +2 -0
  20. package/dist/api/__tests__/offset-page.test.d.ts.map +1 -0
  21. package/dist/api/__tests__/offset-page.test.js +510 -0
  22. package/dist/api/__tests__/threads.test.d.ts +2 -0
  23. package/dist/api/__tests__/threads.test.d.ts.map +1 -0
  24. package/dist/api/__tests__/threads.test.js +338 -0
  25. package/dist/api/models/index.d.ts +2 -0
  26. package/dist/api/models/index.d.ts.map +1 -0
  27. package/dist/api/models/thread.d.ts +120 -0
  28. package/dist/api/models/thread.d.ts.map +1 -0
  29. package/dist/api/pagination/base.d.ts +48 -0
  30. package/dist/api/pagination/base.d.ts.map +1 -0
  31. package/dist/api/pagination/base.js +45 -0
  32. package/dist/api/pagination/cursor.d.ts +44 -0
  33. package/dist/api/pagination/cursor.d.ts.map +1 -0
  34. package/dist/api/pagination/cursor.js +52 -0
  35. package/dist/api/pagination/offset.d.ts +42 -0
  36. package/dist/api/pagination/offset.d.ts.map +1 -0
  37. package/dist/api/pagination/offset.js +55 -0
  38. package/dist/api/resources/threads/events.d.ts +21 -0
  39. package/dist/api/resources/threads/events.d.ts.map +1 -0
  40. package/dist/api/resources/threads/events.js +24 -0
  41. package/dist/api/resources/threads/index.d.ts +4 -0
  42. package/dist/api/resources/threads/index.d.ts.map +1 -0
  43. package/dist/api/resources/threads/index.js +2 -0
  44. package/dist/api/resources/threads/threads.d.ts +57 -0
  45. package/dist/api/resources/threads/threads.d.ts.map +1 -0
  46. package/dist/api/resources/threads/threads.js +199 -0
  47. package/dist/api/resources/threads/types.d.ts +123 -0
  48. package/dist/api/resources/threads/types.d.ts.map +1 -0
  49. package/dist/api/resources/threads/utils.d.ts +18 -0
  50. package/dist/api/resources/threads/utils.d.ts.map +1 -0
  51. package/dist/api/resources/threads/utils.js +78 -0
  52. package/dist/context.d.ts +5 -1
  53. package/dist/context.d.ts.map +1 -1
  54. package/dist/context.js +6 -1
  55. package/dist/index.d.ts +9 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +7 -0
  58. package/dist/internal.d.ts +4 -0
  59. package/dist/internal.d.ts.map +1 -0
  60. package/dist/internal.js +2 -0
  61. package/dist/kernl/index.d.ts +3 -0
  62. package/dist/kernl/index.d.ts.map +1 -0
  63. package/dist/kernl/index.js +2 -0
  64. package/dist/kernl/kernl.d.ts +64 -0
  65. package/dist/kernl/kernl.d.ts.map +1 -0
  66. package/dist/kernl/kernl.js +116 -0
  67. package/dist/kernl/threads.d.ts +110 -0
  68. package/dist/kernl/threads.d.ts.map +1 -0
  69. package/dist/kernl/threads.js +126 -0
  70. package/dist/kernl.d.ts +22 -6
  71. package/dist/kernl.d.ts.map +1 -1
  72. package/dist/kernl.js +73 -10
  73. package/dist/lib/env.d.ts +3 -3
  74. package/dist/lib/env.js +1 -1
  75. package/dist/mcp/__tests__/integration.test.js +8 -8
  76. package/dist/mcp/__tests__/utils.test.js +6 -6
  77. package/dist/mcp/http.d.ts +1 -1
  78. package/dist/mcp/http.d.ts.map +1 -1
  79. package/dist/mcp/http.js +9 -9
  80. package/dist/mcp/sse.d.ts +1 -1
  81. package/dist/mcp/sse.d.ts.map +1 -1
  82. package/dist/mcp/sse.js +7 -7
  83. package/dist/mcp/utils.d.ts +1 -1
  84. package/dist/mcp/utils.d.ts.map +1 -1
  85. package/dist/mcp/utils.js +4 -5
  86. package/dist/storage/__tests__/in-memory.test.d.ts +2 -0
  87. package/dist/storage/__tests__/in-memory.test.d.ts.map +1 -0
  88. package/dist/storage/__tests__/in-memory.test.js +455 -0
  89. package/dist/storage/base.d.ts +64 -0
  90. package/dist/storage/base.d.ts.map +1 -0
  91. package/dist/storage/base.js +4 -0
  92. package/dist/storage/in-memory.d.ts +62 -0
  93. package/dist/storage/in-memory.d.ts.map +1 -0
  94. package/dist/storage/in-memory.js +283 -0
  95. package/dist/storage/index.d.ts +10 -0
  96. package/dist/storage/index.d.ts.map +1 -0
  97. package/dist/storage/index.js +7 -0
  98. package/dist/storage/thread.d.ts +123 -0
  99. package/dist/storage/thread.d.ts.map +1 -0
  100. package/dist/storage/thread.js +4 -0
  101. package/dist/task.d.ts +5 -3
  102. package/dist/task.d.ts.map +1 -1
  103. package/dist/task.js +10 -8
  104. package/dist/thread/__tests__/fixtures/mock-model.d.ts +1 -2
  105. package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -1
  106. package/dist/thread/__tests__/integration.test.js +6 -6
  107. package/dist/thread/__tests__/namespace.test.d.ts +2 -0
  108. package/dist/thread/__tests__/namespace.test.d.ts.map +1 -0
  109. package/dist/thread/__tests__/namespace.test.js +131 -0
  110. package/dist/thread/__tests__/thread-persistence.test.d.ts +2 -0
  111. package/dist/thread/__tests__/thread-persistence.test.d.ts.map +1 -0
  112. package/dist/thread/__tests__/thread-persistence.test.js +351 -0
  113. package/dist/thread/__tests__/thread.test.js +49 -51
  114. package/dist/thread/thread.d.ts +70 -18
  115. package/dist/thread/thread.d.ts.map +1 -1
  116. package/dist/thread/thread.js +211 -73
  117. package/dist/thread/utils.d.ts +36 -8
  118. package/dist/thread/utils.d.ts.map +1 -1
  119. package/dist/thread/utils.js +52 -8
  120. package/dist/tool/__tests__/fixtures.js +1 -1
  121. package/dist/tool/__tests__/toolkit.test.js +15 -12
  122. package/dist/tool/tool.js +3 -3
  123. package/dist/types/kernl.d.ts +42 -0
  124. package/dist/types/kernl.d.ts.map +1 -0
  125. package/dist/types/thread.d.ts +108 -22
  126. package/dist/types/thread.d.ts.map +1 -1
  127. package/dist/types/thread.js +12 -0
  128. package/package.json +11 -7
  129. package/src/agent/__tests__/concurrency.test.ts +194 -0
  130. package/src/agent/__tests__/run.test.ts +441 -0
  131. package/src/agent/index.ts +0 -0
  132. package/src/agent.ts +139 -24
  133. package/src/api/__tests__/cursor-page.test.ts +512 -0
  134. package/src/api/__tests__/offset-page.test.ts +624 -0
  135. package/src/api/__tests__/threads.test.ts +415 -0
  136. package/src/api/models/index.ts +6 -0
  137. package/src/api/models/thread.ts +138 -0
  138. package/src/api/pagination/base.ts +79 -0
  139. package/src/api/pagination/cursor.ts +86 -0
  140. package/src/api/pagination/offset.ts +89 -0
  141. package/src/api/resources/threads/events.ts +26 -0
  142. package/src/api/resources/threads/index.ts +9 -0
  143. package/src/api/resources/threads/threads.ts +256 -0
  144. package/src/api/resources/threads/types.ts +143 -0
  145. package/src/api/resources/threads/utils.ts +104 -0
  146. package/src/context.ts +10 -1
  147. package/src/index.ts +49 -1
  148. package/src/internal.ts +15 -0
  149. package/src/kernl.ts +86 -17
  150. package/src/mcp/__tests__/integration.test.ts +8 -9
  151. package/src/mcp/__tests__/utils.test.ts +6 -6
  152. package/src/mcp/http.ts +9 -9
  153. package/src/mcp/sse.ts +7 -7
  154. package/src/mcp/utils.ts +6 -5
  155. package/src/storage/__tests__/in-memory.test.ts +534 -0
  156. package/src/storage/base.ts +77 -0
  157. package/src/storage/in-memory.ts +372 -0
  158. package/src/storage/index.ts +21 -0
  159. package/src/storage/thread.ts +141 -0
  160. package/src/task.ts +12 -10
  161. package/src/thread/__tests__/fixtures/mock-model.ts +2 -4
  162. package/src/thread/__tests__/integration.test.ts +13 -12
  163. package/src/thread/__tests__/namespace.test.ts +158 -0
  164. package/src/thread/__tests__/thread-persistence.test.ts +367 -0
  165. package/src/thread/__tests__/thread.test.ts +52 -54
  166. package/src/thread/thread.ts +247 -96
  167. package/src/thread/utils.ts +76 -13
  168. package/src/tool/__tests__/fixtures.ts +1 -1
  169. package/src/tool/__tests__/toolkit.test.ts +15 -12
  170. package/src/tool/tool.ts +3 -3
  171. package/src/types/kernl.ts +51 -0
  172. package/src/types/thread.ts +139 -25
  173. package/vitest.config.ts +1 -0
  174. package/dist/env.d.ts +0 -45
  175. package/dist/env.d.ts.map +0 -1
  176. package/dist/env.js +0 -31
  177. package/dist/error.d.ts +0 -1
  178. package/dist/error.d.ts.map +0 -1
  179. package/dist/kernel.d.ts +0 -7
  180. package/dist/kernel.d.ts.map +0 -1
  181. package/dist/kernel.js +0 -7
  182. package/dist/lib/serde/__tests__/codec.test.d.ts +0 -2
  183. package/dist/lib/serde/__tests__/codec.test.d.ts.map +0 -1
  184. package/dist/lib/serde/__tests__/codec.test.js +0 -75
  185. package/dist/lib/serde/codec.d.ts +0 -12
  186. package/dist/lib/serde/codec.d.ts.map +0 -1
  187. package/dist/lib/serde/codec.js +0 -54
  188. package/dist/lib/serde/thread.d.ts +0 -1
  189. package/dist/lib/serde/thread.d.ts.map +0 -1
  190. package/dist/lib/serde/thread.js +0 -172
  191. package/dist/lib/serde/tool.d.ts +0 -36
  192. package/dist/lib/serde/tool.d.ts.map +0 -1
  193. package/dist/lib/utils.d.ts +0 -19
  194. package/dist/lib/utils.d.ts.map +0 -1
  195. package/dist/lib/utils.js +0 -41
  196. package/dist/logger.d.ts +0 -36
  197. package/dist/logger.d.ts.map +0 -1
  198. package/dist/logger.js +0 -43
  199. package/dist/mcp/__tests__/fixtures/echo-server.d.ts +0 -3
  200. package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +0 -1
  201. package/dist/mcp/__tests__/fixtures/echo-server.js +0 -92
  202. package/dist/mcp/__tests__/fixtures/math-server.d.ts +0 -3
  203. package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +0 -1
  204. package/dist/mcp/__tests__/fixtures/math-server.js +0 -98
  205. package/dist/mcp/__tests__/fixtures/test-server.d.ts +0 -3
  206. package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +0 -1
  207. package/dist/mcp/__tests__/fixtures/test-server.js +0 -163
  208. package/dist/mcp/__tests__/test-utils.d.ts +0 -17
  209. package/dist/mcp/__tests__/test-utils.d.ts.map +0 -1
  210. package/dist/mcp/__tests__/test-utils.js +0 -42
  211. package/dist/mcp/node.d.ts +0 -60
  212. package/dist/mcp/node.d.ts.map +0 -1
  213. package/dist/mcp/node.js +0 -297
  214. package/dist/model.d.ts +0 -175
  215. package/dist/model.d.ts.map +0 -1
  216. package/dist/providers/ai.d.ts +0 -1
  217. package/dist/providers/ai.d.ts.map +0 -1
  218. package/dist/providers/ai.js +0 -1
  219. package/dist/providers/default.d.ts +0 -16
  220. package/dist/providers/default.d.ts.map +0 -1
  221. package/dist/providers/default.js +0 -17
  222. package/dist/providers/registry.d.ts +0 -1
  223. package/dist/providers/registry.d.ts.map +0 -1
  224. package/dist/providers/registry.js +0 -1
  225. package/dist/sched/scheduler.d.ts +0 -20
  226. package/dist/sched/scheduler.d.ts.map +0 -1
  227. package/dist/sched/task.d.ts +0 -92
  228. package/dist/sched/task.d.ts.map +0 -1
  229. package/dist/sched/task.js +0 -102
  230. package/dist/serde/__tests__/codec.test.d.ts +0 -2
  231. package/dist/serde/__tests__/codec.test.d.ts.map +0 -1
  232. package/dist/serde/__tests__/codec.test.js +0 -75
  233. package/dist/serde/codec.d.ts +0 -12
  234. package/dist/serde/codec.d.ts.map +0 -1
  235. package/dist/serde/codec.js +0 -54
  236. package/dist/serde/json.d.ts +0 -8
  237. package/dist/serde/json.d.ts.map +0 -1
  238. package/dist/serde/json.js +0 -13
  239. package/dist/serde/thread.d.ts +0 -687
  240. package/dist/serde/thread.d.ts.map +0 -1
  241. package/dist/serde/thread.js +0 -158
  242. package/dist/serde/tool.d.ts +0 -36
  243. package/dist/serde/tool.d.ts.map +0 -1
  244. package/dist/session.d.ts +0 -1
  245. package/dist/session.d.ts.map +0 -1
  246. package/dist/session.js +0 -1
  247. package/dist/thread/__tests__/stream.test.d.ts +0 -2
  248. package/dist/thread/__tests__/stream.test.d.ts.map +0 -1
  249. package/dist/thread/__tests__/stream.test.js +0 -244
  250. package/dist/tool/mcp.d.ts +0 -75
  251. package/dist/tool/mcp.d.ts.map +0 -1
  252. package/dist/tool/mcp.js +0 -111
  253. package/dist/tools.d.ts +0 -362
  254. package/dist/tools.d.ts.map +0 -1
  255. package/dist/tools.js +0 -220
  256. package/dist/types/proto.d.ts +0 -1551
  257. package/dist/types/proto.d.ts.map +0 -1
  258. package/dist/types/proto.js +0 -531
  259. package/dist/usage.d.ts +0 -43
  260. package/dist/usage.d.ts.map +0 -1
  261. package/dist/usage.js +0 -61
  262. package/src/lib/serde/thread.ts +0 -188
  263. /package/dist/{error.js → agent/index.js} +0 -0
  264. /package/dist/{lib/serde/tool.js → api/models/index.js} +0 -0
  265. /package/dist/{model.js → api/models/thread.js} +0 -0
  266. /package/dist/{sched/scheduler.js → api/resources/threads/types.js} +0 -0
  267. /package/dist/{serde/tool.js → types/kernl.js} +0 -0
@@ -47,23 +47,21 @@ describe("Thread", () => {
47
47
  model,
48
48
  });
49
49
  const kernl = new Kernl();
50
- const thread = new Thread(kernl, agent, userMessage("Hello world"));
50
+ const thread = new Thread({ agent, input: userMessage("Hello world") });
51
51
  const result = await thread.execute();
52
52
  // Access private history via type assertion for testing
53
53
  const history = thread.history;
54
54
  expect(history).toEqual([
55
- {
55
+ expect.objectContaining({
56
56
  kind: "message",
57
- id: expect.any(String),
58
57
  role: "user",
59
58
  content: [{ kind: "text", text: "Hello world" }],
60
- },
61
- {
59
+ }),
60
+ expect.objectContaining({
62
61
  kind: "message",
63
- id: "msg_1",
64
62
  role: "assistant",
65
63
  content: [{ kind: "text", text: "Hello world" }],
66
- },
64
+ }),
67
65
  ]);
68
66
  expect(thread._tick).toBe(1);
69
67
  });
@@ -94,16 +92,15 @@ describe("Thread", () => {
94
92
  model,
95
93
  });
96
94
  const kernl = new Kernl();
97
- const thread = new Thread(kernl, agent, userMessage("Test input"));
95
+ const thread = new Thread({ agent, input: userMessage("Test input") });
98
96
  await thread.execute();
99
97
  const history = thread.history;
100
98
  const firstMessage = history[0];
101
- expect(firstMessage).toEqual({
99
+ expect(firstMessage).toEqual(expect.objectContaining({
102
100
  kind: "message",
103
- id: expect.any(String),
104
101
  role: "user",
105
102
  content: [{ kind: "text", text: "Test input" }],
106
- });
103
+ }));
107
104
  });
108
105
  it("should use array input as-is", async () => {
109
106
  const model = createMockModel(async (req) => {
@@ -140,11 +137,15 @@ describe("Thread", () => {
140
137
  },
141
138
  ];
142
139
  const kernl = new Kernl();
143
- const thread = new Thread(kernl, agent, events);
140
+ const thread = new Thread({ agent, input: events });
144
141
  await thread.execute();
145
142
  const history = thread.history;
146
143
  const firstMessage = history[0];
147
- expect(firstMessage).toEqual(events[0]);
144
+ expect(firstMessage).toEqual(expect.objectContaining({
145
+ kind: events[0].kind,
146
+ role: events[0].role,
147
+ content: events[0].content,
148
+ }));
148
149
  });
149
150
  });
150
151
  describe("Multi-Turn Execution", () => {
@@ -214,48 +215,45 @@ describe("Thread", () => {
214
215
  ],
215
216
  });
216
217
  const kernl = new Kernl();
217
- const thread = new Thread(kernl, agent, userMessage("Use the echo tool"));
218
+ const thread = new Thread({ agent, input: userMessage("Use the echo tool") });
218
219
  const result = await thread.execute();
219
220
  const history = thread.history;
220
221
  expect(history).toEqual([
221
222
  // Initial user message
222
- {
223
+ expect.objectContaining({
223
224
  kind: "message",
224
- id: expect.any(String),
225
225
  role: "user",
226
226
  content: [{ kind: "text", text: "Use the echo tool" }],
227
- },
227
+ }),
228
228
  // Assistant message (tick 1)
229
- {
229
+ expect.objectContaining({
230
230
  kind: "message",
231
- id: "msg_1",
232
231
  role: "assistant",
233
232
  content: [],
234
- },
233
+ }),
235
234
  // Tool call (tick 1)
236
- {
235
+ expect.objectContaining({
237
236
  kind: "tool-call",
238
237
  toolId: "echo",
239
238
  callId: "call_1",
240
239
  state: IN_PROGRESS,
241
240
  arguments: JSON.stringify({ text: "test" }),
242
- },
241
+ }),
243
242
  // Tool result (executed after tick 1)
244
- {
243
+ expect.objectContaining({
245
244
  kind: "tool-result",
246
245
  callId: "call_1",
247
246
  toolId: "echo",
248
247
  state: COMPLETED,
249
248
  result: "Echo: test",
250
249
  error: null,
251
- },
250
+ }),
252
251
  // Final assistant message (tick 2)
253
- {
252
+ expect.objectContaining({
254
253
  kind: "message",
255
- id: "msg_2",
256
254
  role: "assistant",
257
255
  content: [{ kind: "text", text: "Done!" }],
258
- },
256
+ }),
259
257
  ]);
260
258
  expect(thread._tick).toBe(2);
261
259
  });
@@ -349,7 +347,7 @@ describe("Thread", () => {
349
347
  ],
350
348
  });
351
349
  const kernl = new Kernl();
352
- const thread = new Thread(kernl, agent, userMessage("Start"));
350
+ const thread = new Thread({ agent, input: userMessage("Start") });
353
351
  const result = await thread.execute();
354
352
  const history = thread.history;
355
353
  // Should have: 1 user msg + 3 assistant msgs + 2 tool calls + 2 tool results = 8 events
@@ -416,19 +414,19 @@ describe("Thread", () => {
416
414
  toolkits: [], // No tools available
417
415
  });
418
416
  const kernl = new Kernl();
419
- const thread = new Thread(kernl, agent, userMessage("test"));
417
+ const thread = new Thread({ agent, input: userMessage("test") });
420
418
  await thread.execute();
421
419
  const history = thread.history;
422
420
  // Check that the tool result is an error
423
421
  const toolResult = history.find((e) => e.kind === "tool-result");
424
- expect(toolResult).toEqual({
422
+ expect(toolResult).toEqual(expect.objectContaining({
425
423
  kind: "tool-result",
426
424
  callId: "call_1",
427
425
  toolId: "nonexistent",
428
426
  state: FAILED,
429
427
  result: undefined,
430
428
  error: "Tool nonexistent not found",
431
- });
429
+ }));
432
430
  });
433
431
  it("should handle tool execution error", async () => {
434
432
  let callCount = 0;
@@ -498,7 +496,7 @@ describe("Thread", () => {
498
496
  ],
499
497
  });
500
498
  const kernl = new Kernl();
501
- const thread = new Thread(kernl, agent, userMessage("test"));
499
+ const thread = new Thread({ agent, input: userMessage("test") });
502
500
  await thread.execute();
503
501
  const history = thread.history;
504
502
  const toolResult = history.find((e) => e.kind === "tool-result");
@@ -575,19 +573,19 @@ describe("Thread", () => {
575
573
  toolkits: [new FunctionToolkit({ id: "test-tools", tools: [addTool] })],
576
574
  });
577
575
  const kernl = new Kernl();
578
- const thread = new Thread(kernl, agent, userMessage("Add 5 and 3"));
576
+ const thread = new Thread({ agent, input: userMessage("Add 5 and 3") });
579
577
  await thread.execute();
580
578
  // @ts-expect-error
581
579
  const history = thread.history;
582
580
  const toolResult = history.find((e) => e.kind === "tool-result");
583
- expect(toolResult).toEqual({
581
+ expect(toolResult).toEqual(expect.objectContaining({
584
582
  kind: "tool-result",
585
583
  callId: "call_1",
586
584
  toolId: "add",
587
585
  state: COMPLETED,
588
586
  result: 8,
589
587
  error: null,
590
- });
588
+ }));
591
589
  });
592
590
  });
593
591
  describe("Parallel Tool Execution", () => {
@@ -670,29 +668,29 @@ describe("Thread", () => {
670
668
  ],
671
669
  });
672
670
  const kernl = new Kernl();
673
- const thread = new Thread(kernl, agent, userMessage("test"));
671
+ const thread = new Thread({ agent, input: userMessage("test") });
674
672
  await thread.execute();
675
673
  const history = thread.history;
676
674
  // Should have both tool results in history
677
675
  const toolResults = history.filter((e) => e.kind === "tool-result");
678
676
  expect(toolResults).toHaveLength(2);
679
677
  expect(toolResults).toEqual(expect.arrayContaining([
680
- {
678
+ expect.objectContaining({
681
679
  kind: "tool-result",
682
680
  callId: "call_1",
683
681
  toolId: "tool1",
684
682
  state: COMPLETED,
685
683
  result: "Tool1: a",
686
684
  error: null,
687
- },
688
- {
685
+ }),
686
+ expect.objectContaining({
689
687
  kind: "tool-result",
690
688
  callId: "call_2",
691
689
  toolId: "tool2",
692
690
  state: COMPLETED,
693
691
  result: "Tool2: b",
694
692
  error: null,
695
- },
693
+ }),
696
694
  ]));
697
695
  });
698
696
  });
@@ -761,7 +759,7 @@ describe("Thread", () => {
761
759
  ],
762
760
  });
763
761
  const kernl = new Kernl();
764
- const thread = new Thread(kernl, agent, userMessage("test"));
762
+ const thread = new Thread({ agent, input: userMessage("test") });
765
763
  const result = await thread.execute();
766
764
  expect(thread._tick).toBe(3);
767
765
  });
@@ -829,7 +827,7 @@ describe("Thread", () => {
829
827
  ],
830
828
  });
831
829
  const kernl = new Kernl();
832
- const thread = new Thread(kernl, agent, userMessage("test"));
830
+ const thread = new Thread({ agent, input: userMessage("test") });
833
831
  const result = await thread.execute();
834
832
  // Verify the thread executed both turns
835
833
  expect(thread._tick).toBe(2);
@@ -864,7 +862,7 @@ describe("Thread", () => {
864
862
  model,
865
863
  });
866
864
  const kernl = new Kernl();
867
- const thread = new Thread(kernl, agent, userMessage("test"));
865
+ const thread = new Thread({ agent, input: userMessage("test") });
868
866
  const result = await thread.execute();
869
867
  expect(thread._tick).toBe(1);
870
868
  });
@@ -932,7 +930,7 @@ describe("Thread", () => {
932
930
  ],
933
931
  });
934
932
  const kernl = new Kernl();
935
- const thread = new Thread(kernl, agent, userMessage("test"));
933
+ const thread = new Thread({ agent, input: userMessage("test") });
936
934
  const result = await thread.execute();
937
935
  // Should have made 2 calls - first with tool, second without
938
936
  expect(thread._tick).toBe(2);
@@ -967,7 +965,7 @@ describe("Thread", () => {
967
965
  responseType: "text",
968
966
  });
969
967
  const kernl = new Kernl();
970
- const thread = new Thread(kernl, agent, userMessage("test"));
968
+ const thread = new Thread({ agent, input: userMessage("test") });
971
969
  const result = await thread.execute();
972
970
  expect(result.response).toBe("Hello, world!");
973
971
  expect(thread._tick).toBe(1);
@@ -1010,7 +1008,7 @@ describe("Thread", () => {
1010
1008
  responseType: responseSchema,
1011
1009
  });
1012
1010
  const kernl = new Kernl();
1013
- const thread = new Thread(kernl, agent, userMessage("test"));
1011
+ const thread = new Thread({ agent, input: userMessage("test") });
1014
1012
  const result = await thread.execute();
1015
1013
  expect(result.response).toEqual({
1016
1014
  name: "Alice",
@@ -1054,7 +1052,7 @@ describe("Thread", () => {
1054
1052
  responseType: responseSchema,
1055
1053
  });
1056
1054
  const kernl = new Kernl();
1057
- const thread = new Thread(kernl, agent, userMessage("test"));
1055
+ const thread = new Thread({ agent, input: userMessage("test") });
1058
1056
  await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
1059
1057
  });
1060
1058
  it("should throw ModelBehaviorError when JSON doesn't match schema", async () => {
@@ -1094,7 +1092,7 @@ describe("Thread", () => {
1094
1092
  responseType: responseSchema,
1095
1093
  });
1096
1094
  const kernl = new Kernl();
1097
- const thread = new Thread(kernl, agent, userMessage("test"));
1095
+ const thread = new Thread({ agent, input: userMessage("test") });
1098
1096
  await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
1099
1097
  });
1100
1098
  it("should throw ModelBehaviorError when required fields are missing", async () => {
@@ -1135,7 +1133,7 @@ describe("Thread", () => {
1135
1133
  responseType: responseSchema,
1136
1134
  });
1137
1135
  const kernl = new Kernl();
1138
- const thread = new Thread(kernl, agent, userMessage("test"));
1136
+ const thread = new Thread({ agent, input: userMessage("test") });
1139
1137
  await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
1140
1138
  });
1141
1139
  it("should handle nested structured output", async () => {
@@ -1189,7 +1187,7 @@ describe("Thread", () => {
1189
1187
  responseType: responseSchema,
1190
1188
  });
1191
1189
  const kernl = new Kernl();
1192
- const thread = new Thread(kernl, agent, userMessage("test"));
1190
+ const thread = new Thread({ agent, input: userMessage("test") });
1193
1191
  const result = await thread.execute();
1194
1192
  expect(result.response).toEqual({
1195
1193
  user: {
@@ -1250,7 +1248,7 @@ describe("Thread", () => {
1250
1248
  responseType: "text",
1251
1249
  });
1252
1250
  const kernl = new Kernl();
1253
- const thread = new Thread(kernl, agent, userMessage("test"));
1251
+ const thread = new Thread({ agent, input: userMessage("test") });
1254
1252
  const result = await thread.execute();
1255
1253
  // Should have made 2 calls
1256
1254
  expect(callCount).toBe(2);
@@ -1,30 +1,71 @@
1
- import { Kernl } from "../kernl";
2
1
  import { Agent } from "../agent";
3
2
  import { Context } from "../context";
4
3
  import type { Task } from "../task";
4
+ import type { ResolvedAgentResponse } from "../guardrail";
5
5
  import { LanguageModel } from "@kernl-sdk/protocol";
6
- import type { ThreadEvent, ThreadOptions, ThreadExecuteResult, ThreadState, ThreadStreamEvent } from "../types/thread";
6
+ import type { ThreadEvent, ThreadState, ThreadOptions, ThreadEventInner, ThreadStreamEvent, ThreadExecuteResult } from "../types/thread";
7
7
  import type { AgentResponseType } from "../types/agent";
8
- import type { ResolvedAgentResponse } from "../guardrail";
9
8
  /**
10
9
  * A thread drives the execution loop for an agent.
10
+ *
11
+ * Ground principles:
12
+ *
13
+ * 1) Event log is source of truth.
14
+ * - Persistent storage (e.g. Postgres) is treated as an append-only per-thread log of `ThreadEvent`s:
15
+ * monotonic `seq`, no gaps, no updates/deletes.
16
+ * - `Thread.state`, `tick`, etc. are projections of that log, not an alternative source of truth.
17
+ *
18
+ * 2) Single writer per thread.
19
+ * - At most one executor is allowed for a given `tid` at a time.
20
+ * - Callers are responsible for enforcing this (e.g. locking/versioning) so two processes cannot
21
+ * interleave or race on `seq` or state.
22
+ *
23
+ * 3) Persist before use / observation.
24
+ * - Before an event can:
25
+ * - influence a future tick (i.e. be part of `history` fed back into the model), or
26
+ * - be considered “delivered” to a client,
27
+ * it SHOULD be durably written to storage when storage is configured.
28
+ *
29
+ * 4) Transaction boundaries match semantic steps.
30
+ * - The intended strategy is to buffer within a tick, then atomically persist all new events + state
31
+ * at the end of `tick()`.
32
+ * - After a crash, you only ever see whole ticks or none, never half a tick, from the store’s
33
+ * point of view.
34
+ *
35
+ * 5) Recovery is replay.
36
+ * - On restart, callers rebuild a `Thread` from the stored event log (plus optional snapshots).
37
+ * - Any incomplete tick or pending tool call is handled by a clear, deterministic policy at a
38
+ * higher layer (e.g. re-run, mark failed, or require manual intervention).
39
+ *
40
+ * On storage failures:
41
+ *
42
+ * “If storage is configured, it is authoritative” → fail hard on persist errors rather than
43
+ * treating persistence as best-effort.
44
+ *
45
+ * If a storage implementation is present, `persist(...)` is expected to throw on failure, and
46
+ * that error should bubble out of `_execute()` / `stream()` and stop the thread.
11
47
  */
12
48
  export declare class Thread<TContext = unknown, TResponse extends AgentResponseType = "text"> {
13
- private kernl;
14
- readonly id: string;
49
+ readonly tid: string;
50
+ readonly namespace: string;
15
51
  readonly agent: Agent<TContext, TResponse>;
16
52
  readonly context: Context<TContext>;
17
53
  readonly model: LanguageModel;
18
54
  readonly parent: Task<TContext> | null;
19
- readonly mode: "blocking" | "stream";
20
- readonly input: ThreadEvent[];
55
+ readonly createdAt: Date;
56
+ readonly updatedAt: Date;
57
+ readonly metadata: Record<string, unknown> | null;
21
58
  _tick: number;
59
+ _seq: number;
22
60
  state: ThreadState;
61
+ private cpbuf;
62
+ private persisted;
23
63
  private history;
24
64
  private abort?;
25
- constructor(kernl: Kernl, agent: Agent<TContext, TResponse>, input: ThreadEvent[], options?: ThreadOptions<TContext>);
65
+ private storage?;
66
+ constructor(options: ThreadOptions<TContext, TResponse>);
26
67
  /**
27
- * Blocking execution loop - runs until terminal state or interruption
68
+ * Blocking execution - runs until terminal state or interruption
28
69
  */
29
70
  execute(): Promise<ThreadExecuteResult<ResolvedAgentResponse<TResponse>>>;
30
71
  /**
@@ -32,15 +73,7 @@ export declare class Thread<TContext = unknown, TResponse extends AgentResponseT
32
73
  */
33
74
  stream(): AsyncIterable<ThreadStreamEvent>;
34
75
  /**
35
- * Cancel the running thread
36
- */
37
- cancel(): void;
38
- /**
39
- * Append a new event to the thread history
40
- */
41
- append(event: ThreadEvent): void;
42
- /**
43
- * Main execution loop - always yields events, callers can propagate or discard (as in execute())
76
+ * Main execution loop - always yields events, callers can propagate or discard.
44
77
  *
45
78
  * NOTE: Streaming structured output deferred for now. Prioritizing correctness + simplicity,
46
79
  * and unclear what use cases there would actually be for streaming a structured output (other than maybe gen UI).
@@ -53,6 +86,25 @@ export declare class Thread<TContext = unknown, TResponse extends AgentResponseT
53
86
  * For now, we stream text-delta and tool events, final validation happens in _execute().
54
87
  */
55
88
  private tick;
89
+ /**
90
+ * Persist current thread state to storage.
91
+ *
92
+ * - If storage is configured, it is authoritative - failures throw and halt execution.
93
+ * - No-op if storage is not configured.
94
+ */
95
+ private checkpoint;
96
+ /**
97
+ * Append one or more items to history + enrich w/ runtime headers.
98
+ *
99
+ * Core rule:
100
+ *
101
+ * > An event becomes a ThreadEvent (and gets seq/timestamp) exactly when it is appended to history. <
102
+ */
103
+ append(...items: ThreadEventInner[]): ThreadEvent[];
104
+ /**
105
+ * Cancel the running thread
106
+ */
107
+ cancel(): void;
56
108
  /**
57
109
  * Perform the actions returned by the model
58
110
  */
@@ -1 +1 @@
1
- {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,OAAO,EAEL,aAAa,EAMd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAEV,WAAW,EACX,aAAa,EACb,mBAAmB,EAEnB,WAAW,EACX,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AASzD;;GAEG;AACH,qBAAa,MAAM,CACjB,QAAQ,GAAG,OAAO,EAClB,SAAS,SAAS,iBAAiB,GAAG,MAAM;IAE5C,OAAO,CAAC,KAAK,CAAQ;IAErB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC;IAI9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,OAAO,CAAsE;IACrF,OAAO,CAAC,KAAK,CAAC,CAAkB;gBAG9B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,EACjC,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC;IAgBnC;;OAEG;IACG,OAAO,IAAI,OAAO,CACtB,mBAAmB,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,CACtD;IAcD;;OAEG;IACI,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC;IAkBjD;;OAEG;IACH,MAAM;IAIN;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAIhC;;;;;OAKG;YACY,QAAQ;IAiDvB;;;;;OAKG;YACY,IAAI;IAwBnB;;OAEG;YACW,cAAc;IA6C5B;;;;OAIG;YACW,YAAY;IA4C1B;;OAEG;YACW,mBAAmB;CA2ClC"}
1
+ {"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKzD,OAAO,EAML,aAAa,EAGd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAEV,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,MAAM,CACjB,QAAQ,GAAG,OAAO,EAClB,SAAS,SAAS,iBAAiB,GAAG,MAAM;IAE5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAIlD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,OAAO,CAAwE;IAEvF,OAAO,CAAC,KAAK,CAAC,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAC,CAAc;gBAElB,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC;IA+BvD;;OAEG;IACG,OAAO,IAAI,OAAO,CACtB,mBAAmB,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,CACtD;IAoBD;;OAEG;IACI,MAAM,IAAI,aAAa,CAAC,iBAAiB,CAAC;IAuBjD;;;;;OAKG;YACY,QAAQ;IAgEvB;;;;;OAKG;YACY,IAAI;IA8BnB;;;;;OAKG;YACW,UAAU;IAuCxB;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,KAAK,EAAE,gBAAgB,EAAE,GAAG,WAAW,EAAE;IAiBnD;;OAEG;IACH,MAAM;IAQN;;OAEG;YACW,cAAc;IAyC5B;;;;OAIG;YACW,YAAY;IA2C1B;;OAEG;YACW,mBAAmB;CA2ClC"}