# JARVIS Home Command v8 – Skill Engine JARVIS besitzt jetzt eine **pluginfähige Skill Engine**. Die bisherigen festen Home-Tools sind weiterhin vorhanden, werden aber als **Core Skills** in eine gemeinsame Registry eingehängt. Zusätzliche Fähigkeiten können ohne Änderung am Go-Core unter `skills//skill.json` installiert und zur Laufzeit neu geladen werden. ## Neue Architektur ```text Voice / Text │ ▼ JARVIS Agent / Workflow Planner │ ▼ Skill Registry │ ├── Core Skills (Go) │ ├── calendar.* │ ├── tasks.* │ ├── recipes.* │ └── ... │ └── Plugin Skills (jarvis.skill.v1) ├── skill.json ├── Input JSON Schema ├── Output JSON Schema └── beliebiges Executable / Script / Binary │ ▼ Schema Validation │ ▼ Skill Execution │ ▼ standardisiertes JSON Result ``` Der Agent kann Core- und Plugin-Skills in demselben Workflow kombinieren. Ein Plugin-Skill erscheint für Ollama technisch als natives Function Tool; für den Rest des Systems ist er aber ein versioniertes Skill-Modul mit klarer Modulgrenze. ## `jarvis.skill.v1` Ein Modul kann mehrere Actions exportieren: ```json { "protocol": "jarvis.skill.v1", "id": "weather.local", "name": "Local Weather", "version": "1.0.0", "description": "Liefert lokale Wetterdaten.", "enabled": true, "runtime": { "type": "process", "command": "./weather", "timeout_ms": 5000 }, "permissions": { "network": true, "system_exec": false }, "actions": [ { "name": "forecast", "description": "Liefert eine Wettervorhersage für einen Ort.", "triggers": ["wetter", "vorhersage"], "mutates": false, "input_schema": { "type": "object", "additionalProperties": false, "properties": { "location": {"type": "string"} }, "required": ["location"] }, "output_schema": { "type": "object", "additionalProperties": false, "properties": { "summary": {"type": "string"} }, "required": ["summary"] } } ] } ``` ### Prozess-Input JARVIS startet den Prozess mit dem Skill-Verzeichnis als Working Directory und sendet genau ein JSON-Envelope auf stdin: ```json { "protocol": "jarvis.skill.v1", "request_id": "skill_...", "skill_id": "weather.local", "action": "forecast", "input": {"location": "Berlin"}, "context": { "trace_id": "http_...", "now": "2026-08-29T10:20:00+02:00", "timezone": "Europe/Berlin" } } ``` ### Prozess-Output stdout muss genau eine JSON-Antwort enthalten. Debug-/Logausgaben gehören auf stderr. ```json { "protocol": "jarvis.skill.v1", "success": true, "data": {"summary": "..."}, "message": "Wetterdaten geladen.", "warnings": [], "mutated": false } ``` Fehler werden ebenfalls strukturiert zurückgegeben: ```json { "protocol": "jarvis.skill.v1", "success": false, "error": { "code": "UPSTREAM_UNAVAILABLE", "message": "Wetterdienst nicht erreichbar" } } ``` Input wird **vor dem Prozessstart** validiert; `data` wird anschließend gegen `output_schema` validiert. Ein Skill mit ungültigem JSON oder falschem Schema kann damit nicht unkontrolliert in den Agent-Loop einspeisen. ## Skill-Kombinationen Der bestehende Workflow-Agent arbeitet jetzt auf der Skill Registry. Zum Beispiel kann eine zukünftige Anfrage wie ```text Prüfe das Wetter für Samstag und verschiebe den Grilltermin auf Sonntag, wenn Regen gemeldet ist. ``` zu einem Ablauf werden wie: ```text skill_weather_local_forecast ↓ calendar_list ↓ calendar_update ``` Die KI entscheidet über Auswahl und Reihenfolge; **jeder einzelne Skill-Call wird separat gegen sein JSON-Schema geprüft**. ## Mutationen und Bestätigungen Plugin-Actions können deklarieren: ```json { "mutates": true, "requires_confirmation": true } ``` Bei `requires_confirmation=true` führt JARVIS den Skill nicht sofort aus. Stattdessen wird der exakte Skill-Name inklusive Arguments als serverseitige Pending Action gespeichert. Ein anschließendes `ja` führt genau diesen vorgemerkten Call aus — ohne erneute Interpretation durch das Modell. Auch für Plugin-Skills gilt weiterhin: **`success=false` kann niemals zu einer serverseitigen Erfolgsmeldung werden.** ## Sicherheit der Process Runtime Process-Skills sind für **lokale, vertrauenswürdige Plugins** gedacht. Der Runner setzt derzeit: - Skill-Verzeichnis als Working Directory - separates Timeout pro Aufruf - Output-Größenlimit - minimale Environment-Liste statt Vererbung der kompletten JARVIS-Umgebung - kein systemweiter Entry-Point ohne zusätzliche Freigabe - Input-/Output-Schema-Validierung - Debug-Trace für Request, Runtime und Result `JARVIS_SKILL_ALLOW_SYSTEM_EXEC=false` verhindert standardmäßig, dass ein Manifest direkt `/bin/...`, `python3`, `node` usw. als Entry-Point auswählt. Plugin-eigene Executables unter dem Skill-Verzeichnis funktionieren weiterhin. **Wichtig:** Ein nativer Prozess ist keine harte Sicherheits-Sandbox gegen absichtlich bösartigen Code. Der Docker-Container ist eine äußere Grenze, aber ein vertrauenswürdiges Plugin kann innerhalb seiner OS-Rechte handeln. `permissions.network` ist in v8 eine deklarative Capability für Transparenz und zukünftige Sandbox-Runtimes; sie blockiert Netzwerkzugriffe der Process-Runtime noch nicht. `permissions.system_exec` wird dagegen für den direkten Manifest-Entry-Point erzwungen. Die Registry ist bewusst so gebaut, dass später weitere Runtime-Typen wie WASM oder separate Container ergänzt werden können. ## Installation und Reload Skills liegen standardmäßig unter: ```text ./skills ``` Docker mountet sie nach: ```text /app/skills ``` Nach dem Hinzufügen oder Ändern eines Skills muss JARVIS nicht neu kompiliert werden. Entweder im UI **RELOAD** verwenden oder: ```text POST /api/skills/reload ``` Status: ```text GET /api/skills ``` Der Debug-Export enthält ebenfalls die geladene Skill Registry. ## Konfiguration ```env JARVIS_SKILLS_DIR=./skills JARVIS_SKILLS_ENABLED=true JARVIS_SKILL_PROCESS_ENABLED=true JARVIS_SKILL_ALLOW_SYSTEM_EXEC=false JARVIS_SKILL_MAX_TIMEOUT_MS=30000 JARVIS_SKILL_MAX_OUTPUT_KB=1024 ``` Unter `skills/_template/` liegt ein kopierbarer Modulframe. `skills/demo-text/` ist ein kleines ausführbares Beispiel. Ordner mit führendem `_` werden vom Loader ignoriert. --- ## Bestehender Home-Core Die folgenden Abschnitte beschreiben die weiterhin vorhandenen Core-Funktionen. Technisch werden sie in v8 als Core Skills registriert; ihre bisherigen Function-Namen bleiben aus Kompatibilitätsgründen unverändert. Lokales Home-Dashboard in Go mit Ollama, Voice, RAG, Kalender, Aufgaben, Erinnerungen, Einkauf, Rezepten, Essensplanung und KI-Kanban. **v6 ersetzt den bisherigen Action-Planner im Chatpfad durch eine echte Tool-/Function-Calling-Architektur.** Die KI darf den Home-State nicht direkt verändern. Sie wählt ein registriertes Tool und liefert dessen JSON-Arguments; Go validiert und führt es aus. ## Architektur ```text Voice / Text │ ▼ Conversation Window │ ▼ Ollama Tool Agent │ native message.tool_calls ▼ Tool Registry + JSON Schema │ ├── calendar_* ├── tasks_* ├── reminders_* ├── groceries_* ├── recipes_* ├── mealplan_* ├── kanban_* ├── rag_search └── system_get_time │ ▼ Go Domain Services / Validator │ ▼ Store │ ▼ Domain Automations ├── Event -> Reminder ├── MealPlan + Recipe -> Groceries └── Task -> Kanban ``` ### Kernregel **Keine Tool-Ausführung = kein Commit.** Ein Modell darf also nicht mehr durch eine freie Chat-Antwort behaupten, es habe einen Termin oder Essensplan gespeichert. Mutations-Bestätigungen werden serverseitig aus dem tatsächlich persistierten Tool-Ergebnis formuliert. ## Native Ollama Tools JARVIS sendet JSON-Schemas direkt über das `tools`-Feld an Ollamas `/api/chat`. Die Modellantwort wird über `message.tool_calls[].function.name` und `arguments` ausgewertet. Der Agent kann mehrere Schritte durchführen: ```text User: Plane morgen Nudeln mit Tomatensauce für vier Personen. 1. recipes_search -> recipe_id=r1 2. mealplan_create -> date resolved by Go -> recipe_id=r1 -> servings=4 3. Home automation -> Zutaten skaliert in Einkaufsliste 4. Deterministische Commit-Antwort ``` ### Compatibility Selector Nicht jedes lokale Modell emittiert `tool_calls` immer zuverlässig. Wenn eine eindeutige Home-Anfrage vorliegt, aber das Modell nur Prosa liefert, führt JARVIS **einmalig** einen JSON-Selector-Aufruf aus: ```json { "tool": "calendar_create", "arguments": { "title": "Zahnarzt", "when": { "kind": "relative", "offset_days": 1, "time": "15:30" } }, "confidence": 0.99 } ``` Auch dieser Pfad darf nur registrierte Tools ausführen. Unbekannte Toolnamen werden abgelehnt. ## Zeitmodell Die KI berechnet Wochentage und relative Daten **nicht mehr selbst**. Sie liefert symbolische Zeitobjekte. Heute: ```json {"kind":"relative","offset_days":0} ``` Morgen 15:30: ```json {"kind":"relative","offset_days":1,"time":"15:30"} ``` Montag: ```json {"kind":"weekday","weekday":"monday"} ``` Nächsten Montag: ```json {"kind":"weekday","weekday":"monday","force_next":true} ``` Explizites Datum: ```json {"kind":"absolute","date":"2026-08-31","time":"18:00"} ``` Erst das Go-Tool wandelt diese Struktur in einen RFC3339-Zeitstempel in `JARVIS_TIMEZONE` um. ## Registrierte Tools ### System / Wissen - `system_get_time` - `rag_search` ### Kalender - `calendar_list` - `calendar_create` - `calendar_update` - `calendar_delete` ### Aufgaben - `tasks_list` - `tasks_create` - `tasks_update` - `tasks_delete` ### Erinnerungen - `reminders_list` - `reminders_create` - `reminders_delete` ### Einkauf - `groceries_list` - `groceries_add` - `groceries_update` - `groceries_remove` ### Rezepte - `recipes_search` - `recipes_get` - `recipes_create` ### Essensplan - `mealplan_list` - `mealplan_create` - `mealplan_update` - `mealplan_delete` ### Kanban - `kanban_list` - `kanban_create` - `kanban_move` - `kanban_delete` Die vollständigen JSON-Schemas werden zur Laufzeit aus der Registry an Ollama gesendet und im Debug-Trace protokolliert. Du kannst sie außerdem direkt über `GET /api/agent/tools` ansehen. ## Conversation Context Der Agent bekommt weiterhin ein echtes `user`-/`assistant`-History-Fenster. Der komplette Home-State wird ihm aber **nicht mehr bei jeder Anfrage in den Prompt geschoben**. Wenn JARVIS Informationen braucht, muss er ein Lese-Tool verwenden. Dadurch ist der Store die einzige Wahrheit. Die letzte erfolgreiche Mutation wird für kurze Follow-ups als kompakte Session-Referenz gehalten: ```text kind=meal_plan id=meal_abc ``` Damit kann ein Folgekommando wie ```text Nein, doch Freitag. ``` den gerade veränderten Eintrag gezielt über `mealplan_update` korrigieren. ## Autonomous Task Scout Der Task Scout analysiert weiterhin Nutzertexte und Dokumente auf Verpflichtungen. Neu ist: Ein akzeptierter Scout-Kandidat schreibt nicht mehr direkt in den Store, sondern wird über dasselbe `tasks_create`-Tool ausgeführt. Damit gibt es für KI-erzeugte Aufgaben nur noch einen validierten Schreibpfad. Kalendertermine, Erinnerungen, JARVIS-Antworten und explizite Home-Kommandos bleiben als Scout-Quelle ausgeschlossen. ## Domain Automations Nach erfolgreicher Tool-Mutation werden die deterministischen Automationen synchron ausgeführt: - Kalendertermin -> automatischer Reminder 30 Minuten vorher - Rezept im Essensplan -> Zutaten in die Einkaufsliste - Portionen -> Zutatenmenge skaliert - Aufgabe -> verknüpfte Kanban-Karte - erledigte Aufgabe -> KI-Kanban auf DONE - gelöschte Aufgabe -> verwaiste KI-Karte entfernt Manuell verschobene Kanban-Karten werden weiterhin als Override geschützt. ## RAG RAG ist jetzt selbst ein Tool: `rag_search`. Das Modell erhält Dokumentauszüge nur dann, wenn es sie tatsächlich benötigt. Dadurch konkurriert ein großer RAG-Block nicht mehr bei jeder Home-Anfrage mit Conversation History und Tool-Schemas um Kontext. Unterstützte Dokumente bleiben u. a. TXT, Markdown, CSV, JSON, YAML, XML, ICS, HTML, EML, DOCX sowie PDF über `pdftotext` und Bilder via lokalem Tesseract. ## Voice Die bestehende lokale Pipeline bleibt erhalten: ```text Browser Mic -> Go -> ffmpeg -> whisper.cpp -> Tool Agent -> Piper -> Browser Audio ``` Der globale Voice-Button und die Mikrofonbuttons je Modul bleiben erhalten. Der Modulname wird als Gesprächskontext mitgesendet; die konkrete Toolwahl bleibt beim Agenten. ## Debug Trace v2 `EXPORT DEBUG` enthält jetzt zusätzlich Tool-Agent-Events: - `agent.request` - `ollama.tools.request` - `ollama.tools.response` - `agent.tool_call` - `agent.tool_result` - `agent.selector_request` - `agent.selector_response` - `agent.completed` - `state.before_command` - `state.after_command` Beispiel: ```json { "trace_id": "...", "component": "agent", "stage": "tool_call", "data": { "tool": "mealplan_create", "arguments": { "title": "Pommes Currywurst", "when": {"kind":"weekday","weekday":"sunday"}, "meal": "lunch" } } } ``` Danach folgt ein separates `tool_result` mit dem real gespeicherten Objekt. Damit lässt sich eindeutig unterscheiden, ob ein Fehler aus Tool-Auswahl, Arguments, Validator, Store oder UI stammt. ## Konfiguration ```env JARVIS_ADDR=:8080 JARVIS_DATA=./data/store.json JARVIS_RAG_DATA=./data/rag.json JARVIS_WAKE_WORD=jarvis JARVIS_TIMEZONE=Europe/Berlin OLLAMA_URL=http://localhost:11434 OLLAMA_MODEL=auto OLLAMA_EMBED_MODEL=auto # allgemeiner Chat OLLAMA_NUM_CTX=16384 OLLAMA_NUM_PREDICT=1200 OLLAMA_TEMPERATURE=0.15 # Native Tool Agent OLLAMA_AGENT_NUM_CTX=16384 OLLAMA_AGENT_NUM_PREDICT=700 OLLAMA_AGENT_TEMPERATURE=0 JARVIS_AGENT_MAX_STEPS=6 # Scout / Kanban strukturierte Calls OLLAMA_PLANNER_NUM_CTX=8192 OLLAMA_PLANNER_NUM_PREDICT=700 OLLAMA_PLANNER_TEMPERATURE=0 JARVIS_HISTORY_TOKENS=3200 TASK_SCOUT_INTERVAL=2m HOME_AUTOMATION_INTERVAL=1m KANBAN_AI_INTERVAL=5m JARVIS_DEBUG_TRACE=true JARVIS_DEBUG_DIR=./data/debug ``` Für kleine lokale Modelle ist Temperatur `0` beim Tool-Agent bewusst empfohlen. ## Start ```bash ollama serve ollama list ``` Dann: ```bash cp .env.example .env # optional ./scripts/doctor.sh go run ./cmd/homehub ``` Dashboard: ```text http://localhost:8080 ``` ## Empfohlene Tests nach dem Upgrade Alten Chat einmal über `CLEAR CHAT` leeren, dann z. B.: ```text Trage morgen Zahnarzt um 15:30 Uhr in den Kalender ein. Welche Termine habe ich morgen? Sonntag wären Pommes Currywurst toll, zu Mittag. Was gibt es Sonntag zu Mittag? Plane Montag Nudeln mit Tomatensauce zum Abendessen für vier Personen. Verschiebe das auf Freitag. Füge zwei Liter Milch zur Einkaufsliste hinzu. Was steht auf der Einkaufsliste? ``` Bei einem Fehler: 1. `CLEAR TRACE` 2. Ablauf einmal reproduzieren 3. `EXPORT DEBUG` 4. JSON hier bereitstellen ## Tests ```bash go test ./... go vet ./... go build ./cmd/homehub node --check internal/ui/static/app.js ``` Die Tests enthalten native Tool Calls, einen mehrstufigen `recipes_search -> mealplan_create`-Ablauf, den JSON-Selector-Fallback und die Garantie, dass unbekannte Tools den Store nicht verändern. ## Zusätzliche UI-Variante: Clean Design Die Oberfläche enthält jetzt zusätzlich einen umschaltbaren **Clean-Modus** mit heller, minimaler, Apple-inspirierter Optik. - Umschalten direkt im Command-Bereich über `JARVIS | CLEAN` - Einstellung wird im Browser per `localStorage` gespeichert - Funktionsumfang bleibt identisch, es ändert sich nur das visuelle Design ## v7 Clean App – eigene Informationsarchitektur Der **CLEAN**-Modus ist jetzt keine reine Farbvariante des HUDs mehr, sondern eine eigene Multi-Screen-App. - **Home** – Assistant, nächste Termine, offene Aufgaben, nächstes Essen und Systemstatus - **Kalender** – 7-Tage-Kalender plus Erinnerungen - **Aufgaben** – fokussierte Aufgabenansicht mit KPIs und Voice-Trigger - **Kanban** – eigenständige Vollbild-Workflow-Ansicht - **Essen & Einkauf** – Essensplanung, Einkaufsliste und Rezepte auf einem Screen - **Wissen & System** – RAG, Dokumente, Sprachpipeline und Telemetrie Desktop verwendet eine permanente linke Sidebar. Unter 850 px wechselt die Navigation in eine mobile Bottom-Bar. Die letzte Clean-Ansicht sowie das ausgewählte Design werden lokal im Browser gespeichert. Der bestehende **JARVIS/HUD**-Modus bleibt vollständig verfügbar. ## v7.1 – Kanban AI Reliability Fix `AI ORGANIZE` verwendet jetzt ebenfalls Ollamas native Tool-Calling-Schnittstelle statt freien JSON-Text zu parsen. Ablauf: 1. `kanban_recommend` als internes Tool mit festem JSON-Schema 2. Native `message.tool_calls` werden bevorzugt ausgewertet 3. Kompatibilitäts-Retry im JSON-Modus für Modelle ohne zuverlässiges Tool Calling 4. Parser akzeptiert zusätzlich JSON in Markdown-Fences oder eingebettetem Begleittext 5. Bei leerem/abgeschnittenem Modelloutput wird kein HTTP-502/`unexpected end of JSON input` mehr ausgelöst. Die synchronisierten Task-Karten bleiben deterministisch erhalten. Der Debug-Trace enthält jetzt `kanban.tool_request`, `kanban.tool_response`, `kanban.json_retry_*` und gegebenenfalls `kanban.deterministic_fallback`. ## v7.2 – Workflow Planner / Unteraufgaben Komplexe Home-Anfragen werden nicht mehr künstlich auf genau einen Tool-Aufruf reduziert. Wenn ein lokales Modell keine nativen Tool-Calls liefert, verwendet JARVIS einen schema-strikten **Workflow Planner**: ```json { "goal": "Abendessen für sieben Tage planen", "steps": [ { "id": "s1", "task": "Abendessen für Tag 1 planen", "tool": "mealplan_create", "arguments": { "title": "Gemüsepfanne mit Reis", "when": {"kind": "relative", "offset_days": 0}, "meal": "dinner" } } ], "confidence": 0.96 } ``` - 1–24 Unteraufgaben pro Workflow - jede Unteraufgabe muss ein registriertes Tool verwenden - alle Schritte laufen weiterhin durch die Go-Tool-Registry und deren Validatoren - kreative Planungswünsche (`plane`, `erstelle`, `generiere`) dürfen sinnvolle Vorschläge selbst auswählen - harte persönliche Fakten (z. B. Allergien) werden nicht erfunden - `ganze Woche` ohne weitere Präzisierung bedeutet bei Planungswünschen die nächsten sieben Kalendertage - der API-Response enthält `plan`, das HUD zeigt `PLAN · N SCHRITTE` - Debug-Trace: `agent.workflow_plan_request`, `agent.workflow_plan_response`, `agent.workflow_plan` ## v7.3 – Recipe-First Meal Planning & Grocery Pipeline Die Essensplanung ist jetzt **recipe-first**: - generierte Wochen-/Mehrfachpläne verwenden bevorzugt vorhandene Rezepte aus dem Rezeptbuch - jeder geplante Recipe-Slot speichert `recipeId`; bei zu wenigen Rezepten dürfen vorhandene Rezepte wiederholt werden statt Zutaten zu erfinden - bei komplett leerem Rezeptbuch darf der Workflow zunächst vollständige Rezepte mit Zutaten/Schritten erzeugen und sie danach einplanen - neue Tools: `recipes_list`, `recipes_update`, `groceries_sync_mealplan` - `groceries_sync_mealplan` aggregiert Mengen über alle verknüpften Rezepte und meldet Mahlzeiten ohne Rezeptzuordnung separat - automatische Einkaufssynchronisation betrachtet jetzt 14 statt 8 Tage, sodass auch eine kommende Montag–Sonntag-Woche vollständig erfasst wird - alte freie Mealplan-Titel werden automatisch mit bestehenden Rezepten verknüpft, wenn die Textähnlichkeit eindeutig genug ist - Rezeptänderungen aktualisieren die abgeleitete Einkaufsliste automatisch - im UI können Rezepte jetzt über ✎ bearbeitet werden; der Einkauf-Screen hat zusätzlich `↻ AUS PLAN` Beispiel: ```text Erstelle einen Essensplan für die ganze Woche, nur Abendessen. → recipes_list → 7 × mealplan_create mit recipe_id → automatische Zutatenaggregation Was muss ich dafür einkaufen? → groceries_sync_mealplan → deterministische Einkaufsliste aus den gepflegten Rezeptzutaten ``` Zutaten werden dabei niemals frei aus einem Gerichtsnamen erfunden. Ohne Rezeptzuordnung meldet JARVIS die betroffene Mahlzeit als fehlende Rezeptbasis. ## v7.4 – Confirmed Actions / Kalender-Löschfix Bestätigungsdialoge wie `Soll ich den Termin löschen?` werden jetzt als konkrete serverseitige Pending Tool Action gespeichert. Ein anschließendes `ja` wird nicht erneut vom LLM interpretiert, sondern führt exakt das vorgemerkte Tool mit der zuvor gelesenen Entity-ID aus. Zusätzliche Invariante: Wenn ein Schreib-Tool `success:false` liefert, darf freie Modellprosa niemals eine erfolgreiche Änderung behaupten. Die Antwort wird dann deterministisch aus dem Toolfehler erzeugt. Der Debug-Export enthält hierfür `runtime.pending_tool_confirmation` sowie Trace-Stufen `agent.pending_tool_set` und `agent.pending_tool_execute`. ## Clean UI Polish v7.5 Der Clean-Modus besitzt stärkere Textkontraste für Chat-Hervorhebungen, sekundäre Metadaten, Eingaben und Karten. Helle JARVIS-Cyan-Vererbungen werden im Clean-Modus gezielt überschrieben. ## v7.5.1 – Clean Home Grid Fix Die Clean-Home-Seite verwendet jetzt ein bewusstes 12-Spalten-Raster: Begrüßung + nächster Termin = 7/5, Aufgaben + Essen = 6/6 und Local AI = volle Breite. Dadurch entstehen auf großen Displays keine ungenutzten Restspalten mehr. ## Docker Voice / Whisper (v7.6) Im Docker-Betrieb ist `whisper-cli` jetzt **Bestandteil des JARVIS-Images**. Beim Image-Build wird `whisper.cpp` (standardmäßig `v1.9.1`) kompiliert und `/usr/local/bin/whisper-cli` in das Runtime-Image kopiert. Damit ist STT nicht mehr davon abhängig, dass auf dem Host `./local-tools/whisper-cli` liegt. Das Whisper-Modell bleibt als Volume unter `/models`, damit die große Modelldatei nicht bei jedem Build im Image landet. Für das Standard-Setup muss auf dem Host vorhanden sein: ```text ./models/ggml-small.bin ``` Der schnellste Weg, Voice vollständig vorzubereiten, ist: ```bash ./scripts/setup-docker-voice.sh docker compose build --no-cache jarvis docker compose up -d ``` Das Setup-Script lädt das Whisper-small-Modell sowie die deutsche Piper-Stimme herunter und legt das Piper-Linux-Paket passend zur Host-Architektur unter `./local-tools` ab. ### Diagnose im Container ```bash docker compose exec jarvis /app/scripts/doctor.sh ``` oder nur Whisper: ```bash docker compose exec jarvis sh -lc 'command -v whisper-cli && whisper-cli --help >/dev/null && ls -lh /models/ggml-small.bin' ``` Im HUD wird bei nicht bereitem STT jetzt unterschieden zwischen: - `BIN` – Whisper-Binary fehlt oder ist nicht ausführbar - `MODEL` – Whisper-Modell fehlt - `FFMPEG` – Audio-Konverter fehlt - `LOCAL` – STT ist vollständig einsatzbereit Die API liefert zusätzlich die aufgelösten Binary-/Modellpfade und konkrete Diagnosefehler unter `system.voice`. Auch `docker compose logs jarvis` schreibt beim Start den Voice-Status mit den einzelnen Komponenten. > Hinweis: Wenn du von einer älteren Version aktualisierst, reicht `docker compose up -d` möglicherweise nicht, weil Docker das alte Image weiterverwendet. Für den ersten Start von v7.6 daher einmal `docker compose build --no-cache jarvis` ausführen. # v9 – Distributed Skill Mesh v9 verschiebt Plugin-Code aus dem JARVIS-Master in eigenständige Runtime-Worker. Der Agent sieht weiterhin **eine** Skill Registry, aber eine Action kann jetzt von drei Providern stammen: ```text core -> deterministische Go-Domain-Services im Master plugin -> lokaler Prozess-Skill (native Entwicklung; in Docker standardmäßig aus) remote -> HTTP Skill Worker in eigenem Container/Host ``` ## Protokolle - `jarvis.skill.v1` – Skill-/Action-Definition inklusive Input-/Output-Schema - `jarvis.skill.worker.v1` – Enrollment, Registry, Heartbeat und Deregistration - `jarvis.skill.invoke.v1` – Remote-Ausführung und Resultat ## Worker-Lifecycle ```text Worker startet -> POST /api/mesh/enroll -> Master prüft Enrollment Token <- worker_id + zufälliges Session Access Token + Lease -> PUT /api/mesh/workers/{id}/skills -> periodisch POST /api/mesh/workers/{id}/heartbeat -> Master nimmt registrierte Actions in die Skill Registry auf -> Master ruft POST http://worker/v1/invoke -> Worker validiert Input, führt Skill aus, validiert Output -> bei sauberem Shutdown DELETE /api/mesh/workers/{id} ``` Bleibt ein Heartbeat länger als die Lease aus, entfernt der Master den Worker und seine Actions automatisch aus der aktiven Registry. Mehrere Worker dürfen denselben Skill/Action-Namen als Replik bereitstellen; der Master verteilt Aufrufe round-robin und probiert bei einem Transportfehler den nächsten Provider. ## Runtime-Worker Images Unter `workers/docker/` liegen fertige Dockerfiles für: - `Dockerfile.python` – Python 3.13 - `Dockerfile.node` – Node.js 22 - `Dockerfile.golang` – Go 1.23 Toolchain - `Dockerfile.rust` – Rust Toolchain - `Dockerfile.c` – GCC / C - `Dockerfile.cpp` – G++ / CMake - `Dockerfile.csharp` – .NET SDK 8 / C# Alle Images verwenden denselben kleinen Go-basierten `jarvis-skill-worker`. Die jeweilige Sprache/Toolchain ist nur die Laufzeit für den Skill-Code. ## Docker Compose starten Zuerst einen eigenen Enrollment Token setzen, z. B. in `.env`: ```env JARVIS_MESH_ENROLLMENT_TOKEN=bitte-einen-langen-zufaelligen-wert-verwenden ``` Master/Ollama: ```bash docker compose up -d --build jarvis ollama ``` Alle Runtime-Worker: ```bash docker compose --profile skill-workers up -d --build ``` Oder nur Python: ```bash docker compose --profile skill-workers up -d --build skill-python ``` Das mitgelieferte Python-Beispiel liegt unter `skills/python/example-text` und registriert `skill_example_python_text_uppercase`. ## Skill-Verteilung Jeder Worker bekommt sein eigenes `/skills`-Volume: ```text skills/ python/ mein-python-skill/ skill.json main.py node/ go/ rust/ c/ cpp/ csharp/ ``` Der Worker scannt nur seine Runtime-Gruppe. `POST /api/mesh/workers/{id}/reload` fordert einen laufenden Worker auf, `/skills` neu einzulesen und seine Registry erneut beim Master zu veröffentlichen. ## Docker Controller über docker.sock Der Master kann optional den Docker Engine Unix Socket verwenden: ```yaml - /var/run/docker.sock:/var/run/docker.sock ``` Konfiguration: ```env JARVIS_DOCKER_CONTROLLER_ENABLED=true JARVIS_DOCKER_SOCKET=/var/run/docker.sock JARVIS_DOCKER_SKILL_LABEL=com.jarvis.skill-service JARVIS_DOCKER_SKILL_LABEL_VALUE=true ``` **Wichtig:** Zugriff auf `docker.sock` entspricht praktisch administrativem Docker-/Host-Zugriff. Deshalb implementiert der JARVIS-Controller bewusst nur `list`, `start`, `stop` und `restart`. Vor jeder Mutation listet er die Docker-Container neu und akzeptiert ausschließlich eine **exakte vollständige Container-ID**, die aktuell das konfigurierte Label mit dem exakten Wert besitzt. Er bietet keine allgemeinen `exec`, `create`, `remove`, Volume- oder Image-Operationen an. Compose versieht die Skill-Worker mit: ```yaml labels: com.jarvis.skill-service: "true" com.jarvis.skill-runtime: python com.jarvis.worker-name: python-main ``` API: ```text GET /api/docker/skill-services POST /api/docker/skill-services/{full-container-id}/start POST /api/docker/skill-services/{full-container-id}/stop POST /api/docker/skill-services/{full-container-id}/restart ``` Im Screen **Wissen & System** werden sowohl enrolled Remote Worker als auch gelabelte Docker Skill Services angezeigt. Dort können Worker-Skills neu geladen und Container gestartet/gestoppt/neugestartet werden. ## Mesh API ```text POST /api/mesh/enroll GET /api/mesh/workers PUT /api/mesh/workers/{id}/skills POST /api/mesh/workers/{id}/heartbeat POST /api/mesh/workers/{id}/reload DELETE /api/mesh/workers/{id} ``` Die Enrollment-Anfrage enthält den Bootstrap-Token. Der Master gibt danach pro Worker ein kryptografisch zufälliges Session Access Token aus. Dieses Token authentifiziert Worker -> Master Heartbeats/Registry und Master -> Worker Invoke/Reload. Enrollment-/Access-Token werden vom Debug-Trace als Secrets redigiert. ## Remote Invoke Der Master sendet an den Worker: ```json { "protocol": "jarvis.skill.invoke.v1", "request_id": "remote_...", "skill_id": "example.python.text", "action": "uppercase", "input": {"text": "Hallo"}, "context": { "trace_id": "http_...", "now": "2026-08-29T10:20:00+02:00", "timezone": "Europe/Berlin" } } ``` Die gleiche `trace_id` läuft damit vom User-Request über Agent/Workflow und Master bis in den Remote-Worker. ## Sicherheitsmodell Remote Skill Worker sind eine deutlich stärkere Prozessgrenze als Code im Master-Container, aber Container sind nicht automatisch eine perfekte Sandbox. Für untrusted Skills weiterhin sinnvoll: - keine unnötigen Host-Volumes - kein `docker.sock` in Worker-Containern - keine `--privileged` Worker - minimale Netzwerkfreigaben - Ressourcenlimits in Compose/Orchestrator - Secrets nur gezielt pro Worker injizieren Der **Master** bekommt den Docker-Socket nur für den expliziten Service-Controller. Die Runtime-Worker bekommen ihn nicht. ### 1 Container = 1 Skill ist ebenfalls möglich Der generische Worker kann sowohl mehrere Unterordner unter `/skills` als auch einen direkt auf `/skills/skill.json` gemounteten Einzel-Skill laden. Damit kann ein besonders sensibler Skill einen vollständig eigenen Container bekommen, während normale Skills gemeinsam einen Runtime-Worker nutzen. Wenn der Master die Worker später über den Docker-Controller starten soll, müssen die Container bereits existieren. Dafür z. B. einmal: ```bash docker compose --profile skill-workers build docker compose --profile skill-workers create ``` Anschließend erkennt `GET /api/docker/skill-services` auch die gestoppten, gelabelten Container und die UI kann sie starten. Der Controller erstellt oder entfernt absichtlich keine Container. ## v9.1 – Home Control Skill Pack v9.1 ergänzt das Skill Mesh um dedizierte Remote-Integrationen für Philips Hue, UniFi Network, UniFi Protect, Proxmox VE, Dockge/Compose, Netzwerkdiagnose/Wake-on-LAN und ntfy. Außerdem unterstützt das Skill-Protokoll jetzt `runtime.env_from`, sodass Secrets aus Worker-ENVs nur explizit an den jeweiligen Skill-Prozess weitergegeben werden. Details, ENV-Beispiele und Workflow-Ideen stehen in [`HOME-CONTROL-SKILLS.md`](HOME-CONTROL-SKILLS.md). ## v9.1.1 – Clean UI State Render Fix - Fixes a Clean-UI JavaScript scope regression where `renderCleanHome()` referenced the `renderState()`-local `sttLabel`. The resulting `ReferenceError` stopped the render chain before groceries, meals, recipes and later modules were refreshed. - Voice status labels are now derived through shared helpers (`sttStatusLabel` / `ttsStatusLabel`). - Dashboard module renderers are isolated with `safeRender`, so an optional card can no longer prevent unrelated modules such as the grocery list from updating. - Adds a regression test for the exact failure. ## v9.1.2 – Render Pipeline Reliability - Fixed a fatal Clean/HUD state rendering regression caused by dereferencing non-existent `#stt` / `#tts` DOM nodes after the voice diagnostics update. - System status rendering is now isolated behind `safeRender('system-status', ...)`; a HUD/status failure can no longer suppress groceries, skills, activity, chat, calendar, tasks, recipes or other state panels. - Voice tooltips now target the actual `.status-chip` elements via `#sttStatus` / `#ttsStatus`. - Static UI assets are served with `Cache-Control: no-store` to prevent an old `app.js` from surviving a container/image update. - Added a UI regression test that rejects direct JavaScript dereferences of DOM IDs that do not exist in `index.html`.