Elixir BEAM OTP Phoenix LiveView

Was diese Agenten-
Demo kann, und
wo sie aufhört.

Eine Schleife, die plant, Werkzeuge ausführt und die Ergebnisse liest, und die sich fehlende Werkzeuge selbst schreibt. Sie läuft in einfachen OTP-Prozessen, ohne Agent-Framework.

Diese Seite geht die beweglichen Teile durch, spielt einen Lauf Schritt für Schritt nach und listet die Grenzen so, wie sie heute im Code stehen. Jede Zahl darauf stammt aus dem Quellcode.

Drei Seiten

Eine App, drei Blicke auf einen Agenten

Jede Seite fügt der vorigen eine Idee hinzu. Alle teilen sich dieselben Werkzeuge und Prozesse.

/

Klassischer Agent vs. KI-Agent

Dasselbe Ziel geht an zwei Agenten, die sich nur in ihrem Gehirn unterscheiden. Der eine nutzt Stichwortregeln (ClassicPlan), der andere ein Modell (AiPlan, Mock oder Claude). Die Ergebnisse stehen nebeneinander.

/dynamic

Werkzeuge, zur Laufzeit geschrieben

Du beschreibst ein Werkzeug, und ein Modell schreibt das Elixir-Modul. Du siehst zu, wie es den Sanitizer passiert, kompiliert, unter einem versionierten Namen geladen wird und einen Probeaufruf beantwortet. Die Registry listet alle bisher generierten Werkzeuge.

/chat/:id

Ein Agent, der seine Werkzeuge selbst wählt

Jede Unterhaltung ist ein GenServer. Ein Worker fährt die Schleife, und Fortschritt, Freigaben, die Spur und gemerkte Fakten erreichen die LiveView über PubSub, sobald sie passieren.

Einen Lauf durchklicken

Was passiert, wenn du eine Nachricht schickst

