# Tools and allowlists

> Default read/write/edit/bash tools, extension tools, allowlist and exclude-tools filters, and blocked-tool termination behavior.

- Repository: earendil-works/pi
- GitHub: https://github.com/earendil-works/pi
- Human docs: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1
- Complete Markdown: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1/llms-full.txt

## Source Files

- `packages/coding-agent/examples/sdk/05-tools.ts`
- `packages/coding-agent/test/agent-session-dynamic-tools.test.ts`
- `packages/coding-agent/test/suite/regressions/2835-tools-allowlist-filters-extension-tools.test.ts`
- `packages/coding-agent/test/suite/regressions/3592-no-builtin-tools-keeps-extension-tools.test.ts`
- `packages/coding-agent/test/suite/regressions/5109-exclude-tools.test.ts`
- `packages/coding-agent/test/suite/regressions/5998-blocked-tool-terminate.test.ts`

---

---
title: "Tools and allowlists"
description: "Default read/write/edit/bash tools, extension tools, allowlist and exclude-tools filters, and blocked-tool termination behavior."
---

`createAgentSession` in `@earendil-works/pi-coding-agent` builds a session tool surface from built-ins (`read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`), optional `customTools`, and tools registered through extensions (`pi.registerTool`). Allowlists (`tools`), disables (`noTools`), and excludes (`excludedToolNames`) decide which names appear in `getAllTools()`, which stay in `getActiveToolNames()`, and what the system prompt lists under available tools.

## Built-in tools

Built-in names used by the SDK and session registry:

| Name | Role in evidence |
|------|------------------|
| `read` | File read; system prompt line uses snippet `Read file contents` |
| `write` | File write; default active with `edit` / `bash` |
| `edit` | File edit; default active |
| `bash` | Shell; default active; can expose `PI_*` session env |
| `grep` | Search; selectable via allowlist |
| `find` | Find; present in full tool set |
| `ls` | List; selectable via allowlist |

Tool name strings match against **all available** tools for the session. When you pass `cwd`, `createAgentSession()` applies that cwd when constructing the built-in tools.

### Default active vs available

The session distinguishes:

| API | Meaning in tests |
|-----|------------------|
| `session.getAllTools()` | Full registered/available set after filters |
| `session.getActiveToolNames()` | Tools treated as active for the turn / prompt |
| `session.systemPrompt` | Prompt text that lists active tools and guidelines |

With a normal session plus an extension tool, default active built-ins observed after excluding `read` are `bash`, `edit`, and `write` (so the default active built-in set is `read`, `write`, `edit`, `bash`). `find`, `grep`, and `ls` remain in the full tool set when built-ins are registered, but they are not required for the default active set.

### System prompt shape

Active tools appear as bullet lines:

```text
- read: Read file contents
- dynamic_tool: Run dynamic test behavior
- Use dynamic_tool when the user asks for dynamic behavior tests.
```

`promptSnippet` becomes the short description after the name. `promptGuidelines` are listed as additional bullets. When no tools are active:

```text
Available tools:
(none)
```

For bash-capable sessions, the prompt can also include:

```text
You can inspect PI_* environment variables for current model and session details.
```

## Configure tools in the SDK

### Allowlist with `tools`

Pass tool name strings to enable only those tools:

```ts
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";

// Read-only (no edit/write)
const { session: readOnlySession } = await createAgentSession({
  tools: ["read", "grep", "find", "ls"],
  sessionManager: SessionManager.inMemory(),
});

// Custom selection
const { session: customToolsSession } = await createAgentSession({
  tools: ["read", "bash", "grep"],
  sessionManager: SessionManager.inMemory(),
});

// Custom cwd + selected tools
const customCwd = "/path/to/project";
const { session: customCwdSession } = await createAgentSession({
  cwd: customCwd,
  tools: ["read", "bash", "edit", "write"],
  sessionManager: SessionManager.inMemory(customCwd),
});
```

