<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JigJoy</title>
    <description>The latest articles on DEV Community by JigJoy (@jigjoy).</description>
    <link>https://dev.to/jigjoy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12673%2F0815940b-2b5b-4cdd-9626-9dd0bf5f8be4.png</url>
      <title>DEV Community: JigJoy</title>
      <link>https://dev.to/jigjoy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jigjoy"/>
    <language>en</language>
    <item>
      <title>Build Your Own CLI Agent: A Step-by-Step Guide</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Tue, 19 May 2026 16:24:01 +0000</pubDate>
      <link>https://dev.to/jigjoy/build-your-own-cli-agent-a-step-by-step-guide-4dhb</link>
      <guid>https://dev.to/jigjoy/build-your-own-cli-agent-a-step-by-step-guide-4dhb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2azjinqj77tkqsjrjegn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2azjinqj77tkqsjrjegn.png" alt="Build Your Own CLI Agent — terminal chat with Ink and Mozaik" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I love JavaScript. Even though I have learned several languages, JavaScript has become my first choice when building new projects. In this post I'll walk through a small &lt;strong&gt;terminal chat app&lt;/strong&gt; where a language model can run shell commands for you — something you can actually run locally. At the end there's a GitHub template if you'd rather clone than copy.&lt;/p&gt;

&lt;p&gt;Two libraries do most of the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/vadimdemedes/ink" rel="noopener noreferrer"&gt;&lt;strong&gt;Ink&lt;/strong&gt;&lt;/a&gt; draws the chat in the terminal — the input box, scrolling text, and key handling — using React so you are not building a TUI from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@mozaik-ai/core" rel="noopener noreferrer"&gt;&lt;strong&gt;Mozaik&lt;/strong&gt;&lt;/a&gt; connects the model, tool calls, and conversation memory. Think of it as the wiring between "the model said something," "a tool finished," and "show that in the UI," so your screen code does not have to know every detail of the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ink&lt;/strong&gt; is what the user sees; &lt;strong&gt;Mozaik&lt;/strong&gt; is what coordinates the agent behind the scenes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;One Node command that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shows a simple chat UI in the terminal.&lt;/li&gt;
&lt;li&gt;Forwards what you type to a hosted language model.&lt;/li&gt;
&lt;li&gt;Lets the model run terminal commands through a single tool (&lt;code&gt;run_command&lt;/code&gt;) when it needs to inspect the machine or run a build.&lt;/li&gt;
&lt;li&gt;Prints the model's replies (and optional "calling a tool…" hints) in that same UI — without pushing API calls into every component.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is a clean split: the terminal view stays simple; the agent and tools stay in one place and are easier to test or swap later.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the pieces fit together
&lt;/h2&gt;

&lt;p&gt;Mozaik runs everything on a shared &lt;strong&gt;AgenticEnvironment&lt;/strong&gt;: when the model speaks, asks for a tool, or a tool returns a result, every piece that has joined that environment can hear about it. For this CLI you mainly care about two kinds of participant:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Typical base class&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BaseAgentParticipant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remembers the conversation, asks the model for the next step, and runs tools (like &lt;code&gt;run_command&lt;/code&gt;) when the model asks for them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observer / UI bridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BaseObserverParticipant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Listens for assistant text and tool activity from the agent and forwards it into Ink through small callbacks — so the screen updates when the model speaks or starts a tool, without owning the agent loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this project, typing in Ink calls &lt;code&gt;session.send&lt;/code&gt;, which hands your text to the agent. You could add other participant types later (for example streaming stdin), but a straight send-to-agent path keeps the tutorial easy to follow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐     session.send()      ┌──────────────────┐
│  Ink (app)  │ ──────────────────────► │  TerminalAgent   │
└─────────────┘                         │  (agent loop)    │
       ▲                                └────────┬─────────┘
       │ callbacks                            │ join()
       │                                        ▼
