# Tools

Tools are reusable functions that agents can call to perform specific actions like processing payments, sending emails, or updating databases.

## Tool Structure

Every tool must implement the `Tool` interface:

```ts
// Example: Tool with input and optional output schemas
interface Tool<
  Input extends z.ZodSchema,
  Memory = any,
  Output extends z.ZodSchema = z.ZodTypeAny
> {
  name: string; // Unique tool identifier
  description: string; // What the tool does (used by LLM)
  input: Input; // Zod schema for input validation
  output?: Output; // Optional: Zod schema for output validation (e.g., outputSchema)
  isGlobal?: boolean; // Optional: available across all LLM calls
  execute: ({ input, state, agent }) => Promise<{ result? }>;
}
```

## Execute Function Signature

The `execute` function is the core of every tool. It receives validated input from the LLM, current state (including memory), and the agent instance, then modifies state by reference and returns an optional result.

### Parameters

```ts
execute: ({ input, state, agent }) => Promise<{ result? }>;
```

* **`input`**: Validated data matching your Zod schema - contains the parameters the LLM extracted from the conversation
* **`state`**: Current conversation state including memory, sessionId, and other context data
* **`agent`**: Agent instance providing access to PII gateway, logging, and other platform features

### Return Value

The execute function returns a Promise with an object containing:

* **`result?`** (optional): Result that gets sent back to the LLM as the tool's response

**Important v2.0 Change**: State is now updated by reference directly within the execute function. You no longer return state updates - instead, modify the state object directly.

## State Object Structure

The `state` object contains:

* **`memory`**: Your user-defined memory schema - the main working memory for your agent
* **`sessionId`**: Unique identifier for the current session
* **`messages`**: Array of conversation messages (AI, Human, System, etc.)
* **`history`**: Array of HistoryStep objects tracking the flow execution (node visits, trigger events, tool calls)
* **Other platform context**: Additional fields managed by the platform

## Basic Tool Example

Here's a simple refund processing tool:

```ts
import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { logger } from 'mindedjs';
import memorySchema from '../schema';

type Memory = z.infer<typeof memorySchema>;

const schema = z.object({
  orderId: z.string(),
  customerName: z.string(),
});

const refundOrderTool: Tool<typeof schema, Memory> = {
  name: 'refundOrder',
  description: 'Process a customer refund for their order',
  input: schema,
  execute: async ({ input, state, agent }) => {
    // Access current memory
    const currentMemory = state.memory;

    // Use the provided logger [[memory:4133901]]
    logger.info('Processing refund', {
      sessionId: state.sessionId,
      orderId: input.orderId,
    });

    // Your business logic here
    const refundAmount = await processRefundInSystem(input.orderId);

    // Update state directly by reference (v2.0 change)
    state.memory.orderId = input.orderId;
    state.memory.customerName = input.customerName;
    state.memory.issue = `Refund processed: $${refundAmount}`;

    // Only return result, no state
    return {
      result: `Refund processed successfully for order ${input.orderId}`,
    };
  },
};

export default refundOrderTool;
```

## Input Schema Configuration

Input schemas use Zod for validation and provide important metadata to help the LLM understand how to better use each input parameter.

### Parameter Descriptions

Add descriptions to parameters using Zod's `.describe()` method. These descriptions help the LLM understand what each parameter represents:

```ts
const schema = z.object({
  orderId: z.string().describe('The unique identifier of the customer order to process'),
  customerName: z.string().describe('Full name of the customer requesting the refund'),
  refundAmount: z.number().describe('Amount to refund in dollars (optional if full refund)'),
  reason: z.string().describe('Reason for the refund request'),
});
```

### Optional Parameters

Make parameters optional using Zod's `.optional()` method. Optional parameters don't need to be provided by the LLM:

```ts
const schema = z.object({
  orderId: z.string().describe('The unique identifier of the customer order'),
  customerName: z.string().describe('Full name of the customer'),
  refundAmount: z.number().optional().describe('Specific refund amount, defaults to full order amount'),
  expedited: z.boolean().optional().describe('Whether to expedite the refund process'),
});
```

### Combining Descriptions and Optional Parameters

You can chain Zod methods to create descriptive optional parameters:

