@ekairos/thread 1.22.1 → 1.22.2

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 (2) hide show
  1. package/README.md +355 -273
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,45 +1,10 @@
1
- # @ekairos/thread
1
+ # @ekairos/thread
2
2
 
3
- Durable AI threads for production apps.
3
+ Durable thread engine for Workflow-compatible AI agents.
4
4
 
5
- `@ekairos/thread` gives you an execution model that is:
5
+ `@ekairos/thread` is the execution layer used by Ekairos agents. It persists context, items, steps, parts, and executions, while streaming UI chunks and enforcing transition contracts.
6
6
 
7
- - workflow-compatible,
8
- - persistence-first,
9
- - traceable by design,
10
- - simple to embed in domain applications.
11
-
12
- It is the runtime used by Ekairos coding agents and domain agents.
13
-
14
- ## Why Thread
15
-
16
- Most chat abstractions stop at "messages in, text out".
17
- Thread models the full lifecycle:
18
-
19
- 1. Persist trigger event.
20
- 2. Create execution.
21
- 3. Run model reaction.
22
- 4. Persist normalized parts.
23
- 5. Execute actions (tools).
24
- 6. Persist tool outcomes.
25
- 7. Decide continue or end.
26
- 8. Emit traces for every durable step.
27
-
28
- This design supports long-running, resumable agent runs without losing state.
29
-
30
- ## Core Concepts
31
-
32
- - `Thread`: durable loop orchestrator.
33
- - `Reactor`: pluggable reaction implementation (`AI SDK`, `Codex`, `Claude`, `Cursor`, ...).
34
- - `Thread Key`: stable public identifier (`thread.key`) for continuity.
35
- - `Context`: typed persistent state attached to a thread.
36
- - `Item`: normalized event (`input_text`, `output_text`, etc).
37
- - `Execution`: one run for a trigger/reaction pair.
38
- - `Step`: one loop iteration inside an execution.
39
- - `Part`: normalized content fragment persisted by step.
40
- - `Trace`: machine timeline (`thread.*`, `workflow.*`) for observability.
41
-
42
- ## Installation
7
+ ## Install
43
8
 
