feat(brain): skill_scaffold — Templates statt Skill aus dem Nichts

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>
This commit is contained in:
2026-05-30 00:02:45 +02:00
parent add303970b
commit 0540c49c66
6 changed files with 639 additions and 1 deletions
+70
View File
@@ -186,6 +186,47 @@ META_TOOLS = [
},
},
},
{
"type": "function",
"function": {
"name": "skill_scaffold",
"description": (
"ERSTE WAHL fuer Skill-Bau wenn das Muster zu einem Template passt — "
"Brain expandiert das Skelett, Du sparst Dir das vollstaendige "
"Python-Programm zu generieren. Wenn Stefan eine externe API "
"mehrmals nutzt: SOFORT `skill_scaffold` statt jedes Mal "
"ad-hoc Bash-curl.\n\n"
"Verfuegbare Templates:\n"
" - **oauth-api**: OAuth2-API (Spotify, GitHub, Reddit, Google, Discord, …). "
"Token kommt vom Brain mit Auto-Refresh. params: "
"`{service:'spotify', base_url?:'https://...'}`\n"
" - **apikey-api**: API mit statischem Key (OpenWeather, OpenAI, Twilio). "
"Key liegt im skill.json config_schema → CFG_<NAME> ENV. params: "
"`{api_name:'OpenWeather', key_env:'OWM_API_KEY', auth_header?:'Authorization', auth_prefix?:'Bearer ', base_url:'https://...'}`\n"
" - **file-process**: Skelett fuer Datei-In/Datei-Out (PDF, Bild, JSON umformen). "
"process()-Funktion ist Stub — danach `skill_update` mit echtem Code. params: "
"`{output_ext:'txt'}`\n\n"
"Nach Scaffold kannst Du das Skelett via `skill_update` weiter "
"anpassen falls noetig (mehr pip_packages, andere args, …). "
"Aber meistens reicht das Template direkt.\n\n"
"Wenn kein Template passt: erst pruefen ob Du wirklich ein "
"kustomes brauchst, sonst lieber Template + Update."
),
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string",
"description": "Skill-Name (kebab-case, ohne Versionssuffix)"},
"template": {"type": "string",
"enum": ["oauth-api", "apikey-api", "file-process"],
"description": "Eines der drei Templates"},
"params": {"type": "object",
"description": "Template-spezifische Parameter (siehe description)"},
},
"required": ["name", "template"],
},
},
},
{
"type": "function",
"function": {
@@ -945,6 +986,35 @@ class Agent:
},
})
return f"OK — Skill '{manifest['name']}' erstellt (active={manifest['active']})."
if name == "skill_scaffold":
skill_name = (arguments.get("name") or "").strip()
template = (arguments.get("template") or "").strip()
params = arguments.get("params") or {}
if not skill_name or not template:
return "FEHLER: name + template erforderlich."
try:
manifest = skills_mod.scaffold_skill(
name=skill_name, template=template, params=params, author="aria",
)
except ValueError as exc:
return f"FEHLER: {exc}"
# Side-Channel-Event analog zu skill_create
self._pending_events.append({
"type": "skill_created",
"skill": {
"name": manifest["name"],
"description": manifest.get("description", ""),
"execution": manifest.get("execution", ""),
"active": manifest.get("active", True),
"setup_error": manifest.get("setup_error"),
"scaffolded_from": template,
},
})
return (
f"OK — Skill '{manifest['name']}' aus Template '{template}' angelegt. "
f"active={manifest['active']}. "
f"Falls noetig: skill_update fuer custom Code, skill_set_config fuer secrets."
)
if name == "skill_list":
items = skills_mod.list_skills(active_only=False)
if not items: