How to Manage Users and Permissions in PI Desktop: A Complete Guide to RACP Roles
PI Desktop implements a fine-grained permission model based on the Remote Agent Control Protocol (RACP), using four distinct roles—viewer, controller, approver, and owner—to authorize every operation through a centralized Principal object.
All user authorization in PI Desktop flows through the Remote Agent Control Protocol (RACP), ensuring consistent security policy enforcement across the UI, plugins, and remote clients. The architecture centralizes role definitions and permission checks in packages/shared/src/racp.ts, while the host implementation in packages/agent-host/src/agent-host.ts validates every request against the caller's Principal object. Understanding how to create Principals and check roles programmatically is essential for extending the application securely.
Understanding the RACP Permission Model
The permission system revolves around the Principal type, which carries a unique subject identifier and an array of RACP roles. When the host receives any request, it extracts the caller's Principal and validates it against the required role for the requested operation.
The Four RACP Roles
RACP defines four hierarchical roles in packages/shared/src/racp.ts at line 36:
export const RACP_ROLES = ["viewer", "controller", "approver", "owner"] as const;
Each role grants specific capabilities:
- viewer – Can read most data but cannot modify state or issue commands.
- controller – May issue commands affecting a session, such as starting or stopping turns.
- approver – Can grant or deny approval requests within the system.
- owner – Has full-privilege access and implicitly includes all other roles.
Role Hierarchy and Ranking
The system calculates permission precedence using a numeric ranking defined at line 572 in packages/shared/src/racp.ts:
const ROLE_RANK: Record<RacpRole, number> = { viewer: 0, controller: 1, approver: 2, owner: 3 };
This hierarchy ensures that higher-ranked roles automatically satisfy requirements for lower-ranked operations. The owner role bypasses all permission checks entirely, making it suitable for the local desktop user who requires unrestricted access.
How Permission Checks Work in the Code
All authorization logic funnels through the rolesAllowOperation function, which evaluates whether a set of roles satisfies the requirements for a specific RACP operation.
The rolesAllowOperation Function
Located at line 583 in packages/shared/src/racp.ts, this function implements the core permission logic:
export function rolesAllowOperation(roles: readonly RacpRole[], operation: RacpOperation): boolean {
const required = RACP_OPERATIONS[operation].role;
if (roles.includes("owner")) return true; // owner can do everything
if (required === "viewer") return roles.length > 0;
return roles.includes(required);
}
The function returns true immediately if the role set includes "owner". For other roles, it checks whether the provided roles array contains the specific role required for the operation. This helper is imported and used throughout the codebase to maintain a single source of truth for authorization logic.
Host-Side Enforcement
The agent host enforces permissions before delegating to the runtime. In packages/agent-host/src/agent-host.ts at line 359, the implementation uses a guard pattern to validate the Principal:
if (!principal.roles.includes(role) && !principal.roles.includes("owner")) {
throw new RACPError("FORBIDDEN", "Caller lacks required role");
}
This check ensures that even if a caller attempts to bypass UI-level restrictions, the backend will reject unauthorized operations. All UI-level permission enforcement delegates to these same backend checks, eliminating security disparities between interface layers.
Implementing User Management
To manage users programmatically, create a Principal object and pass it to host API functions. The host will re-validate the Principal internally before executing any sensitive operations.
Creating a Principal
Define the current user with appropriate roles in your application code:
import type { Principal } from "./agent-host/types";
const me: Principal = {
subject: "desktop", // unique identifier for this host
roles: ["owner"], // give full access (typical for the local desktop)
pairedDevice: true,
};
Checking Permissions Client-Side
Use the shared RACP utility to verify capabilities before attempting restricted operations:
import { rolesAllowOperation } from "packages/shared/src/racp";
import type { RacpOperation } from "packages/shared/src/racp";
const canStartTurn = rolesAllowOperation(me.roles, "turn/start");
if (!canStartTurn) {
console.error("User lacks permission to start a turn");
}
Executing Restricted Operations
Pass the validated Principal to host API methods, which perform secondary authorization:
import { startTurn } from "packages/agent-host/src/agent-host";
await startTurn(me, {
sessionId: "sess_123",
input: { text: "Explain the permission model" },
context: { requestId: "req_001" },
});
Key Files in the Permission Architecture
Understanding these source files is essential for modifying or extending the security model:
packages/shared/src/racp.ts– Defines the RACP contract, role enumeration (RACP_ROLES), role ranking (ROLE_RANK), and therolesAllowOperationhelper used universally for permission checks.packages/agent-host/src/agent-host.ts– Implements the host-side API, extracts the caller'sPrincipal, and enforces role-based guards before delegating to the runtime.packages/agent-host/src/agent-host.test.ts– Contains test suites demonstrating permission scenarios, including owner versus non-owner access patterns.packages/shared/src/types.ts– Houses thePrincipaltype definition and related UI message role definitions.packages/agent-runtime/src/subagent.ts– Validates inbound messages for role (userversusassistant) before processing by the sub-agent runtime.
Summary
- PI Desktop uses RACP roles (viewer, controller, approver, owner) defined in
packages/shared/src/racp.tsto control access to all operations. - The
rolesAllowOperationfunction at line 583 provides the canonical permission check, automatically granting all rights to owners. - Principals carry authorization context through the
subjectidentifier androlesarray, validated by the host inpackages/agent-host/src/agent-host.ts. - Backend guards enforce security regardless of UI state, ensuring consistent policy application across plugins and remote clients.
Frequently Asked Questions
What is the difference between controller and approver roles in PI Desktop?
The controller role allows users to issue commands that affect session state, such as starting or stopping turns, while the approver role specifically grants the ability to approve or deny requests within the workflow. According to the role ranking in packages/shared/src/racp.ts, approver (rank 2) outranks controller (rank 1), meaning approvers can perform controller actions only if they also hold the controller role or the owner role.
How does PI Desktop handle permission checks for the owner role?
The owner role receives special treatment in the rolesAllowOperation function at line 583 of packages/shared/src/racp.ts: if the roles array includes "owner", the function returns true immediately without checking specific operation requirements. This hardcoded exception ensures owners bypass all permission gates, making the role suitable for local desktop administrators who require unrestricted system access.
Can UI components bypass the backend permission checks in PI Desktop?
No. While UI components can perform client-side permission checks using rolesAllowOperation to improve user experience, the host implementation in packages/agent-host/src/agent-host.ts re-validates every request using the Principal object. The guard at line 359 explicitly throws a RACPError with code "FORBIDDEN" if the caller lacks the required role, ensuring backend enforcement remains the definitive authority for all security decisions.
What files should developers modify to add new permission roles to PI Desktop?
To extend the permission system, developers must update the RACP_ROLES array at line 36 of packages/shared/src/racp.ts and adjust the ROLE_RANK mapping at line 572 to include the new role's hierarchy position. Additionally, the RACP_OPERATIONS definition must be updated to specify which new role is required for specific operations, and the host guards in packages/agent-host/src/agent-host.ts may need modification to handle role-specific logic beyond the standard rolesAllowOperation checks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →