Skip to content
Portal Control Protocol

Capability Registry

The Capability Registry is the compositor’s live database of every capability exposed by every running application and system component. When SI wants to know what it can do right now, it queries the registry. When an application launches, its capabilities are registered. When it closes, they are removed. The registry is always in sync with the current desktop state.

This is not a static configuration file or a build-time manifest. The registry reflects reality: what is actually running, what capabilities are actually available, and what state those capabilities are in. Every change emits an event that downstream systems can consume.

Each entry in the registry binds a capability to an application and a tier. An entry carries the application ID, the capability identifier (such as mail.compose or window.close), the intent category, the capability tier, and any metadata the adapter provides (element IDs, action patterns, parameter schemas).

impl CapabilityRegistry {
/// All capabilities for a specific application.
pub fn query_app_capabilities(&self, app_id: &AppId) -> Vec<&CapabilityEntry>;
/// All capabilities matching an action pattern, across all apps.
pub fn query_by_action(&self, action_pattern: &str) -> Vec<(&AppId, &CapabilityEntry)>;
/// All capabilities in a given intent category.
pub fn query_by_category(&self, category: &IntentCategory) -> Vec<(&AppId, &CapabilityEntry)>;
/// The full registry dump.
pub fn list_all(&self) -> HashMap<AppId, Vec<CapabilityEntry>>;
}

When a Wayland client connects, the compositor determines which tier it belongs to and registers its capabilities through the appropriate path:

graph TD
    A["App Launches"] --> B{"Tier?"}
    B -->|Tier 1: Native| C["Load + verify manifest"]
    B -->|Tier 2: AT-SPI2| D["Validate static prediction<br/>Run adapter enrichment"]
    B -->|Tier 2b: Wine| E["Initialize Wine bridge<br/>Enumerate MSAA/UIA"]
    B -->|Tier 3: Compositor| F["Register compositor-only<br/>capabilities"]
    C --> G["Register capabilities"]
    D --> G
    E --> G
    F --> G
    G --> H["Emit app.started +<br/>capability.registered"]

Tier 1 apps ship a signed capability manifest. The compositor loads the manifest, verifies the Ed25519 signature, and registers each declared capability directly. This is the fastest registration path because no runtime discovery is needed.

Tier 2 apps do not ship manifests. The compositor runs the appropriate adapter, which probes the application’s accessibility tree, pattern-matches UI elements against known capability templates, and enriches the discovered capabilities with element IDs and action patterns.

Tier 2b handles Windows applications running under Wine. The Wine bridge translates MSAA and UIA accessibility data into the same capability format as Tier 2.

Tier 3 covers capabilities that live entirely within the compositor: window management, workspace switching, output configuration. These are registered at compositor startup and never change.

Each adapter instance follows a defined lifecycle:

stateDiagram-v2
    [*] --> Idle
    Idle --> Detecting: App launches
    Detecting --> Matched: Capabilities found
    Detecting --> Idle: App closed during detection
    Matched --> Active: Registration complete
    Matched --> Idle: No capabilities found
    Active --> Stale: App state changed
    Stale --> ReDetecting: Re-probe triggered
    ReDetecting --> Active: Capabilities updated
    ReDetecting --> Idle: Capabilities lost
    Active --> Idle: App closed

Idle. The adapter has no target application. It waits for an app launch event.

Detecting. The adapter is probing the application’s UI, running pattern matching, and building a capability map. This is the most expensive state in the lifecycle because it involves accessibility tree traversal and D-Bus roundtrips.

Matched. The adapter found capabilities but registration has not yet completed. This is a brief transitional state.

Active. The adapter’s capabilities are registered and live in the registry. The adapter now listens for state changes and capability diffs.

Stale. Something changed: a dialog opened, a document loaded, a network state shifted. The capabilities the adapter previously registered may no longer be accurate. The adapter transitions to re-detection rather than serving stale data.

Re-detecting. The adapter re-probes the application, computes a diff against the previously registered capabilities, and updates the registry. If capabilities were added, it emits capability.registered. If capabilities were removed, it emits capability.unregistered. The adapter then returns to Active.

Capabilities are not fixed for the lifetime of an application. A document editor gains new capabilities when a document is opened (formatting actions become relevant). A dialog box introduces temporary capabilities while it is visible. A network disconnect can remove capabilities that depend on remote services.

The registry tracks these changes through capability.diff events. Each diff carries a list of added, removed, and modified capabilities. Subscribers (SI’s context manager, the event bus, the learning runtime) consume these diffs to keep their own state in sync.

When a Tier 1 application updates to a new version with a changed manifest, the compositor does not hot-reload capabilities mid-session. The updated manifest takes effect at the next application launch. This is a deliberate choice: hot-reloading capabilities during an active session would create race conditions with in-flight intents and could change the permission landscape underneath an active confirmation prompt.

In practice, the only way capabilities change mid-session is through dynamic diffs (new dialogs, new documents, state changes). The static manifest is treated as immutable for the session lifetime. This simplifies reasoning about capability stability and eliminates an entire class of concurrency bugs.

The registry also supports generating a human-readable summary of current capabilities. This narrative is what SI uses when a user asks “what can you do right now?” It aggregates capabilities by domain rather than by application, counts apps per domain, highlights recent changes (such as a newly installed application), and omits capabilities the user never interacts with.

The narrative updates dynamically as applications launch and close. It is not a cached string but a live composition generated on demand from the current registry state.

Last updated: