@mozaik-ai/core 3.6.1 → 3.6.3

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 +220 -191
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Mozaik
2
2
 
3
- **Mozaik** is a TypeScript framework for building non-blocking AI agents.
3
+ **Mozaik** is a TypeScript framework for building AI agents that share an **agentic environment** instead of being orchestrated through rigid pipelines.
4
4
 
5
- It provides a structured agent loop, a lifecycle hook system, a typed context model, and an experimental multi-agent runtime — giving you full control over how agents think, act, and collaborate.
5
+ In Mozaik, humans, agents, observers, and tools are all `Participant`s of the same `AgenticEnvironment`. Each participant runs **non-blocking** and streams typed `ContextItem`s into the environment. Every other participant sees those items in real time and can react, intercept, or stay silent — without any central scheduler.
6
6
 
7
7
  ---
8
8
 
@@ -21,283 +21,312 @@ OPENAI_API_KEY=your-openai-key-here
21
21
 
22
22
  ---
23
23
 
24
- ## Architecture
24
+ ## The agentic environment
25
25
 
26
- Mozaik is organized into four layers:
26
+ `AgenticEnvironment` is a broadcast bus for typed context items. `Participant`s `join()` it, and any item produced by one participant is delivered to every subscriber's `onContextItem(source, item)` callback.
27
27
 
28
28
  ```mermaid
29
- graph TD
30
- AgentSociety["AgentSociety (experimental)"] --> Agent
31
- Agent --> AgentRuntime
32
- AgentRuntime --> AgentLoop["AgentLoop (FSM + hooks)"]
33
- AgentLoop --> OpenAIResponses["OpenAIResponses (OpenResponses adapter)"]
34
- AgentLoop --> Context["Context + ContextItems"]
29
+ flowchart LR
30
+ Human[BaseHumanParticipant] -->|streamInput| Env(("AgenticEnvironment"))
31
+ Agent[BaseAgentParticipant] -->|"runInference / executeFunctionCall"| Env
32
+ Observer[Custom Participant] -->|join| Env
33
+ Env -->|onContextItem| Human
34
+ Env -->|onContextItem| Agent
35
+ Env -->|onContextItem| Observer
35
36
  ```
36
37
 
37
- | Layer | Responsibility |
38
- | ----------------------- | ------------------------------------------------------ |
39
- | **AgentSociety** | Runs multiple agents concurrently (experimental) |
40
- | **Agent** | Lifecycle callbacks, visitor hooks, high-level `run()` |
41
- | **AgentRuntime** | Drives the loop, dispatches hooks, manages errors |
42
- | **AgentLoop + Context** | FSM transitions and typed context model |
43
-
44
38
  ---
45
39
 
46
- ## Core Concepts
47
-
48
- ### Agent Loop
40
+ ## Non-blocking participants
49
41
 
50
- The agent loop is a **finite state machine** that drives a multi-step inference cycle. Each execution moves through states until it reaches a terminal condition.
42
+ Mozaik ships two ready-to-use participants:
51
43
 
52
- ```mermaid
53
- stateDiagram-v2
54
- [*] --> INFERENCE_PENDING
55
- INFERENCE_PENDING --> FUNCTION_CALL_PENDING: model returned a function call
56
- INFERENCE_PENDING --> MODEL_MESSAGE_RECEIVED: model returned a message
57
- FUNCTION_CALL_PENDING --> INFERENCE_PENDING: tool executed, feeding output back
58
- MODEL_MESSAGE_RECEIVED --> [*]: execution complete
59
- ```
44
+ | Participant | Capabilities | Pulls from |
45
+ | --- | --- | --- |
46
+ | `BaseHumanParticipant` | `InputCapable` | `InputItemSource` |
47
+ | `BaseAgentParticipant` | `InputCapable`, `InferenceCapable`, `FunctionCallCapable` | `InputItemSource`, `InferenceRunner`, `FunctionCallRunner` |
60
48
 
