<svg width="100%" viewBox="0 0 760 420" role="img" xmlns="http://www.w3.org/2000/svg">
  <title>Proactive voice — the agent speaks unprompted</title>
  <desc>hermes sudo_voice plugin POSTs voice-bridge /v1/internal/voice/say; if an active session exists, TTS publishes to the LiveKit room and the Pi plays; if not, voice-bridge returns 404 and the agent can fall back to another surface.</desc>
  <style>
    text{font-family:"Anthropic Sans",-apple-system,system-ui,"Segoe UI",sans-serif}
    .t{font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central}
    .s{font-size:12px;text-anchor:middle;dominant-baseline:central}
    .lbl{font-size:11px;fill:#9c9a92;text-anchor:middle}
    rect{stroke-width:.5}
    .green{fill:#27500a;stroke:#97c459}   .gt{fill:#c0dd97} .gs{fill:#97c459}
    .blue{fill:#1f3b7a;stroke:#8aaef0}    .bt{fill:#cfdcf4} .bs{fill:#8aaef0}
    .orange{fill:#712b13;stroke:#f0997b}  .ot{fill:#f5c4b3} .os{fill:#f0997b}
    .arrow{fill:none;stroke:#9c9a92;stroke-width:1.5}
    .arrow-ok{fill:none;stroke:#1d9e75;stroke-width:1.5}
    .arrow-warn{fill:none;stroke:#d97757;stroke-width:1.5}
    .diamond{fill:#2a2826;stroke:#b4b2a9;stroke-width:.5}
  </style>
  <defs>
    <marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
      <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
    </marker>
  </defs>

  <rect class="orange" x="40" y="40" width="240" height="60" rx="8"/>
  <text class="t ot" x="160" y="64">hermes  ·  sudo_voice plugin</text>
  <text class="s os" x="160" y="84">cronjob / send_message</text>

  <rect class="blue" x="40" y="160" width="240" height="60" rx="8"/>
  <text class="t bt" x="160" y="184">voice-bridge  :18087</text>
  <text class="s bs" x="160" y="204">POST /v1/internal/voice/say</text>

  <polygon class="diamond" points="160,250 260,290 160,330 60,290"/>
  <text class="t nt" x="160" y="294">active session?</text>

  <rect class="blue" x="380" y="40" width="320" height="60" rx="8"/>
  <text class="t bt" x="540" y="64">session.say(text) → TTS → publish</text>
  <text class="s bs" x="540" y="84">cross-thread dispatch onto agents loop</text>

  <rect class="green" x="380" y="160" width="320" height="60" rx="8"/>
  <text class="t gt" x="540" y="184">LiveKit → Pi speaker</text>
  <text class="s gs" x="540" y="204">user hears the proactive message</text>

  <rect class="orange" x="380" y="270" width="320" height="80" rx="8"/>
  <text class="t ot" x="540" y="294">404 → agent falls back</text>
  <text class="s os" x="540" y="316">device offline · no session in voice-bridge</text>
  <text class="s os" x="540" y="334">try whatsapp / chat instead</text>

  <path class="arrow"      d="M 160 100 L 160 158" marker-end="url(#a)"/>
  <path class="arrow"      d="M 160 220 L 160 248" marker-end="url(#a)"/>
  <path class="arrow-ok"   d="M 260 280 C 320 280, 320 70,  378 70" marker-end="url(#a)"/>
  <text class="lbl"        x="316" y="158">yes</text>
  <path class="arrow-ok"   d="M 540 100 L 540 158" marker-end="url(#a)"/>
  <path class="arrow-warn" d="M 260 300 C 320 300, 320 290, 378 290" marker-end="url(#a)" stroke-dasharray="4 3"/>
  <text class="lbl"        x="316" y="320">no</text>
</svg>
