<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="de">
  <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator>
  <link href="https://blog.weisser.dev/de/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://blog.weisser.dev/" rel="alternate" type="text/html" hreflang="en" />
  <updated>2026-10-02T20:11:31+00:00</updated>
  <id>https://blog.weisser.dev/de/feed.xml</id>
  <title type="html">weisser.dev (Deutsch)</title>
  <subtitle>Developer blog by Erik Weisser – microservices, cloud, AI, automation and side projects.</subtitle>
  <author><name>Erik Weisser</name></author>
  <entry xml:lang="de">
    <title type="html">Ein KI-Agent betreibt mein Homelab – und warum das nichts Besonderes ist</title>
    <link href="https://blog.weisser.dev/blog/2026/10/02/guardrails-for-ops-agents-de/" rel="alternate" type="text/html" hreflang="de" />
    <published>2026-10-02T00:00:00+00:00</published>
    <updated>2026-10-02T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/10/02/guardrails-for-ops-agents-de/</id>
    <category term="operations" />
    <category term="ai" />
    <summary type="html">Mein Homelab hat jetzt Agenten. Einer liest Logs und erklärt, was schiefgelaufen ist. Einer läuft alle 15 Minuten, prüft, ob alles gesund ist – und versucht, Probleme zu beheben. Einer schaut sich die Ergebnisse von CVE-Scans an und aktualisiert verwundbare Abhängigkeiten in meinen eigenen Projek...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/10/02/guardrails-for-ops-agents-de/">&lt;p&gt;Mein Homelab hat jetzt Agenten. Einer liest Logs und erklärt, was schiefgelaufen ist. Einer läuft alle 15 Minuten, prüft, ob alles gesund ist – und versucht, Probleme zu beheben. Einer schaut sich die Ergebnisse von CVE-Scans an und aktualisiert verwundbare Abhängigkeiten in meinen eigenen Projekten. Alle sind &lt;a href=&quot;https://claude.com/claude-code&quot;&gt;Claude Code&lt;/a&gt; im Headless-Modus (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude -p&lt;/code&gt;), gestartet aus meinem selbstgebauten Dashboard.&lt;/p&gt;

&lt;p&gt;Die naheliegende Frage: Ist es nicht verrückt, ein LLM auf einen Server loszulassen? Meine Antwort: Wäre es – wenn man ihm eine Root-Shell gibt und weggeht. Aber so betreibt man &lt;em&gt;keine&lt;/em&gt; Automatisierung, und so betreibe ich auch diese nicht.&lt;/p&gt;

&lt;h2 id=&quot;der-aufbau-in-einem-absatz&quot;&gt;Der Aufbau in einem Absatz&lt;/h2&gt;

&lt;p&gt;Ein kleiner Dienst (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agentd&lt;/code&gt;, rund 300 Zeilen Node, keine Abhängigkeiten) läuft neben dem Docker-Host. Mein Dashboard spricht ihn per HTTP mit einem Bearer-Token an; das Dashboard selbst liegt hinter einem Login. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agentd&lt;/code&gt; startet Agenten-Läufe, verwaltet Zeitpläne und speichert jeden Lauf auf der Platte. Jeder Agent ist in einer kleinen JSON-Datei beschrieben: Name, Standardauftrag, ob er nur lesen darf, und ein gemeinsames Regelwerk. Das ist die ganze Architektur.&lt;/p&gt;

&lt;h2 id=&quot;ebene-1-eine-enge-aufgeschriebene-aufgabe&quot;&gt;Ebene 1: Eine enge, aufgeschriebene Aufgabe&lt;/h2&gt;

&lt;p&gt;Jeder Agent hat genau eine Aufgabe. Der Log-Agent bekommt: &lt;em&gt;Hol die Logs dieses Containers, gruppiere die Fehler nach Ursache, bewerte sie, schlage Fixes vor – ändere nichts.&lt;/em&gt; Der Reparatur-Agent bekommt: &lt;em&gt;Prüfe Container, aktuelle Fehler, Speicherplatz und öffentliche Endpunkte; wenn alles passt, sag das in drei Zeilen, sonst finde die Ursache und behebe sie.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dazu bekommt jeder Lauf denselben angehängten System-Prompt mit den Hausregeln:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Änderungen an Apps, Compose-Dateien, Proxy-Konfiguration oder Workflows &lt;strong&gt;nur über Git&lt;/strong&gt;: klonen, ändern, committen, pushen – der CI-Runner deployt. Nie direkt auf dem Server editieren.&lt;/li&gt;
  &lt;li&gt;Ohne Git erlaubt: Logs und Status lesen, einen einzelnen Container neu starten.&lt;/li&gt;
  &lt;li&gt;Verboten: Daten löschen, Force-Push, Secrets ausgeben, öffentliche Erreichbarkeit ändern, Hosts neu starten, Pakete installieren.&lt;/li&gt;
  &lt;li&gt;Wenn ein Fix riskant oder unklar ist: nichts ändern, berichten.&lt;/li&gt;
  &lt;li&gt;Jeder Lauf endet mit denselben drei Abschnitten: &lt;em&gt;Befund&lt;/em&gt;, &lt;em&gt;Maßnahmen&lt;/em&gt; (mit Commit-Links), &lt;em&gt;Offen&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weil der Agent in einem Arbeitsverzeichnis unterhalb meines Home-Ordners läuft, lädt er außerdem automatisch dieselbe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;, die ich für jeden anderen Assistenten pflege. Er weiß, wie die Infrastruktur aufgebaut ist, wo was liegt und &lt;em&gt;wie hier Änderungen gemacht werden&lt;/em&gt; – dasselbe Onboarding, das auch eine neue Kollegin bekäme.&lt;/p&gt;

&lt;p&gt;Ein Prompt ist aber keine Sicherheitsgrenze. Er ist die Stellenbeschreibung. Deshalb gibt es weitere Ebenen.&lt;/p&gt;

&lt;h2 id=&quot;ebene-2-jede-aktion-wird-vor-der-ausführung-geprüft&quot;&gt;Ebene 2: Jede Aktion wird vor der Ausführung geprüft&lt;/h2&gt;

&lt;p&gt;Die Agenten laufen im &lt;strong&gt;Auto-Berechtigungsmodus&lt;/strong&gt; von Claude Code. Dabei schaut sich ein separater Sicherheits-Classifier jede Aktion an, &lt;em&gt;bevor&lt;/em&gt; sie ausgeführt wird, und blockiert alles, was destruktiv, unumkehrbar oder außerhalb des Auftrags ist. Das habe ich nicht einfach geglaubt: Beim Aufbau dieses Setups wollte meine eigene interaktive Session einen Dienst per API-Aufruf von privat auf öffentlich schalten – und wurde gestoppt, weil das Ändern der öffentlichen Erreichbarkeit genau die Art Entscheidung ist, die bei einem Menschen bleiben sollte. Genau dieses Verhalten will ich auch bei einem unbeaufsichtigten Agenten.&lt;/p&gt;

&lt;h2 id=&quot;ebene-3-eine-harte-sperrliste&quot;&gt;Ebene 3: Eine harte Sperrliste&lt;/h2&gt;

&lt;p&gt;Der Classifier ist schlau; eine Sperrliste ist dumm und damit berechenbar. Jeder Lauf startet mit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--disallowedTools&lt;/code&gt; für Befehle, die nie nötig sein sollten: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf&lt;/code&gt;, Docker-Volumes löschen oder prunen, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push --force&lt;/code&gt;, Reboot und Shutdown, Container auf Hypervisor-Ebene zerstören oder stoppen, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkfs&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd&lt;/code&gt;, Paketmanager. Agenten, die nur lesen dürfen, verlieren zusätzlich das Bearbeiten von Dateien, Committen und Pushen. Der Log-Agent kann alles ansehen und nichts anfassen.&lt;/p&gt;

&lt;h2 id=&quot;ebene-4-git-ist-der-einzige-weg-hinein&quot;&gt;Ebene 4: Git ist der einzige Weg hinein&lt;/h2&gt;

&lt;p&gt;Das ist die wichtigste Ebene, und sie hat mit KI gar nichts zu tun. Meine Regel für &lt;em&gt;mich selbst&lt;/em&gt; lautet: Auf dem Server wird nichts von Hand geändert – jede Änderung ist ein Commit, und ein selbst gehosteter Runner deployt sie. Die Agenten folgen derselben Regel.&lt;/p&gt;

&lt;p&gt;Damit bekomme ich alles, was ich mir von einem Änderungsprozess wünsche, gratis:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Eine Nachvollziehbarkeit&lt;/strong&gt;: Jede Änderung ist ein Commit mit einem Trailer, der sie als Agenten-Änderung kennzeichnet.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Einen Diff&lt;/strong&gt;, den ich hinterher lesen kann.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ein Rollback mit einem Befehl&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git revert&lt;/code&gt;, pushen, fertig.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Denselben Deploy-Weg&lt;/strong&gt; wie für menschliche Änderungen – es gibt keinen „Nur-für-Agenten“-Weg in die Produktion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;ebene-5-begrenzter-schadensradius&quot;&gt;Ebene 5: Begrenzter Schadensradius&lt;/h2&gt;

&lt;p&gt;Höchstens zwei Agenten laufen gleichzeitig, ein geplanter Agent überlappt nie mit sich selbst, und jeder Lauf lässt sich aus dem Dashboard abbrechen. Secrets werden dem Modell nie als Text übergeben. Wenn der CVE-Scanner GitHub-Zugriff braucht, um private Repositories zu prüfen, wird der Token nur für die Dauer dieses einen Scans in eine temporäre Datei geschrieben und danach gelöscht.&lt;/p&gt;

&lt;h2 id=&quot;die-zusätzliche-überwachung-das-dashboard-selbst&quot;&gt;Die zusätzliche Überwachung: das Dashboard selbst&lt;/h2&gt;

&lt;p&gt;Agenten berichten, was sie getan haben. Darauf allein verlasse ich mich nicht.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Jeder Lauf wird vollständig aufgezeichnet.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agentd&lt;/code&gt; speichert den kompletten Ereignisstrom: jeden Tool-Aufruf, jeden Befehl, jede Ausgabe. Das Dashboard zeigt das live, während der Agent arbeitet, und danach als lesbaren Verlauf. Wenn ein Agent behauptet „Container neu gestartet“, sehe ich den exakten Befehl.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Das Dashboard ist unabhängig von den Agenten.&lt;/strong&gt; Container-Zustand, Neustarts, Abstürze der letzten 24 Stunden, Speicherplatz, CVE-Zahlen und Erreichbarkeit kommen direkt aus Docker und den Scannern, nicht aus der Zusammenfassung des Agenten. Meldet der Reparatur-Agent „alles gut“, während ein Container in einer Neustart-Schleife hängt, zeigt das Dashboard den Widerspruch.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub ist die zweite Aufzeichnung.&lt;/strong&gt; Alles, was ein Agent geändert hat, ist ein Commit in einem Repository – sichtbar in der normalen Historie, auf einem anderen System als dem, auf dem der Agent arbeitet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;warum-das-nichts-besonderes-ist&quot;&gt;Warum das nichts Besonderes ist&lt;/h2&gt;

&lt;p&gt;Nimm das Etikett „KI“ weg und schau, was tatsächlich da ist:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ein &lt;strong&gt;Bot-Account mit enger Aufgabe&lt;/strong&gt; – wie Dependabot oder Renovate.&lt;/li&gt;
  &lt;li&gt;Ein &lt;strong&gt;Runbook&lt;/strong&gt;, dem er folgt – wie jede Bereitschafts-Automatisierung.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Minimale Rechte&lt;/strong&gt; – eine Nur-Lesen-Rolle für Analysen, eine eingeschränkte Rolle für Fixes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Änderungen über Versionskontrolle und CI&lt;/strong&gt; – wie jede Deployment-Pipeline der letzten zehn Jahre.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Audit-Logs, ein Not-Aus und ein begrenzter Schadensradius&lt;/strong&gt; – wie bei jedem Cronjob, den man an Produktion heranlässt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keine dieser Ideen ist neu. Wir lassen seit Jahren Skripte, CI-Pipelines und Bots auf Produktionssystemen arbeiten, und das Vorgehen, das sicher zu machen, ist bekannt. Ein Agent ist ein fähigeres Skript, das sich zusätzlich selbst erklärt. Er verdient dieselbe Behandlung wie jede andere Automatisierung: klarer Auftrag, begrenzte Rechte, jede Änderung nachvollziehbar und umkehrbar. Kein blindes Vertrauen, keine Panik.&lt;/p&gt;

&lt;h2 id=&quot;was-noch-nicht-perfekt-ist&quot;&gt;Was (noch) nicht perfekt ist&lt;/h2&gt;