61
- An `Execution` object tracks the current state, step count, transition history, and terminal status (`RUNNING`, `COMPLETED`, `FAILED`).
49
+ Each capability method is **non-blocking**: it returns `Promise<void>`, and as the underlying generator yields items they are streamed into the environment one-by-one through the `deliverStream` helper:
62
50
 
63
- ---
51
+ ```5:14:src/application/deliver-stream.ts
52
+ export async function deliverStream<T extends ContextItem>(
53
+ environment: AgenticEnvironment,
54
+ initiator: Participant,
55
+ stream: AsyncIterable<T>,
56
+ ): Promise<void> {
64
57
 
65
- ### Hooks (Events)
58
+ for await (const item of stream) {
59
+ await environment.deliverContextItem(initiator, item);
60
+ }
61
+ }
62
+ ```
66
63
 
67
- Every state transition exposes **before** and **after** hooks. You register callbacks on an `AgentRuntime` using `.on(hookId, callback)`:
64
+ Because each call is a fresh promise that wraps an async iterable, multiple participants can act on the same environment **concurrently**:
68
65
 
69
66
  ```ts
70
- import { AgentRuntime, HookId } from "@mozaik-ai/core"
67
+ import {
68
+ AgenticEnvironment,
69
+ BaseAgentParticipant,
70
+ BaseHumanParticipant,
71
+ Gpt54Mini,
72
+ ModelContext,
73
+ } from "@mozaik-ai/core"
71
74
 
72
- const runtime = new AgentRuntime()
75
+ const environment = new AgenticEnvironment()
73
76
 
74
- runtime.on(HookId.BEFORE_INFERENCE, async (context) => {
75
- console.log("About to call the model...")
76
- })
77
+ const human = new BaseHumanParticipant(humanInputSource)
78
+ const agent = new BaseAgentParticipant(agentInputSource, inferenceRunner, functionCallRunner)
77
79
 
78
- runtime.on(HookId.AFTER_INFERENCE, async (context) => {
79
- console.log("Model responded:", context.inferenceResponse)
80
- })
80
+ human.join(environment)
81
+ agent.join(environment)
81
82
 
82
- runtime.on(HookId.AFTER_FUNCTION_CALL, async (context) => {
83
- console.log("Tool executed:", context.functionCallOutput)
84
- })
83
+ environment.start()
85
84
 
86
- runtime.on(HookId.ON_ERROR, async (context) => {
87
- console.error("Agent failed:", context.error)
88
- })
89
- ```
85
+ const context = ModelContext.create("demo")
86
+ const model = new Gpt54Mini()
90
87
 
91
- Available hooks:
88
+ // Both participants produce items in parallel — neither awaits the other.
89
+ human.streamInput(environment)
90
+ agent.runInference(environment, context, model)
91
+ ```
92
92
 
93
- | Hook | Fires |
94
- | ---------------------- | ------------------------------------ |
95
- | `BEFORE_INFERENCE` | Before the model is called |
96
- | `AFTER_INFERENCE` | After the model responds |
97
- | `BEFORE_FUNCTION_CALL` | Before a tool is invoked |
98
- | `AFTER_FUNCTION_CALL` | After a tool returns |
99
- | `BEFORE_MODEL_MESSAGE` | Before the final message is received |
100
- | `AFTER_MODEL_MESSAGE` | After the final message is received |
101
- | `ON_ERROR` | When any state handler throws |
93
+ The environment fans every item out to every subscriber synchronously and without awaiting them, so a slow listener never blocks producers or other listeners.
102
94
 
103
95
  ---
104
96
 
105
- ### Agent
97
+ ## Intercepting items from other participants
106
98
 
107
- `Agent` wraps an `AgentRuntime` and pre-wires all hooks as overridable methods. Subclass it to customize behavior at any point in the lifecycle:
99
+ `onContextItem(source, item)` is the single intercept point. A participant can:
108
100
 
109
- ```ts
110
- import { Agent, AgentRuntime, RuntimeContext } from "@mozaik-ai/core"
101
+ - **Observe** items from other participants (telemetry, audit, UI streaming).
102
+ - **React** to items by triggering its own capabilities (turn a `UserMessageItem` into an inference run, turn a `FunctionCallItem` into a tool execution).
103
+ - **Ignore** items it doesn't care about — it is just a method call.
111
104
 
