@mozaik-ai/core 3.8.4 → 3.8.5

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 +45 -107
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -93,12 +93,25 @@ The environment fans every item out to every subscriber synchronously and withou
93
93
 
94
94
  ---
95
95
 
96
- ## Agent Loop Implementation
96
+ ## Reactive agent
97
97
 
98
- The easiest way to build **and control** an agent loop is to override three handlers on `BaseAgentParticipant`:
98
+ A reactive agent extends `BaseAgentParticipant` and overrides the handlers it wants to react on. Each handler is already a no-op in the base class, so only the relevant ones need bodies:
99
99
 
100
100
  ```ts
101
- export class CustomAgent extends BaseAgentParticipant {
101
+ import {
102
+ BaseAgentParticipant,
103
+ Participant,
104
+ UserMessageItem,
105
+ FunctionCallItem,
106
+ AgenticEnvironment,
107
+ ModelContext,
108
+ GenerativeModel,
109
+ InputStream,
110
+ InferenceRunner,
111
+ FunctionCallRunner,
112
+ } from "@mozaik-ai/core"
113
+
114
+ export class ReactiveAgent extends BaseAgentParticipant {
102
115
  constructor(
103
116
  inputSource: InputStream,
104
117
  inferenceRunner: InferenceRunner,
@@ -110,30 +123,48 @@ export class CustomAgent extends BaseAgentParticipant {
110
123
  super(inputSource, inferenceRunner, functionCallRunner)
111
124
  }
112
125
 
126
+ // A message from a human (or any other participant) → record it and think.
113
127
  async onMessage(message: string): Promise<void> {
114
128
  this.context.addContextItem(UserMessageItem.create(message))
115
129
  this.runInference(this.environment, this.context, this.model)
116
130
  }
117
131
 
132
+ // The agent just produced a function call → execute it.
118
133
  async onFunctionCall(item: FunctionCallItem): Promise<void> {
119
134
  this.context.addContextItem(item)
120
135
  this.executeFunctionCall(this.environment, item)
121
136
  }
122
137
 
138
+ // The tool just produced an output → feed it back and run inference again.
123
139
  async onFunctionCallOutput(item: FunctionCallOutputItem): Promise<void> {
124
140
  this.context.addContextItem(item)
125
141
  this.runInference(this.environment, this.context, this.model)
126
142
  }
143
+
144
+ // Keep the local context in sync with model-emitted reasoning and replies.
145
+ async onReasoning(item: ReasoningItem): Promise<void> {
146
+ this.context.addContextItem(item)
147
+ }
148
+
149
+ async onModelMessage(item: ModelMessageItem): Promise<void> {
150
+ this.context.addContextItem(item)
151
+ }
127
152
  }
128
153
  ```
129
154
 
155
+ Three things to note:
156
+
157
+ 1. The split between self handlers and `onExternal*` handlers means a participant can encode "act on my own outputs" separately from "observe others", without inspecting `source` by hand.
158
+ 2. The agent never `await`s its own capability calls inside the handlers — those methods are non-blocking, so the environment keeps delivering events while inference and tool execution run in the background.
159
+ 3. Behaviors compose by **reaction**, not orchestration. Add a second agent that overrides `onExternalModelMessage` and you get a critique loop. Add a `TranscriptLogger` and you get a UI stream. Neither change touches the existing participants.
160
+
130
161
  ---
131
162
 
132
163
  ## Reacting to external events
133
164
 
134
165
  Participants can listen to external events and react by overriding methods like `onMessage`, `onExternalFunctionCall`, `onExternalFunctionCallOutput`, `onExternalReasoning`, and `onExternalModelMessage`.
135
166
 
136
- ### Passive observer
167
+ ## Passive observer
137
168
 
138
169
  You can create observers that don't run inference themselves but watch what's happening in the conversation and take side actions (logging, metrics, persistence, etc.). Subclass `Participant` and override only the handlers you care about:
