@kuralle-agents/core 0.12.0 → 0.14.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 (210) hide show
  1. package/README.md +89 -4
  2. package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
  3. package/dist/ai-sdk/uiMessageStream.js +42 -29
  4. package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
  5. package/dist/capabilities/LivePromptAssembler.js +2 -1
  6. package/dist/capabilities/index.d.ts +1 -1
  7. package/dist/eval/EvalRunner.js +4 -4
  8. package/dist/eval/simulation.js +4 -3
  9. package/dist/events/TurnHandle.d.ts +3 -3
  10. package/dist/flow/collectDigression.d.ts +5 -2
  11. package/dist/flow/collectDigression.js +48 -10
  12. package/dist/flow/collectUntilComplete.js +13 -5
  13. package/dist/flow/degrade.js +8 -4
  14. package/dist/flow/emitInteractive.d.ts +2 -2
  15. package/dist/flow/emitInteractive.js +6 -3
  16. package/dist/flow/extraction.d.ts +3 -2
  17. package/dist/flow/extraction.js +14 -4
  18. package/dist/flow/reduceTransition.d.ts +2 -2
  19. package/dist/flow/reduceTransition.js +7 -3
  20. package/dist/flow/runFlow.js +97 -17
  21. package/dist/foundation/AgentStateController.d.ts +1 -1
  22. package/dist/foundation/ToolExecutor.d.ts +2 -2
  23. package/dist/foundation/index.d.ts +0 -11
  24. package/dist/foundation/index.js +0 -5
  25. package/dist/index.d.ts +15 -15
  26. package/dist/index.js +9 -6
  27. package/dist/outcomes/streamPart.d.ts +2 -2
  28. package/dist/outcomes/streamPart.js +2 -3
  29. package/dist/prompts/PromptBuilder.d.ts +3 -3
  30. package/dist/prompts/PromptBuilder.js +13 -6
  31. package/dist/prompts/index.d.ts +1 -1
  32. package/dist/prompts/types.d.ts +9 -2
  33. package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
  34. package/dist/runtime/InMemoryRetrievalCache.js +59 -0
  35. package/dist/runtime/KnowledgeProvider.d.ts +3 -2
  36. package/dist/runtime/KnowledgeProvider.js +60 -31
  37. package/dist/runtime/Runtime.d.ts +47 -1
  38. package/dist/runtime/Runtime.js +273 -36
  39. package/dist/runtime/TokenAccumulator.d.ts +7 -0
  40. package/dist/runtime/TokenAccumulator.js +7 -0
  41. package/dist/runtime/TraceRecorder.d.ts +51 -0
  42. package/dist/runtime/TraceRecorder.js +326 -0
  43. package/dist/runtime/buildAgentToolSurface.js +1 -1
  44. package/dist/runtime/channels/TextDriver.js +57 -31
  45. package/dist/runtime/channels/executeModelTool.d.ts +14 -1
  46. package/dist/runtime/channels/executeModelTool.js +132 -3
  47. package/dist/runtime/channels/extractionTurn.js +1 -1
  48. package/dist/runtime/channels/index.d.ts +0 -3
  49. package/dist/runtime/channels/index.js +0 -6
  50. package/dist/runtime/channels/inputBuffer.d.ts +1 -0
  51. package/dist/runtime/channels/inputBuffer.js +9 -0
  52. package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
  53. package/dist/runtime/channels/streaming/speakGated.js +24 -12
  54. package/dist/runtime/citations/index.d.ts +3 -3
  55. package/dist/runtime/closeRun.js +14 -10
  56. package/dist/runtime/compaction.d.ts +2 -0
  57. package/dist/runtime/compaction.js +3 -2
  58. package/dist/runtime/controlFlowSignal.d.ts +24 -0
  59. package/dist/runtime/controlFlowSignal.js +29 -0
  60. package/dist/runtime/ctx.d.ts +3 -4
  61. package/dist/runtime/ctx.js +147 -60
  62. package/dist/runtime/durable/RunStore.d.ts +16 -0
  63. package/dist/runtime/durable/RunStore.js +7 -1
  64. package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
  65. package/dist/runtime/durable/SessionRunStore.js +136 -34
  66. package/dist/runtime/durable/idempotency.d.ts +1 -0
  67. package/dist/runtime/durable/idempotency.js +3 -0
  68. package/dist/runtime/durable/replay.js +7 -2
  69. package/dist/runtime/durable/types.d.ts +11 -0
  70. package/dist/runtime/goals.d.ts +18 -0
  71. package/dist/runtime/goals.js +158 -0
  72. package/dist/runtime/grounding/gather.d.ts +1 -1
  73. package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
  74. package/dist/runtime/grounding/knowledge.d.ts +1 -1
  75. package/dist/runtime/grounding/knowledge.js +1 -1
  76. package/dist/runtime/handoffContinuation.d.ts +20 -0
  77. package/dist/runtime/handoffContinuation.js +30 -0
  78. package/dist/runtime/handoffOscillation.d.ts +6 -0
  79. package/dist/runtime/handoffOscillation.js +17 -0
  80. package/dist/runtime/hostLoop.js +37 -6
  81. package/dist/runtime/index.d.ts +4 -3
  82. package/dist/runtime/index.js +2 -1
  83. package/dist/runtime/openRun.d.ts +2 -0
  84. package/dist/runtime/openRun.js +46 -17
  85. package/dist/runtime/outcomeMarking.d.ts +2 -2
  86. package/dist/runtime/outcomeMarking.js +2 -1
  87. package/dist/runtime/policies/agentTurn.d.ts +1 -1
  88. package/dist/runtime/policies/limits.d.ts +1 -0
  89. package/dist/runtime/policies/limits.js +3 -0
  90. package/dist/runtime/runHookSafely.d.ts +3 -0
  91. package/dist/runtime/runHookSafely.js +19 -0
  92. package/dist/runtime/select.d.ts +5 -1
  93. package/dist/runtime/select.js +30 -1
  94. package/dist/runtime/turnTokenUsage.d.ts +28 -0
  95. package/dist/runtime/turnTokenUsage.js +70 -0
  96. package/dist/scheduler/index.d.ts +2 -2
  97. package/dist/scheduler/index.js +1 -1
  98. package/dist/services/TracingService.js +2 -3
  99. package/dist/session/SessionStore.d.ts +6 -0
  100. package/dist/session/SessionStore.js +12 -1
  101. package/dist/session/stores/MemoryStore.d.ts +1 -1
  102. package/dist/session/stores/MemoryStore.js +16 -2
  103. package/dist/session/testing.d.ts +6 -1
  104. package/dist/session/testing.js +34 -0
  105. package/dist/session/utils.d.ts +3 -0
  106. package/dist/session/utils.js +23 -0
  107. package/dist/skills/collectSkills.d.ts +11 -3
  108. package/dist/skills/collectSkills.js +51 -8
  109. package/dist/skills/compositeSkillStore.d.ts +26 -0
  110. package/dist/skills/compositeSkillStore.js +54 -0
  111. package/dist/skills/defineSkill.d.ts +21 -0
  112. package/dist/skills/defineSkill.js +26 -0
  113. package/dist/skills/fsSkillStore.d.ts +3 -0
  114. package/dist/skills/fsSkillStore.js +79 -0
  115. package/dist/skills/index.d.ts +4 -0
  116. package/dist/skills/index.js +4 -0
  117. package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
  118. package/dist/skills/parseSkillFrontmatter.js +183 -0
  119. package/dist/skills/wireAgentSkills.d.ts +2 -1
  120. package/dist/skills/wireAgentSkills.js +2 -2
  121. package/dist/testing/mocks.d.ts +3 -3
  122. package/dist/testing/mocks.js +7 -3
  123. package/dist/tools/controlResults.d.ts +14 -0
  124. package/dist/tools/controlResults.js +15 -0
  125. package/dist/tools/effect/ToolExecutor.d.ts +6 -0
  126. package/dist/tools/effect/ToolExecutor.js +75 -36
  127. package/dist/tools/effect/defineTool.d.ts +11 -0
  128. package/dist/tools/effect/defineTool.js +3 -0
  129. package/dist/tools/effect/errors.d.ts +2 -5
  130. package/dist/tools/effect/errors.js +2 -5
  131. package/dist/tools/index.d.ts +0 -1
  132. package/dist/tools/index.js +0 -1
  133. package/dist/tracing/MemoryTraceStore.d.ts +16 -0
  134. package/dist/tracing/MemoryTraceStore.js +56 -0
  135. package/dist/tracing/OtelTraceSink.d.ts +127 -0
  136. package/dist/tracing/OtelTraceSink.js +101 -0
  137. package/dist/tracing/TraceStore.d.ts +19 -0
  138. package/dist/tracing/TraceStore.js +32 -0
  139. package/dist/tracing/index.d.ts +3 -0
  140. package/dist/tracing/index.js +3 -0
  141. package/dist/tracing/testing.d.ts +3 -0
  142. package/dist/tracing/testing.js +45 -0
  143. package/dist/types/agentConfig.d.ts +2 -1
  144. package/dist/types/channel.d.ts +14 -1
  145. package/dist/types/effectTool.d.ts +6 -0
  146. package/dist/types/flow.d.ts +1 -1
  147. package/dist/types/guardrails.d.ts +5 -0
  148. package/dist/types/hooks.d.ts +2 -2
  149. package/dist/types/index.d.ts +25 -15
  150. package/dist/types/index.js +8 -12
  151. package/dist/types/knowledge.d.ts +213 -0
  152. package/dist/types/processors.d.ts +0 -58
  153. package/dist/types/run-context.d.ts +16 -7
  154. package/dist/types/runtime.d.ts +3 -74
  155. package/dist/types/session.d.ts +11 -1
  156. package/dist/types/skills.d.ts +15 -1
  157. package/dist/types/stream.d.ts +138 -68
  158. package/dist/types/stream.js +34 -1
  159. package/dist/types/trace.d.ts +52 -0
  160. package/guides/AGENTS.md +3 -3
  161. package/guides/EXAMPLE_VERIFICATION.md +4 -4
  162. package/guides/FLOWS.md +1 -1
  163. package/guides/GETTING_STARTED.md +1 -1
  164. package/guides/RUNTIME.md +20 -15
  165. package/guides/TOOLS.md +1 -1
  166. package/package.json +13 -14
  167. package/dist/foundation/AgentDefinition.d.ts +0 -17
  168. package/dist/foundation/ConversationEventLog.d.ts +0 -71
  169. package/dist/foundation/ConversationState.d.ts +0 -33
  170. package/dist/foundation/ConversationState.js +0 -1
  171. package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
  172. package/dist/foundation/DefaultConversationEventLog.js +0 -205
  173. package/dist/foundation/DefaultConversationState.d.ts +0 -36
  174. package/dist/foundation/DefaultConversationState.js +0 -103
  175. package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
  176. package/dist/foundation/DefaultToolExecutor.js +0 -132
  177. package/dist/foundation/createFoundation.d.ts +0 -32
  178. package/dist/foundation/createFoundation.js +0 -33
  179. package/dist/hooks/HookRunner.d.ts +0 -37
  180. package/dist/hooks/HookRunner.js +0 -111
  181. package/dist/hooks/builtin/logging.d.ts +0 -5
  182. package/dist/hooks/builtin/logging.js +0 -98
  183. package/dist/hooks/builtin/metrics.d.ts +0 -7
  184. package/dist/hooks/builtin/metrics.js +0 -60
  185. package/dist/hooks/builtin/observability.d.ts +0 -20
  186. package/dist/hooks/builtin/observability.js +0 -572
  187. package/dist/hooks/helpers.d.ts +0 -22
  188. package/dist/hooks/helpers.js +0 -183
  189. package/dist/hooks/index.d.ts +0 -8
  190. package/dist/hooks/index.js +0 -6
  191. package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
  192. package/dist/realtime/RealtimeAudioClient.js +0 -14
  193. package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
  194. package/dist/realtime/RealtimeSessionHandle.js +0 -1
  195. package/dist/realtime/index.d.ts +0 -2
  196. package/dist/realtime/index.js +0 -1
  197. package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
  198. package/dist/runtime/channels/VoiceDriver.js +0 -321
  199. package/dist/runtime/channels/voiceTools.d.ts +0 -9
  200. package/dist/runtime/channels/voiceTools.js +0 -51
  201. package/dist/tools/errorHandling.d.ts +0 -35
  202. package/dist/tools/errorHandling.js +0 -145
  203. package/dist/types/voice.d.ts +0 -517
  204. package/dist/types/voice.js +0 -14
  205. package/dist/utils/isRecord.d.ts +0 -1
  206. package/dist/utils/isRecord.js +0 -3
  207. package/dist/utils/messageNormalization.d.ts +0 -2
  208. package/dist/utils/messageNormalization.js +0 -137
  209. /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
  210. /package/dist/{foundation/ConversationEventLog.js → types/trace.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @kuralle-agents/core
2
2
 
3
- The runtime and primitives for building conversational AI agents — text and voice — with structured flows, routing, and durable tool execution.
3
+ The runtime and primitives for building conversational AI agents with structured flows, routing, and durable tool execution.
4
4
 
5
5
  ## Install
6
6
 
@@ -54,12 +54,97 @@ const runtime = createRuntime({ agents: [agent], defaultAgentId: 'support' });
54
54
 
55
55
  const handle = runtime.run({ input: 'Hello', sessionId: 'demo' });
56
56
  for await (const part of handle.events) { // events is a property, not a method
57
- if (part.type === 'text-delta') process.stdout.write(part.delta);
58
- if (part.type === 'done') console.log('\nSession:', part.sessionId);
57
+ if (part.type === 'text-delta') process.stdout.write(part.payload.delta);
58
+ if (part.type === 'done') console.log('\nSession:', part.payload.sessionId);
59
59
  }
60
60
  await handle; // resolves to TurnResult once the stream is consumed
61
61
  ```
62
62
 
63
+ ## Single-run trace / `runOnce`
64
+
65
+ Use `runOnce` when an evaluator needs one complete, JSON-serializable turn instead
66
+ of a live stream. The trace includes the answer, tool roll-up, and nested spans.
67
+
68
+ ```ts
69
+ const trace = await runtime.runOnce({
70
+ sessionId: 'grounding-eval-42',
71
+ input: 'What was my last invoice total?',
72
+ });
73
+
74
+ const judgeContext = {
75
+ answer: trace.answer,
76
+ evidence: trace.toolResults.map(({ name, result }) => ({ name, result })),
77
+ };
78
+
79
+ const verdict = await groundingJudge(judgeContext);
80
+ console.log(verdict, trace.usedTool, trace.traceId);
81
+ ```
82
+
83
+ `runOnce` executes exactly one normal runtime turn and observes its existing event
84
+ stream. Runtime tracing also captures normal `run()` calls; trace persistence is
85
+ physically separate from the session store and durable effect journal.
86
+
87
+ ## Observability
88
+
89
+ Tracing is enabled by default with an in-process `MemoryTraceStore`. Supply a native
90
+ store for durable reads and add any number of export sinks independently of
91
+ `sessionStore`:
92
+
93
+ ```ts
94
+ import { createRuntime } from '@kuralle-agents/core';
95
+ import { RedisTraceStore } from '@kuralle-agents/redis-store';
96
+
97
+ const traceStore = new RedisTraceStore({
98
+ client,
99
+ traceTtlSeconds: 7 * 24 * 60 * 60,
100
+ });
101
+
102
+ const runtime = createRuntime({
103
+ agents: [agent],
104
+ defaultAgentId: 'support',
105
+ tracing: {
106
+ store: traceStore,
107
+ sampling: 0.1,
108
+ sinks: [externalSink],
109
+ redact: (span) => ({
110
+ ...span,
111
+ attributes: { ...span.attributes, input: undefined, output: undefined },
112
+ }),
113
+ },
114
+ });
115
+
116
+ const latest = (await runtime.listTraces('session-42'))[0];
117
+ const trace = latest ? await runtime.getTrace(latest.traceId) : null;
118
+ ```
119
+
120
+ Sink failures are swallowed and never change the run result. Redaction is off by
121
+ default for useful local debugging; use the hook above before persisting sensitive
122
+ tool inputs or outputs. Set `tracing.enabled: false` to disable capture.
123
+
124
+ ### OTLP and Langfuse
125
+
126
+ The built-in exporter uses HTTP/JSON over `fetch`, so it runs in Bun, Node.js, and
127
+ Cloudflare Workers without a Node OpenTelemetry SDK:
128
+
129
+ ```ts
130
+ import { langfuseSink, otelSink } from '@kuralle-agents/core';
131
+
132
+ const runtime = createRuntime({
133
+ agents: [agent],
134
+ defaultAgentId: 'support',
135
+ tracing: {
136
+ store: traceStore,
137
+ sinks: [
138
+ otelSink({ endpoint: 'https://collector.example.com', headers: { Authorization: 'Bearer token' } }),
139
+ langfuseSink({ publicKey: env.LANGFUSE_PUBLIC_KEY, secretKey: env.LANGFUSE_SECRET_KEY }),
140
+ ],
141
+ },
142
+ });
143
+ ```
144
+
145
+ Endpoints may include `/v1/traces`; otherwise the sink appends it. For self-hosted
146
+ Langfuse, pass its OTLP base URL as `endpoint`.
147
+
63
148
  ## Flows
64
149
 
65
150
  A flow is a node graph that enforces a multi-step procedure without embedding a 600-line SOP in a system prompt.
@@ -145,7 +230,7 @@ return handle.toUIMessageStreamResponse({ sessionId: 'demo' });
145
230
 
146
231
  Kuralle orchestration events (flow telemetry, safety blocks, interactive choices) arrive as typed `data-kuralle-*` parts. Import `KuralleUIMessage` and `KuralleDataParts` for compile-time-safe `message.parts` and `useChat({ onData })` handlers.
147
232
 
148
- For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `HarnessStreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
233
+ For non-UI consumers (curl, custom transports), use `handle.toResponseStream('sse')` to emit raw `StreamPart` JSON-SSE. Or use `@kuralle-agents/hono-server` — `POST /api/chat/sse` defaults to native `UIMessageStream`; append `?format=raw` for the legacy wire.
149
234
 
150
235
  ## Related
151
236
 
@@ -1,7 +1,7 @@
1
1
  import { type UIMessage } from 'ai';
2
2
  import type { ConversationOutcome } from '../outcomes/types.js';
3
3
  import type { ChoiceOption } from '../types/selection.js';
4
- import type { HarnessStreamPart } from '../types/stream.js';
4
+ import type { StreamPart } from '../types/stream.js';
5
5
  export type KuralleMetadata = {
6
6
  sessionId?: string;
7
7
  };
@@ -46,6 +46,6 @@ export type KuralleDataParts = {
46
46
  };
47
47
  };
