feat(brain): Auto-Scaffold — Brain legt Skills selbst an wenn ARIA driftet
Variante C: ARIA hat selbst mit Heuristik-Block + 11 seed_rules den
expliziten skill_scaffold-Befehl ignoriert (32x Spotify-Bash-Calls in
24h, kein einziger scaffold-Aufruf). Verhaltens-Traegheit ist staerker
als jeder Prompt-Hint.
Loesung: Brain wartet nicht mehr. Bei jedem chat()-Aufruf wird die
Heuristik berechnet. Findet sie einen Host mit bekannter Suggestion
(Spotify, GitHub, OpenAI, OpenWeather, Telegram, Microsoft, Discord,
Notion, Reddit) der noch keinen Skill hat → Brain ruft selbst
`scaffold_skill(name, template, params)` mit author='aria-auto'.
Der frische Skill ist sofort im Prompt sichtbar (Skill-Liste wird nach
Scaffold refreshed, Heuristik-Cache invalidiert, Hints neu gerechnet).
Side-Channel-Event 'skill_created' mit Flag 'auto_scaffolded' geht an
die UI — Stefan sieht im Chat dass Brain einen Skill angelegt hat.
ARIA findet beim Tool-Use-Loop einen passenden `run_<name>`-Skill vor
und nutzt ihn idealerweise statt wieder Bash. Macht sie's nicht und
curlt trotzdem weiter, ist der Counter beim naechsten Mal wieder hoch
und Brain scaffolded weiter — aber dann ist der Skill ja schon da, also
nur ein Pfad.
Toggle: BRAIN_AUTO_SCAFFOLD=false zum Abschalten.
scaffold-reflex Regel angepasst: ARIA wird informiert dass Brain
manchmal selbst scaffolded (author=aria-auto) und sie den Skill via
run_<name> nutzen soll statt zu curlen. Bei Hinweisen OHNE Suggestion
(unbekannter Host) soll sie selbst skill_scaffold rufen.
Stefan-Zitat aus der Diskussion ("ARIA lernt es so nicht"): stimmt
inhaltlich, aber pragmatisch wichtiger ist dass Stefans Wartezeit von
20s auf 3s sinkt. Lernen kann sie spaeter — der Skill ist da, sie sieht
den Pfad jedes Mal beim Tool-Listing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -402,7 +402,7 @@ jedem Chat-Turn im Hot-Memory-Block auf:
|
|||||||
- **oauth-reauth-reflex** — bei 401: ZUERST `oauth_get_token` (Auto-Refresh), nur bei dessen Fehler `oauth_authorize`
|
- **oauth-reauth-reflex** — bei 401: ZUERST `oauth_get_token` (Auto-Refresh), nur bei dessen Fehler `oauth_authorize`
|
||||||
- **no-skill-drift** — kein Drift vom Skill zu Ad-hoc-Bash-Befehlen. Skill kaputt? `skill_logs` + `skill_update`. Niemals nur SAGEN „ich baue dir einen Skill", wenn `skill_create` nicht wirklich gefeuert wird
|
- **no-skill-drift** — kein Drift vom Skill zu Ad-hoc-Bash-Befehlen. Skill kaputt? `skill_logs` + `skill_update`. Niemals nur SAGEN „ich baue dir einen Skill", wenn `skill_create` nicht wirklich gefeuert wird
|
||||||
- **runtime-topology** (architektur) — ARIA laeuft als `claude`-CLI-Subprocess IM aria-proxy Container (alpine — kein python3/jq), NICHT im aria-brain. `/data/skills/` und `BRAIN_INTERNAL_URL` existieren dort nicht. Brain-Resources via Brain-Tools (`oauth_get_token`, `memory_search`, `run_<skill>` …), nicht via Bash. SSH zur VM-Host via `ssh aria@host` (Key liegt im Proxy)
|
- **runtime-topology** (architektur) — ARIA laeuft als `claude`-CLI-Subprocess IM aria-proxy Container (alpine — kein python3/jq), NICHT im aria-brain. `/data/skills/` und `BRAIN_INTERNAL_URL` existieren dort nicht. Brain-Resources via Brain-Tools (`oauth_get_token`, `memory_search`, `run_<skill>` …), nicht via Bash. SSH zur VM-Host via `ssh aria@host` (Key liegt im Proxy)
|
||||||
- **scaffold-reflex** — Brain trackt cross-session welche externen Hosts via Bash-curl wiederholt (≥3× in 24h) ohne passenden Skill aufgerufen wurden. Ergebnis landet als `## API-Heuristik`-Block im System-Prompt mit konkretem `skill_scaffold(...)`-Vorschlag → ARIA scaffolded statt zu curlen. Data-Source: `agent_stream.jsonl`, Cache 5 min
|
- **scaffold-reflex** — Brain trackt cross-session welche externen Hosts via Bash-curl wiederholt (≥3× in 24h) ohne passenden Skill aufgerufen wurden. Ergebnis landet als `## API-Heuristik`-Block im System-Prompt. **Auto-Scaffold**: bei bekannten Hosts (Spotify, GitHub, OpenAI etc.) legt Brain den Skill automatisch an — ARIA findet ihn beim nächsten Turn vor (author=`aria-auto`) und nutzt `run_<name>` statt curlen. Toggle via ENV `BRAIN_AUTO_SCAFFOLD=false`. Data-Source: `agent_stream.jsonl`, Cache 5 min
|
||||||
- **external-api-auth-strategy** — OAuth2 → `oauth_get_token`, sonst `config_schema`, NIEMALS hardcoden
|
- **external-api-auth-strategy** — OAuth2 → `oauth_get_token`, sonst `config_schema`, NIEMALS hardcoden
|
||||||
|
|
||||||
### Skill-Scaffold (Templates)
|
### Skill-Scaffold (Templates)
|
||||||
|
|||||||
@@ -887,6 +887,54 @@ class Agent:
|
|||||||
try:
|
try:
|
||||||
import api_heuristic as _ah
|
import api_heuristic as _ah
|
||||||
hints = _ah.compute_hints(existing_skills=all_skills)
|
hints = _ah.compute_hints(existing_skills=all_skills)
|
||||||
|
|
||||||
|
# AUTO-SCAFFOLD (Variante C): wenn ein Hinweis ein konkretes
|
||||||
|
# (name, template, params) hat UND der Skill noch nicht existiert,
|
||||||
|
# legt Brain ihn JETZT an — bevor ARIA wieder Bash-curl macht.
|
||||||
|
# ARIA findet den Skill in den naechsten Tool-Listen vor und
|
||||||
|
# nutzt ihn direkt via `run_<name>`. Toggle via ENV.
|
||||||
|
auto_scaffold = os.environ.get("BRAIN_AUTO_SCAFFOLD", "true").strip().lower() != "false"
|
||||||
|
if auto_scaffold and hints:
|
||||||
|
existing_names = {s.get("name") for s in all_skills}
|
||||||
|
scaffolded_any = False
|
||||||
|
for hint in hints:
|
||||||
|
sug = hint.get("suggestion")
|
||||||
|
if not sug:
|
||||||
|
continue
|
||||||
|
sname, stpl, sparams = sug
|
||||||
|
if sname in existing_names:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
new_manifest = skills_mod.scaffold_skill(
|
||||||
|
name=sname, template=stpl, params=sparams, author="aria-auto",
|
||||||
|
)
|
||||||
|
logger.info("auto_scaffold: '%s' aus '%s' angelegt (trigger: %s mit %d Calls)",
|
||||||
|
sname, stpl, hint["host"], hint["count"])
|
||||||
|
self._pending_events.append({
|
||||||
|
"type": "skill_created",
|
||||||
|
"skill": {
|
||||||
|
"name": new_manifest["name"],
|
||||||
|
"description": new_manifest.get("description", ""),
|
||||||
|
"execution": new_manifest.get("execution", ""),
|
||||||
|
"active": new_manifest.get("active", True),
|
||||||
|
"setup_error": new_manifest.get("setup_error"),
|
||||||
|
"auto_scaffolded": True,
|
||||||
|
"from_template": stpl,
|
||||||
|
"trigger_host": hint["host"],
|
||||||
|
"trigger_count": hint["count"],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
scaffolded_any = True
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("auto_scaffold '%s' fehlgeschlagen: %s", sname, exc)
|
||||||
|
if scaffolded_any:
|
||||||
|
# Skills-Liste refresh damit der frische Skill im Prompt sichtbar ist
|
||||||
|
all_skills = skills_mod.list_skills(active_only=False)
|
||||||
|
active_skills = [s for s in all_skills if s.get("active", True)]
|
||||||
|
_ah.invalidate_cache()
|
||||||
|
# Heuristik neu rechnen — die scaffold-targets sind jetzt weg
|
||||||
|
hints = _ah.compute_hints(existing_skills=all_skills, force=True)
|
||||||
|
|
||||||
api_heuristic_section = _ah.build_section(hints)
|
api_heuristic_section = _ah.build_section(hints)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("api_heuristic fehlgeschlagen: %s", exc)
|
logger.warning("api_heuristic fehlgeschlagen: %s", exc)
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ _SUGGESTIONS: dict[str, tuple[str, str, dict]] = {
|
|||||||
_cache: dict = {"computed_at": 0.0, "hints": []}
|
_cache: dict = {"computed_at": 0.0, "hints": []}
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_cache() -> None:
|
||||||
|
"""Cache leeren — sinnvoll nach skill_create / scaffold damit der neue
|
||||||
|
Skill sofort beim naechsten Aufruf erkannt wird."""
|
||||||
|
_cache.update(computed_at=0.0, hints=[])
|
||||||
|
|
||||||
|
|
||||||
def _extract_hosts_from_bash_input(input_str: str) -> list[str]:
|
def _extract_hosts_from_bash_input(input_str: str) -> list[str]:
|
||||||
"""Hostnames aus URLs in einem Bash-Command. Sehr robust — sucht `https?://host`."""
|
"""Hostnames aus URLs in einem Bash-Command. Sehr robust — sucht `https?://host`."""
|
||||||
if not input_str:
|
if not input_str:
|
||||||
|
|||||||
@@ -216,11 +216,22 @@ SEED_RULES: List[dict] = [
|
|||||||
"content": (
|
"content": (
|
||||||
"Brain trackt server-side wie oft Du in den letzten 24h dieselbe "
|
"Brain trackt server-side wie oft Du in den letzten 24h dieselbe "
|
||||||
"externe API per Bash-curl angerufen hast (Cross-Session-Counter, "
|
"externe API per Bash-curl angerufen hast (Cross-Session-Counter, "
|
||||||
"siehe '## API-Heuristik'-Block im System-Prompt). Sobald da "
|
"siehe '## API-Heuristik'-Block im System-Prompt). \n"
|
||||||
"ein Eintrag steht: das ist KEINE Empfehlung sondern eine "
|
"\n"
|
||||||
"Aufforderung. RUFE als ALLERERSTES `skill_scaffold` mit dem "
|
"AUTO-SCAFFOLD: Brain legt fuer wiederkehrende Hosts mit "
|
||||||
"vorgeschlagenen Template und params auf, BEVOR Du wieder Bash-"
|
"bekanntem Template (Spotify, GitHub, OpenAI, OpenWeather, …) "
|
||||||
"curl machst. Dann nutze den frischen Skill via `run_<name>`.\n"
|
"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"
|
"\n"
|
||||||
"Warum: jede Chat-Anfrage ist eine eigene Claude-CLI-Session — "
|
"Warum: jede Chat-Anfrage ist eine eigene Claude-CLI-Session — "
|
||||||
"Du siehst nicht dass Du gestern auch schon 10x Spotify gecurled "
|
"Du siehst nicht dass Du gestern auch schon 10x Spotify gecurled "
|
||||||
|
|||||||
Reference in New Issue
Block a user