c13cfd2bd0
Duenner, zustandsloser Proxy (nur stdlib) zwischen Aider und llama-swap :8080. Schiebt an einer Token-Schwelle "SAVEPOINT.md finalisieren + stoppen" ein (weich) bzw. antwortet oberhalb eines optionalen Hart-Deckels selbst. Beweist Session- Hygiene per hartem Schnitt statt Auto-Compaction -- ohne eine Zeile Lucy-Code. Alle 4 Akzeptanzkriterien gruen: feuert im Log; SAVEPOINT.md gepflegt; ehrlicher Grenz-Savepoint am ersten Feuern; frische Sitzung liest Savepoint -> baut Tests, 9 unittest gruen, keine Fassade. CPT 3.5 kalibriert (est ~= echte prompt_tokens). Hart-Deckel nachgeruestet (weicher Schnitt allein erzeugt Fassade bei Weiterarbeit ueber die Grenze). Streaming-read1-Fix + 4 kleinere aus adversarialer Review. Laeuft deployt auf der Box unter ~/governor-p0/ (gov-ctl.sh start <soft> <hart>). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
7.8 KiB
Markdown
125 lines
7.8 KiB
Markdown
# Governor — Phase 0
|
||
|
||
Dünner, zustandsloser Token-Wächter-Proxy zwischen einem Off-the-shelf-Coding-Agenten
|
||
(**Aider**) und dem lokalen Modell-Endpoint (llama-swap `:8080`). Er erzwingt
|
||
**Session-Hygiene per hartem Schnitt statt Auto-Compaction**: wenn die Anfrage (= ganze
|
||
Sitzungshistorie, die jede Runde mitkommt) eine Schwelle übersteigt, schiebt er eine
|
||
Anweisung ein, `SAVEPOINT.md` zu finalisieren und zu stoppen — damit Wissen in
|
||
`SAVEPOINT.md` + git lebt, nicht im degradierenden Chat-Kontext.
|
||
|
||
Das ist **Phase 0** des Ablöse-Plans „Lucy IDE-Modus + Governor": den Kern beweisen,
|
||
**ohne** eine Zeile Lucy-Code. Kein bespoke Editor, kein Aider-Fork, kein Modelltausch.
|
||
|
||
## Bausteine (dieses Verzeichnis)
|
||
| Datei | Zweck |
|
||
|---|---|
|
||
| `governor.py` | Der Proxy. Nur Standardbibliothek (läuft mit System-`python3`), zustandslos. |
|
||
| `CONVENTIONS.md` | Aiders schreibgeschützte Arbeitsregeln: SAVEPOINT.md zuerst lesen, laufend pflegen, keine Fassade. |
|
||
| `SAVEPOINT.template.md` | Anfangs-Savepoint für ein frisches Projekt. |
|
||
| `driver.py` | Treibt eine akkumulierende Aider-Sitzung über die Scripting-API (eine Zeile = eine Runde). |
|
||
| `gov-ctl.sh` | Governor sauber starten/stoppen/status (detached via `setsid`). |
|
||
| `run-driver.sh` | `run-driver.sh <msgs> [repo]` — Aider-Sitzung durch den Governor. |
|
||
| `reset-repo.sh` | Wegwerf-Test-Repo frisch aufsetzen. |
|
||
| `hardstop-test.sh` | Direkte curls für die drei Pfade (passthrough / soft / hart). |
|
||
| `msgs-*.txt` | Nachrichtenskripte für die Testläufe. |
|
||
|
||
## Verhalten des Governors
|
||
Pro `/v1/chat/completions`-Anfrage schätzt er die Tokenzahl (`Zeichen / GOV_CHARS_PER_TOKEN`,
|
||
kalibriert auf CPT **3.5** → `est ≈ echte prompt_tokens` auf ~1–3 % bei echten Sessions):
|
||
|
||
- **est < Soft** → unverändert durchreichen.
|
||
- **est ≥ Soft (`GOV_THRESHOLD`, Default 25000)** → hängt die SAVEPOINT-Stopp-Anweisung als
|
||
letzte User-Nachricht an, leitet weiter, loggt `FIRED`. Das Modell schreibt EINEN
|
||
ehrlichen Abschluss-Savepoint.
|
||
- **est ≥ Hart (`GOV_HARD_CEILING`, Default 0 = aus)** → der Governor antwortet SELBST mit
|
||
einer kurzen Stopp-Nachricht, **ohne** das Modell zu fragen; loggt `HARDSTOP`. Verhindert,
|
||
dass über die Grenze hinaus weitergearbeitet wird (siehe Befund unten).
|
||
|
||
Alle anderen Pfade (`/v1/models` etc.) werden roh durchgereicht. Streaming (SSE) wird
|
||
byteweise durchgereicht; die echten `prompt_tokens` aus der Antwort werden zur Kalibrierung
|
||
mitgeloggt.
|
||
|
||
### Umgebungsvariablen
|
||
`GOV_PORT` (8100) · `GOV_HOST` (0.0.0.0) · `GOV_UPSTREAM` (http://127.0.0.1:8080) ·
|
||
`GOV_THRESHOLD` (25000) · `GOV_HARD_CEILING` (0=aus) · `GOV_CHARS_PER_TOKEN` (3.5) ·
|
||
`GOV_LOG` · `GOV_DIRECTIVE` · `GOV_HARDSTOP_MSG`.
|
||
|
||
## Auf der Box laufen lassen (wie in P0 aufgesetzt)
|
||
```bash
|
||
# Aider (einmalig, unter isoliertem Python 3.12 — System-Python 3.14 bricht Aiders Pins):
|
||
pipx install uv && uv tool install --python 3.12 aider-chat
|
||
|
||
# Dateien liegen in ~/governor-p0/. Governor starten (Soft 25k, Hart aus):
|
||
~/governor-p0/gov-ctl.sh start 25000 0 # oder: start 25000 30000 (Hart an)
|
||
|
||
# Aider-Sitzung durch den Governor:
|
||
~/governor-p0/run-driver.sh ~/governor-p0/msgs-todo.txt
|
||
```
|
||
Aider zeigt mit `OPENAI_API_BASE=http://127.0.0.1:8100/v1` und Modell
|
||
`openai/Qwen3-Coder-Next` auf den Governor.
|
||
|
||
## Wichtig: Aiders eigene Zusammenfassung MUSS aus
|
||
Der Treiber setzt `coder.summarizer.max_tokens` auf ~1e9. Sonst fasst Aider die Historie
|
||
selbst zusammen (Auto-Compaction) und die Anfrage wächst nie bis zur Schwelle — der
|
||
Governor wäre ausgehebelt, und man bekäme genau die über-komprimierte Halluzination, die
|
||
der Plan verwirft. Der Governor soll die **alleinige** Sitzungsgrenze sein.
|
||
|
||
## Ergebnisse & Befunde (24.07.2026)
|
||
|
||
### Akzeptanz — alle vier Kriterien bewiesen (Box, Qwen3-Coder-Next)
|
||
1. **Governor zählt + feuert an der Schwelle** — 12-Runden-Todo-Lauf (Soft 8000): Runden 1-6
|
||
`ok`, ab Runde 7 `FIRED` (est 8546 / echt 8652). Hart-Deckel: Anfrage mit est 11429 ≥ 10000
|
||
→ `HARDSTOP`, Governor antwortet selbst (kein Modell-Call). Alles im Log.
|
||
2. **Aider pflegt SAVEPOINT.md** — über den ganzen Aufbau hinweg strukturiert gehalten
|
||
(Ziel/Erledigt/Nächster Schritt/Stolpersteine/Dateien) gemäß `CONVENTIONS.md`.
|
||
3. **An der Grenze: ehrlicher Abschluss + Stopp** — beim ersten Feuern (Runde 7) schrieb das
|
||
Modell einen **ehrlichen** Savepoint (nur real Gebautes unter „Erledigt", Tests als nächster
|
||
Schritt) und verweigerte neuen Code.
|
||
4. **Frische Sitzung macht sauber weiter — keine Fassade** — neue Aider-Sitzung las den
|
||
Grenz-Savepoint, baute `test_todo.py` (der exakte nächste Schritt), und **alle 9 unittest-
|
||
Tests laufen grün** gegen die echte API. Kein Erfinden.
|
||
|
||
### Kalibrierung
|
||
`CHARS_PER_TOKEN = 3.5` → `est` traf die echten `prompt_tokens` bei realen Sessions auf ~1-3 %.
|
||
(Nur künstlicher, extrem repetitiver Fülltext bricht die Heuristik — irrelevant für echten Code.)
|
||
Der Coder läuft mit 128k Kontext (`-c 131072`), also keine Modell-Kappung bei 25k.
|
||
|
||
### Wichtigster Befund: Soft reicht nicht allein → Hart-Deckel nachgerüstet
|
||
Der **weiche** Schnitt erzeugt genau EINEN ehrlichen Grenz-Savepoint — solange die Grenze
|
||
respektiert wird. Schickt man aber über die Grenze hinaus weiter Aufträge (wie im Stresstest),
|
||
verweigert das Modell zwar den Code, schreibt die Absichten aber fortschreitend als „erledigt"
|
||
in SAVEPOINT — genährt von Aiders **irreführenden Commit-Nachrichten** (die aus dem SAVEPOINT-
|
||
Absichtstext geschöpft werden). Ergebnis: eine Fassade (behauptete test_todo.py/README, die es
|
||
nicht gab). Deshalb der optionale **Hart-Deckel** (`GOV_HARD_CEILING`): oberhalb davon antwortet
|
||
der Governor selbst, das Modell kann keine degradierenden Savepoints mehr schreiben. **Empfehlung:
|
||
Hart-Deckel aktiv** (z. B. `Soft 25000, Hart 30000` — ein Finalisier-Zug Luft, dann harter Riegel).
|
||
|
||
### Weitere Befunde / Fallen
|
||
- **Aiders eigene Zusammenfassung MUSS aus** (`summarizer.max_tokens` hoch) — sonst compactet
|
||
Aider selbst und der Governor greift nie. Siehe oben.
|
||
- **Commit-Nachrichten überzeichnen** in der Abschluss-Phase (aus SAVEPOINT-Absicht). Der Code
|
||
ist die Wahrheit; git-Nachrichten sind es hier nicht. → Für Phase 1: Auto-Commit nahe/nach dem
|
||
Feuern zügeln oder Savepoint mit ehrlicher fixer Nachricht committen.
|
||
- **Python 3.14 auf der Box bricht Aiders Pins** (numpy 1.24.3) → Aider via `uv` unter isoliertem
|
||
Python 3.12 installiert.
|
||
- **Aider-Scripting-API (`coder.run`) hängt** in einer Datei-Hinzufügen-Reflexion (Edits landeten
|
||
nicht). Für Einzel-Runden `aider --message` nutzen (sauberer, unterstützt). Der `driver.py`
|
||
taugt für Mehr-Runden-Akkumulation (Governor-Test), nicht als Produktions-Treiber.
|
||
|
||
### Bekannte Grenzen des Governors (aus adversarialer Review, für später)
|
||
- **Chunked Request-Bodies ohne `Content-Length`** werden verworfen (Aiders httpx sendet immer
|
||
`Content-Length` → schlummernd, aber ein Proxy-Hop mit Chunking bräche).
|
||
- **Tool-/Function-Calling**: der Soft-Einschub als letzte `user`-Nachricht kann die Nachrichten-
|
||
reihenfolge stören, wenn Aider ein Tool-Calling-Edit-Format nutzt (Aiders diff/whole sind reiner
|
||
Text → schlummernd). `estimate_tokens` zählt `tools`/`tool_calls` nicht mit.
|
||
- **`https://`-Upstream** wird nicht unterstützt (nur `http.client.HTTPConnection`). Für den
|
||
lokalen `:8080`-Endpoint irrelevant.
|
||
Behoben aus derselben Review: **inkrementelles Streaming** (`read1()` statt `read()` — vorher
|
||
puffernd), **Config-Crash** bei `{ }` in `GOV_DIRECTIVE` (sichere Substitution), **Query-String**
|
||
umging die Erkennung, **Socket-Leak** im Fehlerpfad, doppelte `Date`/`Server`-Header.
|
||
|
||
## Nächste Schritte (Phase 1+)
|
||
Terminal in Lucy einbetten (xterm.js + node-pty, MC2-Muster kopieren) → Voice-Hook auf das
|
||
Governor-Signal → Feinschliff + Aider/Pi-Finalentscheid. Governor evtl. später in mc2-gateway.
|
||
Kandidat für Phase 1-Härtung: Auto-Commit-Zügelung + Hart-Deckel als Default.
|