Zum Inhalt springen

Container-Richtlinien

Capability-Container laufen im Selu-Docker-Netzwerk zusammen mit dem Orchestrator. Um Sicherheit und Performance zu gewährleisten, folge diesen Richtlinien beim Erstellen deines Container-Images.

  • Dein gRPC-Server muss auf Port 50051 hören. Der Orchestrator erwartet dies und mappt ihn automatisch.
  • Capabilities können ausgehende HTTP/HTTPS-Anfragen an externe APIs stellen. Du kannst das über den network-Abschnitt in deiner manifest.yaml steuern.
  • Capabilities können nicht direkt mit anderen Capability-Containern kommunizieren. Alle Inter-Capability-Koordination läuft über den Orchestrator via delegate_to_agent.
  • Der Orchestrator verbindet sich mit deinem Container über das interne Docker-Bridge-Netzwerk. Binde nicht an 127.0.0.1 — binde an 0.0.0.0 oder [::].
  • Container werden in separate Docker-Bridge-Netzwerke nach Klasse eingeteilt: selu-tools-net für Tool-Klasse-Capabilities und selu-envs-net für Environment-Klasse-Capabilities.

Netzwerk-Aktivitäts-Tab mit Protokollierung ausgehender Anfragen

Steuere ausgehenden Netzwerkzugriff in deiner manifest.yaml:

manifest.yaml
network:
mode: allowlist
hosts:
- "api.openweathermap.org:443"
- "*.icloud.com:443" # Wildcard für Subdomains
- "cdn.example.com" # Beliebiger Port

Netzwerk-Modi:

  • none (Standard) — Kein externer Netzwerkzugriff
  • allowlist — Nur die aufgelisteten Hosts sind erreichbar
  • any — Uneingeschränkter Zugriff (mit Vorsicht verwenden)

Wildcard-Muster unterstützen Subdomain-Matching:

  • *.example.com:443 trifft auf api.example.com:443 zu, aber nicht auf example.com:443
  • *.example.com trifft auf Subdomains an beliebigen Ports zu

Der Orchestrator betreibt einen Egress-Proxy, der alle ausgehenden Anfragen für Sicherheitsüberprüfungen protokolliert. Nutzer können diese Protokolle auf der Detail-Seite deines Agenten einsehen. Jeder Container erhält ein eindeutiges Egress-Proxy-Auth-Token zur Anfragenzuordnung.

Der Orchestrator setzt Ressourcenlimits durch, die in deiner manifest.yaml deklariert sind. Wenn du keine Limits deklarierst, gelten vernünftige Standards:

RessourceStandardEmpfohlenes Maximum
Speicher128 MB1 GB
CPU0,5 Kerne2 Kerne
Timeout pro Aufruf30 s120 s
PIDs641024
Offene Dateien (ulimit nofile)256256

Wenn deine Capability Bilder als Teil von Tool-Ergebnissen an das LLM sendet, wendet Selu vor dem Versand an den Provider einen gemeinsamen Normalisierungsschritt an.

Bevor eine multimodale Nachricht an einen LLM-Provider gesendet wird, prüft Selu jedes eingebettete Bild gegen das Größenlimit des Providers:

  1. Wenn das Bild passt — wird es unverändert gesendet.
  2. Wenn das Bild zu groß ist — versucht Selu, es zu verkleinern und als JPEG neu zu komprimieren, um es unter das Limit zu bringen. Es werden mehrere Skalierungsfaktoren (bis zu 20 % der Originalgröße) und Qualitätsstufen (bis zu JPEG-Qualität 35) ausprobiert.
  3. Wenn es immer noch nicht passt — wird das Bild vollständig weggelassen und durch einen kurzen Texthinweis ersetzt, damit der Turn fortgesetzt werden kann, statt fehlzuschlagen.

Das Größenlimit hängt vom Provider ab:

  • Amazon Bedrock: 5 MB pro Bild
  • Andere Provider: 5 MB (gleicher Standardwert, aus Sicherheitsgründen angewendet)

Du musst nichts Besonderes tun — die Normalisierung erfolgt automatisch. Beachte jedoch folgende Punkte:

  • Bevorzuge angemessen große Bilder. Normalisierung kann die Qualität verschlechtern. Wenn dein Tool Bilder erzeugt, halte sie möglichst unter dem Limit.
  • Verlasse dich nicht auf exakte Pixelabmessungen. Verkleinerte Bilder können vom Original abweichen. Wenn exakte Pixeldaten für nachgelagerte Tools wichtig sind, verwende stattdessen Artefakte.
  • Weggelassene Bilder erzeugen einen Text-Platzhalter. Das LLM sieht dann einen Hinweis wie: „Bild weggelassen: Datei ist zu groß für den Modell-Eingang (Limit 5 MB). Bitte den Nutzer, ein kleineres Bild hochzuladen." Deine Capability sollte den Fall berücksichtigen, dass ein von ihr bereitgestelltes Bild für das Modell nicht sichtbar ist.