44
9
  ```bash
45
10
  pnpm add @ekairos/thread
@@ -54,310 +19,427 @@ Optional subpaths:
54
19
  - `@ekairos/thread/mcp`
55
20
  - `@ekairos/thread/oidc`
56
21
 
57
- ## Quick Start
58
-
59
- ### 1) Configure app runtime once
60
-
61
- Thread resolves persistence through runtime.
62
- Do this once in app bootstrap (`src/ekairos.ts`):
22
+ ## Package Surface (from `src/index.ts`)
23
+
24
+ ### Core builders and engine
25
+
26
+ - `createThread`
27
+ - `thread`
28
+ - `Thread`
29
+ - `type ThreadConfig`
30
+ - `type ThreadInstance`
31
+ - `type RegistrableThreadBuilder`
32
+ - `type ThreadOptions`
33
+ - `type ThreadStreamOptions`
34
+
35
+ ### Reactors
36
+
37
+ - `createAiSdkReactor`
38
+ - `createScriptedReactor`
39
+ - `type ThreadReactor`
40
+ - `type ThreadReactorParams`
41
+ - `type ThreadReactionResult`
42
+ - `type ThreadReactionToolCall`
43
+ - `type ThreadReactionLLM`
44
+ - `type CreateAiSdkReactorOptions`
45
+ - `type CreateScriptedReactorOptions`
46
+ - `type ScriptedReactorStep`
47
+
48
+ ### Contracts and transitions
49
+
50
+ - `THREAD_STATUSES`
51
+ - `THREAD_CONTEXT_STATUSES`
52
+ - `THREAD_EXECUTION_STATUSES`
53
+ - `THREAD_STEP_STATUSES`
54
+ - `THREAD_ITEM_STATUSES`
55
+ - `THREAD_ITEM_TYPES`
56
+ - `THREAD_CHANNELS`
57
+ - `THREAD_TRACE_EVENT_KINDS`
58
+ - `THREAD_STREAM_CHUNK_TYPES`
59
+ - `THREAD_CONTEXT_SUBSTATE_KEYS`
60
+ - `THREAD_THREAD_TRANSITIONS`
61
+ - `THREAD_CONTEXT_TRANSITIONS`
62
+ - `THREAD_EXECUTION_TRANSITIONS`
63
+ - `THREAD_STEP_TRANSITIONS`
64
+ - `THREAD_ITEM_TRANSITIONS`
65
+ - `can*Transition`, `assert*Transition`
66
+ - `assertThreadPartKey`
67
+
68
+ ### Stream and parsing
69
+
70
+ - `parseThreadStreamEvent`
71
+ - `assertThreadStreamTransitions`
72
+ - `validateThreadStreamTimeline`
73
+ - `type ThreadStreamEvent`
74
+ - `type ContextCreatedEvent`
75
+ - `type ContextResolvedEvent`
76
+ - `type ContextStatusChangedEvent`
77
+ - `type ThreadCreatedEvent`
78
+ - `type ThreadResolvedEvent`
79
+ - `type ThreadStatusChangedEvent`
80
+ - `type ExecutionCreatedEvent`
81
+ - `type ExecutionStatusChangedEvent`
82
+ - `type ItemCreatedEvent`
83
+ - `type ItemStatusChangedEvent`
84
+ - `type StepCreatedEvent`
85
+ - `type StepStatusChangedEvent`
86
+ - `type PartCreatedEvent`
87
+ - `type PartUpdatedEvent`
88
+ - `type ChunkEmittedEvent`
89
+ - `type ThreadFinishedEvent`
90
+
91
+ ### Event conversion helpers
92
+
93
+ - `createUserItemFromUIMessages`
94
+ - `createAssistantItemFromUIMessages`
95
+ - `convertToUIMessage`
96
+ - `convertItemToModelMessages`
97
+ - `convertItemsToModelMessages`
98
+ - `convertModelMessageToItem`
99
+ - `didToolExecute`
100
+ - `extractToolCallsFromParts`
101
+
102
+ ### React hook
103
+
104
+ - `useThread`
105
+ - `type UseThreadOptions`
106
+ - `type ThreadSnapshot`
107
+ - `type ThreadStreamChunk`
108
+
109
+ ### Registry / codex
110
+
111
+ - `registerThread`
112
+ - `getThread`
113
+ - `getThreadFactory`
114
+ - `hasThread`
115
+ - `listThreads`
116
+ - `createCodexThreadBuilder`
117
+ - codex defaults/types from `codex.ts`
118
+
119
+ ## Thread API Specification
120
+
121
+ ## `createThread`
63
122
 
64
123
  ```ts
65
- import "server-only";
66
- import { configureRuntime } from "@ekairos/domain/runtime";
67
- import { getOrgAdminDb } from "@/lib/admin-org-db";
68
- import appDomain from "@/lib/domain";
69
-
70
- export const runtimeConfig = configureRuntime({
71
- runtime: async (env: { orgId: string }) => {
72
- const db = await getOrgAdminDb(env.orgId, appDomain);
73
- return { db };
74
- },
75
- domain: { domain: appDomain },
76
- });
124
+ createThread<Env>(key: ThreadKey)
77
125
  ```
78
126
 
79
- ### 2) Define a thread
127
+ Builder stages:
80
128
 
81
- ```ts
82
- import { createThread } from "@ekairos/thread";
83
- import { tool } from "ai";
84
- import { z } from "zod";
129
+ 1. `.context((storedContext, env) => context)` (required)
130
+ 2. `.expandEvents((events, context, env) => events)` (optional)
131
+ 3. `.narrative((context, env) => string)` (required)
132
+ 4. `.actions((context, env) => Record<string, ThreadTool>)` (required)
133
+ 5. `.model(modelInit | selector)` (optional)
134
+ 6. `.reactor(reactor)` (optional, default is AI SDK reactor)
135
+ 7. `.shouldContinue(({ reactionEvent, toolCalls, toolExecutionResults, ... }) => boolean)` (optional)
136
+ 8. `.opts(threadOptions)` (optional)
85
137
 
