Zum Inhalt springen

UIAP Core

FeldWert
StatusDraft
Version0.1
Datum2026-03-27
Abhängigkeiten
EditorenPatrick

UIAP (UI Control Protocol) ist ein transport-agnostisches Protokoll zwischen einer laufenden Anwendung und einem KI-Agenten oder Agent-Bridge-Layer.

UIAP Core v0.1 definiert:

  • die gemeinsame Nachrichtenhülle
  • Sitzungsaufbau und Sitzungslebenszyklus
  • Fehlerformat und Fehlercodes
  • Versionsaushandlung
  • Erweiterungsmechanismus
  • Capability-Discovery auf hoher Ebene

UIAP Core v0.1 definiert nicht:

  • konkrete Web-/DOM-/ARIA-Semantik
  • konkrete Browser-Steuerung
  • Policy- oder Sicherheitsregeln im Detail
  • Workflow-/Skill-Definitionen
  • visuelle Präsentation wie Ghost Cursor oder Highlights

Diese Dinge gehören in Profile oder Erweiterungen.

Die Schlüsselwörter MUSS, DARF NICHT, SOLLTE, KANN in diesem Dokument sind normativ gemäss RFC 2119 und BCP 14 zu interpretieren, wenn und nur wenn sie in GROSSBUCHSTABEN erscheinen.

  1. UIAP ist transport-agnostisch. Es KANN über WebSocket, WebRTC DataChannel, postMessage, lokale IPC oder andere Bindings transportiert werden.
  2. UIAP-Nachrichten sind UTF-8 kodierte JSON-Objekte.
  3. Jede UIAP-Nachricht MUSS eine gemeinsame Envelope verwenden.
  4. UIAP Core ist strict-by-envelope, flexible-by-payload: Pflichtfelder MÜSSEN validiert werden; unbekannte optionale Felder SOLLTEN ignoriert werden.
  5. UIAP trennt Protokollvertrag von Ausführungsmechanik.

type Version = string; // Format: "major.minor", z. B. "0.1"
type Timestamp = string; // ISO-8601 UTC, z. B. "2026-03-26T13:12:09.123Z"
type MessageId = string; // 1..128 Zeichen, innerhalb einer Session eindeutig
type SessionId = string; // 1..128 Zeichen, vom Session-Owner vergeben
type ExtensionId = string; // z. B. "uiap.policy" oder "x.vendor.foo"
type MessageType = string; // z. B. "session.initialize"
type JsonObject = Record<string, unknown>;
  • MessageId MUSS innerhalb einer Session eindeutig sein.
  • SessionId MUSS pro aktiver Session eindeutig sein.
  • Vendor-spezifische Kennungen SOLLTEN stabil und menschenlesbar sein.
  • Erweiterungsnamen SOLLTEN kleingeschrieben und punkt-segmentiert sein, z. B. uiap.policy oder x.acme.billing.

Jede Nachricht MUSS diese Hülle verwenden.

interface UIAPEnvelope {
uiap: Version; // Protokollversion dieser Nachricht
kind: "request" | "response" | "event" | "error";
type: MessageType; // z. B. "session.initialize"
id: MessageId;
sessionId?: SessionId; // vor Session-Init optional
correlationId?: MessageId; // Pflicht für response/error
ts: Timestamp;
source: EndpointRef;
target?: EndpointRef;
seq?: number; // optional, für ungeordnete Transporte
requires?: string[]; // benötigte Profile/Extensions
payload: JsonObject; // NIE null, IMMER Objekt
ext?: Record<ExtensionId, unknown>; // optionale Extension-Daten
}
interface EndpointRef {
role: "app" | "agent" | "bridge" | "observer" | string;
id: string;
instanceId?: string;
}
  1. uiap, kind, type, id, ts, source, payload MÜSSEN vorhanden sein.
  2. payload MUSS ein JSON-Objekt sein, auch wenn es leer ist.
  3. correlationId MUSS bei response und error gesetzt sein.
  4. sessionId DARF bei session.initialize fehlen; danach SOLLTE es immer vorhanden sein.
  5. ext DARF nur Erweiterungsdaten enthalten. Core-Felder DÜRFEN NICHT über ext überschrieben werden.
  6. Unbekannte optionale Felder SOLLTEN ignoriert werden.
  7. Sender SOLLTEN optionale Felder lieber weglassen als null senden, sofern null keine eigene Semantik hat.
  • request: erwartet genau eine response oder error
  • response: erfolgreiche Antwort auf eine request
  • event: unidirektionale Zustands- oder Statusmeldung, ohne Pflichtantwort
  • error: fehlgeschlagene Bearbeitung einer request

