Die Cloud-Coding-Agenten sind gut. Sie sind auch ein Bündel von Trade-offs, dem du nicht entkommst: das Modell läuft auf der Maschine von jemand anderem, das Unternehmen setzt die Rate-Limits, dein Code geht durch deren API, und der Preis pro Token ist das, was sie in diesem Quartal sagen. Mit dem meisten kann man leben. Was mich stört, ist die Bindung. Wenn ich morgen einen Coding-Agenten auf ein anderes Modell richten möchte, im Flugzeug arbeiten oder ihn mit etwas pairen will, das ich gerade lokal baue, kann ich das nicht. lillycoder ist die Version dieses Workflows, die andersherum funktioniert. Der Agent läuft lokal, spricht mit jedem LLM-Server, den du sowieso schon hast, und behandelt das Modell auf der anderen Seite als austauschbar.
Die Form des Tools ist absichtlich klein. Du startest lillycoder in einem Projektverzeichnis, und es lässt dich in eine Chat-REPL fallen. Du tippst etwas. Das Modell auf der anderen Seite wählt Tools, um es zu erledigen: Datei lesen, Datei schreiben, Datei bearbeiten, Shell-Befehl ausführen, Paket installieren, Projekt durchsuchen, Verzeichnis auflisten. Jeder Tool-Call kommt als strukturierte Aktion zurück, der Agent führt sie in deinem aktuellen Arbeitsverzeichnis aus, und das Ergebnis fließt in die Konversation zurück. Von außen ist das dieselbe Schleife, die jeder Coding-Agent fährt. Von innen ist der Unterschied das, was die Wahl trifft.
Was die Wahl trifft, ist, was immer du willst. lillycoder spricht das OpenAI-Format /v1/chat/completions, das mittlerweile die Lingua franca lokaler LLM-Server ist. llama.cpp veröffentlicht es. ollama veröffentlicht es auf seiner Kompatibilitätsschicht. LM Studio veröffentlicht es. hydra-llm veröffentlicht es. Der Agent kümmert sich also nicht darum, welcher davon läuft, und beim ersten Start scannt er gängige Ports und bietet an, das zu nutzen, was er findet. Wenn nichts läuft, zeigst du ihm mit --api eine URL. Das Modell ist nicht Teil des Pakets; es ist das, was du gestartet hast.
Local-first heißt ein anderes Bedrohungsmodell als die Cloud-Version. Dort oben ist der schlimmste Fall meist wirtschaftlich: das Modell verbrennt Tokens, du zahlst dafür. Hier unten kann das Modell auf deine Festplatte schreiben und Shell-Befehle ausführen. Das ist mächtiger und gefährlicher, und so zu tun, als wäre es das nicht, wäre verantwortungslos. Also ist das Berechtigungssystem der Teil des Tools, in den ich die meiste Zeit gesteckt habe.
Jede mutierende Aktion ist durch eine Abfrage abgesichert, bevor sie ausgeführt wird. Wenn das Modell beschließt, eine Datei zu schreiben, siehst du den Pfad und die Größe; wenn es einen Befehl ausführen will, siehst du den Befehl. Du antwortest mit einer von vier Optionen:
🦊 lilly wants to: write_file("src/index.js", 142 chars)
[y]es [n]o [a]lways for this tool [p]ath: always for this exact target
>
Die vier Optionen sind keine Deko. y ist die offensichtliche. n lehnt diesen einen Aufruf ab und lässt das Modell erneut versuchen oder die Richtung wechseln. a erteilt einen sitzungsweiten Freibrief für das Tool selbst, was du willst, sobald du dem Modell vertraust, Dateien zu lesen, aber dir trotzdem jedes bash ansehen möchtest. p erteilt einen Freibrief für genau diesen Pfad, was du willst, wenn das Modell an einer einzelnen Datei iteriert und du nicht jedes Speichern bestätigen möchtest. Beide Freibriefe gelten nur in der Sitzung: sie verschwinden, wenn die REPL endet.
Über der Berechtigungsabfrage sitzt ein Hard-Deny-Klassifikator. Es gibt eine Liste von Befehlen, die schlicht nicht laufen, egal was du an der Abfrage antwortest und egal welches Flag du setzt. sudo, rm -rf /, mkfs, dd of=/dev/*, Forkbomben, rekursives chmod oder chown auf / oder ~. Sie werden abgelehnt, bevor sie den Executor erreichen. Das Flag --bypass-permissions (das es für Headless- oder Skript-Nutzung gibt) überspringt die Per-Aufruf-Abfrage, aber es überspringt den Sicherheitsfilter nicht. Das ist Absicht; Bypass soll Routine ersparen, niemals Gefahr.
Der Klassifikator ist absichtlich klein und stumpf. Er ist ein String- und Pattern-Checker, keine Sandbox. Eine bösartige Shell-Pipeline, die denselben Schaden auf eine clevere Weise anrichtet, schlüpft an ihm vorbei; das gesamte Tool zu sandboxen ist ein größeres Projekt als dieses. Was der Klassifikator dir kauft, ist Schutz vor genau der Art Fehler, die entsteht, wenn ein LLM einen Pfad halluziniert oder einen destruktiven Befehl aus den Trainingsdaten kopiert. Das ist der realistische Fehlerfall, und genau dafür gibt es den Klassifikator.
Für sicheres Experimentieren liefert das Repository eine docker-compose.yml, die ein einzelnes WORKINGDIR/ in einen Container mountet, in dem lillycoder bereits installiert ist. Du editierst auf dem Host, lässt den Agenten im Container laufen, und jeder Schaden bleibt in diesem einen gemounteten Ordner. Es ist dieselbe Idee wie ein chroot mit weniger Zeremonie, und es ist die Konfiguration, die ich nutze, wenn ich dem Modell auf einer frischen Codebase Spielraum geben möchte, ohne jede Abfrage zu beobachten.
docker compose up -d
docker compose exec lillycoder bash
# im Container, in /workspace:
lillycoder --api http://host.docker.internal:11434/v1
Innerhalb dieser Sandbox wird das Flag --bypass-permissions deutlich vernünftiger. Die Hard-Deny-Liste läuft weiterhin. Das Modell kann den Mount weiterhin nicht verlassen. Du kannst beobachten, was ein autonomer Agent auf einem 14B-Lokalmodell tatsächlich tut, ohne die Bestätigungsschleife pro Tastendruck, und du kannst das Ganze mit einem docker compose down -v wieder wegwerfen.
Das Modell auf der anderen Seite ist wichtig. Tool-Calling-Zuverlässigkeit gibt es nicht umsonst; sie verlangt ein Modell, das darauf trainiert oder feinabgestimmt wurde, strukturierte Tool-Calls zu erzeugen, ohne aus dem Ruder zu laufen. Die Community-Stufe "gut bei Tools" ist nicht dieselbe wie die Stufe "gut beim Chat", und ein Modell, das hervorragend Prosa schreibt, kann beim Wählen des richtigen Tools mit den richtigen Argumenten unbrauchbar sein. lillycoder pflegt eine kleine Allowlist von Modellfamilien, die sich in meinen Tests als zuverlässig genug erwiesen haben: Qwen 2.5 und 3, Gemma 3, Llama 3.1, Mistral Small 3, Dolphin 3 R1. Wenn du das Tool auf ein Modell außerhalb dieser Liste richtest, warnt es dich. --force bringt die Warnung zum Schweigen, wenn du weißt, was du tust.
Für die eigentliche Coding-Schleife verdient sich auf Consumer-Hardware grob 14B bis 32B in Q4_K_M-Quantisierung sein Geld. Das passt auf eine GPU mit 16 bis 24 GB und produziert Tool-Calls, die in meiner Nutzung von den Cloud-Agenten bei kleinen bis mittleren Aufgaben nicht zu unterscheiden sind. Die Cloud gewinnt weiterhin bei dateiübergreifenden Refactorings im großen Maßstab, mehrstufigem Schließen über sehr lange Kontexte und überall, wo der SOTA-Qualitätsabstand sichtbar wird. Für das tägliche "diese Datei ändern, Tests laufen lassen, Fehler beheben" hat die lokale Schleife aufgeholt.
Es gibt ein Schwesterprojekt namens hydra-llm, das ich vor diesem hier gebaut habe. Es verwaltet lokale LLM-Server: lade ein Modell herunter, starte es auf einem Port, bekomme einen OpenAI-kompatiblen Endpunkt zurück. lillycoder spricht genau dieses Format. Beides zusammen ergibt einen vollständig lokalen Coding-Stack mit zwei Befehlen:
# in hydra-llm:
hydra-llm start qwen2.5-32b
hydra-llm api qwen2.5-32b # gibt die URL aus
# in deinem Projektverzeichnis:
lillycoder --api http://localhost:18087/v1
Das ist alles. hydra-llm übernimmt den Modell-Lebenszyklus. lillycoder ist der Agent darüber. Sie sind zwei Hälften eines Workflows, und jede Hälfte ist austauschbar. Wenn du bereits ollama laufen hast, lass hydra-llm weg. Wenn du einen anderen Agenten lieber magst, richte ihn auf den hydra-llm-Endpunkt. Die Trennung ist Absicht; Tools, die zugleich Runtime und Agent sein wollen, werden in einem von beiden immer schlechter.
Die Installation ist der langweilige Teil, so wie es sein soll. Auf Debian und Ubuntu gibt es ein signiertes apt-Repository, also ist es nach einem einmaligen Keyring-Setup ein normales Paket:
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://ra-yavuz.github.io/apt/pubkey.gpg \
| sudo tee /etc/apt/keyrings/ra-yavuz.gpg >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/ra-yavuz.gpg] https://ra-yavuz.github.io/apt stable main" \
| sudo tee /etc/apt/sources.list.d/ra-yavuz.list
sudo apt update
sudo apt install lillycoder
Aus dem Quellcode auf jedem Linux ist es das übliche git clone und pip install --user -e .. Danach cd in ein Projekt, lillycoder starten und tippen. Wenn ein lokaler LLM-Server bereits auf einem gängigen Port lauscht, findet ihn das Tool. Falls nicht, übergib --api mit der URL.
Die mitgelieferte Persona ist eine Kid-Coder-Stimme, bewusst verspielt, weil ich es angenehm finde, mit ihr zu arbeiten, und die Alternative wäre noch eine knappe Junior-Engineer-Stimme gewesen, von denen die Welt genug hat. Wenn das nicht dein Geschmack ist, leg eine Markdown-Datei unter ~/.config/lillycoder/personas/<name>.md ab und starte mit --persona <name>. Die Persona ist der Systemprompt; alles andere, die Tools, die Abfragen, die Schleife, bleibt gleich.
Das ist auch ein nützlicher Ort, um Projektkonventionen zu kodieren. Eine Persona, die sagt "führe nach Edits immer die Testsuite aus, ändere niemals Dateien außerhalb von src/, bevorzuge reine Funktionen", wird zu einem Verhalten, auf das sich das Modell die ganze Sitzung lang stützt. Es ist keine Sandbox und keine Garantie, aber es prägt, was das Modell wählt, bevor es zur Berechtigungsabfrage kommt, was bedeutet: weniger Abfragen zum Ablehnen und mehr zum automatischen Akzeptieren.
Der größere Punkt hinter all dem ist, dass lokales agentisches Coding kein Forschungsprojekt mehr ist. Die Bausteine sind da: fähige Modelle, eine offene Endpunktform, schnell genug Hardware und die Erkenntnis, dass "Agent" und "Modellserver" zwei verschiedene Anliegen sind, die nicht zusammengeschweißt gehören. lillycoder ist eine spezifische Komposition dieser Bausteine. Es ist meinungsstark in Sachen Sicherheit (Abfragen per Default, Hard-Deny obendrauf), uninteressiert daran, die Modell-Runtime zu sein (zeig ihm einfach eine), und klein genug, dass die gesamte Codebase in einer Sitzung lesbar ist, falls du wissen willst, was auf deiner Maschine läuft.
Wenn du es ausprobieren möchtest, sind die Installationszeilen oben echt und der Quellcode liegt auf GitHub. Wenn lokal schon etwas lauscht, reicht ein lillycoder in einem Ordner. Das erste, was man tippt, dasselbe, was ich tippe, ist "welche Dateien sind in diesem Ordner?" Aus der Antwort siehst du schon, ob das Modell auf der anderen Seite ein nützliches Gegenüber sein wird, und das ist das einzige, was wirklich darüber entscheidet, welches du gewählt hast.
