Technische Dokumentation

Diese Dokumentation richtet sich an Entwickler die OctopOS verstehen, erweitern oder deployen wollen.

1. Architektur-Übersicht

┌─────────────────────────────────────────────────────┐
│                   Browser (React)                    │
│              console/src/  (Vite + Tailwind)         │
└────────────────────────┬────────────────────────────┘
                         │ HTTPS (nginx)
                         ▼
┌─────────────────────────────────────────────────────┐
│              nginx (Port 443 / 80→443)               │
│   Static: /opt/octopos/console/                      │
│   Proxy:  /api/ → localhost:8765                     │
└────────────────────────┬────────────────────────────┘
                         │ HTTP
                         ▼
┌─────────────────────────────────────────────────────┐
│           OctopOS Core (FastAPI, Port 8765)          │
│  ┌──────────┐  ┌─────────────┐  ┌───────────────┐  │
│  │  Agent   │  │  Project    │  │   Session     │  │
│  │Discovery │  │  Loader     │  │   Manager     │  │
│  └────┬─────┘  └──────┬──────┘  └───────┬───────┘  │
│  ┌────▼──────────────────────────────────▼───────┐  │
│  │              Orchestrator                      │  │
│  │  Boss-Agent → dispatch_task → Worker-Agenten  │  │
│  └────────────────────┬──────────────────────────┘  │
│  ┌────────────────────▼──────────────────────────┐  │
│  │            AgentRuntime + Watchdog             │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
                         │
              ┌──────────▼──────────┐
              │  conduwuit (Matrix) │
              │  Port 6167          │
              └─────────────────────┘

Komponenten

KomponenteTechnologieAufgabe
ConsoleReact 18, Vite, Tailwind, shadcnWeb-UI
CoreFastAPI, Python 3.12, litellmREST-API, Orchestrator, Execution-Mode-Policy, shared A-MEM module
Matrixconduwuit, matrix-nioMessaging-Backend
nginxnginx 1.24+Reverse-Proxy, TLS
OllamaollamaLokale LLM-Inferenz

2. Verzeichnisstruktur

Repository

octopos/
├── core/
│   └── src/octopos_core/
│       ├── main.py                # FastAPI-App, alle Endpoints
│       ├── orchestrator.py        # Boss-Agent, Task-Dispatching
│       ├── agent_runtime.py       # Agent-Lifecycle, Watchdog
│       ├── agent_discovery.py     # /agents/ Verzeichnis beobachten
│       ├── agent_config.py        # agent.yaml Parsing (Pydantic)
│       ├── project_config.py      # project.yaml Parsing (Pydantic)
│       ├── project_loader.py      # /projects/ Verzeichnis beobachten
│       ├── session_manager.py     # Chat-History, Persistenz
│       ├── matrix_agent.py        # Matrix-Bot Basisklasse
│       ├── skill_loader.py        # QMD Skill-Parsing
│       ├── tool_registry.py       # Tool-Interface, Path-Safety
│       └── provisioner.py         # Matrix-User, Samba-Share anlegen
├── console/src/
│   ├── pages/                     # Seiten (eine Datei pro Route)
│   ├── components/                # Wiederverwendbare Komponenten
│   │   ├── layout/AdminLayout.tsx
│   │   ├── SkillsPanel.tsx
│   │   └── WebhooksPanel.tsx
│   ├── hooks/useAuth.ts
│   └── lib/api.ts
├── installer/
│   ├── install.sh
│   └── modules/
│       ├── 01_os_check.sh
│       ├── 02_gpu_detect.sh
│       ├── 03_dependencies.sh
│       ├── 04_tuwunel.sh
│       ├── 05_admin_account.sh
│       ├── 06_core_service.sh
│       ├── 07_console.sh
│       ├── 08_ollama.sh
│       └── 09_https.sh
└── docs/

Laufzeit (auf dem Server)

/opt/octopos/core/src/octopos_core/   # Core-Quellcode
/opt/octopos/console/                  # Gebaute React-App
/opt/octopos/venv/                     # Python-Virtualenv

/agents/boss-main/
│   ├── agent.yaml
│   ├── soul.md
│   └── skills/