Core reserviert:

  • session.*
  • capabilities.*
  • error

Erweiterungen MÜSSEN ihren eigenen Namensraum verwenden, z. B.:

  • uiap.policy.*
  • uiap.workflow.*
  • x.vendor.feature.*

Eine UIAP-Session hat genau einen dieser Zustände:

  • NEW
  • INITIALIZING
  • ACTIVE
  • INTERRUPTED
  • TERMINATING
  • TERMINATED
  • NEW -> INITIALIZING durch session.initialize
  • INITIALIZING -> ACTIVE durch session.initialized
  • ACTIVE -> INTERRUPTED durch session.interrupt / session.interrupted
  • INTERRUPTED -> ACTIVE durch session.resume / session.resumed
  • ACTIVE|INTERRUPTED -> TERMINATING durch session.terminate
  • TERMINATING -> TERMINATED durch session.terminated
  • Jeder Zustand KANN bei fatalem Transportfehler direkt zu TERMINATED wechseln
  1. Eine Session MUSS mit session.initialize begonnen werden.
  2. Die Gegenstelle MUSS mit session.initialized oder error antworten.
  3. Vor erfolgreichem Handshake DÜRFEN keine nicht-Session-Core-Nachrichten verarbeitet werden.
  4. Capability-Informationen KÖNNEN inline im Handshake oder nachgelagert geliefert werden.

Startet eine Session und verhandelt Versionen, Profile und Erweiterungen.

interface SessionInitializePayload {
supportedVersions: Version[]; // non-empty
supportedProfiles?: string[]; // z. B. ["[email protected]"]
supportedExtensions?: ExtensionOffer[];
capabilityDelivery?: "inline" | "deferred" | "none";
peer: PeerInfo;
metadata?: JsonObject;
}
interface PeerInfo {
role: "app" | "agent" | "bridge" | string;
name?: string;
version?: string;
locale?: string;
timezone?: string;
tenantId?: string;
userRole?: string;
}
interface ExtensionOffer {
id: ExtensionId;
versions: Version[];
required?: boolean;
}
interface SessionInitializedPayload {
sessionId: SessionId;
selectedVersion: Version;
selectedProfiles?: string[];
selectedExtensions?: SelectedExtension[];
capabilityDelivery: "inline" | "deferred" | "none";
heartbeatMs?: number;
resumeToken?: string;
capabilities?: CapabilityDocument; // nur bei capabilityDelivery="inline"
metadata?: JsonObject;
}
interface SelectedExtension {
id: ExtensionId;
version: Version;
}
  • supportedVersions MUSS mindestens einen Eintrag enthalten.
  • Wenn ein Sender eine Erweiterung als required=true markiert und sie nicht ausgewählt wird, MUSS der Handshake fehlschlagen.
  • selectedVersion MUSS exakt einer vom Initiator angebotenen und vom Empfänger unterstützten Version entsprechen.
  • selectedProfiles DÜRFEN leer sein, wenn nur Core genutzt wird.
  • capabilities DARF nur enthalten sein, wenn capabilityDelivery = "inline".

Nimmt eine bestehende Session wieder auf.

interface SessionResumePayload {
sessionId: SessionId;
resumeToken: string;
metadata?: JsonObject;
}
interface SessionResumedPayload {
sessionId: SessionId;
selectedVersion: Version;
selectedProfiles?: string[];
selectedExtensions?: SelectedExtension[];
heartbeatMs?: number;
metadata?: JsonObject;
}
  • resumeToken ist opak und wird vom Session-Owner definiert.
  • Wenn resumeToken ungültig ist, MUSS error mit code="unknown_session" oder code="permission_denied" zurückgegeben werden.

Unterbricht eine aktive Session, ohne sie zu beenden.

interface SessionInterruptPayload {
reason?: string;
by?: "user" | "system" | "policy" | "transport" | string;
metadata?: JsonObject;
}
interface SessionInterruptedPayload {
status: "interrupted";
reason?: string;
}
  • Im Zustand INTERRUPTED DÜRFEN nur Session-Nachrichten verarbeitet werden, sofern kein Profil etwas anderes definiert.

Liveness-Prüfung.

interface SessionPingPayload {
nonce?: string;
}
interface SessionPongPayload {
nonce?: string;
}
  • Wenn heartbeatMs ausgehandelt wurde, SOLLTE jede Seite bei Leerlauf session.ping senden.
  • Bleibt eine Gegenantwort wiederholt aus, DARF die Session beendet werden.

Beendet die Session geordnet.

interface SessionTerminatePayload {
reason?: "normal" | "timeout" | "error" | "policy" | "disconnect" | string;
metadata?: JsonObject;
}
interface SessionTerminatedPayload {
status: "terminated";
reason?: string;
}
  • Nach session.terminated DÜRFEN keine weiteren nicht-terminierenden UIAP-Nachrichten mehr verarbeitet werden.

Fordert das Capability-Dokument an.

interface CapabilitiesGetPayload {
include?: Array<"roles" | "states" | "affordances" | "actions" | "risk" | "signals" | "all">;
}
interface CapabilitiesListPayload {
revision?: string;
capabilities: CapabilityDocument;
}
  • Wenn include fehlt, MUSS das vollständige Capability-Dokument geliefert werden.
  • revision KANN für Caching und Change Detection genutzt werden.

Meldet eine geänderte Capability-Lage.

interface CapabilitiesChangedPayload {
revision: string;
reason?: "app_update" | "role_change" | "feature_flag" | "configuration" | string;
capabilities: CapabilityDocument;
}
  • In v0.1 MUSS capabilities.changed immer ein vollständiges Ersatzdokument liefern, keine Patches.
  • Empfänger SOLLTEN das alte Dokument vollständig ersetzen.

Fehler werden als kind="error" gesendet.

  • type MUSS "error" sein.
  • correlationId MUSS auf die fehlgeschlagene Request zeigen.
interface UIAPErrorPayload {
code: ErrorCode;
message: string;
retryable?: boolean;
failedType?: string;
details?: JsonObject;
}
type ErrorCode =
| "bad_request"
| "invalid_message"
| "unknown_message_type"
| "unsupported_version"
| "unsupported_profile"
| "unsupported_extension"
| "unknown_session"
| "session_not_active"
| "permission_denied"
| "capability_unavailable"
| "timeout"
| "rate_limited"
| "state_conflict"
| "internal_error";
  • bad_request: formal gültige Envelope, aber semantisch unzulässiger Request
  • invalid_message: Envelope oder Payload strukturell ungültig
  • unknown_message_type: type unbekannt oder in aktuellem Zustand unzulässig
  • unsupported_version: keine kompatible Version verfügbar
  • unsupported_profile: angeforderte oder erforderliche Profile fehlen
  • unsupported_extension: erforderliche Extension fehlt oder falsche Version
  • unknown_session: Session nicht bekannt oder nicht wiederaufnehmbar
  • session_not_active: Nachricht in falschem Session-Zustand
  • permission_denied: Auth-/Policy-Verletzung
  • capability_unavailable: gewünschte Fähigkeit ist nicht deklariert oder nicht aktiv
  • timeout: Anfrage oder Ausführung lief in Timeout
  • rate_limited: Empfänger begrenzt den Traffic
  • state_conflict: Request passt nicht mehr zum aktuellen Zustand
  • internal_error: unerwarteter interner Fehler
  1. Auf eine request MUSS entweder response oder error folgen.
  2. Fehlercodes DÜRFEN durch Erweiterungen ergänzt werden.
  3. Vendor-spezifische Fehler SOLLTEN namespaced sein, z. B. x.vendor.foo_error.

UIAP Core v0.1 verwendet major.minor.

