Quickstart
Overview
Section titled “Overview”This quickstart walks through building a Tier 1 native application that exposes PCP capabilities to the System Intelligence layer. You’ll create a simple text editor that registers two capabilities: editor.read to retrieve the current document content, and editor.save to write new content back. By the end, the compositor will discover your app at startup, index its capabilities, and make them available to SI for invocation.
Prerequisites:
- A working Rust toolchain (edition 2021)
- A PCP-compatible compositor (one that embeds PCP Core)
- The
pcp_corecrate, available from the PCP registry
No external daemon, no socket setup, no IPC configuration. The compositor handles discovery and registration automatically.
Define Capabilities
Section titled “Define Capabilities”A Tier 1 application implements the CapabilityHandler trait from pcp_core. This trait tells the compositor what your app can do and how to invoke each operation. The handler receives capability IDs, parameter payloads, and an invocation context, then returns structured results or typed errors.
Here is the full handler for the text editor:
use pcp_core::prelude::*;
/// A Tier 1 text editor exposing semantic capabilities.pub struct EditorCapabilityHandler { manifest: CapabilityManifest,}
#[async_trait]impl CapabilityHandler for EditorCapabilityHandler { fn manifest(&self) -> &CapabilityManifest { &self.manifest }
async fn invoke( &self, capability_id: &str, params: serde_json::Value, context: &InvocationContext, ) -> Result<CapabilityResult, CapabilityError> { match capability_id { "editor.read" => { let text = self.get_document_text()?; Ok(CapabilityResult { success: true, data: Some(serde_json::json!({ "text": text })), state_after: None, undo_token: None, }) } "editor.save" => { let content = params["text"] .as_str() .ok_or(CapabilityError::InvalidParams( "missing 'text'".into(), ))?; self.save_document(content).await?; Ok(CapabilityResult::success()) } _ => Err(CapabilityError::NotFound(capability_id.into())), } }}A few things to note about this implementation:
- The
invokemethod dispatches on the capability ID string. The compositor routes each invocation to this method with the ID, a JSON parameter payload, and anInvocationContextthat carries session and auth metadata. editor.readis a read-only operation. It returns the document text in thedatafield and setssuccesstotrue. No undo token is needed because the operation has no side effects.editor.saveextracts thetextparameter from the payload, validates its presence, and persists it. TheInvalidParamserror variant makes missing or malformed inputs explicit to the caller.- Any unrecognized capability ID returns
CapabilityError::NotFound. The compositor uses this to distinguish between an unsupported operation and a transient failure.
The get_document_text and save_document methods are application-level logic. They are not part of the PCP contract. The protocol only cares about the trait boundary: what goes in, what comes out, and whether it succeeded.
Ship the Manifest
Section titled “Ship the Manifest”Every Tier 1 application ships a CapabilityManifest alongside its binary. This JSON document declares the app’s identity, version, and the full set of capabilities it exposes. The compositor reads this manifest during discovery to build its capability index without needing to instantiate the application.
Here is the manifest for the text editor:
{ "app_id": "com.example.editor", "version": "1.0.0", "manifest_version": "1.0", "capabilities": [ { "id": "editor.read", "name": "Read Document", "description": "Read the current document content", "category": "TEXT_QUERY", "parameters": { "type": "object", "properties": {} }, "returns": { "type": "object", "properties": { "text": { "type": "string" } } }, "side_effects": "read", "auth_level": "user" }, { "id": "editor.save", "name": "Save Document", "description": "Save the document with provided content", "category": "TEXT_INPUT", "parameters": { "type": "object", "properties": { "text": { "type": "string", "description": "New document content" } }, "required": ["text"] }, "returns": { "type": "object", "properties": {} }, "side_effects": "write", "auth_level": "user" } ]}The manifest must be Ed25519-signed. The signature proves that the capabilities declared in the JSON actually belong to the app that ships it. Without a valid signature, the compositor rejects the manifest at registration. Place the signed manifest at the well-known path for your app (conventionally alongside the binary or in a platform-standard manifest directory).
Each capability entry declares its side_effects level (read or write), its auth_level (who is allowed to invoke it), and its parameter and return schemas. The compositor uses these schemas to validate invocations before they reach your handler. A call to editor.save that omits the text parameter never touches your code; the compositor catches the schema violation and returns an error to the caller.
Register with the Compositor
Section titled “Register with the Compositor”Tier 1 applications do not register themselves manually. The compositor scans well-known paths for signed manifests at startup. When it finds your com.example.editor manifest, it:
- Verifies the Ed25519 signature against the trusted key set.
- Parses the manifest and indexes each capability by ID, category, and app origin.
- Associates the manifest with your application’s capability handler.
From that point on, SI can invoke editor.read or editor.save by capability ID, and the compositor routes the call to your handler in-process. No handshake, no socket, no registration RPC. Discovery and binding happen as a consequence of the manifest existing in the right place with a valid signature.
If the manifest is malformed, the signature fails, or a capability ID collides with an already-registered capability, the compositor logs the failure and skips that application. It does not crash, and it does not block other applications from registering.
Key Constraints
Section titled “Key Constraints”Tier 1 integration comes with rules that distinguish it from lower tiers:
- Operate on the data model, not the rendered surface. Tier 1 capabilities read and write application state directly. They do not query pixel positions, simulate mouse clicks, or read the framebuffer. The point of Tier 1 is semantic access: the compositor invokes operations on your data structures, not your UI layer.
- No script injection. The
evaluate_scriptmechanism is forbidden in Tier 1 handlers. If you need to execute logic, do it in Rust, not by injecting JavaScript or shell commands into the application’s rendering context. - Manifest integrity is enforced. The compositor verifies the Ed25519 signature on every manifest at registration time. A tampered manifest is rejected. A missing signature is rejected. There is no downgrade path to an unsigned manifest.
- Capability IDs are global. Once registered, a capability like
editor.readis addressable by any part of the SI layer. Choose IDs that are specific enough to avoid collisions across applications. Theapp_idnamespace in the manifest helps, but capability IDs themselves must be unique within the compositor’s index.