# How Pi-Web Tool Presets (NONE, READ_ONLY, DEFAULT, FULL) Work and How They're Passed at Session Creation

> Understand Pi-Web tool presets NONE READ_ONLY DEFAULT and FULL. Learn how these presets control available coding tools and pass to the backend during session creation for seamless integration.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-16

---

**Pi-Web uses four built-in tool presets that determine which coding tools are available in a chat session, translating a preset name into a concrete array of tool names that gets passed to the backend during session creation.**

Understanding how tool presets function in the Pi-Web codebase requires tracing the flow from UI selection through the API layer to the RPC session manager. This article examines the complete lifecycle of a tool preset, from the TypeScript definitions in the core library to the moment tools are activated for a new agent session.

## Tool Preset Definitions in lib/tool-presets.ts

The foundation of Pi-Web's preset system lives in **[`lib/tool-presets.ts`](https://github.com/agegr/pi-web/blob/main/lib/tool-presets.ts)**, which exports four constants that enumerate allowed tools for each preset mode:

| Preset | Available Tools |
|--------|-----------------|
| `NONE` | `[]` (empty — all tools disabled) |
| `READ_ONLY` | `["read", "grep", "find", "ls"]` |
| `DEFAULT` | `["read", "bash", "edit", "write"]` |
| `FULL` | `["bash", "read", "edit", "write", "grep", "find", "ls"]` |

This file also provides two critical helper functions. **`getToolNamesForPreset(preset)`** converts a preset enum value into its corresponding string array. **`getPresetFromTools(tools)`** performs the reverse operation: it accepts an array of tool objects (each with an `active` boolean) and returns the matching preset name, with a fallback to `"default"` when no exact match exists.

These helpers ensure the system can both **apply** presets during session creation and **reconstruct** the current preset when examining an existing session's state.

## How the UI Translates Presets to Tool Names

User interaction with tool presets occurs through **[`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts)**, a React hook that manages the preset selector in the chat interface. When a user selects a new preset, the hook executes a three-step process:

1. Calls `getToolNamesForPreset(preset)` to retrieve the concrete tool array
2. Persists the selection via `setPreferredToolPreset` to localStorage
3. Dispatches a `"set_tools"` command to the running session

```typescript
// From hooks/useAgentSession.ts (lines 1727-1732)
const toolNames = getToolNamesForPreset(preset);
setPreferredToolPreset(preset);
await sendAgentCommand(sid, { type: "set_tools", toolNames });

```

The `sendAgentCommand` function bridges to the backend via WebSocket or HTTP, ensuring the tool configuration change propagates immediately to the active RPC session.

## Passing Tool Presets When Creating a New Session

The critical handoff for session creation happens in **[`app/api/agent/new/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/new/route.ts)**. This route handler receives a POST request that may include a `toolNames` array derived from the selected preset, then forwards it to the RPC manager.

```typescript
// From app/api/agent/new/route.ts (line 48)
const { toolNames, cwd, message } = await request.json();
const session = await startRpcSession({ toolNames, cwd, message });

```

The backend does not receive the preset string directly — **the preset is always resolved to tool names before transmission**. This design keeps the API contract explicit about which tools are available and prevents ambiguity in the session initialization protocol.

## Activating Tools in the RPC Session Manager

Once `toolNames` reaches **[`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)**, the `RpcSessionStartOptions` type governs how the configuration is applied. The session manager performs three key operations:

```typescript
// From lib/rpc-manager.ts (lines 698-701)
if (toolNames !== undefined) {
  // Empty list disables all tools and clears the system prompt
  this.setForceEmptySystemPrompt(toolNames.length === 0);
  // Merge with extension tools, then activate
  this.inner.setActiveToolsByName(withExtensionTools(this.inner, toolNames));
}

```

The **`setForceEmptySystemPrompt`** call handles a special case: when `toolNames` is an empty array (the `NONE` preset), the manager strips the system prompt entirely to indicate no tools are available. This distinguishes a deliberately empty toolset from an undefined one.

The **`withExtensionTools`** helper (lines 147-156) ensures extension-provided tools remain available regardless of preset. It appends any non-coding tools from installed extensions to the user-selected list, preserving functionality like custom integrations even under restrictive presets.

## Reading and Reconstructing Presets from Active Sessions

For existing sessions, the UI determines the current preset through a reverse lookup. The flow begins with a `"get_tools"` command handled in **[`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)** (lines 58-66), which returns the full tool list with active flags.

