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
| Komponente | Technologie | Aufgabe |
|---|---|---|
| Console | React 18, Vite, Tailwind, shadcn | Web-UI |
| Core | FastAPI, Python 3.12, litellm | REST-API, Orchestrator, Execution-Mode-Policy, shared A-MEM module |
| Matrix | conduwuit, matrix-nio | Messaging-Backend |
| nginx | nginx 1.24+ | Reverse-Proxy, TLS |
| Ollama | ollama | Lokale 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:
AgentDiscovery.start()— beobachtet/agents/mit watchdogProjectLoader.start()— beobachtet/projects/SessionManager.start()— lädt aktive SessionsAgentRuntime.start()— startet Core-Agenten, Watchdog-Loop- JWT-Secret laden oder generieren
- Matrix Admin-Token holen
_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()
../../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:
load_skills(agent_dir)lädt alle.md-Dateien- Sortierung nach
priority(aufsteigend) select_skills(skills, user_text)filtert nach scope/triggersskills_to_system_prompt(selected)baut System-Prompt-Abschnitt
| scope | Verhalten |
|---|---|
always | Immer geladen |
on-demand | Wenn mindestens ein Trigger-Keyword im Nutzer-Text vorkommt (case-insensitive) |
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
- JWT HS256, 24h Gültigkeit
- Secret in
/etc/octopos/jwt_secret(600,octopos:octopos) - Bei fehlendem Secret →
503(kein stilles Akzeptieren des leeren Secrets) - Rate-Limiting: 10 Login-Versuche pro Minute pro IP
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).
| Modul | Idempotenz-Prüfung |
|---|---|
04_tuwunel.sh | Versions-Check der installierten Binary |
06_core_service.sh | venv existiert, users.json angelegt |
07_console.sh | npm build immer, nginx config wird überschrieben |
08_ollama.sh | ollama Binary vorhanden → skip |
09_https.sh | Cert-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
- Heartbeat-Timeout: Watchdog erkennt nach
timeoutSekunden →on_failureAktion restart: Agent wird neu gestartet, Matrix-Client reconnectetstop: Agent gestoppt, bleibt im Statuserroralert: Nur Log-Eintrag, kein automatischer Neustart
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
- OAuth-Token abgelaufen →
[Fehler] LLM nicht erreichbar - Ollama offline → litellm-Fallback schlägt fehl → Fehlermeldung im Chat
- Kein automatischer Retry (geplant in zukünftiger Version)
15. Bekannte Limitierungen
| Bereich | Limitation | Workaround |
|---|---|---|
| Claude OAuth | Token läuft nach ~30 Tagen ab | claude setup-token wiederholen |
| main.py | 1200+ Zeilen, Kern-Endpunkte (setup/status, auth, gitea, update) | Router-Module (system, projects, user, agent, llm, mcp) übernehmen den Großteil der Logik |
| Worker-Agenten | Keine eigene Matrix-Identität | Nur Boss ist Matrix-Bot |
| Task-Agent TTL | Nicht per Projekt konfigurierbar | 300s hardcoded in agent_runtime.py |
| Sessions | Kein Memory zwischen Sessions | QMD-Skills als persistentes Wissen |
| Rollen | Alle User haben Admin-Rechte | Rollenmodell vorbereitet, nicht durchgesetzt |