Welle 3 · Lucys Freiheiten je Aktionsart, Rückfragen mit „ja 7“, Lucy untersucht rote Hinweise, Wochenbericht

- services/lucy_autonomie.py: Stufe je Aktionsart (nur lesen, erst fragen, selbst handeln), Start „mehr Freiheit“;
  Meldung danach (POST /api/lucy/gehandelt)
- services/rueckfragen.py: nummerierte Rückfragen, nur Knöpfe der Oberfläche (ERLAUBT), „ja“ führt genau die
  gespeicherte Aktion aus, 24 h gültig, hinfällig wenn der Hinweis weg ist, Nachprüfung danach
- notify.sh -k: Antworttasten (Antworttastatur über den Direktweg, ohne Eingriff in Hermes)
- services/ermittlung.py: Wächter meldet rote Hinweise an Lucy; Knopf wird Rückfrage, Lucy sieht über den
  api_server nach und schickt Ursache und Weg als eine Nachricht
- services/wochenbericht.py + mc2-wochenbericht.timer (So 07:30): feste Sätze, Absatz vom Modell, offene
  App-Updates als Rückfragen
- MCP: Werkzeuge fragen die Freiheiten (auf_bitte), neu: freiheiten, rueckfragen, rueckfrage_beantworten,
  sicherheitsupdates_einspielen, snapshot_zurueckspielen, snapshots, geraet_logs, dienst_logs