```ts
const updateOrderSchema = z.object({
  orderId: z.string().describe('The order ID that needs to be updated'),
  newAddress: z.string().optional().describe('Updated shipping address if customer wants to change it'),
  specialInstructions: z.string().optional().describe('Any special delivery instructions from the customer'),
  urgentDelivery: z.boolean().optional().describe('Whether customer needs urgent delivery (additional charges may apply)'),
});
```

## Output Schema Configuration

Tools can optionally define an output schema to validate and structure their return values. This provides type safety and clear documentation of what the tool returns.

### Defining Output Schema

**Important**: The output schema variable must be named `outputSchema` for the platform to parse it correctly.

```ts
import { z } from 'zod';
import { Tool } from '@minded-ai/mindedjs';
import memorySchema from '../schema';

type Memory = z.infer<typeof memorySchema>;

const inputSchema = z.object({
  email: z.string().describe('User email address'),
  password: z.string().describe('User password'),
});

// Must be named 'outputSchema'
const outputSchema = z.object({
  success: z.boolean().describe('Status of the login operation'),
  message: z.string().describe('Login status message'),
  url: z.string().describe('Dashboard URL after successful login'),
  usedPersistedSession: z.boolean().describe('Whether a persisted session was used'),
});

const loginTool: Tool<typeof inputSchema, Memory, typeof outputSchema> = {
  name: 'login_to_dashboard',
  description: 'Login to dashboard and return access details',
  input: inputSchema,
  output: outputSchema,
  type: 'rpa',
  execute: async ({ input, state, agent, page }): Promise<{ result: z.infer<typeof outputSchema> }> => {
    // Tool logic here
    const loginResult = await performLogin(page, input.email, input.password);

    return {
      result: {
        success: true,
        message: 'Successfully logged in',
        url: page.url(),
        usedPersistedSession: false,
      },
    };
  },
};

export default loginTool;
```

### Benefits of Output Schema

* **Type Safety**: TypeScript will enforce that your return value matches the output schema
* **Validation**: Zod validates the output at runtime to catch bugs early
* **Documentation**: Output schema serves as clear documentation of what the tool returns
* **LLM Context**: The LLM receives structured information about the tool's output format

### Output Schema vs Result

* **Without output schema**: Return any value in `result`, no validation
* **With output schema**: Return value must match the schema structure

```ts
// Without output schema - any result
return { result: 'Login successful' };

// With output schema - typed and validated
return {
  result: {
    success: true,
    message: 'Login successful',
    url: 'https://dashboard.example.com',
    usedPersistedSession: false,
  },
};
```

## Tool Registration

### Global Tools

Mark tools as global if you want them to be available in all LLM calls.

```ts
const auditLogTool: Tool<typeof auditSchema, Memory> = {
  name: 'auditLog',
  description: 'Log user action for compliance',
  isGlobal: true, // Available in all flows
  input: z.object({
    action: z.string(),
    details: z.string(),
  }),
  execute: async ({ input, state, agent }) => {
    logger.info('Audit log entry', {
      sessionId: state.sessionId,
      action: input.action,
    });

    await auditService.log({
      userId: state.memory.userId,
      action: input.action,
      details: input.details,
      timestamp: new Date(),
    });

    // No return value needed for tools that don't produce results
    return {};
  },
};
```

### Global vs Non-Global Tools

| Feature       | Non-Global Tools                | Global Tool                         |
| ------------- | ------------------------------- | ----------------------------------- |
| **Execution** | Guaranteed when node is reached | Only when LLM decides to call       |
| **Control**   | Explicit flow control           | LLM-driven decision                 |
| **Use Case**  | Required business logic steps   | Flexible, context-dependent actions |

## State Management

Tools can read and update state including memory by reference:

```ts
const updateProfileTool: Tool<typeof profileSchema, Memory> = {
  name: 'updateProfile',
  description: 'Update customer profile information',
  input: z.object({
    field: z.string(),
    value: z.string(),
  }),
  execute: async ({ input, state, agent }) => {
    // Read current memory from state
    console.log(`Current customer: ${state.memory.customerName}`);

    // Update external system
    await profileService.update(state.memory.customerId, {
      [input.field]: input.value,
    });

    // Update state directly by reference (v2.0 change)
    state.memory[`${input.field}Updated`] = true;

    // No state in return value
    return {};
  },
};
```

## Error Handling

Handle errors gracefully in tools:

