---
title: Add tools and instructions
description: Define an operation, register it for the right agent context, and make its behavior recoverable.
---

Server tools live under `server/src/tools/`. A `ToolDefinition` combines a model-facing JSON schema with runtime validation, execution and a retry policy. The `ToolRegistry` is an immutable code catalog; it contains no user's credentials or execution state.

## Define a small read-only tool

This complete example returns the current time in a requested time zone. Save it as `server/src/tools/time-tools.ts` in your fork. It uses the existing registry and error types.

```ts
import { ServiceError } from '../errors.js';
import { ToolRegistry } from './registry.js';

export const timeTools = new ToolRegistry([{
  name: 'impo_time_in_zone', version: 1,
  family: 'internal', executionLocation: 'server',
  description: 'Read the current time in an IANA time zone.',
  parameters: {
    type: 'object', additionalProperties: false,
    properties: { timeZone: { type: 'string', maxLength: 100 } },
    required: ['timeZone'],
  },
  timeoutMs: 1000, retry: 'read-only',
  validate(input) {
    const value = input as Record<string, unknown> | null;
    const timeZone = value?.timeZone;
    try {
      if (!value || Array.isArray(value) ||
          Object.keys(value).length !== 1 ||
          typeof timeZone !== 'string' || timeZone.length > 100) {
        throw new Error('Invalid arguments');
      }
      new Intl.DateTimeFormat('en-US', { timeZone }).format();
      return { timeZone };
    } catch {
      throw new ServiceError(422, 'invalid_tool_arguments',
        'Provide one valid IANA timeZone.');
    }
  },
  async execute(input, context) {
    context.signal.throwIfAborted();
    const now = new Date();
    const timeZone = input.timeZone as string;
    return { ok: true, data: {
      utc: now.toISOString(), timeZone,
      local: new Intl.DateTimeFormat('en-US', {
        timeZone, dateStyle: 'full', timeStyle: 'long',
      }).format(now),
    } };
  },
}]);
```

For account data, inject an owned repository and use `context.userId`. For durable side effects, use `context.invocationId` as part of the receipt or deduplication strategy. Never let tool arguments select an arbitrary account.

## Register and expose it

In `server/src/runtime.ts`, import `timeTools` and include it in the existing `ToolRegistry.merge(...)` that constructs `serverTools`. Main Chat's `agentConfig` already takes function definitions from that catalog.

Task tools are selected separately in `taskAgentConfig`. If the new operation is appropriate for Tasks, add `...timeTools.functionDefinitions()` there too. Do not replace the existing catalogs. Native-device tools and recursive task creation remain excluded from Tasks.

The deterministic development runtime does not make real model tool selections. Validate the tool directly first; use the Rebyte protocol test path or a configured Rebyte runtime for integration.

## Write the instruction

Put behavior guidance in the English modules under `server/src/prompts/`, not in the mobile app. For the example: “When the user asks for the current time in a place, resolve the IANA time zone and use `impo_time_in_zone`. Report the returned time and zone.”

Update the applicable prompt version when changing a Session's configuration. Existing accepted work must retain its execution configuration. Follow the existing Session refresh and capability-selection behavior rather than mutating an active turn.

## Choose the retry policy

| Policy | Appropriate use |
| --- | --- |
| `read-only` | A repeated read has no external side effect. |
| `transactional` | The implementation has a durable operation identity and can return its prior result. |
| `never` | Repeating an uncertain operation could duplicate a side effect. |

Selecting `transactional` does not create idempotency automatically. The operation and its receipt must enforce it. Return explicit errors and respect `context.signal` when calling external services.

## Verify and ship

Test invalid arguments, unavailable dependencies, account ownership and the declared retry behavior. Run `npm run typecheck` and `npm run test:server`; use `npm run test:rebyte` when changing durable provider dispatch. Deploy the API and worker configuration together when both construct the runtime.

Read the [registry](https://github.com/impoai/impo/blob/main/server/src/tools/registry.ts), [runtime composition](https://github.com/impoai/impo/blob/main/server/src/runtime.ts) and [gadget tools](https://github.com/impoai/impo/blob/main/server/src/tools/gadget-tools.ts) for production examples.