/projects/buchhaltung/
│   ├── project.yaml
│   ├── webhooks.json
│   └── sessions/

/etc/octopos/
│   ├── jwt_secret
│   ├── llm_config.json
│   ├── claude_oauth_token
│   ├── users.json
│   ├── agent_tokens/
│   └── tls/

/var/log/octopos/audit.jsonl

3. Core-Module

main.py

FastAPI-Applikation mit Lifespan-Management. Registriert Router-Module und hält JWT/Auth, auditing und Execution-Mode-Policy.

Router-Module: router_system, router_projects, router_user_*, router_agent_chat, router_llm, router_mcp, router_project_lifecycle, router_user_integrations.

Startup-Reihenfolge:

  1. AgentDiscovery.start() — beobachtet /agents/ mit watchdog
  2. ProjectLoader.start() — beobachtet /projects/
  3. SessionManager.start() — lädt aktive Sessions
  4. AgentRuntime.start() — startet Core-Agenten, Watchdog-Loop
  5. JWT-Secret laden oder generieren
  6. Matrix Admin-Token holen
  7. _setup_matrix_clients() — BossMatrixAgent für Projekte mit Room starten

agent_runtime.py

Verwaltet laufende Agenten als AgentHandle-Objekte:

@dataclass
class AgentHandle:
    config:         AgentConfig
    status:         AgentStatus      # STARTING | RUNNING | STOPPED | ERROR
    last_heartbeat: float
    restart_count:  int
    task:           asyncio.Task     # _run_agent() Coroutine
    matrix_client:  MatrixAgent | None

Watchdog-Loop (alle 10s): Prüft Heartbeat-Alter gegen timeout. Bei Timeout: on_failure auswerten (restart / stop / alert).

orchestrator.py

Kernstück des Systems. Empfängt Nutzer-Nachrichten, baut LLM-Kontext auf, führt Tool-Calls aus.

Message-Queue: Pro Projekt eine asyncio.Queue. Parallele Nachrichten werden sequenziell verarbeitet.

session_manager.py

Jedes Projekt hat genau eine aktive Session. get_context(max_messages=50) gibt die letzten N Nachrichten als OpenAI-kompatibles Format zurück.

4. Agent-Lebenszyklus

Verzeichnis /agents/<id>/ angelegt
         │
         ▼
AgentDiscovery erkennt agent.yaml (watchdog)
         │
         ▼
AgentConfig wird geparst (Pydantic-Validierung)
         │
         ▼
AgentRuntime._spawn() → AgentHandle anlegen
         │
         ▼
_run_agent() als asyncio.Task starten
         │
    ┌────▼────┐
    │ Matrix? │
    │  ja  ───▶ matrix_client.start() → run() (Sync-Loop)
    │            ↕ Watchdog: bei Absturz → 15s → Restart
    │  nein ──▶ Heartbeat-Ticker (asyncio.sleep)
    └─────────┘
         │
         ▼
Watchdog prüft last_heartbeat alle 10s
Timeout? → on_failure: restart → _respawn()

5. Orchestrator und Message-Flow

POST /projects/{id}/message[/stream]
         │
         ▼
orchestrator.handle_message()
         │ (asyncio.Queue pro Projekt)
         ▼
_handle_message_impl()
         ├─ 1. User-Message → SessionManager.append()
         ├─ 2. Boss-Agent Soul laden (soul.md)
         ├─ 3. Skills matchen (scope=always oder trigger-match)
         ├─ 4. System-Prompt = Soul + Skills
         ├─ 5. litellm.acompletion(messages=[system, history, user])
         └─ 6. Tool-Calls?
              ├─ dispatch_task → Worker-Agent spawnen
              │   └─ Worker führt Tool aus → Ergebnis zurück an Boss
              └─ Weitere LLM-Iteration bis fertig
                       │
              Assistant-Message → SessionManager.append()
                       │
              return (response_text, workers_used)

Streaming: Identischer Flow, aber litellm.acompletion(stream=True). Yields data: {"text": "..."} SSE-Events, Abschluss: data: {"done": true}.

6. Konfigurationsformate

agent.yaml

id: mein-agent              # Pflicht, eindeutig
type: specialist             # boss | specialist | worker
identity: Mein Agent Name

llm:
  model: llama3.1:8b        # litellm-kompatibler Modellname
  temperature: 0.7
  max_tokens: 4096

soul: |                     # Optional: inline — oder soul.md verwenden
  Du bist ein hilfreicher Assistent...

tools:
  - file_read
  - file_write
  - web_search
  - dispatch_task

heartbeat:
  interval: 30s
  timeout: 90s
  on_failure: restart        # restart | stop | alert

project.yaml

id: mein-projekt
version: "1.0.0"

identity:
  name: Mein Projekt
  description: Optional

agents:
  boss: boss-agent-id
  workers:
    - specialist-1

matrix:
  room: "!roomid:server.de"  # leer → wird beim Provisionieren angelegt

filesystem:
  samba: true

chat:
  show_swarm: false

soul.md

# Steuerbert

Du bist Steuerbert, ein erfahrener Steuerberater-Assistent.

## Dein Charakter
- Präzise und verlässlich
- Du weist auf Fristen und Risiken hin

## Wichtige Regeln
- Keine Rechtsberatung, nur Information
- Bei Unsicherheit: Fachmann empfehlen

7. Tool-System

class BaseTool(ABC):
    @property @abstractmethod
    def id(self) -> str: ...          # "file_read"

    @property @abstractmethod
    def description(self) -> str: ... # Für LLM sichtbar

    @property @abstractmethod
    def parameters(self) -> dict: ... # JSON-Schema (function calling)

    @abstractmethod
    async def execute(
        self, agent_id: str, project_id: str, **kwargs,
    ) -> str: ...                     # Rückgabe als String ans LLM

Filesystem-Safety

Alle Filesystem-Tools müssen assert_path_within_project() aufrufen:

from .tool_registry import assert_path_within_project

async def execute(self, agent_id, project_id, path, **kwargs):
    safe_path = assert_path_within_project(path, project_id)
    # garantiert innerhalb /projects/<id>/
    return safe_path.read_text()
Path-Traversal-Schutz: ../../etc/passwd, absolute Pfade außerhalb und Symlink-Escapes werden mit PathSafetyError abgelehnt.

8. Skill-System (QMD)

