<svg width="100%" viewBox="0 0 980 660" role="img" xmlns="http://www.w3.org/2000/svg">
  <title>Sudo topology — the full stack at a glance</title>
  <desc>Three vertical zones: the home (sudoedge + HA), the VPS (Caddy fronting sudo-api, livekit-server, voice-bridge, and per-user hermes containers), and external services (browser, Supabase, OpenRouter, Deepgram, TTS, Twilio).</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:11px;text-anchor:middle;dominant-baseline:central}
    .hdr{font-size:13px;font-weight:500;fill:#c2c0b6}
    .lbl{font-size:11px;fill:#9c9a92}
    rect{stroke-width:.5}
    .zone{fill:none;stroke:#5a5852;stroke-width:.5;stroke-dasharray:5 4;opacity:.6}
    .neutral{fill:#444441;stroke:#b4b2a9} .nt{fill:#d3d1c7} .ns{fill:#b4b2a9}
    .blue{fill:#1f3b7a;stroke:#8aaef0}    .bt{fill:#cfdcf4} .bs{fill:#8aaef0}
    .orange{fill:#712b13;stroke:#f0997b}  .ot{fill:#f5c4b3} .os{fill:#f0997b}
    .green{fill:#27500a;stroke:#97c459}   .gt{fill:#c0dd97} .gs{fill:#97c459}
    .purple{fill:#3c3489;stroke:#afa9ec}  .pt{fill:#ceccf6} .ps{fill:#afa9ec}
    .arrow{fill:none;stroke:#9c9a92;stroke-width:1.5}
    .arrow-ok{fill:none;stroke:#1d9e75;stroke-width:1.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>

  <!-- ============ Zones ============ -->
  <rect class="zone" x="20"  y="40" width="220" height="600" rx="12"/>
  <text class="hdr" x="40"  y="62">🏠  Home</text>

  <rect class="zone" x="260" y="40" width="460" height="600" rx="12"/>
  <text class="hdr" x="280" y="62">☁️  VPS — sudohomes.com</text>

  <rect class="zone" x="740" y="40" width="220" height="600" rx="12"/>
  <text class="hdr" x="760" y="62">External</text>

  <!-- ============ Home column ============ -->
  <rect class="neutral" x="60" y="100" width="140" height="40" rx="8"/>
  <text class="t nt" x="130" y="120">🎤 mic · 🔊 speaker</text>

  <rect class="green" x="40" y="172" width="180" height="120" rx="8"/>
  <text class="t gt" x="130" y="196">sudoedge</text>
  <text class="s gs" x="130" y="218">native Python · no container</text>
  <text class="s gs" x="130" y="240">SoftAP captive portal</text>
  <text class="s gs" x="130" y="258">LiveKit client (mic / TTS)</text>
  <text class="s gs" x="130" y="276">home_control bridge</text>

  <rect class="blue" x="60" y="330" width="140" height="50" rx="8"/>
  <text class="t bt" x="130" y="352">home-assistant</text>
  <text class="s bs" x="130" y="370">user's own install</text>

  <path class="arrow" d="M 130 140 L 130 170" marker-end="url(#a)"/>
  <path class="arrow" d="M 130 292 L 130 328" marker-end="url(#a)"/>

  <!-- ============ VPS column ============ -->
  <rect class="green" x="280" y="92" width="420" height="50" rx="8"/>
  <text class="t gt" x="490" y="114">Caddy  ·  :443 TLS  ·  systemd on host</text>
  <text class="s gs" x="490" y="132">routes /, /v1/*, /admin/* → sudo-api  ·  /livekit/* → livekit-server</text>

  <rect class="blue" x="280" y="180" width="190" height="80" rx="8"/>
  <text class="t bt" x="375" y="204">sudo-api  :18086</text>
  <text class="s bs" x="375" y="224">pages · /v1/* · provisioner</text>
  <text class="s bs" x="375" y="242">(spawns hermes via docker.sock)</text>

  <rect class="blue" x="500" y="180" width="200" height="80" rx="8"/>
  <text class="t bt" x="600" y="204">voice-bridge</text>
  <text class="s bs" x="600" y="224">livekit-agents worker</text>
  <text class="s bs" x="600" y="242">STT → hermes → TTS</text>

  <rect class="blue" x="280" y="300" width="190" height="80" rx="8"/>
  <text class="t bt" x="375" y="324">livekit-server  :7880</text>
  <text class="s bs" x="375" y="344">WebRTC SFU</text>
  <text class="s bs" x="375" y="362">room_&lt;user_id&gt;</text>

  <rect class="orange" x="290" y="420" width="400" height="170" rx="8"/>
  <text class="t ot" x="490" y="446">hermes-user-&lt;uuid-hex&gt;  ·  per-user</text>
  <text class="s os" x="490" y="468">upstream nousresearch/hermes-agent · 3 plugins bind-mounted</text>
  <text class="s os" x="490" y="488">api_server  :8642   (reactive voice)</text>
  <text class="s os" x="490" y="506">twilio_whatsapp  :8651   (whatsapp)</text>
  <text class="s os" x="490" y="524">sudo_chat  :8652   (browser SSE)</text>
  <text class="s os" x="490" y="542">sudo_voice  :8653   (proactive voice)</text>
  <text class="s os" x="490" y="568">/opt/data volume · memory · sessions · skills</text>

  <!-- VPS-internal arrows -->
  <path class="arrow" d="M 375 142 L 375 178" marker-end="url(#a)"/>
  <path class="arrow" d="M 600 142 L 600 178" marker-end="url(#a)"/>
  <path class="arrow" d="M 470 220 L 498 220" marker-end="url(#a)"/>
  <text class="lbl" x="484" y="214" text-anchor="middle">ensure</text>
  <path class="arrow" d="M 375 260 L 375 298" marker-end="url(#a)"/>
  <path class="arrow" d="M 470 320 L 498 320" marker-end="url(#a)"/>
  <text class="lbl" x="484" y="314" text-anchor="middle">joins</text>
  <path class="arrow" d="M 600 260 L 600 418" marker-end="url(#a)"/>
  <text class="lbl" x="612" y="338" text-anchor="start">POST /v1/chat/completions</text>
  <path class="arrow" d="M 375 260 L 375 418" marker-end="url(#a)" stroke-dasharray="4 3"/>
  <text class="lbl" x="312" y="338" text-anchor="start">docker run /</text>
  <text class="lbl" x="312" y="352" text-anchor="start">plugin /inbound</text>

  <!-- ============ External column ============ -->
  <rect class="neutral" x="760" y="92"  width="180" height="50" rx="8"/>
  <text class="t nt" x="850" y="114">👤  Browser</text>
  <text class="s ns" x="850" y="132">Supabase JWT</text>

  <rect class="purple" x="760" y="172" width="180" height="50" rx="8"/>
  <text class="t pt" x="850" y="194">Supabase</text>
  <text class="s ps" x="850" y="212">Postgres + Auth</text>

  <rect class="purple" x="760" y="244" width="180" height="50" rx="8"/>
  <text class="t pt" x="850" y="266">OpenRouter</text>
  <text class="s ps" x="850" y="284">LLM inference</text>

  <rect class="purple" x="760" y="316" width="180" height="44" rx="8"/>
  <text class="t pt" x="850" y="338">Deepgram (STT)</text>

  <rect class="purple" x="760" y="376" width="180" height="44" rx="8"/>
  <text class="t pt" x="850" y="398">TTS (ElevenLabs / OpenAI)</text>

  <rect class="purple" x="760" y="450" width="180" height="64" rx="8"/>
  <text class="t pt" x="850" y="474">Twilio WhatsApp</text>
  <text class="s ps" x="850" y="494">shared sender number</text>

  <!-- External-to-VPS arrows -->
  <path class="arrow" d="M 760 117 L 730 117 L 730 117 L 702 117" marker-end="url(#a)"/>
  <path class="arrow" d="M 760 470 L 720 470 L 720 142 L 702 142" marker-end="url(#a)"/>
  <text class="lbl" x="720" y="160" text-anchor="middle">twilio webhook</text>
  <path class="arrow" d="M 690 506 L 760 268" marker-end="url(#a)"/>
  <path class="arrow" d="M 690 220 L 760 197" marker-end="url(#a)"/>
  <text class="lbl" x="708" y="184" text-anchor="start">asyncpg</text>
  <path class="arrow" d="M 690 230 L 760 336" marker-end="url(#a)"/>
  <path class="arrow" d="M 690 240 L 760 396" marker-end="url(#a)"/>

  <!-- Home-to-VPS (WebRTC) -->
  <path class="arrow-ok" d="M 220 232 C 250 232, 240 320, 278 320" marker-end="url(#a)"/>
  <text class="lbl" x="244" y="290" text-anchor="middle">WebRTC audio</text>
</svg>
