How the Craft Agents Permission Mode System Works: A Technical Deep Dive

Craft Agents implements a three-level permission mode system that controls tool execution per-session, ranging from read-only exploration to full automatic execution, with modes stored in ModeManager and validated at runtime via mode-types.ts and mode-manager.ts.

Craft Agents, an open-source AI agent framework maintained by lukilabs/craft-agents-oss, employs a granular permission mode system to govern what actions autonomous sessions can perform. This system operates on a per-session basis rather than globally, ensuring that different agent instances can operate under distinct security constraints. Understanding how these permission modes are defined, stored, and enforced is essential for developers customizing agent behavior or building secure automation workflows.

The Three Permission Modes Defined

The permission mode system categorizes session capabilities into three distinct levels. Each mode uses an internal key for engine logic, a canonical name for programmatic interfaces, and a UI label for end-user display.

Safe Mode (Explore)

Safe mode (internal key: safe, canonical name: explore, UI label: Explore) operates as a read-only sandbox. All write-type tools are blocked, and the system validates every command against read-only allow-lists defined in PermissionsConfigSchema.allowedBashPatterns. No user prompts are generated; operations simply fail if they exceed read-only boundaries.

Ask Mode (Ask to Edit)

Ask mode (internal key: ask, canonical name: ask, UI label: Ask to Edit) serves as the default permission mode for new sessions. The system performs the same validation as safe mode, but when a dangerous operation would be blocked, it raises a permission request to the user rather than failing silently. This creates a manual approval gate for write operations while maintaining security by default.

Allow-All Mode (Execute)

Allow-all mode (internal key: allow-all, canonical name: execute, UI label: Auto) removes all restrictions. The validation step is completely bypassed, allowing all operations to execute without prompting. This mode is appropriate for trusted automation workflows but carries the highest security risk.

The canonical definitions reside in packages/shared/src/agent/mode-types.ts【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-types.ts#L16-L25】, with the internal-to-canonical mapping defined at lines 36-44【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-types.ts#L36-L44】.

Core Architecture and State Management

The permission mode system separates concerns between type definitions and stateful session management, ensuring type safety while maintaining flexible runtime control.

Per-Session State Isolation

Unlike global permission systems, Craft Agents stores permission modes per-session. The ModeManager singleton maintains a private Map<string, ModeState> keyed by session ID【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L30-L33】. Each ModeState object tracks the current permissionMode, a modeVersion counter for optimistic concurrency, and metadata about who last changed the mode.

This architecture ensures that switching modes in one chat session or automation context never affects other concurrent sessions.

Key State Management Methods

The mode-manager.ts file exposes several critical functions for lifecycle management:

  • initializeModeState(sessionId, initialMode?, callbacks?): Creates a new ModeState for a session, defaulting to ask unless overridden【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L94-L105】.
  • getPermissionMode(sessionId): Returns the current mode string for the specified session【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L84-L87】.
  • setPermissionMode(sessionId, mode, metadata?): Updates the state, increments modeVersion, records the changer, and notifies subscribers【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L71-L88】.
  • cyclePermissionMode(sessionId): Implements the SHIFT+TAB keyboard shortcut logic, rotating through the order defined in PERMISSION_MODE_ORDER【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L14-L21】【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L30-L36】.

The cycling order is explicitly defined in mode-types.ts at lines 31-34【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-types.ts#L31-L34】, ensuring predictable UI behavior.

Runtime Permission Validation and Tool Execution

The permission mode system directly influences how the agent validates and executes potentially dangerous tools such as Bash commands, PowerShell scripts, and file writes.

Mode-Based Execution Control

When a tool execution is requested, the permission manager inspects the session's current mode to determine validation behavior:

  • safe mode: All commands are validated against read-only allow-lists (PermissionsConfigSchema.allowedBashPatterns). Write operations are blocked immediately without user prompts.
  • ask mode: Performs the same validation as safe, but instead of blocking, it raises a permission request dialog when dangerous operations are detected, allowing manual user override.
  • allow-all mode: Validation is bypassed entirely; all commands execute without restriction or prompting.

Validation Implementation Details

