Files

354 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# llmrouter — Betrieb (`llm.lan`, seit 2026-09-05)
Der schlanke Nachfolger des LiteLLM-Gateways: ein OpenAI-kompatibler
Reverse-Proxy in Bun/TypeScript. `llm.lan` zeigt auf ihn. Er kennt vier
Eingriffe je Anfrage: Client-Key prüfen, Modellnamen auflösen, Kosten aus der
Antwort mitlesen, eine Log-Zeile schreiben. Keine Datenbank, kein Admin-UI,
keine Format-Übersetzung — alle Upstreams sprechen OpenAI-Format.
> **Wofür wichtig:** Alle Agenten (Spark, Buzz, Klara, Jessie, Inka …),
> opencode auf dem Mac und Claude Code im Terminal sprechen nur diesen
> Endpunkt. Fällt er aus, steht die Agenten-Arbeit. Provider-Keys (OpenRouter,
> Mistral, deizo) liegen nur hier.
>
> Diese Seite ist der Betriebsstand für Störung, Wartung und Nachfolge; die
> technische Referenz steht in [referenz.md](referenz.md).
## Wo läuft was
| Was | Wo |
|---|---|
| Router (`llmrouter`) | LXC **185020** (`llmlite`, `10.18.5.20`) auf `s18p1`, **OCI-Container** `llmrouter_llmrouter_1` (podman-compose, systemd-Unit `llmrouter`), Bild `git.42i.org/spine/llmrouter:latest`, Port **4010** |
| Alter Port **4000** | seit 2026-09-07 von Caddy auf 4010 geleitet (vorher LiteLLM). Hart verdrahtete Clients — Agenten-Images, spark-desktop, ältere Skripte — laufen darüber weiter |
| LiteLLM | **abgeschaltet am 2026-09-07** (`systemctl stop/disable llmlite`). Dateien und Container liegen unangetastet in `/srv/llmlite`, letzte Kosten-Datenbank als `spend-final-20260907.sql.gz` |
| Caddy (`llm.lan`, TLS über `acme.lan`) | derselbe LXC, **eigener oci-Dienst `caddy`** (`/srv/caddy`, systemd-Unit `caddy.service`), `reverse_proxy localhost:4010` |
| Dateien | `/srv/llmrouter/`: `compose.yml`, `.env` (0600); Laufzeit unter `data/`: `config.json`, `key-acls.json`, `keys.json` (0600), `model-blocklist.json`, `log/` (requests.jsonl, changes.log, state.json, aa-cache.json). Sidecar-Binaries daneben, werden nicht mehr gebaut (Ersatz: `podman run` aus dem Image, siehe Deploy) |
| Docker-Image (Prod) | `git.42i.org/spine/llmrouter:latest`, gebaut von CI — seit 2026-09-09 läuft der Prod-Einsatz darauf |
| Lokales Qwen | LLM-Knoten LXC 189080, `10.18.5.30:11507` |
| Sprachdienste lokal | `10.18.5.30:8803` (Qwen3-TTS + whisper.cpp), Ollama `:11434` (Embeddings) |
| Kira im Prod-RZ (deizo) | `10.6.42.111:8000/8001/8002` über den WireGuard-Peer des LXC |
Normalzustand: `curl http://llm.lan:4010/health` liefert `{"ok":true,"zuordnung":"<Zeitstempel>"}`;
der Zeitstempel ist jünger als 12 Stunden.
## Namensräume — was ein Client rufen kann
Die vollständige Tabelle der Namensräume (Stufen, Profile, Rollen, `alt/`,
Claude-Code-Pfad) steht in [referenz.md](referenz.md#namensräume). Kurzfassung:
| Name | Bedeutung |
|---|---|
| `role/<name>` | Rolle (`xo`, `architect`, `teamleader`, `worker`, `qa` …) — **Agenten kennen nur Rollen** |
| `auto/<profil>[:stufe]` | Profile auf drei Achsen: `mass`, `mass:fast`, `allround`, `coding`, `leader` je `:junior`/`:senior` |
| `42i/marvin-2609:low|medium|high|max` | konfigurierte Stufen (Index ≥ 25/45/49/53) |
| `alt/<id>[:effort]` | günstigste Alternative derselben Klasse; aufgelöst bei der ersten Anfrage, dann alle 12 h |
| `openrouter/<id>[:effort]` | 1:1 an OpenRouter |
| `deizo/kira-*`, `42i/marvin-*` | feste Ziele (Prod-RZ, lokal, Cloud-Sprachdienste) |
| alles andere | **404** — kein stiller Fallback mehr seit der LiteLLM-Abschaltung |
Ein Modellwechsel steht in `log/changes.log` (im LXC:
`/srv/llmrouter/data/log/changes.log`).
## Rollen und Profile — drei Ebenen
Ein Agent nennt **nie** ein Modell. Er fragt seine Rolle, die Rolle zeigt auf
ein Profil, und das Profil sucht sich das billigste Modell über den
geforderten Untergrenzen. Umgestellt wird auf jeder Ebene in `config.json`
ohne Deploy, ohne Container anzufassen (42i/intern#1252).
| Profil | Regel | trifft am 2026-09-07 | $/1M gew. |
|---|---|---|---|
| `auto/mass` | fest: lokales Qwen, Ausweich auf `mass:fast` | qwen36:35b | 0 |
| `auto/mass:fast` | Index ≥ 25, Output ≥ 32768 (je Profil senkbar, sonst 65536) | ling-3.0-flash | 0,27 |
| `auto/allround:junior` | Index ≥ 40 | glm-5.3-flash | 0,95 |
| `auto/allround:senior` | Index ≥ 49 | grok-4.6:high | 26,00 |
| `auto/coding:junior` | Coding ≥ 68, Agentic ≥ 45 | glm-5.3-flash | 0,95 |
| `auto/coding:senior` | Coding ≥ 76, Agentic ≥ 50 | grok-4.6:high | 26,00 |
| `auto/leader:junior` | Index ≥ 45, Agentic ≥ 50 | glm-5.3-flash | 0,95 |
| `auto/leader:senior` | Coding ≥ 74, Agentic ≥ 53 | glm-5.3:max | 15,66 |
Warum drei Achsen und nicht eine Zahl: `gemini-3.8-flash` hat Coding 76,3 und
Agentic **41,2** — es schreibt guten Text und trägt den Werkzeug-Rundlauf nicht.
Ein einzelner Index sieht diesen Unterschied strukturell nicht.
**Rollenbelegung:** `xo`, `teamleader`, `lead``auto/leader:junior`;
`worker``auto/mass:fast`; `coder`, `engineer`, `qa`, `perf`
`auto/coding:junior`; alle übrigen →
`auto/allround:junior`; `inka` fest auf `openrouter/openai/gpt-5.6-luna`.
**Keine Rolle steht auf einer Senior-Stufe** (Andreas, 2026-09-07) — hochgestuft
wird einzeln und mit Anlass, nicht vorsorglich. `role/coder` existiert seit
2026-09-09 neben `engineer`, damit Code-Arbeit nicht über `role/worker` (Masse)
läuft.
**Der Ausweich** gilt für Ziele ohne eigenen Rückfall — festes Ziel wie
`auto/mass` (lokales Qwen) oder Schwelle auf einem Ausfall-Modell wie
`auto/mass:fast`. Steht der Knoten, hält eine fremde Arbeit die Karte oder
liefert der Upstream 429/5xx, weicht der Router **einmal** auf den
konfigurierten Ausweich aus; die Log-Zeile trägt dann
`auto/mass->auto/mass:fast`, der Wechsel ist also sichtbar. Die Kette:
`auto/mass``auto/mass:fast` (ling) → `auto/coding:junior`
(glm-5.3-flash) — ling erfüllt die coding:junior-Schwellen strukturell nie,
der Rückfall landet also garantiert auf einem tauglichen Modell. Ohne das
hinge jede Titelzeile jedes Agenten, weil `auto/mass` überall als
`small_model` hängt. Wichtig: Die Ausweich-Auflösung läuft durch die
ACL-Prüfung des aufrufenden Keys — die Container-Personas brauchen die
Kettenziele deshalb in `key-acls.json`, sonst schlägt der Fallback still
fehl (403) und der ursprüngliche Fehler geht durch.
## Wer läuft worauf
| Wer | Rolle | Ziel heute |
|---|---|---|
| agent-spark | `role/engineer` | glm-5.3-flash |
| agent-buzz | `role/ops` | glm-5.3-flash |
| agent-jessie | `role/assistant` | glm-5.3-flash |
| agent-klara | `role/service` | glm-5.3-flash |
| Leiter auf spark-desktop (xo, xo-chat, teamleiter, worker, kanon, thema, diagnose, logbuch) | `role/xo`, `role/teamleader`, `role/worker`, `role/architect`, `role/archivist`, `role/ops` | glm-5.3-flash |
| Inka — Hirn | `role/inka` | **fest** `openai/gpt-5.6-luna` |
| Inka — STT / TTS | `42i/marvin-stt-turbo` / `-tts-turbo` | Voxtral Small / Grok Voice |
| latenztolerante Masse, `small_model` | `auto/mass` | lokales Qwen, 0 $ |
`role/inka` ist bewusst ein fester Modellname statt eines Profils: Die drei
Achsen messen **keine Antwortlatenz**, und am Telefon entscheidet genau die.
Gemessen am 2026-09-07 mit Inkas Zuschnitt (1719k Token Systemprompt, vier
Züge): Luna 0,91,9 s bis zum ersten Wort, `glm-5.3-flash` 1,95,6 s. Der Cache
greift bei beiden; die Zeit geht bei GLM für 70133 Denk-Token drauf, und
Reasoning lässt sich dort nicht abschalten (`"Reasoning is mandatory for this
endpoint"`). Nachmessen: `tools/telefon-latenz.py` in diesem Repo.
## Keys und Rechte
- Client-Token: `keys.json` (Token → Name; im LXC
`/srv/llmrouter/data/keys.json`, in der Docker-Demo `./data/keys.json`). Die
Werte der Personas liegen in OpenBao `agents/<name>/litellm` Feld `key`; es
sind **dieselben Token wie bei LiteLLM**. Noch ohne Token im Router:
`inka-mx`, `nightjob`, `lab-xo`, `team-copilot` (LiteLLM kennt nur Hashes).
Anlegen:
`KEYS_FILE=keys.json bun run src/keys.ts add <name> [--token <bestehend>]`.
- Rechte: `key-acls.json` (Name → erlaubte Namen, `*` als Präfix-Muster),
versioniert in diesem Repo. Elton und Andreas dürfen `openrouter/*` und
`alt/*`, die Agenten nur ihre Rollen und `:medium`.
- Upstream-Keys in `.env` (LXC: `/srv/llmrouter/.env`): `OPENROUTER_API_KEY`,
`MISTRAL_API_KEY`, `KIRA_REASONING_KEY`, `KIRA_STT_KEY`, `KIRA_TTS_KEY`,
`ARTIFICIALANALYSIS_API_KEY`, `ADMIN_KEY`. Quellen in OpenBao:
`agents/jessie/litellm` (OpenRouter, AA, Kira) und `agents/jessie/llmrouter`
(`admin_key`).
- OpenRouter-Konto: **ZDR-Guardrail bleibt bewusst** (Andreas 05.09.2026).
Alibaba- und Meta-gehostete Modelle sind dadurch nicht wählbar; der Router
baut nur auf der Kontoliste auf und kann nichts wählen, was dagegen verstößt.
## Log und Kosten
Je Anfrage eine Zeile: Key, angefragter Name, aufgelöster Alias, tatsächliches
Modell, Effort, Tokens, **Kosten wie von OpenRouter gemeldet**. Lokale Ziele
haben keine Kosten.
```bash
# Tagesstatistik je Key
curl -s "http://llm.lan:4010/stats?days=7" \
-H "Authorization: Bearer $(~/.agents/bin/secret get jessie llmrouter admin_key)" | python3 -m json.tool
# aktuelle Zuordnung aller Stufen und alt/-Referenzen
curl -s http://llm.lan:4010/zuordnung -H "Authorization: Bearer <ADMIN_KEY>" | python3 -m json.tool
# einzelne Anfragen / Modellwechsel / Journal
ssh s18p1.lan 'pct exec 185020 -- tail -20 /srv/llmrouter/data/log/requests.jsonl'
ssh s18p1.lan 'pct exec 185020 -- cat /srv/llmrouter/data/log/changes.log'
ssh s18p1.lan 'pct exec 185020 -- podman logs -l -n 50'
```
Die Antwort trägt `x-llmrouter-model`, damit ein Client sieht, was wirklich
lief. Weitere Log-Wege (SQLite, Stats-Binary): [referenz.md](referenz.md).
## Wartung und Deploy
Quelle dieses Projekts ist dieses Repo (`spine/llmrouter`). Der Prod-Einsatz
läuft seit dem **2026-09-09 als OCI-Container** im LXC (podman-compose hinter
der Unit `llmrouter`, Bild `git.42i.org/spine/llmrouter:latest`, Laufzeitdaten
unter `/srv/llmrouter/data/`). **Der alte Binary-Weg ist eingestellt**
Server-Updates nur noch über das CI-Image; die Sidecar-Binaries
(`resolve`, `stats`, `keys`) werden nicht mehr gebaut, dafür läuft das
Werkzeug aus dem Image (siehe Schritt 3).
1. Änderung in diesem Repo (Config, Code), committen, pushen. CI baut und
published danach `git.42i.org/spine/llmrouter:latest`.
2. Im LXC Bild ziehen und Unit neu starten:
```sh
ssh s18p1.lan 'pct exec 185020 -- podman pull git.42i.org/spine/llmrouter:latest'
ssh s18p1.lan 'pct exec 185020 -- systemctl restart llmrouter'
```
Nach Config-Änderungen an Stufen oder `alt/`: `rm /srv/llmrouter/data/log/state.json`
vor dem Neustart, sonst bleibt die alte Zuordnung. Der Erstlauf fährt für jede
Stufe die Last-Probe (≈210k Token je Stufe).
3. Sidecar-Werkzeuge ohne Binary — Beispiel Tagesstatistik:
```sh
ssh s18p1.lan "pct exec 185020 -- podman run --rm --env-file /srv/llmrouter/.env \
-e LLMROUTER_CONFIG=/data/config.json -e KEYS_FILE=/data/keys.json \
-v /srv/llmrouter/data:/data git.42i.org/spine/llmrouter:latest bun src/stats.ts 7"
```
(analog `src/keys.ts`, `src/resolver.ts`)
4. Rollback auf die Binary (Notfall): `podman-compose down` im
`/srv/llmrouter`, dann die gesicherte Unit zurück
(`cp /etc/systemd/system/llmrouter.service.binary-bak /etc/systemd/system/llmrouter.service`,
`systemctl daemon-reload`, `systemctl enable --now llmrouter`) — die flachen
Dateien und `log/` liegen unverändert daneben.
5. **Caddyfile-Falle:** die Datei ist als Bind-Mount im Container; ein `mv`
tauscht den Inode und `caddy reload` sieht die alte Datei. Nach Änderung
`oci restart caddy`. Quelle: [caddy/Caddyfile](caddy/Caddyfile).
### Umstieg aufs Image (geprüft am 2026-09-08, vollzogen am 2026-09-09)
Voraussetzungen alle erfüllt: podman 5.4.2 im LXC (kein Docker nötig —
Muster wie `caddy.service`: podman-compose + systemd-oneshot), das Image ist
öffentlich pullbar (anonym, wie `42i/caddy`), Multi-Arch enthält amd64.
Layout wandert vorab ins Hausmuster: alles Laufzeitige unter `./data`
(siehe [data/README.md](../data/README.md)), der Compose-Dienst mountet
`./data:/data`. Einmalig migrieren — **Kopie, nicht Verschieben**, damit die
Binary-Unit bis zum Umschalten lauffähig bleibt:
```sh
cd /srv/llmrouter
mkdir data
cp config.json key-acls.json keys.json model-blocklist.json data/
cp -a log data/log # state.json + aa-cache.json wandern mit
```
`cp -a log` ist der entscheidende Schritt: `state.json` (Zuordnung) und
`aa-cache.json` (AA-Index-Cache) liegen im logDir. Mit ihnen startet der
Container sofort mit frischer Zuordnung; ohne sie gibt es eine Startlücke
(~40 s bei `LLMROUTER_LASTTEST=0`, sonst ≈210k Token), in der Stufen mit 503
antworten („noch nicht aufgelöst").
```yaml
services:
llmrouter:
image: git.42i.org/spine/llmrouter:latest
env_file: [.env]
environment:
# .env trägt die Host-Pfade — im Container auf /data übersteuern:
LLMROUTER_CONFIG: /data/config.json
LLMROUTER_ACLS: /data/key-acls.json
KEYS_FILE: /data/keys.json
MODEL_BLOCKLIST: /data/model-blocklist.json
ports: ["127.0.0.1:4010:4010"] # Caddy zeigt weiter auf localhost:4010
volumes: ["./data:/data"]
restart: unless-stopped
```
- `logDir` steht in `config.json` auf `./log` — relativ zur Config
aufgelöst, im Container also `/data/log`, ohne Config-Änderung.
- Sidecar-Binaries (`llmrouter-resolve`, `-stats`, `-keys`) bleiben auf dem
LXC; nach dem Umstieg mit den data/-Pfaden aufrufen
(`KEYS_FILE=/srv/llmrouter/data/keys.json` …). Die alten flachen Dateien
(`config.json`, `keys.json`, die `.bak`-Kopien) können erst aufgeräumt
werden, wenn der Umstieg hält.
- Ablauf: compose-Datei + Unit (Muster caddy.service) anlegen →
`podman-compose up -d` → `/health` prüfen (Zeitstempel aktuell) →
`systemctl stop/disable llmrouter`. Rollback: Binary-Unit wieder
aktivieren, Container stoppen.
- Updates danach: `podman pull` + Container neu starten; CI published
`:latest` (und `:sha-<sha>`) bei jedem Push auf main (paths-Filter —
Doku-Commits bauen nicht).
Prüfstand 2026-09-08 (Probelauf auf dem LXC, Produktion unberührt, Port 4011,
Log-Dir nach /tmp ausgelagert): Image startet mit der Produktions-Config (25
Aliasse, 21 Rollen, 12 Token), `/models` **byte-identisch** zur Binary, Chat
via `/chat/completions` 200 über beide Wege (`42i/marvin-2609:medium` →
`z-ai/glm-5.3-flash`), komplette Zuordnungs-Auffrischung im Container
funktioniert. Der Mount ist hostseitig — `./data:/data` ergibt dem Container
gegenüber denselben `/data`-Inhalt wie der getestete Mount. Container und
Image danach rückstandsfrei entfernt.
## Störung
| Symptom | Prüfen | Tun |
|---|---|---|
| Agenten bekommen 502/Timeout | `curl http://llm.lan:4010/health`; `podman ps` im LXC | `systemctl restart llmrouter`; `podman logs -l` |
| `… ist noch nicht aufgelöst (503)` | `/zuordnung` leer oder alt | `POST /auffrischen` mit Admin-Key, oder `rm /srv/llmrouter/data/log/state.json` + Neustart; braucht OpenRouter- und AA-Zugang |
| 401 für einen Agenten | `podman logs -l` zeigt `401 … (Key n Zeichen)` | Token fehlt in `data/keys.json` → mit `keys.ts add --token` nachtragen |
| 403 `darf … nicht rufen` | `key-acls.json` | Recht ergänzen (versioniert in diesem Repo), Neustart |
| Zahlen im TTS falsch gesprochen | Sprechtext-Regel greift nur für PLZ/Nummern | Fish über OpenRouter zerlegt deutsche Beträge/Daten — deshalb Grok Voice bis Fish bei Uwe läuft (live/live#2247) |
| `llm.lan` antwortet mit LiteLLM-Fehlern statt Router | Caddy zeigt auf 4000 | Caddyfile prüfen, `oci restart caddy` |
**Rückfall ohne Router:** Caddyfile auf `localhost:4000` stellen und Caddy neu
starten — LiteLLM bedient die `2608`-Namen weiter, die `2609`-, `alt/`- und
`role/`-Namen fehlen dann.
## Zugehörige Werkzeuge
- `~/src/tools/openrouter-models.ts` (git.home `andreas/tools`): zeigt die
Stufentabelle oder die Alternative zu einer Modell-ID (`bun run ./openrouter-models.ts anthropic/claude-opus-5:medium`).
- `~/.agents/bin/opencode-normalize`: erzeugt den `llmlan`-Provider in der
opencode-Config aus `/v1/models` des Routers, Effort-Stufen als `variants`.
- `claude-lan` (Funktion in `~/.zshrc`): Claude Code im Terminal über den
Router; die Desktop-App kann das nicht (sie erzwingt `api.anthropic.com`).
- Auswahl-Regel: [src/openrouter-auswahl.ts](../src/openrouter-auswahl.ts) —
seit 2026-09-08 eine eigenständige Kopie (vendored aus `42i/agents`
`llmlite/`, wo die llmlite-Fassung für den Tier-Updater weiterlebt).
## Verlauf
- 2026-09-09: **Output-Gate je Profil senkbar, neue Rolle `coder`.** Das
64K-Output-Gate (MIN_OUTPUT) trieb `auto/mass:fast` auf `solar-pro4`
(0,42 $/1M), weil ling mit 32768 Output-Token durchfiel — gemessen 09.09.:
1.415 Masse-Requests, 1.146 davon unter 100 Completion-Token; das Gate
verfehlte dort seinen Zweck und kostete ~35 % Aufschlag. Jetzt ist
`minOutput` pro Profil in `config.json` setzbar (Default bleibt 65536),
`auto/mass:fast` läuft mit 32768. Dazu `role/coder` →
`auto/coding:junior` neben `engineer`, ACL für `persona-spark`, der
engineer-Agent in der Mac-opencode.json von `role/worker` auf
`role/coder` umgezogen. Deploy über CI-Image (Run #8) + `podman pull` +
Neustart, Configs nach `data/` gesynct, `state.json` gelöscht und frisch
aufgelöst: `auto/mass:fast` → ling-3.0-flash wiederhergestellt (0,27
$/1M, Referenz trägt `Output>=32768`), `role/coder` in `/v1/models`, Chat
je Rolle 200. Seit dem Image-Betrieb antwortet Port 4010 nur auf
`127.0.0.1` des LXC — von außen nur noch über Caddy (443/4000).
- 2026-09-09: **Umstieg aufs Image vollzogen.** Prod läuft als OCI-Container
(`llmrouter_llmrouter_1`, podman-compose hinter der Unit `llmrouter`, Bild
`:latest` von Run #7/e144034). Laufzeitdaten wanderten nach
`/srv/llmrouter/data/` (Hausmuster `./data:/data`), `state.json` und
`aa-cache.json` mitkopiert — Zuordnung unverändert, keine Auffrischung.
Verifiziert: `/health` lokal und über `https://llm.lan` 200, Chat 200.
**Der Binary-Deploy-Weg (bun build, scp, pct push) ist eingestellt**;
Updates nur noch über CI-Image + `podman pull` + Neustart. Unit-Rollback
liegt als `llmrouter.service.binary-bak`.
- 2026-09-08: **Umstieg aufs Image geprüft.** Probelauf des CI-Images auf
dem LXC neben dem Binary (Port 4011): Konfig-/ACL-/Keys-Parität
(`/models` byte-identisch), Chat 200 über beide Wege, Zuordnungs-Refresh
läuft im Container. Rezept und Fallstricke stehen jetzt in
[Wartung und Deploy](#wartung-und-deploy); Prod läuft weiter auf der
Binary. Dazu Registry-Login-Fix in der CI (vertauschte Secret-Werte,
401er am Vormittag) und Login-Kommentar korrigiert.
- 2026-09-08: **Umzug nach `spine/llmrouter`** (git.42i.org). Das komplette
Projekt verließ das Repo `42i/agents` (Historie per subtree split bewahrt);
dazu Docker-Image, Compose-Demo mit `./data`-Mount und Gitea-Actions-CI
nach dem live-Muster. Die Referenz-Doku liegt im Repo (`README.md`,
`docs/`), die Betriebsstand-Seite in `info` bleibt als Zeiger.
- 2026-09-08: **Ausweich-Kette für die Massenarbeit** (42i/agents 4e92f17,
42i/intern#1252). Anlass: ling-3.0-flash fiel am Vorabend aus (23:2023:52,
5× 502 nach ~270 s hängendem Upstream, 117× 429). `auto/mass:fast` hat jetzt
selbst einen Ausweich auf `auto/coding:junior`, und der Ausweich greift auch
bei 429/5xx-Antworten — vorher nur bei fetch-Fehlern und nur für fest-Ziele.
ACLs der Container-Personas um die Kettenziele ergänzt.
- 2026-09-08: **Live-Probe reasoning-tolerant** (42i/agents 312e43c). ling
denkt vor jedem Werkzeugaufruf mit variabler Länge; bei `max_tokens: 400`
scheiterte die Probe willkürlich und kippte `marvin-2609:low` und
`alt/claude-haiku-4.5` auf teurere Modelle (solar-pro4 bzw. glm-5.3-flash),
ohne dass ling defekt war. Budget auf 2000 Token, Timeout auf 90 s.
- 2026-09-07: **LiteLLM abgeschaltet, alles läuft über Rollen.** Alle Agenten
(Container spark/buzz/jessie/klara, die Leiter auf spark-desktop, der XO) rufen
`role/*` statt eines Modellnamens; der XO und das Logbuch kamen dabei vom
Claude-Max-Abo auf opencode. Der `fallback` an LiteLLM ist aus der Config
entfernt — unbekannte Namen sind jetzt 404 statt einer stillen Weiterleitung.
Keine Rolle steht mehr auf der teuren Stufe (42i/intern#1252).
- 2026-09-07: **Caddy aus dem LiteLLM-Stack gelöst** und eigener oci-Dienst
(`/srv/caddy`, Quelle `llmrouter/caddy/`, ausgerollt über
`agents-pull`). Vorher hing er in `llmlite/compose.yml` — ein `podman-compose
down` für LiteLLM hätte die TLS-Terminierung von `llm.lan` mitgenommen und den
Router von außen unerreichbar gemacht, obwohl er läuft. Vorarbeit zur
Abschaltung von LiteLLM (42i/intern#1252).
- 2026-09-05: Router gebaut und in Betrieb, `llm.lan` umgestellt; Stufen
25/45/49/53; Namensräume `openrouter/`, `alt/`, `role/`, `deizo/`; Claude-Code-Pfad.
- 2026-09-06: Sprechtext-Normalisierung (Postleitzahl); TTS-Turbo auf Grok
Voice `ara`, STT-Turbo auf Voxtral Small 24B nach Hör- und Telefontest;
Auftrag an Uwe für Fish S2 Pro und Voxtral Small im Prod-RZ (live/live#2247).