&lt;p&gt;Ehrlich zum aktuellen Stand:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Die Agenten laufen auf dem Hypervisor-Host, weil Claude Code dort angemeldet ist, und erben die Rechte dieses Benutzers. Die Ebenen oben begrenzen, was sie &lt;em&gt;tun&lt;/em&gt;; eine Sandbox sind sie nicht. Nächster Schritt: ein eigener Systembenutzer mit einer expliziten Liste erlaubter Befehle.&lt;/li&gt;
  &lt;li&gt;Bei allem, was nicht trivial ist, sollten die Agenten auf einen Branch pushen und einen Pull Request öffnen, statt direkt auf &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; zu pushen. Kleine, mechanische Fixes (ein Dependency-Update mit grünem Build) dürfen direkt landen. Ein Refactoring nicht.&lt;/li&gt;
  &lt;li&gt;Es gibt noch kein hartes Zeit- oder Schrittlimit pro Lauf. Ein hängender Lauf muss von Hand abgebrochen werden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nichts davon blockiert ein Homelab. Es sind dieselben Backlog-Punkte, die man für jede neue Automatisierung aufschreiben würde – und genau darum geht es.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="de">
    <title type="html">Mein eigenes Portainer – samt Agenten – an einem Tag mit KI gebaut</title>
    <link href="https://blog.weisser.dev/blog/2026/10/02/building-my-own-portainer-and-agents-with-ai-de/" rel="alternate" type="text/html" hreflang="de" />
    <published>2026-10-02T00:00:00+00:00</published>
    <updated>2026-10-02T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/10/02/building-my-own-portainer-and-agents-with-ai-de/</id>
    <category term="operations" />
    <category term="ai" />
    <summary type="html">Mein Homelab besteht aus einem Proxmox-Host, einem Docker-Host und einem wachsenden Haufen Container: ein paar Dienste, die ich selbst baue, viele öffentliche Images, statische Seiten, ein Datenbank-Cluster. Portainer zeigte mir Container an, wusste aber nichts über mein Setup – welcher Hostname ...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/10/02/building-my-own-portainer-and-agents-with-ai-de/">&lt;p&gt;Mein Homelab besteht aus einem Proxmox-Host, einem Docker-Host und einem wachsenden Haufen Container: ein paar Dienste, die ich selbst baue, viele öffentliche Images, statische Seiten, ein Datenbank-Cluster. Portainer zeigte mir Container an, wusste aber nichts über &lt;em&gt;mein&lt;/em&gt; Setup – welcher Hostname öffentlich ist und welcher nur im LAN, welcher Container zu welchem Git-Repo gehört, wann zuletzt deployt wurde, ob ein Backend gerade überhaupt gebraucht wird.&lt;/p&gt;

&lt;p&gt;Also habe ich mir das Dashboard gebaut, das ich eigentlich wollte. Der erste Commit entstand mittags; abends lief es produktiv, mit rund 6.700 Zeilen JavaScript, CSS und HTML, 26 Tests und ohne Datenbank. Den Code habe ich fast nicht selbst geschrieben – ich habe Ergebnisse beschrieben, die KI hat sie gebaut und geprüft, und mehrere KI-Agenten haben parallel gearbeitet. Dieser Beitrag zeigt, was dabei herausgekommen ist, wie die Arbeit ablief und was unterwegs schiefging. (Alle Screenshots stammen aus einem Demo-Modus mit erfundenen Daten – keine meiner echten Anwendungen ist darauf zu sehen.)&lt;/p&gt;

&lt;h2 id=&quot;was-es-kann&quot;&gt;Was es kann&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ein Dashboard, das den Host kennt.&lt;/strong&gt; CPU, Arbeitsspeicher, Platten (schnell und langsam), GPU und alles, was in den letzten 24 Stunden abgestürzt ist – auf einen Blick und live aktualisiert.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/dashboard.png&quot; alt=&quot;Das Dashboard mit Host-Last, GPU und Container-Zahlen&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stacks und Container, sortiert danach, wie sie gepflegt werden.&lt;/strong&gt; Selbst Gebautes ist von öffentlichen Images getrennt. Öffentliche Images bekommen einen &lt;em&gt;Auto-Update&lt;/em&gt;-Schalter pro Image (ein nächtlicher Job bittet Watchtower, genau die Images zu aktualisieren, die ich aktiviert habe). Jeder Stack verlinkt sein Git-Repo und zeigt, &lt;em&gt;wann zuletzt deployt wurde&lt;/em&gt;. Start, Stop, Neustart und ein 48-Stunden-Log-Viewer mit Export sind einen Klick entfernt.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/stacks.png&quot; alt=&quot;Stacks, sortiert nach Eigenentwicklung und öffentlichen Images&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Echtzeit-Zahlen, ohne dafür zu bezahlen.&lt;/strong&gt; Der naheliegende Weg zu CPU und Speicher pro Container ist &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker stats&lt;/code&gt; – und der ist erstaunlich teuer: Jeder Aufruf dauert ein bis zwei Sekunden pro Container und hält den Docker-Daemon beschäftigt. Das Dashboard liest stattdessen die cgroup-Zähler direkt aus dem Dateisystem – eine Handvoll winziger Dateizugriffe pro Container – und schickt alle zwei Sekunden Updates per Server-Sent Events an den Browser, aber nur, solange jemand zuschaut.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/system.png&quot; alt=&quot;System-Ansicht mit Live-Diagrammen für CPU, Speicher, Netzwerk und Platten&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ein Ort für „was ist erreichbar“.&lt;/strong&gt; Docker-Apps hinter dem Reverse Proxy, statische Seiten, externe Seiten, interne Dienste – jeweils mit Typ, öffentlich oder nur LAN, Zugriffsweg, Repo und letztem Deploy. Öffentlich/privat ist ein Schalter; nach dem Umlegen fragt das Dashboard von außen so lange nach, bis der neue Zustand &lt;em&gt;bestätigt&lt;/em&gt; ist, statt nur darauf zu hoffen.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/exposure.png&quot; alt=&quot;Die Erreichbarkeits-Übersicht mit Öffentlich/Privat-Schaltern&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backends, die schlafen, bis sie gebraucht werden.&lt;/strong&gt; Ein Backend aus mehreren Diensten, das ein paar Mal am Tag benutzt wird, muss nicht den ganzen Tag Speicher und CPU belegen. Ein kleiner Proxy im Dashboard nimmt die Anfragen entgegen, startet den Stack, falls er schläft, &lt;strong&gt;hält die Anfrage fest&lt;/strong&gt;, bis die Dienste antworten, und leitet sie dann weiter – ohne Timeouts, WebSockets inklusive. Nach einer einstellbaren Leerlaufzeit schläft der Stack wieder ein. Ich habe einen Kaltstart von etwa 34 Sekunden für fünf Java-Dienste gemessen; die erste Anfrage wartet einfach so lange und gelingt. Wichtige Details: Health-Checks externer Monitore zählen nicht als Aktivität (und der Proxy beantwortet sie, solange der Stack schläft), und ein &lt;em&gt;Aktivitätsschutz&lt;/em&gt; hält einen Stack wach, solange seine Container arbeiten – auch ohne HTTP-Verkehr.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eine Login-Schranke für Apps ohne Login.&lt;/strong&gt; Manche Werkzeuge (eine Konverter-Oberfläche, ein Download-Client) haben gar keine Authentifizierung. Ein einziger Reverse-Proxy-Baustein – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward_auth&lt;/code&gt; gegen das Dashboard – stellt sie hinter einen gemeinsamen Login, domainweit und mit sicherer Weiterleitung zurück dorthin, wo man herkam.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agenten, die Logs lesen und CVEs jagen.&lt;/strong&gt; Das ist der Teil, der mir am besten gefällt:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/agents.png&quot; alt=&quot;Die Agenten-Ansicht mit Zeitplänen und Läufen&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Der &lt;strong&gt;Log-Agent&lt;/strong&gt; kann Logs analysieren, nach &lt;em&gt;Anomalien&lt;/em&gt; suchen (im Vergleich mit den Stunden davor) oder die Docker-Compose-Dateien anhand echter Messwerte feintunen und Änderungen als Pull Request vorschlagen.&lt;/li&gt;
  &lt;li&gt;Der &lt;strong&gt;CVE-Agent&lt;/strong&gt; arbeitet mit Trivy-Scan-Ergebnissen und hat vier Modi: &lt;em&gt;nur finden&lt;/em&gt;, &lt;em&gt;Triage&lt;/em&gt; (wird das verwundbare Paket zur Laufzeit überhaupt genutzt?), &lt;em&gt;als Pull Request beheben&lt;/em&gt; oder &lt;em&gt;direkt auf den Standard-Branch beheben&lt;/em&gt; nach grünem Build. Vor einem Modus, der etwas ändert, prüft er, ob GitHub erreichbar und beschreibbar ist – andernfalls wird der Lauf still auf „nur lesen“ herabgestuft.&lt;/li&gt;
  &lt;li&gt;Alles ist &lt;strong&gt;in der Oberfläche konfigurierbar&lt;/strong&gt;: Modi, Modell, Zeitlimit, Aufträge (Vorlagen mit Platzhaltern), Repositories, Zeitpläne und sogar das Befehls-Präfix, mit dem Docker erreicht wird – damit dasselbe Setup auf einem anderen Rechner läuft.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/security.png&quot; alt=&quot;CVE-Übersicht mit Triage- und Fix-Modi&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ein echtes Beispiel, warum der Triage-Modus sich lohnt: Bei einer älteren React-Seite meldete der Scanner 79 kritische und hohe Befunde, drei davon kritisch. Der Agent las den Code und das Build-Setup und stufte fast alle als &lt;em&gt;Build- oder Dev-Werkzeug ein, das nie in einen Browser ausgeliefert wird&lt;/em&gt; – Gesamtrisiko gering – und nannte die wenigen Fixes, auf die es wirklich ankommt. Genau diesen Bericht hätte ich mir von einem Kollegen gewünscht – geliefert in Minuten.&lt;/p&gt;

