fix(brain): project_summary liest volle Projekt-Historie aus chat_backup.jsonl

"Hol dir die Infos aus Projekt X" lieferte fast immer leer: project_summary las
nur das rollende Conversation-Window (~50 Turns ueber alle Projekte), aeltere
Projekt-Chats sind da rausdistilliert. Jetzt liest _read_project_history die
echte volle Historie aus /shared/config/chat_backup.jsonl (im Brain gemountet),
letzte ~20 Turns des Zielprojekts, Standort-Hints gefiltert, Fallback aufs
Window. Tool-Beschreibung geschaerft. Live gegen echte Daten getestet
(basic_os 20 / vdi 20 / mac_os_update_fehler 6 Turns).

Kein APK-Rebuild noetig (nur Brain).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 22:55:54 +02:00
co-authored by Claude Opus 4.8
parent dc775ee34f
commit a20e57e33a
2 changed files with 76 additions and 15 deletions
+64 -15
View File
@@ -1045,12 +1045,15 @@ META_TOOLS = [
"function": {
"name": "project_summary",
"description": (
"Schau in einen ANDEREN Chat rein und fass zusammen was dort zuletzt "
"passiert ist (letzte ~12 Turns). Funktioniert fuer jedes Projekt (per "
"Name, Fuzzy-Match) UND fuer den Hauptchat (name='Hauptchat'). Nutze es "
"IMMER wenn Stefan sagt 'hol dir die Infos aus Projekt X', 'schau mal in "
"den Hauptchat/in Projekt Y rein', 'was war zuletzt bei ...', 'hol mich "
"ab' — sonst halluzinierst Du Inhalte die nicht da sind."
"Liest die ECHTE gespeicherte Chat-Historie eines ANDEREN Projekts "
"(letzte ~20 Turns aus dem persistierten Verlauf — nicht nur Deinem "
"aktuellen Kontextfenster) und fasst sie zusammen. Funktioniert fuer "
"JEDES Projekt (per Name, Fuzzy-Match) UND fuer den Hauptchat "
"(name='Hauptchat'). Rufe es DIREKT auf, sobald Stefan sagt 'hol dir "
"die Infos aus Projekt X', 'schau mal in Projekt Y / den Hauptchat "
"rein', 'was war zuletzt bei ...', 'hol mich ab' — antworte NICHT aus "
"dem Gedaechtnis, sonst halluzinierst Du. Der Verlauf des Projekts steht "
"sicher zur Verfuegung, auch wenn Ihr da lange nicht wart."
),
"parameters": {
"type": "object",
@@ -1895,6 +1898,46 @@ class Agent:
# ── Tool-Dispatcher ───────────────────────────────────────
def _read_project_history(self, project_id: str, limit: int = 20) -> list:
"""Liest die VOLLE Chat-Historie eines Projekts aus dem geteilten
chat_backup.jsonl (/shared/config, auch im Brain gemountet).
Noetig, weil das rollende Conversation-Window nur die letzten ~50 Turns
ueber ALLE Projekte haelt — aeltere/andere Projekt-Chats sind da laengst
rausdistilliert. Gibt die letzten `limit` (role, text)-Paare zurueck,
gefiltert auf project_id ('' = Hauptthread). Fuehrende Hint-Bloecke
([Standort: ...]) in User-Turns werden entfernt."""
path = "/shared/config/chat_backup.jsonl"
out: list = []
try:
if not os.path.exists(path):
return []
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
o = json.loads(line)
except Exception:
continue
role = o.get("role")
text = o.get("text")
if not role or text is None:
continue
# nur echte Gespraechs-Turns (keine skill/memory/system-Eintraege)
if role not in ("user", "assistant", "aria"):
continue
if (o.get("project_id") or "") != (project_id or ""):
continue
if role == "user":
text = _strip_leading_hint_blocks(text)
out.append((role, text))
except Exception as exc:
logger.warning("project_summary: chat_backup lesen fehlgeschlagen: %s", exc)
return []
return out[-limit:]
def _dispatch_tool(self, name: str, arguments: dict, project_id: str = "") -> str:
"""Fuehrt einen Tool-Call aus und gibt ein kurzes Text-Resultat zurueck.
Niemals werfen — Fehler werden als Text-Resultat reportet damit Claude
@@ -2568,23 +2611,29 @@ class Agent:
# aus einem Projekt heraus in den Hauptthread reinschauen kann.
if pname.lower() in {"hauptchat", "hauptthread", "haupt", "main",
"mainchat", "haupt-chat", "hauptchat-thread"}:
turns = [t for t in self.conversation.turns if not t.project_id]
target_pid = ""
label, desc = "Hauptchat", "der Hauptthread (kein Projekt)"
else:
p = projects_mod.find_project(pname)
if not p:
return f"Kein Chat/Projekt '{pname}' gefunden (fuer den Hauptthread: name='Hauptchat')."
turns = [t for t in self.conversation.turns if t.project_id == p["id"]]
target_pid = p["id"]
label, desc = p["name"], p.get("description", "(keine Beschreibung)")
# VOLLE Historie aus chat_backup.jsonl (nicht nur das rollende
# Window — sonst fehlen aeltere/andere Projekt-Chats komplett).
turns = self._read_project_history(target_pid, limit=20)
if not turns:
return (f"'{label}' hat im aktuellen Conversation-Window noch keine "
f"Turns. {desc}")
tail = turns[-12:]
# Fallback aufs In-Memory-Window, falls chat_backup fehlt/leer.
turns = [(t.role, t.content) for t in self.conversation.turns
if (t.project_id or "") == target_pid]
if not turns:
return (f"'{label}' hat noch keine Chat-Historie. {desc}")
tail = turns[-20:]
summary_lines = []
for t in tail:
prefix = "Stefan" if t.role == "user" else "Du"
summary_lines.append(f"{prefix}: {t.content[:280]}")
preamble = f"'{label}'{desc}.\nLetzte {len(tail)} Turns:\n"
for role, text in tail:
prefix = "Stefan" if role == "user" else "Du"
summary_lines.append(f"{prefix}: {(text or '')[:280]}")
preamble = f"'{label}'{desc}.\nLetzte {len(tail)} Turns aus der Historie:\n"
return preamble + "\n".join(summary_lines)
if name == "project_end":
pname = (arguments.get("name") or "").strip()