Willi Krappen

Multi-Agent Canvas

Beobachtbares Multi-Agent-System: Sonnet-Orchestrator delegiert an parallele Haiku-Worker, zwei Kritiker debattieren, ein Judge entscheidet — alles streamt live auf einen Graph-Canvas, jede Sitzung als event-sourced Permalink reproduzierbar.

Schlüsselentscheidung

Mid-stream Critic-Interrupt rausgenommen — sah spektakulär aus, produzierte aber schlechteren Output (das Modell reagiert auf sein eigenes halbfertiges Ergebnis). Die Debatte feuert jetzt nach Layout v1 gegen das fertige Ergebnis. Weniger visuell aufregend, dafür inhaltlich besser. Das gleiche Muster zieht sich durch: keine UX-Theater-Lügen, lieber den Defer offen kommunizieren (steer_queued → steer_applied).

Skizziere ein Layout, leg ein Markenbild dazu, tipp einen Prompt — eine Sonnet-4.6-Orchestrator-Instanz schlägt eine Besetzung aus Haiku-4.5-Workern vor, fächert parallele Entwürfe auf, lässt zwei Kritiker (Performance + Ästhetik) gegen einen Judge debattieren und liefert eine fertige Landing-Page. Jeder Token streamt in Echtzeit auf einen Graph-Canvas; jede Nachricht zwischen den Agenten ist event-sourced und per Permalink wiederholbar. Die Landing-Page ist nur das Vehikel — das eigentliche Artefakt ist das Engineering-Substrat darunter: WebSocket-Multiplexing über parallele Anthropic-Streams, Event-Source-Replay, Prompt-Caching mit Live-Anzeige der Cache-Hits, ein ehrlicher Cancel- und Steer-Lebenszyklus, eine auf dem Canvas sichtbar verzweigende und wieder zusammenführende Topologie.

Die These

Die Landing-Page ist nur das Vehikel — das eigentliche Artefakt ist das Engineering-Substrat darunter: WebSocket-Multiplexing über parallele Anthropic-Streams, ein event-sourced Replay-Modell, Prompt-Caching mit Live-Anzeige der Cache-Hits, ein ehrlicher Cancel- und Steer-Lebenszyklus und eine Multi-Agent-Debatte, die auf dem Canvas sichtbar verzweigt und wieder zusammenführt.

Der Flow

Sechs Stufen, vom Skizze-Input zum auto-publizierten Layout. Jede Stufe ist auf dem Canvas live sichtbar — Nodes erscheinen, streamen, kollabieren oder leuchten je nach Lifecycle-Zustand.

   ┌─────────────┐
   │   Inputs    │   prompt · reference image · paint-canvas sketch
   └──────┬──────┘
          ▼
   ┌─────────────┐    propose_roster (tool use, vision)
   │ Orchestrator│    delegate(Designer, alts=3)  ─┐
   │  Sonnet 4.6 │    delegate(Mascot,   alts=3)  ─┤  ← parallel
   │ + thinking  │    delegate(Copywriter)        ─┤
   └──────┬──────┘    delegate(Layout)            ─┘
          ▼
   ┌────────────────────────────┐
   │  Workers (Haiku 4.5)       │
   │  Designer×3 · Mascot×3     │
   │  Copywriter · Layout       │
   └──────┬─────────────────────┘
          ▼
   ┌─────────────┐       ┌──── Critic-Performance ┐
   │  Layout v1  │──────►│                         │── Judge ─► one ISSUE/FIX
   └──────┬──────┘       └──── Critic-Aesthetics ──┘                │
          │                                                          │
          └─────► (user approves) ────► Layout v2 ────► auto-publish ◄─┘
                                                            │
                                                            ▼
                                                   /r/<id> permalink replay

Sechs Engineering-Probleme

Jedes hat eine eigene Tiefen-Doku im Repo unter docs/hard-problems/. Das sind die Sachen, die nicht aus einem One-Shot-Prompt herausfallen.

01

WebSocket-Multiplexing über N parallele Anthropic-Streams

Ein einziger Socket, monotonisch wachsende Per-Session-Sequence-IDs, Per-Agent-Demux auf dem Client. Kein paralleler Socket pro Worker — eine Verbindung, viele Sub-Streams.

02

Cancel und Steer mitten im Stream — ohne State-Korruption

Echter Cancel über AbortController; Steer ist ehrlich darüber, dass es erst im nächsten Zug greift (steer_queued vs. steer_applied als zwei sichtbare Events). Kein UX-Theater — die Grenzen des Streaming-Protokolls werden offen kommuniziert.

03

Spekulative parallele Entwürfe mit hartem Kosten-Deckel

