Files
ARIA-AGENT/aria-brain/seed_rules.py
T
duffyduck 8d5991f364 fix(brain): 18. seed_rule — Side-Effect-Tools nicht blind retry'en
Beobachtung 30.05.2026 02:22: Stefan bat 'vorheriges lied'. ARIA hat
POST /previous gemacht — Spotify gab 204 No Content zurueck (Erfolgs-
Antwort ohne Body), aber der alte Skill-Code warf JSON-Parse-Error
weil kein Body zum Parsen. ARIA interpretierte das als 'Skill kaputt',
patchte ihn UND fuehrte previous nochmal aus.

Folge: Stefan landete ZWEI Lieder zurueck statt eins. Aergerlich weil
unerwartete Zustandsaenderung.

Neue Regel adressiert das:
- Side-Effect-Tools (POST/PUT/DELETE, next/previous/play/pause, send-
  message etc.) sind NICHT idempotent — Retry verdoppelt den Effekt.
- Bei unklarem Result IMMER zuerst State pruefen (currently-playing,
  list-Endpoint etc.), dann beurteilen ob Wiederholung noetig.
- HTTP 204 No Content ist KEIN Fehler bei POST/PUT — typische Spotify-
  Antwort. Skill darf 204 NICHT als Parse-Error werten.
- GET-Calls / Search sind retry-safe, hier keine Sorge.

ARIAs zweiter Skill-Patch ist uebrigens technisch korrekt (ARG_-
Konvention zurueck, 204 handled, strukturierte Ausgabe fuer
currently-playing). Nur das doppelte Side-Effect war das Problem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 02:26:17 +02:00

564 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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/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_scaffold-Reflex (mit Cross-Session-Counter)",
"category": "skills",
"content": (
"Brain trackt server-side wie oft Du in den letzten 24h dieselbe "
"externe API per Bash-curl angerufen hast (Cross-Session-Counter, "
"siehe '## API-Heuristik'-Block im System-Prompt). \n"
"\n"
"AUTO-SCAFFOLD: Brain legt fuer wiederkehrende Hosts mit "
"bekanntem Template (Spotify, GitHub, OpenAI, OpenWeather, …) "
"automatisch einen Skill an — Du siehst ihn dann in `## Skills` "
"ohne dass Du ihn selbst gebaut hast (Markierung "
"`author=aria-auto`). NUTZE diesen Skill via `run_<name>` "
"direkt, NICHT mehr Bash-curl gegen den Host. Beispiel: wenn "
"`spotify` plotzlich in der Skill-Liste auftaucht → "
"`run_spotify({method:'GET', path:'/v1/me/player'})` statt "
"Token holen + curl.\n"
"\n"
"Wenn die API-Heuristik einen Eintrag OHNE Suggestion zeigt "
"(unbekannter Host): rufe selbst `skill_scaffold` mit dem "
"passenden Template (oauth-api / apikey-api / file-process), "
"BEVOR Du wieder Bash-curl machst.\n"
"\n"
"Warum: jede Chat-Anfrage ist eine eigene Claude-CLI-Session — "
"Du siehst nicht dass Du gestern auch schon 10x Spotify gecurled "
"hast. Der API-Heuristik-Block ist Dein Cross-Session-Gedaechtnis. "
"Wenn er leer ist: alles OK, weitermachen. Wenn nicht: scaffolden.\n"
"\n"
"Templates (ausfuehrliche Doku siehe skill_scaffold-Tool):\n"
" - **oauth-api**: Spotify/GitHub/Reddit/Google/Discord. Token "
"kommt vom Brain mit Auto-Refresh.\n"
" - **apikey-api**: OpenWeather/OpenAI/Twilio. Key landet im "
"config_schema → CFG_<NAME> ENV. Stefan setzt ihn in Diagnostic.\n"
" - **file-process**: PDF/Bild/JSON-Wandler. process()-Stub, "
"danach `skill_update` mit echtem Code.\n"
"\n"
"Belohnung konkret: ein Spotify-Skill macht 'welches lied laeuft' "
"in 1 Tool-Call (~3s) statt 3-5 Bash-Roundtrips (~13-20s). Stefan "
"merkt das sofort. Ein einmaliger Scaffold-Aufwand spart hunderte "
"Bash-Roundtrips."
),
},
{
"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}