From 2284c1a7806a599e0082b9b76d14f494a622ec18 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 10 Jul 2026 22:44:02 +0200 Subject: [PATCH] fix(proxy): ARIA-Persona als VOLLER System-Prompt-Replace (--system-prompt) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- aria-brain/agent.py | 15 ++++++--------- aria-brain/prompts.py | 29 ++++++++++++++--------------- docker-compose.yml | 2 +- proxy-patches/openai-to-cli.js | 22 ++++++++++++++-------- proxy-patches/routes.js | 4 ++-- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index acf16b3..f1b12f5 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1190,15 +1190,12 @@ class Agent: f"weiterfuehren': project_enter aufrufen." ) messages = [ProxyMessage(role="system", content=system_prompt)] - # Identitaets-Grounding IM Konversations-Strom. Der Proxy stellt die - # Persona via --append-system-prompt zu, das haengt sie aber nur HINTEN - # an Claude Codes Basis-Identitaet an. Bei duennem Verlauf (Hauptchat, - # erster Turn eines neuen Projekts) gewinnt sonst "You are Claude Code" - # und ARIA faellt aus der Rolle. Ein synthetischer erster ARIA-Turn in - # ihrer eigenen Stimme verankert die Rolle so, wie es reiche Projekt- - # Historie ohnehin tut (Self-Grounding, kein -Tag -> kein - # Injection-Trigger). Rein ephemer — wird NIE persistiert. Siehe - # IDENTITY_SEED in prompts.py. + # Identitaets-Grounding IM Konversations-Strom (Defense-in-Depth neben + # dem vollen System-Prompt-Replace via --system-prompt). Ein + # synthetischer erster ARIA-Turn in ihrer eigenen Stimme haelt die Rolle + # per Self-Grounding auch bei duennem Verlauf (Hauptchat / erster Turn + # eines neuen Projekts). Kein -Tag -> kein Injection-Trigger. + # Rein ephemer — wird NIE persistiert. Siehe IDENTITY_SEED in prompts.py. messages.append(ProxyMessage(role="assistant", content=IDENTITY_SEED)) # Conversation-Window auf das aktive Projekt filtern: in einem Projekt # sieht der LLM nur die Projekt-Turns (sauberer Kontext); im Hauptthread diff --git a/aria-brain/prompts.py b/aria-brain/prompts.py index f2adf11..9c7f9bc 100644 --- a/aria-brain/prompts.py +++ b/aria-brain/prompts.py @@ -54,23 +54,22 @@ vorgesehen — nutze es ganz normal. """ -# Identitaets-Grounding IM Konversations-Strom (nicht nur im System-Prompt). +# Identitaets-Grounding IM Konversations-Strom — Defense-in-Depth. # -# Der Proxy stellt die Persona ueber --append-system-prompt zu. Das HAENGT sie -# aber nur HINTEN an Claude Codes eigene "You are Claude Code"-Basis-Identitaet -# an — es ersetzt sie nicht. Ob ARIA in der Rolle bleibt, entscheidet dann der -# Gespraechsverlauf im stdin-Prompt: -# - Projekt: reiche Historie voller ARIA--Turns haelt die -# Rolle -> funktioniert. -# - Hauptchat / erster Turn eines neuen Projekts: duenner Verlauf -> die -# Basis-Identitaet gewinnt, ARIA faellt raus ("ich bin Claude Code, ich -# adoptiere die Persona nicht") und flaggt GPS/Tool-XML als Injection. -# Deshalb setzen wir einen synthetischen ERSTEN ARIA-Turn in IHRER eigenen +# PRIMAERER Fix ist der volle System-Prompt-Replace (--system-prompt statt +# --append-system-prompt, siehe docker-compose.yml + openai-to-cli.js): damit +# ist die ARIA-Persona DIE Identitaet des Modells, nicht ein Anhaengsel hinter +# Claude Codes "You are Claude Code". Zuvor (--append) hat die eingebaute +# Identitaet bei duennem Kontext (Hauptchat, erster Turn eines neuen Projekts) +# gewonnen und ARIA aus der Rolle geworfen ("ich bin Claude Code, ich adoptiere +# die Persona nicht"), samt Flaggen von GPS/Tool-XML als Injection. +# +# ZUSAETZLICH setzen wir einen synthetischen ERSTEN ARIA-Turn in IHRER eigenen # Stimme an den Anfang des Konversations-Stroms. Das Modell setzt seine EIGENE -# etablierte Stimme fort (Self-Grounding) — und weil es ein -# ist und KEIN -Tag, ist es auch kein Injection-Trigger. Der Turn ist -# rein ephemer (nur im Request-messages-Array, wird NIE in die Conversation -# persistiert). +# etablierte Stimme fort (Self-Grounding) — greift auch dann, falls eine CLI- +# Version --system-prompt mal nicht sauber isoliert. Weil es ein +# ist und KEIN -Tag, ist es kein Injection-Trigger. +# Rein ephemer (nur im Request-messages-Array, wird NIE persistiert). IDENTITY_SEED = ( "Ich bin ARIA, Stefans persoenliche Assistentin. Ich bin einsatzbereit und " "antworte durchgehend als ARIA — im Hauptchat wie im Projekt, bei kurzen " diff --git a/docker-compose.yml b/docker-compose.yml index 4435d7d..52373ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: DIST=$$(find /usr/local/lib -path '*/claude-max-api-proxy/dist' -type d | head -1) && sed -i 's/startServer({ port })/startServer({ port, host: process.env.HOST || \"127.0.0.1\" })/' $$DIST/server/standalone.js && sed -i 's/\"--no-session-persistence\",/\"--no-session-persistence\",\"--dangerously-skip-permissions\",/' $$DIST/subprocess/manager.js && - sed -i 's/\"--dangerously-skip-permissions\",/\"--dangerously-skip-permissions\",\"--append-system-prompt\",options.systemPrompt,/' $$DIST/subprocess/manager.js && + sed -i 's/\"--dangerously-skip-permissions\",/\"--dangerously-skip-permissions\",\"--system-prompt\",options.systemPrompt,/' $$DIST/subprocess/manager.js && sed -i 's/const DEFAULT_TIMEOUT = 300000;/const DEFAULT_TIMEOUT = 86400000;/' $$DIST/subprocess/manager.js && sed -i '/prompt, \\/\\/ Pass prompt as argument/d' $$DIST/subprocess/manager.js && sed -i 's|this\\.process\\.stdin?\\.end();|this.process.stdin?.end(prompt);|' $$DIST/subprocess/manager.js && diff --git a/proxy-patches/openai-to-cli.js b/proxy-patches/openai-to-cli.js index d378975..f3c271a 100644 --- a/proxy-patches/openai-to-cli.js +++ b/proxy-patches/openai-to-cli.js @@ -153,9 +153,12 @@ export function messagesToPrompt(messages, tools) { /** * Extrahiert NUR den System-Anteil (System-Messages + Tool-Use-Block) als * rohen Text — OHNE -Tags. Fuer den ECHTEN System-Prompt-Kanal der - * Claude-CLI (--append-system-prompt), damit die ARIA-Persona nicht als - * -getaggter User-Content ankommt (den das Modell als Injection wertet), - * sondern als genuine System-Instruktion. + * Claude-CLI (--system-prompt, VOLLER Replace — nicht --append). Damit ist + * die ARIA-Persona DIE Identitaet des Modells und nicht ein Anhaengsel hinter + * Claude Codes eigener "You are Claude Code"-Identitaet (die bei duennem + * Kontext sonst gewinnt und die Persona als Injection abwehrt). Der Output + * muss deshalb SELBSTTRAGEND sein — er ersetzt Claude Codes System-Prompt + * komplett inkl. dynamischer Sektionen (cwd, git, platform). * Reihenfolge: erst der Tool-Use-Block (Format-Anweisung), dann die * System-Messages in Original-Reihenfolge. */ @@ -217,12 +220,15 @@ export function conversationToPrompt(messages) { export function openaiToCli(request) { // Persona/System + Tool-Block gehen ueber den ECHTEN System-Prompt-Kanal - // (--append-system-prompt, siehe manager.js buildArgs-Patch). Der Prompt - // enthaelt nur noch den Gespraechsverlauf — so kann das Modell die - // ARIA-Vorgaben nicht mehr als -getaggten User-Content und damit als - // Prompt-Injection fehldeuten. + // (--system-prompt = VOLLER Replace, siehe manager.js buildArgs-Patch in + // docker-compose.yml). Der Prompt enthaelt nur noch den Gespraechsverlauf. + // Voller Replace statt --append, weil Anhaengen Claude Codes eingebaute + // "You are Claude Code"-Identitaet stehen laesst — die bei duennem Kontext + // (Hauptchat) gewinnt und die ARIA-Persona als Injection abwehrt. // systemPrompt ist immer ein String (extractSystemPrompt liefert "" statt - // undefined) → --append-system-prompt "" ist harmlos, kein spawn-Crash. + // undefined). ACHTUNG: bei --system-prompt darf er NIE leer sein, sonst + // laeuft das Modell ganz ohne System-Prompt — der Brain schickt aber immer + // eine System-Message + Tool-Block, also ist er real nie leer. return { prompt: conversationToPrompt(request.messages), systemPrompt: extractSystemPrompt(request.messages, request.tools), diff --git a/proxy-patches/routes.js b/proxy-patches/routes.js index 4c5e2c3..a6ac67c 100644 --- a/proxy-patches/routes.js +++ b/proxy-patches/routes.js @@ -363,8 +363,8 @@ async function handleStreamingResponse(req, res, subprocess, cliInput, requestId model: cliInput.model, sessionId: cliInput.sessionId, // ARIA: echter System-Prompt-Kanal — manager.js reicht das (sobald - // gepatcht) als --append-system-prompt an die CLI. Aktuell ignoriert - // ein ungepatchter manager diese Extra-Option gefahrlos. + // gepatcht) als --system-prompt (VOLLER Replace) an die CLI. Aktuell + // ignoriert ein ungepatchter manager diese Extra-Option gefahrlos. systemPrompt: cliInput.systemPrompt, }).catch((err) => { console.error("[Streaming] Subprocess start error:", err);