Designer ×3, Mascot ×3 laufen parallel über Promise.all. USD-Caps pro Session und pro Monat werden vor jedem Modell-Call geprüft; enge maxTokens-Budgets pro Worker verhindern Ausreißer bei einzelnen Calls.

04

Skizze → Vision → eingeschränkte Layout-Generierung

Natives HTML-Canvas als Mal-Eingabe (keine Excalidraw/tldraw-Abhängigkeit) → base64-PNG → Vision-Payload an den Orchestrator → ein Layout-Prompt mit harten Constraints, das die Skizze respektiert.

05

Event-sourced Replay über non-deterministische LLM-Outputs

Jeder WebSocket-Frame wird an libSQL angehängt; Permalink-Replay liest aus dem Log — keine Re-Inference. Die Permalinks sind bit-identische visuelle Replays, kein erneuter Prompt.

06

Prompt-Cached Orchestrator-System-Prompt mit Live-HUD

cache_control: ephemeral auf dem System-Prompt; das Cache-Hit-Signal wird als Live-HUD-Badge sichtbar. Cold-vs-Warm-Run-Kosten werden gemessen und im README dokumentiert.

Stack — pragmatisch ausgewählt

Eine Schicht pro Entscheidung. Wo nötig erklärt — z.B. Sonnet vs. Opus für Tool-Routing, Bun statt Node für Native-WS.

SchichtWahl
RuntimeBun 1.3
APIHono + native Bun WebSocket
Event-StorelibSQL (file-mode lokal, Turso-kompatibel)
Anthropic-SDKmessages.stream() mit adaptivem Thinking
Orchestratorclaude-sonnet-4-6 (5× günstiger als Opus, gleichwertig für Tool-Routing)
Workerclaude-haiku-4-5
FrontendNext.js 15 App Router + React 19
Canvas@xyflow/react + custom labeled-bead edges
StateZustand mit per-Agent-Slices (keine Context-Re-Renders)
UITailwind + Radix Dialog + Framer Motion + Sonner
Skizzen-Inputnatives <canvas> (keine Excalidraw/tldraw-Dep)
DeployDocker + Caddy auf einer VPS

Tradeoffs — offen genannt

Die unangenehmen Entscheidungen, die in den meisten Demos versteckt werden. Hier sichtbar gemacht.

Replay reproduziert den aufgenommenen Lauf, keinen neuen Modell-Aufruf

Permalinks sind bit-identische visuelle Replays aus dem Event-Log — kein neuer Prompt an das Modell. Der Footer der Landing-Page sagt das offen.

Steer greift erst im nächsten Zug

Das Anthropic-Streaming-Protokoll hat keinen Interrupt mitten in der Message; so zu tun wäre UX-Theater. Das Zwei-Event-Protokoll steer_queued → steer_applied macht die Verzögerung sichtbar.

Die Besetzung ist nicht voll emergent

Der System-Prompt drängt den Orchestrator dazu, eine Debatte aus Performance-Critic, Aesthetics-Critic und Judge vorzuschlagen — aus Layout-Gründen für den Canvas. Voll emergent habe ich es ausprobiert: das Ergebnis waren flachere Besetzungen und schwächere Reviews.

Kosten-Deckel zwischen Calls, nicht innerhalb eines Calls

Mitigation: enge maxTokens-Budgets pro Worker. Dokumentiert in docs/hard-problems/03-speculative-drafts.md.

Critic-Interrupt mitten im Stream haben wir wieder rausgenommen

Echte Interrupts sahen spektakulär aus — produzierten aber schlechteren Output (das Modell reagiert auf sein eigenes halbfertiges Ergebnis). Die Debatte startet jetzt nach Layout v1, gegen das fertige Ergebnis. Weniger spektakulär anzusehen, dafür inhaltlich besser.

UI bewusst auf Deutsch

System-Marker (ISSUE, FIX, HEADLINE, <!doctype html>, JSON-Keys) bleiben englisch — die Parser hängen daran. Der Worker-Output ist deutsch.

Was bewusst draußen ist

  • Multi-Tenant — ein einziger Anthropic-Key, ein globaler Kosten-Pool.
  • Auth — die Demo ist öffentlich, Cost-Caps sind die einzige Sperre.
  • Mobiler Canvas — Desktop-only, Mobile zeigt einen Fallback-Banner mit Demo-Video.
  • Verzweigen aus einem Permalink — das Datenmodell unterstützt es, die UI nicht.
  • Versionierte Event-Log-Schemata — additive Typen verträgt das Schema problemlos, Umbenennungen würden alte Replays zerschießen.

Deployment

Docker-Compose plus Caddy auf einer VPS. Caddy holt TLS automatisch; die API serviert WebSocket auf /ws, Replay auf /r/<id>, alles andere routet zu Next.js. Live unter multi.prototyp.ms — Quelle offen unter github.com/stackola/multi.