989 lines
32 KiB
Markdown
989 lines
32 KiB
Markdown
# 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/<modul>/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`.
|