Allowlists apply to **built-in and extension** tool names. Example: `tools: ["read", "dynamic_tool"]` yields only those two in both `getAllTools()` and `getActiveToolNames()`, and the system prompt includes both snippets while omitting `- bash:` and `- edit:`.

| `tools` value | `getAllTools()` | `getActiveToolNames()` | System prompt |
|---------------|-----------------|------------------------|---------------|
| `["read", "dynamic_tool"]` | `dynamic_tool`, `read` | same | includes both; excludes bash/edit |
| `[]` (empty allowlist) | `[]` | `[]` | `Available tools:\n(none)` |

### Disable with `noTools`

`createAgentSession` and `createAgentSessionFromServices` accept:

| Value | Effect |
|-------|--------|
| `"builtin"` | Built-in tools are not active; extension tools can stay active |
| `"all"` | No tools available or active |

With `noTools: "builtin"` and an extension that registers `dynamic_tool` on `session_start`:

- `getAllTools()` still includes  
  `bash`, `dynamic_tool`, `edit`, `find`, `grep`, `ls`, `read`, `write`
- `getActiveToolNames()` is `["dynamic_tool"]` only
- System prompt lists the extension tool and **does not** list `- read:` or `- bash:`

With `noTools: "all"`:

- `getAllTools()` → `[]`
- `getActiveToolNames()` → `[]`
- System prompt → `Available tools:\n(none)`

Service-based construction propagates the same option:

```ts
const services = await createAgentSessionServices({ cwd, agentDir, settingsManager });
const { session } = await createAgentSessionFromServices({
  services,
  sessionManager,
  model,
  noTools: "builtin",
});
```

Without extension tools in that path, `noTools: "builtin"` leaves `getActiveToolNames()` empty and the prompt at `Available tools:\n(none)`.

### Exclude tools

Excludes remove names from both available and active sets. They apply to built-ins and extension tools.

| Input | Result |
|-------|--------|
| `excludedToolNames: ["read", "ask_question"]` | Neither name in `getAllTools()`; active set can still include `bash`, `edit`, `write`, and remaining extensions |
| Allowlist + exclude | **Exclude wins** over allowlist |

Example: allowlist `["read", "bash", "ask_question"]` with excludes `["read", "ask_question"]` leaves only `bash` in both `getAllTools()` and `getActiveToolNames()`.

```text
allowlist ──► candidates
exclude  ──► hard removal (overrides allowlist)
noTools  ──► "builtin" deactivates defaults; "all" clears everything
```

### SDK custom tools

Register tools at session construction with `customTools`:

```ts
const { session } = await createAgentSession({
  cwd,
  agentDir,
  model,
  settingsManager,
  sessionManager,
  resourceLoader,
  customTools: [
    {
      name: "sdk_tool",
      label: "SDK Tool",
      description: "Tool registered through createAgentSession",
      parameters: Type.Object({}),
      execute: async () => ({
        content: [{ type: "text", text: "ok" }],
        details: {},
      }),
    },
  ],
});
```

`sdk_tool` is active (`getActiveToolNames()` includes it). Source metadata:

| Field | Value |
|-------|--------|
| `path` | `<sdk:sdk_tool>` |
| `source` | `"sdk"` |
| `scope` | `"temporary"` |
| `origin` | `"top-level"` |

Custom tools for interactive packaging are also registered through extensions via `pi.registerTool()` (see extensions docs / `06-extensions.ts` in the SDK examples tree).

## Extension and dynamic tools

### Registration paths

| Path | When it appears |
|------|-----------------|
| `pi.registerTool(...)` in an extension factory | Immediately if registered at factory load |
| `pi.on("session_start", () => pi.registerTool(...))` | After `session.bindExtensions({})` refreshes the registry |
| `customTools` on `createAgentSession` | At session creation |

Before `bindExtensions`, tools registered only on `session_start` are **not** in `getAllTools()`. After bind:

- Name appears in `getAllTools()` and typically in `getActiveToolNames()`
- System prompt includes `- <name>: <promptSnippet>` and any `promptGuidelines`

### Tool definition fields used by the registry

| Field | Use |
|-------|-----|
| `name` | Tool id / allowlist / exclude key |
| `label` | Display label |
| `description` | Tool description |
| `promptSnippet` | Short line in system prompt |
| `promptGuidelines` | Extra prompt bullets |
| `parameters` | TypeBox schema |
| `execute` | Implementation |

### Source metadata (`sourceInfo`)

| Origin | `path` | `source` | `scope` | `origin` |
|--------|--------|----------|---------|----------|
| Built-in `read` | `<builtin:read>` | `"builtin"` | `"temporary"` | `"top-level"` |
| Inline extension tool | `<inline:1>` | `"inline"` | `"temporary"` | `"top-level"` |
| SDK `customTools` | `<sdk:sdk_tool>` | `"sdk"` | `"temporary"` | `"top-level"` |

### Bash session environment

`createBashTool(cwd, options)` (from the coding-agent bash tool module) can inject session state into the shell environment via `spawnHook`.

Default exposure includes:

| Variable | Source |
|----------|--------|
| `PI_SESSION_ID` | `session.sessionId` |
| `PI_SESSION_FILE` | `session.sessionFile` |
| `PI_PROVIDER` | `model.provider` |
| `PI_MODEL` | `model.id` |
| `PI_REASONING_LEVEL` | `session.thinkingLevel` |

Set `exposeSessionEnvironment: false` to omit those `PI_*` keys from the spawn env. Custom bash variants can re-register under a different `name` / `label` while reusing `createBashTool` options.

## Block tool calls and terminate the run

Extensions can handle `tool_call` and block execution before `execute` runs:

```ts
pi.on("tool_call", async () => ({
  block: true,
  reason: "Blocked by terminating policy",
  terminate: true,
}));
```

| Field | Effect |
|-------|--------|
| `block: true` | Tool body does not run |
| `reason` | Block reason string |
| `terminate: true` | Ends the agent run after the block |

Observed outcomes after a blocked terminating call:

1. `tool_execution_end` result includes `terminate: true`
2. A `toolResult` message with `isError` is present on the session
3. Subsequent queued model turns do not run (pending responses remain; later assistant text is not produced)

Use this for policy gates that must stop the turn, not only refuse a single tool.

## Session inspection checklist

After configuring tools:

```ts
session.getAllTools().map((t) => t.name);
session.getActiveToolNames();
session.systemPrompt; // look for "- name:" lines or "Available tools:\n(none)"
await session.bindExtensions({}); // required before session_start-registered tools appear
session.dispose();
```

| Goal | Options |
|------|---------|
| Read-only coding | `tools: ["read", "grep", "find", "ls"]` |
| Default editing surface | omit filters, or `tools: ["read", "bash", "edit", "write"]` |
| Extension-only active tools | `noTools: "builtin"` + extension `registerTool` |
| No tools | `tools: []` or `noTools: "all"` |
| Drop specific names | `excludedToolNames` (overrides allowlist) |
| Policy stop on call | `tool_call` handler with `block` + `terminate` |

## Related pages

<CardGroup>
  <Card title="SDK" href="/sdk">
    Embed pi with createAgentSession, custom tools, models, and settings.
  </Card>
  <Card title="SDK examples" href="/sdk-examples">
    Copy-paste recipes including tools and full-control setups.
  </Card>
  <Card title="Extensions" href="/extensions">
    TypeScript extension registration, active tools on next turn, and lifecycle hooks.
  </Card>
  <Card title="Extension examples" href="/extension-examples">
    Reference packages that register tools and policies.
  </Card>
  <Card title="Session runtime" href="/session-runtime">
    AgentSessionRuntime services and embedding sessions without the TUI.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    Confirm the default read/write/edit/bash surface after install.
  </Card>
</CardGroup>