QMD = YAML-Frontmatter + Markdown. Skills werden aus /agents/<id>/skills/*.md geladen.

Ladereihenfolge:

  1. load_skills(agent_dir) lädt alle .md-Dateien
  2. Sortierung nach priority (aufsteigend)
  3. select_skills(skills, user_text) filtert nach scope/triggers
  4. skills_to_system_prompt(selected) baut System-Prompt-Abschnitt
scopeVerhalten
alwaysImmer geladen
on-demandWenn mindestens ein Trigger-Keyword im Nutzer-Text vorkommt (case-insensitive)
Hot-Reload: Skills werden bei jedem Request neu geladen — Änderungen wirken sofort ohne Core-Neustart.

9. Matrix-Integration

conduwuit (Port 6167, intern)
    ↑ nginx proxy (Port 8008, extern)
    ↑
matrix-nio AsyncClient
    ↑
MatrixAgent (Basisklasse)
    ↑
BossMatrixAgent (pro Projekt mit Room)

MatrixAgent Interface

class MatrixAgent(ABC):
    async def start(self) -> None: ...    # Login + Rooms joinen
    async def run(self) -> None: ...      # Sync-Loop (blockierend)
    async def stop(self) -> None: ...     # Cleanup
    async def send_message(self, room_id, text): ...
    async def send_markdown(self, room_id, markdown): ...

    @abstractmethod
    async def on_user_message(self, room, text, sender): ...

Authentifizierung

Bot-Accounts werden beim ersten Start automatisch auf conduwuit registriert. Der Access-Token wird in /etc/octopos/agent_tokens/<id>.json gespeichert.

Watchdog

Bei Exception in run(): 15s warten → start() + run() neu starten. Endlosschleife bis CancelledError.

10. Session-Management

@dataclass
class Message:
    role:      MessageRole    # user | assistant | system | tool
    content:   str
    timestamp: str            # ISO-8601 UTC
    agent_id:  str | None

@dataclass
class Session:
    id:         str           # UUID4
    project_id: str
    started_at: str
    messages:   list[Message]

get_context(max_messages=50) gibt die letzten N Nachrichten als [{"role": "user", "content": "..."}] zurück — direkt kompatibel mit litellm/OpenAI-API.

Sessions werden in /projects/<id>/sessions/ als JSON persistiert. Beim Core-Neustart automatisch geladen.

11. Sicherheitsmodell

Authentifizierung

Filesystem-Isolation

Jedes Projekt läuft als eigener Linux-User proj_<id>. Agenten können nur auf /projects/<id>/ schreiben. assert_path_within_project() wird in allen Filesystem-Tools erzwungen.

Setup-Schutz

POST /setup ist nur verfügbar solange users.json leer ist. Race Condition verhindert durch asyncio.Lock. Atomares Schreiben via tmp.replace(target).

Audit-Log

Alle sicherheitsrelevanten Aktionen in /var/log/octopos/audit.jsonl. Append-only, non-blocking.

12. Installer-Architektur

Modular — jedes Modul ein eigenständiges Bash-Skript, via source eingebunden. Alle Module teilen info(), success(), warn(), error() und $PROFILE (minimal | standard | full).

ModulIdempotenz-Prüfung
04_tuwunel.shVersions-Check der installierten Binary
06_core_service.shvenv existiert, users.json angelegt
07_console.shnpm build immer, nginx config wird überschrieben
08_ollama.shollama Binary vorhanden → skip
09_https.shCert-Datei vorhanden → skip, Ablaufdatum loggen

13. Datenfluss — Eine Nachricht von A bis Z

1. Browser        POST /api/projects/buchhaltung/message/stream
                  Body: {"content": "Was ist die Umsatzsteuer?"}

2. nginx          Proxy → localhost:8765/projects/.../message/stream

3. FastAPI        Auth-Check JWT → send_message_stream()

4. Orchestrator   asyncio.Queue für "buchhaltung"
                  _queue_worker() → _handle_message_impl()

5. Impl:
   a) Session: append(USER, "Was ist die Umsatzsteuer?")
   b) System-Prompt: soul.md + QMD-Skills
      (on-demand: "umsatzsteuer" matched → Skill geladen)
   c) History: letzte 20 Nachrichten aus Session
   d) Tools: dispatch_task, file_read (aus agent.yaml ∩ Registry)

6. LLM-Call       OAuth-Token vorhanden?
   → Ja:  Anthropic SDK direkt
   → Nein: litellm (Ollama / OpenAI)

7. Streaming      Token für Token → SSE: data: {"text": "Die Umsatz..."}

8. Tool-Loop      LLM ruft dispatch_task auf?
   → Worker-Agent gespawnt → LLM-Call → Ergebnis zurück an Boss

9. Session        append(ASSISTANT, vollständige Antwort)

10. Browser       ReadableStream liest SSE → Token erscheinen live
    ChatPage      Markdown-Rendering (ReactMarkdown + prose)

14. Fehlerbehandlung und Resilience

Agent-Ausfall

Matrix-Verbindungsabbruch

Bei Exception in run(): 15s warten → start() + run() neu. Audit-Log: agent.matrix_reconnect.

Core-Neustart

Sessions persistent in /projects/<id>/sessions/ — werden beim Start geladen. Agenten werden automatisch neu gestartet. Matrix-Bots joinen wieder aus project.yaml.

LLM-Fehler

15. Bekannte Limitierungen

BereichLimitationWorkaround
Claude OAuthToken läuft nach ~30 Tagen abclaude setup-token wiederholen
main.py1200+ Zeilen, Kern-Endpunkte (setup/status, auth, gitea, update)Router-Module (system, projects, user, agent, llm, mcp) übernehmen den Großteil der Logik
Worker-AgentenKeine eigene Matrix-IdentitätNur Boss ist Matrix-Bot
Task-Agent TTLNicht per Projekt konfigurierbar300s hardcoded in agent_runtime.py
SessionsKein Memory zwischen SessionsQMD-Skills als persistentes Wissen
RollenAlle User haben Admin-RechteRollenmodell vorbereitet, nicht durchgesetzt