48
48
  export type KuralleUIMessage = UIMessage<KuralleMetadata, KuralleDataParts>;
49
- export declare function harnessToUIMessageStream(source: AsyncIterable<HarnessStreamPart>, opts?: {
49
+ export declare function harnessToUIMessageStream(source: AsyncIterable<StreamPart>, opts?: {
50
50
  sessionId?: string;
51
51
  }): ReadableStream;
@@ -2,64 +2,64 @@ import { createUIMessageStream, generateId, } from 'ai';
2
2
  function writeHarnessPart(part, writer) {
3
3
  switch (part.type) {
4
4
  case 'text-start':
5
- writer.write({ type: 'text-start', id: part.id });
5
+ writer.write({ type: 'text-start', id: part.payload.id });
6
6
  break;
7
7
  case 'text-delta':
8
- writer.write({ type: 'text-delta', id: part.id, delta: part.delta });
8
+ writer.write({ type: 'text-delta', id: part.payload.id, delta: part.payload.delta });
9
9
  break;
10
10
  case 'text-end':
11
- writer.write({ type: 'text-end', id: part.id });
11
+ writer.write({ type: 'text-end', id: part.payload.id });
12
12
  break;
13
13
  case 'text-cancel':
14
- writer.write({ type: 'text-end', id: part.id });
14
+ writer.write({ type: 'text-end', id: part.payload.id });
15
15
  break;
16
16
  case 'tool-call':
17
17
  writer.write({
18
18
  type: 'tool-input-available',
19
- toolCallId: part.toolCallId ?? generateId(),
20
- toolName: part.toolName,
21
- input: part.args,
19
+ toolCallId: part.payload.toolCallId ?? generateId(),
20
+ toolName: part.payload.toolName,
21
+ input: part.payload.args,
22
22
  });
23
23
  break;
24
24
  case 'tool-result':
25
25
  writer.write({
26
26
  type: 'tool-output-available',
27
- toolCallId: part.toolCallId ?? 'unknown',
28
- output: part.result,
27
+ toolCallId: part.payload.toolCallId ?? 'unknown',
28
+ output: part.payload.result,
29
29
  });
30
30
  break;
31
31
  case 'node-enter':
32
32
  writer.write({
33
33
  type: 'data-kuralle-node',
34
- data: { event: 'enter', node: part.nodeName },
34
+ data: { event: 'enter', node: part.payload.nodeName },
35
35
  transient: true,
36
36
  });
37
37
  break;
38
38
  case 'node-exit':
39
39
  writer.write({
40
40
  type: 'data-kuralle-node',
41
- data: { event: 'exit', node: part.nodeName },
41
+ data: { event: 'exit', node: part.payload.nodeName },
42
42
  transient: true,
43
43
  });
44
44
  break;
45
45
  case 'flow-enter':
46
46
  writer.write({
47
47
  type: 'data-kuralle-flow',
48
- data: { event: 'enter', flow: part.flow },
48
+ data: { event: 'enter', flow: part.payload.flow },
49
49
  transient: true,
50
50
  });
51
51
  break;
52
52
  case 'flow-end':
53
53
  writer.write({
54
54
  type: 'data-kuralle-flow',
55
- data: { event: 'end', flow: part.flow, reason: part.reason },
55
+ data: { event: 'end', flow: part.payload.flow, reason: part.payload.reason },
56
56
  transient: true,
57
57
  });
58
58
  break;
59
59
  case 'flow-transition':
60
60
  writer.write({
61
61
  type: 'data-kuralle-flow',
62
- data: { event: 'transition', from: part.from, to: part.to },
62
+ data: { event: 'transition', from: part.payload.from, to: part.payload.to },
63
63
  transient: true,
64
64
  });
65
65
  break;
@@ -67,14 +67,18 @@ function writeHarnessPart(part, writer) {
67
67
  writer.write({
68
68
  type: 'data-kuralle-handoff',
69
69
  id: generateId(),
70
- data: { targetAgent: part.targetAgent, reason: part.reason },
70
+ data: { targetAgent: part.payload.targetAgent, reason: part.payload.reason },
71
71
  });
72
72
  break;
73
73
  case 'interactive':
74
74
  writer.write({
75
75
  type: 'data-kuralle-interactive',
76
- id: part.nodeId,
77
- data: { nodeId: part.nodeId, prompt: part.prompt, options: part.options },
76
+ id: part.payload.nodeId,
77
+ data: {
78
+ nodeId: part.payload.nodeId,
79
+ prompt: part.payload.prompt,
80
+ options: part.payload.options,
81
+ },
78
82
  });
79
83
  break;
80
84
  case 'safety-blocked':
@@ -83,9 +87,9 @@ function writeHarnessPart(part, writer) {
83
87
  id: generateId(),
84
88
  data: {
85
89
  kind: 'safety-blocked',
86
- moderator: part.moderator,
87
- rationale: part.rationale,
88
- userFacingMessage: part.userFacingMessage,
90
+ moderator: part.payload.moderator,
91
+ rationale: part.payload.rationale,
92
+ userFacingMessage: part.payload.userFacingMessage,
89
93
  },
90
94
  });
91
95
  break;
@@ -95,8 +99,8 @@ function writeHarnessPart(part, writer) {
95
99
  id: generateId(),
96
100
  data: {
97
101
  kind: 'pipeline-validation-block',
98
- rationale: part.rationale,
99
- userFacingMessage: part.userFacingMessage,
102
+ rationale: part.payload.rationale,
103
+ userFacingMessage: part.payload.userFacingMessage,
100
104
  },
101
105
  });
102
106
  break;
@@ -104,35 +108,44 @@ function writeHarnessPart(part, writer) {
104
108
  writer.write({
105
109
  type: 'data-kuralle-outcome',
106
110
  id: generateId(),
107
- data: { outcome: part.outcome },
111
+ data: { outcome: part.payload.outcome },
108
112
  });
109
113
  break;
110
114
  case 'interrupted':
111
115
  writer.write({
112
116
  type: 'data-kuralle-control',
113
- data: { event: 'interrupted', reason: part.reason },
117
+ data: { event: 'interrupted', reason: part.payload.reason },
114
118
  transient: true,
115
119
  });
116
120
  break;
117
121
  case 'paused':
118
122
  writer.write({
119
123
  type: 'data-kuralle-control',
120
- data: { event: 'paused', waitingFor: part.waitingFor },
124
+ data: { event: 'paused', waitingFor: part.payload.waitingFor },
121
125
  transient: true,
122
126
  });
123
127
  break;
124
128
  case 'custom':
125
129
  writer.write({
126
130
  type: 'data-kuralle-custom',
127
- data: { name: part.name, data: part.data },
131
+ data: { name: part.payload.name, data: part.payload.data },
128
132
  transient: true,
129
133
  });
130
134
  break;
131
135
  case 'error':
132
- throw new Error(part.error);
136
+ throw new Error(part.payload.error);
133
137
  case 'done':
134
138
  case 'turn-end':
135
139
  break;
140
+ default: {
141
+ // Client-channel exhaustiveness guard. Internal-channel parts are
142
+ // deliberately unmapped here — they never reach a UI client. But a new
143
+ // `client` variant added without a case above would be silently dropped
144
+ // on the ADR-0005 default output path, so pin the residual to internal:
145
+ // adding a client variant without a case fails this assignment.
146
+ const _internalOnly = part;
147
+ void _internalOnly;
148
+ }
136
149
  }
137
150
  }
138
151
  export function harnessToUIMessageStream(source, opts) {
@@ -146,8 +159,8 @@ export function harnessToUIMessageStream(source, opts) {
146
159
  });
147
160
  }
148
161
  for await (const part of source) {
149
- if (part.type === 'done' && part.sessionId) {
150
- doneSessionId = doneSessionId ?? part.sessionId;
162
+ if (part.type === 'done' && part.payload.sessionId) {
163
+ doneSessionId = doneSessionId ?? part.payload.sessionId;
151
164
  }
152
165
  else {
153
166
  writeHarnessPart(part, writer);
@@ -7,7 +7,7 @@
7
7
  * CapabilityHost with Runtime context (working memory, long-term memory,
8
8
  * policy injections) and structures the output per audio-model best practices.
9
9
  *
10
- * Adapters (Gemini, OpenAI Realtime, LiveKit) can extend or replace
10
+ * Adapters can extend or replace
11
11
  * the default with backend-specific voice rules and guardrails.
12
12
  *
13
13
  * Prompt structure follows Google's Live API best practices and
@@ -7,7 +7,7 @@
7
7
  * CapabilityHost with Runtime context (working memory, long-term memory,
8
8
  * policy injections) and structures the output per audio-model best practices.
9
9
  *
10
- * Adapters (Gemini, OpenAI Realtime, LiveKit) can extend or replace
10
+ * Adapters can extend or replace
11
11
  * the default with backend-specific voice rules and guardrails.
12
12
  *
13
13
  * Prompt structure follows Google's Live API best practices and
@@ -47,6 +47,7 @@ const INTERNAL_MEMORY_KEYS = [
47
47
  'flowStateByAgent',
48
48
  '__ariaAssistantText',
49
49
  '__ariaContextBudget',
50
+ '__goals',
50
51
  ];
51
52
  /**
52
53
  * Default LivePromptAssembler implementation.
@@ -1,7 +1,7 @@
1
1
  import type { ZodTypeAny } from 'zod';
2
2
  /**
3
3
  * Backend-agnostic tool declaration. Produced by capabilities,
4
- * consumed by adapters (Gemini, AI SDK, LiveKit).
4
+ * consumed by adapters (AI SDK and others).
5
5
  */
6
6
  export interface ToolDeclaration<TInput = unknown, TOutput = unknown> {
7
7
  name: string;
@@ -25,16 +25,16 @@ export class EvalRunner {
25
25
  const handle = runtime.run({ input: turn.input, sessionId });
26
26
  for await (const part of handle.events) {
27
27
  if (part.type === 'text-delta') {
28
- response += part.delta;
28
+ response += part.payload.delta;
29
29
  }
30
30
  if (part.type === 'tool-call') {
31
- toolsCalled.push(part.toolName);
31
+ toolsCalled.push(part.payload.toolName);
32
32
  }
33
33
  if (part.type === 'flow-transition') {
34
- transitions.push({ from: part.from, to: part.to });
34
+ transitions.push({ from: part.payload.from, to: part.payload.to });
35
35
  }
36
36
  if (part.type === 'done') {
37
- sessionId = part.sessionId;
37
+ sessionId = part.payload.sessionId;
38
38
  }
39
39
  }
40
40
  await handle;
@@ -66,10 +66,11 @@ export async function simulateConversation(options) {
66
66
  let reply = '';
67
67
  for await (const part of handle.events) {
68
68
  if (part.type === 'text-delta')
69
- reply += part.delta;
69
+ reply += part.payload.delta;
70
70
  if (part.type === 'tool-call')
71
- toolsCalled.push(part.toolName);
72
- if (part.type === 'escalation' || (part.type === 'handoff' && part.targetAgent === 'human')) {
71
+ toolsCalled.push(part.payload.toolName);
72
+ if (part.type === 'escalation' ||
73
+ (part.type === 'handoff' && part.payload.targetAgent === 'human')) {
73
74
  escalated = true;
74
75
  }
75
76
  }
@@ -1,7 +1,7 @@
1
- import type { HarnessStreamPart, TurnHandle } from '../types/stream.js';
1
+ import type { StreamPart, TurnHandle } from '../types/stream.js';
2
2
  export interface EventBus {
3
- emit(part: HarnessStreamPart): void;
4
- events(): AsyncIterable<HarnessStreamPart>;
3
+ emit(part: StreamPart): void;
4
+ events(): AsyncIterable<StreamPart>;
5
5
  close(): void;
6
6
  }
7
7
  export declare function createEventBus(): EventBus;
@@ -1,6 +1,5 @@
1
1
  import type { AgentConfig } from '../types/agentConfig.js';
2
2
  import type { ChannelDriver } from '../types/channel.js';
3
- import type { CollectNode } from '../types/flow.js';
4
3
  import type { RunContext } from '../types/run-context.js';
5
4
  import type { RunState } from '../runtime/durable/types.js';
6
5
  import type { NormalizedTransition } from './normalizeTransition.js';
@@ -9,11 +8,15 @@ export interface FlowPark {
9
8
  flow: string;
10
9
  node: string;
11
10
  }
11
+ export declare function pushFlowPark(state: Record<string, unknown>, park: FlowPark): void;
12
+ export declare function popFlowPark(state: Record<string, unknown>): FlowPark | undefined;
12
13
  export declare function getFlowPark(state: Record<string, unknown>): FlowPark | undefined;
13
14
  export declare function looksLikeOffScriptQuestion(input: string): boolean;
14
15
  export interface CollectDigressionOptions {
15
16
  agent: AgentConfig;
16
- node: CollectNode;
17
+ node: {
18
+ id: string;
19
+ };
17
20
  activeFlowName: string;
18
21
  run: RunState;
19
22
  driver: ChannelDriver;
@@ -1,20 +1,57 @@
1
1
  import { resolveReplyNode } from './nodeBuilders.js';
2
2
  import { selectHostTarget } from '../runtime/select.js';
3
- const FLOW_PARK_KEY = '__flowPark';
4
- export function getFlowPark(state) {
5
- const raw = state[FLOW_PARK_KEY];
3
+ import { persistTurnUsageFromTurn } from '../runtime/turnTokenUsage.js';
4
+ const FLOW_PARK_STACK_KEY = '__flowParkStack';
5
+ const LEGACY_FLOW_PARK_KEY = '__flowPark';
6
+ const MAX_FLOW_PARK_DEPTH = 8;
7
+ function isFlowPark(raw) {
6
8
  if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
7
- return undefined;
9
+ return false;
8
10
  }
9
11
  const flow = raw.flow;
10
12
  const node = raw.node;
11
- if (typeof flow === 'string' && typeof node === 'string') {
12
- return { flow, node };
13
+ return typeof flow === 'string' && typeof node === 'string';
14
+ }
15
+ function getFlowParkStack(state) {
16
+ const raw = state[FLOW_PARK_STACK_KEY];
17
+ if (!Array.isArray(raw)) {
18
+ return undefined;
13
19
  }
14
- return undefined;
20
+ const stack = raw.filter(isFlowPark);
21
+ return stack.length > 0 ? stack : undefined;
22
+ }
23
+ export function pushFlowPark(state, park) {
24
+ const stack = getFlowParkStack(state) ?? [];
25
+ stack.push(park);
26
+ while (stack.length > MAX_FLOW_PARK_DEPTH) {
27
+ stack.shift();
28
+ }
29
+ state[FLOW_PARK_STACK_KEY] = stack;
15
30
  }
16
- function setFlowPark(state, park) {
17
- state[FLOW_PARK_KEY] = park;
31
+ export function popFlowPark(state) {
32
+ const stack = getFlowParkStack(state);
33
+ if (!stack) {
34
+ return undefined;
35
+ }
36
+ const park = stack.pop();
37
+ if (stack.length === 0) {
38
+ delete state[FLOW_PARK_STACK_KEY];
39
+ }
40
+ else {
41
+ state[FLOW_PARK_STACK_KEY] = stack;
42
+ }
43
+ return park;
44
+ }
45
+ export function getFlowPark(state) {
46
+ const stack = getFlowParkStack(state);
47
+ if (stack) {
48
+ return stack[stack.length - 1];
49
+ }
50
+ const legacy = state[LEGACY_FLOW_PARK_KEY];
51
+ if (isFlowPark(legacy)) {
52
+ return legacy;
53
+ }
54
+ return undefined;
18
55
  }
19
56
  function appendAssistantMessage(run, text) {
20
57
  if (!text.trim()) {
@@ -53,7 +90,7 @@ export async function runCollectDigression(options) {
53
90
  };
54
91
  }
55
92
  if (selection.kind === 'enterFlow') {
56
- setFlowPark(run.state, { flow: activeFlowName, node: node.id });
93
+ pushFlowPark(run.state, { flow: activeFlowName, node: node.id });
57
94
  return {
58
95
  kind: 'transition',
59
96
  transition: {
@@ -74,6 +111,7 @@ export async function runCollectDigression(options) {
74
111
  tools: ctx.globalTools,
75
112
  };
76
113
  const turn = await driver.runAgentTurn(resolveReplyNode(replyNode, run.state, { freeConversation: true }), ctx);
114
+ await persistTurnUsageFromTurn(ctx, turn);
77
115
  if (turn.text.trim()) {
78
116
  appendAssistantMessage(run, turn.text);
79
117
  }
@@ -1,4 +1,5 @@
1
1
  import { runCollectDigression } from './collectDigression.js';
2
+ import { persistTurnUsageFromTurn } from '../runtime/turnTokenUsage.js';
2
3
  import { hasPendingUserInput } from '../runtime/channels/inputBuffer.js';
3
4
  import { userInputToText } from '../runtime/userInput.js';
4
5
  import { resolveCollectExtractionNode } from './nodeBuilders.js';
@@ -16,8 +17,13 @@ function appendUserMessage(run, input) {
16
17
  run.messages = [...run.messages, message];
17
18
  }
18
19
  export async function collectUntilComplete(node, run, driver, ctx, options) {
20
+ // A satisfied collect defers completion for ONE pass when there is pending input,
21
+ // so a correction ("no, Tuesday") is re-extracted and overwrites before completing
22
+ // (G14). Bounded to a single pass so the loop cannot spin if a driver's awaitUser
23
+ // does not clear the pending buffer — completion no longer depends on that.
24
+ let pendingConsumed = false;
19
25
  for (;;) {
20
- if (schemaSatisfied(node, run.state)) {
26
+ if (schemaSatisfied(node, run.state) && (!hasPendingUserInput(ctx.session) || pendingConsumed)) {
21
27
  const data = projectCollectData(node, run.state);
22
28
  return normalizeTransition(await node.onComplete(data, run.state));
23
29
  }
@@ -32,6 +38,7 @@ export async function collectUntilComplete(node, run, driver, ctx, options) {
32
38
  if (hasPendingUserInput(ctx.session)) {
33
39
  const signal = await driver.awaitUser(ctx);
34
40
  appendUserMessage(run, signal.input);
41
+ pendingConsumed = true;
35
42
  }
36
43
  else if (ctx.turnInputConsumed) {
37
44
  // No fresh input to extract this turn: ask (deterministically) for the
@@ -59,6 +66,7 @@ export async function collectUntilComplete(node, run, driver, ctx, options) {
59
66
  const turn = await (driver.runExtraction
60
67
  ? driver.runExtraction(resolved, ctx)
61
68
  : driver.runAgentTurn(resolved, ctx));
69
+ await persistTurnUsageFromTurn(ctx, turn);
62
70
  mergeExtractionFromTurn(node, run, turn, ctx);
63
71
  const missingAfter = computeMissingFields(node, getCollectData(run.state, node.id));
64
72
  const advanced = missingAfter.length < missingBefore.length || schemaSatisfied(node, run.state);
@@ -113,10 +121,10 @@ export function emitCollectAsk(node, run, ctx) {
113
121
  return;
114
122
  }
115
123
  const id = crypto.randomUUID();
116
- ctx.emit({ type: 'text-start', id });
117
- ctx.emit({ type: 'text-delta', id, delta: text });
118
- ctx.emit({ type: 'text-end', id });
119
- ctx.emit({ type: 'turn-end' });
124
+ ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
125
+ ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
126
+ ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
127
+ ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
120
128
  appendAssistantMessage(run, text);
121
129
  }
122
130
  function mergeExtractionFromTurn(node, run, turn, ctx) {
@@ -7,9 +7,9 @@ export function appendSafeAssistantMessage(run, ctx, text = SAFE_DEGRADED_MESSAG
7
7
  const message = { role: 'assistant', content: text };
8
8
  run.messages = [...run.messages, message];
9
9
  const id = crypto.randomUUID();
10
- ctx.emit({ type: 'text-start', id });
11
- ctx.emit({ type: 'text-delta', id, delta: text });
12
- ctx.emit({ type: 'text-end', id });
10
+ ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
11
+ ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: text } });
12
+ ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
13
13
  }
14
14
  export async function degradeFlowError(flow, registry, run, driver, ctx, dispatchNode) {
15
15
  appendSafeAssistantMessage(run, ctx);
@@ -29,6 +29,10 @@ export async function degradeFlowError(flow, registry, run, driver, ctx, dispatc
29
29
  // Fall through to graceful end if the escalate node also fails.
30
30
  }
31
31
  }
32
- ctx.emit({ type: 'flow-end', flow: flow.name, reason: 'error_degraded' });
32
+ ctx.emit({
33
+ channel: 'internal',
34
+ type: 'flow-end',
35
+ payload: { flow: flow.name, reason: 'error_degraded' },
36
+ });
33
37
  return { kind: 'ended', reason: 'error_degraded' };
34
38
  }
@@ -1,3 +1,3 @@
1
1
  import type { FlowNode, FlowState } from '../types/flow.js';
2
- import type { HarnessStreamPart } from '../types/stream.js';
3
- export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part: HarnessStreamPart) => void): void;
2
+ import type { StreamPart } from '../types/stream.js';
3
+ export declare function emitInteractiveOnNodeEnter(node: FlowNode, state: FlowState, emit: (part: StreamPart) => void): void;
@@ -35,9 +35,12 @@ export function emitInteractiveOnNodeEnter(node, state, emit) {
35
35
  return;
36
36
  }
37
37
  emit({
38
+ channel: 'internal',
38
39
  type: 'interactive',
39
- nodeId: node.id,
40
- options: node.choices,
41
- prompt: interactivePrompt(node, state),
40
+ payload: {
41
+ nodeId: node.id,
42
+ options: node.choices,
43
+ prompt: interactivePrompt(node, state),
44
+ },
42
45
  });
43
46
  }
@@ -1,6 +1,7 @@
1
1
  import type { CollectNode, FlowState } from '../types/flow.js';
2
2
  import type { Tool } from '../types/effectTool.js';
3
- import type { HarnessStreamPart } from '../types/stream.js';
3
+ import type { StreamPart } from '../types/stream.js';
4
+ export declare function resetCollect(state: FlowState, nodeId: string): void;
4
5
  export declare function getCollectData(state: FlowState, nodeId: string): Record<string, unknown>;
5
6
  export declare function incrementCollectTurns(state: FlowState, nodeId: string): number;
6
7
  export declare function computeMissingFields(node: CollectNode, data: Record<string, unknown>): string[];
@@ -19,4 +20,4 @@ export declare function mergeTurnExtraction(node: CollectNode, state: FlowState,
19
20
  name: string;
20
21
  result: unknown;
21
22
  }>): MergeTurnExtractionResult;
22
- export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part: HarnessStreamPart) => void): void;
23
+ export declare function emitExtractionTelemetry(node: CollectNode, state: FlowState, incoming: Record<string, unknown>, emit: (part: StreamPart) => void): void;