86
- type Env = { orgId: string; sessionId: string };
87
- type Ctx = { orgId: string; sessionId: string };
138
+ Builder terminals:
88
139
 
89
- export const helloThread = createThread<Env>("hello.thread")
90
- .context(async (stored, env) => ({
91
- orgId: env.orgId,
92
- sessionId: env.sessionId,
93
- ...(stored.content ?? {}),
94
- }))
95
- .narrative((ctx) => `You are a precise assistant. Session=${ctx.content?.sessionId}`)
96
- .actions(() => ({
97
- ping: tool({
98
- description: "Return pong",
99
- inputSchema: z.object({ text: z.string().optional() }),
100
- execute: async ({ text }) => ({ pong: text ?? "ok" }),
101
- }),
102
- }))
103
- .model("openai/gpt-5.2")
104
- .build();
105
- ```
140
+ - `.build()` -> `ThreadInstance`
141
+ - `.react(triggerEvent, params)`
142
+ - `.stream(triggerEvent, params)` (deprecated alias)
143
+ - `.register()`
144
+ - `.config()`
106
145
 
107
- ### 2.1) Reactor model (new)
146
+ ### `ThreadConfig<Context, Env>`
108
147
 
109
- Thread runs through a `reactor`:
148
+ Required keys:
110
149
 
111
- - default: `createAiSdkReactor()` (included in `@ekairos/thread`)
112
- - optional: custom/provider reactor via `.reactor(...)`
150
+ - `context`
151
+ - `narrative`
152
+ - `actions` (or legacy `tools`)
113
153
 
114
- ```ts
115
- import { createThread, createAiSdkReactor } from "@ekairos/thread";
154
+ Optional keys:
116
155
 
117
- const thread = createThread<{ orgId: string }>("my.thread")
118
- .context((stored, env) => ({ ...(stored.content ?? {}), orgId: env.orgId }))
119
- .narrative(() => "System prompt")
120
- .actions(() => ({}))
121
- .reactor(createAiSdkReactor())
122
- .build();
123
- ```
156
+ - `expandEvents`
157
+ - `model`
158
+ - `reactor`
159
+ - `shouldContinue`
160
+ - `opts`
124
161
 
125
- `createAiSdkReactor` also accepts optional per-turn config hooks:
162
+ ### `Thread.react`
126
163
 
127
- ```ts
128
- import { createAiSdkReactor } from "@ekairos/thread";
164
+ Primary form:
129
165
 
130
- const reactor = createAiSdkReactor({
131
- resolveConfig: async ({ env }) => {
132
- "use step";
133
- return { model: env.model ?? "openai/gpt-5.2", maxModelSteps: 2 };
134
- },
135
- selectModel: ({ config, baseModel }) => config.model ?? baseModel,
136
- selectMaxModelSteps: ({ config, baseMaxModelSteps }) =>
137
- typeof config.maxModelSteps === "number"
138
- ? config.maxModelSteps
139
- : baseMaxModelSteps,
140
- });
166
+ ```ts
167
+ thread.react(triggerEvent, {
168
+ env,
169
+ context: { id } | { key } | null,
170
+ options,
171
+ })
141
172
  ```
142
173
 
143
- Provider reactors live in `packages/reactors/*`:
144
-
145
- - `@ekairos/openai-reactor` (`createOpenAIReactor`, `createCodexReactor`)
146
- - `@ekairos/claude-reactor` (scaffold)
147
- - `@ekairos/cursor-reactor` (scaffold)
148
-
149
- ### 3) Run from a workflow
174
+ Return shape:
150
175
 
151
176
  ```ts
152
- import { getWritable } from "workflow";
153
- import type { UIMessageChunk } from "ai";
154
- import type { ThreadItem } from "@ekairos/thread";
155
- import { helloThread } from "./hello.thread";
156
-
157
- export async function helloWorkflow(params: {
158
- env: { orgId: string; sessionId: string };
159
- triggerEvent: ThreadItem;
160
- threadKey?: string;
161
- }) {
162
- "use workflow";
163
-
164
- const writable = getWritable<UIMessageChunk>();
165
- return await helloThread.react(params.triggerEvent, {
166
- env: params.env,
167
- context: params.threadKey ? { key: params.threadKey } : null,
168
- options: { writable, maxIterations: 2, maxModelSteps: 1 },
169
- });
177
+ {
178
+ contextId: string;
179
+ context: StoredContext<Context>;
180
+ triggerEventId: string;
181
+ reactionEventId: string;
182
+ executionId: string;
170
183
  }
171
184
  ```
