~/cases/gnomaAktiv

gnoma

Warum gnoma jeden Prompt durch einen sichtbaren, provider-agnostischen Router schickt — und was der Bau eines Terminal-Agenten über das Verdienen von Vertrauen lehrt.

Solo-Engineer · Persönliches Projekt · 2025 - heute

Kontext

Ich betreibe mehrere LLM-Provider nebeneinander — Cloud-Modelle für harte Probleme, lokale für billige. Jeder Terminal-Agent, den ich probiert habe, hat mich auf einen Provider festgelegt oder stillschweigend das Modell gewählt.

Problem

Modellqualität variiert je nach Aufgabe, Routing ist also wirklich nützlich. Aber Routing ohne Sichtbarkeit ist eine Black Box: Wer nicht sieht, warum ein Agent ein Modell gewählt hat, vertraut ihm keine echte Arbeit an.

Randbedingungen

Solo-Projekt in Go, ausgeliefert als einzelne statische Binary. gnoma telefoniert nach Hause zu niemandem, und lokale Modelle sind erstklassige Bürger, kein Fallback.

Ziele

Provider-agnostisch by Design. Die Routing-Entscheidung in jeder Runde zeigen. Sicherheit an der Content-Boundary, nicht nachgerüstet. Erweiterbar über Protokolle statt über eine Plugin-Runtime.

Architektur

Prompts laufen über die TUI in einen Agent-Loop. Ein Multi-Armed-Bandit-Router wägt Capability-Gates, deklarierte Stärken, Latenz und Kosten ab und wählt pro Prompt einen Arm. Ein Tier-0-Kleinmodell klassifiziert Prompts und beantwortet triviale, ohne den Rechner zu verlassen. Eine Safety-Schicht scannt jede ausgehende Nachricht und jedes Tool-Ergebnis auf Secrets und Boundary-Verstöße. Tools erweitern den Loop über LSP, MCP-Server und Hooks.

  • Du tippst in der TUI.
  • Der Agent-Loop nimmt den Prompt entgegen; eine Safety-Schicht prüft jede ausgehende Nachricht und jedes Tool-Ergebnis.
  • Der Bandit-Router wählt pro Prompt den besten Provider-Arm und zeigt seine Wahl.
  • Der Prompt geht an einen von sechs Providern oder wird lokal von einem kleinen Modell beantwortet.
  • Tools erweitern den Agenten über LSP, MCP-Server und Hooks.
gnoma · Request-Pfad

Trade-offs

Der Bandit-Router passt sich beobachtetem Verhalten an, ist beim Kaltstart aber konservativ. Triviale Prompts lokal zu beantworten spart Kosten und Latenz, braucht aber einen sichtbaren Override-Pfad. Erweiterung über Protokolle hält die Binary klein, macht Erweiterungen aber von externem Tooling abhängig.

Umsetzung

Pfade werden TOCTOU-sicher kanonisiert, Unicode wird bereinigt, bevor ein Provider ihn sieht, und Language-Server-Grounding läuft über vierzehn LSP-Server. Das Ergebnis ist eine statische Binary plus Multi-Arch-Container-Image.

Entscheidungen & Trade-offs

EntscheidungWarumTrade-off
Multi-Armed-Bandit-Router statt statischer RegelnRouting passt sich an beobachtete Latenz und Qualität an, statt zu raten.Kaltstart-Wahlen sind konservativ und weniger vorhersagbar.
Tier-0-Modell für triviale PromptsEinfache Prompts verlassen den Rechner nie und kosten nichts.Fehlklassifikation braucht einen sichtbaren Override-Pfad.
Eine statische Binary, Erweiterung über ProtokolleSofortige Installation, keine Laufzeit-Abhängigkeiten, kein Plugin-Manager.Keine dynamischen Plugins; Erweiterung braucht MCP-Server oder Hooks.
Safety-Schicht an der Content-BoundarySecrets und Boundary-Verstöße werden abgefangen, bevor etwas den Host verlässt.Geringe Latenz pro Runde; gelegentliche False Positives.

Ergebnis

  • Provider-agnostisch über Anthropic, OpenAI, Google, Mistral, Ollama und llama.cpp.
  • Der Router zeigt in jeder Runde, welchen Arm er gewählt hat und warum.
  • Liefert als einzelne statische Binary mit Multi-Arch-Container-Image.

Nachweise

Provider-Backends
6

Anthropic, OpenAI, Google, Mistral, Ollama, llama.cpp

LSP-Integrationen
14

Language-Server-Grounding

Distribution
Statisches Binary

Zusätzlich ein Multi-Arch-Container-Image

Was ich heute anders machen würde

Ich habe den Router zuerst gebaut und seine Observability zweitens. Heute würde ich mit dem Decision-Record pro Runde anfangen — der Router verdient Vertrauen nur, wenn jede Wahl im Nachhinein erklärbar bleibt.