112
- class MyAgent extends Agent {
113
- constructor() {
114
- super(new AgentRuntime())
115
- }
105
+ Items are discriminated by the `ContextItem` subclass and, for messages, the `role` field. The full taxonomy is in [src/domain/model-context/context-item](src/domain/model-context/context-item):
116
106
 
117
- async afterInference(context: RuntimeContext): Promise<void> {
118
- console.log("Tokens used:", context.inferenceResponse?.tokenUsage)
119
- }
107
+ - Client-produced: `UserMessageItem`, `DeveloperMessageItem`, `SystemMessageItem`, `FunctionCallOutputItem`
108
+ - Model-produced: `ModelMessageItem`, `FunctionCallItem`, `ReasoningItem`
120
109
 
121
- async onError(context: RuntimeContext): Promise<void> {
122
- console.error("Something went wrong:", context.error?.message)
123
- }
124
- }
110
+ ### Passive observer
125
111
 
126
- const agent = new MyAgent()
127
- await agent.run("Summarize the latest news", model, context)
128
- ```
112
+ ```ts
113
+ import { Participant, ContextItem } from "@mozaik-ai/core"
129
114
 
130
- ---
115
+ export class TranscriptLogger extends Participant {
116
+ async onContextItem(source: Participant, item: ContextItem): Promise<void> {
117
+ console.log(`[${source.constructor.name}]`, item.toJSON())
118
+ }
119
+ }
120
+ ```
131
121
 
132
- ### InferenceVisitor
122
+ ### Reactive agent
133
123
 
134
- `InferenceVisitor` is a **visitor interface** that lets you observe the inference lifecycle without modifying the agent itself. Attach it to any `Agent` instance via `setInferenceVisitor`.
124
+ A reactive agent extends `BaseAgentParticipant` and uses incoming items from *other* participants to decide when to run inference or execute a tool call:
135
125
 
136
126
  ```ts