172
185
 
173
- ## Thread Lifecycle (Detailed)
186
+ ### `ThreadStreamOptions`
174
187
 
175
- For each `react(...)` call:
188
+ - `maxIterations?: number` (default `20`)
189
+ - `maxModelSteps?: number` (default `1`)
190
+ - `preventClose?: boolean` (default `false`)
191
+ - `sendFinish?: boolean` (default `true`)
192
+ - `silent?: boolean` (default `false`)
193
+ - `writable?: WritableStream<UIMessageChunk>`
176
194
 
177
- 1. `initializeContext` creates or loads context.
178
- 2. `saveTriggerAndCreateExecution` persists trigger and execution.
179
- 3. `createThreadStep` starts iteration record.
180
- 4. `buildSystemPrompt` and `buildTools` are evaluated.
181
- 5. `executeReaction` runs model + tool call planning.
182
- 6. `saveThreadPartsStep` persists normalized parts.
183
- 7. `saveReactionItem` or `updateItem` updates stable reaction item.
184
- 8. Tool executions run and are merged into persisted parts.
185
- 9. `shouldContinue(...)` decides next iteration or completion.
186
- 10. `completeExecution` closes run status.
195
+ ### `ThreadOptions`
187
196
 
188
- All side effects are executed through workflow-safe steps.
197
+ Lifecycle callbacks:
189
198
 
190
- ## Event and Item Model
199
+ - `onContextCreated`
200
+ - `onContextUpdated`
201
+ - `onEventCreated`
202
+ - `onToolCallExecuted`
203
+ - `onEnd`
191
204
 
192
- Key utilities:
205
+ ## Reactor Specification
193
206
 
194
- - `createUserItemFromUIMessages(...)`
195
- - `createAssistantItemFromUIMessages(...)`
196
- - `convertItemsToModelMessages(...)`
197
- - `convertModelMessageToItem(...)`
198
- - `didToolExecute(...)`
199
- - `extractToolCallsFromParts(...)`
207
+ A reactor receives the full execution context for one iteration and returns normalized assistant output + tool calls.
200
208
 
201
- This keeps a stable internal representation while remaining compatible with UI/model formats.
209
+ ### `ThreadReactorParams`
202
210
 
203
- ## Runtime and Persistence
211
+ - `env`
212
+ - `context`
213
+ - `contextIdentifier`
214
+ - `triggerEvent`
215
+ - `model`
216
+ - `systemPrompt`
217
+ - `actions`
218
+ - `toolsForModel`
219
+ - `eventId`
220
+ - `executionId`
221
+ - `contextId`
222
+ - `stepId`
223
+ - `iteration`
224
+ - `maxModelSteps`
225
+ - `sendStart`
226
+ - `silent`
227
+ - `writable`
204
228
 
205
- Thread runtime resolves from `@ekairos/domain/runtime` bootstrap.
229
+ ### `ThreadReactionResult`
206
230
 
207
- Default persistence adapter:
231
+ - `assistantEvent: ThreadItem`
232
+ - `toolCalls: ThreadReactionToolCall[]`
233
+ - `messagesForModel: ModelMessage[]`
234
+ - `llm?: ThreadReactionLLM`
208
235
 
209
- - `InstantStore` (`@ekairos/thread/instant`)
236
+ ## Built-in Reactors
210
237
 
211
- Schema:
238
+ ## `createAiSdkReactor` (production default)
212
239
 
213
- - `thread_threads`
214
- - `thread_contexts`
215
- - `thread_items`
216
- - `thread_executions`
217
- - `thread_steps`
218
- - `thread_parts`
219
- - `thread_trace_events`
220
- - `thread_trace_runs`
221
- - `thread_trace_spans`
222
-
223
- Import domain schema:
240
+ Uses AI SDK streaming + tool extraction through engine steps.
224
241
 
225
242
  ```ts
226
- import { threadDomain } from "@ekairos/thread/schema";
227
- ```
243
+ import { createAiSdkReactor } from "@ekairos/thread";
228
244
 
229
- ## Streaming
245
+ const reactor = createAiSdkReactor({
246
+ resolveConfig: async ({ env, context, iteration }) => {
247
+ "use step";
248
+ return {
249
+ model: env.model ?? "openai/gpt-5.2",
250
+ maxModelSteps: iteration === 0 ? 2 : 1,
251
+ tenant: context.content?.orgId,
252
+ };
253
+ },
254
+ selectModel: ({ baseModel, config }) => config.model ?? baseModel,
255
+ selectMaxModelSteps: ({ baseMaxModelSteps, config }) =>
256
+ typeof config.maxModelSteps === "number"
257
+ ? config.maxModelSteps
258
+ : baseMaxModelSteps,
259
+ });
260
+ ```
230
261
 
231
- Thread writes `UIMessageChunk` to workflow writable streams.
262
+ Use in thread:
232
263
 
233
- Options:
264
+ ```ts
265
+ createThread<{ orgId: string }>("support.agent")
266
+ .context((stored, env) => ({ ...stored.content, orgId: env.orgId }))
267
+ .narrative(() => "You are a precise assistant")
268
+ .actions(() => ({}))
269
+ .reactor(reactor)
270
+ .build();
271
+ ```
234
272
 
235
- - `writable`: custom stream.
236
- - `silent`: disable stream writes, keep persistence.
237
- - `preventClose`: do not close writer.
238
- - `sendFinish`: control final `finish` chunk.
273
+ ## `createScriptedReactor` (testing and deterministic local loops)
239
274
 
240
- Namespaced streams are supported using `context:<contextId>`.
275
+ No network/model calls. Returns scripted payloads per iteration.
241
276
 
242
- ## Identity Model
277
+ ```ts
278
+ import { createScriptedReactor } from "@ekairos/thread";
279
+
280
+ const reactor = createScriptedReactor({
281
+ steps: [
282
+ {
283
+ assistantEvent: {
284
+ content: { parts: [{ type: "text", text: "Deterministic answer" }] },
285
+ },
286
+ toolCalls: [],
287
+ messagesForModel: [],
288
+ },
289
+ ],
290
+ repeatLast: true,
291
+ });
292
+ ```
243
293
 
244
- - `thread.key` is the functional continuity id.
245
- - `context.id` is internal state id for typed context persistence.
246
- - A thread can own one or more contexts; default runtime behavior is one active context per thread.
294
+ Rules:
247
295
 
248
- ### Open Responses alignment
296
+ - `steps` must contain at least 1 entry.
297
+ - If all steps are consumed and `repeatLast !== true`, reactor throws.
298
+ - `assistantEvent` is normalized with fallback fields:
299
+ - `id = params.eventId`
300
+ - `type = "output_text"`
301
+ - `channel = triggerEvent.channel`
302
+ - `createdAt = now`
249
303
 
250
- Thread is protocol-aligned with Open Responses item/event semantics and keeps durable execution
251
- through Workflow.
304
+ ## Production Pattern
252
305
 
253
- - Public continuity id should be `thread.key`.
254
- - Context remains internal typed state, but can be exposed as an extension field in thread query APIs.
255
- - Safe extension pattern: include `context` object in thread payload while preserving standard fields.
306
+ ```ts
307
+ import { createThread, createAiSdkReactor } from "@ekairos/thread";
308
+ import { tool } from "ai";
309
+ import { z } from "zod";
256
310
 
257
- Example shape for a thread query response:
311
+ type Env = { orgId: string; sessionId: string };
258
312
 
