# Add a custom tool

Define a model-callable operation, validate its input, use runtime context, and return a supported result.

Use a custom tool when the agent needs executable behavior that is not provided by the built-in file and terminal tools.

## Complete example

Create `.pi/extensions/customer-tools.mjs`:

```js
export default function customerTools(pi) {
  pi.registerTool({
    name: 'lookup_customer',
    label: 'Look up customer',
    description: 'Look up a customer using their stable customer ID.',
    parameters: {
      type: 'object',
      properties: {
        customerId: {
          type: 'string',
          description: 'Stable customer identifier.',
        },
      },
      required: ['customerId'],
      additionalProperties: false,
    },
    async execute(_toolCallId, { customerId }, _signal, _onUpdate, ctx) {
      const source = ctx.external?.provider ?? 'api';

      return {
        content: [
          {
            type: 'text',
            text: `Customer ${customerId} requested from ${source}.`,
          },
        ],
        details: {
          customerId,
          source,
        },
      };
    },
  });
}
```

## Registration fields

| Field         | Requirement                                         |
| ------------- | --------------------------------------------------- |
| `name`        | 1–64 letters, numbers, underscores, or hyphens      |
| `label`       | Optional human-readable label                       |
| `description` | Required explanation used by the model              |
| `parameters`  | Required JSON-Schema-like object                    |
| `execute`     | Async function called in the sandbox extension host |

Write descriptions that explain when to use the tool. Keep the schema narrow and reject unexpected fields with `additionalProperties: false`.

## Execution arguments

```js
async execute(toolCallId, params, signal, onUpdate, ctx) {
  // ...
}
```

| Argument     | Hosted behavior                                             |
| ------------ | ----------------------------------------------------------- |
| `toolCallId` | Stable identifier for this tool invocation                  |
| `params`     | Model arguments validated against the declared schema       |
| `signal`     | Reserved by the hosted contract; currently may be undefined |
| `onUpdate`   | Reserved for progress updates; currently may be undefined   |
| `ctx`        | Bounded runtime context, including `ctx.external`           |

Do not depend on `signal` or `onUpdate` until their hosted behavior is documented as supported.

## Return a result

Every successful execution returns:

```js
return {
  content: [{ type: "text", text: "Result visible to the model" }],
  details: { optional: "structured diagnostic data" },
};
```

Hosted custom tools return text content only.

## Failure behavior

Throw an `Error` with a concise, non-secret message when execution cannot complete:

```js
throw new Error('Customer record was not found.');
```

Tool execution is bounded to 10 minutes. Do not return credentials, raw provider responses containing secrets, or unbounded payloads.

Validate with `salambo manifest`, then use a real [deploy and smoke test](/docs/agent-development/deploy-smoke-test).