&lt;p&gt;(Wie ich solche Agenten in ihrer Spur halte, behandelt &lt;a href=&quot;/blog/2026/10/02/guardrails-for-ops-agents-de/&quot;&gt;der vorige Beitrag&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Einstellungen für alles.&lt;/strong&gt; Aktualisierungsintervalle, Live-Metriken, Auto-Update-Uhrzeit, Schlaf-Vorgaben, Agentenverhalten – eine Seite, keine Code-Änderungen.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/servdash/settings.png&quot; alt=&quot;Die Einstellungs-Seite&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;wie-ich-mit-der-ki-gearbeitet-habe&quot;&gt;Wie ich mit der KI gearbeitet habe&lt;/h2&gt;

&lt;p&gt;Für das Dashboard habe ich kaum einen Editor angefasst. Der Ablauf sah so aus:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Das Ergebnis in normalen Sätzen beschreiben&lt;/strong&gt;, auch die lästigen Teile („es darf nicht in einen Timeout laufen, während das Backend startet“), und die KI zuerst eine kurze Spezifikation in die &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; des Repos schreiben lassen.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Die KI in kleinen Schritten bauen lassen&lt;/strong&gt; – committen, pushen, per CI deployen und dann &lt;em&gt;live verifizieren&lt;/em&gt;: mit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; einloggen, die neuen Endpunkte aufrufen, die Antwortform prüfen, die Tests laufen lassen. „Es kompiliert“ galt nie als fertig.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Parallele Agenten für unabhängige Arbeit.&lt;/strong&gt; Für die größere Sitzung hinter diesem Beitrag liefen sechs Sub-Agenten gleichzeitig – Speicher und Migrationen, CVE-Fixes, der Reverse Proxy, die Blog-Struktur, Hosting-Schalter, eine Medien-Pipeline –, jeder mit derselben Briefing-Datei (den Regeln: alles über Git, keine Geheimnisse in Ausgaben, vor dem Melden verifizieren) und eigenem Code-Bereich.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Eine einzige Quelle für Kontext&lt;/strong&gt;: eine &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;, die jeder Agent lädt. Sie ist langweilig, und sie ist der Grund, warum die Agenten nicht dieselben Erkenntnisse immer wieder neu entdecken.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;was-schiefging-und-was-ich-gelernt-habe&quot;&gt;Was schiefging (und was ich gelernt habe)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Das Dashboard sagte „öffentlich“ bei etwas Privatem.&lt;/strong&gt; Der Status wurde daraus abgeleitet, &lt;em&gt;in welchem Ordner eine Konfigurationsdatei liegt&lt;/em&gt; – und ein Deploy aus Git legte die Datei still wieder zurück. Die Lösung: Maßgeblich ist &lt;em&gt;die Tunnel-Regel&lt;/em&gt;, also das, was tatsächlich erreichbar ist, nicht das, was eine Datei behauptet. Zustand aus der Wirklichkeit ableiten.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;„Das Dashboard ist langsam“ lag nicht am Dashboard.&lt;/strong&gt; Drei Videokonvertierungen und mehrere parallele Builds hatten eine einzelne langsame Festplatte gesättigt (ein Plattentest zeigte etwa 8 dauerhafte Schreibvorgänge pro Sekunde gegenüber 444 auf der SSD). Die Lösungen: Datenbanken auf die SSD, schwere Jobs mit einer Sperre serialisiert, CPU-Deckel für den Konverter und &lt;em&gt;Stale-while-revalidate&lt;/em&gt;-Caching mit Aufwärmen im Hintergrund, damit die Oberfläche nie auf eine teure Abfrage wartet.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Das Verschieben des Container-Speichers bei laufenden Containern&lt;/strong&gt; ließ sie in den alten, gelöschten Pfad schreiben. Uploads scheiterten etwa eine Stunde lang; der Log-Agent brachte das Symptom ans Licht, und die Ursache war mein eigener Speicherumzug. Lehre: Nach einer Migration mit einem &lt;em&gt;Schreibtest&lt;/em&gt; prüfen, nicht mit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Selbst die Screenshots brauchten einen Umweg.&lt;/strong&gt; Das &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--screenshot&lt;/code&gt; von Headless-Chrome lieferte bei einer Seite mit Live-Ereignisstrom leere Bilder; den Browser über sein DevTools-Protokoll zu steuern löste das – und lieferte als Bonus bessere Bilder in voller Höhe.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;was-sich-zum-abschauen-lohnt&quot;&gt;Was sich zum Abschauen lohnt&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Keine Datenbank.&lt;/strong&gt; JSON-Dateien neben dem Container reichen für Einstellungen und Schlafzustand und machen das Ganze trivial umziehbar.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Git ist die Quelle der Wahrheit für Konfiguration; die Wirklichkeit ist die Quelle der Wahrheit für Zustand.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Billig messen.&lt;/strong&gt; Lesen, was der Kernel ohnehin zählt.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Würdevoll ausfallen.&lt;/strong&gt; Ist GitHub nicht erreichbar, berichtet der Agent trotzdem; ist die Tunnel-API nicht erreichbar, sagt der Status „unbekannt“, statt zu lügen.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Die KI für die Verifikation einsetzen, nicht nur für den Code.&lt;/strong&gt; Die wertvollsten Agentenläufe waren die, die andere Arbeit geprüft haben.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Das Dashboard ist (noch) nicht Open Source und stark auf mein eigenes Setup zugeschnitten. Aber die Bausteine – cgroup-basierte Metriken, ein Aufweck-Proxy, der Anfragen festhält, ein &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forward_auth&lt;/code&gt;-Gate, modusbasierte Agenten mit GitHub-Vorprüfung – sind klein genug, um sie an einem Nachmittag selbst zu bauen. Heute hat mich daran erinnert, wie kurz „ein Nachmittag“ geworden ist.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">awesome-opencode: 108 Agents, 15 Skills, and a 2-Minute Setup for OpenCode</title>
    <link href="https://blog.weisser.dev/blog/2026/03/27/awesome-opencode/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-27T00:00:00+00:00</published>
    <updated>2026-03-27T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/27/awesome-opencode/</id>
    <category term="ai" />
    <category term="tools" />
    <summary type="html">Setting up OpenCode properly – agents, skills, model configuration, MCP servers, project rules – takes about 30 minutes if you read the docs and do it by hand. Or 2 minutes with one command:
</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/27/awesome-opencode/">&lt;p&gt;Setting up &lt;a href=&quot;https://opencode.ai&quot;&gt;OpenCode&lt;/a&gt; properly – agents, skills, model configuration, MCP servers, project rules – takes about 30 minutes if you read the docs and do it by hand. Or 2 minutes with one command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx @weisser-dev/awesome-opencode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/weisser-dev/awesome-opencode&quot;&gt;awesome-opencode&lt;/a&gt; is an interactive CLI tool and template collection that analyzes your project, recommends the right configuration, and generates everything OpenCode needs to work well with your stack. 108 agents across 10 categories, 15 skills, 18 curated MCP servers, smart model detection, and an auto-generated AGENTS.md – all from a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx&lt;/code&gt; command.&lt;/p&gt;

&lt;h2 id=&quot;why-this-exists&quot;&gt;Why this exists&lt;/h2&gt;

&lt;p&gt;OpenCode is powerful, but the configuration surface is large. You need to decide which agents to install, how to write skill definitions, which MCP servers to connect, how to assign models to different agent tiers, and what project rules to define in AGENTS.md. Most of this requires reading multiple documentation pages, and the result is that many teams either skip configuration entirely or set up a minimal config that doesn’t take advantage of what OpenCode can do.&lt;/p&gt;

&lt;p&gt;The specific problems I wanted to solve:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Agent discovery.&lt;/strong&gt; With 108 available agents across 10 categories, knowing which ones are relevant to a Terraform-heavy infrastructure project versus a React frontend is not obvious. The tool scans your project files (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tf&lt;/code&gt; maps to Terraform, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rs&lt;/code&gt; to Rust, and so on for 28 language/technology mappings) and recommends agents with reason tags explaining why – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Terraform (IaC))&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(Docker / Containers)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(default)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Model optimization.&lt;/strong&gt; Using the same frontier model for every agent is expensive and unnecessary. A code-writing agent needs Opus or GPT-5.2, but a context manager or documentation agent runs fine on Haiku or Flash. Configuring this per agent manually is tedious. The tool detects your existing models, benchmarks them, and assigns the right tier automatically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;MCP server fragmentation.&lt;/strong&gt; MCP servers are scattered across &lt;a href=&quot;https://mcp.so&quot;&gt;mcp.so&lt;/a&gt;, GitHub repositories, and the &lt;a href=&quot;https://registry.modelcontextprotocol.io&quot;&gt;official registry&lt;/a&gt;. The tool curates 18 servers filtered by your project languages, plus live search against the official MCP Registry API.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AGENTS.md.&lt;/strong&gt; Project rules are one of the most impactful things you can configure, but writing them from scratch for every project is the kind of task that gets deferred forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The CLI follows a linear flow that takes about two minutes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Language detection.&lt;/strong&gt; Auto-detects languages by scanning file extensions in your project root. You can also select manually for fresh projects with no files yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Agent selection.&lt;/strong&gt; Presents all 108 agents organized by category – Core Development, Language Specialists, Infrastructure, Quality &amp;amp; Security, Data &amp;amp; AI, Developer Experience, Specialized Domains, Business &amp;amp; Product, Meta &amp;amp; Orchestration, Research &amp;amp; Analysis. Agents recommended for your detected languages appear first with reason tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Skill selection.&lt;/strong&gt; 15 skills covering common workflows: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git-release&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pr-review&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;migration&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-patterns&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependency-audit&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;incident-postmortem&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-optimize&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adr-write&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api-contract&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changelog-generate&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci-pipeline&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env-setup&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error-triage&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;performance-profile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Model strategy.&lt;/strong&gt; Reads your existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opencode.json&lt;/code&gt;, fingerprints every configured model (even through custom Bedrock or Azure provider prefixes like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eu.anthropic.claude-opus-4-6-v1&lt;/code&gt;), shows coding benchmarks on a 0–100 scale and cost tiers from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$$$$$&lt;/code&gt;, then offers to auto-optimize: frontier models for code-writing agents, strong models for review agents, fast models for context and exploration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. MCP servers.&lt;/strong&gt; Curated list filtered by your languages, plus an optional live search against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registry.modelcontextprotocol.io&lt;/code&gt; to find and configure any registered server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Step limits.&lt;/strong&gt; Optional per-agent step limits for cost control – code-writing agents get unlimited steps, review agents get 10–15, fast agents get 5–10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Generation.&lt;/strong&gt; Writes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.opencode/agents/*.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.opencode/skills/*/SKILL.md&lt;/code&gt;, updates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opencode.json&lt;/code&gt;, creates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.opencode/advanced.json&lt;/code&gt; (state for re-runs), and generates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt; with project-specific rules.&lt;/p&gt;

&lt;p&gt;On re-run, the tool detects the existing configuration and offers to start OpenCode directly or reconfigure.&lt;/p&gt;

&lt;h2 id=&quot;model-fingerprinting&quot;&gt;Model fingerprinting&lt;/h2&gt;

&lt;p&gt;The part I find most technically interesting is the model detection. LLM provider IDs rarely follow a consistent naming convention. If you use Amazon Bedrock, your model ID might be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abcd/eu.anthropic.claude-opus-4-6-v1&lt;/code&gt;. Azure has its own format. Self-hosted endpoints use whatever the operator decided. But the tool still needs to know that this is Claude Opus 4 so it can look up the benchmark score and assign the right agent tier.&lt;/p&gt;

&lt;p&gt;The solution is 26 model fingerprints – regex patterns that extract the canonical model identity from any provider-prefixed ID. The fingerprinting covers Anthropic (Claude), OpenAI (GPT, o-series), Google (Gemini), DeepSeek, Meta (Llama), and Mistral. Each fingerprint maps to a tier (frontier, strong, fast), a cost bracket, and a coding benchmark score sourced from &lt;a href=&quot;https://pricepertoken.com/leaderboards/coding&quot;&gt;pricepertoken.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The auto-optimize strategy then maps agent types to tiers:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Agent type&lt;/th&gt;
      &lt;th&gt;Model tier&lt;/th&gt;
      &lt;th&gt;Step limit&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Code-writing, language specialists&lt;/td&gt;
      &lt;td&gt;Frontier&lt;/td&gt;
      &lt;td&gt;Unlimited&lt;/td&gt;
      &lt;td&gt;Opus, GPT-5.2, o3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Code review, architecture, security&lt;/td&gt;
      &lt;td&gt;Strong&lt;/td&gt;
      &lt;td&gt;10–15&lt;/td&gt;
      &lt;td&gt;Sonnet, GPT-5.1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Exploration, docs, context management&lt;/td&gt;
      &lt;td&gt;Fast&lt;/td&gt;
      &lt;td&gt;5–10&lt;/td&gt;
      &lt;td&gt;Haiku, Flash, mini&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This means you can run 20+ agents without paying frontier prices for all of them, while the agents that actually write and review code still get the best models available.&lt;/p&gt;

&lt;h2 id=&quot;the-108-agents&quot;&gt;The 108 agents&lt;/h2&gt;

&lt;p&gt;Every agent is a markdown file with YAML frontmatter defining the description, mode, model tier, temperature, permissions, and step limit. The categories:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Core Development&lt;/strong&gt; (8) – API design, backend, frontend, fullstack, GraphQL, microservices, mobile, WebSocket&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Language Specialists&lt;/strong&gt; (22) – Angular through Vue, covering the major languages and frameworks with specialist knowledge&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; (13) – cloud architecture, Kubernetes, Terraform, Docker, SRE, incident response&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Quality &amp;amp; Security&lt;/strong&gt; (11) – code review, penetration testing, compliance, accessibility, chaos engineering&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data &amp;amp; AI&lt;/strong&gt; (12) – data engineering, ML, LLM architecture, prompt engineering, NLP&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt; (11) – build systems, CLI tools, documentation, refactoring, testing&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Specialized Domains&lt;/strong&gt; (8) – blockchain, embedded systems, fintech, game development, IoT&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Business &amp;amp; Product&lt;/strong&gt; (9) – product management, technical writing, UX research, scrum&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Meta &amp;amp; Orchestration&lt;/strong&gt; (7) – multi-agent coordination, context management, workflow automation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Research &amp;amp; Analysis&lt;/strong&gt; (7) – competitive analysis, market research, trend forecasting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent collection is adapted from &lt;a href=&quot;https://github.com/VoltAgent/awesome-claude-code-subagents&quot;&gt;VoltAgent/awesome-claude-code-subagents&lt;/a&gt;, reworked for OpenCode’s markdown agent format with permissions, skill integration, and tier assignments.&lt;/p&gt;

&lt;h2 id=&quot;technical-details&quot;&gt;Technical details&lt;/h2&gt;

&lt;p&gt;The CLI is pure ESM Node.js – no TypeScript compilation, no build step. The core logic lives in a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.js&lt;/code&gt; file of about 1,800 lines. Agent and skill templates are plain markdown files stored in the repo’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/&lt;/code&gt; directory, not embedded strings, which means they’re easy to review, edit, and contribute to.&lt;/p&gt;

&lt;p&gt;Dependencies are minimal: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@inquirer/prompts&lt;/code&gt; for interactive selection, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chalk&lt;/code&gt; for terminal colors, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ora&lt;/code&gt; for spinners. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prepublishOnly&lt;/code&gt; script syncs templates from the repo root into the npm package before publishing. CI validates that the agent count stays at 108+, the skill count at 15+, templates are in sync, and syntax is correct.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx @weisser-dev/awesome-opencode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. If you want to install globally instead:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; @weisser-dev/awesome-opencode
awesome-opencode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For manual setup without the CLI – copying individual agent and skill templates – see the &lt;a href=&quot;https://github.com/weisser-dev/awesome-opencode#manual-setup-without-cli&quot;&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;contributing&quot;&gt;Contributing&lt;/h2&gt;

&lt;p&gt;The project is MIT-licensed and welcomes contributions. The most useful additions right now are new agent templates (add a markdown file to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/agents/&lt;/code&gt; and register it in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AVAILABLE_AGENTS&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.js&lt;/code&gt;), new skill definitions (add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKILL.md&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/skills/&amp;lt;name&amp;gt;/&lt;/code&gt; and register in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AVAILABLE_SKILLS&lt;/code&gt;), model fingerprints for new providers, and MCP server recommendations.&lt;/p&gt;

&lt;h2 id=&quot;credits&quot;&gt;Credits&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/VoltAgent/awesome-claude-code-subagents&quot;&gt;VoltAgent/awesome-claude-code-subagents&lt;/a&gt; – the 127+ agent collection that served as the primary inspiration, adapted for OpenCode’s format&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/darrenhinde/OpenAgentsControl&quot;&gt;darrenhinde/OpenAgentsControl&lt;/a&gt; – agent configuration patterns&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://opencode.ai/docs/&quot;&gt;OpenCode Docs&lt;/a&gt; – agents, skills, MCP, permissions, and configuration reference&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pricepertoken.com/leaderboards/coding&quot;&gt;pricepertoken.com&lt;/a&gt; – model benchmark rankings and pricing data&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://registry.modelcontextprotocol.io&quot;&gt;registry.modelcontextprotocol.io&lt;/a&gt; – official MCP server registry API&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://mcp.so&quot;&gt;mcp.so&lt;/a&gt; – community MCP server directory&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">opencode-remote-telegram: Control OpenCode from Your Phone via Telegram</title>
    <link href="https://blog.weisser.dev/blog/2026/03/25/opencode-remote-telegram/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-25T00:00:00+00:00</published>
    <updated>2026-03-25T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/25/opencode-remote-telegram/</id>
    <category term="projects" />
    <category term="ai" />
    <summary type="html">opencode-remote-telegram is a Telegram bot that lets you control OpenCode from your phone. Pick a project, pick a model, type what you want — the agent works, you get the formatted result.
</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/25/opencode-remote-telegram/">&lt;p&gt;&lt;a href=&quot;https://github.com/weisser-dev/opencode-remote-telegram&quot;&gt;opencode-remote-telegram&lt;/a&gt; is a Telegram bot that lets you control &lt;a href=&quot;https://opencode.ai&quot;&gt;OpenCode&lt;/a&gt; from your phone. Pick a project, pick a model, type what you want — the agent works, you get the formatted result.&lt;/p&gt;

&lt;h2 id=&quot;the-origin-story&quot;&gt;The origin story&lt;/h2&gt;

&lt;p&gt;The project was inspired by &lt;a href=&quot;https://github.com/RoundTable02/remote-opencode&quot;&gt;RoundTable02/remote-opencode&lt;/a&gt;, a Discord bot for remote OpenCode access. The goal: take the concept, build a clean Telegram-native version with proper UX. It turned into a full rewrite — per-instance auth, SSE event handling, inline keyboards, persistent state, and a system prompt that teaches the model to format its output for Telegram.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Your Phone (Telegram)
        │
        ▼
