feat(brain): Skill-Bypass-Detection + Bypass-Lehre als pinned Memory
Variante 3+ (Lerneffekt-Variante): Variante C scaffolded zwar Skills auto,
aber ARIA lernt nicht — sie wird beim naechsten Mal trotzdem zu Bash
greifen. Stefans Punkt: Lernen geht nur ueber Brain-Memory.
Mechanik:
1. api_heuristic.detect_recent_bypass(skills, since_sec=600):
schaut letzte 10 Min im agent_stream.jsonl, findet Bash-curl gegen
Hosts fuer die bereits ein matching Skill existiert. Returnt
{host, skill_name, count, last_ts}.
2. api_heuristic.build_bypass_section(events):
Drastischer Markdown-Block "## 🚨 SKILL-BYPASS ERKANNT" mit konkretem
run_<skill>-Hint pro betroffenem Host. Landet direkt im System-Prompt
noch VOR dem normalen API-Heuristik-Block.
3. agent.py._upsert_bypass_lesson(ev):
Schreibt eine pinned type=rule Memory mit source=auto-feedback und
migration_key=auto/skill-bypass/<skill_name>. Idempotent: bei
Wiederholung wird die alte Memory ueberschrieben (Counter aktualisiert),
keine Karteileichen. Content nennt konkret den run-Tool-Namen und
Performance-Vergleich (3s Tool-Call vs 13-20s Bash-Wrapper).
Diese Memory ist permanent pinned → kommt bei jedem Chat-Turn,
cross-session, cross-restart als Hot-Memory durch. Damit lernt ARIA
es im wortlichen Sinne, nicht nur Reibung in der aktuellen Konversation.
Idempotenz wichtig: bei jedem Bypass-Detection-Lauf wird die Memory
upgedatet (nicht dupliziert). Stefan kann sie via Diagnostic-Gehirn-Tab
loeschen falls sie nervt.
Stefan-Frage beantwortet: 'sie wuerde es aber nur lernen wenn sie es
auch im gehirn speichert oder?' — exakt. Schimpfen im Prompt ist
Reibung dieser Session, pinned Memory ist permanenter Lerneffekt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -829,6 +829,65 @@ class Agent:
|
||||
self._pending_events = []
|
||||
return events
|
||||
|
||||
def _upsert_bypass_lesson(self, ev: dict) -> None:
|
||||
"""Speichert die Lehre aus einem Skill-Bypass als pinned Memory.
|
||||
Idempotent ueber migration_key — bei Wiederholung wird der vorhandene
|
||||
Punkt aktualisiert (Counter hoeher). So lernt ARIA cross-session,
|
||||
nicht nur in der aktuellen Konversation."""
|
||||
from datetime import datetime, timezone
|
||||
import uuid as _uuid
|
||||
from memory.vector_store import COLLECTION
|
||||
from qdrant_client.http import models as _qm
|
||||
|
||||
skill_name = ev["skill_name"]
|
||||
host = ev["host"]
|
||||
count = ev["count"]
|
||||
migration_key = f"auto/skill-bypass/{skill_name}"
|
||||
title = f"Skill '{skill_name}' nutzen, nicht curl"
|
||||
run_tool = f"run_{skill_name.replace('-', '_')}"
|
||||
content = (
|
||||
f"WICHTIG fuer Performance + Stefans Wartezeit: "
|
||||
f"Skill '{skill_name}' existiert und deckt {host} ab. "
|
||||
f"Nutze `{run_tool}(...)` als Brain-Tool, NICHT Bash-curl gegen {host}. "
|
||||
f"Brain hat {count}× erkannt dass dieser Skill umgangen wurde "
|
||||
f"(letzter Vorfall: heute). Ein Skill-Aufruf = 1 Tool-Call (~3s) "
|
||||
f"vs. Bash-Wrapper = 3-5 Tool-Calls (~13-20s)."
|
||||
)
|
||||
|
||||
# Alte Version mit gleicher migration_key entfernen (Counter-Update)
|
||||
try:
|
||||
self.store.client.delete(
|
||||
collection_name=COLLECTION,
|
||||
points_selector=_qm.FilterSelector(filter=_qm.Filter(must=[
|
||||
_qm.FieldCondition(key="migration_key",
|
||||
match=_qm.MatchValue(value=migration_key))
|
||||
])),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
vec = self.embedder.embed(content)
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
payload = {
|
||||
"type": "rule",
|
||||
"title": title,
|
||||
"content": content,
|
||||
"pinned": True,
|
||||
"category": "skills",
|
||||
"source": "auto-feedback",
|
||||
"tags": [],
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"migration_key": migration_key,
|
||||
"attachments": [],
|
||||
}
|
||||
self.store.client.upsert(
|
||||
collection_name=COLLECTION,
|
||||
points=[_qm.PointStruct(id=str(_uuid.uuid4()), vector=vec, payload=payload)],
|
||||
)
|
||||
logger.info("bypass-lesson upserted: skill=%s host=%s count=%d",
|
||||
skill_name, host, count)
|
||||
|
||||
# ── Hauptpfad: ein User-Turn → Tool-Loop → finaler Reply ──
|
||||
|
||||
MAX_TOOL_ITERATIONS = 8 # Schutz vor Endlos-Loops
|
||||
@@ -936,6 +995,27 @@ class Agent:
|
||||
hints = _ah.compute_hints(existing_skills=all_skills, force=True)
|
||||
|
||||
api_heuristic_section = _ah.build_section(hints)
|
||||
|
||||
# BYPASS-DETECTION (Variante 3 / Lerneffekt):
|
||||
# Hat ARIA in den letzten ~10min Bash-curl gegen einen Host
|
||||
# gemacht OBWOHL der Skill existiert? → drastischer Hint im
|
||||
# Prompt JETZT + pinned Memory speichern, damit's beim
|
||||
# naechsten Turn / naechster Session weiter sichtbar ist
|
||||
# ("echtes Lernen via Brain-Memory").
|
||||
bypass_events = _ah.detect_recent_bypass(all_skills, since_sec=600)
|
||||
if bypass_events:
|
||||
bypass_section = _ah.build_bypass_section(bypass_events)
|
||||
if bypass_section:
|
||||
api_heuristic_section = (
|
||||
(bypass_section + "\n\n" + api_heuristic_section)
|
||||
if api_heuristic_section else bypass_section
|
||||
)
|
||||
# Pinned-Memory pro Skill speichern, idempotent ueber migration_key
|
||||
for ev in bypass_events:
|
||||
try:
|
||||
self._upsert_bypass_lesson(ev)
|
||||
except Exception as exc:
|
||||
logger.warning("bypass-lesson upsert fehlgeschlagen: %s", exc)
|
||||
except Exception as exc:
|
||||
logger.warning("api_heuristic fehlgeschlagen: %s", exc)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user