feat(brain): Multi-Threading via per-request project_id + per-project queue

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>
This commit is contained in:
2026-07-02 17:57:30 +02:00
co-authored by Claude Opus 4.7
parent 5b2c552a88
commit 7927ad05ae
3 changed files with 191 additions and 64 deletions
+6 -9
View File
@@ -1522,24 +1522,21 @@ class ARIABridge:
"""
brain_url = os.environ.get("BRAIN_URL", "http://aria-brain:8080")
url = f"{brain_url}/chat"
payload = json.dumps({"message": text, "source": source}).encode("utf-8")
# project_id kommt jetzt IM /chat-Body an das Brain (Multi-Threading:
# per-Request-Routing statt globaler active_project-State).
payload = json.dumps({
"message": text, "source": source,
"project_id": project_id or "",
}).encode("utf-8")
logger.info("[brain] chat ← %s '%s' project=%s", source, text[:80], project_id or "(main)")
# User-Nachricht in chat_backup.jsonl loggen — wird beim App-Reconnect
# / Diagnostic-Reload als History-Quelle gelesen. clientMsgId speichern
# damit die App beim chat_history_response ihre lokale Bubble
# dedupen kann (sonst verschwindet sie nach Offline→Online-Race).
# project_id: pre-turn-State (was App geschickt hat). Wenn leer, vom
# Brain-Status nachholen — z.B. bei Trigger-Replies oder Diagnostic-Send.
entry: dict = {"role": "user", "text": text, "source": source}
if client_msg_id:
entry["clientMsgId"] = client_msg_id
if not project_id:
try:
with urllib.request.urlopen(f"{brain_url}/projects/status", timeout=3) as r:
project_id = (json.loads(r.read()).get("active_id") or "")
except Exception:
pass
if project_id:
entry["project_id"] = project_id
self._append_chat_backup(entry)