opencode-remote-telegram
        │  spawns per project, on demand
        │  per-instance random Basic Auth
        │  idle timeout: 10 min
        ▼
opencode serve  (HTTP API + SSE)
        │
        ▼
Your Codebase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bot spawns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opencode serve&lt;/code&gt; in your project directory when you start a coding session. Each instance gets a randomly generated password. Prompts are sent with a system prefix that instructs the model to format output for Telegram (no markdown tables, short paragraphs, code in fenced blocks). Responses are collected silently while witty loading messages rotate, then delivered as properly formatted HTML.&lt;/p&gt;

&lt;h2 id=&quot;install&quot;&gt;Install&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; @weisser-dev/opencode-remote-telegram
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;opencode-remote-telegram setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The wizard walks you through four steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Telegram Bot Token&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Open Telegram → search for &lt;strong&gt;@BotFather&lt;/strong&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/newbot&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Copy the token (looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1234567890:ABCdef…&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Access control&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Message &lt;strong&gt;@userinfobot&lt;/strong&gt; to find your Telegram user ID&lt;/li&gt;
  &lt;li&gt;Enter your ID to restrict access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Projects base directory&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Point to your projects folder — subdirectories are auto-discovered&lt;/li&gt;
  &lt;li&gt;Example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Projects&lt;/code&gt; → discovers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Projects/my-app&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Projects/api&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. OpenCode config&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Recommended: use a single global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opencode.json&lt;/code&gt; for all projects&lt;/li&gt;
  &lt;li&gt;Place it at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/opencode-remote-telegram/opencode.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;This prevents project-level configs from overriding credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;verify-the-setup&quot;&gt;Verify the setup&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;opencode-remote-telegram &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This checks: config loaded, projects found, models loaded, server starts, test prompt gets a response.&lt;/p&gt;

&lt;h2 id=&quot;start-the-bot&quot;&gt;Start the bot&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;opencode-remote-telegram start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/start&lt;/code&gt; in Telegram, the bot shows an onboarding with inline buttons: pick a project, pick a model, start coding.&lt;/p&gt;

&lt;h2 id=&quot;commands&quot;&gt;Commands&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Projects&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/list_projects&lt;/code&gt; — inline keyboard, tap to switch&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/new_project&lt;/code&gt; — clone a GitHub repo interactively&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/switch_project &amp;lt;alias&amp;gt;&lt;/code&gt; — switch by name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/list_models&lt;/code&gt; — inline keyboard, tap to switch&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/switch_model &amp;lt;name&amp;gt;&lt;/code&gt; — switch by name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coding&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/vibe_coding&lt;/code&gt; — start passthrough session&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/stop_coding&lt;/code&gt; — stop session&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/interrupt&lt;/code&gt; — interrupt running task&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/diff&lt;/code&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff --stat HEAD&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/undo&lt;/code&gt; — revert last commit (soft reset, with confirmation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stats &amp;amp; History&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/show_stats&lt;/code&gt; — token count, cost, duration after each response&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/hide_stats&lt;/code&gt; — hide stats&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/history&lt;/code&gt; — last 10 prompts with model and cost&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/costs&lt;/code&gt; — daily and weekly cost summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Queue&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/queue_list&lt;/code&gt; — show queued prompts&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/queue_clear&lt;/code&gt; — clear queue&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/queue_settings&lt;/code&gt; — toggle continue-on-failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/status&lt;/code&gt; — project, model, queue, settings&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/clear&lt;/code&gt; — reset everything&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/help&lt;/code&gt; — all commands&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-user-experience&quot;&gt;The user experience&lt;/h2&gt;

&lt;p&gt;When you send a message while vibe coding is inactive (e.g. after a server restart), the bot offers three options:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Start &amp;amp; send this message&lt;/strong&gt; — activates vibe coding and immediately sends your text as the first prompt&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Just start coding&lt;/strong&gt; — activates vibe coding, waits for the next message&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No&lt;/strong&gt; — shows project/model/help buttons instead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the agent is working (reading files, running tools), witty loading messages rotate every 4 seconds. The final response is delivered as a single formatted message — no partial streaming output, no thinking text.&lt;/p&gt;

&lt;h2 id=&quot;configuration-files&quot;&gt;Configuration files&lt;/h2&gt;

&lt;p&gt;All config lives in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/opencode-remote-telegram/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.json&lt;/code&gt; — bot token, allowed users, project path&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opencode.json&lt;/code&gt; — global OpenCode config (providers, models)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state.json&lt;/code&gt; — persisted state (project/model per chat, settings, history)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logs/&lt;/code&gt; — daily log files&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;debug-mode&quot;&gt;Debug mode&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;opencode-remote-telegram &lt;span class=&quot;nt&quot;&gt;--debug&lt;/span&gt; start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Logs every incoming message, command, callback and outgoing reply with timestamps.&lt;/p&gt;

&lt;h2 id=&quot;what-this-is-a-demo-of&quot;&gt;What this is a demo of&lt;/h2&gt;

&lt;p&gt;The entire project was built with OpenCode — including the architecture decisions, the SSE event mapping, and the Telegram formatting layer. It’s a live example of AI-assisted development: describe what you want, iterate on the output, ship it.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Inspired by &lt;a href=&quot;https://github.com/RoundTable02/remote-opencode&quot;&gt;RoundTable02/remote-opencode&lt;/a&gt; — if you want OpenCode via Discord instead of Telegram, check it out.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Accidentally Committed Credentials to a Public Repo?</title>
    <link href="https://blog.weisser.dev/blog/2026/03/24/leaked-credentials/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-24T00:00:00+00:00</published>
    <updated>2026-03-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/24/leaked-credentials/</id>
    <category term="operations" />
    <category term="security" />
    <summary type="html">Step-by-step remediation for leaked API keys, tokens, and passwords in public Git repositories. What to do in the first 60 seconds, how to clean the history, and how to prevent it from happening again.</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/24/leaked-credentials/">&lt;p&gt;It happens. You paste terminal output into a blog post, commit a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file, or an AI agent includes a real credential in generated content. The secret is now in a public repository. Here is what to do.&lt;/p&gt;

&lt;h2 id=&quot;step-1-revoke-the-credential-immediately--60-seconds&quot;&gt;Step 1: Revoke the credential immediately (&amp;lt; 60 seconds)&lt;/h2&gt;

&lt;p&gt;This is the only step that actually stops the damage. Do it before anything else.&lt;/p&gt;

&lt;p&gt;The credential is already compromised the moment it hits a public repo. Automated scanners like &lt;a href=&quot;https://www.gitguardian.com/&quot;&gt;GitGuardian&lt;/a&gt;, &lt;a href=&quot;https://github.com/trufflesecurity/trufflehog&quot;&gt;truffleHog&lt;/a&gt;, and GitHub’s own secret scanning pick up new commits within seconds. Assume it has already been seen.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Credential type&lt;/th&gt;
      &lt;th&gt;How to revoke&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Telegram Bot Token&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@BotFather&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mybots&lt;/code&gt; → select bot → &lt;strong&gt;API Token&lt;/strong&gt; → &lt;strong&gt;Revoke&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub Personal Access Token&lt;/td&gt;
      &lt;td&gt;Settings → Developer settings → Personal access tokens → Delete&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenAI API Key&lt;/td&gt;
      &lt;td&gt;platform.openai.com → API keys → Delete&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AWS Access Key&lt;/td&gt;
      &lt;td&gt;IAM → Users → Security credentials → Deactivate + Delete&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Discord Bot Token&lt;/td&gt;
      &lt;td&gt;Developer Portal → Application → Bot → &lt;strong&gt;Reset Token&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Generic API key&lt;/td&gt;
      &lt;td&gt;Check the provider’s dashboard — every service has a revoke/rotate option&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;After revoking, generate a new credential and store it properly (see &lt;a href=&quot;#step-4-store-secrets-properly-going-forward&quot;&gt;Step 4&lt;/a&gt;).&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-2-remove-it-from-the-current-content&quot;&gt;Step 2: Remove it from the current content&lt;/h2&gt;

&lt;p&gt;Fix the file that contains the secret right now. Replace the real value with a placeholder:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Before&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OPENCODE_SERVER_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fd691649-311e-4418-98fa-6a0e21a2659b

&lt;span class=&quot;c&quot;&gt;# After&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;OPENCODE_SERVER_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;generated-uuid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Commit and push the fix:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;fix: replace leaked credential with placeholder&quot;&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; remove it from history — but it removes it from the current state and shows intent.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-3-wipe-the-git-history&quot;&gt;Step 3: Wipe the Git history&lt;/h2&gt;

&lt;p&gt;Deleting a commit does not delete history. Anyone who cloned or forked the repo before the fix still has the secret. GitHub also caches commit content internally.&lt;/p&gt;

&lt;p&gt;The cleanest option for a personal repo is an orphan reset — this replaces the entire history with a single new initial commit containing only the current (clean) state:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1. Create a new orphan branch (no history)&lt;/span&gt;
git checkout &lt;span class=&quot;nt&quot;&gt;--orphan&lt;/span&gt; clean-start

&lt;span class=&quot;c&quot;&gt;# 2. Stage everything in its current clean state&lt;/span&gt;
git add &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 3. Create a single new initial commit&lt;/span&gt;
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initial commit&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 4. Delete the old main branch&lt;/span&gt;
git branch &lt;span class=&quot;nt&quot;&gt;-D&lt;/span&gt; main

&lt;span class=&quot;c&quot;&gt;# 5. Rename the orphan to main&lt;/span&gt;
git branch &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; main

&lt;span class=&quot;c&quot;&gt;# 6. Force push to overwrite the remote history&lt;/span&gt;
git push &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After this, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; shows exactly one commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For repositories with collaborators or forks&lt;/strong&gt;, the orphan approach is still the fastest path. Inform anyone who has a local clone — they will need to re-clone:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/you/your-repo.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;They cannot &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt; after a force push that rewrites history — a fresh clone is required.&lt;/p&gt;

&lt;h3 id=&quot;alternative-bfg-repo-cleaner&quot;&gt;Alternative: BFG Repo Cleaner&lt;/h3&gt;

&lt;p&gt;If you want to keep the full history but surgically remove only the secret, &lt;a href=&quot;https://rtyley.github.io/bfg-repo-cleaner/&quot;&gt;BFG Repo Cleaner&lt;/a&gt; is faster than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git filter-branch&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install (macOS)&lt;/span&gt;
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bfg

&lt;span class=&quot;c&quot;&gt;# Create a file listing the secret strings to remove&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;fd691649-311e-4418-98fa-6a0e21a2659b&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; secrets.txt

&lt;span class=&quot;c&quot;&gt;# Run BFG against a bare clone&lt;/span&gt;
git clone &lt;span class=&quot;nt&quot;&gt;--mirror&lt;/span&gt; https://github.com/you/your-repo.git repo-mirror.git
bfg &lt;span class=&quot;nt&quot;&gt;--replace-text&lt;/span&gt; secrets.txt repo-mirror.git

&lt;span class=&quot;c&quot;&gt;# Clean up and force push&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;repo-mirror.git
git reflog expire &lt;span class=&quot;nt&quot;&gt;--expire&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;now &lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt;
git gc &lt;span class=&quot;nt&quot;&gt;--prune&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;now &lt;span class=&quot;nt&quot;&gt;--aggressive&lt;/span&gt;
git push &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;BFG rewrites every commit that contains the string, replacing it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;***REMOVED***&lt;/code&gt;. History is preserved but the secret is gone.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-4-store-secrets-properly-going-forward&quot;&gt;Step 4: Store secrets properly going forward&lt;/h2&gt;

&lt;p&gt;The root cause is almost always that a secret ended up somewhere it should not be — a blog post, a committed config file, a debug log. Here is the prevention stack:&lt;/p&gt;

&lt;h3 id=&quot;never-commit-secrets-to-git&quot;&gt;Never commit secrets to Git&lt;/h3&gt;

&lt;p&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; from the start:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# .gitignore
.env
.env.local
*.config.json
*secret*
*credentials*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;use-a-local-config-file-pattern&quot;&gt;Use a local config file pattern&lt;/h3&gt;

&lt;p&gt;For project configs that contain tokens, keep the real file gitignored and commit only a template:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;remote-opencode.config.json       # gitignored, contains real token
remote-opencode.config.example.json  # committed, contains placeholders
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;use-environment-variables&quot;&gt;Use environment variables&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;your-token&quot;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# or in .env (gitignored):&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;your-token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;use-a-secret-manager-for-teams&quot;&gt;Use a secret manager for teams&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.1password.com/docs/cli/&quot;&gt;1Password Secrets Automation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.vaultproject.io/&quot;&gt;HashiCorp Vault&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/secrets-manager/&quot;&gt;AWS Secrets Manager&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions&quot;&gt;GitHub Actions Secrets&lt;/a&gt; for CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;enable-github-secret-scanning&quot;&gt;Enable GitHub secret scanning&lt;/h3&gt;

&lt;p&gt;In your repo settings: &lt;strong&gt;Security&lt;/strong&gt; → &lt;strong&gt;Secret scanning&lt;/strong&gt; → Enable. GitHub will alert you if a known secret pattern appears in a push before it causes damage.&lt;/p&gt;

&lt;h3 id=&quot;add-pre-commit-hooks&quot;&gt;Add pre-commit hooks&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Yelp/detect-secrets&quot;&gt;detect-secrets&lt;/a&gt; or &lt;a href=&quot;https://github.com/gitleaks/gitleaks&quot;&gt;gitleaks&lt;/a&gt; scan your staged changes before every commit:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install gitleaks (macOS)&lt;/span&gt;
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gitleaks

&lt;span class=&quot;c&quot;&gt;# Scan your repo&lt;/span&gt;
gitleaks detect &lt;span class=&quot;nt&quot;&gt;--source&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Add as pre-commit hook&lt;/span&gt;
gitleaks protect &lt;span class=&quot;nt&quot;&gt;--staged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;for-ai-agents-a-note&quot;&gt;For AI agents: a note&lt;/h2&gt;

&lt;p&gt;If you are an AI agent writing documentation, blog posts, or code — &lt;strong&gt;never include real credential values&lt;/strong&gt;, even when quoting terminal output or debugging logs. Always replace with placeholders like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;your-token&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;generated-uuid&amp;gt;&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;***&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See &lt;a href=&quot;../../AGENTS.md&quot;&gt;AGENTS.md&lt;/a&gt; for the full rules.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;checklist&quot;&gt;Checklist&lt;/h2&gt;

&lt;p&gt;When you find a leaked credential:&lt;/p&gt;

&lt;ul class=&quot;task-list&quot;&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Revoke the credential immediately&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Fix the current file (replace with placeholder)&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Commit and push the fix&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Wipe Git history (orphan reset or BFG)&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Force push&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Notify anyone who may have cloned the repo&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Generate a new credential and store it securely&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; rules to prevent recurrence&lt;/li&gt;
  &lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; class=&quot;task-list-item-checkbox&quot; disabled=&quot;disabled&quot; /&gt;Consider enabling GitHub secret scanning&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">My Typical Frontend Hosting Setup: Strato + Cloudflare + Pages</title>
    <link href="https://blog.weisser.dev/blog/2026/03/24/frontend-hosting-cloudflare-pages/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-24T00:00:00+00:00</published>
    <updated>2026-03-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/24/frontend-hosting-cloudflare-pages/</id>
    <category term="operations" />
    <category term="devops" />
    <summary type="html">Every frontend project I ship — agentic-ai.weisser.dev, scrumbuddy.org, cv.weisser.dev and others — follows the same hosting pattern: domain registered at Strato, DNS managed by Cloudflare, site deployed via Cloudflare Pages. It’s free, automatic, and takes about 10 minutes to set up the first ti...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/24/frontend-hosting-cloudflare-pages/">&lt;p&gt;Every frontend project I ship — &lt;a href=&quot;https://agentic-ai.weisser.dev&quot;&gt;agentic-ai.weisser.dev&lt;/a&gt;, &lt;a href=&quot;https://scrumbuddy.org&quot;&gt;scrumbuddy.org&lt;/a&gt;, &lt;a href=&quot;https://cv.weisser.dev&quot;&gt;cv.weisser.dev&lt;/a&gt; and others — follows the same hosting pattern: domain registered at Strato, DNS managed by Cloudflare, site deployed via Cloudflare Pages. It’s free, automatic, and takes about 10 minutes to set up the first time.&lt;/p&gt;

&lt;h2 id=&quot;the-stack&quot;&gt;The stack&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Strato&lt;/strong&gt; — domain registrar. That’s all it does in this setup.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; — DNS, SSL, CDN, and the actual hosting via Cloudflare Pages.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt; — source of truth. Every push triggers a build and deploy automatically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Vite&lt;/strong&gt; (or any static site generator) — the build tool. Output goes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;step-1-point-your-strato-domain-to-cloudflare&quot;&gt;Step 1: Point your Strato domain to Cloudflare&lt;/h2&gt;

&lt;p&gt;Log in to Strato, go to your domain settings and change the nameservers to the two Cloudflare nameservers assigned to your account. They look like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.ns.cloudflare.com&lt;/code&gt; — you’ll find the exact values in your Cloudflare dashboard under &lt;strong&gt;DNS &amp;gt; Nameservers&lt;/strong&gt; after adding the domain there.&lt;/p&gt;

&lt;p&gt;In Cloudflare: &lt;strong&gt;Add a site&lt;/strong&gt; → enter your domain → choose the free plan → Cloudflare scans your existing DNS records → you confirm and note down the two nameserver addresses → go back to Strato and enter them.&lt;/p&gt;

&lt;p&gt;DNS propagation takes a few minutes to a few hours. Once it’s done, Cloudflare controls everything.&lt;/p&gt;

&lt;h2 id=&quot;step-2-create-a-cloudflare-pages-project&quot;&gt;Step 2: Create a Cloudflare Pages project&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; If you navigate to &lt;strong&gt;Workers &amp;amp; Pages&lt;/strong&gt; in the Cloudflare dashboard and click &lt;strong&gt;Create&lt;/strong&gt;, you’ll land on the Workers tab by default. Don’t start creating a Worker — look for the small link that says something like &lt;strong&gt;“Looking for Pages? Create a Pages project here”&lt;/strong&gt; (or similar wording, Cloudflare changes this occasionally). Click that to get to the actual Pages setup. This trips up almost everyone the first time.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href=&quot;https://pages.cloudflare.com&quot;&gt;pages.cloudflare.com&lt;/a&gt; → &lt;strong&gt;Create a project&lt;/strong&gt; → &lt;strong&gt;Connect to Git&lt;/strong&gt; → authorize Cloudflare to access your GitHub account → select the repository.&lt;/p&gt;

&lt;p&gt;Configure the build settings:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Setting&lt;/th&gt;
      &lt;th&gt;Value&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Branch&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Build command&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run build&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Build output directory&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;For plain HTML/CSS/JS with no build step, leave the build command empty and set the output directory to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save and Deploy&lt;/strong&gt;. Cloudflare clones the repo, runs the build, and publishes to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.pages.dev&lt;/code&gt; URL in under a minute.&lt;/p&gt;

&lt;h2 id=&quot;step-3-add-your-custom-domain&quot;&gt;Step 3: Add your custom domain&lt;/h2&gt;

&lt;p&gt;In your Pages project, go to &lt;strong&gt;Custom domains&lt;/strong&gt; → &lt;strong&gt;Set up a custom domain&lt;/strong&gt; → enter your domain (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agentic-ai.weisser.dev&lt;/code&gt;). Since your domain is already on Cloudflare, it creates the DNS record automatically and provisions SSL via Let’s Encrypt within minutes.&lt;/p&gt;

&lt;p&gt;That’s it. The next &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; will automatically trigger a new build and deploy. No CI pipeline to maintain, no server to manage, no bill at the end of the month.&lt;/p&gt;

&lt;h2 id=&quot;why-this-works-well&quot;&gt;Why this works well&lt;/h2&gt;

&lt;p&gt;The thing I like most about this setup is how little there is to think about once it’s running. Cloudflare handles SSL renewal, CDN caching, DDoS protection, and deployment all in one place. The free tier is genuinely generous for personal projects and small tools — unlimited bandwidth, 500 builds per month, custom domains included.&lt;/p&gt;

&lt;p&gt;The only thing Strato does in this setup is hold the domain registration. If you’re registering a new domain, you could also register it directly in Cloudflare and skip the nameserver step entirely — but for domains you already have elsewhere, the nameserver delegation works just as well.&lt;/p&gt;

&lt;h2 id=&quot;why-cloudflare-pages-over-github-pages&quot;&gt;Why Cloudflare Pages over GitHub Pages&lt;/h2&gt;

&lt;p&gt;Both are free for static sites, but Cloudflare Pages has two advantages that matter in practice:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Private repos work for free.&lt;/strong&gt; GitHub Pages requires a public repository on the free plan — if you want to deploy from a private repo, you need GitHub Pro or a GitHub Team/Enterprise plan. Cloudflare Pages deploys from private repos at no cost.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Every commit gets its own preview URL.&lt;/strong&gt; Each push — not just to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;, but to any branch or pull request — gets a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;commit-hash&amp;gt;.pages.dev&lt;/code&gt; URL. This makes it trivial to review changes, share previews with others, or roll back to any previous deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For public open-source sites where the source doesn’t need to be private, GitHub Pages works fine. For everything else — side projects, client work, internal tools — Cloudflare Pages is the better choice.&lt;/p&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dilatchi/cloudflare-static-site-demo&quot;&gt;dilatchi/cloudflare-static-site-demo&lt;/a&gt; — a clean tutorial repo that walks through the same steps with screenshots&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pages.cloudflare.com&quot;&gt;pages.cloudflare.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Building an Interactive AI Agent Workshop with Vite and Vanilla JS</title>
    <link href="https://blog.weisser.dev/blog/2026/03/23/agentic-ai-workshop/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-23T00:00:00+00:00</published>
    <updated>2026-03-23T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/23/agentic-ai-workshop/</id>
    <category term="ai" />
    <category term="javascript" />
    <summary type="html">A few weeks ago I set out to build a complete, interactive workshop about AI Agents, LLMs, and AI-assisted coding — something that could be used both as a live presentation and as a self-guided learning resource. The result is agentic-ai.weisser.dev, a bilingual (German/English) web application b...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/23/agentic-ai-workshop/">&lt;p&gt;A few weeks ago I set out to build a complete, interactive workshop about AI Agents, LLMs, and AI-assisted coding — something that could be used both as a live presentation and as a self-guided learning resource. The result is &lt;a href=&quot;https://agentic-ai.weisser.dev&quot;&gt;agentic-ai.weisser.dev&lt;/a&gt;, a bilingual (German/English) web application built entirely with Vite and Vanilla JavaScript. The interesting twist: the entire project was developed using OpenCode, an AI coding agent — which made it a kind of meta-exercise in the topic itself.&lt;/p&gt;

&lt;p&gt;The application ships in two distinct modes. &lt;strong&gt;Presenter mode&lt;/strong&gt; is designed for live workshops: it includes a full slide deck with keyboard navigation, a confetti cannon for celebratory moments, and a session timer to keep things on schedule. Slides are authored in plain HTML/JS with a custom layout system — no external presentation framework. &lt;strong&gt;Self-paced mode&lt;/strong&gt; lets participants work through the material independently, complete with embedded quizzes, collapsible hints, and progress tracking stored in localStorage. Both modes share the same underlying content model, so updates to the material propagate everywhere automatically.&lt;/p&gt;

&lt;p&gt;The design system is entirely custom — a dark-first palette with carefully chosen contrast ratios, consistent spacing tokens, and a set of utility classes that kept the CSS manageable without reaching for Tailwind or a component framework. Bilingual support was implemented as a simple key-value JSON structure with a language toggle. No i18n library was pulled in; given the scope, a few dozen strings per language, the overhead wasn’t justified.&lt;/p&gt;

&lt;p&gt;What made this project technically interesting was the challenge of building a presentation tool without a framework. State management — tracking the current slide, quiz answers, timer state — had to be done intentionally rather than delegated to React or Vue conventions. It forced clarity about what actually needs to be stateful versus what can just be re-rendered from the DOM. The build pipeline with Vite made the development experience fast despite the lack of a framework, with instant HMR and a clean production bundle.&lt;/p&gt;

&lt;p&gt;The meta-story here is just as interesting as the technical one. The project was built almost entirely through conversations with OpenCode, an AI coding agent running in the terminal. Features were described in natural language, the agent wrote the code, and I reviewed and iterated. This workflow is exactly what the workshop teaches, so the process of building it was itself a live demonstration of the concepts. The final codebase is clean, well-structured, and doesn’t show obvious signs of “AI-written spaghetti” — which I consider a meaningful data point about the maturity of agent-assisted development in 2026.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">OpenPastebin: A Self-Hosted Pastebin with Automatic Expiration</title>
    <link href="https://blog.weisser.dev/blog/2026/03/22/openpastebin/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-03-22T00:00:00+00:00</published>
    <updated>2026-03-22T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/03/22/openpastebin/</id>
    <category term="javascript" />
    <category term="tools" />
    <summary type="html">Every developer has reached for a pastebin at some point — to share a log snippet, pass a config file to a colleague, or dump some output during a debugging session. The existing options are either commercial services with opaque data retention policies, ad-supported with tracking, or old open-so...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/03/22/openpastebin/">&lt;p&gt;Every developer has reached for a pastebin at some point — to share a log snippet, pass a config file to a colleague, or dump some output during a debugging session. The existing options are either commercial services with opaque data retention policies, ad-supported with tracking, or old open-source projects that haven’t been touched in years. OpenPastebin is my attempt to fix that: a clean, modern, self-hosted pastebin you can run yourself in minutes.&lt;/p&gt;

&lt;p&gt;The core feature set is deliberately minimal. Paste some text, set an expiration (minutes, hours, days, or never), get a short URL back. No accounts required. The paste expires and is deleted from storage automatically. That’s it. The API-first design means every action available in the UI is also available via a plain HTTP call, making it trivially easy to pipe data in from scripts or CI pipelines — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl -X POST https://your-instance/api/paste --data &quot;content=hello&quot;&lt;/code&gt; and you get a URL back.&lt;/p&gt;

&lt;p&gt;Under the hood, the stack is Node.js with a straightforward REST API and a minimal frontend that doesn’t require a build step. Storage is handled by a simple key-value approach with TTL support — Redis works well here, but there’s also a file-based fallback for setups that don’t want to run an additional process. The expiration mechanism relies on the storage layer’s native TTL rather than a background cleanup job, which keeps the application code simple and the operations footprint small.&lt;/p&gt;

&lt;p&gt;Self-hosting matters for a tool like this more than it might seem. Paste content is often sensitive: API keys accidentally left in a snippet, internal configuration, stack traces with file paths. When you self-host, you control exactly who can read that data and for how long. For teams running their own infrastructure, deploying OpenPastebin behind an internal reverse proxy takes ten minutes and means sensitive paste content never leaves your network. The Docker image is available, configuration is environment variables, and there’s no database migration step — a fresh instance is ready to accept pastes immediately.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Building Custom Sub-Agents for AI-Assisted Workflows</title>
    <link href="https://blog.weisser.dev/blog/2026/02/24/building-custom-sub-agents/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-24T00:00:00+00:00</published>
    <updated>2026-02-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/24/building-custom-sub-agents/</id>
    <category term="ai" />
    <summary type="html">As AI coding agents have become part of my daily workflow, I’ve started building specialized sub-agents — focused, single-purpose agents that can be invoked by a parent orchestrator to handle specific tasks. The own-subagents project is a collection of these: Shell-based sub-agents designed for s...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/24/building-custom-sub-agents/">&lt;p&gt;As AI coding agents have become part of my daily workflow, I’ve started building specialized sub-agents — focused, single-purpose agents that can be invoked by a parent orchestrator to handle specific tasks. The own-subagents project is a collection of these: Shell-based sub-agents designed for specific automation tasks that I run repeatedly in my development workflow.&lt;/p&gt;

&lt;p&gt;The design philosophy for sub-agents is narrow scope and explicit I/O. A sub-agent should do one thing well: analyze a codebase for a specific pattern, generate a changelog from git history, validate a configuration file, or transform data from one format to another. The input is well-defined (usually JSON or plain text via stdin), the output is well-defined (JSON or formatted text to stdout), and the sub-agent has no side effects beyond what’s explicitly in its spec. This makes them composable — the orchestrator can chain sub-agents, pass outputs between them, and handle errors at the orchestration level.&lt;/p&gt;

&lt;p&gt;Shell is a pragmatic choice for sub-agents that primarily orchestrate other command-line tools. A sub-agent that needs to run git commands, call the OpenCode CLI, manipulate files, and pass data between steps can do all of this in a shell script with no additional runtime dependencies. The Shell environment’s pipe-and-filter model aligns well with the sub-agent pattern: each tool in the pipeline is itself a form of sub-agent.&lt;/p&gt;

&lt;p&gt;The most valuable thing I’ve learned from building multi-agent pipelines is that reliability depends on the quality of the handoffs between agents. An orchestrator that gives a sub-agent ambiguous instructions or an imprecise schema will get unpredictable results regardless of how capable the sub-agent is. Investing in precise, typed interfaces between agents — with validation at each handoff point — is the engineering work that makes multi-agent systems actually reliable rather than just theoretically possible.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">OpenClaw Setup on VPS</title>
    <link href="https://blog.weisser.dev/blog/2026/02/24/openclaw-vps-setup/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-24T00:00:00+00:00</published>
    <updated>2026-02-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/24/openclaw-vps-setup/</id>
    <category term="openclaw" />
    <summary type="html">Complete guide to installing and configuring OpenClaw on a VPS – Node.js 22, daemon setup, Nginx reverse proxy and firewall configuration.</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/24/openclaw-vps-setup/">&lt;p&gt;This guide walks you through installing OpenClaw on a VPS (Virtual Private Server) like Hetzner, DigitalOcean, or similar providers.&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;VPS with Ubuntu 22.04+ or Debian 12+&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Root or sudo access&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Domain name&lt;/strong&gt; (optional, for remote access)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;At least 2GB RAM&lt;/strong&gt; (4GB recommended for model inference)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;step-1-install-nodejs-22&quot;&gt;Step 1: Install Node.js 22&lt;/h2&gt;

&lt;p&gt;OpenClaw requires Node.js 22 or newer.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Using nvm (recommended)&lt;/span&gt;
curl &lt;span class=&quot;nt&quot;&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/.bashrc
nvm &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;22
nvm use 22
nvm &lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;default 22

&lt;span class=&quot;c&quot;&gt;# Verify installation&lt;/span&gt;
node &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Should show v22.x.x&lt;/span&gt;
npm &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-2-install-openclaw&quot;&gt;Step 2: Install OpenClaw&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install OpenClaw globally&lt;/span&gt;
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; openclaw

&lt;span class=&quot;c&quot;&gt;# Verify installation&lt;/span&gt;
openclaw &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-3-run-the-onboarding-wizard&quot;&gt;Step 3: Run the Onboarding Wizard&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw onboard &lt;span class=&quot;nt&quot;&gt;--install-daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The wizard will guide you through:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Gateway configuration&lt;/strong&gt; (port, bind address, auth)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Model provider setup&lt;/strong&gt; (OpenAI, Qwen, Anthropic, etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Channel configuration&lt;/strong&gt; (Telegram, Discord, WhatsApp, etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Daemon installation&lt;/strong&gt; (systemd service)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;step-4-configure-gateway-for-remote-access&quot;&gt;Step 4: Configure Gateway for Remote Access&lt;/h2&gt;

&lt;p&gt;Edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.openclaw/openclaw.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;gateway&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;port&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;18789&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;bind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;auth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;your-secure-token-here&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tailscale&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;off&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-5-configure-firewall&quot;&gt;Step 5: Configure Firewall&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Allow OpenClaw gateway port&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw allow 18789/tcp
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw allow 22/tcp
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw &lt;span class=&quot;nb&quot;&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-6-install-and-configure-nginx-optional-for-https&quot;&gt;Step 6: Install and Configure Nginx (Optional, for HTTPS)&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; nginx certbot python3-certbot-nginx

&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/openclaw
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;your-domain.com&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://127.0.0.1:18789&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;upgrade&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Real-IP&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl restart nginx

&lt;span class=&quot;c&quot;&gt;# Get SSL certificate&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot &lt;span class=&quot;nt&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; your-domain.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-7-verify-gateway-status&quot;&gt;Step 7: Verify Gateway Status&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw gateway status
openclaw logs &lt;span class=&quot;nt&quot;&gt;--follow&lt;/span&gt;
curl http://127.0.0.1:18789/health
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-8-connect-channels&quot;&gt;Step 8: Connect Channels&lt;/h2&gt;

&lt;p&gt;After gateway is running, configure your channels:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Telegram Bot&lt;/span&gt;
openclaw configure &lt;span class=&quot;nt&quot;&gt;--section&lt;/span&gt; telegram

&lt;span class=&quot;c&quot;&gt;# Discord Bot&lt;/span&gt;
openclaw configure &lt;span class=&quot;nt&quot;&gt;--section&lt;/span&gt; discord
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;security-considerations&quot;&gt;Security Considerations&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Use strong auth tokens&lt;/strong&gt; – Generate with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl rand -hex 32&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Enable firewall&lt;/strong&gt; – Only allow necessary ports&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use HTTPS&lt;/strong&gt; – Always use reverse proxy with SSL for production&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Regular updates&lt;/strong&gt; – Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm update -g openclaw&lt;/code&gt; periodically&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Monitor logs&lt;/strong&gt; – Check &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openclaw logs&lt;/code&gt; for suspicious activity&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Gateway won&apos;t start – check if port is in use&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;lsof &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; :18789

&lt;span class=&quot;c&quot;&gt;# Check logs&lt;/span&gt;
openclaw logs &lt;span class=&quot;nt&quot;&gt;--tail&lt;/span&gt; 100

&lt;span class=&quot;c&quot;&gt;# Restart daemon&lt;/span&gt;
openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-qwen-setup/&quot;&gt;OpenClaw with Qwen (Free Models)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-subagents/&quot;&gt;OpenClaw Subagents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-fallback-models/&quot;&gt;OpenClaw Model Fallback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">OpenClaw Subagents Guide</title>
    <link href="https://blog.weisser.dev/blog/2026/02/24/openclaw-subagents/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-24T00:00:00+00:00</published>
    <updated>2026-02-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/24/openclaw-subagents/</id>
    <category term="openclaw" />
    <summary type="html">Complete guide to using OpenClaw subagents for specialized tasks and multi-agent orchestration – architecture, routing, lifecycle and best practices.</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/24/openclaw-subagents/">&lt;p&gt;Subagents allow you to spawn specialized AI agents for specific tasks, enabling better context management and task separation.&lt;/p&gt;

&lt;h2 id=&quot;what-are-subagents&quot;&gt;What are Subagents?&lt;/h2&gt;

&lt;p&gt;Subagents are isolated agent sessions with their own:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Workspace&lt;/strong&gt; – Separate file context&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt; – Can use different models than the main agent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Memory&lt;/strong&gt; – Independent conversation history&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt; – Same tool access as main agent&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;when-to-use-subagents&quot;&gt;When to Use Subagents&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scenario&lt;/th&gt;
      &lt;th&gt;Use Subagent?&lt;/th&gt;
      &lt;th&gt;Why&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Quick question&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Main agent is faster&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Complex coding task (&amp;gt;20 min)&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Isolated context, parallel execution&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Multi-file refactoring&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Focused workspace&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Background monitoring&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Runs independently&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Specialized domain (e.g., food tracking)&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
      &lt;td&gt;Domain-specific knowledge&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;recommended-project-structure&quot;&gt;Recommended Project Structure&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;own-subagents/
├── global/                    # Reusable subagent definitions
│   ├── frontend-builder.md
│   ├── backend-builder.md
│   ├── test-writer.md
│   ├── code-reviewer.md
│   ├── security-reviewer.md
│   └── meal-screenshot-intake.md
├── runtime-workspaces/        # Active agent workspaces (auto-generated)
└── workspace/
    └── AGENTS.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;creating-subagents&quot;&gt;Creating Subagents&lt;/h2&gt;

&lt;h3 id=&quot;via-cli&quot;&gt;Via CLI&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw agents add backend-builder &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--workspace&lt;/span&gt; /root/.openclaw/workspace/own-subagents/runtime-workspaces/backend-builder
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;example-subagent-definition&quot;&gt;Example Subagent Definition&lt;/h3&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# backend-builder&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Purpose&lt;/span&gt;
Implements API, data model, and business logic changes.

&lt;span class=&quot;gu&quot;&gt;## Inputs&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Endpoint/feature description
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Data model constraints
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Security requirements

&lt;span class=&quot;gu&quot;&gt;## Outputs&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Implementation + validation
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Tests/smoke checks
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Migration/compatibility notes

&lt;span class=&quot;gu&quot;&gt;## Boundaries&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; No secret exposure
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; No destructive DB operations without approval

&lt;span class=&quot;gu&quot;&gt;## Definition of Done&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Relevant tests green
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Input validation present
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; Error cases handled sensibly
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;routing-strategy&quot;&gt;Routing Strategy&lt;/h2&gt;

&lt;h3 id=&quot;task-classification-matrix&quot;&gt;Task Classification Matrix&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Task&lt;/th&gt;
      &lt;th&gt;Agent&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Fix login bug in health-tracker&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;backend-builder&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Write E2E tests&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test-writer&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Food screenshot intake&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meal-screenshot-intake&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Security review&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-reviewer&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;using-subagents-in-practice&quot;&gt;Using Subagents in Practice&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Spawn a backend subagent&lt;/span&gt;
openclaw sessions spawn &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--agent-id&lt;/span&gt; backend-builder &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--task&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Implement user registration endpoint with email verification&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--mode&lt;/span&gt; session &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;user-auth-feature&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# One-shot task (auto-terminates after completion)&lt;/span&gt;
openclaw sessions spawn &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--agent-id&lt;/span&gt; code-reviewer &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--task&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Review PR #42 for security issues&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--mode&lt;/span&gt; run &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pr-42-review&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;managing-subagents&quot;&gt;Managing Subagents&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# List active subagents&lt;/span&gt;
openclaw sessions list &lt;span class=&quot;nt&quot;&gt;--kinds&lt;/span&gt; subagent

&lt;span class=&quot;c&quot;&gt;# Send message to running subagent&lt;/span&gt;
openclaw sessions send &lt;span class=&quot;nt&quot;&gt;--session-key&lt;/span&gt; &amp;lt;key&amp;gt; &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Prioritize login endpoint&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Kill subagent&lt;/span&gt;
openclaw subagents &lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt; backend-builder

&lt;span class=&quot;c&quot;&gt;# View logs&lt;/span&gt;
openclaw logs &lt;span class=&quot;nt&quot;&gt;--session&lt;/span&gt; &amp;lt;session-key&amp;gt; &lt;span class=&quot;nt&quot;&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;subagent-lifecycle&quot;&gt;Subagent Lifecycle&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Main Agent
    │ spawn
    ▼
Subagent (isolated)
    │ complete
    ▼
Result pushed to Main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;advanced-orchestrator-pattern&quot;&gt;Advanced: Orchestrator Pattern&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;User: &quot;Build a complete user auth system&quot;

Main Agent:
  1. spawn product-owner      → Define requirements
  2. spawn software-architect → Design architecture
  3. spawn backend-builder    → Implement API
  4. spawn test-writer        → Write tests
  5. spawn security-reviewer  → Security audit
  6. Synthesize → Present to user
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;model-assignment-per-subagent&quot;&gt;Model Assignment per Subagent&lt;/h2&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;list&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;code-reviewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-5.3-codex&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;meal-screenshot-intake&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/vision-model&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;best-practices&quot;&gt;Best Practices&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Clear task definitions&lt;/strong&gt; – Be explicit about inputs, outputs and scope&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Set explicit timeouts&lt;/strong&gt; – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--timeout-seconds 3600&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use labels for tracking&lt;/strong&gt; – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--label &quot;feature-user-auth&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Clean up after completion&lt;/strong&gt; – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cleanup delete&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Match models to tasks&lt;/strong&gt; – Vision tasks need vision models, code reviews benefit from high-quality models&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Subagent won&apos;t start – check if it exists&lt;/span&gt;
openclaw agents list

&lt;span class=&quot;c&quot;&gt;# Stuck subagent – kill and retry&lt;/span&gt;
openclaw subagents &lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt; &amp;lt;agent-id&amp;gt;

&lt;span class=&quot;c&quot;&gt;# Results not returned – verify mode=run for one-shot tasks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-vps-setup/&quot;&gt;OpenClaw VPS Setup&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-fallback-models/&quot;&gt;OpenClaw Model Fallback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">OpenClaw with Qwen (Free Models)</title>
    <link href="https://blog.weisser.dev/blog/2026/02/24/openclaw-qwen-setup/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-24T00:00:00+00:00</published>
    <updated>2026-02-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/24/openclaw-qwen-setup/</id>
    <category term="openclaw" />
    <summary type="html">How to use OpenClaw with Qwen Portal&apos;s free AI models using the qwen-portal-auth skill – 128k context, vision support, zero cost.</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/24/openclaw-qwen-setup/">&lt;p&gt;This guide shows you how to configure OpenClaw to use &lt;strong&gt;Qwen Portal’s free AI models&lt;/strong&gt; – a cost-effective alternative to paid providers like OpenAI or Anthropic.&lt;/p&gt;

&lt;h2 id=&quot;why-qwen&quot;&gt;Why Qwen?&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Free to use&lt;/strong&gt; – No credit card required&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Good code capabilities&lt;/strong&gt; – Qwen Coder model optimized for programming&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Large context window&lt;/strong&gt; – 128k tokens&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Vision support&lt;/strong&gt; – Qwen Vision model for image analysis&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;OAuth authentication&lt;/strong&gt; – Simple login flow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;OpenClaw installed (see &lt;a href=&quot;/blog/2026/02/24/openclaw-vps-setup/&quot;&gt;VPS Setup&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Qwen Portal account (free at &lt;a href=&quot;https://portal.qwen.ai&quot;&gt;portal.qwen.ai&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;step-1-install-qwen-portal-auth-skill&quot;&gt;Step 1: Install Qwen Portal Auth Skill&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; openclaw  &lt;span class=&quot;c&quot;&gt;# already installed&lt;/span&gt;
openclaw plugins &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; @openclaw/qwen-portal-auth
openclaw plugins list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-2-authenticate-with-qwen-portal&quot;&gt;Step 2: Authenticate with Qwen Portal&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openclaw configure &lt;span class=&quot;nt&quot;&gt;--section&lt;/span&gt; qwen-portal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This opens a browser OAuth flow and stores the token in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.openclaw/agents/&amp;lt;agent&amp;gt;/auth-profiles.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-3-configure-qwen-models&quot;&gt;Step 3: Configure Qwen Models&lt;/h2&gt;

&lt;p&gt;Edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.openclaw/openclaw.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;models&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;providers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;qwen-portal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;baseUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://portal.qwen.ai/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;apiKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-oauth&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-completions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;models&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;coder-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qwen Coder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;reasoning&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cost&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cacheRead&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cacheWrite&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;contextWindow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;maxTokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vision-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qwen Vision&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;reasoning&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;image&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cost&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cacheRead&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cacheWrite&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;contextWindow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;maxTokens&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;defaults&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-4-test-the-configuration&quot;&gt;Step 4: Test the Configuration&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Test text model&lt;/span&gt;
openclaw agent &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; qwen &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Write a Python function to calculate fibonacci&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Test vision model&lt;/span&gt;
openclaw agent &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; qwen-vision &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;What&apos;s in this image?&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--image&lt;/span&gt; ./screenshot.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;model-comparison&quot;&gt;Model Comparison&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Model&lt;/th&gt;
      &lt;th&gt;Context&lt;/th&gt;
      &lt;th&gt;Max Tokens&lt;/th&gt;
      &lt;th&gt;Input Types&lt;/th&gt;
      &lt;th&gt;Best For&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coder-model&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;128k&lt;/td&gt;
      &lt;td&gt;8k&lt;/td&gt;
      &lt;td&gt;Text&lt;/td&gt;
      &lt;td&gt;Code, general tasks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vision-model&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;128k&lt;/td&gt;
      &lt;td&gt;8k&lt;/td&gt;
      &lt;td&gt;Text + Image&lt;/td&gt;
      &lt;td&gt;Image analysis, OCR&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;cost-comparison&quot;&gt;Cost Comparison&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Provider&lt;/th&gt;
      &lt;th&gt;Model&lt;/th&gt;
      &lt;th&gt;Cost per 1M tokens&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Qwen Portal&lt;/td&gt;
      &lt;td&gt;Coder&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Qwen Portal&lt;/td&gt;
      &lt;td&gt;Vision&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenAI&lt;/td&gt;
      &lt;td&gt;GPT-4.1&lt;/td&gt;
      &lt;td&gt;~$10–20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Anthropic&lt;/td&gt;
      &lt;td&gt;Claude Sonnet&lt;/td&gt;
      &lt;td&gt;~$3–15&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;using-qwen-with-subagents&quot;&gt;Using Qwen with Subagents&lt;/h2&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;list&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;code-reviewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# OAuth token expired – re-authenticate&lt;/span&gt;
openclaw configure &lt;span class=&quot;nt&quot;&gt;--section&lt;/span&gt; qwen-portal

&lt;span class=&quot;c&quot;&gt;# Model not found – verify config&lt;/span&gt;
openclaw models list
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.openclaw/openclaw.json | jq &lt;span class=&quot;s1&quot;&gt;&apos;.models.providers.qwen-portal&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-subagents/&quot;&gt;OpenClaw Subagents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-fallback-models/&quot;&gt;OpenClaw Model Fallback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">OpenClaw Model Fallback Configuration</title>
    <link href="https://blog.weisser.dev/blog/2026/02/24/openclaw-fallback-models/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-24T00:00:00+00:00</published>
    <updated>2026-02-24T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/24/openclaw-fallback-models/</id>
    <category term="openclaw" />
    <summary type="html">How to configure automatic model fallback in OpenClaw – auth profile rotation, cooldown progression, multi-provider chains and cost optimization strategies.</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/24/openclaw-fallback-models/">&lt;p&gt;OpenClaw supports &lt;strong&gt;automatic model fallback&lt;/strong&gt; when your primary model provider runs into issues (token exhaustion, rate limits, billing errors).&lt;/p&gt;

&lt;h2 id=&quot;how-fallback-works&quot;&gt;How Fallback Works&lt;/h2&gt;

&lt;p&gt;OpenClaw handles failures in two stages:&lt;/p&gt;

&lt;h3 id=&quot;stage-1-auth-profile-rotation&quot;&gt;Stage 1: Auth Profile Rotation&lt;/h3&gt;
&lt;p&gt;Within the same provider, OpenClaw tries alternative auth profiles. Cooldown is applied to failed profiles (1 min – 1 h).&lt;/p&gt;

&lt;h3 id=&quot;stage-2-model-fallback&quot;&gt;Stage 2: Model Fallback&lt;/h3&gt;
&lt;p&gt;When all profiles for a provider fail, OpenClaw switches to the next model in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agents.defaults.model.fallbacks&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;basic-fallback-setup&quot;&gt;Basic Fallback Setup&lt;/h2&gt;

&lt;p&gt;Edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.openclaw/openclaw.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;defaults&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-5.3-codex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;multiple-fallback-chain&quot;&gt;Multiple Fallback Chain&lt;/h3&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;defaults&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-5.3-codex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-4.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;anthropic/claude-sonnet&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;fallback-triggers&quot;&gt;Fallback Triggers&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Error Type&lt;/th&gt;
      &lt;th&gt;Behavior&lt;/th&gt;
      &lt;th&gt;Backoff&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Billing/Credit Error&lt;/td&gt;
      &lt;td&gt;Profile disabled&lt;/td&gt;
      &lt;td&gt;5 h – 24 h&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rate Limit (429)&lt;/td&gt;
      &lt;td&gt;Cooldown&lt;/td&gt;
      &lt;td&gt;1 min – 1 h&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Timeout&lt;/td&gt;
      &lt;td&gt;Cooldown&lt;/td&gt;
      &lt;td&gt;1 min – 1 h&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Auth Error (401)&lt;/td&gt;
      &lt;td&gt;Cooldown&lt;/td&gt;
      &lt;td&gt;1 min – 1 h&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;cooldown-progression&quot;&gt;Cooldown Progression&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1st failure → 1 minute
2nd failure → 5 minutes
3rd failure → 25 minutes
4th+ failure → 1 hour (cap)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Billing errors are treated more seriously (5 h → 10 h → 24 h cap).&lt;/p&gt;

&lt;h2 id=&quot;manual-model-switching&quot;&gt;Manual Model Switching&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Use specific model for this session&lt;/span&gt;
openclaw agent &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; qwen-portal/coder-model &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Change default permanently&lt;/span&gt;
openclaw config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;agents.defaults.model.primary qwen-portal/coder-model

&lt;span class=&quot;c&quot;&gt;# Check current setup&lt;/span&gt;
openclaw config get agents.defaults.model.primary
openclaw config get agents.defaults.model.fallbacks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;monitoring-fallback-events&quot;&gt;Monitoring Fallback Events&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Check auth profile cooldown status&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.openclaw/agents/main/agent/auth-profiles.json | jq &lt;span class=&quot;s1&quot;&gt;&apos;.usageStats&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Watch logs for fallback events&lt;/span&gt;
openclaw logs &lt;span class=&quot;nt&quot;&gt;--follow&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;fallback&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;cooldown&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;disabled&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;example-scenarios&quot;&gt;Example Scenarios&lt;/h2&gt;

&lt;h3 id=&quot;scenario-1-openai-tokens-exhausted&quot;&gt;Scenario 1: OpenAI Tokens Exhausted&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;OpenAI returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;402 Payment Required&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;OpenClaw disables OpenAI profile (5 h)&lt;/li&gt;
  &lt;li&gt;Automatically switches to Qwen&lt;/li&gt;
  &lt;li&gt;Continues using Qwen until OpenAI cooldown expires&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;scenario-2-temporary-rate-limit&quot;&gt;Scenario 2: Temporary Rate Limit&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;OpenAI returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;429 Too Many Requests&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;OpenClaw applies 1-minute cooldown&lt;/li&gt;
  &lt;li&gt;Retries after cooldown → still rate-limited → falls back to Qwen&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;scenario-3-multi-provider&quot;&gt;Scenario 3: Multi-Provider&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;OpenAI fails → try Anthropic&lt;/li&gt;
  &lt;li&gt;Anthropic fails → try Qwen&lt;/li&gt;
  &lt;li&gt;All fail → error returned to user&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;per-agent-overrides&quot;&gt;Per-Agent Overrides&lt;/h2&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;agents&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;list&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-5.3-codex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/coder-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;code-reviewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openai-codex/gpt-5.3-codex&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;meal-screenshot-intake&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;primary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen-portal/vision-model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fallbacks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;cost-optimization&quot;&gt;Cost Optimization&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Setup&lt;/th&gt;
      &lt;th&gt;Primary&lt;/th&gt;
      &lt;th&gt;Fallback&lt;/th&gt;
      &lt;th&gt;Est. Monthly&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenAI only&lt;/td&gt;
      &lt;td&gt;GPT-5.3&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;~$50–200&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenAI + Qwen&lt;/td&gt;
      &lt;td&gt;GPT-5.3&lt;/td&gt;
      &lt;td&gt;Qwen (free)&lt;/td&gt;
      &lt;td&gt;~$20–80&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Qwen only&lt;/td&gt;
      &lt;td&gt;Qwen&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;$0&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Use expensive models for critical tasks (security reviews, production code), free fallback for bulk/routine work.&lt;/p&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Fallback not working – verify config&lt;/span&gt;
openclaw config get agents.defaults.model.fallbacks

&lt;span class=&quot;c&quot;&gt;# Stuck on fallback – check cooldowns&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.openclaw/agents/main/agent/auth-profiles.json | jq &lt;span class=&quot;s1&quot;&gt;&apos;.usageStats&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Force reset (restart gateway)&lt;/span&gt;
openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-vps-setup/&quot;&gt;OpenClaw VPS Setup&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-qwen-setup/&quot;&gt;OpenClaw with Qwen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2026/02/24/openclaw-subagents/&quot;&gt;OpenClaw Subagents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Extending the Health Tracker: Data Visualization and Trends</title>
    <link href="https://blog.weisser.dev/blog/2026/02/23/extending-health-tracker-data-visualization/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-23T00:00:00+00:00</published>
    <updated>2026-02-23T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/23/extending-health-tracker-data-visualization/</id>
    <category term="ai" />
    <category term="javascript" />
    <summary type="html">Once you have a health data aggregation layer working, the next question is how to display it usefully. Raw numbers in a table answer “what happened” but don’t help you understand “is this good or bad” or “where is this heading.” The extension I built adds trend charts, weekly summaries, and goal...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/23/extending-health-tracker-data-visualization/">&lt;p&gt;Once you have a health data aggregation layer working, the next question is how to display it usefully. Raw numbers in a table answer “what happened” but don’t help you understand “is this good or bad” or “where is this heading.” The extension I built adds trend charts, weekly summaries, and goal tracking — the visualization layer that turns data into understanding.&lt;/p&gt;

&lt;p&gt;For charting, I evaluated a few JavaScript libraries before settling on one that balanced bundle size, API quality, and customizability. The key requirements were: smooth animation on data updates, good mobile touch interaction for pinch-to-zoom on time series, the ability to draw custom annotations (personal records, goal thresholds, notable events), and a clean aesthetic that fits the overall app design. The library’s data model also needed to align reasonably well with the normalized health data schema to avoid excessive transformation on every render.&lt;/p&gt;

&lt;p&gt;The weekly summary component is the most design-intensive part of the extension. The challenge is information density — there’s a lot to say about a week of health data, but a good summary should be scannable in thirty seconds. The design uses a combination of sparklines (tiny inline charts for trend direction), color coding (green/yellow/red for performance relative to goals), and a single highlighted “insight of the week” that calls out the most notable pattern. The ordering of the sections reflects decreasing importance: sleep and recovery first (because they affect everything else), then training load, then nutrition, then step count.&lt;/p&gt;

&lt;p&gt;Goal tracking introduced an interesting data modeling question: what is a health goal, exactly? Goals have a target value, a metric, a time period, and a direction (higher is better, lower is better, or stay within a range). They might be weekly targets, monthly targets, or ongoing. And they need to coexist with historical data that was recorded before the goal existed. The schema I landed on treats goals as time-range annotations on metrics, which means the chart layer can shade goal periods differently and the summary layer can calculate progress correctly regardless of when the goal was set.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">WW Food Tracker: Multimodal AI Agents for Nutrition Logging</title>
    <link href="https://blog.weisser.dev/blog/2026/02/22/ww-food-tracker/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-22T00:00:00+00:00</published>
    <updated>2026-02-22T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/22/ww-food-tracker/</id>
    <category term="ai" />
    <category term="python" />
    <summary type="html">Manual food tracking is tedious. You have to look up every ingredient, estimate portion sizes, and enter everything by hand — which is exactly why most people stop doing it after a few days. The WW Food Tracker takes a different approach: take a screenshot of your meal (from a food delivery app, ...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/22/ww-food-tracker/">&lt;p&gt;Manual food tracking is tedious. You have to look up every ingredient, estimate portion sizes, and enter everything by hand — which is exactly why most people stop doing it after a few days. The WW Food Tracker takes a different approach: take a screenshot of your meal (from a food delivery app, a recipe page, a restaurant menu, or even a photo of your plate), and let an AI agent do the analysis.&lt;/p&gt;

&lt;p&gt;The pipeline starts with a vision model that receives the screenshot and returns a structured description of what’s visible: ingredients, portion estimates, preparation method. This unstructured description is then passed through a second step that converts it to a normalized JSON schema: each food item, estimated quantity in grams, and macronutrient estimates. The JSON output is then fed into a skill runner — a lightweight executor that can apply domain-specific logic on top of the structured data, such as calculating Weight Watchers points, comparing against daily goals, or flagging items that exceed a user-defined threshold.&lt;/p&gt;

&lt;p&gt;The skill pipeline pattern is what makes this more than a one-shot prompt. Each skill is a small, focused function that takes the normalized JSON and produces a specific output. Skills are composable: you can chain them, run them in parallel, or conditionally activate them based on the content of the previous step. This is a practical implementation of the “tool use” pattern that modern AI agents rely on, applied to a concrete domain.&lt;/p&gt;

&lt;p&gt;What I find most interesting about this project from a technical standpoint is how much the quality of the JSON normalization step matters. Vision models are good at describing food, but the structured output needs to be consistent enough for downstream skills to process reliably. Small prompt engineering decisions — how you define the schema, what examples you provide, how you handle ambiguous cases — have a disproportionate effect on the end-to-end quality. Multimodal AI agents are powerful, but the data normalization layer is where most of the real engineering effort lives.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Building an AI-Powered Health Tracker</title>
    <link href="https://blog.weisser.dev/blog/2026/02/22/building-an-ai-health-tracker/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-22T00:00:00+00:00</published>
    <updated>2026-02-22T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/22/building-an-ai-health-tracker/</id>
    <category term="ai" />
    <category term="javascript" />
    <summary type="html">Health data is everywhere and nowhere at the same time. Fitness apps track your workouts, step counters track your movement, nutrition apps track your food, sleep trackers track your recovery — and none of them talk to each other in any meaningful way. The AI health tracker I’ve been building is ...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/22/building-an-ai-health-tracker/">&lt;p&gt;Health data is everywhere and nowhere at the same time. Fitness apps track your workouts, step counters track your movement, nutrition apps track your food, sleep trackers track your recovery — and none of them talk to each other in any meaningful way. The AI health tracker I’ve been building is an attempt to aggregate all of this into a single view and then apply an LLM to generate insights that go beyond what any single data source can provide.&lt;/p&gt;

&lt;p&gt;The architecture is a full-stack JavaScript application: a backend API that connects to various data sources, a normalization layer that translates heterogeneous health data into a consistent schema, a storage layer for historical data, and a frontend that displays aggregated metrics and surfaces AI-generated summaries. The data aggregation is the hardest part — every source has a different API, a different data model, and different refresh rates. Building adapters for each source while keeping the core data model stable requires careful interface design.&lt;/p&gt;

&lt;p&gt;The LLM integration sits on top of the normalized data layer. Rather than dumping raw numbers into a prompt, the system pre-computes meaningful derived metrics — weekly trends, personal records, recovery load estimates — and provides those as structured context alongside the raw data. The model is then prompted to act as a knowledgeable fitness coach, synthesizing across all data dimensions to produce a weekly summary: what went well, what the data suggests about recovery and readiness, and specific recommendations for the coming week.&lt;/p&gt;

&lt;p&gt;What I’ve found technically interesting is the challenge of making LLM outputs trustworthy for health applications. The model needs to be grounded in the actual data rather than generating plausible-sounding advice that doesn’t reflect the user’s real metrics. Prompt engineering for factual grounding — explicitly instructing the model to reference specific data points and not to make claims beyond what the data supports — makes a meaningful difference in output quality. The combination of good data aggregation and careful prompting produces recommendations that feel genuinely personalized rather than generic.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Agent Lyfta: An AI Personal Trainer Powered by Your Workout Data</title>
    <link href="https://blog.weisser.dev/blog/2026/02/22/agent-lyfta-api/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-22T00:00:00+00:00</published>
    <updated>2026-02-22T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/22/agent-lyfta-api/</id>
    <category term="ai" />
    <category term="python" />
    <summary type="html">Lyfta is a solid workout tracking app, but like most fitness apps, it keeps your data locked behind a mobile UI. The moment I found out there was an API, I wanted to do something more interesting with the data than just look at charts in the app. The result is a small Python project that fetches ...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/22/agent-lyfta-api/">&lt;p&gt;Lyfta is a solid workout tracking app, but like most fitness apps, it keeps your data locked behind a mobile UI. The moment I found out there was an API, I wanted to do something more interesting with the data than just look at charts in the app. The result is a small Python project that fetches workout history from the Lyfta API and feeds it into an AI agent acting as a personal trainer.&lt;/p&gt;

&lt;p&gt;The data pipeline is straightforward: authenticate against the Lyfta API, fetch recent workout sessions, normalize the JSON response into a consistent structure (exercise name, sets, reps, weight, date), and hand it over to the agent. The agent receives the workout history as context and is prompted to act as an experienced personal trainer who has been following your progress. It can identify plateaus, note progressive overload patterns, flag imbalances between muscle groups, and suggest adjustments for the next session.&lt;/p&gt;

&lt;p&gt;What makes this interesting architecturally is the difference between a simple API call and an actual agent. A naive implementation would just dump the JSON into a prompt and ask “what should I do next week?” The agent approach is more nuanced: it uses tool calls to query specific time ranges, compare current performance to historical bests, and cross-reference exercises that target similar muscle groups. The agent builds its answer iteratively rather than in a single pass, which produces noticeably more coherent recommendations.&lt;/p&gt;

&lt;p&gt;The broader concept here — AI agents applied to personal health tracking — has real potential. Your health data already exists in various apps and wearables, but it’s fragmented and none of those apps have the reasoning capacity to synthesize it meaningfully. An agent layer that can pull from multiple data sources (workouts, steps, sleep, nutrition) and reason about them together would be genuinely useful. This project is a small proof of concept in that direction, scoped to just the workout side, but the pattern scales.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Forking OpenCode: How Terminal-Based AI Coding Agents Work</title>
    <link href="https://blog.weisser.dev/blog/2026/02/13/opencode-fork/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-02-13T00:00:00+00:00</published>
    <updated>2026-02-13T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/02/13/opencode-fork/</id>
    <category term="ai" />
    <category term="typescript" />
    <summary type="html">OpenCode is an open-source AI coding agent that runs in your terminal. Unlike IDE plugins or browser-based tools, it operates directly on your filesystem, reads your code, and uses tool calls — file reads, writes, shell execution, grep — to complete tasks autonomously. When I discovered the proje...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/02/13/opencode-fork/">&lt;p&gt;OpenCode is an open-source AI coding agent that runs in your terminal. Unlike IDE plugins or browser-based tools, it operates directly on your filesystem, reads your code, and uses tool calls — file reads, writes, shell execution, grep — to complete tasks autonomously. When I discovered the project, I forked it to experiment with modifications and to better understand how it works from the inside.&lt;/p&gt;

&lt;p&gt;The architecture of a terminal-based coding agent is worth understanding. At its core, it’s a loop: receive a task, reason about the next step, call a tool, observe the result, repeat until done. The “tools” are just functions the model is allowed to invoke — read a file, write a file, run a shell command, search for a pattern. The model decides which tools to call and in what order. The TypeScript codebase is clean and well-structured, which makes it a good project to learn from if you’re curious about agent implementation details.&lt;/p&gt;

&lt;p&gt;What makes OpenCode interesting compared to similar tools is the focus on transparency and control. Every tool call is visible in the terminal, every file change is tracked, and the agent explains its reasoning as it works. This makes it much easier to catch mistakes and course-correct than tools that operate more opaquely. The conversation history also stays local — no data is sent to a third-party service beyond the API calls to the LLM provider you configure.&lt;/p&gt;

&lt;p&gt;My fork is primarily a place to experiment — testing prompt modifications, adding tools, exploring how the agent handles edge cases. If you’re interested in AI agent internals or want a coding assistant that runs entirely in your terminal without a GUI, OpenCode is worth looking at. The TypeScript codebase is approachable and the project is actively developed.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">Rebuilding My Personal Site as a Configurable Digital Card</title>
    <link href="https://blog.weisser.dev/blog/2026/01/30/weisser-dev-personal-site/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-01-30T00:00:00+00:00</published>
    <updated>2026-01-30T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/01/30/weisser-dev-personal-site/</id>
    <category term="typescript" />
    <summary type="html">Personal websites have a tendency to get stale. You build something, it looks good for a while, and then a year later the tech stack feels dated, the content is out of sync with reality, and updating it feels like more work than it’s worth. I wanted to break that cycle by building something that’...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/01/30/weisser-dev-personal-site/">&lt;p&gt;Personal websites have a tendency to get stale. You build something, it looks good for a while, and then a year later the tech stack feels dated, the content is out of sync with reality, and updating it feels like more work than it’s worth. I wanted to break that cycle by building something that’s genuinely easy to keep current — a digital business card for weisser.dev that’s configurable through a single JSON file.&lt;/p&gt;

&lt;p&gt;The stack is TypeScript and React, deployed as a static site. The entire content model — name, tagline, links, skills, contact info — lives in a typed configuration object. Updating the site means editing JSON, not touching components. This might sound like over-engineering for a single-person site, but it’s made a real difference in practice: I’ve updated the content several times since launch without touching a single component file.&lt;/p&gt;

&lt;p&gt;Multilingual support was a deliberate design decision. German and English content are both first-class, and the language toggle is persistent across sessions. For a developer based in Germany working on both local and international projects, having both languages available isn’t a vanity feature — it’s actually useful. The implementation uses a simple context-based i18n approach without a heavy library, since the vocabulary is small and the switching logic is trivial.&lt;/p&gt;

&lt;p&gt;Dark mode is implemented via CSS custom properties that respond to the system preference, with a manual toggle that overrides it and saves to localStorage. The design system underneath is minimal: a handful of color tokens, a type scale, and a grid. Nothing that requires a build step to reason about. The result is a site that loads instantly, works without JavaScript for the basic content, and is trivially forkable if someone else wants to use it as a starting point — which is part of why it’s public.&lt;/p&gt;
</content>
  </entry>
  <entry xml:lang="en">
    <title type="html">A Multilingual, Configurable Resume App in TypeScript</title>
    <link href="https://blog.weisser.dev/blog/2026/01/30/multilingual-resume-app/" rel="alternate" type="text/html" hreflang="en" />
    <published>2026-01-30T00:00:00+00:00</published>
    <updated>2026-01-30T00:00:00+00:00</updated>
    <id>https://blog.weisser.dev/blog/2026/01/30/multilingual-resume-app/</id>
    <category term="typescript" />
    <summary type="html">There’s a category of developer who has built their own resume app, and I’ve become one of them. The practical motivation was concrete: I work in a bilingual professional context, maintain separate German and English versions of my CV, and found the update-and-keep-in-sync workflow between two do...</summary>
    <content type="html" xml:base="https://blog.weisser.dev/blog/2026/01/30/multilingual-resume-app/">&lt;p&gt;There’s a category of developer who has built their own resume app, and I’ve become one of them. The practical motivation was concrete: I work in a bilingual professional context, maintain separate German and English versions of my CV, and found the update-and-keep-in-sync workflow between two documents genuinely painful. A single TypeScript application with locale-switching solves the problem by making both languages first-class, not afterthoughts.&lt;/p&gt;

&lt;p&gt;The content model is fully JSON-driven. Personal information, professional experience, education, skills, certifications, publications, and languages all live in a typed TypeScript configuration object. Each text field that differs between locales holds a locale map rather than a string. The React components are locale-aware: they receive the active locale from context and render the appropriate string. Adding a new locale is a matter of adding keys to the configuration — the component layer doesn’t need to change.&lt;/p&gt;

&lt;p&gt;Automatic theme detection uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.matchMedia(&apos;(prefers-color-scheme: dark)&apos;)&lt;/code&gt; with a listener that updates the theme when the system preference changes. A manual toggle overrides the automatic detection and stores the preference in localStorage. The CSS is custom properties-based — the entire theme is a handful of color tokens applied to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:root&lt;/code&gt;, and switching themes is a single class change on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt;. This approach is simpler and faster than a styled-components theme or a CSS-in-JS solution for a project with a clear, constrained color system.&lt;/p&gt;

&lt;p&gt;The question of why build your own CV app rather than use a template is worth addressing directly. The answer isn’t that the technology is impressive (it isn’t) or that it takes less time than a Word document (it doesn’t, initially). The answer is maintainability over time. Once the app is built, updating the content is fast and reliable, the output is consistent across locales, the print stylesheet works, and adding a section or field takes minutes rather than reformatting a document. For a CV you intend to keep current for years, that ongoing maintenance cost difference is worth the upfront investment.&lt;/p&gt;
</content>
  </entry>
</feed>