139
170
 
@@ -168,72 +199,6 @@ export class TranscriptLogger extends Participant {
168
199
  async onModelMessage(): Promise<void> {}
169
200
  }
170
201
  ```
171
-
172
- ### Reactive agent
173
-
174
- A reactive agent extends `BaseAgentParticipant` and overrides the handlers it wants to react on. Each handler is already a no-op in the base class, so only the relevant ones need bodies:
175
-
176
- ```ts
177
- import {
178
- BaseAgentParticipant,
179
- Participant,
180
- UserMessageItem,
181
- FunctionCallItem,
182
- AgenticEnvironment,
183
- ModelContext,
184
- GenerativeModel,
185
- InputStream,
186
- InferenceRunner,
187
- FunctionCallRunner,
188
- } from "@mozaik-ai/core"
189
-
190
- export class ReactiveAgent extends BaseAgentParticipant {
191
- constructor(
192
- inputSource: InputStream,
193
- inferenceRunner: InferenceRunner,
194
- functionCallRunner: FunctionCallRunner,
195
- private readonly environment: AgenticEnvironment,
196
- private readonly context: ModelContext,
197
- private readonly model: GenerativeModel,
198
- ) {
199
- super(inputSource, inferenceRunner, functionCallRunner)
200
- }
201
-
202
- // A message from a human (or any other participant) → record it and think.
203
- async onMessage(message: string): Promise<void> {
204
- this.context.addContextItem(UserMessageItem.create(message))
205
- this.runInference(this.environment, this.context, this.model)
206
- }
207
-
208
- // The agent just produced a function call → execute it.
209
- async onFunctionCall(item: FunctionCallItem): Promise<void> {
210
- this.context.addContextItem(item)
211
- this.executeFunctionCall(this.environment, item)
212
- }
213
-
214
- // The tool just produced an output → feed it back and run inference again.
215
- async onFunctionCallOutput(item: FunctionCallOutputItem): Promise<void> {
216
- this.context.addContextItem(item)
217
- this.runInference(this.environment, this.context, this.model)
218
- }
219
-
220
- // Keep the local context in sync with model-emitted reasoning and replies.
221
- async onReasoning(item: ReasoningItem): Promise<void> {
222
- this.context.addContextItem(item)
223
- }
224
-
225
- async onModelMessage(item: ModelMessageItem): Promise<void> {
226
- this.context.addContextItem(item)
227
- }
228
- }
229
- ```
230
-
231
- Three things to note:
232
-
233
- 1. The split between self handlers and `onExternal*` handlers means a participant can encode "act on my own outputs" separately from "observe others", without inspecting `source` by hand.
234
- 2. The agent never `await`s its own capability calls inside the handlers — those methods are non-blocking, so the environment keeps delivering events while inference and tool execution run in the background.
235
- 3. Behaviors compose by **reaction**, not orchestration. Add a second agent that overrides `onExternalModelMessage` and you get a critique loop. Add a `TranscriptLogger` and you get a UI stream. Neither change touches the existing participants.
236
-
237
202
  ---
238
203
 
239
204
  ## Context and models (reference)
@@ -264,51 +229,28 @@ const response = await runtime.infer(new InferenceRequest(new Gpt54(), context))
264
229
 
265
230
  ---
266
231
 
267
- ## Advanced: overriding generators
232
+ ## Overriding Generators
268
233
 
269
- `BaseAgentParticipant` and `BaseHumanParticipant` are deliberately thin shells around three generator interfaces. Swap any of them to change _how_ events are produced without touching the environment, the participants, or any consumers.
234
+ Mozaik uses generators for inference, function calls, and message streams — that's what lets the system emit events incrementally so participants can react to them as they arrive. Swap any generator to change _how_ events are produced.
270
235
 
271
236
  ### Custom `InputStream`
272
237
 
273
- An `InputStream` is the minimal text-source contract a participant uses to feed messages into the environment:
274
-
275
- ```ts
276
- export interface InputStream {
277
- stream(signal?: AbortSignal): AsyncIterable<string>
278
- }
279
- ```
280
-
281
- Each yielded string is delivered to every other participant through `onMessage(message: string)`. This keeps the wire format dead simple: participants exchange text, and each one decides how to turn that text into a `ContextItem` for its own `ModelContext`.
282
-
283
238
  ```ts