┌─────────────┐                         ┌──────────────────┐
│  UIUpdater  │ ◄── onExternal* ─────── │ AgenticEnvironment│
│  (observer) │                         └──────────────────┘
└─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1 — Bootstrap the runtime (&lt;code&gt;cli.tsx&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The entry file is intentionally tiny: load environment variables, maybe print a one-line usage hint, then hand off to Ink with &lt;code&gt;render(&amp;lt;App /&amp;gt;)&lt;/code&gt;. Keep agent logic out of here — only bootstrapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;meow&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileURLToPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;quiet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;meow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`
    Usage
      $ your-cli

    Starts an interactive chat with the agent.
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;importMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Compose the session (&lt;code&gt;session.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;This file is the "control room." You create the model, the conversation memory, the thing that runs tools, and the shared environment, then plug in your agent and your UI helper. Everything with awkward names (&lt;code&gt;OpenAIInferenceRunner&lt;/code&gt;, &lt;code&gt;ModelContext&lt;/code&gt;, and so on) lives here so the React side stays small. When you are done, the UI only needs one method: &lt;code&gt;send(message)&lt;/code&gt;, forwarded to the agent's &lt;code&gt;onMessage&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Gpt54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;OpenAIInferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DefaultFunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;terminalTools&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./terminal/tools.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TerminalAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./terminal/agent.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UIUpdater&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./ui-updater.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AgentSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AgentListeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onAssistantText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentListeners&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AgentSession&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultFunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;terminalTools&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIInferenceRunner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cli-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Gpt54&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTools&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;terminalTools&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TerminalAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uiUpdater&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UIUpdater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;uiUpdater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — The agent loop (&lt;code&gt;terminal/agent.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;This is the heart of the app. When someone sends a message, you &lt;strong&gt;record it&lt;/strong&gt;, let the &lt;strong&gt;model think&lt;/strong&gt;, and if it wants to run a tool you &lt;strong&gt;run it&lt;/strong&gt; and feed the result back — then the model gets another turn until it answers in plain language.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On new user text → add to conversation and ask for the next model response.&lt;/li&gt;
&lt;li&gt;When the model asks for a tool → remember the call is in flight, run it, store the outcome.&lt;/li&gt;
&lt;li&gt;When every outstanding tool has finished → ask the model again so it can either reply or request another step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no Ink or terminal drawing in this file — only memory and orchestration — so you can change the UI later without touching the agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BaseAgentParticipant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UserMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DeveloperMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;programmaticAgentInputStub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TerminalAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseAgentParticipant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;pendingCalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;programmaticAgentInputStub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;developerMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DeveloperMessageItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`You are a terminal agent. You can run commands in the terminal to help the user with their request.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;developerMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserMessageItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onFunctionCallOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;callId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pendingCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Tools the model can use (&lt;code&gt;terminal/tools.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Tools are how you tell the model what it is allowed to do outside of chat text. Each tool has a name, a short description the model can read, argument shapes, and an &lt;code&gt;invoke&lt;/code&gt; function that runs on your machine.&lt;/p&gt;

&lt;p&gt;Here we expose one tool: &lt;strong&gt;&lt;code&gt;run_command&lt;/code&gt;&lt;/strong&gt;, which executes a shell command and returns output so the model can use it on its next turn.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Terminal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./terminal.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Terminal&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminalTools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run_command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run a command in the terminal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The command to run in the terminal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The current working directory.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cwd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5 — Feed the terminal UI (&lt;code&gt;ui-updater.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The observer sits between Mozaik and Ink. When the agent produces text the user should see, this class forwards it through a callback; when a tool starts, it can add a small status line (for example "calling &lt;code&gt;run_command&lt;/code&gt;").&lt;/p&gt;

&lt;p&gt;You stay subscribed to &lt;strong&gt;external&lt;/strong&gt; events so you hear what the agent is doing, not duplicate the agent's own work — one clear owner of the loop and one clear owner of the display.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;BaseObserverParticipant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Listeners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onAssistantText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UIUpdater&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseObserverParticipant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Listeners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onExternalFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;onExternalModelMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onAssistantText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6 — Wire Ink (&lt;code&gt;app.tsx&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;The Ink layer holds chat history in normal React state, builds the session once so you do not reconnect on every render, and on submit appends the user message then calls &lt;code&gt;session.send&lt;/code&gt;. Anything the observer hears arrives through the callbacks you passed in when creating the session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createAgentSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./session.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useApp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appendMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;createAgentSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;onAssistantText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;appendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;appendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`calling tool: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;appendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// …render messages + &amp;lt;TextInput onSubmit={handleSubmit} /&amp;gt; …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7 — Credentials and build
&lt;/h2&gt;

&lt;p&gt;Put your API key in &lt;code&gt;.env&lt;/code&gt; (the template expects &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;; check Mozaik if you change model or provider). Then install, build, and run the compiled CLI (or &lt;code&gt;npm link&lt;/code&gt; if you want a global command).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# add OPENAI_API_KEY=sk-...&lt;/span&gt;

npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Starter repository
&lt;/h2&gt;

&lt;p&gt;Prefer a working tree over copy-paste? There is a template repo with the same layout this article walks through — agent, observer, tools, and Ink UI already split into files.&lt;/p&gt;

&lt;p&gt;Scaffold a fresh project &lt;strong&gt;without&lt;/strong&gt; copying the template's full git history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx degit jigjoy-ai/cli-agent-starter my-cli-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;my-cli-agent
git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;jigjoy-ai/cli-agent-starter&lt;/code&gt; with your fork or canonical URL if it moves; replace &lt;code&gt;my-cli-agent&lt;/code&gt; with your package name. Then edit &lt;code&gt;package.json&lt;/code&gt; (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;bin&lt;/code&gt;), tweak &lt;code&gt;source/cli.tsx&lt;/code&gt; / &lt;code&gt;source/app.tsx&lt;/code&gt; branding, and start adding participants and tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub alternative:&lt;/strong&gt; enable &lt;strong&gt;Template repository&lt;/strong&gt; in the repo settings and use &lt;strong&gt;Use this template&lt;/strong&gt; — you get a first commit snapshot with a clean history for a new repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starter template:&lt;/strong&gt; &lt;a href="https://github.com/jigjoy-ai/cli-agent-starter" rel="noopener noreferrer"&gt;github.com/jigjoy-ai/cli-agent-starter&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;BaseHumanParticipant&lt;/code&gt; if you want stdin streaming as first-class &lt;code&gt;InputStream&lt;/code&gt; input.&lt;/li&gt;
&lt;li&gt;Add a second &lt;code&gt;BaseAgentParticipant&lt;/code&gt; and use &lt;code&gt;onExternal*&lt;/code&gt; handlers to log or aggregate multi-agent chatter.&lt;/li&gt;
&lt;li&gt;Swap &lt;code&gt;Gpt54&lt;/code&gt; / runners for other models supported by Mozaik as the ecosystem grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have a concise path from "blank Node project" to &lt;strong&gt;Ink front end + Mozaik event bus + tool-running agent&lt;/strong&gt; — with a degit-friendly repo to hit the ground running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mozaik:&lt;/strong&gt; &lt;a href="https://github.com/jigjoy-ai/mozaik" rel="noopener noreferrer"&gt;github.com/jigjoy-ai/mozaik&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="https://jigjoy.ai/blog/mozaik-reactive-agents" rel="noopener noreferrer"&gt;Reactive Agents in Mozaik&lt;/a&gt; · &lt;a href="https://jigjoy.ai/blog/mozaik-3-agentic-environment" rel="noopener noreferrer"&gt;Mozaik 3.0 — Agentic Environment&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read the full article on JigJoy: &lt;a href="https://jigjoy.ai/blog/build-your-own-cli-agent" rel="noopener noreferrer"&gt;jigjoy.ai/blog/build-your-own-cli-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>cli</category>
      <category>mozaik</category>
    </item>
    <item>
      <title>Reactive Agents, Typed Event Handlers, and Agent Swarms: What's New in Mozaik</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Tue, 19 May 2026 13:31:10 +0000</pubDate>
      <link>https://dev.to/jigjoy/reactive-agents-typed-event-handlers-and-agent-swarms-whats-new-in-mozaik-9mh</link>
      <guid>https://dev.to/jigjoy/reactive-agents-typed-event-handlers-and-agent-swarms-whats-new-in-mozaik-9mh</guid>
      <description>&lt;p&gt;When we released Mozaik v3, we introduced an event-based architecture where participants emit, observe, and react to typed context items inside a shared agentic environment. Since then, the framework has kept moving — and the way we think and talk about it has moved with it.&lt;/p&gt;

&lt;p&gt;This post is a tour of what's new. The headline shift is conceptual: Mozaik is now framed around &lt;strong&gt;reactive agents&lt;/strong&gt; — collaborative agents that adapt to their environment and to each other in real time. Underneath that frame, the API has gotten smaller, clearer, and easier to read.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Non-Blocking to Reactive
&lt;/h2&gt;

&lt;p&gt;Mozaik agents have always been non-blocking. That's still true — capability methods stream events as they're produced, so a slow inference or tool call never holds up other participants. But "non-blocking" describes a property, not a way of thinking.&lt;/p&gt;

&lt;p&gt;The way we now think about Mozaik agents is &lt;strong&gt;reactive&lt;/strong&gt;: an agent declares which events it cares about and reacts to them as they arrive — messages from humans, function calls from its own model, reasoning traces from a peer agent, or tool outputs from somewhere else in the environment. Behavior emerges from how agents react, not from a central controller deciding whose turn it is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A reactive agent is event-driven, collaborative, and adapts to its environment and to other agents in real time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This shift in framing pays off in design. Once an agent is a thing that reacts, adding behavior to a system stops being about editing a pipeline and becomes about &lt;strong&gt;joining another participant&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Typed Event Handlers
&lt;/h2&gt;

&lt;p&gt;The biggest API change since 3.0 is on the participant side. In the original release, a participant exposed a single intercept point: &lt;code&gt;onContextItem(source, item)&lt;/code&gt;. You looked at the item, decided whether it came from you or someone else, and branched accordingly.&lt;/p&gt;

&lt;p&gt;That worked, but it pushed a lot of dispatch logic into every agent. So we replaced the single intercept with a set of &lt;strong&gt;typed handlers&lt;/strong&gt;, one per event kind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onMessage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onFunctionCall&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onFunctionCallOutput&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onReasoning&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onModelMessage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each handler defaults to a no-op in the base class, so a reactive agent only writes the ones it actually cares about. The result is shorter, more declarative code — a reactive agent reads like a list of "when this happens, I do that," not like a switch statement.&lt;/p&gt;

&lt;p&gt;Here is a full reactive agent built on top of &lt;code&gt;BaseAgentParticipant&lt;/code&gt;. It records incoming messages, runs inference, executes its own tool calls, and keeps its local context in sync with what the model produces — all by overriding the handlers it cares about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// reactive-agent.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BaseAgentParticipant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;UserMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ReasoningItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReactiveAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseAgentParticipant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;inputSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InputStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inferenceRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionCallRunner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// A message from a human (or any other participant) — record it and think.&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UserMessageItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// The agent just produced a function call — execute it.&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// The tool just produced an output — feed it back and run inference again.&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onFunctionCallOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Keep the local context in sync with model-emitted reasoning and replies.&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onReasoning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReasoningItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onModelMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For comparison, the old style looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: one handler, manual dispatch&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContextItem&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContextItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;UserMessageItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Self vs. External Split
&lt;/h2&gt;

&lt;p&gt;For every typed handler, there are two variants: a &lt;strong&gt;self&lt;/strong&gt; handler that fires when this participant emits the event, and an &lt;strong&gt;external&lt;/strong&gt; handler (prefixed with &lt;code&gt;onExternal&lt;/code&gt;) that fires when another participant emits it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Self (your emissions)&lt;/th&gt;
&lt;th&gt;External (everyone else)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onFunctionCall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onExternalFunctionCall&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onFunctionCallOutput&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onExternalFunctionCallOutput&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onReasoning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onExternalReasoning&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onModelMessage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onExternalModelMessage&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical effect: a reactive agent can encode "act on my own outputs" separately from "observe others" — no more manual &lt;code&gt;source === this&lt;/code&gt; checks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want to build a &lt;strong&gt;critic&lt;/strong&gt; that reviews a peer agent's answers? Override &lt;code&gt;onExternalModelMessage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Want to feed your model's own tool calls back into a runner? Override &lt;code&gt;onFunctionCall&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two concerns stop bleeding into each other.&lt;/p&gt;

&lt;p&gt;As a concrete example, here is a passive participant that only listens to external events. It does not run inference and does not execute tools — it just watches what other participants emit and logs it. Drop it into any environment and you have a live transcript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// transcript-logger.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ReasoningItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TranscriptLogger&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Participant&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[message]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onExternalFunctionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] function_call`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onExternalFunctionCallOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCallOutputItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] function_call_output`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onExternalReasoning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReasoningItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] reasoning`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onExternalModelMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Participant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelMessageItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] model_message`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJSON&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Plain Messages on the Bus
&lt;/h2&gt;

&lt;p&gt;Conversational text used to flow through the same context-item pipeline as everything else. It worked, but it was heavier than it needed to be. Now there are two clean lanes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Messages&lt;/strong&gt; — plain strings, delivered via &lt;code&gt;onMessage(message: string)&lt;/code&gt;. Use them for what a human types, what one agent says to another, and any other free-form text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ContextItems&lt;/strong&gt; — typed model internals (function calls, function call outputs, reasoning items, model messages), delivered via their dedicated handlers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The participant side mirrors this: the old &lt;code&gt;InputItemSource&lt;/code&gt; has become &lt;code&gt;InputStream&lt;/code&gt;, an async iterable of strings. Each yielded string fans out via &lt;code&gt;onMessage&lt;/code&gt; to every other participant. Inference and tool runners still produce typed ContextItems, exactly as before.&lt;/p&gt;

&lt;p&gt;The benefit is mostly clarity. When you read a reactive agent now, you can tell at a glance whether a handler is reacting to "someone said something" or to "the model produced a tool call."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;InputStream&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Async iterable of plain strings — each yield becomes onMessage for peers.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;humanInput&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;InputStream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Add rate limiting to all API endpoints&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Focus on the NestJS backend first&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Bigger Model Lineup
&lt;/h2&gt;

&lt;p&gt;The default OpenAI provider now ships a wider model lineup. In addition to &lt;code&gt;Gpt54&lt;/code&gt;, &lt;code&gt;Gpt54Mini&lt;/code&gt;, and &lt;code&gt;Gpt54Nano&lt;/code&gt;, there is now &lt;code&gt;Gpt55&lt;/code&gt;. Same &lt;code&gt;OpenAIResponses&lt;/code&gt; runtime, same OpenResponses-aligned types — pick the model that fits the job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Gpt55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Gpt54Mini&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Heavier reasoning for planning; lighter model for review passes.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;planner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Gpt55&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviewer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Gpt54Mini&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  From One Agent to a Swarm
&lt;/h2&gt;

&lt;p&gt;The reactive frame really earns its keep when more than one agent shares an environment. We've started calling these collaborative groups &lt;strong&gt;agent swarms&lt;/strong&gt;: a handful of focused reactive agents — planner, executors, reviewer, and observers — all joining one &lt;code&gt;AgenticEnvironment&lt;/code&gt; and reacting to each other's events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnck46rpbnizcbnn8xt8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnck46rpbnizcbnn8xt8i.png" alt="Agent swarms — multiple reactive agents collaborating in one agentic environment" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agent swarms — multiple reactive agents collaborating in one agentic environment&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The clearest production example is &lt;a href="https://github.com/jigjoy-ai/baro" rel="noopener noreferrer"&gt;&lt;strong&gt;baro&lt;/strong&gt;&lt;/a&gt;, a Claude agent orchestrator built on Mozaik. Ten specialized participants — planner, executors, reviewer, fixer, librarian, auditor, and more — work fully concurrently on the same goal, like a team collaborating in real time instead of a single agent doing everything alone.&lt;/p&gt;

&lt;p&gt;Adding a new role to a swarm doesn't mean editing a central controller. It means writing one more reactive agent and &lt;code&gt;join()&lt;/code&gt;ing it to the environment. Everything else keeps working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgenticEnvironment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mozaik-ai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AgenticEnvironment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;critic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TranscriptLogger&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Composing Intelligence
&lt;/h2&gt;

&lt;p&gt;Each of these changes is small on its own. Together, they shift Mozaik further toward what we wanted from the start: a framework where intelligent behavior is something you &lt;strong&gt;compose&lt;/strong&gt;, not something you &lt;strong&gt;orchestrate&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;What it unlocks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reactive agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Behavior from event handlers, not a central &lt;code&gt;run()&lt;/code&gt; loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typed handlers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Readable "when X, do Y" agents without dispatch boilerplate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self vs. external&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Critics, observers, and actors coexist without &lt;code&gt;source === this&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plain messages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conversational text separate from model-internal ContextItems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent swarms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Many focused agents on one bus — see &lt;a href="https://github.com/jigjoy-ai/baro" rel="noopener noreferrer"&gt;baro&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reactive agents make the building block explicit. Typed handlers make each agent easier to read. The self vs. external split keeps intent obvious. Plain messages keep the conversational lane clean. And the swarm pattern shows what falls out the other side: groups of agents that adapt to their environment and to each other in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mozaik-ai/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Star on GitHub:&lt;/strong&gt; &lt;a href="https://github.com/jigjoy-ai/mozaik" rel="noopener noreferrer"&gt;github.com/jigjoy-ai/mozaik&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="https://jigjoy.ai/blog/mozaik-3-agentic-environment" rel="noopener noreferrer"&gt;Mozaik 3.0 — Structured Context &amp;amp; the Agentic Environment&lt;/a&gt; · &lt;/p&gt;

&lt;p&gt;Read the full article on JigJoy: &lt;a href="https://jigjoy.ai/blog/mozaik-reactive-agents" rel="noopener noreferrer"&gt;jigjoy.ai/blog/mozaik-reactive-agents&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>mozaik</category>
    </item>
    <item>
      <title>Structured Context, Context Memory, Context Item Generators, and the Agentic Environment</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Tue, 05 May 2026 14:43:29 +0000</pubDate>
      <link>https://dev.to/jigjoy/structured-context-context-memory-context-item-generators-and-the-agentic-environment-496l</link>
      <guid>https://dev.to/jigjoy/structured-context-context-memory-context-item-generators-and-the-agentic-environment-496l</guid>
      <description>&lt;p&gt;Mozaik started as a multi-provider library. With version 3.0.0, we are evolving it into a full TypeScript framework for building non-blocking AI agents.&lt;/p&gt;

&lt;p&gt;This release introduces an event-based architecture designed around inversion of control. Instead of forcing developers into rigid, sequential pipelines, Mozaik enables humans, agents, observers, and tools to collaborate inside a shared agentic environment. Participants can emit, observe, and react to typed context items in real time, making multi-agent orchestration more flexible, composable, and concurrent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured Context
&lt;/h2&gt;

&lt;p&gt;Mozaik 3 introduces a structured, multi-provider context model compatible with the OpenResponses specification.&lt;/p&gt;

&lt;p&gt;Instead of treating prompts as plain strings, Mozaik models context as typed items, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SystemMessageItem&lt;/li&gt;
&lt;li&gt;DeveloperMessageItem&lt;/li&gt;
&lt;li&gt;UserMessageItem&lt;/li&gt;
&lt;li&gt;FunctionCallItem&lt;/li&gt;
&lt;li&gt;FunctionCallOutputItem&lt;/li&gt;
&lt;li&gt;ReasoningItem&lt;/li&gt;
&lt;li&gt;ModelMessageItem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flubvgnallo6rn7bou4z5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flubvgnallo6rn7bou4z5.png" alt=" " width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These context items help developers provide clearer, more precise instructions to language models. For example, combining system, developer, and user messages gives the model a better-organized context than placing everything into a single user message.&lt;/p&gt;

&lt;p&gt;This structure also makes model interactions easier to inspect, store, replay, and adapt across providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Memory
&lt;/h2&gt;

&lt;p&gt;Mozaik now models context as an aggregate of ordered context items through ModelContext.&lt;/p&gt;

&lt;p&gt;This gives developers a structured foundation for both short-term working memory and long-term persistence. The framework includes a repository interface, allowing context to be saved and retrieved through different storage backends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fov1vfczklc3tk1czx8sx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fov1vfczklc3tk1czx8sx.png" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mozaik provides an in-memory implementation for working memory, while developers can implement their own repositories for file systems, databases, or other persistence layers.&lt;/p&gt;

&lt;p&gt;This approach keeps memory explicit, portable, and provider-independent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Item Generators
&lt;/h2&gt;

&lt;p&gt;Mozaik 3 introduces asynchronous context item generators for key parts of the framework, including input handling, inference, and function call execution.&lt;/p&gt;

&lt;p&gt;Instead of relying on blocking await chains, runners can produce context items over time through async iterables. This enables items to be generated and delivered in real time while giving developers control over when and how those streams are consumed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bgzezru9u3hdu85be1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bgzezru9u3hdu85be1w.png" alt=" " width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mozaik provides default implementations for function call execution and OpenAI inference. Developers can also create their own implementations, which is especially useful for testing, mocking runners, customizing tool execution, or adding support for additional model providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Environment
&lt;/h2&gt;

&lt;p&gt;The main goal of Mozaik 3 is to enable AI agents to collaborate in a non-blocking way.&lt;/p&gt;

&lt;p&gt;We believe that truly agentic systems should not be limited to sequential chains. In many real-world use cases, agents need to observe, react, and contribute concurrently.&lt;/p&gt;

&lt;p&gt;To support this, Mozaik introduces the Participant abstraction and the AgenticEnvironment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Participants join the same environment and react to typed context items as they arrive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Participants, including humans and agents, join the same environment and subscribe to context items generated by other participants. When one participant emits a context item, other participants can observe it, react to it, ignore it, or use it to update their own internal context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0hpel8ikevbx9ae4km4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0hpel8ikevbx9ae4km4.png" alt=" " width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or example, a human can send a UserMessageItem into the environment. One or more agents can react by running inference, producing model messages, requesting function calls, or incorporating the new item into their own context. Other agents can observe those outputs and respond with their own contributions.&lt;/p&gt;

&lt;p&gt;This enables agents to work in parallel and remain aware of what other participants are doing during inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default Participants
&lt;/h2&gt;

&lt;p&gt;To make multi-agent development faster, Mozaik 3 also includes default participant implementations:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;BaseAgentParticipant&lt;br&gt;
BaseHumanParticipant&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These base classes provide common capabilities for generating, receiving, and reacting to context items, so developers can set up multi-agent systems with less boilerplate while still keeping full control over the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Foundation for Multi-Agent Systems
&lt;/h2&gt;

&lt;p&gt;Mozaik 3 moves beyond simple provider abstraction.&lt;/p&gt;

&lt;p&gt;It introduces a framework-level architecture for building reactive, non-blocking, context-aware AI systems. By combining structured context, explicit memory, async context item generation, and a shared agentic environment, Mozaik gives developers the building blocks for more flexible and scalable multi-agent applications.&lt;/p&gt;

&lt;p&gt;With version 3.0.0, Mozaik becomes a foundation for building agents that do not just run in sequence, but collaborate through shared context in real time.&lt;/p&gt;

&lt;p&gt;Star on GitHub: &lt;a href="https://github.com/jigjoy-ai/mozaik" rel="noopener noreferrer"&gt;https://github.com/jigjoy-ai/mozaik&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why Agent Frameworks End Up As SDK Wrappers - And How To Overcome It</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Thu, 16 Apr 2026 07:48:43 +0000</pubDate>
      <link>https://dev.to/jigjoy/why-agent-frameworks-end-up-as-sdk-wrappers-and-how-to-overcome-it-51j9</link>
      <guid>https://dev.to/jigjoy/why-agent-frameworks-end-up-as-sdk-wrappers-and-how-to-overcome-it-51j9</guid>
      <description>&lt;p&gt;Today, most frameworks for building AI agents are missing something fundamental. If you look closely at the language they use, you'll notice a pattern: their domain models are anemic. They give you abstractions like "agent", "tool", "step", but they don't actually model the thing that matters most - context. Because of that, developers are left on their own to deal with problems like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context window overflow&lt;/li&gt;
&lt;li&gt;context bloating&lt;/li&gt;
&lt;li&gt;loss of structure across multiple model calls&lt;/li&gt;
&lt;li&gt;messy handling of tool outputs and reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And where does all of that logic end up? In your application layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost: Polluting Your Domain
&lt;/h2&gt;

&lt;p&gt;Instead of focusing on your actual domain (finance, healthcare, internal tooling, etc.), you start writing code like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;guessing what the model "needs to see" next&lt;/li&gt;
&lt;li&gt;use your own way and schema to persist context&lt;/li&gt;
&lt;li&gt;load context with schema which is not most efficient way to do it&lt;/li&gt;
&lt;li&gt;and so on...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not your domain. It's not even context engineering. In the absence of the right abstractions, developers are pushed to reimplement core LLM concepts themselves - while mixing them with their own domain logic. And this is where complexity arises. We experienced these issues firsthand. That’s what pushed us to address them—so engineers like us can extract more from LLMs and open up new possibilities.&lt;/p&gt;

&lt;p&gt;The goal with Mozaik is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Enable developers to use a rich domain model for handling context in agentic applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So instead of letting LLM concerns leak into your domain, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep your domain logic isolated and aligned with best practices&lt;/li&gt;
&lt;li&gt;use standardized building blocks to build your own context model&lt;/li&gt;
&lt;li&gt;don't spend time reinventing the wheel&lt;/li&gt;
&lt;li&gt;and hopefully, enjoy the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, this is a space we're actively learning in. LLMs are still evolving, and we want to both learn and share what we discover while working on these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point: OpenResponses
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysv7moyuo16voti82b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysv7moyuo16voti82b5.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We didn't start from scratch.&lt;/p&gt;

&lt;p&gt;Our starting point is the OpenResponses specification, published by companies like OpenAI, OpenRouter, Vercel and others in January this year. Their goal is to standardize how we work with LLM providers. They define a shared structure that reflects how models actually operate.&lt;/p&gt;

&lt;p&gt;At its core:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Context is composed of context items.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-created items
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;user message&lt;/li&gt;
&lt;li&gt;developer message&lt;/li&gt;
&lt;li&gt;function call output&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Model-generated items
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;reasoning&lt;/li&gt;
&lt;li&gt;function call&lt;/li&gt;
&lt;li&gt;model message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They also introduce an important idea:&lt;/p&gt;

&lt;p&gt;Model-generated items are state machines that can be streamed with semantic events.&lt;/p&gt;

&lt;p&gt;Those are the fundamental building blocks of the OpenResponses specification and how major LLM providers implement them. For a deeper dive, you can check: &lt;a href="https://www.openresponses.org/" rel="noopener noreferrer"&gt;https://www.openresponses.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3g16u5ulj76jvqh2fq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3g16u5ulj76jvqh2fq8.png" alt=" " width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Take on This
&lt;/h2&gt;

&lt;p&gt;OpenResponses gives us the source of truth for how LLMs work today. These building blocks should not be ignored. But the specification itself is not enough. Developers still need a way to work with it in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Mozaik
&lt;/h2&gt;

&lt;p&gt;Our approach is to take this specification and turn it into a rich object domain model. The goal is not to abstract everything away, but to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make context explicit&lt;/li&gt;
&lt;li&gt;make it composable&lt;/li&gt;
&lt;li&gt;make it persistent&lt;/li&gt;
&lt;li&gt;make it evolvable across multiple steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With our base implementation, developers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build structured context from typed items&lt;/li&gt;
&lt;li&gt;manage model-generated items (reasoning, function calls, outputs)&lt;/li&gt;
&lt;li&gt;persist context&lt;/li&gt;
&lt;li&gt;restore it and continue execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without leaking context engineering concerns into their core domain logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leads
&lt;/h2&gt;

&lt;p&gt;We see this as a starting point.&lt;/p&gt;

&lt;p&gt;By introducing a richer domain model for context, new opportunities open up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better strategies for context compression&lt;/li&gt;
&lt;li&gt;smarter handling of long-running interactions&lt;/li&gt;
&lt;li&gt;clearer debugging and observability&lt;/li&gt;
&lt;li&gt;more predictable and controllable multi-agent systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Basic Example
&lt;/h2&gt;

&lt;p&gt;Here's a minimal example of building and storing context using Mozaik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contextRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InMemoryContextRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tell me a joke about birds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;developerMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DeveloperMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a joke teller. You will be given a joke and you will need to tell it to the user.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`pr-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;developerMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contextRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GPT54Model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generatedItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contextRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;restoredContexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contextRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByProjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;restoredContexts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This uses an in-memory repository, but in real applications you can plug in your own persistence layer.&lt;/p&gt;

&lt;p&gt;You can find more working examples in the GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jigjoy-ai/mozaik-examples" rel="noopener noreferrer"&gt;github.com/jigjoy-ai/mozaik-examples&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The industry is moving fast. But if we keep ignoring context as a core primitive, we'll keep rebuilding the same fragile systems. Mozaik is our attempt to fix that - by giving context the place it actually deserves. And this is just the beginning. We're excited to see where this journey takes us.&lt;/p&gt;

&lt;p&gt;If you like what we’re building, give Mozaik a ⭐ on GitHub.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jigjoy-ai" rel="noopener noreferrer"&gt;
        jigjoy-ai
      &lt;/a&gt; / &lt;a href="https://github.com/jigjoy-ai/mozaik" rel="noopener noreferrer"&gt;
        mozaik
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Mozaik is an open-source TypeScript framework for building event-driven AI agents.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Mozaik&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Mozaik&lt;/strong&gt; is an open-source TypeScript framework for building AI agents that share an &lt;strong&gt;agentic environment&lt;/strong&gt; instead of being orchestrated through rigid pipelines.&lt;/p&gt;

&lt;p&gt;In Mozaik, humans, agents, observers, and tools are all &lt;code&gt;Participant&lt;/code&gt;s of the same &lt;code&gt;AgenticEnvironment&lt;/code&gt;. Each participant runs &lt;strong&gt;non-blocking&lt;/strong&gt; and streams typed &lt;code&gt;ContextItem&lt;/code&gt;s into the environment. Every other participant sees those items in real time and can react, intercept, or stay silent — without any central scheduler.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn add @mozaik-ai/core&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;API Key Configuration&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="highlight highlight-source-dotenv notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; .env&lt;/span&gt;
&lt;span class="pl-v"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pl-k"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;your-openai-key-here&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The agentic environment&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;AgenticEnvironment&lt;/code&gt; is a broadcast bus for typed context items. &lt;code&gt;Participant&lt;/code&gt;s &lt;code&gt;join()&lt;/code&gt; it, and any item produced by one participant is delivered to every subscriber's &lt;code&gt;onContextItem(source, item)&lt;/code&gt; callback.&lt;/p&gt;


  &lt;div class="js-render-enrichment-target"&gt;
    &lt;div class="render-plaintext-hidden"&gt;
      &lt;pre&gt;flowchart LR
    Human[BaseHumanParticipant] --&amp;gt;|streamInput| Env(("AgenticEnvironment"))
    Agent[BaseAgentParticipant] --&amp;gt;|"runInference / executeFunctionCall"| Env
    Observer[Custom Participant] --&amp;gt;|join| Env
    Env --&amp;gt;|onContextItem| Human
    Env --&amp;gt;|onContextItem| Agent
    Env --&amp;gt;|onContextItem| Observer
&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full"&gt;
    &lt;span&gt;
  
    
    
    &lt;span class="sr-only"&gt;Loading&lt;/span&gt;
&lt;/span&gt;
  &lt;/span&gt;





&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Non-blocking participants&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Mozaik ships two ready-to-use participants:&lt;/p&gt;

&lt;p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
&lt;thead&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;th&gt;Participant&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;Capabilities&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;Pulls from&lt;/th&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/thead&gt;
&lt;br&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/p&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jigjoy-ai/mozaik" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>llm</category>
      <category>ai</category>
      <category>agents</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Spektrum: Turn Natural Language into Live Web Apps (Deploy in Minutes with AI)</title>
      <dc:creator>Hadil Ben Abdallah</dc:creator>
      <pubDate>Tue, 24 Mar 2026 06:01:30 +0000</pubDate>
      <link>https://dev.to/jigjoy/spektrum-turn-natural-language-into-live-web-apps-deploy-in-minutes-with-ai-5292</link>
      <guid>https://dev.to/jigjoy/spektrum-turn-natural-language-into-live-web-apps-deploy-in-minutes-with-ai-5292</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Turn a single prompt into a fully deployed web app in minutes, no setup, no infrastructure, no friction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’ve ever tried to turn an idea into a working product, you already know that writing code is only a small part of the journey. The real friction usually comes from everything around it: setting up the project, deciding on the architecture, connecting services, deploying, and then iterating when things break.&lt;/p&gt;

&lt;p&gt;At some point, what started as an exciting idea slowly turns into a process. And that process often kills momentum.&lt;/p&gt;

&lt;p&gt;But what if you could skip most of that?&lt;/p&gt;

&lt;p&gt;What if you could simply describe what you want to build in plain English and get back a live, deployed web application?&lt;/p&gt;

&lt;p&gt;That’s exactly what &lt;strong&gt;Spektrum&lt;/strong&gt; is designed to do.&lt;/p&gt;

&lt;p&gt;This represents a new wave of AI app generation, where natural language becomes the interface for building real products.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Spektrum?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://jigjoy.ai/spektrum" rel="noopener noreferrer"&gt;Spektrum&lt;/a&gt;&lt;/strong&gt; is a &lt;strong&gt;vibe coding SDK&lt;/strong&gt; that transforms natural language into fully functional, deployed web applications. Instead of manually writing and wiring everything yourself, you define your intent, and the system takes care of execution.&lt;/p&gt;

&lt;p&gt;In practice, that means you can describe an idea, let the AI generate the code, and receive a publicly accessible app URL in return. It’s not just a code generator; it’s a system that handles the entire lifecycle from idea to deployment.&lt;/p&gt;

&lt;p&gt;What makes this interesting is that Spektrum doesn’t stop at generating code snippets. It actually produces complete, runnable applications that you can use, share, or integrate into your own products.&lt;/p&gt;

&lt;p&gt;This makes Spektrum a powerful tool for AI app generation, turning natural language into web apps without traditional setup overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Do with Spektrum
&lt;/h2&gt;

&lt;p&gt;Spektrum opens up a faster way to go from idea to execution without getting blocked by setup or infrastructure decisions. Whether you're exploring a new concept or building something real, it lets you focus on the outcome instead of the process.&lt;/p&gt;

&lt;p&gt;With Spektrum, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn ideas into working web apps instantly
&lt;/li&gt;
&lt;li&gt;Build MVPs without setup overhead
&lt;/li&gt;
&lt;li&gt;Generate production-ready UI from natural language
&lt;/li&gt;
&lt;li&gt;Experiment with new product ideas quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes Spektrum especially powerful for developers who want to move fast without sacrificing real output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters (More Than You Think)
&lt;/h2&gt;

&lt;p&gt;Before AI tooling became mainstream, teams relied heavily on planning methodologies to manage complexity. Approaches like specification by example and domain-driven design helped bridge the gap between ideas and implementation, but they still depended heavily on human coordination.&lt;/p&gt;

&lt;p&gt;Even today, many AI tools inherit similar problems. Context gets lost between steps, developers are still responsible for managing state, and deployment pipelines remain a separate concern.&lt;/p&gt;

&lt;p&gt;Spektrum takes a different approach by collapsing these steps into a single flow. Rather than treating coding, infrastructure, and deployment as separate phases, it handles them as one continuous process.&lt;/p&gt;

&lt;p&gt;Compared to traditional development workflows, Spektrum removes multiple layers of setup, making the path from idea to working product significantly shorter.&lt;/p&gt;

&lt;p&gt;The result is not just faster development, but a fundamentally different way of thinking about building software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jigjoy.ai/spektrum" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore Spektrum&lt;/a&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Features That Make Spektrum Different
&lt;/h2&gt;

&lt;p&gt;Spektrum isn’t just about generating code faster. It introduces a model powered by AI coding agents that handle execution, state, and deployment behind the scenes.&lt;/p&gt;

&lt;p&gt;Here are some of the key capabilities that make that possible:&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding Agents in the Cloud
&lt;/h3&gt;

&lt;p&gt;One of the most important aspects of Spektrum is that it removes the need to manage infrastructure or execution context manually. The system handles state, code generation, and deployment in the background, allowing you to focus entirely on what you want to build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-Time Monitoring
&lt;/h3&gt;

&lt;p&gt;Spektrum also provides visibility into the process. You can track how your application is being generated, monitor deployment progress, and access logs when needed. This makes the system feel less like a black box and more like a collaborative tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Idea to Production in Minutes
&lt;/h3&gt;

&lt;p&gt;Many tools promise speed, but still require significant setup before you see results. Spektrum reduces that gap dramatically by combining project creation, task definition, and deployment into a single streamlined workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexible, Usage-Based Pricing
&lt;/h3&gt;

&lt;p&gt;The pricing model is also designed to encourage experimentation. With a token-based system and an average cost of around $0.50 per app generation, it becomes easy to test ideas without committing to heavy infrastructure costs.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Spektrum Works (From Prompt to Live App in a Few Lines)
&lt;/h2&gt;

&lt;p&gt;Using Spektrum feels surprisingly minimal. The workflow is intentionally simple, which makes it easy to experiment without dealing with setup overhead.&lt;/p&gt;

&lt;p&gt;Here’s what that looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SpektrumSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@spektrum-ai/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpektrumSDK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;portfolio-website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a portfolio website for a software engineer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;codeAndDeploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAppUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Live at: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You describe what you want&lt;/li&gt;
&lt;li&gt;Spektrum generates the code&lt;/li&gt;
&lt;li&gt;The app is deployed automatically&lt;/li&gt;
&lt;li&gt;You get a live URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At a high level, this represents a new way of building software, where natural language to web app generation becomes a practical and repeatable workflow.&lt;/p&gt;

&lt;p&gt;What stands out here isn’t just the small amount of code. It’s the fact that everything behind the scenes, including code generation, environment setup, and deployment, is handled automatically.&lt;/p&gt;

&lt;p&gt;This is where the experience starts to feel fundamentally different from traditional development workflows.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started with Spektrum (Two Ways)
&lt;/h2&gt;

&lt;p&gt;One thing I appreciated while testing Spektrum is that it doesn’t force you into a single workflow. Depending on how you prefer to build, you can either integrate it programmatically using the SDK or use the platform directly through a visual interface.&lt;/p&gt;

&lt;p&gt;If you’re a developer looking to embed app generation into your own product, the SDK approach gives you full control. But if your goal is to quickly test ideas, validate concepts, or just experience how fast this workflow can be, the platform UI is by far the fastest way to get started.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1: Using the SDK (For Integration)
&lt;/h3&gt;

&lt;p&gt;If you want to integrate Spektrum into your own application or automate workflows, you can use the SDK.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @spektrum-ai/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Spektrum requires &lt;strong&gt;Node.js 20.6.0 or higher&lt;/strong&gt;, mainly because of modern runtime features like &lt;code&gt;--env-file&lt;/code&gt; support.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;.env&lt;/code&gt; file at the root of your project and add your API key:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPEKTRUM_API_KEY=your_api_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can get your API key by signing up on the &lt;a href="https://app.jigjoy.ai/api-keys" rel="noopener noreferrer"&gt;JigJoy&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;Once your environment is set up, you initialize the SDK:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spektrum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpektrumSDK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From there, everything revolves around two core concepts: &lt;strong&gt;projects&lt;/strong&gt; and &lt;strong&gt;tasks&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;project&lt;/strong&gt; is a container for your application&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;task&lt;/strong&gt; is a description of what you want to build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you call &lt;code&gt;createTask&lt;/code&gt;, you’re not just passing a title; you’re defining the intent and structure of your application. The more precise your description is, the better the result will be.&lt;/p&gt;

&lt;p&gt;After defining the task, calling &lt;code&gt;codeAndDeploy&lt;/code&gt; triggers the full pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI interprets your request&lt;/li&gt;
&lt;li&gt;Generates the codebase&lt;/li&gt;
&lt;li&gt;Prepares the environment&lt;/li&gt;
&lt;li&gt;Deploys the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, &lt;code&gt;getAppUrl&lt;/code&gt; returns a live, publicly accessible URL where your app is already running.&lt;/p&gt;

&lt;p&gt;At that point, you’re no longer in a “development phase.” You already have a working product you can test, share, or iterate on.&lt;/p&gt;

&lt;p&gt;This flow gives you full flexibility, especially if you're building something more advanced or integrating AI-generated apps into your own system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 2: Using the Spektrum Platform (What I Actually Used)
&lt;/h3&gt;

&lt;p&gt;In my case, I wanted to experience Spektrum the same way most developers would when discovering a new tool: quickly, without setup, and without reading too much documentation upfront. So instead of starting with the SDK, I used the Spektrum platform UI directly.&lt;/p&gt;

&lt;p&gt;The flow is surprisingly simple and removes almost all friction.&lt;/p&gt;

&lt;p&gt;When you open the platform, you’re guided through a step-by-step onboarding flow.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: Generate an API Key
&lt;/h4&gt;

&lt;p&gt;The first step is generating your API key. Instead of manually creating environment variables or configuring anything locally, you simply click a button and the platform handles it for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffejt6zw07rdo8ynlwmyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffejt6zw07rdo8ynlwmyv.png" alt="Spektrum platform onboarding showing API key generation step" width="800" height="363"&gt;&lt;/a&gt;Generate your API key directly from the platform&lt;/p&gt;

&lt;p&gt;Within seconds, your key is generated and displayed, ready to be used if you want to switch to the SDK later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oviudotqlklmbgo71tz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oviudotqlklmbgo71tz.png" alt="Spektrum platform showing generated API key" width="800" height="369"&gt;&lt;/a&gt;Your API key is instantly created and ready to use&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 2: Create Your First Project
&lt;/h4&gt;

&lt;p&gt;Next, you create your first project. This acts as the container for everything you’re about to build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ebr4y278r4kxze6ykuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ebr4y278r4kxze6ykuz.png" alt="Spektrum create first project interface with code snippet and run button" width="800" height="372"&gt;&lt;/a&gt;Create your first project with a single click&lt;/p&gt;

&lt;p&gt;Again, this doesn’t require writing anything manually. You can either run the suggested snippet or simply use the interface, and within a few seconds, your project is ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgl18oal0m20uzldib8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgl18oal0m20uzldib8v.png" alt="Spektrum project created with public app URL" width="800" height="423"&gt;&lt;/a&gt;A project is created with a ready-to-use environment&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 3: Create Your First Task
&lt;/h4&gt;

&lt;p&gt;Once the project is created, the next step is defining a task.&lt;/p&gt;

&lt;p&gt;This is where you describe what you want to build. And you can run multiple tasks within the same project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuuhb3tugxfd081vwx9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feuuhb3tugxfd081vwx9k.png" alt="Spektrum create task interface with prompt for Japanese learning app" width="800" height="353"&gt;&lt;/a&gt;Define what you want to build using a task&lt;/p&gt;

&lt;p&gt;What’s interesting here is that you’re not thinking in terms of components or files. You’re defining intent, and the system takes care of translating that into an actual application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i7zq2dqfjni5qvst8w5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i7zq2dqfjni5qvst8w5.png" alt="Spektrum task creation interface showing Japanese vocabulary app prompt" width="800" height="351"&gt;&lt;/a&gt;First task is created with a single click&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 4: Generate and Deploy the App
&lt;/h4&gt;

&lt;p&gt;Once the task is defined, you simply run it.&lt;/p&gt;

&lt;p&gt;This is the point where everything comes together.&lt;/p&gt;

&lt;p&gt;You simply click a button, and Spektrum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interprets your request&lt;/li&gt;
&lt;li&gt;Generates the full codebase&lt;/li&gt;
&lt;li&gt;Prepares the environment&lt;/li&gt;
&lt;li&gt;Deploys the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwevqbqluzh3y1360umgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwevqbqluzh3y1360umgf.png" alt="Spektrum generate first app screen showing codeAndDeploy step" width="800" height="291"&gt;&lt;/a&gt;Generate and deploy your first app&lt;/p&gt;

&lt;p&gt;Within seconds, the app is built, deployed, and accessible through a live URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0luk047lxwgqw69zat9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0luk047lxwgqw69zat9.png" alt="Spektrum generating and deploying app from prompt" width="800" height="380"&gt;&lt;/a&gt;Application generated and deployed&lt;/p&gt;

&lt;p&gt;There’s no setup, no configuration, and no deployment pipeline to manage. The entire process feels more like interacting with a system than building software in the traditional sense.&lt;/p&gt;


&lt;h2&gt;
  
  
  My Experience: Building a Real App with Spektrum
&lt;/h2&gt;

&lt;p&gt;Instead of over-engineering the prompt, I kept it intentionally simple to see how far Spektrum could go on its own.&lt;/p&gt;

&lt;p&gt;This was my prompt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a gamified app for learning Japanese vocabulary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That’s it. One sentence.&lt;/p&gt;

&lt;p&gt;What happened next is what genuinely surprised me.&lt;/p&gt;

&lt;p&gt;Spektrum didn’t just generate a basic app based on that input. It expanded the idea into a fully structured experience, automatically designing a complete gamified Japanese vocabulary dashboard with multiple sections, including a hero area, progress tracking, a lesson quest path, daily missions, featured flashcards, and a leaderboard.&lt;/p&gt;

&lt;p&gt;It also introduced reusable UI components like buttons, cards, badges, and progress indicators, along with utility helpers and polished global styling, without me explicitly asking for any of that.&lt;/p&gt;

&lt;p&gt;In other words, it didn’t just execute the prompt. It interpreted the intent behind it and filled in the gaps like an experienced developer would.&lt;/p&gt;

&lt;p&gt;This felt like the system was making product-level decisions, not just generating code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4eoe4be6t3kukjxbh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4eoe4be6t3kukjxbh3.png" alt="Spektrum real-time monitoring dashboard with logs and deployment tracking" width="800" height="382"&gt;&lt;/a&gt;Real-time monitoring showing how Spektrum interprets and builds beyond the initial prompt&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpehds7c1xff3r3ckg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpehds7c1xff3r3ckg3.png" alt="Japanese vocabulary dashboard generated with Spektrum showing gamified UI" width="781" height="872"&gt;&lt;/a&gt;A fully structured gamified app generated from a single-sentence prompt&lt;/p&gt;

&lt;p&gt;Then I just clicked the "Deploy" button and my app was live in a few seconds. It honestly took less than a minute 😅&lt;/p&gt;

&lt;p&gt;&lt;a href="https://b73756ff-281d-4b68-92f3-8b2021fe3f95.apps.jigjoy.ai/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;🚀 Try the Live App I Built&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;What stood out to me wasn’t just the speed, but how complete the result felt from the first iteration. The layout was structured and the UI didn’t feel randomly stitched together.&lt;/p&gt;

&lt;p&gt;More importantly, iteration was simple. Instead of rewriting code, I could refine the prompt, adjust the requirements, and regenerate the app. That shift alone changes how you approach building.&lt;/p&gt;

&lt;p&gt;You spend less time managing implementation details and more time thinking about the product itself.&lt;/p&gt;


&lt;h2&gt;
  
  
  Who Is Spektrum For?
&lt;/h2&gt;

&lt;p&gt;Spektrum isn’t about replacing developers. Instead, it focuses on removing the friction that slows them down, especially in the early stages of building.&lt;/p&gt;

&lt;p&gt;It’s particularly useful for developers building MVPs, indie hackers exploring new ideas, startups validating features, or teams looking to integrate app generation into their own platforms.&lt;/p&gt;

&lt;p&gt;Common use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rapid MVP prototyping&lt;/li&gt;
&lt;li&gt;Internal tools generation&lt;/li&gt;
&lt;li&gt;AI-powered product features&lt;/li&gt;
&lt;li&gt;Experimenting with new ideas quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your workflow often includes the thought, “This will take a few hours to set up before I even start building,” Spektrum changes that dynamic completely.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Small Note for the Community 💙
&lt;/h2&gt;

&lt;p&gt;If you’ve been exploring Spektrum or thinking about building with it, joining the JigJoy Discord is a great way to connect with the team, ask questions, and see what others are building in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discord.com/invite/dvxY9J2kWX" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;💬 Join JigJoy on Discord&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;If this project sparked your interest or gave you ideas, dropping a ⭐ on the repo is one of the simplest ways to support the team and help it reach more developers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jigjoy-ai/spektrum-sdk" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;⭐ Star Spektrum on GitHub&lt;/a&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There’s a moment every developer remembers when an idea turns into something real and working. It’s the moment where everything clicks, and the effort suddenly feels worth it.&lt;/p&gt;

&lt;p&gt;Spektrum is trying to bring that moment closer by removing the layers that slow developers down. It doesn’t replace creativity or problem-solving, but it reduces the friction between intention and execution.&lt;/p&gt;

&lt;p&gt;If the current direction of AI development continues, we may not just be writing code in the future. We may be describing systems and watching them come to life.&lt;/p&gt;

&lt;p&gt;Tools like Spektrum aren’t just improving development. They’re redefining how software gets built.&lt;/p&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thanks for reading! 🙏🏻 &lt;br&gt; I hope you found this useful ✅ &lt;br&gt; Please react and follow for more 😍 &lt;br&gt; Made with 💙 by &lt;a href="https://dev.to/hadil"&gt;Hadil Ben Abdallah&lt;/a&gt;
&lt;/th&gt;
&lt;th&gt;
&lt;a href="https://www.linkedin.com/in/hadil-ben-abdallah/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu48q29oef3l4a6eow30h.png" alt="LinkedIn" width="40" height="40"&gt;&lt;/a&gt; &lt;a href="https://github.com/Hadil-Ben-Abdallah" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuvszgj6eun7xfvnwv51.png" alt="GitHub" width="50" height="50"&gt;&lt;/a&gt; &lt;a href="https://app.daily.dev/hadilbenabdallah" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20akag0pdeq95u76k9e8.png" alt="Daily.dev" width="40" height="40"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="ltag__user ltag__user__id__1209000"&gt;
    &lt;a href="/hadil" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1209000%2Fb29d37d8-2efe-4391-9796-a6f8a483f1bd.png" alt="hadil image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/hadil"&gt;Hadil Ben Abdallah&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hadil"&gt;Software Engineer • Technical Writer (250K+ readers)
I turn brands into websites people 💙 to use&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>From Monolith to Microservices: How Bounded Context Improves AI-Driven Development</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:44:03 +0000</pubDate>
      <link>https://dev.to/jigjoy/from-monolith-to-microservices-how-bounded-context-improves-ai-driven-development-382f</link>
      <guid>https://dev.to/jigjoy/from-monolith-to-microservices-how-bounded-context-improves-ai-driven-development-382f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When we first launched our vibe coding platform, the architecture started as a single monolithic application. Initially this approach allowed us to move quickly and ship features fast.&lt;/p&gt;

&lt;p&gt;But as the platform grew, the warning signs appeared.&lt;/p&gt;

&lt;p&gt;The codebase became increasingly complex, new features were harder to implement, and development velocity began to slow down. What once felt manageable started to evolve into a tightly coupled system that was difficult to reason about.&lt;/p&gt;

&lt;p&gt;To solve this, we made a strategic decision: refactor the platform from a monolith into a microservices architecture.&lt;/p&gt;

&lt;p&gt;In this article we explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Why monolithic architectures struggle in AI-driven systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How Domain-Driven Design (DDD) helps define system boundaries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why bounded contexts are critical for AI agents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How we split our platform into eight microservices&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architectural shift significantly improved both developer productivity and AI agent performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Domain-Driven Design
&lt;/h2&gt;

&lt;p&gt;The concept of Domain-Driven Design (DDD) was introduced by Eric Evans in the influential book Domain‑Driven Design: Tackling Complexity in the Heart of Software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaoz8ghxy7zj5bjpl12s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaoz8ghxy7zj5bjpl12s.jpg" alt=" " width="712" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the book was published in 2003, its ideas remain extremely relevant today. Many modern architectural patterns—including microservices—are heavily inspired by DDD principles.&lt;/p&gt;

&lt;p&gt;DDD focuses on one core idea:&lt;/p&gt;

&lt;p&gt;Software architecture should mirror the structure of the business domain. Instead of organizing systems purely around technical layers, DDD encourages teams to structure software around real business capabilities and concepts.&lt;/p&gt;

&lt;p&gt;While doing domain distillation, we detected the 6 main bounded contexts that can act as independent services: project planning, AI coding, project deployment, version control, observability, account and billing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvz5nkx1t2ol5tlnhe2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvz5nkx1t2ol5tlnhe2e.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monolith vs Microservices Debate
&lt;/h2&gt;

&lt;p&gt;For years developers have debated a fundamental architectural question:&lt;/p&gt;

&lt;p&gt;Should applications remain monolithic, or should they be broken into microservices?&lt;/p&gt;

&lt;p&gt;Monoliths have advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Simpler deployments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easier early development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fewer distributed systems challenges&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, as systems scale, monoliths often become harder to maintain. Large codebases accumulate complexity and eventually turn into what developers often call a "Big Ball of Mud."&lt;/p&gt;

&lt;p&gt;Today the conversation has evolved even further. We are no longer building systems only for human developers. We are building systems that AI agents interact with, modify, and reason about. And this introduces a new challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Monolithic Architectures Confuse AI Agents
&lt;/h2&gt;

&lt;p&gt;AI agents rely heavily on clear context boundaries.When a system grows into a large monolithic repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Domain concepts overlap&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terminology becomes ambiguous&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Architectural boundaries blur&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it harder for AI systems to correctly interpret intent. For example, an AI agent might receive a command such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a large monolith, the term project might refer to several different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A planning workspace&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A source code repository&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A deployment environment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without clear boundaries, the AI agent must guess the meaning. And guessing leads to errors. This is why architectural clarity matters even more in AI-driven development environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Bounded Context?
&lt;/h2&gt;

&lt;p&gt;A Bounded Context is a central concept in Domain-Driven Design. It defines a clear boundary within which a specific domain model applies and terminology has a single meaning.&lt;/p&gt;

&lt;p&gt;Inside a bounded context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Language is consistent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concepts are clearly defined&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain logic belongs only to that context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a shared understanding between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Developers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain experts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI agents&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each context effectively becomes its own mini-world with well-defined rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Bounded Contexts Improve AI Development
&lt;/h3&gt;

&lt;p&gt;In large software systems, the same word can mean different things in different domains. While building our AI coding platform, we encountered this problem quickly. Consider the word project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ik5g0t5d1ceg7mjhw2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ik5g0t5d1ceg7mjhw2t.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When both meanings existed in the same codebase, confusion spread. Not only for engineers—but for AI agents operating within the platform. By introducing bounded contexts, we ensured that each domain defines its own terminology and behavior. This dramatically improved the reliability of both human workflows and AI-generated development tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Microservices Architecture
&lt;/h2&gt;

&lt;p&gt;After refactoring the platform using Domain-Driven Design principles, we divided the system into eight bounded contexts, each implemented as a separate microservice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ygq5pwlprz8dxz3baws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ygq5pwlprz8dxz3baws.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allowed each domain to evolve independently while maintaining clear system boundaries.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Planning Context&lt;/strong&gt; - manages planning activities. Within this domain we store: tasks, projects, agent which generate tasks...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI Coding Context&lt;/strong&gt; - this is where development actually happens. AI coding agents operate in this domain and perform tasks such as: writing code, committing changes, managing development workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Project Deployment Context&lt;/strong&gt; - manages the infrastructure and pipelines required to deploy software built by AI agents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version Control Context&lt;/strong&gt; - while platforms like GitHub provide robust version control, we chose to build a lightweight internal system optimized specifically for AI-driven development workflows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability Context&lt;/strong&gt; - collects events and analytics from across the platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Account and Billing Context&lt;/strong&gt; - manages user accounts, platform credits, payments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Gateway&lt;/strong&gt; - acts as the central entry point into the platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Platform UI&lt;/strong&gt; - provides the user interface that connects all services together.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Avoiding the Big Ball of Mud
&lt;/h2&gt;

&lt;p&gt;Without clear architectural boundaries, complex systems eventually degrade into tightly coupled codebases that are difficult to maintain. Developers often describe this situation as a "Big Ball of Mud."&lt;/p&gt;

&lt;p&gt;Symptoms typically include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Spaghetti code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increasing bugs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slower development cycles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Poor onboarding experience&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confusing system behavior&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For AI-driven platforms, the consequences are even worse because agents lose the contextual clarity required to generate reliable code.&lt;/p&gt;

&lt;p&gt;By organizing our platform around bounded contexts and microservices, we achieved several key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clear domain ownership&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Faster feature development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better system scalability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved AI agent performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More maintainable architecture&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;As AI agents become increasingly involved in software development, architecture design must evolve alongside them. Bounded contexts and microservices are no longer just about scaling engineering teams—they also help AI systems reason about complex software platforms. For us, adopting Domain-Driven Design and microservices architecture transformed a growing monolith into a scalable platform designed for the future of AI-assisted development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;— Eric Evans&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>ai</category>
      <category>vibecoding</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why a Single Interface for Multiple LLM Providers Is a Bad Design Decision</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Thu, 19 Mar 2026 06:29:02 +0000</pubDate>
      <link>https://dev.to/jigjoy/dont-write-frameworks-for-dummies-100l</link>
      <guid>https://dev.to/jigjoy/dont-write-frameworks-for-dummies-100l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Don't write frameworks for dummies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence stuck with me while reading Domain-Driven Design: Tackling Complexity in the Heart of Software. I didn't fully understand it at first - but after building (and then redesigning) an AI orchestration framework, I do now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where We Started
&lt;/h2&gt;

&lt;p&gt;When we began building a framework for orchestrating AI agents, one of the first features we introduced was a unified request interface across multiple LLM providers.&lt;/p&gt;

&lt;p&gt;At first, it felt like a great design decision.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;One interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple providers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean abstraction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that simplicity turned out to be misleading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2kz9fda5g9dkokcjrxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2kz9fda5g9dkokcjrxu.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with "Unified" Abstractions
&lt;/h2&gt;

&lt;p&gt;Over time, cracks started to show:&lt;/p&gt;

&lt;h3&gt;
  
  
  All models looked the same to developers
&lt;/h3&gt;

&lt;p&gt;By flattening everything into a single interface, we erased the differences between models. Developers stopped thinking about capabilities and limitations - which is exactly what they should be thinking about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding new models became harder, not easier
&lt;/h3&gt;

&lt;p&gt;New models come with new capabilities, parameters, and behaviors. A "unified" interface either ignores those features, or becomes bloated trying to support everything. Neither is good design.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ubiquitous language was wrong
&lt;/h3&gt;

&lt;p&gt;The API didn't reflect the domain. It reflected our attempt to simplify it. That meant the language of the library didn't express real model capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  We enabled misuse by design
&lt;/h3&gt;

&lt;p&gt;The worst part: the abstraction allowed developers to make mistakes easily - and only discover them at runtime.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;passing &lt;code&gt;reasoning_effort="high"&lt;/code&gt; to a model that doesn't support reasoning.&lt;/p&gt;

&lt;p&gt;The system didn't prevent it. It allowed it.&lt;/p&gt;

&lt;p&gt;That's not just a bad developer experience - it's a failure in design. The abstraction wasn't helping developers. It was hiding the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Redesign
&lt;/h2&gt;

&lt;p&gt;So we changed direction.&lt;/p&gt;

&lt;p&gt;Instead of forcing a unified interface, we started modeling each LLM separately, along with its capabilities and constraints.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fablhts86ii95joo4vore.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fablhts86ii95joo4vore.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Is the Right Move
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Advanced users can extract more value
&lt;/h3&gt;

&lt;p&gt;Power users are no longer limited by a lowest-common-denominator API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Models are not the same - and that matters
&lt;/h3&gt;

&lt;p&gt;Treating them as identical leads to misuse. Embracing differences leads to better outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invalid configurations are caught early
&lt;/h3&gt;

&lt;p&gt;Instead of runtime surprises, errors are surfaced immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better developer experience (DX)
&lt;/h3&gt;

&lt;p&gt;Clear APIs, explicit capabilities, fewer hidden assumptions.&lt;/p&gt;

&lt;p&gt;Most importantly: the library expresses domain knowledge correctly&lt;br&gt;
The design now reflects reality - not an oversimplified version of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;We're not done yet.&lt;/p&gt;

&lt;p&gt;We haven't fully committed the redesign - but we've started moving in this direction, and the changes will roll out in the next versions of Mozaik.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;make model capabilities explicit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;prevent invalid usage early&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and let developers actually use the power of each model instead of hiding it&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still evolving, and we'll likely learn more (and fix more mistakes) along the way.&lt;/p&gt;

&lt;p&gt;But one thing is already clear:&lt;/p&gt;

&lt;p&gt;We're no longer trying to hide the complexity of LLMs. We're designing for it.&lt;/p&gt;

&lt;p&gt;Source:&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://jigjoy.ai/blog/single-interface-for-multiple-llms" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjigjoy.ai%2Fblog%2Fsingle-interface-for-multiple-llms%2Fthumbnail-og.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://jigjoy.ai/blog/single-interface-for-multiple-llms" rel="noopener noreferrer" class="c-link"&gt;
            Why a Single Interface for Multiple LLM Providers Is a Bad Design Decision
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Why unified LLM abstractions fail and how modeling each LLM separately improves developer experience. A lesson in Domain-Driven Design.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          jigjoy.ai
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to Build an Autonomous AI Agent That Executes Terminal Commands</title>
      <dc:creator>Miodrag Vilotijević</dc:creator>
      <pubDate>Sun, 15 Mar 2026 18:54:33 +0000</pubDate>
      <link>https://dev.to/jigjoy/how-to-build-an-autonomous-ai-agent-that-executes-terminal-commands-23ke</link>
      <guid>https://dev.to/jigjoy/how-to-build-an-autonomous-ai-agent-that-executes-terminal-commands-23ke</guid>
      <description>&lt;h2&gt;
  
  
  What Makes an Agent Autonomous?
&lt;/h2&gt;

&lt;p&gt;An autonomous agent is an agent that can make independent decisions to accomplish tasks-without constant human guidance.&lt;/p&gt;

&lt;p&gt;But how do we measure the level of autonomy an agent has?&lt;/p&gt;

&lt;p&gt;The answer lies in the variety of tasks an agent can perform on its own.&lt;/p&gt;

&lt;p&gt;Consider an agent that has a single tool-the ability to write files. That's one degree of autonomy. It can create content, save it, and that's about it.&lt;/p&gt;

&lt;p&gt;Now consider an agent with access to a terminal. Suddenly, the possibilities explode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;List files in any directory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read and write files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Execute scripts and programs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install packages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interact with git repositories&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make network requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And much more...&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a high degree of autonomy-which, as we'll discuss, comes with both incredible power and important security considerations.&lt;/p&gt;

&lt;p&gt;This is basically the idea behind modern coding tools like Claude Code—or even better, OpenClaw.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Concept: Tool Execution Loop
&lt;/h2&gt;

&lt;p&gt;The main thing we need to enable is an implementation for executing tools in a loop.&lt;/p&gt;

&lt;p&gt;I'll use a library I built for this- &lt;code&gt;@mozaik-ai/core&lt;/code&gt; - a TypeScript framework for orchestrating AI agents.&lt;/p&gt;

&lt;p&gt;With Mozaik, we can easily equip an agent with tools. So we just need to create a terminal tool for executing commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Terminal Tool
&lt;/h2&gt;

&lt;p&gt;First, let's define the terminal class that will give our agent the ability to execute shell commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spawn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Terminal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CommandResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

            &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

            &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="na"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="na"&gt;exitCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now we define the tool that will give our agent the ability to execute shell commands:&lt;/p&gt;

&lt;p&gt;The tool is straightforward: it takes a command string, executes it using Node.js's child_process module, and returns the output.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Terminal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run_command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run a command in the terminal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The command to run in the terminal.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The current working directory.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cwd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Running command: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in directory: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--------------------------------&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Attaching the Tool to an Agent
&lt;/h2&gt;

&lt;p&gt;Now we attach the terminal tool to our agent:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MozaikRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-5-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Analyze the github respository and update the README.md file with a high level description of the project.`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a terminal agent. 

You can run commands in the terminal to help the user with their request. 
Do not ask any questions to the user. Just run the commands and return the result.

Tools:
- run_command: Run a command in the terminal. You can use this tool to run any command in the terminal.

User Request:
- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userRequest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MozaikAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Watching the Agent Work
&lt;/h2&gt;

&lt;p&gt;Let's give the agent a task and watch it work. Below you can see what happens step by step - the terminal commands the agent autonomously executes to get the job done.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Look at this folder and tell me in one sentence what it does 
                    — like you're explaining it to someone who has never written a line of code.`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The agent breaks it down on its own. It runs two commands to get the job done. The first command lists the files in the current directory, and the second command reads README.md to figure out the purpose of the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpcn2ur9tmckirwzbah7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpcn2ur9tmckirwzbah7.png" alt=" " width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see which commands the agent executed to get the job done. No instructions on how to do it. Just the goal—and the agent figures out the rest.&lt;/p&gt;

&lt;p&gt;Now, let's give the agent a more complex task:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Analyze this directory and write 
                    a detailed description of the project 
                    in a file called purpose.md.`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We see the agent executed multiple commands to get the job done, and finally wrote the purpose.md file with the description of the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzye42kz6nqxtjjpv1fx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzye42kz6nqxtjjpv1fx.png" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;With great autonomy comes great responsibility. Terminal access is powerful—but it can also be dangerous if not properly controlled.&lt;/p&gt;

&lt;p&gt;The balance between autonomy and safety is a spectrum. Start with tighter controls and expand as you build confidence in your agent's behavior.&lt;/p&gt;
&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building an autonomous agent with terminal access is surprisingly straightforward—the core pattern is just a tool execution loop.&lt;/p&gt;

&lt;p&gt;What makes these agents powerful isn't the complexity of the implementation, but the emergent capabilities that arise from giving an LLM access to a shell.&lt;/p&gt;

&lt;p&gt;The agent doesn't need to be explicitly programmed for every scenario. Give it a goal, give it tools, and it will find a way.&lt;/p&gt;

&lt;p&gt;GitHub Repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Mijura" rel="noopener noreferrer"&gt;
        Mijura
      &lt;/a&gt; / &lt;a href="https://github.com/Mijura/terminal-agent" rel="noopener noreferrer"&gt;
        terminal-agent
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A minimal AI-powered terminal agent that can execute shell commands and interact with the local system to automate tasks and workflows.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;@mozaik-ai/terminal&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A small terminal-agent project that lets an AI act as a terminal operator. It exposes a tool for running shell commands programmatically and returning structured results (stdout, stderr, exit code). The agent is built on top of Mozaik AI core primitives and is intended for automation tasks where an AI needs to inspect the repository, run commands, and modify files.&lt;/p&gt;

&lt;p&gt;Key points&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project name: @mozaik-ai/terminal&lt;/li&gt;
&lt;li&gt;Purpose: Provide a terminal tool that an AI agent can use to run commands in a shell and capture results.&lt;/li&gt;
&lt;li&gt;Language: TypeScript (Node.js)&lt;/li&gt;
&lt;li&gt;Entry point: src/index.ts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run arbitrary shell commands via a typed tool (run_command)&lt;/li&gt;
&lt;li&gt;Captures stdout, stderr, and exit code&lt;/li&gt;
&lt;li&gt;Simple Terminal class that spawns child processes and returns CommandResult objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How it works (high level)&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;src/index.ts wires up a MozaikAgent with a single tool: run_command&lt;/li&gt;

&lt;li&gt;The tool schema requires { command, cwd } and invokes Terminal.runCommand&lt;/li&gt;

&lt;li&gt;Terminal.runCommand uses child_process.spawn (with shell…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Mijura/terminal-agent" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Source:&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://jigjoy.ai/blog/autonomous-terminal-agent" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjigjoy.ai%2Fblog%2Fautonomous-terminal-agent%2Fthumbnail-og.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://jigjoy.ai/blog/autonomous-terminal-agent" rel="noopener noreferrer" class="c-link"&gt;
            Building an Autonomous Agent That Can Run Terminal Commands
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn how to build an autonomous AI agent with terminal access. Discover how tool-equipped agents can execute shell commands and accomplish complex tasks.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          jigjoy.ai
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>ai</category>
      <category>agents</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
