fix(proxy): ARIA-Persona als VOLLER System-Prompt-Replace (--system-prompt)
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 aus268636cbleibt 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>
This commit is contained in:
+6
-9
@@ -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 <system>-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 <system>-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
|
||||
|
||||
+14
-15
@@ -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-<previous_response>-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 <previous_response>
|
||||
# ist und KEIN <system>-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
|
||||
# <previous_response> ist und KEIN <system>-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 "
|
||||
|
||||
+1
-1
@@ -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 &&
|
||||
|
||||
@@ -153,9 +153,12 @@ export function messagesToPrompt(messages, tools) {
|
||||
/**
|
||||
* Extrahiert NUR den System-Anteil (System-Messages + Tool-Use-Block) als
|
||||
* rohen Text — OHNE <system>-Tags. Fuer den ECHTEN System-Prompt-Kanal der
|
||||
* Claude-CLI (--append-system-prompt), damit die ARIA-Persona nicht als
|
||||
* <system>-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 <system>-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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user