- Ausführer-Aktion journal (nur lesend), POST /api/homelab/ziele/{id}/sicherheitsupdates
- Läufe des Homelabs mit flock (Oberfläche und Steward schreiben beide)
- Skill homelab-orchestrator, SOUL-Abschnitt als Kopie in deploy/nur-box

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-25 19:12:50 +02:00
co-authored by Claude Opus 5.5
parent dd23f3fb4c
commit 12cefb427a
37 changed files with 2649 additions and 169 deletions
+70 -24
View File
@@ -3,13 +3,15 @@
Homelab Orchestrator — MCP-Server für Lucy (Hermes), seit 25.09.2026.
Stdio-MCP-Server, von Hermes Gateway als Unterprozess gestartet (auf der KI-Box, mit dem venv von MC2). Gibt Lucy die
Knöpfe des Orchestrators: Lage lesen, Updates starten, „Alle aktualisieren“, Dienste neu starten, Kernel aufräumen,
Host neu starten, Modell-Radar. Die Logik steht in orchestrator_werkzeuge.py (ohne MCP testbar); alles geht über die
REST-API von MC2 mit denselben Leitplanken wie die Knöpfe der Oberfläche.
Knöpfe des Orchestrators: Lage lesen, Protokolle lesen, Updates und Sicherheitsupdates starten, „Alle aktualisieren“,
Dienste neu starten, Snapshots zurückspielen, Kernel aufräumen, Host neu starten, Modell-Radar — und die Rückfragen
an den Commander. Die Logik steht in orchestrator_werkzeuge.py (ohne MCP testbar); alles geht über die REST-API von MC2
mit denselben Leitplanken wie die Knöpfe der Oberfläche.
Die Beschreibungen sind bewusst als Leitplanken formuliert (Lesen jederzeit, Handeln nur auf Bitte des Commanders),
damit das Modell nicht auf eigene Faust Updates oder Neustarts auslöst — der Grund jeder Aktion wird protokolliert.
FastMCP liest die Beschreibung beim Anmelden, deshalb steht sie in description= (orchestrator_werkzeuge.BESCHREIBUNG).
Handeln: auf ausdrückliche Bitte des Commanders (auf_bitte=true) oder nach Lucys Freiheiten je Aktionsart (seit dem
Abend des 25.09.2026, Einstellungen → Lucy) — selbst mit Meldung danach, als Rückfrage oder gar nicht. Grund und
Stufe jeder Aktion werden protokolliert. FastMCP liest die Beschreibung beim Anmelden, deshalb steht sie in
description= (orchestrator_werkzeuge.BESCHREIBUNG). Neue Werkzeuge sieht Hermes erst nach einem Neustart des Gateways.
Einrichten auf der Box (einmal): hermes mcp add homelab-orchestrator --command <venv>/bin/python --args <dieses Skript>
"""
@@ -21,6 +23,8 @@ mcp = FastMCP("homelab-orchestrator")
B = w.BESCHREIBUNG
# --- Lesen ----------------------------------------------------------------------------------------------------
@mcp.tool(description=B["lage"])
def lage() -> str:
return w.lage()
@@ -37,8 +41,8 @@ def speicher() -> str:
@mcp.tool(description=B["protokoll"])
def protokoll(anzahl: int = 15) -> str:
return w.protokoll(anzahl)
def protokoll(anzahl: int = 15, ziel: str | None = None) -> str:
return w.protokoll(anzahl, ziel)
@mcp.tool(description=B["radar"])
@@ -46,44 +50,86 @@ def radar() -> str:
return w.radar()
@mcp.tool(description=B["freiheiten"])
def freiheiten() -> str:
return w.freiheiten()
@mcp.tool(description=B["rueckfragen"])
def rueckfragen() -> str:
return w.rueckfragen()
@mcp.tool(description=B["geraet_logs"])
def geraet_logs(ziel: str, zeilen: int = 80) -> str:
return w.geraet_logs(ziel, zeilen)
@mcp.tool(description=B["dienst_logs"])
def dienst_logs(dienst: str, zeilen: int = 80) -> str:
return w.dienst_logs(dienst, zeilen)
@mcp.tool(description=B["snapshots"])
def snapshots(ziel: str) -> str:
return w.snapshots(ziel)
# --- Handeln --------------------------------------------------------------------------------------------------
@mcp.tool(description=B["update_starten"])
def update_starten(ziel: str, baustein: str, grund: str) -> str:
return w.update_starten(ziel, baustein, grund)
def update_starten(ziel: str, baustein: str, grund: str, auf_bitte: bool = False) -> str:
return w.update_starten(ziel, baustein, grund, auf_bitte)
@mcp.tool(description=B["sicherheitsupdates_einspielen"])
def sicherheitsupdates_einspielen(ziel: str, grund: str, auf_bitte: bool = False) -> str:
return w.sicherheitsupdates_einspielen(ziel, grund, auf_bitte)
@mcp.tool(description=B["alle_aktualisieren"])
def alle_aktualisieren(bereich: str, grund: str) -> str:
return w.alle_aktualisieren(bereich, grund)
def alle_aktualisieren(bereich: str, grund: str, auf_bitte: bool = False) -> str:
return w.alle_aktualisieren(bereich, grund, auf_bitte)
@mcp.tool(description=B["nach_updates_suchen"])
def nach_updates_suchen(grund: str) -> str:
return w.nach_updates_suchen(grund)
def nach_updates_suchen(grund: str, auf_bitte: bool = False) -> str:
return w.nach_updates_suchen(grund, auf_bitte)
@mcp.tool(description=B["dienst_neustarten"])
def dienst_neustarten(dienst: str, grund: str) -> str:
return w.dienst_neustarten(dienst, grund)
def dienst_neustarten(dienst: str, grund: str, auf_bitte: bool = False) -> str:
return w.dienst_neustarten(dienst, grund, auf_bitte)
@mcp.tool(description=B["snapshot_zurueckspielen"])
def snapshot_zurueckspielen(ziel: str, snapshot: str, grund: str, auf_bitte: bool = False) -> str:
return w.snapshot_zurueckspielen(ziel, snapshot, grund, auf_bitte)
@mcp.tool(description=B["kernel_aufraeumen"])
def kernel_aufraeumen(grund: str) -> str:
return w.kernel_aufraeumen(grund)
def kernel_aufraeumen(grund: str, auf_bitte: bool = False) -> str:
return w.kernel_aufraeumen(grund, auf_bitte)
@mcp.tool(description=B["host_neustarten"])
def host_neustarten(grund: str) -> str:
return w.host_neustarten(grund)
def host_neustarten(grund: str, auf_bitte: bool = False) -> str:
return w.host_neustarten(grund, auf_bitte)
@mcp.tool(description=B["radar_testen"])
def radar_testen(kandidat: str, grund: str) -> str:
return w.radar_testen(kandidat, grund)
def radar_testen(kandidat: str, grund: str, auf_bitte: bool = False) -> str:
return w.radar_testen(kandidat, grund, auf_bitte)
@mcp.tool(description=B["radar_suchen"])
def radar_suchen(grund: str) -> str:
return w.radar_suchen(grund)
def radar_suchen(grund: str, auf_bitte: bool = False) -> str:
return w.radar_suchen(grund, auf_bitte)
@mcp.tool(description=B["rueckfrage_beantworten"])
def rueckfrage_beantworten(antwort: str, nr: int | None = None) -> str:
return w.rueckfrage_beantworten(antwort, nr)
if __name__ == "__main__":