284
239
  import { InputStream } from "@mozaik-ai/core"
285
240
 
286
- export class QueueInputStream implements InputStream {
287
- private readonly queue: string[] = []
288
- private resolveNext?: () => void
289
-
290
- push(message: string) {
291
- this.queue.push(message)
292
- this.resolveNext?.()
293
- this.resolveNext = undefined
294
- }
295
-
296
- async *stream(signal?: AbortSignal): AsyncIterable<string> {
297
- while (!signal?.aborted) {
298
- while (this.queue.length > 0) {
299
- yield this.queue.shift()!
300
- }
301
- await new Promise<void>((resolve) => (this.resolveNext = resolve))
302
- }
241
+ export class HelloInputStream implements InputStream {
242
+ async *stream(): AsyncIterable<string> {
243
+ yield "Hello"
244
+ yield "World"
303
245
  }
304
246
  }
305
247
  ```
306
248
 
307
- Use it for stdin, websockets, an HTTP queue, or anything that produces text over time. A reactive agent typically wraps the incoming string in a `UserMessageItem` inside `onMessage` before adding it to its `ModelContext` — see the [Reactive agent](#reactive-agent) example above.
249
+ Each yielded string is delivered to other participants through `onMessage(message: string)`.
308
250
 
309
251
  ### Custom `InferenceRunner`
310
252
 
311
- Wrap any model runtime — including `OpenAIResponses` — and decide how its output becomes a stream of items. Here we expand a single `InferenceResponse` into per-item delivery:
253
+ An `InferenceRunner` can yield `ReasoningItem`, `FunctionCallItem`, and `ModelMessageItem`.
312
254
 
313
255
  ```ts
314
256
  import {
@@ -336,11 +278,9 @@ export class OpenAIInferenceRunner implements InferenceRunner {
336
278
  }
337
279
  ```
338
280
 
339
- Replace the body with a streaming runtime and items will flow into the environment as soon as the model produces them.
340
-
341
281
  ### Custom `FunctionCallRunner`
342
282
 
343
- Resolve a `FunctionCallItem` against a tool registry and yield its output:
283
+ A `FunctionCallRunner` can only produce `FunctionCallOutputItem`.
344
284
 
345
285
  ```ts
346
286
  import { FunctionCallRunner, FunctionCallItem, FunctionCallOutputItem, Tool } from "@mozaik-ai/core"
@@ -364,7 +304,7 @@ export class ToolRegistryFunctionCallRunner implements FunctionCallRunner {
364
304
  import { BaseAgentParticipant, AgenticEnvironment } from "@mozaik-ai/core"
365
305
 
366
306
  const agent = new BaseAgentParticipant(
367
- new QueueInputStream(),
307
+ new HelloInputStream(),
368
308
  new OpenAIInferenceRunner(),
369
309
  new ToolRegistryFunctionCallRunner(tools),
370
310
  )
@@ -372,8 +312,6 @@ const agent = new BaseAgentParticipant(
372
312
  agent.join(new AgenticEnvironment())
373
313
  ```
374
314
 
375
- You now own input, inference, and tool execution end-to-end while keeping the same `Participant` contract — and any other participant in the environment can still observe and react to everything the agent emits.
376
-
377
315
  ---
378
316
 
379
317
  ## Made with Mozaik
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mozaik-ai/core",
3
- "version": "3.8.4",
3
+ "version": "3.8.5",
4
4
  "description": "A TypeScript framework for building reactive agents",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",