LM-Studio-Matrix mit gemma-4-e4b: zwoelf Laeufe und vier Befunde

Skill 12.0.0 bis 12.2.0 und Adapter 2.0.0 bis 2.2.0.

Werkzeugfreigabe: Die Shell-Rechte des OpenCode-Adapters sind jetzt eine
Denylist wie beim Claude-Adapter statt einer Allowlist. Ausloeser war der
erste Qwen-Lauf, dessen einzige beide Werkzeugaufrufe an einer Pipeline
scheiterten. Kontrolltest belegt beide Richtungen: Get-ChildItem | Format-Table
laeuft durch, rm wird verweigert, die Datei bleibt bestehen.

Lokaler Betrieb: Der Preflight entlaedt alle Modelle vor jedem Lauf und laedt
den Kontext auf das Modellmaximum. Gemessen waren zuvor beide Modelle
gleichzeitig geladen - 168 MiB frei von 16,3 GB. Qwen 27B passt auf dieser
Karte nicht und wurde durch qwen3.5-9b ersetzt, in Q4_K_M wie Gemma.

Messinstrument: analyse-anforderungen.py erkennt Feldnamen in Markdown-
Fettschrift und Modulpraefixe in IDs. Der erste lokale Lauf mit Artefakten
wurde sonst mit null Anforderungen gezaehlt statt mit neun. Regressionsprobe
an vier Claude-Laeufen unveraendert.

Befunde: Der Standard-Ausgabeblock, mit dem Claude sieben Artefakte erzeugt,
liefert bei gemma-4-e4b null von sechs Laeufen ein Ergebnis - das Modell loest
den Pfad relativ zum Arbeitsverzeichnis auf. Ein Lauf startete alle sieben
vorgesehenen Rollen und lieferte neun formkonforme Anforderungen. Ein anderer
erzeugte sieben richtig benannte Dateien ohne eine einzige formkonforme
Anforderung. Und eine Shell-Umleitung schrieb an der Denylist vorbei in den
eingefrorenen Snapshot - gefunden vom Vorher/Nachher-Vergleich, nicht von der
Regel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christoph Schwörer
2026-09-01 11:02:04 +02:00
co-authored by Claude Opus 5
parent 654339464e
commit 28e927013b
218 changed files with 20745 additions and 17 deletions
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Verdichtet die RawResult.json unterhalb eines Verzeichnisses zu einer Tabelle.
Gedacht fuer die Sichtung einer Matrix: Welche Zelle hat gemessen, welche ist
eine Fehlmessung, und woran lag es. Schreibt nichts - reine Auswertung.
python lauf-uebersicht.py "<Verzeichnis>" [--details]
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
def schreibziele(lauf: Path) -> list[str]:
"""Wohin der Lauf zu schreiben versuchte, samt Ergebnis.
Der haeufigste Grund fuer ein leeres Ergebnisverzeichnis ist ein
Schreibversuch an den falschen Ort - der ist ohne diese Aufstellung nicht
zu sehen, weil ``written_files`` dann schlicht leer bleibt.
"""
session = lauf / "_meta" / "opencode-session.json"
if not session.is_file():
return []
try:
daten = json.loads(session.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return []
ziele = []
for nachricht in daten.get("messages", []):
for teil in nachricht.get("parts", []):
if teil.get("type") != "tool" or teil.get("tool") not in ("write", "edit"):
continue
zustand = teil.get("state", {})
ziele.append(
f"{teil.get('tool')} {zustand.get('status')} -> "
f"{zustand.get('input', {}).get('filePath')}"
)
return ziele
def main() -> int:
parser = argparse.ArgumentParser(description="Uebersicht ueber Versuchslaeufe")
parser.add_argument("verzeichnis")
parser.add_argument("--details", action="store_true", help="Schreibziele zeigen")
args = parser.parse_args()
wurzel = Path(args.verzeichnis)
zeilen = []
for datei in sorted(wurzel.rglob("RawResult.json")):
try:
d = json.loads(datei.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
continue
lauf = datei.parent
zeilen.append(
{
"lauf": lauf.name,
"modus": d.get("mode", "?"),
"exit": d.get("exit_code"),
"status": d.get("subtype", "?"),
"min": round(d.get("duration_ms", 0) / 60000, 1),
"turns": d.get("num_turns", 0),
"tools": d.get("tool_call_count", 0),
"sub": d.get("subagent_stats", {}).get("spawned", 0),
"dateien": len(d.get("written_files") or []),
# Eine 0 aus einem laufenden Subagenten ist kein Messwert.
"tokens": (
d["usage"]["total_tokens"]
if d.get("usage_captured", True)
else None
),
"pfad": lauf,
}
)
if not zeilen:
print("Keine RawResult.json gefunden.")
return 1
kopf = f"{'Lauf':44} {'Modus':8} {'St':3} {'Min':>6} {'Turn':>5} {'Tool':>5} {'Sub':>4} {'Dat':>4} {'Tokens':>10}"
print(kopf)
print("-" * len(kopf))
for z in zeilen:
tok = "n. erf." if z["tokens"] is None else f"{z['tokens']:,}"
print(
f"{z['lauf'][:44]:44} {z['modus']:8} {str(z['exit']):3} {z['min']:6.1f} "
f"{z['turns']:5} {z['tools']:5} {z['sub']:4} {z['dateien']:4} {tok:>10}"
)
if args.details:
for ziel in schreibziele(z["pfad"]):
print(f" {ziel}")
gueltig = [z for z in zeilen if z["dateien"] > 0]
print(
f"\n{len(zeilen)} Laeufe, davon {len(gueltig)} mit Ergebnisdateien, "
f"{len(zeilen) - len(gueltig)} Fehlmessungen."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())