How the Command Permission Model Interacts with Autonomy Tiers in OpenHuman

OpenHuman combines a command classification system with session-based autonomy tiers to enforce fine-grained access control, where every incoming command is classified into a CommandClass and then evaluated against the current Autonomy tier to produce an Allow, Prompt, or Block decision.

In the tinyhumansai/openhuman repository, the interaction between the command permission model and autonomy tiers forms the core security architecture that protects the host environment. This system classifies every RPC method and tool invocation into risk categories, then evaluates each operation against configurable trust levels to determine whether execution proceeds silently, requires user approval, or is forbidden entirely.

Command Classification and Autonomy Tiers Overview

The security model relies on two orthogonal concepts that work together at runtime: Command Classes that categorize operations by risk, and Autonomy Tiers that define session-wide trust boundaries.

CommandClass Categories

Every incoming command is classified into a specific CommandClass based on its potential impact on the system. The core implementation in src/openhuman/security/command.rs defines five distinct categories:

  • Read – Operations that only retrieve information without side effects
  • Write – Modifications to files or state (default fallback for unmatched commands)
  • Network – Outbound connections and remote resource fetching
  • Install – Package installation and tool deployment
  • Destructive – Irreversible operations like rm -rf or workspace deletion

Any command that does not match the classification patterns automatically falls back to Write, ensuring conservative permissions by default. Additionally, system-level directories such as credential stores are always blocked via the is_always_forbidden guard, regardless of classification.

Autonomy Tier Structure

The Autonomy struct, defined in src/openhuman/security/autonomy.rs, represents the session's trust level through a matrix of tiers and modifier flags:

  • Base tiers: readonly, supervised, full
  • Modifier flags: workspace_only, trusted_roots, allow_tool_install

These tiers are stored in the core context and consulted by the permission gate for every operation. A session running with full autonomy plus the allow_tool_install flag, for example, can execute network and installation commands without interruption, while supervised autonomy requires explicit confirmation for potentially dangerous operations.

The Three-Step Permission Flow

When the core receives an RPC call in src/core/dispatch.rs, the permission system evaluates the request through a deterministic three-step pipeline:

  1. Command Classification – The system calls classify_command on the incoming method name to determine its CommandClass
  2. Tier Retrieval – The current session's Autonomy value is fetched from the core context via ctx.autonomy()
  3. Gate Decision – The gate_decision(class, tier) function applies enforcement rules to return Allow, Prompt, or Block

The gate applies specific logic based on the command class and tier combination:

  • Read commands are generally permitted for all autonomy levels
  • Write commands require at least supervised autonomy; full autonomy permits writes without prompting
  • Network and Install operations need the allow_tool_install flag or full tier, otherwise prompting or blocking occurs
  • Destructive commands are blocked unless the tier is full and the session is explicitly marked as trusted

Core Implementation Details

The permission gate is implemented in the security module with clear separation between classification logic and tier management. The following Rust pseudo-code illustrates the decision flow used in src/openhuman/security/command.rs:

// Core logic from src/openhuman/security/command.rs
fn handle_incoming_rpc(method: &str, ctx: &CoreContext) -> Result<RpcOutcome, RpcError> {
    // 1️⃣ Classify the command
    let cmd_class = classify_command(method);

    // 2️⃣ Get the session's autonomy tier
    let tier = ctx.autonomy();

    // 3️⃣ Decide whether the command may proceed
    match gate_decision(cmd_class, tier) {
        Decision::Allow   => run_the_command(method, ctx),
        Decision::Prompt  => request_user_approval(method, ctx),
        Decision::Block   => Err(RpcError::Forbidden),
    }
}

The gate_decision function implements the matrix of rules that maps CommandClass and Autonomy combinations to enforcement actions. When the decision is Allow, execution proceeds immediately. A Prompt decision triggers a user approval request through the UI, while Block immediately returns an RpcError::Forbidden to the caller.

Frontend Integration

When the permission gate returns Decision::Prompt, the frontend renders an approval card that allows the user to approve or deny the specific operation. The React component handling this interaction communicates with the core RPC client to finalize the decision:

// Frontend approval prompt component
function ApprovalRequestCard({ request }: { request: ApprovalRequest }) {
  const { approve, deny } = useCoreRpcClient();

  return (
    <Card>
      <p>{request.description}</p>
      <Button onClick={() => approve(request.id)}>Allow</Button>
      <Button onClick={() => deny(request.id)}>Deny</Button>
    </Card>
  );
}

This separation ensures that the core security logic remains agnostic of the UI implementation while maintaining strict enforcement of the autonomy constraints.

Key Source Files

The permission system spans multiple files across the OpenHuman codebase:

  • src/openhuman/security/command.rs – Contains the classify_command function and gate_decision logic that implements the interaction between command classes and autonomy tiers
  • src/openhuman/security/autonomy.rs – Defines the Autonomy struct, tier constants (readonly, supervised, full), and helper methods for flag checking
  • src/core/dispatch.rs – Entry point where incoming RPC calls are routed through the permission gate before execution
  • AGENTS.md – High-level documentation describing the command permission model and autonomy tier configuration options

Summary

  • Command Classes categorize operations by risk level (Read, Write, Network, Install, Destructive), with Write serving as the conservative default for unknown commands
  • Autonomy Tiers define session trust levels through the Autonomy struct, supporting base tiers (readonly, supervised, full) combined with feature flags like allow_tool_install
  • The gate_decision function in src/openhuman/security/command.rs mediates between these systems, returning Allow, Prompt, or Block based on the command class and current tier
  • System-level protections like is_always_forbidden provide hard boundaries that override tier permissions for sensitive directories
  • The architecture separates core enforcement logic from UI concerns, with the frontend only rendering approval prompts when the core explicitly requests them via the Prompt decision

Frequently Asked Questions

What happens when a command doesn't match any known CommandClass?

When classify_command cannot match an incoming method name to a known pattern, it conservatively falls back to the Write classification. This ensures that unknown commands are treated as potentially state-modifying operations and must pass the stricter permission checks required for write access, rather than being implicitly allowed as read-only operations.

Which autonomy tier is required for network operations?

Network commands require either the full autonomy tier or the presence of the allow_tool_install flag on the session's Autonomy struct. If these conditions are not met, network operations will trigger a Prompt decision (requiring user approval) or be Blocked entirely, depending on the specific implementation of the gate rules in the security module.

How does OpenHuman handle destructive commands differently?

Destructive commands receive special treatment as the highest-risk category. Unlike Write operations that may be allowed under supervised autonomy, destructive commands are **Block**ed unless the session has full autonomy and is explicitly marked as trusted. This dual requirement prevents accidental data loss even in high-autonomy sessions that haven't been vetted for dangerous operations.

Where is the permission gate entry point in the codebase?

The permission gate is invoked in src/core/dispatch.rs, which serves as the central router for all incoming RPC calls. This file retrieves the command classification, fetches the current Autonomy tier from the CoreContext, and passes both values to gate_decision before determining whether to execute the command, request approval, or return a permission error.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →