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