70d1500096
OpenClaw (aria-core) ist raus, ARIA laeuft jetzt mit eigenem Agent-Framework
im aria-brain Container. Vector-DB-basiertes Gedaechtnis statt Sessions,
eigener Conversation-Loop mit Hot+Cold-Memory + Rolling Window, Tool-Use
fuer Skills, Memory-Destillat-Pipeline.
aria-brain/ (neuer Container)
- main.py FastAPI auf 8080, alle Endpoints
- agent.py Conversation-Loop mit Tool-Use (skill_create + run_<skill>)
- conversation.py Rolling Window, JSONL-Persistenz, Distill-Marker
- proxy_client.py httpx-Wrapper zum Claude-Proxy, OpenAI-Format
- prompts.py System-Prompt aus Hot+Cold+Skills
- migration.py Markdown-Parser fuer brain-import/ → atomare Memories
- skills.py Filesystem-Layer fuer /data/skills/<name>/ (Python-only,
venv pro Skill, tar.gz Export/Import, Run-Logs)
- memory/ Embedder (sentence-transformers, multilingual MiniLM)
+ VectorStore (Qdrant-Wrapper)
docker-compose.yml
- aria-core (OpenClaw) raus, openclaw-config Volume raus
- aria-brain Service (FastAPI + Memory)
- aria-qdrant Service (Vector-DB) mit Bind-Mount aria-data/brain/qdrant/
- Diagnostic teilt jetzt Netzwerk mit Bridge (vorher: aria-core)
- Brain bekommt SSH-Mount fuer aria-wohnung + /import fuer brain-import/
bridge/aria_bridge.py
- send_to_core → HTTP-Call an aria-brain:8080/chat (statt OpenClaw-WS)
- aria-core-spezifische Handler raus: doctor_fix, aria_restart,
aria_session_reset, Auto-Compact-Logik, OpenClaw-Handshake
- Generischer container_restart-Handler (Whitelist Bridge/Brain/Qdrant)
- Side-Channel-Events aus /chat-Response (z.B. skill_created) werden
als RVS-Events forwarded
- file_list_request / file_delete_request → an Diagnostic forwarded
- Tote OpenClaw-Connection-Logik bleibt im Code als Referenz (nicht aktiv)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
"""
|
|
System-Prompt-Bau aus Memory-Punkten.
|
|
|
|
Strategie:
|
|
1. Alle pinned Punkte (Hot Memory) — gruppiert nach Type — in den
|
|
System-Prompt schreiben. IMMER drin.
|
|
2. Top-K semantisch aehnliche Punkte (Cold Memory) zur aktuellen
|
|
User-Nachricht — als "Moeglicherweise relevant" eingehaengt.
|
|
3. Aktive Skills als kompakte Liste (nur Name + Description) — damit
|
|
ARIA weiss was sie hat.
|
|
|
|
Phase B Punkt 1: nur Hot-Memory-Bau, Skills + Cold-Search kommen
|
|
mit dem Conversation-Loop in spaeteren Phasen.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import List
|
|
|
|
from memory import MemoryPoint
|
|
|
|
TYPE_HEADINGS = {
|
|
"identity": "## Wer du bist",
|
|
"rule": "## Sicherheitsregeln & Prinzipien",
|
|
"preference": "## Benutzer-Praeferenzen",
|
|
"tool": "## Tool-Freigaben",
|
|
"skill": "## Deine Skills",
|
|
}
|
|
|
|
|
|
def build_hot_memory_section(pinned: List[MemoryPoint]) -> str:
|
|
"""Baue den 'IMMER-im-Prompt'-Block aus pinned Punkten."""
|
|
grouped: dict[str, List[MemoryPoint]] = {}
|
|
for p in pinned:
|
|
grouped.setdefault(p.type, []).append(p)
|
|
|
|
parts: List[str] = []
|
|
# Sortier-Reihenfolge: identity → rule → preference → tool → skill → Rest
|
|
order = ["identity", "rule", "preference", "tool", "skill"]
|
|
for t in order:
|
|
items = grouped.pop(t, [])
|
|
if not items:
|
|
continue
|
|
parts.append(TYPE_HEADINGS.get(t, f"## {t}"))
|
|
for p in items:
|
|
parts.append(f"### {p.title}")
|
|
parts.append(p.content.strip())
|
|
parts.append("")
|
|
|
|
# uebrige Types (falls jemand was anderes als pinned markiert)
|
|
for t, items in grouped.items():
|
|
parts.append(f"## {t}")
|
|
for p in items:
|
|
parts.append(f"### {p.title}")
|
|
parts.append(p.content.strip())
|
|
parts.append("")
|
|
|
|
return "\n".join(parts).strip()
|
|
|
|
|
|
def build_cold_memory_section(matches: List[MemoryPoint]) -> str:
|
|
"""Baue 'Moeglicherweise relevant'-Block aus Search-Treffern."""
|
|
if not matches:
|
|
return ""
|
|
lines = ["## Moeglicherweise relevant (aus Gedaechtnis)"]
|
|
for p in matches:
|
|
score = f" [score={p.score:.2f}]" if p.score is not None else ""
|
|
lines.append(f"- **{p.title}**{score}")
|
|
lines.append(f" {p.content.strip()}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def build_skills_section(skills: List[dict]) -> str:
|
|
"""Listet alle Skills (aktiv + deaktiviert) damit ARIA weiss was es gibt
|
|
und keine doppelt baut. Plus klare Schwelle wann ein Skill sich lohnt."""
|
|
lines = ["## Deine Skills"]
|
|
if skills:
|
|
for s in skills:
|
|
active = s.get("active", True)
|
|
marker = "" if active else " [DEAKTIVIERT — kann nicht aufgerufen werden]"
|
|
lines.append(f"- **{s.get('name', '?')}**{marker} — {s.get('description', '(ohne Beschreibung)')}")
|
|
lines.append("")
|
|
lines.append("Wenn ein vorhandener Skill zur Aufgabe passt: nutze ihn via Tool-Call.")
|
|
else:
|
|
lines.append("(noch keine Skills vorhanden)")
|
|
|
|
lines.append("")
|
|
lines.append("### Wann lohnt sich ein neuer Skill?")
|
|
lines.append("")
|
|
lines.append("**Skills sind IMMER Python** — eigene venv pro Skill mit den noetigen "
|
|
"pip-Paketen. Kein apt im Skill, kein systemweiter Install. Python deckt "
|
|
"in der Regel alles ab (yt-dlp, requests, pypdf, pillow, openpyxl, "
|
|
"static-ffmpeg, beautifulsoup4, …). Falls etwas WIRKLICH nur via apt geht: "
|
|
"Stefan fragen ob es ins Brain-Dockerfile soll.")
|
|
lines.append("")
|
|
lines.append("**Harte Regel — IMMER Skill anlegen wenn:** die Loesung erfordert eine "
|
|
"pip-Library. Begruendung: Brain-Container hat keinen persistenten State "
|
|
"ausser /data/skills/. Ohne Skill wuerde der Install bei jedem "
|
|
"Container-Restart wiederholt.")
|
|
lines.append("")
|
|
lines.append("**Sonst — Skill nur wenn alle vier zutreffen:**")
|
|
lines.append("")
|
|
lines.append("1. **Wiederkehrend** — die Aufgabe wird realistisch nochmal gestellt. "
|
|
"Einmal-Faelle (\"wie spaet ist es jetzt\") kein Skill.")
|
|
lines.append("2. **Nicht-trivial** — mehrere Schritte. Ein einzelner Shell-Befehl "
|
|
"(`date`, `hostname`, `ls`) ist KEIN Skill — das macht Bash direkt.")
|
|
lines.append("3. **Parametrisierbar** — der Skill nimmt Eingaben (URL, Datei, Suchbegriff) "
|
|
"und gibt ein nuetzliches Ergebnis zurueck.")
|
|
lines.append("4. **Wiederverwendbar als ganzes** — Stefan wuerde es zukuenftig per Name "
|
|
"ansprechen (\"mach mir den YouTube zu MP3\") statt jedes Mal zu erklaeren.")
|
|
lines.append("")
|
|
lines.append("Wenn nichts installiert werden muss UND nicht alle vier zutreffen: einfach "
|
|
"die Aufgabe loesen ohne Skill anzulegen. Stefan kann jederzeit sagen "
|
|
"'bau daraus einen Skill'.")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def build_system_prompt(
|
|
pinned: List[MemoryPoint],
|
|
cold: List[MemoryPoint] | None = None,
|
|
skills: List[dict] | None = None,
|
|
) -> str:
|
|
"""Kompletter System-Prompt: Hot + Cold + Skills."""
|
|
parts = [build_hot_memory_section(pinned)]
|
|
if skills:
|
|
parts.append("")
|
|
parts.append(build_skills_section(skills))
|
|
if cold:
|
|
parts.append("")
|
|
parts.append(build_cold_memory_section(cold))
|
|
return "\n".join(parts).strip()
|