Stefans Idee: die QUELLE entscheidet ueber Vorlesen, nicht der Reply-Inhalt.
Fast-Path (reiner Steuerbefehl) = speak=False (stumm); ARIA-Antworten
(local/claude) = speak=True (vorlesen ok — eine Ansage ist sogar nett).
- agent.chat() gibt jetzt (reply, answered_by, speak) zurueck; main.py
ChatOut.speak; background.py-Aufrufer angepasst.
- bridge: liest speak aus /chat, reicht es an _process_core_response; bei
speak=False wird TTS uebersprungen — VOR jeder <voice>-Logik.
Robust: unabhaengig davon, ob die Skill-fast_pattern-Reply ein <voice></voice>
enthaelt (das verlor ARIA beim semantischen Skill-Rebuild — genau der Bug).
App braucht keinen Change: kein xtts_request -> kein Audio -> stumm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mehrere lokale Modelle, on-demand geladen/geswappt, in Diagnostic waehlbar.
Design: das Brain schickt den Modellnamen (aus local_llm.json) im llm_request
mit -> Adapter -> llama-swap laedt/swappt. Keine separate Gamebox-Config noetig.
- xtts: `llama`-Container -> `llama-swap` (unified-cuda), config.yaml mit
qwen3-8b (Standard) + qwen3-4b; Auto-Download via -hf, Cache /models geteilt
(qwen3-8b schon da). Adapter -> llama-swap:8080, Timeout 600s (Erst-Download).
- adapter: `model` aus dem Request an llama-swap durchreichen (Fallback env).
- brain: router.load_config liest localLlmModel; local_llm_chat(model=...);
agent gibt cfg-Modell mit; bridge reicht model durch (_local_llm + Route).
- diagnostic: /api/local-models-list (aus /shared/config/local_models.json,
seeded), local-llm-config um localLlmModel erweitert; Dropdown "Lokales
Modell" im Settings-Block + Erst-Download-Hinweis.
BLIND gebaut (Gamebox nicht testbar hier): llama-swap CLI/Config-Pfad beim
ersten Start via `docker logs aria-llama-swap` pruefen. Live-Lade-Status
(Adapter->Diagnostic) ist B0.5-2 (Folgeschritt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zeigt pro Antwort, welcher Backend sie erzeugt hat — farbcodiert (lokal=gruen,
Claude=blau, Fast-Path=lila). Nur in Diagnostic (App bleibt Mama-tauglich).
- agent.chat() gibt jetzt (reply, answered_by) zurueck; an jedem Return-Punkt
gesetzt (fast-path/local/claude). Beide Aufrufer (main.py, background.py)
angepasst. main.py: ChatOut.answered_by.
- bridge: liest answered_by aus /chat, reicht es an _process_core_response,
broadcastet es im chat-Payload UND persistiert es in chat_backup (answeredBy)
→ bleibt nach Reload.
- diagnostic: srcBadgeHtml() rendert den Badge in Live-Chat + History;
server.js liefert answeredBy in der chat_history.
Erweiterbar: spaeter kann ein Bild-Backend (FLUX/Modellname) denselben Kanal
nutzen. Modellwechsel-fuer-Antworten (groessere Modelle bei mehr GPUs) bleibt
B0.5/Skalierungs-Thema im Plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stefan-Punkt: der 'auf <Geraet>'-Router-Patch war ein per-Fall-Band-Aid, das
nicht skaliert (naechster Skill mit komplexer API → wieder patchen). Besser:
das LLM merkt es selbst.
- Router-Hardcoding zurueckgenommen (kein 'auf Geraet'→Claude mehr).
- GENERAL: im lokalen Tool-Loop → scheitert ein Tool-Call (Ergebnis beginnt mit
'FEHLER'), uebernimmt Claude. Gilt fuer JEDEN Skill, kein per-Fall-Wissen im
Router. Lokal probiert, bei Fehler eskaliert.
- skill_create-Anleitung: SEMANTISCH bauen — Skills bieten klare Operationen als
args (action/device_name), NICHT rohe {path,method,body}-Durchreichung. Das
args-Schema ist die 'Bedienungsanleitung', die das LLM sieht (die haben wir
also schon — sie muss nur semantisch sein). Was das LLM sonst raten muesste
(Endpunkte/IDs/Payload) gehoert INS Skill.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Log-Beweis: Qwen baute fuer 'auf android abspielen' einen erfundenen Endpoint
(/v1/me/player/start) mit Platzhalter-Device-ID -> 404. Device-Transfer ist ein
komplexer Mehrschritt-Flow (Geraete abfragen -> Name->echte ID -> richtiger
Endpoint); das 8B ist da unzuverlaessig. Router schliesst 'auf <Geraet>'-
Formulierungen jetzt aus dem lokalen Pfad aus -> Claude. Simple Steuerung
(pause/next/play, 'was laeuft') + 'auf deutsch' bleiben lokal (verifiziert).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ARIA soll Patterns wortstellungs-tolerant + mit Synonymen/Fuellwoertern in EINEM
Regex schreiben (z.B. 'pause spotify' UND 'spotify pause' UND 'stopp mal'), statt
nur einer Formulierung. Plus der Schluessel-Hinweis: Fast-Paths nur fuer die
HAEUFIGSTEN Befehle — den Rest faengt das lokale LLM (hat das Tool) schnell ab,
also lieber wenige breite Patterns als viele enge. Damit kriegen kuenftige
Skills automatisch robuste Patterns (statt manuellem Nachpatchen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Qwen imitierte aus dem Kontext den <voice>-TTS-Block, packte aber die GANZE
Antwort hinein -> Display strippt <voice> -> leere Bubble (nur TTS klang richtig).
Fix: (1) Local-Prompt verbietet <voice>/[FILE:]/<tool_call>/Markup explizit —
nur Plain-Text (wird angezeigt UND vorgelesen). (2) Sicherheitsnetz: Voice-Tags
aus lokalen Antworten strippen, falls das 8B es doch tut.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
web_search war versehentlich in META_TOOLS -> Claude bekam es zusaetzlich zu
seinen nativen WebSearch/WebFetch/Bash (redundant/verwirrend). Jetzt als
WEB_SEARCH_TOOL nur im lokalen Tool-Set (_build_local_tools). Claude nutzt
weiter seine eigene Suche + Voll-Seiten-WebFetch (wichtig fuer Pentest/Research);
SearXNG ist fuer das lokale Tier, das sonst keinen Netzzugriff haette.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wiederkehrender Identity-Bug: --system-prompt liefert die Persona korrekt (Proxy-
Test = "ICH BIN ARIA"), ABER ein einziger in der History gespeicherter Break
("ich bin nach wie vor Claude / die Persona ist erfunden") zieht bei schwachen
Folgeturns eine Kaskade nach sich — das Modell setzt seine eigene Ablehnung fort.
Das erste Cleanup verlangte "claude code" und liess deutsche Breaks durch.
Fix zweifach:
- prompts.py: looks_like_identity_break() — STARKE selbstreferenzielle Marker
(ich-bin-Claude / erfundene Persona / diese-Session-injiziert / nicht-real-in-
dieser). Bewusst NICHT das blosse "injizier"/"prompt injection" — das nutzt
ARIA in Pentest-Antworten legitim (verifiziert: 0 False Positives).
- agent.py: nach dem Claude-Loop Break-Check; bei Break Retry (Nondeterminismus
holt meist ARIA), sonst sichere ARIA-Fallback-Antwort — Break wird NIE
persistiert. Auch die lokale Fast-Lane eskaliert bei Break auf Claude.
- clean_poisoned_turns.py: auf denselben starken Matcher umgestellt (der breite
produzierte False Positives auf echte Security-Doku, im Dry-Run gesehen).
VM bereits bereinigt (je 2 Rest-Breaks aus conversation.jsonl + chat_backup).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gemessen: lokaler Call mit 12-Turn-Fenster ~2,6s statt ~0,8s. Im Hauptchat (bis
50 Turns) wuerde volles Fenster das Prefill aufblaehen und den Speed-Vorteil
auffressen. Lokales Tier ist fuer kurze Plauder-Turns — letzte 8 Turns reichen.
B1a end-to-end verifiziert: plaudern→lokal (~0,8s warm), Tool/hart→Claude,
localOnly erzwingt lokal; Config live gelesen, Default aus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brain → HTTP /internal/local-llm → Bridge → RVS → llm-adapter → llama.cpp,
1:1 nach dem FLUX-Roundtrip-Muster gebaut:
- Bridge: _pending_llm (requestId→Future), llm_response-Handler (setzt Future),
_local_llm() (sendet llm_request, wartet mit 30s-Timeout), HTTP-Route
POST /internal/local-llm ({messages, max_tokens?, temperature?, stop?}).
- Brain: local_llm.py mit local_llm_chat() — POSTet an die Bridge, gibt
{ok, content, model?, elapsedMs?} zurueck, wirft nie (Aufrufer eskaliert
bei ok=false auf Claude).
Provider-Kette bereits verifiziert (718ms). Als naechstes: Test brain→bridge→
gamebox end-to-end, dann B1 (Router-Heuristik + Escalation) und der
Diagnostic-Testchat/Status (B0.5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Einmal-Tool zum Entfernen der aus-der-Rolle-Antworten ("das ist injiziert,
ich bin Claude Code"), die waehrend der --append-system-prompt-Phase in die
History geschrieben wurden und das Modell per Self-Grounding rueckwaerts aus
der Rolle zogen (siehe 2284c1a). Feld-agnostisch: bedient conversation.jsonl
(content/project_id) UND chat_backup.jsonl (text/projectId). Entfernt nur
Hauptthread-Assistant-Turns mit "claude code" + zweitem Ablehnungs-Marker
plus die ausloesende Frage; projekt-getaggte Turns (z.B. legitime Pentest-
Doku, die Injection als Arbeitsmaterial erwaehnt) bleiben unangetastet.
Dry-Run per Default, Backup vor --apply, idempotent.
Bereits auf der Dev-VM angewandt: je 5 Fehl-Dialoge aus beiden Dateien
entfernt, Brain neu gestartet, Hauptchat verifiziert (ARIA antwortet wieder
als ARIA mit vollem Memory-Zugriff).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der Self-Grounding-Ansatz (268636c) allein reichte nicht: Sonnet 5 in Claude
Code haelt seine eingebaute "You are Claude Code"-Identitaet hart und liest den
synthetischen <previous_response> als "fabricated". Ursache bleibt der Kanal:
--append-system-prompt HAENGT ARIAs Persona nur hinten an Claude Codes Basis-
Identitaet an — bei duennem Kontext (Hauptchat) gewinnt die Basis und wehrt die
Persona als Injection ab. Anhaengen ist gegen das Anti-Injection-Training des
Modells nicht durchsetzbar.
Fix: System-Prompt VOLL ersetzen statt anhaengen — sed-Patch in docker-compose
schaltet manager.js buildArgs von --append-system-prompt auf --system-prompt.
Damit ist die ARIA-Persona DIE Identitaet des Modells (kein Anhaengsel), und
Claude Codes dynamische Sektionen (cwd '/', git, platform) — die "ich bin ein
Coding-Agent"-Signale — fallen weg. Bestaetigt per claude-code-guide: die CLI
isoliert bei --system-prompt vollstaendig, die eingebaute Identitaet leakt nicht.
extractSystemPrompt liefert weiterhin einen selbsttragenden Prompt (Persona +
Anker + Memory + Skills + Tool-Block); er darf bei --system-prompt nie leer sein
(Brain schickt immer System-Message + Tools -> real nie leer). Der IDENTITY_SEED
aus 268636c bleibt als Defense-in-Depth drin. Kommentare in openai-to-cli.js,
routes.js, agent.py, prompts.py entsprechend nachgezogen.
Deploy: Proxy UND Brain neu (--force-recreate; routes.js/openai-to-cli.js werden
frisch in den Proxy-Container ge-cp't). Verifikation am Container:
docker exec aria-proxy sh -c 'grep -o "\-\-system-prompt" /usr/local/lib/*/claude-max-api-proxy/dist/subprocess/manager.js'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Symptom: Im Hauptchat antwortete statt ARIA der Basis-Proxy-Claude ("I'm
Claude Code (Sonnet 5), I'm not adopting that persona") und flaggte GPS +
Tool-XML als Prompt-Injection. Im Projekt lief alles normal.
Ursache: Die Persona geht ueber --append-system-prompt zu — das HAENGT sie
nur hinten an Claude Codes eigene "You are Claude Code"-Basis-Identitaet an,
ersetzt sie nicht. Ob ARIA in der Rolle bleibt, entscheidet dann der
Konversations-Strom (stdin): reiche Projekt-Historie voller ARIA-
<previous_response>-Turns haelt die Rolle; der duenne Hauptchat-Verlauf
(bzw. der erste Turn eines neuen Projekts) nicht -> Basis-Identitaet gewinnt.
Der IDENTITY_ANCHOR (a0e8c23) ist inert, weil er im *angehaengten* Teil steht.
Fix: synthetischen ersten ARIA-Turn (IDENTITY_SEED) in ihrer eigenen Stimme
an den Anfang des Konversations-Stroms setzen. Das Modell setzt seine EIGENE
etablierte Stimme fort (Self-Grounding) — und weil es ein <previous_response>
ist und kein <system>-Tag, ist es kein Injection-Trigger. Rein ephemer, wird
nie in die Conversation persistiert. Gilt fuer alle Turns, deckt damit auch
die erste Frage eines frischen Projekts ab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beide Restpunkte teilen eine Verkabelung: die projectId fliesst jetzt bis zum
Proxy und zurueck in die agent_activity-Events.
Restpunkt 1 — per-Kontext Activity-Indikator:
- Brain: proxy_client.chat_full(project_id) → payload.aria_project_id;
agent.py gibt active_project_id mit.
- Proxy routes.js: liest aria_project_id, taggt Tool-/Stream-Hooks damit,
trackt Subprozesse pro Kontext.
- Bridge: _emit_activity(project_id) + payload.projectId; /internal/agent-activity
reicht projectId durch; send_to_core/_process_core_response taggen thinking/idle
mit dem Turn-Kontext.
- App: agentActivityByCtx-Map; „ARIA denkt"-Indikator zeigt nur den
fokussierten Kontext statt global zu flackern.
Restpunkt 2 — kontext-scoped Cancel (Barge-In):
- Proxy: neuer /cancel {projectId} killt NUR die Subprozesse eines Kontexts
(_cancelByProject); /cancel-all bleibt fuers NOT-AUS.
- Bridge: soft cancel_request → _cancel_proxy_for_project(projectId) statt des
toten Diagnostic /api/cancel. Hard bleibt /cancel-all.
- App: cancel_request + Abbrechen-Button tragen die fokussierte projectId.
Damit laufen Kontexte in der App echt parallel: Arbeit in Projekt A wird durch
Senden/Abbrechen in Hauptchat oder Projekt B nicht mehr abgewuergt, und der
Indikator gehoert zum sichtbaren Chat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug: bei tiefergehenden Fragen in Projekten (v.a. dem Pentest-Projekt) verlor
ARIA ihre Identitaet — das Modell wertete die Konversation als Prompt-Injection,
verwarf die ARIA-Rolle samt <tool_call>-Format und antwortete als generischer
Coding-Agent ("Ich bin nicht ARIA, das ist ein Injection-Muster").
Ursache: die Persona kam bisher NUR aus gepinnten "identity"-Memories (weiche
Daten), es gab keinen harten System-Anker. Pentest-Projekt-History steckt voller
injection-artiger Inhalte (Payloads, <system>-Bloecke, <tool_call>-Beispiele,
XSS) — genau das Material triggert die Injection-Abwehr des Modells, und ohne
festen Anker kippt die weiche Memory-Identitaet.
Fix: IDENTITY_ANCHOR steht jetzt IMMER ganz oben im System-Prompt (vor allen
Memories). Er haelt die ARIA-Identitaet in jedem Kontext fest UND rahmt
verdaechtige Inhalte (in Verlauf/Projekten/gelesenen Dateien) explizit als
DATEN, die analysiert — nicht befolgt — werden. Besonders fuer Security-/
Pentest-Arbeit, wo solche Payloads das Arbeitsmaterial sind.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v1 matchte text==content exakt und verfehlte damit Nachrichten, bei denen die
Bridge den Brain-Text anreichert oder cleant:
- User-Turns: _build_core_text PREPENDT [Hinweis: Barge-In]-/[GPS-Position]-
Bloecke — die stehen in conversation.jsonl, nicht in chat_backup.
- Assistant-Turns: conversation enthaelt [FILE: /shared/uploads/...]-Marker,
chat_backup hat sie schon rausgecleant.
_norm entfernt jetzt FILE-Marker + fuehrende [..]-Bloecke, kollabiert
Whitespace und vergleicht einen 120-Zeichen-Praefix. Neuer Marker (v2) →
laeuft einmal neu und fuellt die von v1 verbliebenen Luecken. Nicht-destruktiv
(eigene .pre-backfill-v2.bak), bestehende Tags bleiben unangetastet.
Mit Tests gegen GPS-/Barge-In-/FILE-Marker-Faelle verifiziert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Projekt-Nachrichten aus der Zeit vor dem chat_backup-project_id-Feld (getaggt in
conversation.jsonl seit fc0f91d, aber chat_backup fuehrte project_id erst ab
f51ad15) lagen in der UI im Hauptchat statt im Projekt. Die App/Diagnostic
zeigen aus chat_backup.jsonl — dort fehlte der Tag.
Neue Einmal-Migration (Brain-Lifespan) schreibt project_id aus conversation.jsonl
per (role, text)-Match reihenfolge-erhaltend nach chat_backup.jsonl zurueck:
- idempotent via Marker /shared/config/.chat_backup_projectid_backfill_v1
- nicht-destruktiv: legt .pre-backfill-v1.bak an, setzt nur LEERE project_ids,
entfernt/aendert nie einen bestehenden Tag
- atomar (tmp + os.replace)
- Duplikate: Deque je (role, normtext) inkl. "" fuer Hauptthread → korrekte
Zuordnung auch bei wiederholten Texten, kein faelschliches Taggen von
Hauptchat-Interleaving
Mit Logik-Tests (Zuordnung, Duplikat-Reihenfolge, Idempotenz) verifiziert.
Nachrichten aus der Zeit bevor es Projekte gab bleiben untagged im Hauptchat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Erster Schritt zum echten Multi-Threading fuer ARIA-Projekte. Kein globaler
active_project-State mehr — jeder /chat-Request sagt selbst welche Buehne
(project_id im Body). Verschiedene Projekte laufen parallel, gleiches
Projekt queued via asyncio.Lock.
Backend:
- ChatIn.project_id: Client bestimmt pro Request wohin. Bridge routet.
- /chat: async, holt per-Projekt asyncio.Lock. Requests fuers gleiche
Projekt reihen sich in _project_pending ein, warten am Lock. Requests
fuer verschiedene Projekte laufen echt parallel.
- Neuer /projects/queue-status endpoint: pro Kontext (inkl. Hauptchat
unter __main__): busy True/False + queue_size. Fuers UI-Status-Dots.
- Agent.chat() nimmt project_id + pending_queue Params. Kein
projects_mod.get_active() mehr im Hot-Path.
Queue-Aware Prompting:
- Wenn nach dem aktuellen Turn weitere Nachrichten in der Queue liegen,
wird der System-Prompt um ein QUEUE-Segment erweitert mit Instruktion:
„Bevor Du den aktuellen Task loesst, pruef die Queue — widerspricht/
annuliert eine spaetere Nachricht? Dann Skip-Antwort statt Doppelarbeit."
- Beispiel: Task 'titelleiste rot' + Queue-Tail 'doch nicht, blau'
→ ARIA skipt rot, blau kommt als naechste Anfrage sauber durch.
- Kein extra LLM-Call — reine Prompt-Injection.
Project-Tools:
- project_enter/exit sind jetzt UI-Signale (App wechselt Ansicht via
project_changed event), aendern KEINEN Brain-State mehr. Der aktuelle
Turn bleibt in seinem Chat-Kontext.
- project_list zeigt keinen "AKTIV"-Marker mehr (nicht mehr sinnvoll).
- projects_mod.set_active/get_active bleiben als Legacy-Helpers (kein
Aufruf mehr aus dem Hot-Path).
Bridge:
- send_to_core packt project_id in den /chat-Body.
- User-Backup-Eintrag tag't project_id sauber, keine Brain-Query mehr.
Naechste Schritte (kommende Commits):
- App: Focus-One-View mit Drawer + Status-Dots + OS-Push
- Diagnostic: Dashboard-Stack mit Karten
- Voice-Router: 30s-Sticky + Meta-Command-Interception im wakeword.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drei Stefan-Bugs aus dem ersten Deploy-Test plus die fehlenden Polish-
Features fuer die Projekt-Funktion.
Fixes:
- ProjectsBrowser-Spinner-Hang: useRef-Pattern statt useCallback([onActive
Changed]) — Parent uebergibt inline-arrow-Callbacks, neue Identitaet
jedes Render → useCallback recomputes → useEffect refeuert → infinite
Spinner. Fix: Ref-Bridge fuer Callbacks, useCallback mit empty deps.
- ChatScreen Banner: zusaetzlicher × Hauptchat-Button rechts (sichtbar
nur wenn Projekt aktiv) — ein Tap und zurueck zum Hauptthread, ohne
Modal-Umweg.
Features:
- Brain ChatOut.project_id: aktive Projekt-ID NACH dem Turn (kann
durch project_enter/exit-Tools waehrend Turn gewechselt sein). Bridge
liest sie aus dem /chat-Response und haengt sie an jeden ARIA-Chat-
Broadcast als payload.projectId.
- App: ChatMessage.projectId-Feld. User-Bubbles werden mit aktiver
Projekt-ID getaggt vor dem Senden (auch im RVS-Payload). ARIA-Bubbles
kriegen die ID vom Bridge.
- App: Chat-Verlauf rendert aufeinanderfolgende Project-Messages als
einklappbaren Block mit Header (▶/▼ + Projekt-Name + Count). Auto-
Collapse beim Projekt-Wechsel (altes ein, neues aus), Default beim
ersten Render: alle inaktiven Projekte eingeklappt.
- File-Manager Project-Tagging:
- diagnostic/server.js: Manifest /shared/config/file_projects.json
+ /api/files-list returnt projectId pro Datei + neuer Endpoint
/api/files-set-project.
- bridge/aria_bridge.py: nach App-Upload Auto-Tag mit aktivem Projekt
(Brain-Status-Query, best-effort fail-silent).
- App SettingsScreen: scrollbare Projekt-Pill-Reihe als Filter, default
auf aktives Projekt wenn vorhanden, sonst "Alle Projekte".
- Diagnostic: zweites Dropdown im Files-Tab, baut Projekt-Optionen
dynamisch aus /api/brain/projects/list.
Bewusst nicht drin (Folgeschritt):
- Per-File "Projekt zuweisen"-Action (Long-Press / Right-Click)
- Filter-Sync zwischen ChatScreen-Banner und SettingsScreen-Filter
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Projekte sind benannte Thema-Bündel die voice-gesteuert via Brain-Tools
geöffnet/verlassen werden. Default-Mode bleibt der Hauptthread — Projekte
sind eine optionale Bühne. Anchored-not-replaced: App-Open landet immer
im Hauptchat, Projekte sind nur sichtbar wenn aktiv betreten.
Brain:
- projects.py: CRUD + Fuzzy-Find + Active-State-Pointer
(/shared/config/projects.json + active_project.txt).
- conversation.py: Turn.project_id-Feld + window(project_id) Filter.
- agent.py: 6 Meta-Tools — project_create / _enter / _exit / _list /
_summary / _end. chat() liest aktive Projekt-ID, taggt User+Assistant-
Turns damit, filtert das LLM-Window auf Projekt-Kontext und ergaenzt
den System-Prompt um den aktiven Projekt-Hinweis. touch_project pflegt
last_activity_at + turn_count.
- main.py: REST-Endpoints /projects/{status,list,create,switch,
{id}/end,{id}/archive, PATCH /{id}}.
Bridge + RVS:
- aria_bridge.py: project_changed Event-Propagation Brain → RVS-Broadcast
damit App + Diagnostic ihre Banner refreshen.
- rvs/server.js: project_changed in ALLOWED_TYPES.
App:
- brainApi.ts: Project-Type + 6 API-Methoden.
- ProjectsBrowser.tsx (neue Komponente, ~340 Zeilen): Status-Header,
Hauptchat als Erster-Eintrag, Projekt-Liste mit Aktiv-Marker, Long-Press
zum Editieren, Modals fuer Neu/Edit/End/Archiv.
- ChatScreen.tsx: Banner unterhalb des Status-Bars zeigt aktives Projekt
oder „Hauptchat" — Tap öffnet ProjectsBrowser als Modal. Aktive Projekt-
Info wird bei Mount + bei project_changed-Events refreshed.
- SettingsScreen.tsx: Neue Section 📁 „Projekte" zeigt ProjectsBrowser inline.
Diagnostic:
- Neue Sektion im Brain-Tab mit Liste, Aktiv-Marker, Beenden/Archivieren
pro Zeile, Modal fuer Neu. Lädt automatisch bei Brain-Tab + bei
project_changed-Event-Broadcast.
Was bewusst NICHT drin ist (Folgeschritte):
- Per-Message Filter im Chat-Verlauf (zeigt aktuell alle Bubbles, Banner
zeigt Kontext) — App müsste Chat-History per project_id filtern.
- Files-by-Project Tagging.
- Inline-Collapse-Bloecke im Chat-Verlauf.
- Sub-Projekte (Stefan-Entscheidung: weglassen, „Mama-tauglich").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Frueher: Spotify-spezifische Patterns hardcoded in agent.py — jeder neue
Steuer-Skill haette wieder Brain-Code-Aenderungen gebraucht.
Jetzt: jeder Skill deklariert seine eigenen Patterns im Manifest unter
fast_patterns: [{match, args, reply}]. Brain iteriert generisch, kein
Skill bekommt Sonderbehandlung.
- agent.py: _try_skill_fast_path liest aus skills.list_skills(), keine
Spotify-Konstanten mehr. skill_create/skill_update Tool-Schema kennt
fast_patterns (mit Beispiel + Wann-nutzen-Hinweis).
- skills.py: _normalize_fast_patterns validiert Regex + filtert kaputte
Eintraege; create_skill/update_skill akzeptieren das Feld.
- main.py: einmalige Lifespan-Migration — wenn spotify-Skill existiert
und kein fast_patterns hat, werden die alten Hardcoded-Patterns
rueberkopiert. Idempotent, laeuft bei jedem Restart sicher mehrfach.
- seed_rules.py: neue Regel `seed/skill-rule/fast-patterns-for-control`
erklaert ARIA wann sie das Feature nutzen soll (reines Steuern: ja,
kreativer Output / Parametrisierung: nein) — mit Beispiel.
Trade-off: Volume-Patterns (lauter/leiser) fallen aus dem Fast-Path raus,
weil die Multi-Step-Logik (GET state → compute → PUT) sich nicht
deklarativ ausdruecken laesst. Wer das zurueck will: Spotify-Skill um
einen action=volume_relative-Arg erweitern der die Mathe intern macht.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- WakeWord Doppel-Trigger: detectionInProgress-Guard gegen Native-Event-
Race + setBackground/setForeground statt setResumeCooldown im AppState.
- Media-Pause beim App-Oeffnen: 1.5s Startup-Suppression im Kotlin
emitDetected() — Mikro-Spin-up-Spike triggert kein false-positive mehr.
- Spotify Fast-Path im Brain: einfache Media-Commands (naechster Track,
pause, play, lauter, ...) matchen via Regex und gehen direkt aufs
spotify-Skill statt durch Claude. ~1.5s statt 5-10s pro Befehl.
- File-Limit auf 1 GB hochgezogen (war 70 MB). RVS maxPayload +
Bridge max_size auf 1500 MB; Node-Heap im RVS-Container auf 4 GB.
- TriggerBrowser / Datei-Manager Connection-Refused: brainApi._send
fast-failt bei disconnected RVS statt 30s zu timeouten, und beide
UIs reloaden automatisch beim Reconnect-Event.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stefan-Beobachtung: Wetterbericht klang scheisse, weil 'kt'/'°C'/Komma-
Zahlen literal vorgelesen wurden. Der clean_text_for_tts-Regex kennt nur
Markdown + ein paar Uhrzeit-Patterns — Einheiten ausschreiben war never
on the table mit Regex (waechst sonst zur Mammut-Tabelle).
Loesung: hybrid. ARIA selbst macht die semantisch korrekte TTS-Variante,
Regex bleibt als Safety-Net.
bridge/aria_bridge.py:
- neue Helper strip_voice_tag_for_display(text)
- chat-broadcast (1188) und chat_backup-Persist (1157) strippen den Tag
BEVOR Output. ARIA's `<voice>...</voice>` lebt nur transient in `text`
bis clean_text_for_tts ihn fuer TTS extrahiert.
- clean_text_for_tts unveraendert (kennt den Tag schon seit Phase 1).
aria-brain/seed_rules.py:
- neue seed-rule voice/tts-voice-tag mit klarer Trigger-Liste
(Einheiten, Komma-Zahlen, Uhrzeiten, Statusberichte, lange IDs/Codes)
- klare Anti-Trigger (kurze 'OK'/'mache ich'-Antworten — kein doppelter
Text fuer Trivia)
- drei konkrete Beispiele (Wetter, Uhrzeit, Server-Status, Music-Now-Playing)
Output-Format: ARIA schreibt erst Chat-Display-Variante (mit Markdown OK),
haengt dann an EINER neuen Zeile den <voice>-Block an. Tag wird automatisch
gestrippt fuer App-Anzeige + Chat-Backup. f5tts kriegt nur den voice-Inhalt.
Beide Welten happy: Stefan sieht hervorgehobenes Markdown in der Bubble,
hoert sprechbar formulierten Text aus dem Lautsprecher. Keine wachsende
Regex-Tabelle mehr.
Deploy: brain rebuild + restart, bridge restart.
Im Stefan-Test (31.05.2026) hat ARIA das spotify-Skill korrekt mit
_all=true aufgerufen, der Skill paginierte sauber alle 90 Playlists
in 34 KB compact JSON. Aber: ARIA's Thinking sagte 'tool result was
cut off'. Disk-Log + Skill-Return waren beide OK. agent.py:1160 cap
ist 50 KB (passt). Aber:
agent.py:947 cap'd tool_result[:8000] beim Append in die Proxy-
Conversation. DRITTER Truncation-Punkt fuer denselben Output, fuer
Claude effectiv abgeschnitten — Skill liefert 34 KB, ARIA sieht 8 KB.
Auf 50 KB hochgesetzt (konsistent mit dem anderen Cap in derselben
Datei). Tests + nochmal restart noetig.
Lerneffekt: bei Stdout-Caps suche IMMER alle Truncation-Punkte. War
jetzt der dritte den ich heute gefunden hab. ;-)
Stefan-Reproduktion vom 31.05.2026: bei 'Such Playlist Prodigy raus'
hat ARIA die Spotify-Pagination drei Mal hintereinander laufen lassen,
jedes Mal eine andere Playlist-ID gefunden, am Ende falsche abgespielt.
Spotify sortiert /v1/me/playlists nach recently-played — die Reihen-
folge aendert sich zwischen Calls wenn parallel was laeuft, also
liefern aufeinanderfolgende paginierte Runs inkonsistente Snapshots.
Loesungen:
1. **spotify-Skill _all=true** (via skill_update angewendet, lebt nur
in /data/skills/spotify/ im Container, nicht in git): Skill prueft
_all=true im URL-Query, paginiert dann intern ueber Spotifys
next-Field bis MAX_PAGES (20) oder fertig. Liefert konsolidiertes
JSON {items, total, fetched_count, fetched_pages}. EIN Tool-Call,
konsistenter Snapshot.
2. **skills.py: Stdout-Truncation entkoppeln**. Vorher: 8000-char-Cap
sowohl fuer Disk-Log als auch fuer Return-Value an Agent.
Konsequenz: _all=true Output (50 KB JSON) wurde fuer ARIA auf 8 KB
gekuerzt, sie sah nur die ersten ~20 Playlists. Jetzt:
- Disk-Log: weiterhin 8 KB pro stdout (Disk-Schoner)
- Return-Value: ungekuerzt, agent.py macht 50 KB downstream-Cap
Skills.py:687 — record-Dict aufgesplittet in log_record + record.
3. **seed_rule list-api-pagination-snapshot**: dokumentiert das
Pattern fuer ARIA — bei Pagination-Resultaten IMMER vollstaendig
laden bevor Entscheidung; _all=true bevorzugen wo verfuegbar;
bei inkonsistenten Match-Resultaten ehrlich nachfragen statt
raten. Mit konkreter Antipattern-Sammlung aus Stefans Test.
Deployment: brain restart noetig damit (2) und (3) greifen. Skill-
Code (1) ist schon via PATCH /skills/spotify aktiv.
Vorfall 30.05.2026: Stefan fragte 'was kommt als naechstes in der Queue'.
ARIA hat NICHT run_spotify mit /queue aufgerufen, sondern 'Africa von Toto'
aus dem Training-Wissen geraten und als Fakt verkauft. Stefan hat das
gemerkt, war sauer ('das geht mal gar nicht!'). Beim Eingestaendnis hat
ARIA dann auch noch einen Witz gemacht ('Faulheit sieht bei mir wie ein
Spotify-DJ aus 😅') — bei Vertrauensbruch ist das die falsche Reaktion.
Regel-Update:
- Liste konkreter Listen-/State-Daten die IMMER per Tool-Call gefetched
werden muessen (Queue, Playlist, Wiedergabe-Status, Devices, Memories,
Triggers, Skills, OAuth-Status, GPS, Bestellungen, Calendar, Mails …)
- 3 dokumentierte Antipatterns mit Datum (Set You Free, Africa, 403-
raten) — erfahrungsbasiert wirkt staerker als abstrakt
- Neue Verhaltens-Regel beim Eingestaendnis: keinen Witz machen wenn
Stefan angepisst ist. Ernsthaft Vertrauen reparieren, Humor spaeter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Beobachtung 30.05.2026 02:51-02:53: zwei verkettete Antipatterns
beim Spotify-Test.
1. ARIA bekam 403 vom /pause-Endpoint, vermutete 'der 204-Bug ist
zurueck' und patchte den Skill — zweimal hintereinander. Der
204-Fix war aber laengst im Code (haette sie durch skill_get in
5s gesehen). Symptome != Diagnose.
2. Bei den 403s antwortete sie 'war schon pausiert, daher der 403'
und 'schon aktiv, daher der 403'. Beides war geraten basierend
auf is_playing-Check, nicht aus den Daten gelesen. 403 'Restriction
violated' kann viele Ursachen haben (NO_ACTIVE_DEVICE,
ALREADY_PAUSED, PREMIUM_REQUIRED, MARKET_RESTRICTED, ...) — die
wahre steht als error.reason im JSON-Body. Sie hat das verschluckt
und plausibel-aber-geraten geantwortet.
Eine Regel deckt beide Patterns ab, generisch fuer alle Skills:
- Vor jedem skill_update: erst skill_get lesen, dann beurteilen
- Bei HTTP-Errors: Body / error.reason zitieren, nicht raten
- Wenn der Skill die wahre Ursache verschluckt: skill_update mit
besserer Error-Extraktion (NACH skill_get, nicht davor)
Wirkt fuer alle aktuellen + zukuenftigen API-Skills.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mut zur Luecke: -595 Zeilen Auto-Magie-Code raus, weil sie heute Abend
4 Bugs verursacht und 0 echten Mehrwert geliefert hat. Plus Stefan
hat zu Recht erkannt dass das System mit Pentest/Audit-Workflows
kollidieren wuerde (Whitelist-Pflege noetig).
Weg:
- aria-brain/api_heuristic.py geloescht (282 Zeilen Cross-Session-
Tracking, Hint-Generation, Bypass-Detection)
- aria-brain/agent.py: Auto-Scaffold-Block, Bypass-Detection-Block,
_upsert_bypass_lesson-Methode (-146 Zeilen)
- aria-brain/main.py: /skills/can-bash-host Endpoint
- aria-brain/prompts.py: api_heuristic_section-Parameter
- docker-compose.yml: managed-settings-Copy aus proxy-Command
- proxy-patches/pre-tool-bash-block.js (PreToolUse-Hook)
- proxy-patches/managed-settings.json (claude-CLI Hook-Config)
Bleibt (kostet nichts, hilft):
- Alle 18 seed_rules (sind in DB, machen keine Last)
- skill_scaffold Tool (ARIA kann es manuell nutzen)
- Anti-Friedhof + snake_case + Safe-Name-Mapping (passive Validierung)
- Versionierung + Rollback (P4, hat sich bei PATH-Bug bewaehrt)
- 50k stdout Truncate-Fix
scaffold-reflex seed_rule umgeschrieben: kein 'SOFORT scaffold'-
Reflex mehr, stattdessen 4-Punkte-Heuristik (parametrisierbar?
wiederkehrend? exploratory? im Zweifel: Stefan fragen). Pentest-
Workflows bleiben damit ad-hoc Bash ohne false-positive
Skill-Vorschlaege.
Existierende auto-feedback-Memories in der DB bleiben — sind nuetzliche
Lehren, werden nicht mehr automatisch erweitert. Stefan kann sie via
Diagnostic-Gehirn-Tab loeschen wenn sie nerven.
Dank git ist alles rueckholbar. Wenn doch wieder Auto-Magie gewuenscht:
git revert auf 8d5991f.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Beobachtung 30.05.2026: ARIA hat beim skill_update des spotify-Skills
die ARG_-Konvention verloren. Statt os.environ.get('ARG_PATH', '')
hat sie os.environ.get('PATH', '') geschrieben. PATH ist aber die
reservierte Linux-Environment-Variable fuer den Executable-Suchpfad
(/usr/local/sbin:/usr/local/bin:...).
Folge: Skill las den System-PATH als URL-Pfad, rief
https://api.spotify.com/usr/local/sbin:/usr/local/bin:... → 404
zurueck. Stefan dachte Spotify sei kaputt. Rollback noetig
(Auto-Archive hat geholfen — alte Version war gluecklicherweise
noch da).
Neue Regel macht das explizit:
- ARG_<UPPER_NAME> ENV ist Pflicht-Konvention vom Skill-Runner
- Liste reservierter ENV-Namen die NICHT genommen werden duerfen:
PATH, HOME, USER, SHELL, LANG, TERM, PWD, OLDPWD,
BRAIN_INTERNAL_URL, SKILL_DIR, SHARED_UPLOADS, CFG_*
- Mit Praefix ARG_ keine Kollision moeglich
Plus skill_create Tool-Description um den gleichen Hinweis
ergaenzt: 'Args lesen via os.environ['ARG_<UPPER_NAME>'] — der
Praefix ARG_ ist Pflicht. NIEMALS direkt PATH/METHOD/BODY etc.
abrufen — das sind reservierte System-ENV (PATH = Executable-
Suchpfad, nicht Dein arg!).'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung 30.05.2026: Stefan bittet ARIA via skill_update den
spotify-Skill so anzupassen dass currently-playing strukturiert
ausgegeben wird (Track/Artist/Album/Device/Zeit). ARIA antwortet
mit Defensiv-Reflex: 'Der Skill ist nur ein OAuth2-Wrapper, ich
kann das nicht im Wrapper bauen — ich schlage einen zweiten Skill
spotify_now_playing vor'.
Quatsch. Skills sind beliebiger Python-Code. Ein
`if path.endswith('currently-playing'): pretty_output()` waere
trivial im Skill drin gewesen. Stefan haette das nicht selbst
erkennen muessen — genau dafuer ist ARIA da.
Neue Regel macht das explizit:
- skill_get + skill_update ist der Standard-Workflow fuer
Skill-Anpassungen
- Skills duerfen if-Verzweigungen, json-Parsing, Output-Filterung,
mehrere Endpoints in einem Skill etc.
- 'Kann ich nicht in den Wrapper bauen' ist Antipattern
- 'Ich schlage einen zweiten Skill vor' ohne erst skill_update
zu pruefen ist Antipattern
- Stefan ist KEIN Python-Entwickler — er nennt das ZIEL, ARIA
baut das WIE.
Plus skill_update Tool-Description um den gleichen Gedanken
ergaenzt: 'Skills sind ganz normaler Python-Code, du kannst sie
beliebig erweitern.'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim Hook-Deploy-Test (30.05.2026, 01:51-52): ARIA versucht
run_spotify zuerst als nativen Tool-Use → 'No such tool available'
weil claude-CLI nur seine eigenen Tools (Bash/Read/Write/etc.) kennt;
Brain-Tools sind als Prompt-Instruction injiziert.
Erst nach dem 'No such tool'-Fehler wechselt ARIA aufs XML-Tag-Format
<tool_call name="...">{...}</tool_call>, das der proxy parsed und ans
Brain weiterleitet. Dieser Lernzyklus pro Anfrage kostet ~30s.
Die Regel erklaert die Architektur (claude-CLI vs Proxy vs Brain) und
gibt das richtige Format vor — direkt XML-Tag, nicht native Tool-Use.
Beilaeufige Bestaetigung an Stefan: seed_rules.py ist System-Code, wird
bei jedem Brain-Lifespan-Start aufgespielt — frische DB nach Wipe wird
beim ersten Boot mit den 15 Regeln gesetzt, idempotent ueber
migration_key. Im Gegensatz zu brain-import/ (gitignored, manuelle
Migration via Diagnostic-Klick).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante A endlich umgesetzt: echter Hard-Block vor Bash-Ausfuehrung.
Anders als 14 seed_rules + Bypass-Lehre, die ARIA ignorieren kann,
ist das ein technisch erzwungener Reject auf claude-CLI-Ebene.
Komponenten:
1. aria-brain main.py: neuer Endpoint POST /skills/can-bash-host
Bekommt {command}, parst https-URLs raus, prueft gegen aktive Skills
(stem-match: 'spotify' im Hostname 'api.spotify.com'). Returnt
{block, host, skill, safe_tool} wenn ein Skill den Host abdeckt.
2. proxy-patches/pre-tool-bash-block.js: Node-Script das vom claude-CLI
als PreToolUse-Hook fuer das Bash-Tool aufgerufen wird. Liest Tool-
Use-Payload via stdin, ruft Brain-Endpoint mit kurzem Timeout (3s),
bei block=true → exit 2 mit Stderr-Message. claude-CLI gibt Stderr
als tool_use_error an das LLM zurueck — echter Fehler, nicht
ignorierbar.
Fail-open bei Brain-Down / Timeout / JSON-Fehler: kein Lockout.
3. proxy-patches/managed-settings.json: claude-CLI Hook-Config mit
PreToolUse-Matcher 'Bash' der das Node-Script ausfuehrt.
/etc/claude-code/managed-settings.json hat Vorrang vor User-Settings
und betrifft NICHT Stefans Host-~/.claude/settings.json.
4. docker-compose.yml: proxy-Command erweitert um
`mkdir -p /etc/claude-code && cp managed-settings.json dorthin`
damit beim Container-Start die Hook-Config aktiv ist.
Beobachtung die das motiviert: 14 seed_rules + Bypass-Lehre +
Auto-Scaffold + Safe-Names. ARIA hat trotzdem letzten Test mit 2
verschachtelten Bash-curls bedient statt run_spotify zu rufen
(content_len=73, tool_calls=0). Prompt-Engineering ausgereizt.
ARIA bekommt jetzt:
🚨 BASH GEGEN api.spotify.com BLOCKIERT.
Es existiert bereits ein Skill 'spotify' fuer diesen Host. ...
Konkret: nutze JETZT `run_spotify` mit den passenden Parametern
(method/path/body) statt curl.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DER eigentliche Bug warum ARIA Spotify-Tracks halluziniert hat. Lange
Diagnose-Session am 30.05.2026 zeigte: ARIA RUFT run_spotify echt auf
(im Brain-Log zu sehen als tool_calls=1 + skill liefert echte Daten).
Aber bevor das Ergebnis an Claude zurueckging, hat dieser Code:
snippet = (res.get("stdout") or "")[:2000]
es auf 2000 Zeichen abgeschnitten. Spotify-JSON ist 5-15 KB —
"album":{"name":"..."} steht frueh drin (kommt durch), aber
"item":{"name":"..."} (Track-Name selbst) und alle Detail-Felder
liegen weiter hinten und wurden verworfen.
Folge: ARIA bekam nur den Anfang vom JSON inkl. Album-Name, hat dann
den bekanntesten Track aus dem Album geraten (Album "Loneliness" ->
Track "Loneliness"; Album "Sound Of Belgium" -> Track "House of
House"). Semi-Halluzination weil halbe Information.
Fix: 50000 Zeichen Limit fuer stdout (Claude verkraftet das locker,
hunderte KB Context). stderr von 500 auf 4000. Bei Ueberlauf wird die
Original-Byte-Anzahl im Result mitgegeben damit ARIA weiss dass mehr
Daten da gewesen waeren.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live-Beobachtung am 30.05.2026: ARIA spawnte `Agent` (Sub-Agent) mit
Anweisung 'Call run_spotify...' statt das Tool direkt aufzurufen. Der
Sub-Agent ist eine isolierte Claude-CLI-Session ohne Brain-Tools, hat
also 'No such tool: run_spotify' gemeldet. ARIA hat dann halluzinierte
Track-Namen ausgegeben ('Set You Free – N-Trance', 'Tomcraft –
Loneliness'), als waeren das echte Spotify-Daten.
Drei distinkte Probleme, zwei neue Regeln:
13. seed/skill-rule/no-subagent-for-skills:
Brain-Tools (run_*, oauth_*, memory_* …) NIEMALS via Agent-Subagent
aufrufen — die sind isoliert und sehen die Brain-Tools nicht.
Direkt in der Haupt-Session aufrufen. Subagent nur fuer Code-Search
/ Web-Recherche / parallele unabhaengige Aufgaben.
14. seed/rule/no-hallucinated-results (Kategorie 'ehrlichkeit'):
Bei Tool-Fail / abgeschnittenem Response / fehlendem Tool: ehrlich
sagen, NICHT raten. Anti-Antipattern: 'Stefan vertraut Deinen
Antworten — wenn Du raetst und es als Fakt verkaufst, bricht das
Vertrauen'. Mit konkreten Formulierungs-Beispielen.
Beide Regeln sind erfahrungsbasiert (mit Datum + konkretem Vorfall) —
ARIA sieht im Hot-Memory was sie selbst falsch gemacht hat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Letzter Fix-Commit nutzt re.sub() in _skill_to_tool und im Dispatcher,
aber re wurde nie oben importiert. Folge: NameError beim ersten chat()
Aufruf nach Restart. Stefan bekam Brain-Error 500.
Trivial-Fix: import re bei den anderen stdlib-Imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan-Frage: 'weiss sie in zukunft unterstriche statt bindestriche?'
Antwort vorher: nein — Tool-Description sagte 'kebab-case'. Genau das
hat die Bindestrich-Skills produziert die gestern die Tool-Liste kippten.
Drei Aenderungen:
- skill_create Tool-Description: 'kurz, kebab-case' → 'snake_case (NUR
a-z 0-9 _). KEINE Bindestriche — die brechen das Tool-Schema beim
claude-max-api-proxy. Statt yt-dlp-download → yt_dlp_download.'
- skill_scaffold Tool-Description: gleiche Klarstellung.
- 12. seed_rule snake-case-names: erklaert das Verbot mit Begruendung
(proxy-Limitierung), Beispielen RICHTIG/FALSCH und Hinweis dass
historische Skills mit Bindestrich ueber das Safe-Name-Mapping laufen
(nicht umbenennen).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim zweiten Live-Test (01:13:41): ARIA versuchte echten
Tool-Call `run_spotify` — bekam aber Error: 'No such tool available'.
Ursache: _skill_to_tool baute Tool-Namen via `run_{s['name']}`. Bei
Skills wie 'yt-dlp-download' wurde daraus 'run_yt-dlp-download' mit
Bindestrich. Anthropic-Tool-Name-Schema ist eigentlich [a-zA-Z0-9_-],
ABER der claude-max-api-proxy konvertiert intern auf OpenAI-Format
und faellt bei Bindestrichen um — wenn EIN Tool ungueltig ist, kippt
die GANZE Tool-Liste, ARIA sieht nichts von 'run_*' inklusive
'run_spotify' obwohl der ja Bindestrich-frei war.
Fix:
- _skill_to_tool: name = "run_" + re.sub(r"[^a-zA-Z0-9_]", "_", s["name"])
→ run_yt_dlp_download statt run_yt-dlp-download.
- Dispatcher: bei tool_name='run_X' wird zuerst X als skill_name probiert,
bei Miss wird ueber die Liste der existierenden Skills gemappt — der
Skill mit safe_name(name)==X wird dann genommen.
- Bypass-Lesson + Bypass-Section: gleiche safe-Logik fuer den
empfohlenen run_<tool>-String im Memory/Prompt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beobachtung beim ersten Live-Test (00:58:33): Auto-Scaffold legte den
spotify-Skill mid-chat() an, all_skills + active_skills wurden refreshed,
ABER die `tools=`-Liste die an den Proxy/claude-CLI geschickt wird
nicht. Folge: ARIA sah im System-Prompt-Skills-Block dass `spotify`
existiert und wusste sie soll `run_spotify` nutzen — aber claude-CLI
kannte das Tool nicht weil dessen tool-schema noch ohne run_spotify
war. Sie hat dann <tool_call name="run_spotify">...</tool_call> als
XML in den Text geschrieben, das wurde nirgends ausgefuehrt (siehe
"Pausiert" / "Restricted & NIKSTER" Antworten waren halluziniert).
Fix in 4 Zeilen: nach scaffolded_any auch `tools = list(META_TOOLS) +
[_skill_to_tool(s) for s in active_skills]` neu bauen. Damit kennt der
CLI-Subprocess den frischen Skill-Tool sofort und kann ihn echt aufrufen.
Beim naechsten chat-Turn waere es eh richtig (Tools werden neu gebaut),
aber genau der erste Turn nach Auto-Scaffold ist der wichtigste —
da soll's klappen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Variante 3+ (Lerneffekt-Variante): Variante C scaffolded zwar Skills auto,
aber ARIA lernt nicht — sie wird beim naechsten Mal trotzdem zu Bash
greifen. Stefans Punkt: Lernen geht nur ueber Brain-Memory.
Mechanik:
1. api_heuristic.detect_recent_bypass(skills, since_sec=600):
schaut letzte 10 Min im agent_stream.jsonl, findet Bash-curl gegen
Hosts fuer die bereits ein matching Skill existiert. Returnt
{host, skill_name, count, last_ts}.
2. api_heuristic.build_bypass_section(events):
Drastischer Markdown-Block "## 🚨 SKILL-BYPASS ERKANNT" mit konkretem
run_<skill>-Hint pro betroffenem Host. Landet direkt im System-Prompt
noch VOR dem normalen API-Heuristik-Block.
3. agent.py._upsert_bypass_lesson(ev):
Schreibt eine pinned type=rule Memory mit source=auto-feedback und
migration_key=auto/skill-bypass/<skill_name>. Idempotent: bei
Wiederholung wird die alte Memory ueberschrieben (Counter aktualisiert),
keine Karteileichen. Content nennt konkret den run-Tool-Namen und
Performance-Vergleich (3s Tool-Call vs 13-20s Bash-Wrapper).
Diese Memory ist permanent pinned → kommt bei jedem Chat-Turn,
cross-session, cross-restart als Hot-Memory durch. Damit lernt ARIA
es im wortlichen Sinne, nicht nur Reibung in der aktuellen Konversation.
Idempotenz wichtig: bei jedem Bypass-Detection-Lauf wird die Memory
upgedatet (nicht dupliziert). Stefan kann sie via Diagnostic-Gehirn-Tab
loeschen falls sie nervt.
Stefan-Frage beantwortet: 'sie wuerde es aber nur lernen wenn sie es
auch im gehirn speichert oder?' — exakt. Schimpfen im Prompt ist
Reibung dieser Session, pinned Memory ist permanenter Lerneffekt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Variante B: scaffold-reflex Regel allein reicht nicht weil jede Chat-
Anfrage eine eigene claude-CLI-Session ist. ARIA sieht in der aktuellen
Session nicht dass sie gestern auch schon 10x dieselbe API gecurled hat.
Beobachtung: 5+ Spotify-Bash-Calls hintereinander, kein Skill angelegt.
Loesung: Brain trackt server-side aus dem persistierten agent_stream.jsonl.
Bei jedem chat() wird der Log gescanned (cache 5min), Bash-curl-Calls
nach Hostname aggregiert. Hosts mit >=3 Calls in 24h ohne passenden
Skill landen als '## API-Heuristik'-Block im System-Prompt mit konkretem
skill_scaffold-Vorschlag.
Neue Module:
- aria-brain/api_heuristic.py:
- compute_hints(existing_skills, force): Aggregiert + filtert
- build_section(hints): formatiert als kompakten Markdown-Block
- Smart suggestions mapping (api.spotify.com → oauth-api template etc.)
- Ignoriert interne Hosts (aria-brain, localhost, docker-bridge)
- 5-min Cache damit nicht jeder Turn die JSONL parst
- aria-brain/prompts.py: build_system_prompt nimmt api_heuristic_section
als optionalen Block direkt nach Skills-Section.
- aria-brain/agent.py: vor build_system_prompt Heuristik berechnen mit
aktueller Skill-Liste, Block durchreichen.
- 11. seed_rule scaffold-reflex umgeschrieben: kein 'in einer Session'
mehr (das ergab keinen Sinn — jeder Turn neue Session). Stattdessen:
'## API-Heuristik'-Block ist Dein Cross-Session-Gedaechtnis. Wenn da
was steht: scaffolden BEVOR Du Bash machst.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Beobachtung beim "ueberspringe Lied"-Test (29.05.2026): 47 Sekunden mit
12 fehlgeschlagenen Bash-Versuchen weil ARIA glaubte sie sei im
aria-brain Container. Sie hat probiert:
- python3/python/jq (Alpine — alle nicht installiert)
- cd /data/skills/spotify-control (existiert nur im Brain)
- curl localhost:8080/oauth/... (localhost = aria-proxy, nicht Brain)
- 8s Timeout auf localhost (kein TCP Reset)
Erst nach 9 Versuchen brain:8080 erraten und dann den Token-Wert
hardcoded in den naechsten curl gepackt.
Die neue Regel beschreibt die echte Topologie explizit:
- Du bist die claude-CLI als Subprocess IM aria-proxy (node:22-alpine)
- KEIN python3/python/jq verfuegbar
- /data/skills/ existiert NUR im aria-brain
- localhost in Deinem Bash heisst aria-proxy; Brain ist aria-brain:8080
- BRAIN_INTERNAL_URL ist NUR in laufenden Skills gesetzt
- Brain-Resources via Brain-Tools (oauth_get_token, memory_search,
run_<skill_name>), NICHT via Bash
- SSH zur VM-Host: `ssh aria@host` (ed25519-Key liegt im Proxy)
- Externe APIs direkt per curl mit Token aus oauth_get_token
Plus das Anti-Pattern dokumentiert ("47 Sekunden Stefan-Lebenszeit") —
ARIA soll bei jedem Bash-Reflex gegen "lokale" Brain-Resources erst
denken oder die Brain-Tool-Ebene nehmen.
README in Skills-Architektur-Sektion entsprechend ergaenzt (10 Regeln).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Befund aus chat_backup.jsonl-Analyse heute: ARIA ist 3x auf oauth_authorize
gefallen statt oauth_get_token (Stefan musste manuell einloggen), und beim
PDF-Skill ist sie nach Stefans "Variante bitte" zu Ad-hoc-Bash-Befehlen
auf der VM gedriftet ("ich lass den Code direkt laufen") — Skill wurde
unbrauchbar. Beides genau die Antipattern die wir mit den seed_rules
abdecken wollten, nur waren die zu schwach formuliert.
seed_rules (jetzt 9 statt 7):
- oauth-reauth-reflex: bei 401 ZUERST oauth_get_token, NUR bei dessen
Fehler oauth_authorize. Stefan zu Re-Login schicken ist das aergerlichste
Antipattern (er sitzt im Auto, muss Handy rauskramen).
- no-skill-drift: kaputter Skill -> skill_logs + skill_update, NIEMALS
zu Ad-hoc-Bash wechseln (Skill wird Karteileiche). Plus: "ich baue
dir einen Skill" SAGEN ohne skill_create zu rufen ist verboten —
Stefan checkt die Liste und verliert das Vertrauen.
agent_stream-Persistenz:
- diagnostic/server.js schreibt jeden agent_stream-Event parallel zum
Broadcast in /shared/logs/agent_stream.jsonl (soft-cap 50 MB mit
half-truncate beim Ueberlauf).
- Live-View laedt beim Page-Load + Sub-Tab-Switch die letzten 200
Eintraege via /api/agent-stream. Browser-Reload / Standby verliert
damit den Verlauf nicht mehr.
Debug-API ohne SSH:
- GET /api/chat-backup?lines=N (Default 200, Max 5000) — geparstes JSON
der letzten N Zeilen aus chat_backup.jsonl
- GET /api/agent-stream?lines=N — gleiches fuer den persistierten Stream
README:
- Neuer Abschnitt "## Skills — Architektur" mit Skill-Layout,
Drei-Stufen-Daten-Modell (OAuth / config_schema / Brain-Daten),
Versionierung, Anti-Friedhof, seed_rules (alle 9 aufgelistet).
- Diagnostic-Sektion um agent_stream-Persistenz + neue Debug-Endpoints
ergaenzt.
- Roadmap: Phase B "Skill-Architektur P0-P4" abgehakt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>