```typescript
// UI-side preset reconstruction
const tools = await sendAgentCommand(sessionId, { type: 'get_tools' });
// tools: [{ name: 'read', active: true }, { name: 'bash', active: false }, ...]
const preset = getPresetFromTools(tools); // "read-only" | "default" | "full" | "none"

```

This mechanism ensures the preset selector always reflects ground truth from the backend rather than cached UI state, preventing synchronization issues when tools are modified through other channels.

## Persisting User Preferences Across Sessions

The last preset selected by a user survives page reloads through **[`lib/tool-preset-preference.ts`](https://github.com/agegr/pi-web/blob/main/lib/tool-preset-preference.ts)**, which manages the `pi-tool-preset` key in browser localStorage:

```typescript
// From lib/tool-preset-preference.ts (lines 32-37)
export function setPreferredToolPreset(preset: ToolPreset): void {
  if (typeof window !== 'undefined') {
    localStorage.setItem('pi-tool-preset', preset);
  }
}

export function getPreferredToolPreset(): ToolPreset {
  return (localStorage.getItem('pi-tool-preset') as ToolPreset) || 'default';
}

```

This preference seeds the preset selector for **new** sessions only. **Existing sessions always derive their preset from actual active tools via `getPresetFromTools`**, ensuring the UI never misrepresents a session's capabilities.

## Complete Code Example: Session Creation with a Preset

```typescript
import { getToolNamesForPreset, ToolPreset } from '@/lib/tool-presets';

async function createReadOnlySession(cwd: string, message: string) {
  const preset: ToolPreset = 'read-only';
  const toolNames = getToolNamesForPreset(preset);
  
  const response = await fetch('/api/agent/new', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cwd, message, toolNames }),
  });
  
  return response.json(); // { sessionId: string }
}

```

## Summary

- **Preset definitions** in [`lib/tool-presets.ts`](https://github.com/agegr/pi-web/blob/main/lib/tool-presets.ts) provide four fixed tool configurations as string arrays, with bidirectional conversion helpers.
- **UI translation** in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) converts preset selections to `toolNames` arrays before any backend communication.
- **Session creation** at [`app/api/agent/new/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/agent/new/route.ts) accepts `toolNames` in the POST body and forwards it to the RPC manager.
- **Tool activation** in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) applies the tool list, handles the special `NONE` case by clearing the system prompt, and merges extension tools via `withExtensionTools`.
- **Preset reconstruction** uses `getPresetFromTools` to derive the preset name from a session's actual active tools, ensuring UI accuracy.
- **Preference persistence** stores the last selected preset in localStorage under `pi-tool-preset` for initialization of future new sessions.

## Frequently Asked Questions

### What happens when I select the NONE preset?

The `NONE` preset translates to an empty tool array `[]`. When passed to [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts), this triggers `setForceEmptySystemPrompt(true)`, which disables all tools and clears the system prompt entirely. According to the source code, this special handling distinguishes intentional tool disabling from an undefined configuration.

### Can extensions override preset restrictions?

Extensions cannot override preset restrictions, but they are **preserved** across all presets. The `withExtensionTools` helper in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (lines 147-156) automatically merges any extension-provided tools with the user-selected list, ensuring custom integrations remain functional even when all standard coding tools are disabled.

### How does Pi-Web remember my last preset choice?

The last preset is stored in browser `localStorage` under the key `pi-tool-preset` by [`lib/tool-preset-preference.ts`](https://github.com/agegr/pi-web/blob/main/lib/tool-preset-preference.ts). This value initializes the preset selector for new sessions. However, for existing sessions, the UI always queries the backend via `"get_tools"` and uses `getPresetFromTools` to display the accurate current state.

### Why does the API use toolNames instead of the preset string?

The API uses concrete `toolNames` arrays rather than preset identifiers to maintain an **explicit contract** about session capabilities. This design prevents ambiguity — the backend knows exactly which tools to activate without needing to import preset definitions, and the UI remains the single source of truth for preset-to-tool mapping logic.