259
- ```json
260
- {
261
- "object": "conversation",
262
- "id": "thread-key-or-id",
263
- "status": "completed",
264
- "context": {
265
- "id": "ctx_123",
266
- "key": "code.agent.session.abc",
267
- "status": "completed",
268
- "content": {}
269
- }
270
- }
313
+ export const supportThread = createThread<Env>("support.agent")
314
+ .context((stored, env) => ({
315
+ orgId: env.orgId,
316
+ sessionId: env.sessionId,
317
+ ...stored.content,
318
+ }))
319
+ .narrative((context) => `Assist session ${context.content?.sessionId}`)
320
+ .actions(() => ({
321
+ ping: tool({
322
+ description: "Health check",
323
+ inputSchema: z.object({ text: z.string().optional() }),
324
+ execute: async ({ text }) => ({ pong: text ?? "ok" }),
325
+ }),
326
+ }))
327
+ .reactor(createAiSdkReactor())
328
+ .shouldContinue(({ reactionEvent }) => {
329
+ const parts = reactionEvent.content?.parts ?? [];
330
+ const hasTool = parts.some((part: any) => part?.type === "tool-call");
331
+ return hasTool;
332
+ })
333
+ .build();
271
334
  ```
272
335
 
273
- This extension is additive and does not break Open Responses compatibility.
336
+ ## Testing Pattern
274
337
 
275
- ## Tracing and Observability
276
-
277
- Thread emits lifecycle traces by default through step operations.
278
-
279
- Typical namespaces:
280
-
281
- - `thread.run`
282
- - `thread.context`
283
- - `thread.execution`
284
- - `thread.step`
285
- - `thread.item`
286
- - `thread.part`
287
- - `thread.review`
288
- - `thread.llm`
289
- - `workflow.run`
290
-
291
- These traces are intended for local persistence plus optional mirror ingestion to central collectors.
292
-
293
- ## Registry API
338
+ ```ts
339
+ import { createThread, createScriptedReactor } from "@ekairos/thread";
294
340
 
295
- Register and resolve threads by key:
341
+ type Env = { orgId: string };
296
342
 
297
- ```ts
298
- import { registerThread, getThread } from "@ekairos/thread";
343
+ const testThread = createThread<Env>("thread.test")
344
+ .context((stored, env) => ({ orgId: env.orgId, ...stored.content }))
345
+ .narrative(() => "Test narrative")
346
+ .actions(() => ({}))
347
+ .reactor(
348
+ createScriptedReactor({
349
+ steps: [
350
+ {
351
+ assistantEvent: {
352
+ content: { parts: [{ type: "text", text: "ok-1" }] },
353
+ },
354
+ toolCalls: [],
355
+ messagesForModel: [],
356
+ },
357
+ ],
358
+ repeatLast: true,
359
+ }),
360
+ )
361
+ .build();
299
362
  ```
300
363
 
301
- Builder convenience:
364
+ ## Stream Contract
302
365
 
303
- ```ts
304
- const builder = createThread<Env>("my.key").context(...).narrative(...).actions(...);
305
- builder.register();
306
- ```
366
+ Thread stream events (`thread.stream.ts`) are entity-based.
307
367
 
308
- ## Preconfigured Codex Thread
368
+ Hierarchy:
309
369
 
310
- Use `@ekairos/thread/codex` to create coding threads with minimal wiring.
370
+ 1. context
371
+ 2. thread
372
+ 3. item
373
+ 4. step
374
+ 5. part
375
+ 6. chunk
311
376
 
312
- ```ts
313
- import { createCodexThreadBuilder } from "@ekairos/thread/codex";
377
+ Event types:
314
378
 