```ts
const paymentTool: Tool<typeof paymentSchema, Memory> = {
  name: 'processPayment',
  description: 'Process customer payment',
  input: z.object({
    amount: z.number(),
    cardToken: z.string(),
  }),
  execute: async ({ input, state, agent }) => {
    try {
      const result = await paymentService.charge({
        amount: input.amount,
        cardToken: input.cardToken,
        customerId: state.memory.customerId,
      });

      // Update state directly by reference
      state.memory.lastPaymentId = result.transactionId;

      return {
        result: `Payment successful: ${result.transactionId}`,
      };
    } catch (err) {
      logger.error({
        message: 'Payment failed',
        sessionId: state.sessionId,
        err,
      });

      // Update state directly by reference
      state.memory.paymentError = err.message;

      return {
        result: 'Payment failed. Please try again or contact support.',
      };
    }
  },
};
```

## Flow Control with goto

Tools can control the flow by setting the `goto` property directly on the state object. This allows tools to programmatically jump to a specific node by setting `state.goto` to the target node ID. This is useful for dynamic flow control based on runtime conditions.

### Basic goto Usage

```ts
const moveToAnotherRepresentativeTool: Tool<typeof schema, Memory> = {
  name: 'moveToAnotherRepresentative',
  description: 'Transfer the conversation to another representative or department',
  input: schema,
  execute: async ({ input, state }) => {
    logger.info({
      message: 'Moving conversation to another representative',
      sessionId: state.sessionId,
      department: input.department,
      reason: input.reason,
      priority: input.priority,
    });

    // Update state directly by reference (v2.0 change)
    state.goto = 'share-new-representative';
    state.memory.orderId = 'share-new-representative';

    return {};
  },
};
```

### Dynamic Routing Example

```ts
const routingTool: Tool<typeof routingSchema, Memory> = {
  name: 'routingTool',
  description: 'Route conversation based on urgency',
  input: routingSchema,
  execute: async ({ input, state }) => {
    // Perform logic to determine routing
    if (input.condition === 'urgent') {
      // Jump to urgent handler node by updating state directly
      state.goto = 'urgent-handler-node';

      return {
        result: 'Routing to urgent handler',
      };
    }

    // Normal flow continues
    return { result: 'Continue normal flow' };
  },
};
```

> **Important:** The jump occurs only when the current invocation completes and the next one begins. The agent will finish executing the current node/tool before jumping to the specified node.

## Best Practices

1. **Clear Descriptions**: Write descriptions that help the LLM understand when to use the tool
2. **Input Validation**: Use Zod schemas to validate all inputs
3. **State Updates**: Update state directly by reference - modify only the parts that need to change
4. **Error Handling**: Always handle errors gracefully and provide meaningful messages
5. **Async Operations**: Use async/await for external API calls and database operations
6. **Logging**: Use the provided logger for consistent, structured logging with session context

## Tool Nodes

Tool nodes force execution of specific tools at defined points in your flow, bypassing LLM decision-making.

### Configuration

```yaml
nodes:
  - name: 'Force Refund Processing'
    type: 'tool'
    toolName: 'refundOrder'
```

### Overriding Tool Input Parameters

Tool input parameters' values are inferred by an LLM during runtime. You can override specific input parameters directly in your flow YAML to ensure deterministic values. Parameters support placeholder syntax to inject dynamic values from memory, environment variables, tool outputs, and system values. See [Context](https://docs.minded.com/nodes#context) for comprehensive details.

#### Available Placeholders

* **Memory**: `{state.memory.propertyName}` or `{memory.propertyName}`
* **Tool outputs**: `{tools.NodeName.field}`
* **Environment variables**: `{env.VARIABLE_NAME}`
* **System values**: `{system.key}`

#### Example

```yaml
nodes:
  - name: 'Ship Order'
    type: 'tool'
    toolName: 'createShipment'
    parameters:
      # Simple string values
      recipient: '{state.memory.customerEmail}'
      # Static values
      priority: 'high'
      # Nested objects
      shippingAddress: '{memory.customer.address}'
      # Tool outputs
      customerTier: '{tools.lookupCustomer.tier}'
      # Environment variables
      apiEndpoint: '{env.SHIPPING_API_URL}'
      # System values
      timestamp: '{system.currentTime}'
```

**Note**: Complex objects and arrays are passed to tools as native objects, not as JSON strings.

## See Also

* [RPA Tools Documentation](https://docs.minded.com/low-code-editor/rpa-tools) - RPA tool development guide
* [Node Types](https://docs.minded.com/low-code-editor/nodes) - Using tools in flow nodes