Wähle eine Aufgabe und geh sie Schritt für Schritt durch. Das Schleifen-Diagramm hebt den Teil hervor, der gerade arbeitet. Das ist eine Nachstellung: Werkzeugnamen und Texte sind Beispiele, aber jeder Schritt ist einer, den der Code tatsächlich macht.

    Ergebnisse gehen als Beobachtungen zurück, nächste Runde keine Schritte mehr: die Begründung ist die Antwort Planer ToolPlanner.plan/2 Pipeline nur bei neuem Werkzeug Werkzeug Limits.run + Tool.run Antwort
    Architektur

    Gehirn, Schleife, Körper

    Der einzige Teil, der etwas entscheidet, ist der Planer, und er ist austauschbar. Alles drumherum ist einfaches OTP und Phoenix: die Schleife, die Werkzeuge, die Prozesse und die Prüfungen.

    BROWSER PROZESSE GEHIRN ZUSTAND ChatLive /chat/:id · Freigabe · Spur ChatAgent GenServer pro Unterhaltung cast PubSub Worker Task.Supervisor, einer pro Aufgabe Fortschritt TaskAgent die Schleife, max. 5 Runden ToolPlanner Mock (Stichwörter) · Claude (JSON) sieht Tools, Verlauf, Ergebnisse, Fakten Generator Mock · Claude · Ollama, schreibt Tools Werkzeuge Katalog + generiert neues Werkzeug du gibst neuen Code frei Memory Fakten, JSON-Datei Storage ETS-Registry + .ex-Dateien Trace :telemetry-Spans, Tokens Registry findet den Agenten per ID
    Kontext

    Was der Planer zu sehen bekommt

    Das Modell merkt sich zwischen zwei Aufrufen nichts. Vor jeder Runde setzt TaskAgent den Kontext des Planers neu zusammen: sechs Teile, jeder aus einer anderen Quelle, jeder unterschiedlich lange gültig. Alles, was der Agent „weiß“, muss darin stehen.

    System-Prompt fest

    Die Regeln: vorhandene Werkzeuge nutzen, neue in snake_case erfinden, nach Ergebnissen neu planen, dauerhafte Fakten merken.

    ToolPlanner.Anthropic @system

    Vorhandene Werkzeuge jede Runde neu

    Der Katalog plus jedes generierte Werkzeug aus der Registry, mit Name, Pflichtparametern und Beschreibung. Ein in Runde 1 geschriebenes Werkzeug wird in Runde 2 angeboten.

    TaskAgent.available_tools/0

    Gemerkte Fakten über Unterhaltungen hinweg

    Die Fakten aus der JSON-Datei, höchstens 50 zu je 300 Zeichen. Jede Runde neu gelesen, ein gerade gemerkter Fakt zählt also sofort.

    Memory.list/0

    Bisherige Unterhaltung eine Unterhaltung

    Die letzten 4 Runden, jeweils verkürzt auf die Aufgabe und ihre Antwort (oder ihre Schritt-Ergebnisse). Damit wird „und jetzt in Kelvin“ auflösbar.

    ChatAgent.history/1

    Aufgabe eine Aufgabe

    Die Nachricht, die du gerade geschickt hast, unverändert.

    ChatAgent.send_message/2

    Bisherige Ergebnisse eine Aufgabe, wächst pro Runde

    Jeder Schritt dieser Aufgabe mit Parametern und Ergebnis oder Fehler, jeweils nach 2000 Zeichen abgeschnitten. So beobachtet die Schleife.

    TaskAgent observations
    system: You plan the tools of a software agent. …
            (etwa 40 Zeilen Regeln)
    
    user:
    Available tools:
      - ai_plan(goal) – AI assisted task planning
      - celsius_to_fahrenheit(celsius) – Converts Celsius to Fahrenheit
      - celsius_to_kelvin(celsius) – Converts Celsius to Kelvin
      - classic_plan(goal) – Rule based task planning without AI
      - forget(id) – Forgets the remembered fact with the given id
      - remember(fact) – Remembers a short, self-contained fact …
    
    Things you remember:
      [1] The user lives in Hamburg
    
    Conversation so far:
      - User: Convert 30 degrees Celsius to Fahrenheit
        Result: 30 °C is 86 °F.
    
    Task:
    and now in Kelvin
    
    Results so far:
      - celsius_to_kelvin({"celsius":30}) -> %{kelvin: 303.15}
    Bei jedem Aufruf neu gebautDer zweite Planer-Aufruf der Aufgabe oben: celsius_to_kelvin wurde in Runde 1 generiert und steht schon in der Werkzeugliste, sein Ergebnis unter „Results so far“.
    Eine Nachricht, kein TranskriptFrühere Runden werden nicht als User- und Assistant-Nachrichten wiederholt. Sie sind Textzeilen in einer einzigen User-Nachricht, jede eine Aufgabe mit ihrer Antwort.
    Begrenzt, bis auf die WerkzeugeVerlauf, Fakten und Ergebnisse haben Grenzen. Die Werkzeugliste wächst mit jedem generierten Werkzeug, und nichts kürzt sie. Gecacht wird auch nichts: System-Prompt und Werkzeugliste gehen bei jedem Aufruf neu mit.

    So sieht der Kontext des Planers mit dem Claude-Backend aus. Der Mock-Planer bekommt dieselbe Map, schaut aber nur auf die Aufgabe und die Ergebnisse.

    Dynamische Werkzeuge

    Von „dieses Werkzeug fehlt“ zum geladenen Modul

    Nennt der Planer ein Werkzeug, das noch niemand geschrieben hat, übernimmt Dynamic.ensure_action/2. Was Sanitizer, Compiler, Schema-Prüfung oder Probeaufruf ablehnen, geht mit Begründung zurück an das Modell, insgesamt höchstens drei Versuche. Lehnst du ab, ist Schluss.

    Fähigkeiten und Grenzen

    Was sie kann, und was nicht

    Die Grenzen sind markiert. Absicht heißt eine bewusste Entscheidung für eine Demo, Abkürzung etwas, das ein echtes System bräuchte, Risiko ein Sicherheitsvorbehalt und Modell Verhalten, das in Eval-Läufen mit Claude zu sehen war.

    ↻Planen, handeln, beobachten

    Der Planer sieht die Ergebnisse jedes Schritts und plant die nächste Runde. So kann er Werte von einem Schritt in den nächsten tragen, einen gescheiterten Schritt wiederholen oder antworten.

    Agents.TaskAgent

    ✎Fehlende Werkzeuge schreiben

    Ein unbekannter Werkzeugname löst Codegenerierung aus. Das Modul wird abgelegt, kompiliert, unter einem versionierten Namen geladen und in späteren Runden wiederverwendet.

    Dynamic · Dynamic.Generator.*

    ☑Statische Sicherheitsprüfung

    Ein Gang durch den AST mit einer Verbotsliste (Shell, Eval, Spawn, Atome) und einer Liste erlaubter Module. Dateien schreiben geht nur unterhalb des Sandbox-Verzeichnisses, ins Netz nur per GET über eine einzige Funktion.

    Dynamic.Sanitizer · Dynamic.Http

    ✋Freigabe durch einen Menschen

    Neuer Code wartet vor seinem ersten Lauf auf einen Klick: freigeben oder ablehnen, mit dem vollständigen Quelltext. Standardmäßig an, nach 10 Minuten läuft die Wartezeit ab.

    ChatAgent.Worker

    ⌛Begrenzte Ausführung

    Jeder Schritt läuft genau einmal, in einem eigenen Prozess mit Zeit- und Heap-Grenze. Wird eine überschritten, entsteht ein lesbarer Fehler, auf den der Planer reagieren kann.

    Dynamic.Limits

    ↺Wiederholen, wo es passt

    Aufrufe an die Claude-API werden bei 408, 429, 5xx und 529 und bei abgebrochenen Verbindungen wiederholt, unter Beachtung von retry-after. Werkzeuge nie, damit keine Seite doppelt abgerufen wird.

    AI.Retry

    ★Gedächtnis über Unterhaltungen hinweg

    „Remember that …“ merkt sich einen Fakt, „forget fact 3“ entfernt ihn. Fakten stehen in jedem Planer-Prompt und erscheinen sofort in jedem offenen Chat.

    Memory · Tools.Remember/Forget

    ⏱Eine Spur zu jeder Antwort

    Jeder Planer-Aufruf, jede Codegenerierung, jeder Werkzeug-Schritt und jede Wartezeit auf eine Freigabe steht mit Dauer und Tokens da, darunter die Summen. Dieselben Events speisen Kennzahlen im LiveDashboard.

    Trace · :telemetry

    ⚙Ein Prozess pro Unterhaltung

    Ein GenServer pro Unterhaltung, gefunden über eine Registry. Er übersteht ein Neuladen der Seite und endet nach 15 Minuten ohne Zuschauer. Stürzt er ab, startet die LiveView einen neuen, andere Unterhaltungen bleiben unberührt.

    Agents.ChatAgent

    ⇆Austauschbare Gehirne

    Der Planer kann der Mock oder Claude sein, der Generator der Mock, Claude oder ein lokales Ollama-Modell. Du wählst pro Lauf in der Oberfläche, und die Mocks laufen ohne API-Key.

    ToolPlanner · Dynamic.Generator

    ⚖Eval-Harness

    mix agent.eval lässt feste Fälle mehrmals laufen und meldet eine Erfolgsquote mit Runden, Tokens und Zeit. Die Fälle prüfen den Weg des Agenten, nicht nur die Antwort.

    Eval · eval/cases.exs

    ◯Kein Framework

    Werkzeuge sind ein Behaviour mit vier Callbacks, validiert mit NimbleOptions. Alles andere ist GenServer, Registry, DynamicSupervisor, Task.Supervisor und PubSub.

    Tool · application.ex

    Keine Sandbox

    Der Sanitizer sieht nur den AST. Code, der ihn passiert, läuft mit allen Rechten des BEAM-Knotens. Was fehlt, ist Isolation auf Betriebssystemebene.

    Risiko

    GET erreicht alles, was der Knoten erreicht

    Die URL kommt vom Modell. Ein GET kann interne Dienste und Metadaten-Endpunkte treffen oder Daten in einen Query-String schreiben. Wo das zählt, gehört die Einschränkung ins Netz.

    Risiko

    Freigabe nur für neuen Code

    Katalog-Werkzeuge und wiederverwendete generierte Werkzeuge laufen ohne Nachfrage. Rechte pro Werkzeug gibt es nicht, und der Eval-Harness gibt automatisch frei.

    Risiko

    Kurzes Gedächtnis für die Unterhaltung

    Der Planer sieht die letzten 4 Runden, jeweils zusammengefasst, und Schritt-Ergebnisse nach 2000 Zeichen abgeschnitten. Nichts verdichtet älteren Kontext.

    Abkürzung

    Unterhaltungen leben im RAM

    Nachrichten gibt es nur im Agent-Prozess. Nach 15 Minuten ohne Zuschauer oder einem Neustart sind sie weg. Dauerhaft gespeichert werden nur Fakten und generierte Werkzeuge.

    Abkürzung

    Ein Gedächtnis für alle

    Fakten sind eine globale Liste mit höchstens 50 Einträgen zu je 300 Zeichen. Für einen Nutzer reicht das; bei mehreren bräuchte jeder sein eigenes.

    Abkürzung

    Ein einzelner Knoten

    Registry, ETS-Werkzeugregistry und geladene Module gelten nur auf einem Knoten. Ein Cluster bräuchte verteilte Suche und Code-Laden auf jedem Knoten.

    Abkürzung

    Tokens, keine Kosten

    Die Spur zählt Tokens, zeigt aber keine Preise, weil Preise im Code veralten. Ein Budget, das einen Lauf stoppt, gibt es nicht.

    Abkürzung

    JSON-Schleife statt nativem Tool-Use

    Der Planer liefert Schritte als erzwungenes JSON, statt die Tool-Aufrufe der API zu nutzen. Nur so kann der Stichwort-Mock dieselbe Schleife antreiben.

    Absicht

    Ein Schritt nach dem anderen

    Schritte laufen nacheinander, damit jeder sein eigenes Ergebnis behält. Parallele Werkzeugaufrufe gibt es nicht, und jede Unterhaltung bearbeitet eine Aufgabe zur Zeit; eine Nachricht in der Zwischenzeit wird ignoriert.

    Absicht

    Höchstens 5 Runden

    Nach 5 Runden mit Werkzeugaufrufen gibt die Schleife mit :max_rounds auf und liefert, was sie hat.

    Absicht

    Nur kleine Werkzeuge

    Generierte Werkzeuge rechnen, verarbeiten Text oder lesen eine Seite. Uhr, Zufallszahlen, Prozesse und Schreiben außerhalb des Sandbox-Verzeichnisses bekommen sie nicht.

    Absicht

    Die Mocks sind absichtlich dumm

    Der Mock-Planer sucht Stichwörter und ignoriert Verlauf und Fakten. Der Mock-Generator gibt nur seine Parameter zurück. Sie zeigen die Mechanik, keine Intelligenz.

    Absicht

    Kein Streaming

    Fortschritt kommt pro geplanter Runde und pro fertigem Schritt. Der Text des Modells selbst wird nicht gestreamt.

    Absicht

    Rechnet im Kopf

    Mit Claude kamen Temperaturumrechnungen in 0 Runden zurück: die richtige Antwort ganz ohne Werkzeug. Das ist vernünftig, überspringt aber die Werkzeuge, die der Fall testen sollte.

    Modell

    Kombiniert statt zu verketten

    Sollte es die Wörter eines Seitentitels zählen, generierte Claude ein kombiniertes Werkzeug, statt ein Seiten- und ein Text-Werkzeug zu verketten.

    Modell

    Verstümmelte Werkzeugnamen

    In 3 von 30 Läufen gerieten Teile des Schritts in den Werkzeugnamen. Die Pipeline lehnte sie ab, und der Planer fing sich wieder. Das Ausgabe-Schema beschränkt Namen jetzt per Pattern; ein Claude-Lauf hat das noch nicht geprüft.

    Modell

    Schreibt ein Werkzeug, das es schon gibt

    Einmal generierte Claude prioritize_todo_list, obwohl classic_plan im Katalog stand. Das kostete etwa 23 Sekunden.

    Modell
    Zahlen

    Die Grenzen in Zahlen

    Das sind die Voreinstellungen im Code. Die meisten lassen sich über config :agent_demo, ... ändern.

    WasWertWo
    Runden mit Werkzeugaufrufen pro Aufgabe5TaskAgent @max_rounds
    Frühere Runden, die der Planer sieht4ChatAgent @history_turns
    Schritt-Ergebnis für den Planer2000 ZeichenToolPlanner.Anthropic
    Zeit pro Werkzeug-Schritt / Probeaufruf60 s / 20 sDynamic.Limits, Dynamic
    Heap pro Werkzeug-Schritt64 MBDynamic.Limits
    HTTP-GET Timeout / Größe10 s / 2 MBDynamic.Http
    Generierungsversuche (1 + Reparaturen)3Dynamic generation_attempts
    Werkzeugname^[a-z][a-z0-9_]{2,49}$Dynamic.name_pattern/0
    Warten auf Freigabe10 minChatAgent.Worker
    Agent endet ohne Zuschauer nach15 minChatAgent @idle_timeout
    Fakten / Länge pro Fakt50 / 300 ZeichenMemory
    Wiederholungen eines Claude-API-Aufrufs3AI.Retry
    Wiederholungen eines Werkzeug-Schritts0TaskAgent
    Gemessen, nicht behauptet

    Der erste Eval-Lauf mit Claude

    Das ist mix agent.eval --planner anthropic --generator anthropic --runs 3 mit der ersten Fassung der Fälle: 27 von 30 Läufen bestanden. Die Spalte mit den Runden zeigte, dass „bestanden“ nicht immer hieß, dass der Agent den vorgesehenen Weg genommen hatte.

    Alle drei Fehlschläge: verstümmelte WerkzeugnamenDie Namensprüfung fing sie ab, der Planer fing sich in der nächsten Runde. Das Schema verbietet solche Namen jetzt.
    0 Runden, trotzdem grüncelsius, celsius_reuse und use_memory wurden ohne Werkzeug beantwortet. Also wurde nichts generiert und nichts wiederverwendet.
    Was sich seitdem geändert hatDie Fälle prüfen jetzt den Weg: Zahlen, die zu lang zum Kopfrechnen sind, min_rounds und nothing_generated. Einen Claude-Lauf mit den neuen Fällen gibt es noch nicht.