315
- const builder = createCodexThreadBuilder({
316
- key: "code.agent",
317
- context: async (stored, env) => ({ ...(stored.content ?? {}), ...env }),
318
- executeCodex: async ({ input, env }) => {
319
- // Call Codex app server here (usually in a use-step function)
320
- return {
321
- threadId: "t_123",
322
- turnId: "turn_123",
323
- assistantText: "done",
324
- reasoningText: "",
325
- diff: "",
326
- toolParts: [],
327
- };
328
- },
329
- });
330
- ```
379
+ - `context.created`
380
+ - `context.resolved`
381
+ - `context.status.changed`
382
+ - `thread.created`
383
+ - `thread.resolved`
384
+ - `thread.status.changed`
385
+ - `execution.created`
386
+ - `execution.status.changed`
387
+ - `item.created`
388
+ - `item.status.changed`
389
+ - `step.created`
390
+ - `step.status.changed`
391
+ - `part.created`
392
+ - `part.updated`
393
+ - `chunk.emitted`
394
+ - `thread.finished`
331
395
 
332
- What it configures for you:
396
+ Chunk types (`THREAD_STREAM_CHUNK_TYPES`):
333
397
 
334
- - `codex` action schema,
335
- - default model selection (`openai/gpt-5.2`),
336
- - default continue rule (`stop after codex action executes`),
337
- - default narrative fallback.
398
+ - `data-context-id`
399
+ - `data-context-substate`
400
+ - `data-thread-ping`
401
+ - `tool-output-available`
402
+ - `tool-output-error`
403
+ - `finish`
338
404
 
339
- For direct Codex runtime (without "tool indirection"), use
340
- `@ekairos/openai-reactor` + `createCodexReactor(...)`.
405
+ Validation helpers:
341
406
 
342
- ## MCP and OIDC
407
+ - `parseThreadStreamEvent(event)`
408
+ - `assertThreadStreamTransitions(event)`
409
+ - `validateThreadStreamTimeline(events)`
343
410
 
344
- Utilities are exposed for protocol integration:
411
+ ## Transition Contract
345
412
 
346
- - `@ekairos/thread/mcp`
347
- - `@ekairos/thread/oidc`
413
+ Allowed status transitions are exported as constants and enforced by assertion helpers.
414
+
415
+ - Thread: `open -> streaming -> (open | closed | failed)`, `failed -> open`
416
+ - Context: `open <-> streaming`, `(open | streaming) -> closed`
417
+ - Execution: `executing -> (completed | failed)`
418
+ - Step: `running -> (completed | failed)`
419
+ - Item: `stored -> (pending | completed)`, `pending -> completed`
348
420
 
349
- Use these from server-side API routes when exposing thread-driven tools via MCP.
421
+ ## Runtime and Schema
350
422
 
351
- ## DX Guidelines
423
+ - Import schema with `threadDomain` from `@ekairos/thread/schema`
424
+ - Store integration defaults to `InstantStore`
425
+ - Runtime must be configured via `@ekairos/domain/runtime` in host app
352
426
 
353
- - Keep `env` serializable.
354
- - Keep thread definition declarative.
355
- - Put DB/network side effects inside step functions.
356
- - Prefer `context.id` for deterministic resume.
357
- - Use explicit thread keys (`domain.agent.name` format).
427
+ Persisted entities:
358
428
 
359
- ## Breaking-Change Policy
429
+ - `thread_threads`
430
+ - `thread_contexts`
431
+ - `thread_items`
432
+ - `thread_executions`
433
+ - `thread_steps`
434
+ - `thread_parts`
435
+ - `thread_trace_events`
436
+ - `thread_trace_runs`
437
+ - `thread_trace_spans`
360
438
 
361
- Thread prioritizes runtime correctness over implicit compatibility shims.
439
+ ## Notes for Productive Usage
362
440
 
363
- When behavior conflicts with durability or protocol clarity, explicit configuration is preferred over hidden fallbacks.
441
+ - Always pass explicit `env`.
442
+ - Prefer `context: { key }` for stable continuation and `context: { id }` for deterministic resume.
443
+ - Keep IO in workflow steps.
444
+ - Use `createScriptedReactor` for deterministic regression tests and component demos.
445
+ - Validate stream timelines with `validateThreadStreamTimeline` when consuming SSE externally.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/thread",
3
- "version": "1.22.1",
3
+ "version": "1.22.2",
4
4
  "description": "Pulzar Thread - Workflow-based AI Threads",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -110,7 +110,7 @@
110
110
  },
111
111
  "dependencies": {
112
112
  "@ai-sdk/openai": "^2.0.52",
113
- "@ekairos/domain": "^1.22.1",
113
+ "@ekairos/domain": "^1.22.2",
114
114
  "@instantdb/admin": "0.22.126",
115
115
  "@instantdb/core": "0.22.126",
116
116
  "@vercel/mcp-adapter": "^1.0.0",