Capability Registry
Overview
Section titled “Overview”The Capability Registry is the daemon’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.
Registry Structure
Section titled “Registry Structure”The registry is implemented as an InMemoryRegistry backed by a dashmap for concurrent access. It stores capability and app descriptors and exposes a straightforward query API.
pub struct InMemoryRegistry { /* dashmap-backed */ }
impl InMemoryRegistry { pub fn register(&self, descriptor: CapabilityDescriptor); pub fn unregister(&self, id: CapabilityId); pub fn get(&self, id: CapabilityId) -> Option<CapabilityDescriptor>; pub fn query(&self, filter) -> Vec<CapabilityDescriptor>; pub fn register_app(&self, descriptor: AppDescriptor); pub fn apps(&self) -> Vec<AppDescriptor>; pub fn get_app(&self, app_id: &str) -> Option<AppDescriptor>; pub fn capability_count(&self) -> usize; pub fn app_count(&self) -> usize;}Every mutation (register, unregister, register_app) emits a CapabilityChange event and a capability_diff snapshot. Subscribers receive the diff and can update their local state without polling. The dashmap backing allows concurrent reads and writes without a global lock, which keeps query latency in the microsecond range even under load.
Registration Flow
Section titled “Registration Flow”When an application launches, the daemon 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: Accessibility| D["Run accessibility adapters<br/>AT-SPI2 tree walk or Wine MSAA/UIA bridge"]
B -->|Tier 3: Simulation| F["Register window-management<br/>fallback capabilities"]
C --> G["Register capabilities"]
D --> G
F --> G
G --> H["Emit app.started +<br/>capability.registered"]
Tier 1 apps ship a signed capability manifest. The daemon 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 daemon 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.
Windows applications running under Wine are bridged the same way: the Wine adapter translates MSAA and UIA accessibility data into the same capability format as the AT-SPI2 adapter.
Tier 3 is the universal fallback for applications that expose neither a manifest nor accessibility data. They receive window-management capabilities driven by input simulation. The compositor’s own operations — window management, workspace switching, output configuration — are exposed separately through the compositor capability domain and registered at daemon startup.
Adapter State Machine
Section titled “Adapter State Machine”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.
Dynamic Capability Changes
Section titled “Dynamic Capability Changes”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.
Capability Versioning
Section titled “Capability Versioning”When a Tier 1 application updates to a new version with a changed manifest, the daemon 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.
Hot-Reload Policy
Section titled “Hot-Reload Policy”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.
Capability Narrative
Section titled “Capability Narrative”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.