OctopOS — Entwickler-Guide

Wie man OctopOS erweitert: neue Tools, Skills, Console-Seiten, Installer-Module.

1. Entwicklungsumgebung

Voraussetzungen

Console — lokale Entwicklung

cd console
npm install
npm run dev   # Vite Dev-Server auf Port 5173

API-Calls gehen an /api/.... Dafür wird entweder ein lokaler Core (Port 8765) oder ein Proxy in der Vite-Konfiguration benötigt:

// vite.config.ts
export default defineConfig({
  plugins: [react()],
  server: { proxy: { "/api": "http://192.168.178.181" } }
})

Core — lokale Entwicklung

cd core
python3 -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
uvicorn octopos_core.main:app --reload --port 8765

Mit --reload startet der Server bei Dateiänderungen automatisch neu.

2. Eigenes Tool schreiben

Tools sind Python-Klassen die von BaseTool erben. Sie werden im tool_registry registriert und können dann in agent.yaml aktiviert werden.

Minimales Beispiel

# core/src/octopos_core/tools/my_tool.py
from ..tool_registry import BaseTool

class MyTool(BaseTool):
    @property
    def id(self) -> str: return "my_tool"

    @property
    def description(self) -> str: return "Beschreibung für das LLM"

    @property
    def parameters(self) -> dict:
        return {
            "type": "object",
            "properties": {
                "input": {"type": "string", "description": "Die Eingabe"}
            },
            "required": ["input"]
        }

    async def execute(self, agent_id, project_id, input, **kwargs) -> str:
        return f"Verarbeitet: {input}"

Filesystem-Tool mit Pfad-Sicherheit

Für Tools die auf das Projektverzeichnis zugreifen, muss assert_path_within_project verwendet werden — andernfalls können Agenten das Dateisystem verlassen.

from ..tool_registry import BaseTool, assert_path_within_project

class MyFileTool(BaseTool):
    async def execute(self, agent_id, project_id, path, **kwargs) -> str:
        safe_path = assert_path_within_project(path, project_id)
        if not safe_path.exists():
            return f"Datei nicht gefunden: {path}"
        return safe_path.read_text(encoding="utf-8")

Tool registrieren

In tool_registry.py am Ende der Registry-Initialisierung eintragen:

registry.register(MyTool())

Tool im Agenten aktivieren

In agent.yaml des gewünschten Agenten:

tools:
  - my_tool

3. Eigene Skills schreiben

Skills sind Markdown-Dateien mit YAML-Frontmatter. Sie liegen unter /agents/<id>/skills/ und werden automatisch in den System-Prompt des Agenten geladen.

Skill-Datei

---
skill: Steuerrecht Grundlagen
version: "1.0"
scope: on-demand
triggers:
  - steuer
  - finanzamt
  - umsatzsteuer
priority: 10
---

## Steuerrecht Grundlagen

Umsatzsteuer (USt) beträgt in Deutschland standardmäßig 19%.
Ermäßigter Satz: 7% für Lebensmittel, Bücher, ÖPNV.

Scope-Optionen

ScopeVerhalten
alwaysImmer in jedem Request geladen, unabhängig vom Inhalt der Nachricht
on-demandNur wenn ein Trigger-Keyword im User-Text vorkommt
Hot-Reload: Skills werden bei jedem Request neu eingelesen. Ein Neustart des Core-Services ist nicht notwendig — Änderungen wirken sofort.

Skill über die Konsole anlegen

Agenten → Agent auswählen → Buch-Icon → Neuer Skill

4. Core-Endpoint hinzufügen

Alle Endpoints sind in main.py definiert. Bei wachsender Größe (über ~1500 Zeilen) sollte in separate Router-Module aufgeteilt werden.

Beispiel-Endpoint

from pydantic import BaseModel
from .auth import require_auth
from .audit import audit_log

class MyRequest(BaseModel):
    name: str
    value: str

@app.post("/my-endpoint")
async def my_endpoint(
    req: MyRequest,
    current_user: str = Depends(require_auth)
):
    # ... Logik ...
    await audit_log(
        user=current_user,
        action="my_resource.create",
        target=req.name
    )
    return {"created": True, "name": req.name}

audit_log-Signatur

await audit_log(
    user=current_user,    # Benutzername
    action="...",         # z.B. "agent.create"
    target="...",         # Betroffenes Objekt (optional)
    project_id="...",     # Projekt-ID (optional)
    details={}            # Zusätzliche Infos (optional)
)
Auth-Hinweis: Depends(require_auth) für geschützte Endpoints. Für öffentliche Endpoints (z.B. /health, /hooks/*) kein Depends angeben.

5. Console-Seite hinzufügen

Neue Seiten bestehen aus 4 Schritten: Komponente, API-Methode, Route und Navigation.

  1. 1 Page-Komponente erstellen unter console/src/pages/MyPage.tsx
    import { useEffect, useState } from "react"
    import { api } from "@/lib/api"
    
    export default function MyPage() {
      const [data, setData] = useState(null)
    
      useEffect(() => {
        api.getMyData().then(setData)
      }, [])
    
      return (
        <div className="p-6">
          <h1 className="text-xl font-semibold mb-4">Meine Seite</h1>
          {/* Inhalt */}
        </div>
      )
    }
  2. 2 API-Methode hinzufügen in console/src/lib/api.ts
    export interface MyData {
      id: string
      name: string
    }
    
    // Im api-Objekt:
    getMyData: (): Promise<MyData[]> =>
      fetchJSON("/api/my-endpoint"),
  3. 3 Route registrieren in console/src/App.tsx
    import MyPage from "./pages/MyPage"
    
    // In der Router-Konfiguration:
    <Route path="/my-page" element={<MyPage />} />
  4. 4 Nav-Eintrag hinzufügen in console/src/layouts/AdminLayout.tsx
    import { MyIcon } from "lucide-react"
    
    // In der navItems-Liste:
    { href: "/my-page", label: "Meine Seite", icon: MyIcon },

Styling-Konventionen

6. Installer-Modul hinzufügen

Installer-Module sind eigenständige Bash-Skripte die von install.sh per source eingebunden werden. Jedes Modul muss idempotent sein.

Modul-Template

#!/usr/bin/env bash
# installer/modules/my_module.sh

install_my_component() {
    info "Prüfe my-component..."

    if command -v my-tool >/dev/null 2>&1; then
        info "my-component bereits installiert — übersprungen"
        return 0
    fi

    info "Installiere my-component..."
    apt-get install -y my-package || {
        error "Installation von my-component fehlgeschlagen"
        return 1
    }

    success "my-component installiert"
}

install_my_component

In install.sh einbinden

source "$SCRIPT_DIR/modules/my_module.sh"

Regeln für Module

7. Deploy-Workflow

Core — schnelles Deployment (einzelne Datei)

# Einzelne Datei auf die VM kopieren und Service neu starten
scp core/src/octopos_core/main.py octopos@192.168.178.181:/tmp/main.py
ssh octopos@192.168.178.181 "sudo cp /tmp/main.py /opt/octopos/core/src/octopos_core/main.py && sudo systemctl restart octopos-core"

Core — vollständiges Verzeichnis

scp -r core/src/octopos_core octopos@192.168.178.181:/tmp/octopos_core
ssh octopos@192.168.178.181 "sudo cp -r /tmp/octopos_core /opt/octopos/core/src/ && sudo systemctl restart octopos-core"

Console — Deploy

# Build erstellen
cd console && npm run build

# Dist auf die VM kopieren
scp -r dist/ octopos@192.168.178.181:/tmp/console_dist/

# Auf der VM installieren
ssh octopos@192.168.178.181 "sudo cp -r /tmp/console_dist/. /var/www/octopos/"

Status prüfen

ssh octopos@192.168.178.181 \
  "systemctl is-active octopos-core && journalctl -u octopos-core -n 20 --no-pager"

Git-Workflow

Regel: Immer git pull --rebase vor einem Push. Commits sollten Feature + Console + Core zusammen bündeln wenn möglich.
git pull --rebase origin main
git add core/... console/...
git commit -m "feat: mein neues Feature"
git push

8. Coding-Konventionen

Python (Core)

TypeScript (Console)

Allgemein

Dateigrößen

main.py: Aktuell ~1180 Zeilen. Ab ~1500 Zeilen in separate Router-Module aufteilen (routers/agents.py, routers/projects.py etc.) um die Wartbarkeit zu erhalten.