Selu unterstützt zwei Session-Modelle, die beeinflussen, wie deine Capability-Container verwaltet werden:

  • Mehrere Gesprächs-Threads teilen sich dieselben Capability-Container
  • Container-Zustand und Arbeitsbereich-Daten bleiben threadübergreifend erhalten
  • Ressourceneffizienter
  • Gut für zustandslose Capabilities
  • Jeder Gesprächs-Thread erhält dedizierte Capability-Container
  • Vollständige Arbeitsbereich-Isolation zwischen Threads
  • Höhere Ressourcennutzung, verhindert aber threadübergreifende Interferenzen
  • Unverzichtbar für zustandsbehaftete Capabilities wie Dateimanager oder Entwicklungsumgebungen

Agenten können Per-Thread-Isolation wählen, indem sie session.isolation: per_thread in ihrer agent.yaml setzen. Wenn dies aktiviert ist:

  • Deine Capability erhält einen einzigartigen Container für jeden Gesprächs-Thread
  • Arbeitsbereich-Daten werden nicht zwischen verschiedenen Nutzer-Gesprächen geteilt
  • Der Container-Lebenszyklus ist an den spezifischen Thread gebunden, nicht an die Nutzer-Session
  • Verwende minimale Base-Imagespython:3.12-slim, golang:1.22-alpine oder debian:bookworm-slim. Kleinere Images bedeuten schnellere Installationen für Nutzer.
  • Multi-Stage-Builds — Kompiliere in einem Build-Stage, kopiere nur das Binary/Runtime in das finale Stage.
  • Pinne Versionen — Pinne immer Base-Image-Tags und Dependency-Versionen für Reproduzierbarkeit.
  • Non-Root-Nutzer — Führe deinen Prozess als Non-Root-Nutzer aus. Selu wird Images, die als Root laufen, während der Marketplace-Überprüfung markieren.

Beispiel für ein Multi-Stage-Dockerfile:

Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
USER 1000
EXPOSE 50051
CMD ["python", "server.py"]
  • Keine Host-Mounts — Capabilities können keine Host-Verzeichnisse mounten. Aller Dateizugriff ist auf das ephemerale Dateisystem des Containers beschränkt.
  • Kein privilegierter Modus — Container laufen unprivilegiert. Capabilities, die spezielle Linux-Capabilities benötigen, werden vom Marketplace abgelehnt.
  • Zugangsdaten via Umgebungsvariablen — Verwende den credentials-Abschnitt in manifest.yaml, um erforderliche Geheimnisse zu deklarieren. Der Orchestrator injiziert sie beim Start aus dem verschlüsselten Credential-Store des Nutzers. Kodiere niemals Geheimnisse fest.
  • Orchestrator-injizierte Umgebungsvariablen — Zusätzlich zu Zugangsdaten erhält jeder Container die Umgebungsvariablen SELU_SESSION_ID und SELU_CAPABILITY_ID, die die aktuelle Session und Capability-Instanz identifizieren.
  • Keine eingehenden Ports — Nur Port 50051 ist exponiert, und nur für das interne Docker-Netzwerk. Capabilities sind von außerhalb des Selu-Stacks nicht zugänglich.

Beim Entwerfen von Capabilities für Agenten, die Per-Thread-Isolation verwenden, berücksichtige:

  • Verwende /workspace für thread-spezifische Dateien und Daten
  • Alle Container erhalten ein tmpfs-Mount bei /tmp (64 MB, noexec) unabhängig von der Dateisystem-Richtlinie — nutze es nur für temporäre Scratch-Daten
  • Erwarte ein sauberes Dateisystem für jeden neuen Gesprächs-Thread
  • Verlasse dich nicht darauf, dass Dateien über verschiedene Gespräche hinweg bestehen
  • Verwende externe APIs oder Datenbanken für Zustände, die threadübergreifend bestehen sollen
  • Erwäge die Verwendung der eingebauten Speicher-Tools des Agenten für einfache Schlüssel-Wert-Persistierung
  • Dokumentiere alle externen Abhängigkeiten klar in deinem Manifest
  • Implementiere ordnungsgemäße Bereinigung in deinen Capability-Shutdown-Handlern
  • Lasse keine Hintergrundprozesse nach Tool-Aufrufen laufen
  • Verwende angemessene Timeouts für langfristige Operationen
  • Veraltete Container aus früheren Orchestrator-Läufen werden beim Start automatisch bereinigt — verlasse dich nicht darauf, dass dein Container einen Orchestrator-Neustart überlebt

Deklariere alle Geheimnisse, die deine Capability benötigt, im credentials-Abschnitt von manifest.yaml. Nutzer werden während des Agenten-Setups aufgefordert, diese einzugeben:

manifest.yaml
credentials:
- name: OPENWEATHER_API_KEY
scope: system
required: true
description: >
API-Schlüssel von OpenWeatherMap (https://openweathermap.org/api).
Erstelle ein kostenloses Konto, um deinen Schlüssel zu erhalten.
- name: CACHE_REDIS_URL
scope: system
required: false
description: >
Optionale Redis-URL zum Cachen von Wetterdaten. Falls nicht angegeben,
werden Antworten jedes Mal frisch abgerufen.

Der Orchestrator injiziert diese als Umgebungsvariablen beim Starten deines Containers. Füge immer hilfreiche Beschreibungen hinzu — sie werden Nutzern angezeigt, wenn sie den Agenten einrichten.

  • system — Geteilt über alle Nutzer. Wird einmalig von einem Admin gesetzt.
  • user — Persönlich für jeden Nutzer. Jede Person gibt ihre eigenen API-Schlüssel an.

Wähle system für organisationsweite API-Schlüssel und user für persönliche Konten.

Implementiere das Healthcheck RPC aus capability.proto. Der Orchestrator ruft diese gRPC-Methode während des Container-Starts auf, um die Bereitschaft zu prüfen. Sobald deine Capability mit ready: true antwortet, gilt der Container als aktiv und der Orchestrator beginnt, Aufrufe an ihn weiterzuleiten.

Nach dem Start gibt es keine periodische Überwachung. Wenn eine Capability während eines Aufrufs fehlschlägt, wird der Fehler an den Agenten zurückgemeldet — der Orchestrator startet den Container nicht automatisch neu.

Für Capabilities mit dynamischer Tool-Discovery hat dein Discovery-Tool Zugriff auf konfigurierte Zugangsdaten und kann je nach Verfügbarkeit unterschiedliche Tool-Sets zurückgeben:

server.py
def handle_discovery(self, credentials):
tools = [
# Immer verfügbar
{
"name": "basic_search",
"description": "Grundlegende Suchfunktionalität",
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
"recommended_policy": "allow"
}
]
# Zusätzliche Tools basierend auf Zugangsdaten
if "PREMIUM_API_KEY" in credentials:
tools.append({
"name": "premium_search",
"description": "Erweiterte Suche mit Premium-Funktionen",
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
"recommended_policy": "ask"
})
if "ADMIN_TOKEN" in credentials:
tools.append({
"name": "admin_functions",
"description": "Administrative Operationen",
"input_schema": {"type": "object", "properties": {"action": {"type": "string"}}},
"recommended_policy": "block"
})
return tools

So kann deine Capability ihre Funktionalität je nach konfigurierten Zugangsdaten anpassen.

Capabilities können Dateien über das Artefakt-System mit Agenten austauschen. Das ermöglicht Tools, Dokumente zu verarbeiten, Dateien zu erzeugen und komplexe Daten zwischen Capability-Aufrufen innerhalb derselben Session weiterzugeben.

Wenn ein Agent dein Tool mit Dateianhängen aufruft, lädt der Orchestrator sie automatisch in deinen Container, bevor dein Tool aufgerufen wird:

server.py
def handle_tool_call(self, tool_name, args, session_id):
if tool_name == "process_document":
attachments = args.get("attachments", [])
for attachment in attachments:
filename = attachment["filename"]
mime_type = attachment["mime_type"]
size_bytes = attachment["size_bytes"]
capability_artifact_id = attachment["capability_artifact_id"]
# Nutze die gRPC DownloadInputArtifact-Methode, um die Dateidaten zu holen
file_data = self.download_artifact(capability_artifact_id)
result = process_file(file_data, mime_type)

Deine Tools können Dateien erzeugen und als Artefakte zurückgeben, damit andere Tools sie verwenden können:

server.py
def handle_tool_call(self, tool_name, args, session_id):
if tool_name == "generate_report":
pdf_data = create_pdf_report(args["report_type"])
artifact_id = self.upload_artifact("report.pdf", "application/pdf", pdf_data)
return {
"message": "Bericht erfolgreich erstellt",
"artifact": {
"filename": "report.pdf",
"mime_type": "application/pdf",
"capability_artifact_id": artifact_id
}
}
  • Artefakte sind auf die Nutzer-Session beschränkt und verfallen automatisch nach 6 Stunden
  • Der Orchestrator übernimmt das gesamte Artefakt-ID-Management und die Übersetzung zwischen Capabilities
  • Die Dateigröße ist auf 5 MB pro Artefakt begrenzt
  • Gängige MIME-Typen werden unterstützt (Dokumente, Bilder, Textdateien usw.)

Siehe die gRPC-Schnittstelle für den vollständigen Proto-Vertrag oder das manifest.yaml-Referenz für vollständige Schema-Details.