137
- import { InferenceVisitor, InferenceResponse, RuntimeContext } from "@mozaik-ai/core"
138
-
139
- class TelemetryVisitor implements InferenceVisitor {
140
- async onStart(context: RuntimeContext): Promise<void> {
141
- console.log("Agent started, execution:", context.execution.executionId)
142
- }
127
+ import {
128
+ BaseAgentParticipant,
129
+ Participant,
130
+ ContextItem,
131
+ UserMessageItem,
132
+ FunctionCallItem,
133
+ AgenticEnvironment,
134
+ ModelContext,
135
+ GenerativeModel,
136
+ InputItemSource,
137
+ InferenceRunner,
138
+ FunctionCallRunner,
139
+ } from "@mozaik-ai/core"
143
140
 
144
- async afterInference(response: InferenceResponse): Promise<void> {
145
- console.log("Inference complete, tokens:", response.tokenUsage)
146
- }
141
+ export class ReactiveAgent extends BaseAgentParticipant {
142
+ constructor(
143
+ inputSource: InputItemSource,
144
+ inferenceRunner: InferenceRunner,
145
+ functionCallRunner: FunctionCallRunner,
146
+ private readonly environment: AgenticEnvironment,
147
+ private readonly context: ModelContext,
148
+ private readonly model: GenerativeModel,
149
+ ) {
150
+ super(inputSource, inferenceRunner, functionCallRunner)
151
+ }
152
+
153
+ async onContextItem(source: Participant, item: ContextItem): Promise<void> {
154
+ if (source === this) return
155
+
156
+ this.context.addContextItem(item)
157
+
158
+ if (item instanceof UserMessageItem) {
159
+ this.runInference(this.environment, this.context, this.model)
160
+ return
161
+ }
162
+
163
+ if (item instanceof FunctionCallItem) {
164
+ this.executeFunctionCall(this.environment, item)
165
+ return
166
+ }
167
+ }
147
168
  }
148
-
149
- const agent = new MyAgent()
150
- agent.setInferenceVisitor(new TelemetryVisitor())
151
169
  ```
152
170
 
153
- Use `InferenceVisitor` for concerns that should stay separate from agent logic: logging, telemetry, streaming responses to a UI, or recording usage metrics.
171
+ Two things to note:
154
172
 
155
- ---
156
-
157
- ### Model Context
158
-
159
- `ModelContext` is the structured input passed to the model. It is composed of ordered **`ContextItem`** objects — each representing a single unit of context.
160
-
161
- **Client-provided items** (you add these):
173
+ 1. The agent never `await`s its own capability calls inside `onContextItem` — the methods are non-blocking, so the environment keeps delivering items while inference and tool execution run in the background.
174
+ 2. Behaviors compose by **reaction**, not orchestration. Add a second agent that listens for `ModelMessageItem`s and you get a critique loop. Add a `TranscriptLogger` and you get a UI stream. Neither change touches the existing participants.
162
175
 
163
- - `UserMessage` — the user's turn
164
- - `DeveloperMessage` — system instructions
165
- - `FunctionCallOutput` — tool result fed back to the model
176
+ ---
166
177
 
167
- **Model-produced items** (appended by the loop):
178
+ ## Context and models (reference)
168
179
 
169
- - `ModelMessage` — the model's final text response
170
- - `FunctionCall` — a tool invocation requested by the model
171
- - `Reasoning` — the model's internal reasoning trace
180
+ `ModelContext` is the ordered list of `ContextItem`s a `GenerativeModel` is asked to reason over. It is constructed and mutated explicitly — typically inside a participant in response to delivered items.
172
181
 
173
182
  ```ts
174
- import { ModelContext, DeveloperMessage, UserMessage, InMemoryContextRepository } from "@mozaik-ai/core"
183
+ import {
184
+ ModelContext,
185
+ DeveloperMessageItem,
186
+ UserMessageItem,
187
+ InMemoryModelContextRepository,
188
+ } from "@mozaik-ai/core"
175
189
 
176
190
  const context = ModelContext.create("project-id")
177
- .addContextItem(DeveloperMessage.create("You are a helpful assistant."))
178
- .addContextItem(UserMessage.create("What is the capital of France?"))
191
+ .addContextItem(DeveloperMessageItem.create("You are a helpful assistant."))
192
+ .addContextItem(UserMessageItem.create("What is the capital of France?"))
179
193
 
180
194
  const repo = new InMemoryModelContextRepository()
181
195
  await repo.save(context)
182
-
183
- // later...
184
- const restored = await repo.getByProjectId("project-id")
185
196
  ```
186
197
 
187
- ModelContext can be persisted and restored via `ModelContextRepository`. The built-in `InMemoryModelContextRepository` is suitable for development; implement `ModelContextRepository` to connect any storage backend.
188
-
189
- ---
190
-
191
- ### OpenResponses
192
-
193
- `OpenAIResponses` is the framework's inference provider. It implements the **OpenResponses** specification ([openresponses.org](https://www.openresponses.org/)), mapping Mozaik's typed `ModelContext` to the OpenAI Responses API and back.
194
-
195
- It is the default provider used by `AgentRuntime` and handles:
196
-
197
- - Serializing `ContextItem` objects to API-compatible input
198
- - Deserializing model output into `ModelMessage`, `FunctionCall`, and `Reasoning` items
199
- - Extracting token usage from the response
198
+ Implement `ModelContextRepository` to plug in any storage backend.
200
199
 
201
- You can use it directly for single-shot inference outside the agent loop:
200
+ The default OpenAI provider is `OpenAIResponses`, implementing the [OpenResponses](https://www.openresponses.org/) spec. It maps `ModelContext` to the OpenAI Responses API and back into typed `ContextItem`s. Bundled models: `Gpt54`, `Gpt54Mini`, `Gpt54Nano`.
202
201
 
203
202
  ```ts
204
203
  import { OpenAIResponses, InferenceRequest, Gpt54 } from "@mozaik-ai/core"
205
204
 
206
- const openaiResponses = new OpenAIResponses()
207
- const request = new InferenceRequest(new Gpt54(), context)
208
- const response = await openaiResponses.infer(request)
209
-
210
- context.applyModelOutput(response.contextItems)
205
+ const runtime = new OpenAIResponses()
206
+ const response = await runtime.infer(new InferenceRequest(new Gpt54(), context))
211
207
  ```
212
208
 
213
- Available models: `Gpt54`, `Gpt54Mini`, `Gpt54Nano`.
214
-
215
209
  ---
216
210
 
217
- ### AgentSociety _(experimental)_
211
+ ## Advanced: overriding generators
218
212
 
219
- `AgentSociety` is the framework's answer to one of the core limitations in today's agent systems: **agents that block on each other**. Most multi-agent frameworks run agents sequentially, which creates bottlenecks and prevents true parallelism.
213
+ `BaseAgentParticipant` and `BaseHumanParticipant` are deliberately thin shells around three generator interfaces. Swap any of them to change *how* items are produced without touching the environment, the participants, or any consumers.
220
214
 
221
- `AgentSociety` runs agents **concurrently and non-blocking**. When `enter()` is called, every agent in the society starts its execution independently — none waits for another to finish.
215
+ ### Custom `InputItemSource`
222
216
 
223
217
  ```ts
224
- import { AgentSociety, AgentRuntime } from "@mozaik-ai/core"
225
-
226
- const society = new AgentSociety("research-team")
218
+ import {
219
+ InputItemSource,
220
+ UserMessageItem,
221
+ DeveloperMessageItem,
222
+ SystemMessageItem,
223
+ } from "@mozaik-ai/core"
227
224
 
228
- society.join(new ResearchAgent())
229
- society.join(new SummaryAgent())
230
- society.join(new FactCheckAgent())
225
+ type InputItem = UserMessageItem | DeveloperMessageItem | SystemMessageItem
226
+
227
+ export class QueueInputSource implements InputItemSource {
228
+ private readonly queue: InputItem[] = []
229
+ private resolveNext?: () => void
230
+
231
+ push(item: InputItem) {
232
+ this.queue.push(item)
233
+ this.resolveNext?.()
234
+ this.resolveNext = undefined
235
+ }
236
+
237
+ async *stream(signal?: AbortSignal): AsyncIterable<InputItem> {
238
+ while (!signal?.aborted) {
239
+ while (this.queue.length > 0) {
240
+ yield this.queue.shift()!
241
+ }
242
+ await new Promise<void>((resolve) => (this.resolveNext = resolve))
243
+ }
244
+ }
245
+ }
246
+ ```
231
247
 
232
- // Start the society's event loop
233
- society.start()
248
+ Use it for stdin, websockets, an HTTP queue, or anything that produces user/developer/system messages over time.
234
249
 
235
- // All three agents run in parallel on the same context
236
- society.enter("Analyze the impact of AI on software development", model, context)
250
+ ### Custom `InferenceRunner`
237
251
 
238
- // Shut down when done
239
- society.stop()
240
- ```
252
+ 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:
241
253
 
242
- All agents receive the same `ModelContext`, enabling **collaborative context building** — each agent's output is visible to others through the shared context as the execution progresses.
254
+ ```ts
255
+ import {
256
+ InferenceRunner,
257
+ InferenceRequest,
258
+ ModelContext,
259
+ GenerativeModel,
260
+ OpenAIResponses,
261
+ ReasoningItem,
262
+ FunctionCallItem,
263
+ ModelMessageItem,
264
+ } from "@mozaik-ai/core"
243
265
 
244
- > **Experimental:** `AgentSociety` is in active development. The API is stable enough to build on, but expect refinements — particularly around context isolation, coordination primitives, and agent-to-agent messaging. Feedback and contributions are welcome.
266
+ type InferenceItem = ReasoningItem | FunctionCallItem | ModelMessageItem
267
+
268
+ export class OpenAIInferenceRunner implements InferenceRunner {
269
+ private readonly runtime = new OpenAIResponses()
270
+
271
+ async *run(
272
+ context: ModelContext,
273
+ model: GenerativeModel,
274
+ signal?: AbortSignal,
275
+ ): AsyncIterable<InferenceItem> {
276
+ const response = await this.runtime.infer(new InferenceRequest(model, context))
277
+ for (const item of response.contextItems) {
278
+ yield item as InferenceItem
279
+ }
280
+ }
281
+ }
282
+ ```
245
283
 
246
- ---
284
+ Replace the body with a streaming runtime and items will flow into the environment as soon as the model produces them.
247
285
 
248
- ## Full Example
286
+ ### Custom `FunctionCallRunner`
249
287
 
250
- End-to-end example with a tool-calling agent and hook observability:
288
+ Resolve a `FunctionCallItem` against a tool registry and yield its output:
251
289
 
252
290
  ```ts
253
291
  import {
254
- Agent,
255
- AgentRuntime,
256
- HookId,
257
- ModelContext,
258
- DeveloperMessage,
259
- Gpt54Mini,
260
- InMemoryModelContextRepository,
261
- Tool,
292
+ FunctionCallRunner,
293
+ FunctionCallItem,
294
+ FunctionCallOutputItem,
295
+ Tool,
262
296
  } from "@mozaik-ai/core"
263
297
 
264
- // Define a tool
265
- const weatherTool = new Tool({
266
- name: "get_weather",
267
- description: "Get the current weather for a city",
268
- parameters: {
269
- type: "object",
270
- properties: { city: { type: "string" } },
271
- required: ["city"],
272
- },
273
- invoke: async ({ city }) => ({ temperature: "22°C", condition: "sunny" }),
274
- })
298
+ export class ToolRegistryFunctionCallRunner implements FunctionCallRunner {
299
+ constructor(private readonly tools: Tool[]) {}
275
300
 
276
- // Create runtime and wire hooks
277
- const runtime = new AgentRuntime()
301
+ async *run(
302
+ call: FunctionCallItem,
303
+ signal?: AbortSignal,
304
+ ): AsyncIterable<FunctionCallOutputItem> {
305
+ const tool = this.tools.find((t) => t.name === call.name)
306
+ if (!tool) throw new Error(`Unknown tool: ${call.name}`)
278
307
 
279
- runtime.on(HookId.BEFORE_INFERENCE, async (ctx) => {
280
- console.log(`[step ${ctx.execution.stepCount}] calling model...`)
281
- })
308
+ const result = await tool.invoke(JSON.parse(call.args))
309
+ yield FunctionCallOutputItem.create(call.callId, JSON.stringify(result))
310
+ }
311
+ }
312
+ ```
282
313
 
283
- runtime.on(HookId.AFTER_FUNCTION_CALL, async (ctx) => {
284
- console.log("tool result:", ctx.functionCallOutput)
285
- })
314
+ ### Wiring it together
286
315
 
287
- // Create agent
288
- const agent = new Agent(runtime)
316
+ ```ts
317
+ import { BaseAgentParticipant, AgenticEnvironment } from "@mozaik-ai/core"
289
318
 
290
- // Set up model and context
291
- const model = new Gpt54Mini([weatherTool])
292
- const context = ModelContext.create("demo").addContextItem(DeveloperMessage.create("You are a weather assistant."))
319
+ const agent = new BaseAgentParticipant(
320
+ new QueueInputSource(),
321
+ new OpenAIInferenceRunner(),
322
+ new ToolRegistryFunctionCallRunner(tools),
323
+ )
293
324
 
294
- const repo = new InMemoryModelContextRepository()
295
- await repo.save(context)
296
-
297
- // Run
298
- await agent.run("What's the weather like in Tokyo?", model, context)
325
+ agent.join(new AgenticEnvironment())
299
326
  ```
300
327
 
328
+ 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.
329
+
301
330
  ---
302
331
 
303
332
  ## Author & License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mozaik-ai/core",
3
- "version": "3.6.1",
3
+ "version": "3.6.3",
4
4
  "description": "A TypeScript framework for building AI agents",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",