# How to Create Custom Tools for PrimeAgent: A Step-by-Step Guide

> Learn to create custom tools for PrimeAgent with this step-by-step guide. Write TypeScript extensions and register them using pi.registerTool() for enhanced agent functionality.

- Repository: [Prime Intellect/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent)
- Tags: how-to-guide
- Published: 2026-08-14

---

**You create custom tools for PrimeAgent by writing a TypeScript extension that registers a `ToolDefinition` via `pi.registerTool()`, then placing the file in `~/.prime/agent/extensions/` or your project's `.prime/agent/extensions/` folder.**

PrimeAgent's extensibility model lets you equip the LLM with new capabilities beyond built-in tools like `bash` and `edit`. Custom tools plug into the same runtime as native features—complete with schema validation, streaming UI updates, and security controls. This guide covers the architecture, API, and implementation details drawn directly from the PrimeIntellect-ai/prime-agent source code.

## Extension Architecture and Discovery

PrimeAgent discovers extensions automatically from two locations:

- `~/.prime/agent/extensions/` (user-global)
- `.prime/agent/extensions/` (project-local)

Extensions load via [jiti](https://github.com/unjs/jiti), so you write TypeScript without a separate compilation step. The loader handles parsing and registration at runtime.

According to the documentation in [`packages/coding-agent/docs/extensions.md`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/extensions.md), the discovery system prioritizes project-local extensions, enabling per-project tool customization.

## The Extension API: `pi.registerTool()`

Each extension receives an `ExtensionAPI` object (conventionally named `pi`). This object exposes the full extension surface, including the critical `registerTool` method for custom tool registration.

The `registerTool` signature appears in [`packages/coding-agent/src/core/extensions/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/extensions/types.ts) at line 1081:

```typescript
registerTool<TParams extends TObject, TDetails = unknown, TState = unknown>(
  definition: ToolDefinition<TParams, TDetails, TState>
): void;

```

Through this API, extensions can also subscribe to events (`pi.on`), register commands (`pi.registerCommand`), and access session context.

## Tool Definition Structure

A `ToolDefinition` requires five key properties:

| Property | Type | Purpose |
|----------|------|---------|
| `name` | `string` | Identifier the LLM uses to invoke the tool |
| `label` | `string` | Display name in the TUI |
| `description` | `string` | Help text shown to the LLM and user |
| `parameters` | `TObject` (typebox schema) | JSON Schema for validation |
| `execute` | `async function` | Runtime implementation |

The `execute` callback receives five arguments defined in [`packages/coding-agent/src/core/extensions/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/extensions/types.ts):

```typescript
async execute(
  toolCallId: string,           // Unique call identifier
  params: Static<TParams>,      // Validated parameters
  signal: AbortSignal,          // Cancellation support
  onUpdate: (update: unknown) => void,  // Streaming updates
  ctx: unknown                  // Extension context
): Promise<ToolResult>

```

## Registration and Execution Flow

The lifecycle spans three core files:

1. **[`packages/coding-agent/src/core/extensions/loader.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/extensions/loader.ts)** — Loads extensions, calls their default export with `pi`, and populates the session tool registry with registered definitions.

2. **[`packages/coding-agent/src/modes/interactive/components/tool-execution.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/interactive/components/tool-execution.ts)** — When the LLM emits a `tool_call`, looks up the tool by name, validates parameters against the typebox schema, and invokes `execute`.

3. **[`packages/coding-agent/src/core/tools/tool-definition-wrapper.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts)** — Wraps the raw result into the standard `ToolResult` format for streaming to the LLM and rendering in the interface.

## Complete Custom Tool Example

Below is a minimal working extension based on the reference implementation in [`packages/coding-agent/docs/extensions.md`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/extensions.md) (lines 76-90) and [`packages/coding-agent/examples/extensions/hello.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/examples/extensions/hello.ts):

```typescript
// file: ~/.prime/agent/extensions/greet.ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "@sinclair/typebox";

export default function (pi: ExtensionAPI) {
  pi.registerTool({
    name: "greet",
    label: "Greet",
    description: "Greet someone by name",
    
    parameters: Type.Object({
      name: Type.String({ description: "Name to greet" }),
    }),
    
    async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
      return {
        content: [{ type: "text", text: `Hello, ${params.name}!` }],
        details: {},
      };
    },
  });
}

```

Key implementation notes:

- **Schema validation**: The `parameters` field uses `@sinclair/typebox` for JSON Schema generation—the same library PrimeAgent uses internally.
- **Result format**: `content` must be an array of content blocks; `type: "text"` is the simplest option.
- **Streaming**: Call `onUpdate()` during long-running operations to send progress to the TUI.

## Testing Your Custom Tool

Launch PrimeAgent with the `-e` flag to load your extension:

```bash
prime-agent -e ~/.prime/agent/extensions/greet.ts

```

The LLM can now invoke your tool with a JSON tool call:

```json
{
  "toolCallId": "tc-1",
  "toolName": "greet",
  "arguments": { "name": "Alice" }
}

```

The agent executes `execute()`, streams the result back, and displays "Hello, Alice!" in the interface.

## Advanced Patterns

### Long-Running Operations with Streaming

For tools that take time, use the `onUpdate` callback to send intermediate state:

```typescript
async execute(toolCallId, params, signal, onUpdate, ctx) {
  onUpdate({ status: "processing", step: 1 });
  await fetchExternalData(params.query);
  onUpdate({ status: "complete", step: 2 });
  
  return {
    content: [{ type: "text", text: "Data retrieved" }],
    details: { query: params.query },
  };
}

```

### Accessing Context and Cancellation

The `ctx` parameter provides extension-scoped state and utilities. The `signal` parameter integrates with PrimeAgent's abort mechanics—check `signal.aborted` or pass it to `fetch()` for cooperative cancellation.

## Key Source Files Reference

| File | Role |
|------|------|
| [`packages/coding-agent/docs/extensions.md`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/extensions.md) | Official guide with examples |
| [`packages/coding-agent/src/core/extensions/types.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/extensions/types.ts) | `ExtensionAPI` and `ToolDefinition` types |
| [`packages/coding-agent/src/core/extensions/loader.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/extensions/loader.ts) | Extension loading and tool registry |
| [`packages/coding-agent/src/core/tools/tool-definition-wrapper.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/tools/tool-definition-wrapper.ts) | Result normalization |
| [`packages/coding-agent/examples/extensions/hello.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/examples/extensions/hello.ts) | Working sample extension |
| [`packages/coding-agent/src/modes/interactive/components/tool-execution.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/interactive/components/tool-execution.ts) | Runtime tool invocation |

## Summary

- **Create an extension file** in `~/.prime/agent/extensions/` or `.prime/agent/extensions/`
- **Import `ExtensionAPI`** and `@sinclair/typebox` for type-safe tool definitions
- **Call `pi.registerTool()`** with `name`, `label`, `description`, `parameters` (typebox schema), and `execute` function
- **Return a `ToolResult`** with `content` array and optional `details`
- **Launch with `-e`** to test, then deploy to project folders for team sharing

## Frequently Asked Questions

### What TypeScript features can I use in extensions?

PrimeAgent loads extensions with **jiti**, which supports modern TypeScript (ES modules, decorators, JSX) without pre-compilation. You can import npm packages installed in your project or use relative imports to shared code.

### Can I register multiple tools in one extension?

Yes. A single extension file can call `pi.registerTool()` multiple times—once per tool. The loader accumulates all registrations into the session's tool registry.

### How does parameter validation work?

PrimeAgent validates incoming tool call arguments against your **typebox schema** before `execute()` runs. Invalid parameters trigger an automatic error response to the LLM without invoking your code.

### Can custom tools call other tools?

The `ctx` parameter exposes extension-scoped utilities. For cross-tool coordination, share state through the extension closure or use PrimeAgent's event system (`pi.on`) to react to tool execution events.