<?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: Jouliose</title>
    <description>The latest articles on DEV Community by Jouliose (@jouliose).</description>
    <link>https://dev.to/jouliose</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189725%2F617a429b-4082-4b21-a7f0-9487a0210dc1.png</url>
      <title>DEV Community: Jouliose</title>
      <link>https://dev.to/jouliose</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jouliose"/>
    <language>en</language>
    <item>
      <title>Building a "Jarvis" agent mesh with Neuron workflows</title>
      <dc:creator>Jouliose</dc:creator>
      <pubDate>Sat, 17 Oct 2026 02:35:31 +0000</pubDate>
      <link>https://dev.to/jouliose/building-a-jarvis-agent-mesh-with-neuron-workflows-42cd</link>
      <guid>https://dev.to/jouliose/building-a-jarvis-agent-mesh-with-neuron-workflows-42cd</guid>
      <description>&lt;p&gt;&lt;em&gt;How typed events, an idempotent signal bus and a bounded watchdog let several PHP agents cooperate without putting scheduling or transport inside the framework.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I run a small holding company, and like many people I wanted a "Jarvis": a set of agents that prepare my morning briefing, watch operations, and tell me when something stops working. Instead of one giant agent with a giant prompt, I tried a &lt;strong&gt;mesh&lt;/strong&gt;: small, role-specific agents coordinated by a &lt;a href="https://github.com/neuron-core/neuron-ai" rel="noopener noreferrer"&gt;Neuron AI&lt;/a&gt; workflow.&lt;/p&gt;

&lt;p&gt;The result is a compact example, proposed as &lt;a href="https://github.com/neuron-core/neuron-ai/pull/683" rel="noopener noreferrer"&gt;PR #683&lt;/a&gt; (&lt;code&gt;examples/agent/jarvis-mesh.php&lt;/code&gt; and &lt;code&gt;docs/jarvis-mesh.md&lt;/code&gt;). This post explains the design choices.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Transparency note:&lt;/strong&gt; this prototype was built with AI coding assistance, and it is meant as a starting point, not production code. The PR is still open and has not been reviewed or merged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The idea: let the workflow own the sequence, not the agents
&lt;/h2&gt;

&lt;p&gt;Three agents each have one job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Research&lt;/strong&gt; prepares sourced highlights for the day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations&lt;/strong&gt; summarizes status and risks, in light of the research.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Briefing&lt;/strong&gt; composes the final daily briefing from both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They never call each other. A Neuron &lt;code&gt;Workflow&lt;/code&gt; routes between them using &lt;strong&gt;typed events&lt;/strong&gt;: &lt;code&gt;BriefingStarted&lt;/code&gt; -&amp;gt; &lt;code&gt;ResearchCompleted&lt;/code&gt; -&amp;gt; &lt;code&gt;OperationsCompleted&lt;/code&gt; -&amp;gt; &lt;code&gt;BriefingComposed&lt;/code&gt;. Each node takes one event type and returns the next one, so the graph of the business process is readable straight from the type signatures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adapters instead of framework code
&lt;/h2&gt;

&lt;p&gt;The example deliberately defines four small interfaces that your application implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;JarvisSignalBus&lt;/code&gt;: publish a typed signal, &lt;strong&gt;idempotent by ID&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JarvisKnowledgeStore&lt;/code&gt;: shared notes between agents, &lt;strong&gt;idempotent by key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JarvisHeartbeatStore&lt;/code&gt;: when was each agent last seen?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JarvisAgentFactory&lt;/code&gt;: returns a freshly configured &lt;code&gt;AgentInterface&lt;/code&gt; per role and thread.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are passed to the workflow as a &lt;code&gt;WorkflowResources&lt;/code&gt; object, rebuilt for each execution segment. Provider, tools, instructions and message store all live in your factory, so the workflow code never changes when you swap an LLM or a storage backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surviving replays
&lt;/h2&gt;

&lt;p&gt;Workflows can be interrupted and resumed, so every side effect has to tolerate being run twice. Two mechanisms help:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent calls go through the node's &lt;code&gt;memoize()&lt;/code&gt;, so a completed agent call is not repeated when an interrupted run resumes.&lt;/li&gt;
&lt;li&gt;Signals and knowledge writes use stable keys (for example &lt;code&gt;"{workflowId}/research.completed"&lt;/code&gt;), so a repeated publication does not create duplicates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One honest limit, also stated in the docs: memoization cannot make an &lt;em&gt;external&lt;/em&gt; side effect exactly-once. Idempotent adapters are your responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bounded watchdog for stale agents
&lt;/h2&gt;

&lt;p&gt;The second workflow, &lt;code&gt;WatchdogWorkflow&lt;/code&gt;, is a &lt;strong&gt;single-pass sweep&lt;/strong&gt;. Your scheduler (cron, a queue, whatever you already use) starts a fresh execution per sweep with a list of agents, a timestamp and a staleness threshold. For each agent, one node checks the last heartbeat and, if it is stale or missing, writes a note and publishes an &lt;code&gt;agent.heartbeat.stale&lt;/code&gt; signal under a key derived from the agent and its last-seen time. Running the same sweep twice therefore raises the same alert once.&lt;/p&gt;

&lt;p&gt;The workflow is capped at one step and a 60-second lease, so it cannot loop forever. Scheduling, heartbeat production, alert delivery and monitoring policy stay in the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not use workflow signals as the bus?
&lt;/h2&gt;

&lt;p&gt;Neuron's &lt;code&gt;Workflow::signal()&lt;/code&gt; answers the &lt;em&gt;current matching wait&lt;/em&gt;. It is not a broadcast and not a queue. For fan-out between independent agents you need a real shared bus, which is why the example treats it as an application integration boundary. The rule I settled on: &lt;strong&gt;transport and scheduling outside workflow nodes; business sequencing and state inside the graph.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it up
&lt;/h2&gt;

&lt;p&gt;In your application, you provide the adapters and a persistence backend, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$workflow&lt;/span&gt; &lt;span class="o"&gt;=&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;DailyBriefingWorkflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$date&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;setPersistence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$workflowPersistence&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;setResources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resourcesFactory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$state&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$workflow&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$briefing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'briefing'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use durable, shared implementations for the bus and the knowledge store, and a multi-process-safe persistence backend if workers may overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested it
&lt;/h2&gt;

&lt;p&gt;I ran the example against Neuron 4.1 with simulated agents and in-memory adapters (no real LLM, no real database). The briefing ran end to end; an agent that failed mid-run was resumed without repeating the completed research step; and the watchdog raised one alert for a stale agent and one for a missing agent, ignored a healthy one, and did not duplicate alerts on a second sweep. I have not run the full Neuron test suite or PHPStan, and re-running an already completed workflow with the same ID calls the agents again (signals stay deduplicated).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would build next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A real heartbeat producer inside each agent worker.&lt;/li&gt;
&lt;li&gt;Alert delivery (chat, email) as a consumer of &lt;code&gt;agent.heartbeat.stale&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Proper unit tests for the adapters with in-memory fakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to the Neuron maintainers for the framework and for the invitation to share this. Feedback on the design is very welcome on the PR.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jules Langlois&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
