# How Craft Agents Permission Modes Work: Safe (Explore), Ask (Ask to Edit), and Allow-All (Execute)

> Understand Craft Agents permission modes: Safe (Explore), Ask (Ask to Edit), and Allow-All (Execute). Control tool execution and ensure security. Learn how to manage agent permissions effectively.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Craft Agents uses three permission modes—safe (Explore), ask (Ask to Edit), and allow-all (Execute)—to control whether tools run automatically, require user approval, or are blocked entirely, with each session maintaining its own isolated ModeState.**

The `craft-ai-agents/craft-agents-oss` repository implements a per-session permission system that governs how AI agents interact with filesystem and shell tools. Every conversation runs inside a **session** that owns a dedicated `ModeState` object, determining whether write operations proceed silently, prompt for confirmation, or are denied completely.

## The Three Permission Modes Explained

The runtime stores three internal keys (`safe`, `ask`, `allow-all`) defined in [`packages/shared/src/agent/mode-types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-types.ts), while the UI and configuration files use human-readable canonical names.

### Safe Mode (Explore)

**Safe mode** provides a read-only sandbox where all write-type tools—including `Write`, `Edit`, `MultiEdit`, and `NotebookEdit`—are blocked. Bash commands are filtered against an allow-list defined in `~/.craft-agent/permissions/default.json`. The function `getBashRejectionReason` in [`packages/shared/src/agent/mode-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-manager.ts) analyzes each command and returns either `null` (allowed) or a detailed `BashRejectionReason` explaining the block. No user prompts are shown; rejected commands simply fail with a message directing the user to switch modes.

### Ask Mode (Ask to Edit)

**Ask mode** is the default interactive setting. When the agent attempts a dangerous operation blocked by safe-mode rules, the system creates a `PermissionRequest` event (`type: 'permission_request'` in the RPC protocol) and pauses execution until the user responds. The chosen mode is then stored back via `setPermissionMode`. This mode balances automation with safety by requiring explicit confirmation for writes while allowing reads to proceed unchecked.

### Allow-All Mode (Execute)

**Allow-all mode** skips all permission checks, permitting every tool to run immediately. According to the source code in [`packages/shared/src/agent/mode-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-manager.ts), the manager bypasses `getBashRejectionReason` entirely and sends commands directly to the tool implementation. This mode is only advisable for fully trusted scripts or isolated environments where security constraints are unnecessary.

## Where Permission Mode State Lives

Each session maintains its own **ModeState** managed by the singleton `ModeManager` exported from [`packages/shared/src/agent/mode-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-manager.ts).

The `ModeState` object contains:

- `permissionMode` – the current mode (`safe`, `ask`, or `allow-all`)
- `previousPermissionMode` – the mode before the latest transition (used for diagnostics)
- `modeVersion` – a monotonic counter incrementing on every change, enabling UI components to detect updates
- `lastChangedBy` – the actor who triggered the change (`user`, `system`, `restore`, etc.)

The `ModeManager` keeps a `Map<string, ModeState>` keyed by session ID, guaranteeing isolation between concurrent sessions. When a new workspace is created, the optional `defaults.permissionMode` field in `WorkspaceConfig` (defined in [`packages/shared/src/workspaces/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/workspaces/types.ts)) specifies the inherited mode; if omitted, the system falls back to `ask`.

## How Modes Gate Tool Execution

### Safe Mode Validation

In safe mode, the hard-coded set `SAFE_MODE_CONFIG.blockedTools` prevents any write operations. For Bash commands, `getBashRejectionReason` validates against `allowedBashPatterns` from the permissions JSON. The engine uses incremental regex diagnostics to pinpoint mismatches and suggest actionable fixes (for example, "run from within the repo directory").

### Ask Mode Permission Requests

When a command is rejected by safe-mode checks while in ask mode, the agent emits a `PermissionRequest` event. The UI displays the request, and the user's response may temporarily switch to `allow-all` or maintain `ask` mode via `setPermissionMode(sessionId, mode, metadata)`.

### Allow-All Bypass

In allow-all mode, the permission layer is completely bypassed. The command proceeds directly to the tool implementation without invoking `getBashRejectionReason` or checking blocked tool lists.

## Changing Permission Modes

### Programmatic API

The [`mode-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/mode-manager.ts) module exports `setPermissionMode(sessionId, mode, metadata?)` to update state programmatically. It returns `true` when the mode actually changes and fires callbacks to subscribers.

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

// Switch a session to Execute (allow-all)
setPermissionMode(sessionId, 'allow-all', { changedBy: 'user' });

```

### UI Shortcut

Users can press **SHIFT + TAB** in the interface to invoke `cyclePermissionMode(sessionId, enabledModes?)`. This function walks through the ordered list `PERMISSION_MODE_ORDER = ['safe', 'ask', 'allow-all']` defined in [`packages/shared/src/agent/mode-types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-types.ts) and commits the next mode with `changedBy: 'user'`.