The validation logic resides in getBashRejectionReason and getPowerShellRejectionReason within mode-manager.ts. These functions consult per-workspace permission configuration files to determine if a command pattern is allowed.

When a command is rejected, the system generates error messages that explicitly mention the required mode switch, guiding users to press SHIFT+TAB or programmatically change modes【/cache/repos/github.com/lukilabs/craft-agents-oss/main/packages/shared/src/agent/mode-manager.ts#L85-L92】.

For automation workflows, the permission mode can be preset in automation JSON files (e.g., "permissionMode": "allow-all") as documented in apps/electron/resources/docs/automations.md.

Programmatic Control of Permission Modes

Developers can interact with the permission system directly through the mode-manager module to build custom UIs, automation scripts, or monitoring tools.

import {
  getPermissionMode,
  setPermissionMode,
  cyclePermissionMode,
  PermissionMode,
} from '@craft-agent/shared/agent/mode-manager';

// Assume we have a session ID (e.g. from a chat window)
const sessionId = 'abc123';

// 1️⃣ Read the current mode
let current = getPermissionMode(sessionId);
console.log('Current mode:', current); // → 'ask' (default)

// 2️⃣ Change the mode programmatically
setPermissionMode(sessionId, 'safe', { changedBy: 'user' });
console.log('Now in safe mode:', getPermissionMode(sessionId)); // → 'safe'

// 3️⃣ Let the UI cycle the mode (same as SHIFT+TAB)
const next = cyclePermissionMode(sessionId);
console.log('Cycled to:', next); // → 'ask' (safe → ask)

// 4️⃣ Hook into changes (e.g. to update UI)
function onModeChange(state) {
  console.log('Mode changed to', state.permissionMode, 'by', state.lastChangedBy);
}
import { modeManager } from '@craft-agent/shared/agent/mode-manager';
modeManager.subscribe(sessionId, () => onModeChange(modeManager.getState(sessionId)));

This API surface allows fine-grained control over session security boundaries while maintaining the integrity of the permission mode system.

Summary

  • Craft Agents uses three distinct permission modes—safe/explore, ask, and allow-all/execute—to control session capabilities and tool execution
  • Modes are stored per-session in ModeManager, never globally, using ModeState objects keyed by session ID in a Map<string, ModeState>
  • The packages/shared/src/agent/mode-types.ts file defines internal keys, canonical names, UI labels, and the SHIFT+TAB cycling order
  • Runtime enforcement occurs through getBashRejectionReason and getPowerShellRejectionReason in mode-manager.ts, with validation bypassed only in allow-all mode
  • Developers can programmatically control modes via initializeModeState, getPermissionMode, setPermissionMode, and cyclePermissionMode

Frequently Asked Questions

What is the default permission mode in Craft Agents?

The default permission mode is ask (displayed as "Ask to Edit"), which requires user confirmation before executing potentially dangerous operations. When initializeModeState is called for a new session in packages/shared/src/agent/mode-manager.ts, it creates a state defaulting to ask unless overridden by automation configurations or explicit parameters.

How do I change the permission mode programmatically?

Import setPermissionMode from @craft-agent/shared/agent/mode-manager and invoke it with the session ID and desired mode identifier (safe, ask, or allow-all). You can also use cyclePermissionMode to rotate through modes in the sequence defined by PERMISSION_MODE_ORDER in mode-types.ts, which mirrors the SHIFT+TAB keyboard shortcut behavior.

Are permission modes global or per-session?

Permission modes are strictly per-session. The ModeManager singleton maintains a private Map<string, ModeState> where each session ID maps to its own isolated state object. This architecture ensures that changing modes in one chat window or automation context never affects other concurrent sessions, providing granular security boundaries.

How does safe mode restrict tool execution?

In safe mode (labeled "Explore"), the system validates all commands against read-only allow-lists such as PermissionsConfigSchema.allowedBashPatterns before execution. The getBashRejectionReason and getPowerShellRejectionReason functions in mode-manager.ts enforce these restrictions, blocking any write-type operations immediately without prompting the user, effectively creating a sandboxed read-only environment.

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 →