<svg width="680" height="430" viewBox="0 0 680 430" role="img" xmlns="http://www.w3.org/2000/svg">
<title>任务状态机与 agent loop 的三层嵌套（sundynix-agentix / dispatcher）</title>
<desc>外层任务状态机由 NATS 事件驱动、状态落 PG；running 展开为 Eino compose.Graph 的 DAG 调度；每个 agent 节点内是 Eino react.Agent 的推理-调工具-观察循环。</desc>
<defs>
<marker id="ah" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,1 L8,5 L0,9" fill="none" stroke="#888780" stroke-width="1.6"/>
</marker>
<style>
text{font-family:-apple-system,"Segoe UI","Helvetica Neue",Arial,"PingFang SC","Microsoft YaHei",sans-serif}
.th{font-size:14px;font-weight:500}
.ts{font-size:12px;font-weight:400}
.arr{stroke:#888780;stroke-width:1.5;fill:none}
.leader{stroke:#b4b2a9;stroke-width:1;stroke-dasharray:3 3;fill:none}
</style>
</defs>

<rect x="0" y="0" width="680" height="430" rx="14" fill="#fbfaf7"/>
<text x="30" y="30" class="th" fill="#2c2c2a">任务状态机 &amp; agent loop —— 三层嵌套</text>

<rect x="40" y="44" width="12" height="12" rx="3" fill="#eeedfe" stroke="#534ab7"/>
<text x="58" y="54" class="ts" fill="#5f5e5a">Eino 编排 / 循环</text>
<rect x="188" y="44" width="12" height="12" rx="3" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="206" y="54" class="ts" fill="#5f5e5a">状态 / 结构</text>
<rect x="310" y="44" width="12" height="12" rx="3" fill="#e1f5ee" stroke="#0f6e56"/>
<text x="328" y="54" class="ts" fill="#5f5e5a">外部：工具 / 总线</text>

<rect x="40" y="70" width="600" height="98" rx="10" fill="#ffffff" stroke="#d3d1c7"/>
<text x="54" y="88" class="ts" fill="#5f5e5a">① 任务状态机（dispatcher 无状态，转移走总线）</text>
<rect x="64" y="96" width="110" height="40" rx="8" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="119" y="120" text-anchor="middle" class="th" fill="#2c2c2a">submitted</text>
<line class="arr" x1="174" y1="116" x2="213" y2="116" marker-end="url(#ah)"/>
<rect x="214" y="96" width="110" height="40" rx="8" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="269" y="120" text-anchor="middle" class="th" fill="#2c2c2a">running</text>
<line class="arr" x1="324" y1="116" x2="363" y2="116" marker-end="url(#ah)"/>
<rect x="364" y="96" width="258" height="40" rx="8" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="493" y="114" text-anchor="middle" class="th" fill="#2c2c2a">done · failed · timeout</text>
<text x="493" y="129" text-anchor="middle" class="ts" fill="#5f5e5a">rejected · waiting（HITL 暂停）</text>
<text x="54" y="156" class="ts" fill="#5f5e5a">转移 = setStatus → NATS(status.task) → 网关 UpdateTaskStatus 落 PG</text>

<text x="340" y="183" text-anchor="middle" class="ts" fill="#5f5e5a">▼ running 展开为</text>

<rect x="40" y="190" width="600" height="86" rx="10" fill="#ffffff" stroke="#d3d1c7"/>
<text x="54" y="208" class="ts" fill="#5f5e5a">② Eino compose.Graph —— 按 DAG 调度节点</text>
<rect x="64" y="218" width="104" height="40" rx="8" fill="#eeedfe" stroke="#534ab7"/>
<text x="116" y="242" text-anchor="middle" class="th" fill="#3c3489">agent 节点</text>
<line class="arr" x1="168" y1="238" x2="195" y2="238" marker-end="url(#ah)"/>
<rect x="196" y="218" width="80" height="40" rx="8" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="236" y="242" text-anchor="middle" class="th" fill="#2c2c2a">branch ◇</text>
<line class="arr" x1="276" y1="238" x2="303" y2="238" marker-end="url(#ah)"/>
<rect x="304" y="218" width="130" height="40" rx="8" fill="#eeedfe" stroke="#534ab7"/>
<text x="369" y="236" text-anchor="middle" class="th" fill="#3c3489">节点 ∥ 节点</text>
<text x="369" y="251" text-anchor="middle" class="ts" fill="#534ab7">无依赖并行</text>
<text x="450" y="242" class="ts" fill="#5f5e5a">真假边路由 · HITL→checkpoint</text>

<text x="340" y="290" text-anchor="middle" class="ts" fill="#5f5e5a">▼ 每个 agent 节点放大为</text>

<rect x="40" y="296" width="600" height="116" rx="10" fill="#ffffff" stroke="#d3d1c7"/>
<text x="54" y="314" class="ts" fill="#5f5e5a">③ Eino react.Agent —— 节点内的 ReAct 循环</text>
<rect x="70" y="326" width="110" height="40" rx="8" fill="#eeedfe" stroke="#534ab7"/>
<text x="125" y="350" text-anchor="middle" class="th" fill="#3c3489">推理 (LLM)</text>
<line class="arr" x1="180" y1="346" x2="229" y2="346" marker-end="url(#ah)"/>
<rect x="230" y="326" width="120" height="40" rx="8" fill="#e1f5ee" stroke="#0f6e56"/>
<text x="290" y="344" text-anchor="middle" class="th" fill="#085041">调工具</text>
<text x="290" y="359" text-anchor="middle" class="ts" fill="#0f6e56">MCP / NATS</text>
<line class="arr" x1="350" y1="346" x2="419" y2="346" marker-end="url(#ah)"/>
<rect x="420" y="326" width="110" height="40" rx="8" fill="#eeedfe" stroke="#534ab7"/>
<text x="475" y="350" text-anchor="middle" class="th" fill="#3c3489">观察结果</text>
<line class="arr" x1="530" y1="346" x2="539" y2="346" marker-end="url(#ah)"/>
<rect x="540" y="326" width="96" height="40" rx="8" fill="#f1efe8" stroke="#b4b2a9"/>
<text x="588" y="344" text-anchor="middle" class="th" fill="#2c2c2a">流式输出</text>
<text x="588" y="359" text-anchor="middle" class="ts" fill="#5f5e5a">停止</text>
<line class="leader" x1="475" y1="380" x2="129" y2="380"/>
<line class="arr" x1="129" y1="380" x2="129" y2="368" marker-end="url(#ah)"/>
<text x="302" y="398" text-anchor="middle" class="ts" fill="#5f5e5a">循环：还要调工具 → 再推理（至 MaxStep 或不再调工具）</text>
</svg>