### Persistence

When a session restores after a restart, `hydratePreviousPermissionMode` recovers the `previousPermissionMode` value without modifying the current mode, preserving transition history across launches.

## Configuration and Bash Allow-Lists

The permissions configuration resides in `~/.craft-agent/permissions/default.json` and follows the schema defined in [`packages/shared/src/agent/mode-types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-types.ts):

```json
{
  "allowedBashPatterns": ["...regex…"],
  "allowedWritePaths": ["...glob…"],
  "blockedCommandHints": [
    { "command": "git", "reason": "...", "example": "git status" }
  ]
}

```

These patterns compile at runtime into `CompiledBashPattern` objects. The `PermissionPaths` type in [`mode-types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/mode-types.ts) enables error messages to link directly to the configuration file requiring edits.

## Code Examples

### Query the Current Mode

```typescript
import { getPermissionMode } from '@craft-agent/shared/agent/mode-manager';

const cur = getPermissionMode(sessionId);
console.log(`Session ${sessionId} is in ${cur} mode`);

```

### Switch to Ask Mode

```typescript
import { setPermissionMode } from '@craft-agent/shared/agent/mode-manager';

setPermissionMode(sessionId, 'ask', { changedBy: 'user' });

```

### Cycle Through Modes

```typescript
import { cyclePermissionMode } from '@craft-agent/shared/agent/mode-manager';

const newMode = cyclePermissionMode(sessionId); // → next in SAFE → ASK → ALLOW-ALL
console.log(`Mode changed to ${newMode}`);

```

### Handle Rejected Bash Commands

```typescript
import { getBashRejectionReason, formatBashRejectionMessage } from '@craft-agent/shared/agent/mode-manager';
import { PERMISSION_MODE_CONFIG } from '@craft-agent/shared/agent/mode-types';

function tryCommand(cmd: string, sessionId: string) {
  const mode = getPermissionMode(sessionId);
  if (mode !== 'allow-all') {
    const reason = getBashRejectionReason(cmd, /* config resolved from permissions */);
    if (reason) {
      console.log(formatBashRejectionMessage(reason, PERMISSION_MODE_CONFIG[mode]));
      // UI would now show a permission request to the user.
      return;
    }
  }
  // Execute command safely…
}

```

## Summary

- **Three modes** govern tool execution: `safe` (read-only), `ask` (prompt before edit), and `allow-all` (unrestricted execution).
- **ModeState** is stored per-session in a `ModeManager` singleton, keyed by session ID to ensure isolation.
- **Default behavior** falls back to `ask` mode unless overridden by `WorkspaceConfig.defaults.permissionMode`.
- **Safe mode** blocks write tools and validates Bash commands against `~/.craft-agent/permissions/default.json` using `getBashRejectionReason`.
- **Mode transitions** occur via `setPermissionMode`, UI shortcuts (`cyclePermissionMode`), or persistence hooks (`hydratePreviousPermissionMode`).

## Frequently Asked Questions

### What is the default permission mode for new Craft Agents sessions?

New sessions default to **ask** (Ask to Edit) mode unless the workspace configuration specifies otherwise. The `WorkspaceConfig` interface in [`packages/shared/src/workspaces/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/workspaces/types.ts) allows setting `defaults.permissionMode` to `safe`, `ask`, or `allow-all`, which newly created sessions inherit upon initialization.

### How does safe mode determine which Bash commands are allowed?

Safe mode uses the `getBashRejectionReason` function in [`packages/shared/src/agent/mode-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/mode-manager.ts) to check commands against regex patterns defined in `allowedBashPatterns` within `~/.craft-agent/permissions/default.json`. Commands matching the allow-list return `null` and proceed; others return a `BashRejectionReason` detailing why the command was blocked.

### Can I change permission modes programmatically during a conversation?

Yes. Import `setPermissionMode` from `@craft-agent/shared/agent/mode-manager` and call it with the session ID and desired mode (`safe`, `ask`, or `allow-all`). The function updates the `ModeState`, increments the `modeVersion` counter, and notifies subscribers, returning `true` if the mode actually changed.

### What happens to permission mode when I restart the application?

The current mode persists, but the `previousPermissionMode` field is restored separately via `hydratePreviousPermissionMode`. This ensures that diagnostic history survives restarts while maintaining the active mode, allowing the system to track who changed the mode and when across application launches.