Beispiele:

  • 0.1
  • 0.2
  • 1.0
  • Der Initiator MUSS in session.initialize.supportedVersions alle unterstützten Versionen nennen.
  • Der Empfänger MUSS genau eine selectedVersion auswählen.
  • Nach erfolgreichem Handshake MUSS jede weitere Nachricht uiap = selectedVersion tragen.

Für v0.x gilt bewusst wenig Romantik:

  • Gleiche Major-Version bedeutet nicht automatisch volle Kompatibilität.
  • Kompatibilität entsteht erst durch explizite Aushandlung.
  • Nach Aushandlung gilt die ausgewählte Version als verbindlich.

Rein redaktionelle Änderungen SOLLTEN die Wire-Semantik nicht ändern. Wenn sich Wire-Semantik ändert, MUSS mindestens die Minor-Version steigen.


UIAP Core ist erweiterbar.

Erweiterungen werden in session.initialize.supportedExtensions angeboten und in session.initialized.selectedExtensions bestätigt.

ext transportiert Erweiterungsdaten.

Beispiel:

{
"ext": {
"uiap.policy": {
"decision": "confirm_required"
}
}
}
  1. ext-Einträge MÜSSEN nach Extension-ID gruppiert sein.
  2. Unverhandelte optionale Erweiterungen SOLLTEN ignoriert werden.
  3. Wenn eine Nachricht eine Erweiterung zwingend voraussetzt, MUSS sie diese in requires nennen.
  4. Trifft eine Nachricht mit unerfülltem requires ein, MUSS unsupported_extension oder unsupported_profile zurückgegeben werden.
  5. Erweiterungen DÜRFEN Core-Felder nicht umdefinieren.

Erweiterungen DÜRFEN neue type-Namespaces definieren, z. B.:

  • uiap.policy.evaluate
  • uiap.workflow.start
  • x.vendor.analytics.sample

Eine UIAP Core v0.1-konforme Implementierung MUSS:

  • Envelope validieren
  • session.initialize verarbeiten
  • session.terminate verarbeiten
  • session.ping / session.pong unterstützen
  • capabilities.get / capabilities.list unterstützen
  • Fehler im definierten Format senden
  • unbekannte optionale Felder ignorieren
  • Versionen, Profile und Extensions verhandeln können

{
"uiap": "0.1",
"kind": "request",
"type": "session.initialize",
"id": "msg_1",
"ts": "2026-03-26T13:00:00.000Z",
"source": { "role": "agent", "id": "agent-runtime" },
"payload": {
"supportedVersions": ["0.1"],
"supportedProfiles": ["[email protected]"],
"supportedExtensions": [
{ "id": "uiap.policy", "versions": ["0.1"], "required": false }
],
"capabilityDelivery": "deferred",
"peer": {
"role": "agent",
"name": "onboarding-agent",
"version": "0.1.0",
"locale": "de-CH",
"timezone": "Europe/Zurich"
}
}
}
{
"uiap": "0.1",
"kind": "response",
"type": "session.initialized",
"id": "msg_2",
"correlationId": "msg_1",
"sessionId": "sess_123",
"ts": "2026-03-26T13:00:00.040Z",
"source": { "role": "app", "id": "videoland-app" },
"payload": {
"sessionId": "sess_123",
"selectedVersion": "0.1",
"selectedProfiles": ["[email protected]"],
"selectedExtensions": [
{ "id": "uiap.policy", "version": "0.1" }
],
"capabilityDelivery": "deferred",
"heartbeatMs": 15000
}
}

  • [RFC2119] Key words for use in RFCs to Indicate Requirement Levels, BCP 14
  • Transport-Sicherheit ist nicht Teil von UIAP Core; die Absicherung des Transportkanals (z.B. TLS für WebSocket) MUSS vom Binding sichergestellt werden.
  • Session-Tokens (resumeToken) SOLLTEN kryptographisch zufällig und kurzlebig sein.
  • ext-Felder DÜRFEN NICHT dazu verwendet werden, Sicherheitsmechanismen zu umgehen.
  • Implementierungen SOLLTEN Rate-Limiting für eingehende Nachrichten unterstützen.
VersionDatumÄnderungen
0.12026-03-27Initialer Entwurf