Skip to content

UIAP Core

FieldValue
StatusDraft
Version0.1
Date2026-03-27
Dependencies
EditorsPatrick

UIAP (UI Control Protocol) is a transport-agnostic protocol between a running application and an AI agent or agent bridge layer.

UIAP Core v0.1 defines:

  • the common message envelope
  • session establishment and session lifecycle
  • error format and error codes
  • version negotiation
  • extension mechanism
  • high-level capability discovery

UIAP Core v0.1 does not define:

  • concrete web/DOM/ARIA semantics
  • concrete browser control
  • detailed policy or security rules
  • workflow/skill definitions
  • visual presentation such as ghost cursors or highlights

These concerns belong in profiles or extensions.

The key words MUST, MUST NOT, SHOULD, MAY in this document are to be interpreted as described in RFC 2119 and BCP 14, when and only when they appear in ALL CAPS.

  1. UIAP is transport-agnostic. It MAY be carried over WebSocket, WebRTC DataChannel, postMessage, local IPC, or other bindings.
  2. UIAP messages are UTF-8 encoded JSON objects.
  3. Every UIAP message MUST use a common Envelope.
  4. UIAP Core is strict-by-envelope, flexible-by-payload: mandatory fields MUST be validated; unknown optional fields SHOULD be ignored.
  5. UIAP separates protocol contract from execution mechanics.

type Version = string; // Format: "major.minor", e.g. "0.1"
type Timestamp = string; // ISO-8601 UTC, e.g. "2026-03-26T13:12:09.123Z"
type MessageId = string; // 1..128 characters, unique within a session
type SessionId = string; // 1..128 characters, assigned by the session owner
type ExtensionId = string; // e.g. "uiap.policy" or "x.vendor.foo"
type MessageType = string; // e.g. "session.initialize"
type JsonObject = Record<string, unknown>;
  • MessageId MUST be unique within a session.
  • SessionId MUST be unique per active session.
  • Vendor-specific identifiers SHOULD be stable and human-readable.
  • Extension names SHOULD be lowercase and dot-segmented, e.g. uiap.policy or x.acme.billing.

Every message MUST use this envelope.

interface UIAPEnvelope {
uiap: Version; // protocol version of this message
kind: "request" | "response" | "event" | "error";
type: MessageType; // e.g. "session.initialize"
id: MessageId;
sessionId?: SessionId; // optional before session init
correlationId?: MessageId; // mandatory for response/error
ts: Timestamp;
source: EndpointRef;
target?: EndpointRef;
seq?: number; // optional, for unordered transports
requires?: string[]; // required profiles/extensions
payload: JsonObject; // NEVER null, ALWAYS an object
ext?: Record<ExtensionId, unknown>; // optional extension data
}
interface EndpointRef {
role: "app" | "agent" | "bridge" | "observer" | string;
id: string;
instanceId?: string;
}
  1. uiap, kind, type, id, ts, source, payload MUST be present.
  2. payload MUST be a JSON object, even if empty.
  3. correlationId MUST be set for response and error.
  4. sessionId MAY be absent for session.initialize; after that it SHOULD always be present.
  5. ext MUST only contain extension data. Core fields MUST NOT be overridden via ext.
  6. Unknown optional fields SHOULD be ignored.
  7. Senders SHOULD omit optional fields rather than sending null, unless null carries its own semantics.
  • request: expects exactly one response or error
  • response: successful reply to a request
  • event: unidirectional state or status notification, no mandatory reply
  • error: failed processing of a request

Core reserves:

  • session.*
  • capabilities.*
  • error

Extensions MUST use their own namespace, e.g.:

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

A UIAP session has exactly one of these states:

  • NEW
  • INITIALIZING
  • ACTIVE
  • INTERRUPTED
  • TERMINATING
  • TERMINATED
  • NEW -> INITIALIZING via session.initialize
  • INITIALIZING -> ACTIVE via session.initialized
  • ACTIVE -> INTERRUPTED via session.interrupt / session.interrupted
  • INTERRUPTED -> ACTIVE via session.resume / session.resumed
  • ACTIVE|INTERRUPTED -> TERMINATING via session.terminate
  • TERMINATING -> TERMINATED via session.terminated
  • Any state MAY transition directly to TERMINATED on a fatal transport error
  1. A session MUST be started with session.initialize.
  2. The remote peer MUST reply with session.initialized or error.
  3. Before a successful handshake, no non-session-core messages MUST be processed.
  4. Capability information MAY be delivered inline during the handshake or deferred.

Starts a session and negotiates versions, profiles, and extensions.

interface SessionInitializePayload {
supportedVersions: Version[]; // non-empty
supportedProfiles?: string[]; // e.g. ["[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; // only when capabilityDelivery="inline"
metadata?: JsonObject;
}
interface SelectedExtension {
id: ExtensionId;
version: Version;
}
  • supportedVersions MUST contain at least one entry.
  • If a sender marks an extension as required=true and it is not selected, the handshake MUST fail.
  • selectedVersion MUST correspond to exactly one version offered by the initiator and supported by the receiver.
  • selectedProfiles MAY be empty if only Core is used.
  • capabilities MUST only be present when capabilityDelivery = "inline".

Resumes an existing session.

interface SessionResumePayload {
sessionId: SessionId;
resumeToken: string;
metadata?: JsonObject;
}
interface SessionResumedPayload {
sessionId: SessionId;
selectedVersion: Version;
selectedProfiles?: string[];
selectedExtensions?: SelectedExtension[];
heartbeatMs?: number;
metadata?: JsonObject;
}
  • resumeToken is opaque and defined by the session owner.
  • If resumeToken is invalid, an error with code="unknown_session" or code="permission_denied" MUST be returned.

Interrupts an active session without terminating it.

interface SessionInterruptPayload {
reason?: string;
by?: "user" | "system" | "policy" | "transport" | string;
metadata?: JsonObject;
}
interface SessionInterruptedPayload {
status: "interrupted";
reason?: string;
}
  • In the INTERRUPTED state, only session messages MUST be processed, unless a profile defines otherwise.

Liveness check.

interface SessionPingPayload {
nonce?: string;
}
interface SessionPongPayload {
nonce?: string;
}
  • If heartbeatMs was negotiated, each side SHOULD send session.ping during idle periods.
  • If repeated responses are missing, the session MAY be terminated.

Terminates the session gracefully.

interface SessionTerminatePayload {
reason?: "normal" | "timeout" | "error" | "policy" | "disconnect" | string;
metadata?: JsonObject;
}
interface SessionTerminatedPayload {
status: "terminated";
reason?: string;
}
  • After session.terminated, no further non-terminating UIAP messages MUST be processed.

Requests the capability document.

interface CapabilitiesGetPayload {
include?: Array<"roles" | "states" | "affordances" | "actions" | "risk" | "signals" | "all">;
}
interface CapabilitiesListPayload {
revision?: string;
capabilities: CapabilityDocument;
}
  • If include is absent, the full capability document MUST be delivered.
  • revision MAY be used for caching and change detection.

Announces a change in the capability landscape.

interface CapabilitiesChangedPayload {
revision: string;
reason?: "app_update" | "role_change" | "feature_flag" | "configuration" | string;
capabilities: CapabilityDocument;
}
  • In v0.1 capabilities.changed MUST always deliver a full replacement document, not patches.
  • Receivers SHOULD fully replace the previous document.

Errors are sent as kind="error".

  • type MUST be "error".
  • correlationId MUST point to the failed request.
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: structurally valid envelope, but semantically invalid request
  • invalid_message: envelope or payload is structurally invalid
  • unknown_message_type: type is unknown or not permitted in the current state
  • unsupported_version: no compatible version available
  • unsupported_profile: requested or required profiles are missing
  • unsupported_extension: required extension is missing or has an incompatible version
  • unknown_session: session is not known or not resumable
  • session_not_active: message received in wrong session state
  • permission_denied: auth/policy violation
  • capability_unavailable: requested capability is not declared or not active
  • timeout: request or execution timed out
  • rate_limited: receiver is throttling traffic
  • state_conflict: request no longer matches the current state
  • internal_error: unexpected internal error
  1. Every request MUST be followed by either a response or an error.
  2. Error codes MAY be extended by extensions.
  3. Vendor-specific errors SHOULD be namespaced, e.g. x.vendor.foo_error.

UIAP Core v0.1 uses major.minor.

Examples:

  • 0.1
  • 0.2
  • 1.0
  • The initiator MUST list all supported versions in session.initialize.supportedVersions.
  • The receiver MUST select exactly one selectedVersion.
  • After a successful handshake, every subsequent message MUST carry uiap = selectedVersion.

For v0.x, compatibility is intentionally strict:

  • Same major version does not automatically imply full compatibility.
  • Compatibility is established only through explicit negotiation.
  • After negotiation, the selected version is binding.

Purely editorial changes SHOULD NOT alter wire semantics. If wire semantics change, the minor version MUST be incremented at minimum.


UIAP Core is extensible.

Extensions are offered in session.initialize.supportedExtensions and confirmed in session.initialized.selectedExtensions.

ext carries extension data.

Example:

{
"ext": {
"uiap.policy": {
"decision": "confirm_required"
}
}
}
  1. ext entries MUST be grouped by extension ID.
  2. Unnegotiated optional extensions SHOULD be ignored.
  3. If a message mandatorily requires an extension, it MUST list it in requires.
  4. If a message arrives with an unfulfilled requires, unsupported_extension or unsupported_profile MUST be returned.
  5. Extensions MUST NOT redefine core fields.

Extensions MAY define new type namespaces, e.g.:

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

A UIAP Core v0.1-conformant implementation MUST:

  • validate the envelope
  • process session.initialize
  • process session.terminate
  • support session.ping / session.pong
  • support capabilities.get / capabilities.list
  • send errors in the defined format
  • ignore unknown optional fields
  • be able to negotiate versions, profiles, and extensions

{
"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 security is not part of UIAP Core; securing the transport channel (e.g. TLS for WebSocket) MUST be ensured by the binding.
  • Session tokens (resumeToken) SHOULD be cryptographically random and short-lived.
  • ext fields MUST NOT be used to circumvent security mechanisms.
  • Implementations SHOULD support rate limiting for incoming messages.
VersionDateChanges
0.12026-03-27Initial draft