0540c49c66
Variante C: niedrigere Huerde zum Skill-Bau. Statt einen kompletten
Python-Skill via skill_create zu generieren (~100 Zeilen Code, teuer in
Tokens und fehleranfaellig), waehlt ARIA ein Template + minimale params,
Brain expandiert das Skelett in ~1s zu fertigem Skill.
Beobachtung: ARIA driftet bei Spotify, PDF etc. zu Bash-curl statt
einen Skill zu bauen, weil die Skill-Bau-Huerde zu hoch ist (Code,
README, args, pip_packages, config_schema). Mit Templates ist die
Huerde minimal.
Neue Module:
- aria-brain/skill_templates.py: drei mitgelieferte Templates
- oauth-api: OAuth2-API (Spotify, GitHub, Reddit, Google, Discord, ...).
Token via BRAIN_INTERNAL_URL/oauth/<s>/token mit Auto-Refresh.
Args: method/path/body/base_url
- apikey-api: API mit statischem Key (OpenWeather, OpenAI, Twilio).
Key liegt im config_schema -> CFG_<NAME> ENV, KEIN hardcoden.
Konfigurierbar: auth_header (Authorization|X-Api-Key), auth_prefix.
- file-process: Skelett fuer File-In/File-Out (PDF, Bild, JSON).
process()-Funktion ist Stub, ARIA fuellt sie via skill_update.
Templates nutzen Token-Replacement statt f-Strings (sonst Konflikt
mit dem skill-internen Python-Code).
- aria-brain/skills.py: scaffold_skill(name, template, params, author)
wrappt create_skill mit den expandierten Feldern.
- aria-brain/agent.py: neues Brain-Tool skill_scaffold mit detaillierter
Description (Template-Liste + params-Schema). Dispatcher-Handler
schickt skill_created Side-Channel-Event analog zu skill_create.
- aria-brain/main.py: POST /skills/scaffold + GET /skills/templates
(letzteres listet alle Templates fuer UI/Diagnostic).
- 11. seed_rule scaffold-reflex: bei 2x derselben API per Bash-curl
SOFORT skill_scaffold rufen. Belohnung explizit benannt
("welches lied" von 20s auf 3s).
README mit Skills-Scaffold-Tabelle ergaenzt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
322 lines
15 KiB
Python
322 lines
15 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/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/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/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/skill-rule/scaffold-reflex",
|
|
"type": "rule",
|
|
"title": "Skill-Regel: skill_scaffold-Reflex bei wiederholten API-Calls",
|
|
"category": "skills",
|
|
"content": (
|
|
"Wenn Du dieselbe externe API in einer Session 2x oder oefter "
|
|
"via Bash-curl angerufen hast: HOER AUF und rufe SOFORT "
|
|
"`skill_scaffold` auf. Drei Spotify-Calls hintereinander, "
|
|
"drei Wetter-Abfragen, drei Calendar-Events — JEDES dieser "
|
|
"Pattern schreit nach einem Skill.\n"
|
|
"\n"
|
|
"skill_scaffold ist die niedrige Hürde: Du musst keinen "
|
|
"kompletten Python-Skill schreiben. Du waehlst nur:\n"
|
|
" - oauth-api fuer OAuth2-Services (Spotify, GitHub, Reddit, "
|
|
"Google, Discord) -- Token kommt vom Brain mit Auto-Refresh\n"
|
|
" - apikey-api fuer statische Keys (OpenWeather, OpenAI, "
|
|
"Twilio) -- Key landet im config_schema, Stefan setzt ihn in "
|
|
"Diagnostic\n"
|
|
" - file-process fuer Datei-In/Datei-Out (PDF, Bild, Daten)\n"
|
|
"\n"
|
|
"Brain expandiert das Template in ~1s zu einem fertigen Skill. "
|
|
"Falls Du was Spezielles brauchst: erst Scaffold, dann "
|
|
"`skill_update` mit Anpassung. NICHT umgekehrt — und schon gar "
|
|
"nicht das fuenfte Mal das gleiche Bash-Skript bauen.\n"
|
|
"\n"
|
|
"Belohnung: ein Spotify-Skill macht 'welches lied laeuft' in "
|
|
"1 Tool-Call (~3s) statt 5 Bash-Roundtrips (~20s). Stefan "
|
|
"merkt das sofort und ist zufriedener."
|
|
),
|
|
},
|
|
{
|
|
"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}
|