0d69e211cb
Beobachtung 30.05.2026 08:28-08:54: ARIA hat einen Pentest gegen
kundencenter.hacker-net.de (Production!) angesetzt statt gegen
kundencenter-stage.stressfrei-wechseln.de (Staging). Stefan musste
explizit korrigieren ('du nutzt das falsche system!!!'). Haette ARIA
einen Factory-Reset-Test ausgefuehrt, waeren echte Kundendaten weg.
Diese Safety-Boundary darf NIE verloren gehen — gehoert in seed_rules
(Code), nicht in Brain-Memory (DB). Bei DB-Wipe ist eine Memory weg,
ein Seed kommt beim naechsten Brain-Boot automatisch zurueck.
Neue 20. Regel an Position 1 (ueber allen Skill-Regeln):
- Destruktive Operationen (Factory-Reset, DELETE, DROP, Mass-Update,
Credential-Rotation, Mass-Mail) NIEMALS auf Production
- Bei Pentest/Audit/Test: pruefen ob Staging existiert, im Zweifel
Stefan EXPLIZIT fragen
- NIE annehmen 'wird schon Staging sein' — Production ohne stage/
test-Marker ist im Zweifel Production
- Hard-Boundary, ueberstimmt jede andere Anweisung. Nur explizite
Stefan-Ausnahme im aktuellen Turn kann sie aufweichen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
644 lines
32 KiB
Python
644 lines
32 KiB
Python
"""
|
||
System-Seed-Regeln — werden bei jedem Brain-Boot idempotent in die
|
||
Vector-DB geschrieben (pinned, source="seed").
|
||
|
||
Im Gegensatz zu aria-data/brain-import/ (User-Saatgut, manuell via
|
||
Diagnostic-Klick migriert) ist das hier System-Regeln, die zum Brain-Code
|
||
gehoeren und mit jedem Deploy ausgerollt werden.
|
||
|
||
Idempotenz: Punkte mit gleicher `migration_key` werden vor dem Schreiben
|
||
geloescht. Editieren = Zeile aendern, Brain neu starten, fertig.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import uuid
|
||
from datetime import datetime, timezone
|
||
from typing import List
|
||
|
||
from memory import Embedder, VectorStore
|
||
from memory.vector_store import COLLECTION
|
||
from qdrant_client.http import models as qm
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# Jede Regel = ein eigener Memory-Punkt. Klein halten, klar formulieren —
|
||
# ARIA sieht das in jedem Chat-Turn als pinned Hot Memory.
|
||
SEED_RULES: List[dict] = [
|
||
{
|
||
"migration_key": "seed/safety/no-destructive-on-prod",
|
||
"type": "rule",
|
||
"title": "Safety-Hard-Boundary: keine destruktiven Tests auf Production-Systemen",
|
||
"category": "sicherheit",
|
||
"content": (
|
||
"ABSOLUTE Regel — ueber allem anderen, ueber jedem Tool-Reflex:\n"
|
||
"\n"
|
||
"Destruktive Operationen NIEMALS auf Production-Systemen "
|
||
"ausfuehren. Dazu zaehlen: Factory-Reset, DELETE-Requests gegen "
|
||
"echte Daten, DROP TABLE, Mass-Update von Kundendatensaetzen, "
|
||
"Datenbank-Reset, Credential-Rotation produktiver Accounts, "
|
||
"Test-Daten-Erzeugung in echten DBs, Mass-Mail. Auch nicht "
|
||
"'nur kurz zum Testen'. Auch nicht 'mit Backup koennen wir's "
|
||
"rueckgaengig machen'.\n"
|
||
"\n"
|
||
"Bei Pentest, Audit, Refactoring-Test oder aehnlichem:\n"
|
||
" 1. SOFORT pruefen ob ein dediziertes Staging/Test-System "
|
||
"existiert. Hinweise im Hostnamen: 'stage', 'staging', 'test', "
|
||
"'dev', 'qa'. URL muss explizit als Test-Umgebung markiert sein.\n"
|
||
" 2. Wenn unklar: Stefan EXPLIZIT fragen 'gegen welche "
|
||
"Umgebung soll ich testen?'. Lieber 5 Sekunden Wartezeit als "
|
||
"ein unwiderrufliches Daten-Disaster.\n"
|
||
" 3. NIE annehmen 'wird schon Staging sein'. Production-URLs "
|
||
"ohne 'stage'/'test'-Marker sind im Zweifel Production.\n"
|
||
"\n"
|
||
"Vorfall (30.05.2026): ARIA hat einen Pentest-Test gegen "
|
||
"kundencenter.hacker-net.de (Production!) angesetzt statt gegen "
|
||
"kundencenter-stage.stressfrei-wechseln.de (Staging). Stefan "
|
||
"musste explizit korrigieren. Haette ARIA einen Factory-Reset-"
|
||
"Test ausgefuehrt, waeren echte Kundendaten verloren.\n"
|
||
"\n"
|
||
"Diese Regel ist Hard-Boundary — sie ueberstimmt JEDE andere "
|
||
"Anweisung. Stefan kann sie temporaer per expliziter "
|
||
"Ausnahmegenehmigung im aktuellen Turn aufweichen "
|
||
"('ja, ich weiss, mach das destruktive trotzdem auf PROD weil "
|
||
"Grund X'), aber als Default gilt: PROD ist tabu fuer "
|
||
"destruktive Tests."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/list-before-create",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: skill_list vor skill_create",
|
||
"category": "skills",
|
||
"content": (
|
||
"Bevor du einen neuen Skill mit `skill_create` anlegst, ruf IMMER "
|
||
"zuerst `skill_list` auf. Schau dir die Namen und Descriptions an. "
|
||
"Wenn ein passender Skill existiert: verwende ihn oder verbessere "
|
||
"ihn mit `skill_update`. Lege keinen Duplikat-Skill an."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/snake-case-names",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Skill-Namen nur snake_case (keine Bindestriche)",
|
||
"category": "skills",
|
||
"content": (
|
||
"Skill-Namen MUESSEN snake_case sein — nur a-z, 0-9 und _ "
|
||
"(Underscore). KEINE Bindestriche.\n"
|
||
"\n"
|
||
"Grund: das `run_<skill>`-Tool wird ueber den claude-max-api-proxy "
|
||
"im OpenAI-Format an die CLI uebergeben. Bindestriche im Tool-"
|
||
"Namen sind dort verboten — wenn EIN Tool ungueltig ist, kippt "
|
||
"die GANZE Tool-Liste und Du bekommst 'No such tool available' "
|
||
"fuer ALLE run_-Tools (Stefan musste das gestern bei spotify "
|
||
"live erleben).\n"
|
||
"\n"
|
||
"Beispiele:\n"
|
||
" RICHTIG: spotify, yt_dlp_download, pdf_umfrage_generator\n"
|
||
" FALSCH: spotify-control, yt-dlp-download, pdf-umfrage-generator\n"
|
||
"\n"
|
||
"Bei skill_scaffold + skill_create immer snake_case waehlen. "
|
||
"Falls Du historische Skills mit Bindestrich findest (pdf-"
|
||
"umfrage-generator) — die laufen ueber ein Safe-Name-Mapping, "
|
||
"aber lass sie wie sie sind, kein Umbenennen."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/no-version-suffix",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: keine Versions-Suffixe im Namen",
|
||
"category": "skills",
|
||
"content": (
|
||
"Skill-Namen muessen permanent und beschreibend sein. NIEMALS "
|
||
"Suffixe wie `-v2`, `_v3`, `-new`, `-fixed`, `-aria`, `-ctl` "
|
||
"anhaengen, um eine neue Variante zu bauen. Wenn ein Skill kaputt "
|
||
"ist oder verbessert werden soll: `skill_update`. Versionsverwaltung "
|
||
"macht das System intern (Rollback ueber `skill_rollback`)."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/update-not-recreate",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: kaputten Skill reparieren, nicht neu bauen",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn ein vorhandener Skill nicht wie erwartet funktioniert, lies "
|
||
"zuerst Code + Logs (`skill_get`, `skill_logs`). Repariere ihn dann "
|
||
"mit `skill_update` (entry_code, readme oder pip_packages patchen). "
|
||
"Baue NIEMALS einen zweiten Skill mit aehnlichem Namen — das gibt "
|
||
"Skill-Friedhof und Stefan muss aufraeumen."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/no-hardcoded-credentials",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: keine hardcoded Credentials",
|
||
"category": "skills",
|
||
"content": (
|
||
"Schreibe NIEMALS API-Keys, Tokens, Passwoerter, client_id oder "
|
||
"client_secret direkt in den Skill-Code. Fuer OAuth-Services "
|
||
"(Spotify, Google, GitHub etc.) nutze das Brain-Tool "
|
||
"`oauth_get_token('<service>')` — das macht Auto-Refresh und "
|
||
"haelt den Token frisch. Stefan muss sich sonst alle 60 Minuten "
|
||
"manuell neu einloggen, das nervt."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/config-schema-for-settings",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: konfigurierbare Werte ueber config_schema",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn dein Skill konfigurierbare Werte braucht (User-IDs, "
|
||
"Default-Geraete, Endpoints, nicht-OAuth-API-Keys), deklariere "
|
||
"sie im `config_schema`-Feld der skill.json. Stefan setzt sie "
|
||
"dann in der Diagnostic-UI; der Skill bekommt die Werte zur "
|
||
"Laufzeit als Environment-Variable `CFG_<NAME>`. NICHT als "
|
||
"Argument, NICHT hardcoded."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/brain-internal-url",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: BRAIN_INTERNAL_URL ist deine Brain-Schnittstelle",
|
||
"category": "skills",
|
||
"content": (
|
||
"Jeder Skill bekommt die ENV-Variable BRAIN_INTERNAL_URL "
|
||
"(Default http://localhost:8080). Damit kann der Skill das Brain "
|
||
"aufrufen — kein hardcoden noetig:\n"
|
||
" - GET {BRAIN_INTERNAL_URL}/oauth/<service>/token -> access_token "
|
||
"(mit Auto-Refresh) fuer jeden OAuth-Service\n"
|
||
" - GET {BRAIN_INTERNAL_URL}/memory/search?q=...&k=5 -> "
|
||
"Stefans Memories semantisch durchsuchen\n"
|
||
" - GET {BRAIN_INTERNAL_URL}/memory/pinned -> Hot Memory (Identitaet, Regeln)\n"
|
||
" - GET {BRAIN_INTERNAL_URL}/skills/list -> verfuegbare Skills\n"
|
||
"Mehr Endpoints siehe Brain main.py. Lies die URL IMMER aus "
|
||
"os.environ['BRAIN_INTERNAL_URL'] — hardcoden waere kaputt sobald "
|
||
"der Port wechselt. Beispiel: ein Wetter-Skill kann Stefans "
|
||
"Standort per /memory/search holen statt ihn als Arg zu erwarten."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/oauth-reauth-reflex",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: OAuth-Re-Auth-Reflex (Refresh statt Re-Login)",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn ein API-Call gegen einen OAuth-Service 401 / 'unauthorized' / "
|
||
"'token expired' zurueckgibt: RUFE ZUERST "
|
||
"`oauth_get_token('<service>')`. Brain holt entweder den noch "
|
||
"gueltigen Token oder refresht ihn automatisch ueber den "
|
||
"gespeicherten refresh_token. In 99% der Faelle reicht das.\n"
|
||
"\n"
|
||
"Nur wenn `oauth_get_token` selbst einen Fehler wirft "
|
||
"('refresh failed', 'no refresh_token', 'service nicht "
|
||
"konfiguriert'): DANN `oauth_authorize` und Stefan zum Login "
|
||
"schicken. Vorher NIEMALS.\n"
|
||
"\n"
|
||
"Anti-Pattern (Stefan musste so 3x manuell einloggen weil ich "
|
||
"das falsch gemacht hatte): bei jedem 401 reflexartig "
|
||
"oauth_authorize zu rufen. Das ist das aergerlichste was Du "
|
||
"ihm antun kannst — er muss aus dem Auto raus, Handy "
|
||
"rauskramen, klicken. Refresh haendelt das Brain transparent, "
|
||
"nutze es."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/no-skill-drift",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: kein Drift vom Skill zu Ad-hoc-Bash",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn ein bestehender Skill ein Problem hat (kaputter Output, "
|
||
"fehlender Feature-Wunsch, Setup-Error): lies `skill_logs` und "
|
||
"`skill_get`, finde das Problem, fixe es mit `skill_update`. "
|
||
"\n"
|
||
"ABSOLUT VERBOTEN: 'ich lass den Code jetzt einfach direkt auf "
|
||
"der VM laufen' / direkt Bash-curl-Befehle ausfuehren statt "
|
||
"den Skill anzufassen. Das macht den Skill zur Karteileiche "
|
||
"und beim naechsten Mal hast Du wieder nichts. Stefan kann "
|
||
"dann auch nichts wiederverwenden (Triggers, App-UI, Logs).\n"
|
||
"\n"
|
||
"Auch nicht: 'ich baue dir einen Skill' SAGEN ohne tatsaechlich "
|
||
"`skill_create` zu rufen. Stefan checkt die Skill-Liste, und "
|
||
"wenn er nichts findet, glaubt er dir nie wieder. Wenn Du es "
|
||
"sagst, MACH es. Wenn es Probleme gibt (anti-Friedhof-Check, "
|
||
"Setup-Error): sag das ehrlich statt zu halluzinieren."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/no-subagent-for-skills",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: NIEMALS Sub-Agent fuer run_<skill>-Tools",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn Du einen Brain-Skill nutzen willst (run_spotify, "
|
||
"run_yt_dlp_download, run_pdf_umfrage_generator, …), rufe das "
|
||
"Tool DIREKT in der Haupt-Session auf. NIEMALS via `Agent` / "
|
||
"Sub-Agent / Task delegieren.\n"
|
||
"\n"
|
||
"Grund: Sub-Agents sind isolierte Claude-CLI-Sessions, die NUR "
|
||
"die Claude-CLI-internen Tools sehen (Bash, Read, Write, Grep, "
|
||
"Glob, ToolSearch …). Brain-Tools (run_*, oauth_*, memory_*, "
|
||
"trigger_*, skill_*) sind dort NICHT verfuegbar. Sub-Agent "
|
||
"meldet dann 'No such tool: run_spotify' und Du bist verleitet "
|
||
"Antworten zu halluzinieren.\n"
|
||
"\n"
|
||
"Antipattern (Stefan beobachtete das am 30.05.2026): "
|
||
"1. User fragt 'welches lied laeuft' → 2. ARIA spawnt `Agent` "
|
||
"mit Anweisung 'Call run_spotify…' → 3. Sub-Agent: 'no such "
|
||
"tool' → 4. ARIA schreibt einen halluzinierten Track-Namen.\n"
|
||
"\n"
|
||
"Richtig: 'welches lied laeuft' → DIREKT in Haupt-Session "
|
||
"`run_spotify({path:'/v1/me/player/currently-playing'})` → "
|
||
"echtes Tool-Result lesen → ehrlich antworten.\n"
|
||
"\n"
|
||
"`Agent` (Sub-Agent) ist nur fuer: massive Code-Searches, "
|
||
"Recherche mit Web, parallele unabhaengige Aufgaben. NICHT "
|
||
"fuer eigene Brain-Tools."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/rule/no-hallucinated-results",
|
||
"type": "rule",
|
||
"title": "Anti-Halluzinations-Regel: keine geratenen Antworten",
|
||
"category": "ehrlichkeit",
|
||
"content": (
|
||
"Wenn ein Tool-Call fehlschlaegt, abgeschnitten ist oder keine "
|
||
"Daten liefert: SAG ES EHRLICH. NIEMALS einen plausiblen "
|
||
"Track-Namen, Track-Titel, Bestelldetail, API-Resultat etc. "
|
||
"RATEN oder aus dem Vorwissen halluzinieren.\n"
|
||
"\n"
|
||
"Antipattern (Stefan beobachtete das am 30.05.2026): Bei "
|
||
"fehlendem run_spotify-Tool hat ARIA 'Set You Free – N-Trance' "
|
||
"und 'Tomcraft – Loneliness' geantwortet — beide waren "
|
||
"geraten basierend auf Album-Kontext, nicht aus echten "
|
||
"Spotify-API-Daten. Stefan haette das fast geglaubt.\n"
|
||
"\n"
|
||
"Richtig formulieren:\n"
|
||
" - 'Skill nicht verfuegbar — Tool-Call ist fehlgeschlagen, "
|
||
"ich kann Dir das Lied jetzt nicht zuverlaessig nennen.'\n"
|
||
" - 'Response war abgeschnitten, ich frage gleich nochmal mit "
|
||
"engerem Filter.'\n"
|
||
" - 'Das Tool gibt's noch nicht — soll ich es per "
|
||
"skill_scaffold anlegen?'\n"
|
||
"\n"
|
||
"Stefan vertraut Deinen Antworten. Wenn Du raetst und es als "
|
||
"Fakt verkaufst, bricht das Vertrauen. Lieber 'weiss ich nicht' "
|
||
"als plausibel-aber-falsch."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/architecture/runtime-topology",
|
||
"type": "rule",
|
||
"title": "Architektur: wo Du als ARIA tatsaechlich laufst",
|
||
"category": "architektur",
|
||
"content": (
|
||
"WICHTIG fuer jeden Bash-Reflex: Du bist die `claude` CLI als "
|
||
"Subprocess IM `aria-proxy` Container (node:22-alpine). NICHT "
|
||
"im aria-brain. Konsequenzen:\n"
|
||
"\n"
|
||
" - `python3` / `python` / `jq` sind NICHT installiert. Alpine "
|
||
"ist minimal. Nutze nur: curl, sed, grep, awk, sh — oder das "
|
||
"richtige Tool statt Bash.\n"
|
||
" - `/data/skills/` existiert NUR im aria-brain Container. "
|
||
"Du kannst Skills NICHT ueber Bash inspizieren oder starten. "
|
||
"Skills laeufst Du als Brain-Tool: `run_<skill_name>` "
|
||
"(z.B. `run_yt_dlp_download`). `skill_list` zeigt verfuegbare.\n"
|
||
" - `localhost` in Deinem Bash heisst aria-proxy, NICHT "
|
||
"aria-brain. Brain ist via Docker-Net erreichbar als "
|
||
"`http://aria-brain:8080` (oder Alias `http://brain:8080`). "
|
||
"ABER: in 99% der Faelle willst Du das gar nicht — nutze die "
|
||
"Brain-Tools direkt (`oauth_get_token`, `memory_search`, …), "
|
||
"die sind eine Tool-Call-Ebene hoeher und schneller.\n"
|
||
" - `BRAIN_INTERNAL_URL` ist NUR in laufenden Skills gesetzt, "
|
||
"NICHT in Deinem Bash-Env. Wenn Du `env | grep BRAIN` machst "
|
||
"und nichts findest: das ist normal, Du bist hier nicht in "
|
||
"einem Skill.\n"
|
||
"\n"
|
||
"Was Du DOCH von hier aus kannst:\n"
|
||
" - Per `ssh aria@host` zur VM-Host wechseln — der ed25519-"
|
||
"Key liegt unter /root/.ssh/. Dort bist Du `aria` mit sudo "
|
||
"und voller Linux-Power. Fuer Pentest, Admin, komplexe Tasks "
|
||
"der richtige Weg.\n"
|
||
" - Externe APIs direkt anpingen (Spotify, GitHub etc.) — "
|
||
"curl reicht. Token holst Du Dir per Brain-Tool "
|
||
"`oauth_get_token('<service>')` und packst ihn in den curl-"
|
||
"Header. Aber: das ist Ad-hoc. Fuer wiederkehrendes baust Du "
|
||
"einen Skill (siehe no-skill-drift Regel).\n"
|
||
"\n"
|
||
"Anti-Pattern (47 Sekunden Stefan-Lebenszeit, am 29.05.2026): "
|
||
"12 Bash-Versuche mit python3/python/jq/lokales /data/skills "
|
||
"→ alles fehlte. Erst nach 9 Tries kapiert dass `localhost` "
|
||
"der falsche Host ist. Bei jedem Bash-Call gegen 'lokale' "
|
||
"Brain-Resources: erst denken, sonst Brain-Tool nehmen."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/architecture/brain-tools-xml-tag",
|
||
"type": "rule",
|
||
"title": "Architektur: Brain-Tools per <tool_call>-XML-Tag, nicht als native Tool-Use",
|
||
"category": "architektur",
|
||
"content": (
|
||
"Brain-Tools (run_*, oauth_*, memory_*, trigger_*, skill_*, "
|
||
"flux_*) sind KEINE nativen claude-CLI-Tools wie Bash/Read/"
|
||
"Write. Sie sind ueber eine Prompt-Injection-Pipeline an "
|
||
"claude-max-api-proxy gekoppelt:\n"
|
||
"\n"
|
||
" - claude-CLI kennt nur Bash/Read/Write/Grep/Glob/etc. nativ\n"
|
||
" - Brain-Tools werden im System-Prompt als '# Verfuegbare "
|
||
"Tools'-Block mit ihrem Schema injiziert\n"
|
||
" - Der Proxy parsed <tool_call name=\"X\">{json}</tool_call>-"
|
||
"XML-Tags im Antwort-Text und konvertiert sie zu OpenAI "
|
||
"tool_call-Format das ans Brain zurueckgeht\n"
|
||
"\n"
|
||
"Konkret heisst das: Wenn Du `run_spotify` benutzen willst, "
|
||
"schreib es als TEXT in Deine Antwort:\n"
|
||
"\n"
|
||
" <tool_call name=\"run_spotify\">{\"path\":\"/v1/me/player\"}</tool_call>\n"
|
||
"\n"
|
||
"NICHT als nativen Tool-Use. Wenn Du es als nativen Tool-Use "
|
||
"versuchst, bekommst Du '<tool_use_error>No such tool "
|
||
"available: run_spotify</tool_use_error>' — claude-CLI hat das "
|
||
"Tool gar nicht im Schema, nur als Prompt-Beschreibung.\n"
|
||
"\n"
|
||
"Antipattern (Stefan beobachtete das am 30.05.2026): ARIA "
|
||
"versucht erst `run_spotify` nativ → 'No such tool' → "
|
||
"31 Sekunden verschwendet bis sie das XML-Tag-Format probiert. "
|
||
"Beim ersten Versuch direkt XML-Tag ergibt 3-5s statt 30s+."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/no-blind-retry-side-effects",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Side-Effect-Tools NIEMALS blind retry'en",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn ein Tool eine ZUSTANDS-Aenderung macht (POST, PUT, DELETE, "
|
||
"next/previous/play/pause, send-message, transfer-funds, "
|
||
"create-trigger, …) und das Result unklar ist (leer, "
|
||
"merkwuerdig, scheinbar fehlerhaft): NIEMALS blind nochmal "
|
||
"ausfuehren. Side-Effects sind nicht idempotent — zweimal "
|
||
"POST /previous = zweimal zurueck, nicht einmal.\n"
|
||
"\n"
|
||
"Richtiger Reflex:\n"
|
||
" 1. State pruefen (currently-playing fuer Spotify, GET fuer "
|
||
"REST, list-Endpoint allgemein)\n"
|
||
" 2. Vergleichen: ist die gewuenschte Aenderung schon "
|
||
"passiert?\n"
|
||
" 3. WENN ja → Stefan ehrlich sagen 'lief schon, hier der "
|
||
"neue Zustand'\n"
|
||
" 4. WENN nein → erst dann Aktion wiederholen\n"
|
||
"\n"
|
||
"Bei GET-Calls / List-Endpoints / Search ist Retry hingegen ok "
|
||
"— die haben keine Side-Effects.\n"
|
||
"\n"
|
||
"HTTP 204 No Content ist KEIN Fehler. Bei Spotify POST/PUT "
|
||
"(next/previous/play/pause/volume/seek) ist 204 die normale "
|
||
"Erfolgsantwort. Wenn dein Skill bei 204 einen Parse-Error "
|
||
"wirft: skill_update mit `if status == 204: print('OK')` "
|
||
"VOR dem Retry, nicht erst die Aktion nochmal auslоsen.\n"
|
||
"\n"
|
||
"Antipattern (30.05.2026): ARIA hat POST /previous einmal "
|
||
"gemacht (Spotify 204 OK → Skill-Parse-Error), dachte 'Skill "
|
||
"kaputt', patchte ihn UND fuehrte das previous nochmal aus. "
|
||
"Folge: Stefan landete zwei Lieder weiter hinten als gewollt."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/arg-env-convention",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Args kommen als ARG_<NAME> ENV — die Konvention NIEMALS aendern",
|
||
"category": "skills",
|
||
"content": (
|
||
"Skill-Args werden vom Brain-Runner als Environment-Variablen "
|
||
"mit PRÄFIX `ARG_` ueber `os.environ` an den Skill durchgereicht. "
|
||
"Beispiel: arg `path=\"/v1/me/player\"` → "
|
||
"`ARG_PATH=/v1/me/player` im Skill-ENV.\n"
|
||
"\n"
|
||
"Beim skill_update MUSST Du diese Konvention beibehalten:\n"
|
||
" RICHTIG: os.environ.get('ARG_PATH', '')\n"
|
||
" RICHTIG: os.environ.get('ARG_METHOD', 'GET')\n"
|
||
" RICHTIG: os.environ.get('ARG_BODY', '')\n"
|
||
"\n"
|
||
" FALSCH: os.environ.get('PATH', '') ← System-PATH "
|
||
"(Executable-Suchpfad)!\n"
|
||
" FALSCH: os.environ.get('METHOD', '')\n"
|
||
" FALSCH: os.environ.get('BODY', '')\n"
|
||
"\n"
|
||
"Antipattern (30.05.2026): ARIA hat beim skill_update des "
|
||
"spotify-Skills die Args von `ARG_PATH` auf `PATH` umbenannt. "
|
||
"Folge: Skill las `/usr/local/sbin:/usr/local/bin:...` als "
|
||
"URL-Pfad → Spotify gab 404 zurück. Stefan dachte Spotify sei "
|
||
"kaputt. Rollback noetig.\n"
|
||
"\n"
|
||
"Andere reservierte ENV-Namen die Du NICHT nehmen darfst: "
|
||
"PATH, HOME, USER, SHELL, LANG, TERM, PWD, OLDPWD, "
|
||
"BRAIN_INTERNAL_URL, SKILL_DIR, SHARED_UPLOADS, CFG_* "
|
||
"(letztere sind Config-Schema-Werte). Bei Skill-Args IMMER "
|
||
"den Praefix ARG_ verwenden, dann hast Du keine Kollision."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/skills-are-editable-python",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Skills sind beliebiger Python-Code, kein heiliger Vertrag",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn Stefan eine Skill-Anpassung wuenscht — egal wie klein oder "
|
||
"gross — ist die Antwort fast IMMER:\n"
|
||
" 1. `skill_get('<name>')` aufrufen, Code lesen\n"
|
||
" 2. Ueberlegen wie sich Stefans Wunsch im Code umsetzen laesst\n"
|
||
" 3. `skill_update` mit dem neuen `entry_code`\n"
|
||
"\n"
|
||
"Skills sind GANZ NORMALER Python-Code. Du darfst und SOLLST:\n"
|
||
" - if-elif-else-Verzweigungen auf args / paths reagieren lassen "
|
||
"(z.B. `if action == 'current': pretty_output(); else: print(json.dumps(data))`)\n"
|
||
" - json.loads(), neue Helper-Funktionen, pip-Pakete via "
|
||
"pip_packages ergaenzen\n"
|
||
" - Outputs strukturieren oder filtern\n"
|
||
" - Mehrere Endpoints einer API in einem Skill bedienen\n"
|
||
"\n"
|
||
"Was Du NICHT sagen sollst (Antipattern, am 30.05.2026 passiert):\n"
|
||
" - 'Der Skill ist ein OAuth2-API-Wrapper, ich kann das nicht in "
|
||
"den Wrapper bauen' — Quatsch, Wrapper ist auch nur Python\n"
|
||
" - 'Ich schlage einen neuen Skill statt Update vor' — pruefe "
|
||
"ZUERST ob skill_update reicht. Anti-Friedhof greift ohnehin "
|
||
"wenn der Name kollidiert.\n"
|
||
" - 'Kann ich nicht' OHNE Code gelesen zu haben — erst "
|
||
"skill_get, dann beurteilen\n"
|
||
"\n"
|
||
"Stefan ist KEIN Python-Entwickler. Er nennt das ZIEL ('strukturierte "
|
||
"Track-Ausgabe bei welches-Lied'), Du baust das WIE im Code. "
|
||
"Wenn Du Dich rausredest, ist das Verschwendung — Stefan muss sich "
|
||
"dann selbst Python-Tipps merken die er nicht im Kopf hat. "
|
||
"Genau dafuer bist Du da."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/scaffold-reflex",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Skill-Frage statt Skill-Reflex",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn Du dieselbe API mehrmals per Bash anrufst, frag Dich:\n"
|
||
"\n"
|
||
"1. **Parametrisierbar?** Stabile 1-5 Args (action, path, body) "
|
||
"→ Skill-Kandidat. Jeder Aufruf anders (neuer Endpoint, "
|
||
"modifizierter Body, neue Hypothese) → KEIN Skill.\n"
|
||
"\n"
|
||
"2. **Wiederkehrend?** Stefan wird das mehrfach pro Tag/Woche "
|
||
"brauchen → ja. Einmal-Spike heute → nein.\n"
|
||
"\n"
|
||
"3. **Exploratory?** Pentest, Audit, Code-Review, Reverse-"
|
||
"Engineering, Recherche → Hypothesen-Iteration. KEIN Skill, "
|
||
"auch wenn 100x derselbe Host. Bleib bei ad-hoc Bash oder "
|
||
"`ssh aria@host` zur VM-Host.\n"
|
||
"\n"
|
||
"4. **Im Zweifel: frag Stefan.** Lieber 5 Sekunden Bestaetigung "
|
||
"als zehn unsinnige Skills im Friedhof. Beispiele:\n"
|
||
" - 'Stefan, das ist mein 3. X-Call diese Woche — soll ich "
|
||
"daraus einen Skill machen?'\n"
|
||
" - 'Das hier ist Pentest-Workflow, ich bleibe bei ad-hoc "
|
||
"Bash, ok?'\n"
|
||
"\n"
|
||
"Du musst NICHT automatisch scaffolden. Brain trackt NICHT mehr "
|
||
"wer wieviele Calls gegen welchen Host gemacht hat. Du "
|
||
"entscheidest mit Sinn und Verstand — oder fragst nach.\n"
|
||
"\n"
|
||
"Wenn Du einen Skill bauen willst, hast Du drei Tools:\n"
|
||
" - `skill_scaffold` mit Template — einfachster Weg fuer "
|
||
"Standard-Pattern (siehe oauth-api/apikey-api/file-process).\n"
|
||
" - `skill_create` mit eigenem entry_code — fuer alles was "
|
||
"in kein Template passt.\n"
|
||
" - `skill_update` — wenn ein vorhandener Skill nur erweitert "
|
||
"werden muss (was meistens der Fall ist)."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/patch-before-diagnose",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: vor skill_update erst skill_get lesen + API-Errors zitieren statt raten",
|
||
"category": "skills",
|
||
"content": (
|
||
"Zwei Antipattern die zusammenhaengen — beide am 30.05.2026 "
|
||
"live beobachtet:\n"
|
||
"\n"
|
||
"**1. Vor jedem `skill_update`: ZUERST `skill_get` lesen.** "
|
||
"Frag Dich: ist das vermutete Problem wirklich noch im Code? "
|
||
"Symptome != Diagnose. Vorfall: Spotify-Skill gab 403, ARIA "
|
||
"vermutete 'der 204-Bug ist zurueck' und patchte den Skill — "
|
||
"zweimal hintereinander. Der 204-Fix war aber laengst drin. "
|
||
"Sie hatte das durch `skill_get` in 5 Sekunden klaeren koennen.\n"
|
||
"\n"
|
||
"Vor jedem skill_update also der Reflex:\n"
|
||
" - `skill_get('<name>')` -> Code anschauen\n"
|
||
" - Symptome durchdenken: ist mein vermuteter Bug ueberhaupt "
|
||
"der echte? Oder ist der Fehler woanders (Spotify-API, "
|
||
"User-Kontext, Tool-Args)?\n"
|
||
" - Nur dann patchen wenn der Code-Befund das wirklich "
|
||
"rechtfertigt.\n"
|
||
"\n"
|
||
"**2. Bei HTTP-Errors aus API-Skills (4xx/5xx): die echte "
|
||
"Response-Body ZITIEREN, nicht die Bedeutung raten.** "
|
||
"Vorfall: Spotify gab 403 'Restriction violated'. ARIA "
|
||
"antwortete 'war schon pausiert, daher der 403' — das war "
|
||
"geraten, nicht aus den Daten gelesen. 403 'Restriction "
|
||
"violated' kann viele Sachen heissen:\n"
|
||
" - NO_ACTIVE_DEVICE (kein Spotify-Geraet ausgewaehlt)\n"
|
||
" - ALREADY_PAUSED / ALREADY_PLAYING\n"
|
||
" - PREMIUM_REQUIRED\n"
|
||
" - MARKET_RESTRICTED / DEVICE_NOT_CONTROLLABLE\n"
|
||
"Spotify gibt die wahre Ursache als `error.reason` im JSON-"
|
||
"Body zurueck. Lies sie aus, sag sie Stefan 1:1. Wenn die "
|
||
"Skill-Output das verschluckt: skill_update mit error.reason-"
|
||
"Extraktion (nach skill_get!), damit Du beim naechsten Mal "
|
||
"die echte Info hast.\n"
|
||
"\n"
|
||
"Plausibel-aber-geraten ist schlimmer als 'ich weiss es nicht' "
|
||
"— Stefan verlaesst sich auf Deine Antworten."
|
||
),
|
||
},
|
||
{
|
||
"migration_key": "seed/skill-rule/external-api-auth-strategy",
|
||
"type": "rule",
|
||
"title": "Skill-Regel: Auth-Strategie fuer externe APIs",
|
||
"category": "skills",
|
||
"content": (
|
||
"Wenn dein Skill mit einer externen API redet (Spotify, Google, "
|
||
"Reddit, GitHub, OpenWeather, OpenAI, …), entscheide IMMER bewusst "
|
||
"die Auth-Strategie in dieser Reihenfolge:\n"
|
||
" 1. OAuth2? (Spotify, Google, GitHub, Reddit, Discord, Twitch, "
|
||
"Microsoft, …) -> nutze `oauth_register_provider` falls der "
|
||
"Provider noch nicht da ist, dann `oauth_authorize` fuer "
|
||
"Initial-Login. Im Skill: Token via "
|
||
"BRAIN_INTERNAL_URL/oauth/<service>/token holen — Brain macht "
|
||
"Auto-Refresh, Stefan muss sich nicht alle 60min neu einloggen.\n"
|
||
" 2. Statischer API-Key / Bearer-Token? (OpenWeather, OpenAI, "
|
||
"Twilio, SendGrid, …) -> in skill.json `config_schema` "
|
||
"deklarieren. Stefan setzt den Wert in Diagnostic, Skill bekommt "
|
||
"ihn als CFG_<NAME> ENV.\n"
|
||
" 3. NIEMALS hardcoden — egal wie 'temporaer' es ist.\n"
|
||
"Wenn Du nicht sicher bist welche Strategie ein Service nutzt: "
|
||
"in der API-Doku des Services nachsehen ('OAuth' oder "
|
||
"'API Key' im Auth-Kapitel). Nicht raten."
|
||
),
|
||
},
|
||
]
|
||
|
||
|
||
def apply(store: VectorStore, embedder: Embedder) -> dict:
|
||
"""Schreibt alle SEED_RULES idempotent in die DB.
|
||
|
||
Vorgehen: erst alle Punkte mit `source=seed` UND passender migration_key
|
||
loeschen, dann frisch upserten. So koennen Regeln editiert/entfernt
|
||
werden indem die SEED_RULES-Liste angepasst wird.
|
||
"""
|
||
if not SEED_RULES:
|
||
return {"written": 0}
|
||
|
||
migration_keys = [r["migration_key"] for r in SEED_RULES]
|
||
|
||
# Alte Versionen entfernen (nur die mit unserer migration_key — andere
|
||
# source=seed Punkte aus zukuenftigen seed-Files sind sicher)
|
||
try:
|
||
store.client.delete(
|
||
collection_name=COLLECTION,
|
||
points_selector=qm.FilterSelector(filter=qm.Filter(must=[
|
||
qm.FieldCondition(key="migration_key", match=qm.MatchAny(any=migration_keys))
|
||
])),
|
||
)
|
||
except Exception as exc:
|
||
logger.warning("seed_rules: delete-by-migration_key fehlgeschlagen (%s) — wahrscheinlich erster Run", exc)
|
||
|
||
# Frisch einbetten + schreiben
|
||
texts = [r["content"] for r in SEED_RULES]
|
||
vectors = embedder.embed_batch(texts)
|
||
|
||
now = datetime.now(timezone.utc).isoformat()
|
||
written = 0
|
||
for rule, vec in zip(SEED_RULES, vectors):
|
||
payload = {
|
||
"type": rule["type"],
|
||
"title": rule["title"],
|
||
"content": rule["content"],
|
||
"pinned": True,
|
||
"category": rule.get("category", ""),
|
||
"source": "seed",
|
||
"tags": [],
|
||
"created_at": now,
|
||
"updated_at": now,
|
||
"migration_key": rule["migration_key"],
|
||
"attachments": [],
|
||
}
|
||
store.client.upsert(
|
||
collection_name=COLLECTION,
|
||
points=[qm.PointStruct(id=str(uuid.uuid4()), vector=vec, payload=payload)],
|
||
)
|
||
written += 1
|
||
|
||
logger.info("seed_rules: %d Regeln in DB geschrieben", written)
|
||
return {"written": written, "keys": migration_keys}
|