# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.minded.com/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# Introduction

Welcome to Minded Docs 👋

Minded is an AI agent development platform designed for rapidly building, deploying, and scaling production-grade agents.

It brings developers, product teams, and business users together to accelerate AI agent creation by up to 10x.

* **Generate with AI**: Prompt Copilot, our AI assistant, to quickly generate sophisticated AI agents. Copilot seamlessly integrates with your existing systems, APIs, and workflows, ensuring every agent is production-ready from the start.
* **Build Visually**: Easily refine agents and workflows with our intuitive visual editor. Drag-and-drop to create, configure, and test your agents without writing code.
* **Extend with Code**: Customize and extend agents by editing code directly. Enjoy two-way synchronization with GitHub, so your changes stay seamlessly integrated across visual and code-based workflows.
* **Manage Centrally**: Control and manage your agents with built-in governance, including granular access controls, version control, audit logs, and comprehensive monitoring—all accessible from a single unified platform.

Let's dive in and start building powerful AI agents with Minded!

***

## ✨ Why MindedJS?

* **SDK with no-code platform** – Define Agent as Code or use the visual no-code editor on top.

  <figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-d49168904004742ebdb9ac2a0f191e1f3d3a0e1b%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

## 🚀 Get Started

Ready to build your first AI agent? Start with our [Quick Start Guide](/getting-started/quick-start) to create a working agent in minutes.

### What You'll Learn

1. [**Installation**](/getting-started/installation) - Set up MindedJS in your project
2. [**Quick Start**](/getting-started/quick-start) - Build your first refund processing agent
3. [**Core Concepts**](/low-code-editor/flows) - Understand flows, nodes, edges, and memory
4. [**Your First Eval**](/resources/your-first-eval) - Solve your first eval

### Key Concepts

* [**Flows**](/low-code-editor/flows) - Directed graphs of nodes connected by edges
* [**Nodes**](/low-code-editor/nodes) - Processing units (triggers, prompts, tools, junctions)
* [**Edges**](/low-code-editor/edges) - Routing logic between nodes
* [**Tools**](/low-code-editor/tools) - Strongly-typed functions for LLMs to call
* [**Memory**](/sdk/memory) - Type-safe state management across your flow

***

## 📚 Documentation

This documentation is organized to help you learn progressively:

* **New to MindedJS?** Start with [Installation](/getting-started/installation) and [Quick Start](/getting-started/quick-start)
* **Understanding concepts?** Explore [Core Concepts](/low-code-editor/flows)
* **Ready to build?** Check out [Implementation Examples](https://github.com/minded-ai/mindedjs/blob/main/docs/implementation-examples/node-examples.md)
* **Need reference?** Browse the [API Reference](https://github.com/minded-ai/mindedjs/blob/main/docs/api-reference/agent-class.md)

Let's build something amazing! 🚀


# Installation

Get MindedJS up and running in your project.

## Requirements

* Node.js 18 or later
* npm, yarn, or pnpm

## Install MindedJS

{% tabs %}
{% tab title="npm" %}

```bash
npm i @minded-ai/mindedjs langchain
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add @minded-ai/mindedjs langchain
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add @minded-ai/mindedjs langchain
```

{% endtab %}
{% endtabs %}

## Next Steps

Now that MindedJS is installed:

1. Configure your environment variables in the [Environment Configuration](/getting-started/environment-configuration) guide
2. Set up your project with `minded.json` in the [Project Configuration](/getting-started/project-configuration) guide
3. Build your first agent with the [Quick Start](/getting-started/quick-start) guide!


# Environment Configuration

Configure your environment variables to customize MindedJS behavior and connect to external services.

## Environment Variables

Create a `.env` file in your project root to configure MindedJS:

```bash
# .env file
```

#### `BROWSER_TASK_MODE`

Controls the mode in which the browser task runs.

```bash
BROWSER_TASK_MODE=cloud
```

* **`onPrem`**: Browser task runs on the customer's own infrastructure with a remotely installed on-premise kit.
* **`localRun`**: Browser task runs locally where the SDK is running. Requires configuring a UV project and installing dependencies.
* **`cloud`**: Browser task runs on the Minded platform. This is the default mode.

### Platform Connection

#### `MINDED_CONNECTION_TOKEN`

Required to connect to the Minded platform for advanced features and monitoring. Each token is specific to the agent you are working on.

```bash
MINDED_CONNECTION_TOKEN=your-minded-connection-token
```

{% hint style="info" %}
Your connection token is available on the [Minded platform](https://app.minded.com) in the agent page within the GitHub integration popup. You can also use the npx command provided there to automatically set the minded token.
{% endhint %}

## Example .env File

Here's a complete example of a `.env` file:

```bash
# Core Configuration

# Platform Connection
MINDED_CONNECTION_TOKEN=your-minded-connection-token-here
```

## Loading Environment Variables

MindedJS automatically loads environment variables from your `.env` file. Make sure to:

1. Create the `.env` file in your project root
2. Add `.env` to your `.gitignore` file to keep credentials secure
3. Never commit your `.env` file to version control

```bash
# Add to .gitignore
.env
```

## Runtime Environment Detection

MindedJS uses specific environment variables to determine the runtime environment and adjust behavior accordingly.

### Environment Variables

| Environment        | NODE\_ENV     | MINDED\_CLOUD | Description                          |
| ------------------ | ------------- | ------------- | ------------------------------------ |
| Local Development  | `development` | `false`       | Running locally on developer machine |
| Minded Platform    | `development` | `true`        | Running in Minded platform sandbox   |
| Staging Service    | `staging`     | `true`        | Agent deployed as staging service    |
| Production Service | `production`  | `true`        | Agent deployed as production service |

### Usage in Code

```typescript
// Check if running on Minded Cloud
if (process.env.MINDED_CLOUD === 'true') {
  // Running on Minded infrastructure (dev/staging/prod)
  logger.info({ message: 'Running on Minded Cloud' });
}

// Check specific environment
if (process.env.NODE_ENV === 'production' && process.env.MINDED_CLOUD === 'true') {
  // Production environment on Minded Cloud
  logger.info({ message: 'Production mode' });
}
```

### Environment-Specific Behavior

```typescript
const isDevelopment = process.env.NODE_ENV === 'development';
const isMindedCloud = process.env.MINDED_CLOUD === 'true';

// Enable debug features only in development
if (isDevelopment) {
  // Enable verbose logging
  logger.setLevel('debug');
}

// Use different endpoints based on environment
const apiEndpoint = isMindedCloud ? 'https://api.minded.com' : 'http://localhost:3000';
```

## Next Steps

With your environment configured, continue to:

1. [Project Configuration](/getting-started/project-configuration) - Set up your `minded.json` file
2. [Quick Start](/getting-started/quick-start) - Build your first agent


# Project Configuration

Configure your MindedJS agent using the `minded.json` file to define how your agent operates, which flows to use, and deployment settings.

## minded.json Overview

The `minded.json` file is the central configuration file for your MindedJS agent. It defines:

* **Flows**: Paths to your flow definition files
* **Tools**: Paths to your tool implementations (optional)
* **LLM Configuration**: Which language model to use and its settings
* **Agent Path**: Where your agent is exported (for deployment only)

This file should be placed in your project root directory.

## Configuration Structure

```json
{
  "flows": ["./flows"],
  "tools": ["./tools"],
  "agent": "./agent.ts",
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-5.2",
      "temperature": 0.7
    }
  }
}
```

## Configuration Fields

### flows (required)

An array of paths to directories containing your flow YAML files.

```json
{
  "flows": ["./flows", "./custom-flows"]
}
```

The agent will load all `.yaml` and `.yml` files from these directories.

### tools (optional)

An array of paths to directories containing your tool implementations.

```json
{
  "tools": ["./tools"]
}
```

If not specified, tools should be passed directly to the Agent constructor.

### agent (deployment only)

The path to your main agent file where the agent is exported as default. This field is used by the Minded platform during deployment and is not required for local development.

```json
{
  "agent": "./agent.ts"
}
```

{% hint style="info" %}
The `agent` field is only used during deployment to the Minded platform. It tells the platform where to find your agent's default export. This field is not used by the Agent class at runtime.
{% endhint %}

### llm (required)

Configures the language model for your agent.

```json
{
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7,
      "maxTokens": 2000
    }
  }
}
```

#### Supported LLM Providers

**ChatOpenAI**

```json
{
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-5.2",
      "temperature": 0.7
    }
  }
}
```

**AzureChatOpenAI**

```json
{
  "llm": {
    "name": "AzureChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7
    }
  }
}
```

## Usage in Your Agent

The `minded.json` configuration is passed directly to your Agent constructor:

```typescript
import { Agent } from '@minded-ai/mindedjs';
import memorySchema from './schema';
import tools from './tools';
import mindedConfig from './minded.json';

const agent = new Agent({
  memorySchema,
  config: mindedConfig,
  tools, // Optional if specified in minded.json
});
```

## Complete Example

Here's a full example of a `minded.json` file for a customer support agent:

```json
{
  "flows": ["./flows"],
  "tools": ["./tools"],
  "agent": "./customerSupportAgent.ts",
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7,
      "maxTokens": 1000,
      "topP": 0.9
    }
  }
}
```

Corresponding project structure:

```
your-project/
├── minded.json              # Agent configuration
├── customerSupportAgent.ts  # Main agent file (default export)
├── agentMemorySchema.ts               # Memory schema
├── flows/                  # Flow definitions
│   ├── mainFlow.yaml
│   └── escalationFlow.yaml
└── tools/                  # Tool implementations
    ├── lookupOrder.ts
    ├── processRefund.ts
    └── index.ts
```

## Environment-Specific Configuration

You can create environment-specific configurations:

```
your-project/
├── minded.json              # Default configuration
├── minded.development.json  # Development overrides
├── minded.production.json   # Production settings
└── minded.staging.json      # Staging configuration
```

Load the appropriate configuration based on your environment:

```typescript
const env = process.env.NODE_ENV || 'development';
const configPath = env === 'production' ? './minded.production.json' : './minded.json';
const mindedConfig = require(configPath);

const agent = new Agent({
  memorySchema,
  config: mindedConfig,
  tools,
});
```

## Next Steps

With your `minded.json` configured, you're ready to:

1. [Build your first agent](/getting-started/quick-start) - Follow the quick start guide
2. [Define your flows](/low-code-editor/flows) - Learn about flow structure
3. [Deploy to Minded platform](https://github.com/minded-ai/mindedjs/blob/main/docs/platform/deployment.md) - Deploy your agent


# Quick Start

Build your first MindedJS agent in minutes! We'll create a customer support agent that can process refunds.

## What You'll Build

A customer support agent for an ecommerce store:

* Greets customers and asks for order details
* Intelligently routes to refund processing when appropriate
* Processes refunds with business logic
* Maintains conversation state throughout

## Project Setup

First, create the necessary files in your project:

### 1. Agent Configuration

Create `minded.json` in your project root (see [Project Configuration](/getting-started/project-configuration) for detailed documentation):

```json
{
  "flows": ["./flows"],
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-5.2"
    }
  }
}
```

### 2. Flow Definition

Create `flows/refundFlow.yaml`:

```yaml
name: 'Order Refund Flow'
nodes:
  - type: 'trigger'
    triggerType: 'manual'
    name: 'CLI Trigger'
  - type: 'promptNode'
    name: 'Support Agent'
    prompt: "Hello! I'm your customer support agent. I can help you with refunds. Please provide your name, order ID, and describe your issue."
  - type: 'tool'
    name: 'Process Refund'
    toolName: 'processRefund'
edges:
  - source: 'CLI Trigger'
    target: 'Support Agent'
    type: 'stepForward'
  - source: 'Support Agent'
    target: 'Process Refund'
    type: 'promptCondition'
    prompt: 'The user is asking for a refund and provided their order details'
```

### 3. Memory Schema

Create `agentMemorySchema.ts`:

```ts
import { z } from 'zod';

export default z.object({
  customerName: z.string().optional(),
  orderId: z.string().optional(),
  issue: z.string().optional(),
});
```

### 4. Tools

Create `tools/processRefund.ts`:

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

type Memory = z.infer<typeof memorySchema>;

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

const processRefundTool: Tool<typeof schema, Memory> = {
  name: 'processRefund',
  description: 'Process a refund for the customer order',
  input: schema,
  execute: async ({ input, memory }) => {
    console.log('*Action: Processing refund*');
    console.log(`Refunding order ${input.orderId} for ${input.customerName}`);
    console.log(`Reason: ${input.reason}`);

    // Simulate refund processing
    const refundAmount = Math.floor(Math.random() * 100) + 20; // Random amount between $20-$120
    console.log(`Refund of $${memory.orderAmount} has been processed successfully!`);

    return {
      memory: {
        customerName: input.customerName,
        orderId: input.orderId,
        issue: `Refund processed - $${refundAmount}`,
      },
    };
  },
};

export default processRefundTool;
```

Create `tools/index.ts`:

```ts
import processRefundTool from './processRefund';

export default [processRefundTool];
```

### 5. Main Agent

Create `agent.ts`:

```ts
import { Agent } from 'mindedjs/src/agent';
import { HumanMessage } from '@langchain/core/messages';
import { events } from 'mindedjs/src/index';
import * as readline from 'readline';
import memorySchema from './schema';
import tools from './tools';
import config from './minded.json';

// 1. Create readline interface for interactive chat
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

// 2. Create the agent with config from minded.json
const agent = new Agent({
  memorySchema,
  config,
  tools,
});

// 3. Listen for AI messages
agent.on(events.AI_MESSAGE, async ({ message }) => {
  console.log('AI:', message);
  promptUser(); // Continue the conversation
});

// 4. Handle trigger events
agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
  if (triggerName === 'CLI Trigger') {
    return {
      memory: {},
      messages: [new HumanMessage(triggerBody)],
    };
  }
});

// 5. Function to prompt user for input
const promptUser = () => {
  rl.question('You: ', async (input) => {
    if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
      console.log('Goodbye!');
      rl.close();
      return;
    }

    console.log(); // Add spacing for readability

    // Send user message to agent
    await agent.invoke({
      triggerBody: input,
      triggerName: 'CLI Trigger',
    });
  });
};

// 6. Start the interactive session
console.log('🤖 Customer Support Agent is ready!');
console.log('Type your message below (or "exit" to quit):\n');
promptUser();
```

## Project Structure

Your project should look like this:

```
your-project/
├── minded.json          # Agent configuration
├── schema.ts           # Memory schema definition
├── agent.ts            # Main agent file
├── flows/              # Flow definitions
│   └── refundFlow.yaml  # Individual flow files
└── tools/              # Tools directory
    ├── processRefund.ts # Refund processing tool
    └── index.ts        # Export your tools
```

## Run Your Agent

Start the interactive chat session:

```bash
npx ts-node agent.ts
```

This will start an interactive conversation where you can chat with your agent:

```
🤖 Customer Support Agent is ready!
Type your message below (or "exit" to quit):

You: Hi, I want to refund my order #1234. I received the wrong item.

AI: Hello! I'm your customer support agent. I can help you with refunds. Please provide your name, order ID, and describe your issue.

You: My name is John Smith, order ID is 1234, and I received the wrong size shoes.

*Action: Processing refund*
Refunding order 1234 for John Smith
Reason: received the wrong size shoes
Refund of $87 has been processed successfully!

You: Thank you!

AI: You're welcome! Your refund has been processed successfully. Is there anything else I can help you with?

You: exit
Goodbye!
```

**Tips:**

* Type `exit` or `quit` to end the conversation
* Each message maintains conversation context and memory
* The agent will intelligently route to refund processing when appropriate

## What Happens

1. **Trigger Activation**: The manual trigger receives your input message
2. **Support Agent**: The prompt node greets the customer and asks for details
3. **Intelligent Routing**: If the customer mentions refunds and provides order details, the flow routes to the refund tool
4. **Refund Processing**: The tool processes the refund and updates memory
5. **State Management**: Memory persists customer information throughout the conversation

## Next Steps

Now that you have a working agent, explore:

* [**Core Concepts**](/low-code-editor/flows) - Understand how flows, nodes, and edges work
* [**Node Types**](/low-code-editor/nodes) - Learn about all available node types
* [**Edge Types**](/low-code-editor/edges) - Learn about all available edge types
* [**Tools**](/low-code-editor/tools) - Learn about all available tools


# Flows

Flows are the core orchestration mechanism in MindedJS that define how your AI agent processes conversations. A flow represents a complete conversation pathway from trigger to completion, consisting of nodes (processing units), edges (routing logic), and memory (shared state).

## Flow Structure

Every flow follows this YAML structure:

```yaml
id: flow-id                    # Unique flow identifier
name: Flow Name                # Human-readable flow name
nodes:                         # Array of processing nodes
  - type: trigger
    name: node-name
    displayName: Node Name
    # ... node-specific properties

edges:                         # Array of routing connections
  - source: source-node-name
    target: target-node-name
    type: stepForward
    # ... edge-specific properties
```

### Flow Properties

* `id` (string, required): Unique identifier for the flow
* `name` (string, required): Human-readable name for the flow
* `nodes` (array, required): Array of node definitions
* `edges` (array, required): Array of edge definitions connecting nodes

## Flow Components

| Component                           | Purpose    | Description                                          |
| ----------------------------------- | ---------- | ---------------------------------------------------- |
| [**Nodes**](/low-code-editor/nodes) | Processing | Handle specific tasks like LLM calls, tool execution |
| [**Edges**](/low-code-editor/edges) | Routing    | Define transitions and decision logic between nodes  |
| [**Memory**](/sdk/memory)           | State      | Shared context that persists throughout the flow     |

## Example

```yaml
id: customer-support-flow
name: Customer Support Flow
nodes:
  - type: trigger
    triggerType: webhook
    name: customer-request
    displayName: Customer Request

  - type: promptNode
    name: support-agent
    displayName: Support Agent
    prompt: |
      You are a helpful customer support agent.
      Assist the customer with their inquiry.

edges:
  - source: customer-request
    target: support-agent
    type: stepForward
```


# Nodes

Nodes are the building blocks of MindedJS flows - discrete processing units that handle different aspects of your agent's workflow.

## Common Node Properties

All nodes require these fields:

* `name` (string, required): Unique identifier for the node
* `type` (NodeType, required): The node type (see table below)
* `displayName` (string, required): Human-readable label shown in the editor

## Node Types Overview

| Node Type (`NodeType`)               | Purpose        | Description                              |
| ------------------------------------ | -------------- | ---------------------------------------- |
| [`trigger`](#trigger-nodes)          | Entry Points   | Start flows from various sources         |
| [`promptNode`](#prompt-nodes)        | LLM Processing | Generate responses using language models |
| [`tool`](#tool-nodes)                | Actions        | Execute external functions and APIs      |
| [`junction`](#junction-nodes)        | Flow Control   | Route and organize flow logic            |
| [`jumpToNode`](#jump-to-nodes)       | Flow Control   | Jump to specific nodes or subflows       |
| [`browserTask`](#browser-task-nodes) | Automation     | AI-powered web page interactions         |

## Trigger Nodes

Trigger nodes are entry points that start your flows and initialize them with memory and messages.

### Trigger Nodes Properties

* `type` (NodeType, required): Always `trigger`
* `triggerType` (string, required): The type of trigger to use. See the table below for details.

| `triggerType` | Description                                                     |
| ------------- | --------------------------------------------------------------- |
| `webhook`     | Triggered by HTTP webhook calls                                 |
| `app`         | Triggered by external app events (e.g., Zendesk ticket created) |
| `schedule`    | Triggered on a cron schedule                                    |

#### Webhook Trigger (`webhook`)

Triggered by HTTP requests to the agent's webhook endpoint.

**Properties**:

* `autoTrigger` (boolean, optional): Auto-execute when testing flows in the UI editor
* `defaultPayload` (string, optional): Default JSON payload for testing in the UI editor

```yaml
- type: trigger
  triggerType: webhook
  name: payment-webhook
  displayName: Payment Webhook
  autoTrigger: true
  defaultPayload: '{"orderId": "123"}'
```

#### App Trigger (`app`)

Triggered by external application events.

```yaml
- type: trigger
  triggerType: app
  name: zendesk-ticket-created
  displayName: Zendesk Ticket Created
  appTriggerId: zendesk-new-ticket
```

#### Schedule Trigger (`schedule`)

Triggered on a cron schedule.

**Properties**:

* `cronExpression` (string, required): Cron expression (e.g., `0 9 * * *` for daily at 9 AM)
* `timezone` (string, optional): Timezone for the schedule (defaults to UTC)

```yaml
- type: trigger
  triggerType: schedule
  name: daily-report
  displayName: Daily Report
  cronExpression: '0 9 * * *'
```

### Trigger Implementation

Handle triggers in your agent code:

```ts
import { Agent, AgentEvents } from '@minded-ai/mindedjs';

const agent = new Agent({ /* agent options... */ });

agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody, state }) => {
  if (triggerName === 'payment-webhook' && triggerBody.body?.orderId) {
    state.memory.orderId = triggerBody.body.orderId;
    return { isQualified: true };
  }
  
  // Return { isQualified: false } to prevent flow execution
  return { isQualified: false };
});

export default agent;
```

## Prompt Nodes

Prompt nodes process input through LLM to generate intelligent responses or invoke tools. Prompt nodes are commonly used to notify and collect information.

### Use Cases

* Notify the user about the result of a previous node.
* Collect information from the user.
* Invoke (scoped) tools.

### Prompt Nodes Properties

* `type`: (NodeType, required) Always `promptNode`
* `prompt` (string, required): The prompt to be sent to the LLM. The prompt is an instruction for what AI message to generate or a (scoped) tool to invoke. This is not general instruction for what the agent should do (e.g. "wait for x").
* `humanInTheLoop` (boolean, optional): When `true`, pauses execution after this node for human input before continuing. Use this property only if you need to gather an input from the user. Usually combined with a `promptCondition` edge to allow for iterative processing over human input.
* `canStayOnNode` (boolean, optional): When `true`, allows the node to route back to itself for iterative processing. Usually combined with `humanInTheLoop: true` to allow for iteration over human input.
* `llmConfig` (boolean, optional): Used to customize the LLM used by the prompt node. Leave empty to use the default LLM configured.
* `sendAiMessage` (boolean, optional): Default: `true`. When `true` emits `AgentEvents.AI_MESSAGE` event to send the response to the user. When `false`, the AI response is processed internally without sending to the user. Set to `false` for internal calculations, reasoning, or decision-making logic that shouldn't be visible to users.

```yaml
- type: promptNode
  name: generate-order-confirmation
  displayName: Generate Order Confirmation
  prompt: 'Generate Order Confirmation for {memory.customerName}'
  sendAiMessage: true
```

### Handle AI message (`sendAiMessage`)

Prompt nodes usually result in an AI message being sent to the user. Use the `AgentEvents.AI_MESSAGE` event to route the message according to user requirement (e.g., send email, send a Slack message, update Jira ticket, etc.).

```js
agent.on(AgentEvents.AI_MESSAGE, ({ message, state }) => {
  // Send message to Slack channel
  slackChat.postMessage({
    ...options,
    test: message
  });
});;
```

In development mode (playground), the message is automatically shown in the chat regardless of the message handler.

### Context

* Prompt nodes automatically have access to previous nodes' output as context.
* You can inject dynamic values into prompts using placeholders that reference memory, environment variables, tool outputs, and system values.

**Available Placeholders**

* [Memory](#memory-placeholders): `{state.memory.propertyName}` or `{memory.propertyName}`
* [Tool outputs](#toolnode-output-placeholders): `{tools.NodeName.propertyName}`
* [Environment variables](#environment-variable-placeholders): `{env.VARIABLE_NAME}`
* [System values](#system-placeholders): `{system.propertyName}`

#### Memory Placeholders

Access values from the agent's memory state using `{memory.propertyName}`:

```yaml
prompt: 'Send a confirmation email to {memory.customerName} that their order {memory.orderId} has been processed successfully'
```

**Nested objects:**

```yaml
prompt: 'Generate a shipping label for delivery to {memory.customer.address.city}, {memory.customer.address.country} and notify the customer'
```

**Array access:**

```yaml
prompt: 'Create a packing list including {memory.orderItems[0]} and {memory.orderItems[1]} with their quantities'
```

**Complex objects:**

When referencing entire objects or arrays, they will be serialized to JSON:

```yaml
prompt: 'Validate the following order data and check for any inconsistencies: {memory.orderDetails}'
# If orderDetails is {id: "123", total: 99.99}, the prompt interpolated as:
# Validate the following order data and check for any inconsistencies: {"id":"123","total":99.99}
```

#### Tool/Node Output Placeholders

Access output from previous tool or operator nodes using `{tools.NodeName.propertyName}`:

```yaml
# After a tool node named "lookup-customer" returns {email: "user@example.com", tier: "premium"}
prompt: 'Send a personalized welcome email to {tools.lookup-customer.email} with {tools.lookup-customer.tier} tier benefits'
```

**With operator/browser task nodes:**

```yaml
# After an operator node named "scrape-price" with outputSchema field "price"
prompt: 'Compare the scraped price of {tools.scrape-price.price} with our database and alert if there is a significant difference'
```

#### Environment Variable Placeholders

Access environment variables using `{env.VARIABLE_NAME}`:

```yaml
prompt: 'If the user needs help, direct them to contact support at {env.SUPPORT_EMAIL} or call {env.SUPPORT_PHONE}'
```

**Common use cases:**

```yaml
# API endpoints
prompt: 'Fetch the latest order status from {env.API_BASE_URL}/orders and summarize it for the customer'
```

```yaml
# Company information
prompt: 'Draft a professional response thanking the customer for choosing {env.COMPANY_NAME} and offer assistance'
```

#### System Placeholders

Access built-in system values using `{system.propertyName}`:

```yaml
prompt: 'Log this customer inquiry with timestamp {system.currentTime} and respond with an acknowledgment'
# The timestamp will be: 2024-01-15T10:30:00.000Z
```

#### Placeholder Behavior

* **String values**: Inserted directly into the prompt
* **Numbers and booleans**: Converted to strings
* **Objects and arrays**: Serialized to JSON format
* **Undefined values**: If a placeholder references a non-existent property, it returns the original placeholder unchanged (e.g., `{memory.nonexistent}` stays as `{memory.nonexistent}`)
* **Null values**: Rendered as `"null"`

#### Complete Example

```yaml
nodes:
  - type: promptNode
    name: order-confirmation
    displayName: Order Confirmation
    prompt: |
      Order Confirmation for {memory.customerName}
      
      Order Details:
      - Order ID: {memory.orderId}
      - Items: {memory.orderItems}
      - Total: {memory.orderTotal}
      - Customer Tier: {tools.lookup-customer.tier}
      - Shipping to: {memory.shippingAddress.city}
      
      Support: {env.SUPPORT_EMAIL}
      Processed at: {system.currentTime}
```

### Limitations

* Prompt nodes can not save information to memory directly. Rather the user & agent messages are stored in the messages array of the state object. Making user's input available to the next prompt nodes or tools in the form of input schema.
* Image recognition is only supported when a user attaches an image to the message. If you need to extract information from an image, use a tool with agent.llm to process the image. Return the result in the tool response so following prompt nodes can use it.
* By default, prompt nodes send a message to the user. Set `sendAiMessage: false` if you want internal processing without user-facing output.

### Prompt examples

* "Draft a professional email to the merchant requesting verification of the dispute"
* "Ask the user for their contact information (name, email, phone)"

### Common Prompt Patterns

#### Extract Information from User

Use `humanInTheLoop: true` and `canStayOnNode: true` with `promptCondition` edges for iterative information gathering:

```yaml
nodes:
  - type: promptNode
    name: collect-customer-info
    displayName: Collect Customer Information
    prompt: 'Ask the user for their contact information (name, email, phone).'
    humanInTheLoop: true
    canStayOnNode: true

edges:
  - type: promptCondition
    source: collect-customer-info
    target: validate-info
    prompt: 'Has all required information been collected?'
```

#### Notify User

Use standard prompt nodes with `stepForward` edges for one-way notifications:

```yaml
nodes:
  - type: promptNode
    name: send-confirmation
    displayName: Send Confirmation
    prompt: 'Send confirmation: Order {memory.orderId}, Total: {memory.orderTotal}'

edges:
  - type: stepForward
    source: process-order
    target: send-confirmation
```

### Custom LLM Configuration

Provide a custom LLM configuration to the prompt node using the `llmConfig` property with the following fields:

* `name`: (required) name of the LLM to use, must be one of `ChatOpenAI`, `AzureChatOpenAI`, `MindedChatOpenAI`.
* `properties`: (optional) LLM configuration properties

```yaml
nodes:
  - type: promptNode
    name: send-confirmation
    displayName: Send Confirmation
    prompt: 'Send confirmation: Order {memory.orderId}, Total: {memory.orderTotal}'
    llmConfig:
      name: ChatOpenAI
      properties:
        model: gpt-5.2
        temperature: 0
        max_tokens: 500
```

### Internal Processing (No User Message)

To process logic internally without sending a message to the user, set `sendAiMessage: false`:

```yaml
- type: promptNode
  name: calculate-refund-amount
  displayName: Calculate Refund Amount
  prompt: 'Based on the order details: {memory.orderItems}, calculate the appropriate refund amount.'
  sendAiMessage: false
```

Use cases for `sendAiMessage: false`:

* **Internal calculations and reasoning**: Determining refund amounts, calculating discounts, analyzing data
* **Decision-making logic**: Evaluating conditions before taking actions
* **Information extraction**: Processing and structuring data for subsequent nodes
* **Pre-processing**: Preparing inputs for tools or other nodes

The AI response is added to conversation history and available to subsequent nodes, but not sent to the user.

## Tool Nodes

Tool nodes execute functions to perform actions like API calls, database queries, or external integrations.

### Tool Nodes Properties

* `type` (NodeType, required): Always `tool`
* `toolName` (string, required): The name of the registered tool function to execute. Must match a tool defined in your agent's tools configuration.
* `prompt` (string, optional): The prompt to be sent to the LLM to hint how tool parameters should be inferred.
* `parameters` (object, optional): The parameters to pass to the tool function. Tool parameters values are inferred by an LLM during runtime. You can override specific input parameters directly to ensure deterministic values. Parameters support placeholder syntax to inject dynamic values from memory, environment variables, other tool outputs, and system values. See [Context](#context) for more details.

```yaml
- type: tool
  name: lookup-customer-order
  displayName: Lookup Customer Order
  toolName: lookupOrder
  parameters:
    orderId: '{tools.lookup-customer.orderId}'
```

### Tool Implementation

Tools are registered in your agent code. See [Tools](/low-code-editor/tools) for implementation details.

## Junction Nodes

Junction nodes provide flow control without processing, useful for organizing routing logic.

### Basic Junction

```yaml
- type: junction
  name: customer-routing-hub
  displayName: Customer Routing Hub
```

### Routing Example

```yaml
nodes:
  - type: junction
    name: route-customer-request
    displayName: Route Customer Request

edges:
  - source: route-customer-request
    target: vip-support
    type: logicalCondition
    condition: "state.memory.customerTier === 'premium'"
  - source: route-customer-request
    target: standard-support
    type: logicalCondition
    condition: "state.memory.customerTier === 'standard'"
```

## Jump To Nodes

Jump To nodes provide direct navigation to specific nodes, enabling flow transitions and subflow execution.

### Jump To Node Properties

* `type` (NodeType, required): Always `jumpToNode`
* `targetNodeId` (string, required) The `name` of the node to jump to. The target node is resolved across all loaded flows at runtime. If the target node doesn't exist, the flow will error.

### Basic Jump To Node

```yaml
nodes:
  - type: jumpToNode
    name: go-to-order-processing
    displayName: Go To Order Processing
    targetNodeId: process-customer-order
```

### Cross-Flow Navigation

Jump To nodes enable navigation between flows. Target nodes can exist in different flow files, and state is preserved during jumps:

```yaml
# flows/mainFlow.yaml
nodes:
  - type: jumpToNode
    name: go-to-refund-flow
    displayName: Go To Refund Flow
    targetNodeId: refund-processor  # resolves to node in any loaded flow
```

```yaml
# flows/refundFlow.yaml
nodes:
  - type: tool
    name: refund-processor
    displayName: Refund Processor
    toolName: refundOrder
```

## Browser Task Nodes

Browser task nodes allow your agent to interact with web pages using AI-powered automation.

### Browser Task Properties

* `type` (NodeType, required): Always `browserTask`
* `prompt` (string, required): The prompt to be sent to the LLM to instruct the browser task.

### Basic Browser Task

```yaml
- type: browserTask
  name: search-products
  displayName: Search Products
  prompt: 'Go to amazon.com and search for wireless headphones under $100'
```

### Custom Model

Specify which AI model to use (default: `gpt-5.2`):

```yaml
- type: browserTask
  name: complex-task
  displayName: Complex Web Task
  prompt: 'Navigate to the support page and fill out the contact form'
  model: 'gpt-5.2'
```

## Best Practices

* **Use descriptive names**: `technical-support-specialist` not `agent-1`
* **Keep prompts focused**: Define specific roles and context, avoid overly broad prompts
* **Always include `displayName`**: Required for prompt nodes, recommended for all nodes

## Next Steps

* [**Edges**](/low-code-editor/edges) - Connect nodes with intelligent routing
* [**Tools**](/low-code-editor/tools) - Build powerful tool functions
* [**Memory Types**](https://github.com/minded-ai/mindedjs/blob/main/docs/platform/memory.md) - Design effective state management


# Edges

Edges are the **routing logic** that connects nodes in your MindedJS flows. They determine how your agent moves through the workflow, making intelligent decisions about which path to take based on different conditions and contexts.

## What are Edges?

Edges define the **transitions between nodes** in your flow. Each edge specifies:

* **Source** - The starting node
* **Target** - The destination node
* **Type** - The routing logic type
* **Condition** - The criteria for taking this path
* **Prompt** - The prompt for the LLM to make a decision

## Edge Types

MindedJS provides three types of edges for different routing scenarios:

| Edge Type                                         | Purpose       | When to Use                                      |
| ------------------------------------------------- | ------------- | ------------------------------------------------ |
| [**Step Forward**](#step-forward-edges)           | Unconditional | Sequential processing, guaranteed transitions    |
| [**Prompt Condition**](#prompt-condition-edges)   | LLM-based     | Intelligent routing based on context and content |
| [**Logical Condition**](#logical-condition-edges) | Code-based    | Deterministic routing based on memory/data       |

## Step Forward Edges

Step forward edges create **unconditional transitions** between nodes. They're useful for sequential processing where you always want to move to the next step.

```yaml
- source: Customer Support Request
  target: Support Agent
  type: stepForward
```

**Use Cases:** Data transformation pipelines, required validation steps, guaranteed notifications, logging and audit trails.

## Prompt Condition Edges

Prompt condition edges use **language models** to make intelligent routing decisions based on conversation context, memory state, and complex reasoning.

```yaml
- source: Support Agent
  target: Escalate to Human
  type: promptCondition
  prompt: 'Based on the conversation, should this be escalated to a human agent?'
```

### Context-Aware Routing

```yaml
- source: Customer Service Bot
  target: VIP Support
  type: promptCondition
  prompt: |
    Should this customer be routed to VIP support?

    Consider:
    - Customer tier
    - Issue severity 
    - Previous escalations
    - Current sentiment: (analyze from conversation)
```

### Multi-Path Routing

```yaml
# Route to different specialists based on issue type
- source: Issue Classifier
  target: Technical Support
  type: promptCondition
  prompt: 'Is this a technical/software issue requiring troubleshooting?'

- source: Issue Classifier
  target: Billing Support
  type: promptCondition
  prompt: 'Is this a billing, payment, or account-related issue?'

- source: Issue Classifier
  target: General Support
  type: promptCondition
  prompt: "Is this a general inquiry that doesn't fit other categories?"
```

## Logical Condition Edges

Logical condition edges use **JavaScript expressions** to make deterministic routing decisions based on memory state, providing precise control over flow logic. Prefer using prompt condition edges, unless you need an exact match of an ouput of a previous node.

```yaml
- source: Order Lookup
  target: Refund Processing
  type: logicalCondition
  condition: "state.memory.orderStatus === 'delivered'"
```

### How Condition Expressions Work

The condition field accepts a **JavaScript expression** that will be evaluated to determine if the edge should be taken. The expression has access to the current agent state through the `state` variable and the previous node's result through the `lastNodeResult` variable.

The condition is automatically wrapped in a function that provides the state and lastNodeResult result context:

```javascript
// Your condition: state.memory.orderStatus === 'delivered'
// Becomes:
(function ({ state, lastNodeResult }) {
  return state.memory.orderStatus === 'delivered';
});
```

```javascript
// Your condition: lastNodeResult.price > 100
// Becomes:
(function ({ state, lastNodeResult }) {
  return lastNodeResult.price > 100;
});
```

### Supported Expression Types

#### Simple Comparisons

```yaml
# Equality check
condition: "state.memory.status === 'active'"

# Numeric comparison
condition: "state.memory.orderValue > 1000"

# String comparison
condition: "state.memory.customerTier !== 'basic'"

# Boolean check
condition: "state.memory.isVerified"
condition: "!state.memory.hasErrors"
```

#### Complex Expressions

```yaml
# Multiple conditions with AND
condition: "state.memory.orderValue > 500 && state.memory.customerAge < 30"

# OR conditions
condition: "state.memory.priority === 'high' || state.memory.customerTier === 'vip'"

# Nested property access
condition: "state.memory.order.items.length > 5"

# Array methods
condition: "state.memory.tags.includes('urgent')"

# String methods
condition: "state.memory.email.endsWith('@company.com')"

# Combining state and lastNodeResult
condition: "lastNodeResult.status === 'success' && state.memory.retryCount === 0"

# lastNodeResult result with fallback
condition: "(lastNodeResult?.productName || state.memory.defaultProduct) === 'iPhone 15'"
```

#### Advanced JavaScript Operations

```yaml
# Ternary operators
condition: "state.memory.score > 80 ? state.memory.level === 'expert' : state.memory.level === 'beginner'"

# Date comparisons
condition: "new Date(state.memory.orderDate) < new Date()"

# Regular expressions
condition: "/^[A-Z]{2}\\d{4}$/.test(state.memory.orderId)"

# Math operations
condition: "Math.abs(state.memory.temperature - 72) < 5"

# JSON operations
condition: "JSON.parse(state.memory.configString).enabled === true"
```

### Available Context

Your condition expressions have access to:

* **state**: The full agent state object (access as `state.memory`, `state.messages`, etc.)
* **lastNodeResult**: The result from the previous node in the flow
* **Standard JavaScript globals**: `Math`, `Date`, `JSON`, `console`
* **String/Array/Object methods**: All standard JavaScript methods

#### Accessing State Properties

```yaml
# Access memory through state
condition: "state.memory.orderStatus === 'pending'"

# Access messages
condition: "state.messages.length > 10"

# Access other state properties
condition: "state.conversationId && state.memory.isAuthenticated"
```

#### Accessing Previous Node Results

The `lastNodeResult` object provides access to the result of the last executed node in the flow. This is particularly useful when you need to route based on what the previous operator or tool returned.

```yaml
# Route based on operator output
- source: Search Products
  target: Show Results
  type: logicalCondition
  condition: 'lastNodeResult && lastNodeResult.productCount > 0'

# Route based on tool result
- source: Validate Order
  target: Process Payment
  type: logicalCondition
  condition: "lastNodeResult.status === 'valid'"

# Check specific fields from operator outputSchema
- source: Extract Customer Data
  target: VIP Handler
  type: logicalCondition
  condition: "lastNodeResult.customerTier === 'premium'"
```

**How `lastNodeResult` Works**

MindedJS automatically extracts and normalizes results from different node types:

* **Operator (Browser Task) nodes**: Extracts from `outputSchema` fields
* **Tool nodes**: Extracts the `result` property returned by the tool
* **RPA nodes**: Extracts collected data from RPA steps
* **App Tool nodes**: Extracts the tool execution result

**Example with Operator:**

```yaml
# Operator node with outputSchema
- name: search-amazon
  type: browserTask
  displayName: Search Amazon
  prompt: Search for iPhone 15 on Amazon and extract the price
  outputSchema:
    - name: productName
      type: string
    - name: price
      type: number

# Next node with logical condition using lastNodeResult
- source: search-amazon
  target: High Price Handler
  type: logicalCondition
  condition: 'lastNodeResult.price > 1000'

- source: search-amazon
  target: Low Price Handler
  type: logicalCondition
  condition: 'lastNodeResult.price <= 1000'
```

**Example with Tool:**

```typescript
// Tool that returns a result
const validateOrderTool: Tool = {
  name: 'validateOrder',
  execute: async ({ input, state }) => {
    return {
      result: {
        status: 'valid',
        orderId: '12345',
        total: 299.99,
      },
    };
  },
};
```

```yaml
# Route based on tool result
- source: Validate Order Tool
  target: Process Payment
  type: logicalCondition
  condition: "lastNodeResult.status === 'valid' && lastNodeResult.total < 500"
```

**Safety Checks:**

Always check if `lastNodeResult` exists before accessing its properties:

```yaml
# Safe condition with null check
condition: "lastNodeResult && lastNodeResult.status === 'success'"

# Alternative with optional chaining syntax
condition: "lastNodeResult?.productName === 'iPhone 15'"
```

#### Example: Using lastNodeResult After a Tool

When a tool executes and returns a result, you can route based on that result using logical conditions. Here's a complete example with the `refundOrder` tool:

**Tool Implementation:**

```typescript
const refundOrderTool: Tool<typeof schema, Memory> = {
  name: 'refundOrder',
  description: 'Refund order',
  input: schema,
  execute: async ({ input, state }) => {
    return {
      result: {
        status: 'success', // or 'failure'
        refundAmount: 100,
      },
    };
  },
};
```

**Flow Configuration with Logical Conditions:**

```yaml
edges:
  - source: refundOrder
    target: Success
    type: logicalCondition
    condition: "lastNodeResult.status === 'success'"

  - source: refundOrder
    target: Failure
    type: logicalCondition
    condition: "else"
```

In this example, the tool returns a `result` object with a `status` property. The edges then route to different nodes based on whether the refund was successful or failed.

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-6797052a0088b72740d3c0bd20fd321672ece60d%2Fprevious-after-tool.png?alt=media" alt="Using lastNodeResult after a tool"><figcaption></figcaption></figure>

#### Example: Using lastNodeResult After an Operator

For Operator (Browser Task) nodes, you need to define the output schema first, then you can route based on the operator's result.

**Step 1: Configure Operator Output Schema**

In your operator node, add an output schema field to capture the result:

* **Field name**: `result`
* **Field type**: `Number`
* **Description**: `0 = failure, 1 = success`

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-d8e0d242948e2d51bc4dbe9c415c5f6f0846452a%2Foperator-setup.png?alt=media" alt="Operator setup with output schema"><figcaption></figcaption></figure>

**Step 2: Configure Edges with Logical Conditions**

```yaml
edges:
  - source: Operator
    target: Success
    type: logicalCondition
    condition: 'lastNodeResult.result === 1'

  - source: Operator
    target: Failure
    type: logicalCondition
    condition: 'lastNodeResult.result === 0'
```

The operator will extract the result value according to the prompt and output schema, and the edges will route based on whether the result is `1` (success) or `0` (failure).

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-df57c964b3ea72db58df14d3fdc7c9ccaf16f0ac%2Fprevious-after-operator.png?alt=media" alt="Using lastNodeResult after an operator"><figcaption></figcaption></figure>

**Note:** For security, the following are NOT available:

* `process`, `require`, `global`, `Buffer`
* File system or network operations
* Module imports

### Memory-Based Routing

```yaml
# Route based on customer tier
- source: Customer Routing
  target: Premium Support
  type: logicalCondition
  condition: "state.memory.customerTier === 'premium'"

# Route based on order value
- source: Order Processing
  target: Manual Review
  type: logicalCondition
  condition: 'state.memory.orderValue > 1000'
```

### Complex Logic Conditions

```yaml
# Multiple conditions with AND logic
- source: Payment Processor
  target: Fraud Check
  type: logicalCondition
  condition: |
    state.memory.orderValue > 500 && 
    state.memory.customerAge < 30 &&
    state.memory.shippingCountry !== state.memory.billingCountry

# Date-based conditions
- source: Order Validator
  target: Late Order Handler
  type: logicalCondition
  condition: |
    (Date.now() - new Date(state.memory.orderDate).getTime()) / (1000 * 60 * 60 * 24) > 30
```

### Debugging Logical Conditions

For comprehensive debugging of logical conditions, see the [Debugging Guide](/sdk/debugging#debugging-logical-conditions).

## Edge Evaluation Order

When multiple edges exist from the same source node, MindedJS evaluates them by **edge type priority**, not declaration order:

### Priority System

| Priority | Edge Type          | Behavior                                                |
| -------- | ------------------ | ------------------------------------------------------- |
| **1st**  | `stepForward`      | Always executes first (max 1 per source)                |
| **2nd**  | `logicalCondition` | Executes if no step forward edge                        |
| **3rd**  | `promptCondition`  | Executes if no step forward and logical conditions fail |

```yaml
# Priority-based evaluation regardless of declaration order
edges:
  - source: Decision Node
    target: AI Router # 3rd priority: Only if logical fails
    type: promptCondition
    prompt: 'Which handler should process this request?'

  - source: Decision Node
    target: Default Handler # 1st priority: Always executes
    type: stepForward

  - source: Decision Node
    target: High Priority # 2nd priority: Checked if no stepForward are present
    type: logicalCondition
    condition: "state.memory.priority === 'high'"
```

### Logical Condition Order

Within logical condition edges from the same source, evaluation follows **declaration order**:

```yaml
edges:
  # Logical conditions evaluated in declaration order: 1 -> 2 -> 3
  - source: Order Processor
    target: VIP Handler # 1st: Checked first
    type: logicalCondition
    condition: "state.memory.customerTier === 'vip'"

  - source: Order Processor
    target: Large Order # 2nd: Checked if VIP fails
    type: logicalCondition
    condition: 'state.memory.orderValue > 1000'

  - source: Order Processor
    target: Standard Handler # 3rd: Checked if both above fail
    type: logicalCondition
    condition: 'true' # Fallback condition
```

### Mixed Edge Example

```yaml
edges:
  # This stepForward will ALWAYS execute first, regardless of order
  - source: Payment Processor
    target: Success Page
    type: stepForward

  # These will never execute because stepForward takes priority
  - source: Payment Processor
    target: Error Handler
    type: logicalCondition
    condition: "state.memory.status === 'error'"

  - source: Payment Processor
    target: AI Router
    type: promptCondition
    prompt: 'Should we route to error handling?'
```

## Advanced Patterns

### Fallback Chains

When you have multiple `logicalCondition` edges from the same source, you can define a final fallback edge by using `condition: "else"`. This fallback edge is taken only if none of the previous conditions evaluate to `true`.

```yaml
# Try specific handlers first, fall back to general
- source: Issue Classifier
  target: Billing Specialist
  type: logicalCondition
  condition: "state.memory.issueType === 'billing'"

- source: Issue Classifier
  target: General Support
  type: logicalCondition
  condition: "else"
```

### Error Handling

```yaml
- source: Payment Processor
  target: Success Handler
  type: logicalCondition
  condition: "state.memory.paymentStatus === 'success'"

- source: Payment Processor
  target: Failure Handler
  type: logicalCondition
  condition: "state.memory.paymentStatus === 'failed' || state.memory.retryCount >= 3"
```

## Next Steps

* [**Tools**](/low-code-editor/tools) - Build powerful functions that your edges can route to
* [**Memory Types**](https://github.com/minded-ai/mindedjs/blob/main/docs/platform/memory.md) - Design state that supports effective routing
* [**Implementation Examples**](https://github.com/minded-ai/mindedjs/blob/main/docs/implementation-examples/edge-examples.md) - See complete edge examples

Master edges to create intelligent, responsive flows that adapt to any situation! 🔄


# 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](/low-code-editor/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](/low-code-editor/rpa-tools) - RPA tool development guide
* [Node Types](/low-code-editor/nodes) - Using tools in flow nodes


# Triggers

Triggers are the entry points into your agent's flows, handling incoming events and initializing conversations with context and memory.

## Trigger Types

MindedJS supports two types of triggers:

### 1. Webhook Triggers

Triggered by HTTP requests to specific endpoints in the Minded platform. Configured through the Minded platform interface. Each webhook trigger belongs to a specific agent and cannot be shared between agents.

> **Note**: All triggers are agent-specific and configured through the Minded platform.

#### Webhook Structure

For webhook triggers, `triggerBody` contains:

* **`body`**: The HTTP request body payload
* **`headers`**: The HTTP request headers

```typescript
// Webhook triggerBody structure
{
  body: any;        // Request payload
  headers: {        // Request headers
    'content-type': string;
    'x-forwarded-for': string;
    'user-agent': string;
    // ... other headers
  }
}
```

**Accessing headers:**

```typescript
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerBody }) => {
  const senderIP = triggerBody.headers?.['x-forwarded-for'];
  const contentType = triggerBody.headers?.['content-type'];
  return { isQualified: true };
});
```

**Common headers:** `x-forwarded-for`, `content-type`, `user-agent`, and custom headers.

#### Validate Webhook Input

```typescript
// ✅ Good - Input validation
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerBody, state }) => {
  if (triggerBody.headers?.authorization !== 'expected-token') {
    return { isQualified: false };
  }
  if (!triggerBody.body?.customerId || !triggerBody.body?.message) {
    return { isQualified: false };
  }
  return { isQualified: true };
});
```

#### Output Schema

Define an `outputSchema` on webhook triggers to automatically extract structured fields from the payload using LLM. Extracted fields are available via `{trigger.fieldName}` placeholders in prompts and parameters.

```yaml
# In your flow YAML
trigger:
  type: trigger
  triggerType: webhook
  outputSchema:
    - name: orderId
      type: string
      description: The order identifier
    - name: customerEmail
      type: string
      description: Customer email address
```

**Usage in prompts:**

```
Process order {trigger.orderId} for customer {trigger.customerEmail}
```

### 2. Schedule Triggers

Triggered automatically based on a cron schedule. Configured with a cron expression and optional timezone (defaults to UTC).

#### Cron Expression Format

**Examples:**

* `'0 0 * * *'` - Daily at midnight
* `'0 9 * * 1'` - Every Monday at 9:00 AM
* `'*/15 * * * *'` - Every 15 minutes
* `'0 12 * * 1-5'` - Every weekday at noon

#### Timezone Configuration

Use IANA timezone identifiers:

```typescript
{
  triggerType: TriggerType.SCHEDULE,
  cronExpression: '0 9 * * 1-5', // Weekdays at 9 AM
  timezone: 'America/New_York' // Optional, defaults to UTC
}
```

**Behavior:**

* Automatically deployed when agent is deployed
* Enabled when agent is "live", disabled otherwise
* Creates new session unless `sessionId` is provided

## Trigger Event

The `TRIGGER_EVENT` is emitted when any trigger is invoked, allowing you to qualify triggers and modify state before processing.

### TriggerEvent Interface

```typescript
export interface TriggerEvent {
  triggerBody: any;
  triggerName: string;
}
```

### Event Lifecycle

The handler serves two purposes:

1. **Qualifying Triggers**: Return `{ isQualified: boolean }` to determine if processing should continue
2. **Adding Information to State**: Modify `state` directly by reference

**Handler must return:**

```typescript
return { isQualified: true }; // Continue processing
return { isQualified: false }; // Stop processing
```

**Modifying state:**

```typescript
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerBody, state }) => {
  state.memory.customerId = triggerBody.body?.customerId;
  state.messages.push(new SystemMessage('Processing request'));
  return { isQualified: true };
});
```

### Development Environment: Dashboard Messages

In development, agents are invoked with `triggerName = 'dashboard_message'` for dashboard messages. Handle this case to prevent default webhook behavior. Otherwise, avoid using this parameter.

```typescript
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody, state }) => {
  if (triggerName === 'dashboard_message') {
    return { isQualified: true };
  }

  // Handle webhook triggers
  if (triggerBody.body && triggerBody.headers) {
    if (!triggerBody.body.customerId) {
      return { isQualified: false };
    }
    state.memory.webhookData = triggerBody.body;
    return { isQualified: true };
  }

  return { isQualified: false };
});
```

## Extracting Session ID from Webhooks

Extract a unique `sessionId` from webhook payloads to maintain conversation continuity.

### Using parseSessionIdFromTrigger

Configure the Agent to automatically extract `sessionId`:

```typescript
const agent = new Agent({
  memorySchema,
  config,
  tools,
  parseSessionIdFromTrigger: (triggerBody) => triggerBody?.body?.data?.item?.id,
});
```

**How it works:**

* Receives `triggerBody` as parameter
* Returns a string session identifier
* If `undefined`/`null`, platform generates a new `sessionId`
* Used to resume existing agent sessions or create new ones

**Examples:**

```typescript
// Extract from nested structure
parseSessionIdFromTrigger: (triggerBody) => {
  return triggerBody?.body?.data?.item?.id;
};
```

**Fallback:** If not provided or returns `undefined`/`null`, checks `triggerBody.sessionId`, then generates a new UUID.

### Session ID in Trigger Events

* **Sandbox Playground**: Platform auto-generates `sessionId` for each execution
* **Production**: Users provide their own `sessionId` to support resuming sessions
* **Session Continuity**: Matching `sessionId` resumes previous state instead of starting fresh
* **Schedule Triggers**: Creates new session unless `sessionId` is explicitly provided


# Playbooks

Playbooks are reusable instructions that are automatically included in all prompt and tool nodes of your agent. They help maintain consistency and reduce repetition across your agent's behavior.

## Overview

Playbooks are defined using EditorJS blocks format and support:

* [**EditorJS blocks**](https://editorjs.io/) for rich content structure
* **EJS templating** for dynamic content
* **Placeholders** for runtime values
* **Multiple files** for better organization
* **Platform integration** for web-based editing

## Configuration

Add the playbooks directory to your `minded.json`:

```json
{
  "playbooks": ["./src/playbooks"]
}
```

## Creating Playbooks

Create YAML files in your playbooks directory using the EditorJS blocks structure:

```yaml
# src/playbooks/general.yaml
id: general-playbooks
name: General Customer Service
blocks:
  - type: header
    data:
      text: Customer Service Guidelines
      level: 2
  - type: paragraph
    data:
      text: 'You are a helpful customer service agent for {memory.companyName}.'
  - type: paragraph
    data:
      text: 'Always be polite and professional in your responses.'
  - type: paragraph
    data:
      text: 'Current time: {system.currentTime}'
  - type: paragraph
    data:
      text: 'User location: {memory.userLocation}'
```

## Block Types

Playbooks support various EditorJS block types:

### Header Block

```yaml
- type: header
  data:
    text: 'Section Title'
    level: 2 # 1-6 for h1-h6
```

### Paragraph Block

```yaml
- type: paragraph
  data:
    text: 'Your paragraph content here'
```

### List Block

```yaml
- type: list
  data:
    style: unordered # or "ordered"
    items:
      - 'First item'
      - 'Second item'
      - 'Third item'
```

### Quote Block

```yaml
- type: quote
  data:
    text: 'Important note or quote'
    caption: 'Optional caption'
```

### Code Block

```yaml
- type: code
  data:
    code: |
      function example() {
        return "code example";
      }
```

### Delimiter Block

```yaml
- type: delimiter
  data: {}
```

## Using Templates

### EJS Templates

Use EJS syntax (`<%= %>`) for dynamic content evaluated at compile time:

```yaml
- type: paragraph
  data:
    text: 'Today is <%= new Date().toDateString() %>'
- type: paragraph
  data:
    text: 'Environment: <%= process.env.NODE_ENV %>'
```

### Placeholders

Use placeholders for runtime values from the agent's state:

**Memory placeholders** (`{memory.key}`):

```yaml
- type: paragraph
  data:
    text: 'Hello {memory.customerName},'
- type: paragraph
  data:
    text: 'Your order {memory.orderId} is being processed.'
```

**System placeholders** (`{system.key}`):

```yaml
- type: paragraph
  data:
    text: 'Current time: {system.currentTime}' # Returns new Date().toISOString()
```

These placeholders are automatically replaced with values from the agent's memory and system context when the playbooks are compiled.

## Using Memory Values in Playbooks

Placeholders in playbooks are replaced with values from the agent's memory state:

```typescript
const updateCustomerTool: Tool<typeof inputSchema, any> = {
  name: 'updateCustomer',
  description: 'Update customer information',
  input: inputSchema,
  isGlobal: true,
  execute: async ({ input, state }) => {
    return {
      memory: {
        memory: state.memory,
        customer: input,
        customerName: input.name,
        userLocation: input.location,
      },
    };
  },
};
```

## Nested Values

Placeholders support nested object access from the memory state:

```yaml
- type: paragraph
  data:
    text: 'Customer: {memory.customer.name}'
- type: paragraph
  data:
    text: 'Email: {memory.customer.email}'
- type: paragraph
  data:
    text: 'Order status: {memory.order.status}'
```

These values are resolved from nested properties in the agent's memory, for example: `memory.customer.name`.

## Complete Example

```yaml
# src/playbooks/customer-service.yaml
id: customer-service-guidelines
name: Customer Service Guidelines
blocks:
  - type: header
    data:
      text: Customer Service Best Practices
      level: 1
  - type: paragraph
    data:
      text: 'You are representing {companyName} as a professional customer service agent.'
  - type: list
    data:
      style: unordered
      items:
        - 'Always greet customers warmly'
        - 'Listen actively to their concerns'
        - 'Provide clear and helpful solutions'
        - 'Follow up to ensure satisfaction'
  - type: quote
    data:
      text: "The customer's experience is our top priority"
      caption: 'Company motto'
  - type: paragraph
    data:
      text: 'Current session started: <%= new Date().toISOString() %>'
```

## Best Practices

1. **Organize by purpose**: Create separate files for different aspects (general, domain-specific, compliance, etc.)
2. **Use meaningful IDs**: Each playbook must have a unique ID
3. **Structure content logically**: Use headers, lists, and quotes to organize information
4. **Keep blocks focused**: Each block should contain a single concept or instruction
5. **Document placeholders**: Comment which parameters your playbooks expect
6. **Test thoroughly**: Verify that all placeholders are being populated correctly

## Example Structure

```
src/playbooks/
├── general.yaml          # General behavior guidelines
├── compliance.yaml       # Regulatory compliance rules
├── brand-voice.yaml      # Brand-specific communication style
└── domain-specific.yaml  # Domain-specific instructions
```

## Platform Integration

When using the Minded platform:

1. Playbooks are editable through the web interface using a rich text editor
2. Changes are synchronized with your Git repository
3. The platform uses the same EditorJS blocks format
4. Local YAML files are automatically converted to the blocks format
5. All templating features work the same in both local and platform environments

## Migration from Content Format

If you have existing playbooks using the old `content` format, convert them to blocks:

**Old format:**

```yaml
content: |
  # Customer Service
  Always be polite and professional.
  Current time: <%= new Date().toISOString() %>
```

**New format:**

```yaml
blocks:
  - type: header
    data:
      text: Customer Service
      level: 1
  - type: paragraph
    data:
      text: Always be polite and professional.
  - type: paragraph
    data:
      text: 'Current time: <%= new Date().toISOString() %>'
```


# RPA Tools

RPA (Robotic Process Automation) tools are special tools that automate browser interactions using Playwright. They provide automatic screenshot capture, action logging, and browser session management.

## What are RPA Tools?

RPA tools automate tasks on the web by controlling a browser programmatically. They are ideal for:

* Web scraping and data extraction
* Form filling and submission
* Clicking buttons and navigating websites
* Automating repetitive web-based workflows
* Interacting with web applications that don't have APIs

## RPA Tool Structure

RPA tools must implement the `RPATool` interface with `type: 'rpa'`:

```ts
// Example: RPA Tool with input and optional output schemas
interface RPATool<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
  type: 'rpa'; // Required: marks this as an RPA tool
  proxyConfig?: ProxyConfig; // Optional: unified proxy configuration
  browserTaskMode?: BrowserTaskMode; // Optional: browser provider (local/cloud/onPrem)
  persistSession?: boolean; // Optional: persist cookies & localStorage across executions (default: true)
  execute: ({ input, state, agent, page }) => Promise<{ result? }>;
}
```

## Execute Function Signature

RPA tools receive an additional `page` parameter (Playwright Page object) in their execute function:

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

### Parameters

* **`input`**: Validated data matching your Zod schema
* **`state`**: Current conversation state including memory, sessionId, and other context
* **`agent`**: Agent instance providing access to PII gateway, logging, and other platform features
* **`page`**: Playwright Page object for browser automation - **automatically provided for RPA tools**

### Proxy Configuration

RPA tools can optionally specify proxy configuration using the unified `ProxyConfig` type. This allows you to use Minded-managed proxies or custom proxy servers:

```ts
import { ProxyConfig, ProxyProvider, MindedProxyMode } from '@minded-ai/mindedjs';

// Custom proxy server
const rpaToolCustom: Tool<typeof schema, Memory> = {
  name: 'rpa_get_order_details',
  description: 'Navigate to a website and extract order details',
  input: schema,
  type: 'rpa',
  proxyConfig: {
    provider: ProxyProvider.CUSTOM,
    server: 'http://proxy.example.com:8080',
    username: 'user', // Optional
    password: 'pass', // Optional
  },
  browserTaskMode: BrowserTaskMode.CLOUD,
};
```

**Proxy Configuration Options:**

* **Minded Proxy - Trusted IP**: Routes through Minded proxy for IP whitelisting without region selection
* **Minded Proxy - Region**: Routes through Minded proxy with a specific country/region (requires `countryCode`)
* **Custom Proxy**: Use your own proxy server (requires `server`, optional `username` and `password`)

**Note**: Proxy configuration only applies when using cloud or on-prem browser providers. Local browser sessions ignore proxy settings.

If a tool omits `proxyConfig`, the SDK falls back to environment variables:

| Variable                     | Values                   | Notes                              |
| ---------------------------- | ------------------------ | ---------------------------------- |
| `BROWSER_PROXY_PROVIDER`     | `minded` \| `custom`     | Unset means no proxy               |
| `BROWSER_PROXY_MODE`         | `trusted_ip` \| `region` | Required when provider is `minded` |
| `BROWSER_PROXY_COUNTRY_CODE` | ISO-3166-1 alpha-2       | Required when mode is `region`     |
| `BROWSER_PROXY_SERVER`       | URL                      | Required when provider is `custom` |
| `BROWSER_PROXY_USERNAME`     | string                   | Optional custom-proxy credential   |
| `BROWSER_PROXY_PASSWORD`     | string                   | Optional custom-proxy credential   |

Invalid or incomplete proxy env values are ignored (no proxy), same as an invalid `BROWSER_TASK_MODE` falling back to cloud.

### Browser Provider Configuration

RPA tools can optionally specify which browser provider to use:

```ts
import { BrowserTaskMode } from '@minded-ai/mindedjs';

const rpaTool: Tool<typeof schema, Memory> = {
  name: 'rpa_get_order_details',
  description: 'Navigate to a website and extract order details',
  input: schema,
  type: 'rpa',
  browserTaskMode: BrowserTaskMode.CLOUD, // Optional: local/cloud/onPrem
  // ... rest of tool definition
};
```

Available options:

* `BrowserTaskMode.LOCAL`: Use local browser instance (browser lives in the same machine as the agent), for local development only.
* `BrowserTaskMode.CLOUD`: Use cloud browser provider (allows for proxy configuration, also automatically passes CloudFlare and other anti-bot protection)

If not specified, the tool will use the browser provider configured in your environment (`BROWSER_TASK_MODE`: `localRun` | `cloud` | `onPrem`). Prompt nodes, YAML tool-run nodes, and the standalone tool executor all use the same rule: **tool settings first, then env**. YAML browser-task nodes use `node.proxyConfig` first, then the env proxy config.

Prompt-node RPA tools go through `withBrowserSession` only (the extra pre-create session was removed) so live view stays tied to that run's `toolCallId`.

### Automatic Features

When you mark a tool as `type: 'rpa'`, the platform automatically:

1. **Browser Session Management**: Creates and manages a browser session
2. **Screenshot Capture**: Takes screenshots before and after every Playwright action (click, fill, type, goto, etc.)
3. **Action Logging**: Automatically logs all browser actions (e.g., "Navigate to: <https://example.com>", "Click: .submit-button")
4. **Error Handling**: Captures screenshots and HTML on errors for debugging. No need to add any additional code to handle errors (try/catch blocks).

All screenshots and logs are automatically displayed in the UI when the tool executes.

## Basic RPA Tool Example

Here's a simple RPA tool that navigates to a website and extracts data:

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

type Memory = z.infer<typeof memorySchema>;

const schema = z.object({
  url: z.string().describe('The website URL to navigate to'),
});

import { ProxyConfig, ProxyProvider, MindedProxyMode } from '@minded-ai/mindedjs';

const rpaGetOrderDetailsTool: Tool<typeof schema, Memory> = {
  name: 'rpa_get_order_details',
  description: 'Navigate to a website and extract order details',
  input: schema,
  type: 'rpa', // Mark as RPA tool
  proxyConfig: {
    provider: ProxyProvider.MINDED,
    mode: MindedProxyMode.REGION,
    countryCode: 'US',
  }, // Optional: use US proxy
  browserTaskMode: BrowserTaskMode.CLOUD, // Optional: use cloud provider
  execute: async ({ input, state, agent, page }) => {
    logger.info({
      message: 'Navigating to website',
      sessionId: state.sessionId,
      url: input.url,
    });

    // Navigate to the website
    // Screenshot and log are automatically captured
    await page.goto(input.url, { timeout: 60000 });

    // Wait for content to load
    await page.waitForSelector('.order-summary', { timeout: 10000 });

    // Extract data from the page
    const orderItems = await page.locator('.order-item').allInnerTexts();
    const totalAmount = await page.locator('.total-amount').innerText();

    logger.info({
      message: 'Extracted order details',
      sessionId: state.sessionId,
      itemCount: orderItems.length,
    });

    // Update state if needed
    state.memory.lastOrderUrl = input.url;

    return {
      result: {
        items: orderItems,
        total: totalAmount,
      },
    };
  },
};

export default rpaGetOrderDetailsTool;
```

## RPA Development Guidelines

### Prerequisites

Before creating an RPA tool, gather:

* **The URL of the website** you need to automate
* **The task to be performed** (e.g., "Fill out a form", "Extract order details")
* **Any required information** (e.g., login credentials, form data)

### Development Workflow

1. **Plan the automation steps**
   * Break down the task into discrete steps (e.g., "Navigate to URL", "Click on X button", "Type Y into Z input field")
   * Identify the selectors you'll need for each element
2. **Test with Playwright MCP**
   * Use the Playwright MCP to interact with the browser interactively
   * Read the HTML of the page to find correct selectors
   * Test each step before writing the code
   * Iterate up to 3 times if a step fails
3. **Write the tool code**
   * Use the `page` object provided in the execute function
   * Add comments to explain what each action does
   * Use appropriate Playwright methods (click, fill, type, goto, etc.)
4. **Test the complete tool**
   * Run: `npm run tool <toolName> <param1>=<value1> <param2>=<value2> ...`
   * Add `cookies=false` flag to avoid using cookies from previous sessions (useful for login tasks)
   * Check screenshots and logs in the UI
   * If the tool fails, check the `rpaTestResults` folder for error screenshots and HTML

### Available Data Sources

In the context of an RPA tool, you have access to:

1. **Tool input parameters**: Data extracted by the LLM from the conversation
2. **Environment variables**: `process.env` for configuration and secrets
3. **State and memory**: Current conversation state and agent memory

If you need additional data, ask the user whether you should add it to the input parameters.

### General Rules

* **Important - avoid using waitForNavigation**: As it can lead to hangs. Use waitForSelector instead.
* **Test before implementing**: Use Playwright MCP to test interactions before writing code
* **Don't close browser sessions**: The platform manages browser sessions automatically
* **Never assume credentials**: Always ask the user for credentials if they're unavailable
* **Verify changes**: When modifying an RPA script, re-run the complete flow to verify it works
* **Follow instructions**: Complete only the task specified - don't add extra steps
* **Use Playwright MCP during development**: Prefer interactive testing over running the tool directly while building

### CAPTCHA Handling

**If a website requires a CAPTCHA:**

* Use the `resolve_captcha` MCP tool in the development phase.
* Use the `resolveCaptcha` function exported by the `@minded-ai/mindedjs` package in the rpa tool code.
* Captcha resolution is not always successful, add retry mechanism that would retry to resolve the CAPTCHA up to 5 times.

## Tool Registration

RPA tools are registered the same way as regular tools:

```ts
// In your tools/index.ts
import rpaGetOrderDetailsTool from './rpaGetOrderDetails';

export default [
  rpaGetOrderDetailsTool,
  // ... other tools
];
```

## Tool Nodes

RPA tools can be used in tool nodes just like regular tools:

```yaml
nodes:
  - name: 'Extract Order Data'
    type: 'tool'
    toolName: 'rpa_get_order_details'
```

The platform will automatically:

* Create a browser session
* Execute the RPA tool with screenshot and log capture
* Display results in the UI

## Testing

### Local Testing

```bash
# Test an RPA tool locally
npm run tool rpa_get_order_details url=https://example.com

# Test without cookies (useful for login flows)
npm run tool rpa_get_order_details url=https://example.com cookies=false
```

### Debugging Failed Executions

If a tool fails:

1. Check the `rpaTestResults/` folder for:
   * `screenshot.jpeg`: Final state screenshot
   * `content.html`: HTML content at failure point
2. Review logs in the UI to see which step failed
3. Check screenshots to see the visual state at each step

## Limitations

* **Browser Context**: Each RPA tool execution uses a fresh browser context (cookies are preserved)
* **Session Management**: Browser sessions are managed automatically - don't manually create or destroy them
* **Concurrent Execution**: Multiple RPA tools in the same flow share the same browser session

## See Also

* [Tools Documentation](/low-code-editor/tools) - General tool development guide
* [Playwright Documentation](https://playwright.dev) - Complete Playwright API reference
* [Node Types](/low-code-editor/nodes) - Using tools in flow nodes


# Agent API

The Agent class is the core component of MindedJS that orchestrates conversations, manages state, and coordinates flows. This document covers the initialization options and key methods available on the Agent instance.

## Constructor

The Agent constructor accepts configuration options to customize its behavior:

```typescript
new Agent({
  memorySchema: z.ZodSchema,
  config: MindedSDKConfig,
  tools: Tool[],
  platformToken?: string,
  memorySaver?: BaseCheckpointSaver,
  interruptSessionManager?: InterruptSessionManager,
  parseSessionIdFromTrigger?: (trigger: any) => string,
});
```

### Options

* **memorySchema** (required): Zod schema defining the agent's memory structure
* **config** (required): SDK configuration including flows and LLM settings
* **tools** (required): Array of custom tools available to the agent
* **platformToken**: Optional token for platform authentication
* **memorySaver**: Optional custom checkpoint saver for conversation memory
* **interruptSessionManager**: Optional custom interrupt session manager
* **parseSessionIdFromTrigger**: Optional function to extract session ID from triggers (defaults to `triggerBody.sessionId`)

## Key Methods

### updateState

Updates the state of an active session. This method allows you to programmatically modify the conversation state, including messages, memory, and other state properties.

#### Signature

```typescript
async updateState({
  sessionId: string,
  state: Partial<State>
}): Promise<void>
```

#### Parameters

* `sessionId` (string): The unique identifier of the session to update
* `state` (Partial): An object containing the state properties to update. Can include:
  * `messages`: Array of messages to add or update
  * `memory`: Partial memory object to merge with existing memory
  * `goto`: Node ID to override the next execution point
  * Any other state properties defined in your agent

#### Usage Examples

**Update Memory**

```typescript
// Update specific memory fields
await agent.updateState({
  sessionId: 'session-123',
  state: {
    memory: {
      user: {
        name: 'John Doe',
        preferences: {
          language: 'en',
        },
      },
    },
  },
});
```

**Add Messages**

```typescript
import { HumanMessage, AIMessage } from '@langchain/core/messages';

// Add a new message to the conversation
await agent.updateState({
  sessionId: 'session-123',
  state: {
    messages: [new HumanMessage('User input'), new AIMessage('Agent response')],
  },
});
```

**Update Multiple State Properties**

```typescript
// Update both memory and messages
await agent.updateState({
  sessionId: 'session-123',
  state: {
    memory: {
      conversation: {
        topic: 'Order inquiry',
        urgency: 'high',
      },
    },
    messages: [new HumanMessage('I need help with my order')],
  },
});
```

#### Common Use Cases

1. **Tool State Updates**: Tools can update state after execution

   ```typescript
   // Inside a tool's execute method
   async execute({ input, state, agent }) {
     // Perform tool logic
     const result = await processOrder(input.orderId);

     // Update state with results
     await agent.updateState({
       sessionId: state.sessionId,
       state: {
         memory: {
           order: {
             id: result.orderId,
             status: result.status
           }
         }
       }
     });

     return { result };
   }
   ```
2. **External Event Handling**: Update state based on external events

   ```typescript
   // Handle webhook or external trigger
   async handleWebhook(sessionId: string, webhookData: any) {
     await agent.updateState({
       sessionId,
       state: {
         memory: {
           externalData: webhookData,
           lastWebhookTime: new Date().toISOString()
         }
       }
     });
   }
   ```
3. **Voice Session Corrections**: Update messages during voice conversations

   ```typescript
   // Correct a voice transcription
   await agent.updateState({
     sessionId: voiceSessionId,
     state: {
       messages: [correctedMessage],
       goto: currentNodeId,
     },
   });
   ```

### getState

Retrieves the current state of a session.

#### Signature

```typescript
async getState(sessionId: string): Promise<State<Memory>>
```

#### Parameters

* `sessionId` (string): The unique identifier of the session

#### Returns

A `State` object containing:

* `sessionId`: The session identifier
* `memory`: The current memory state
* `messages`: Array of conversation messages
* `sessionType`: The type of session (TEXT, VOICE, etc.)
* Other state properties

#### Usage Example

```typescript
// Get current state
const state = await agent.getState('session-123');

// Access memory
console.log('User name:', state.memory.user?.name);

// Check conversation history
console.log('Message count:', state.messages.length);

// Get session type
console.log('Session type:', state.sessionType);
```

### compiledGraph

Access to the underlying LangGraph compiled graph for advanced operations.

#### Usage Example

```typescript
// Get LangGraph state directly
const graphState = await agent.compiledGraph.getState(agent.getLangraphConfig(sessionId));

// Update state with LangGraph API
await agent.compiledGraph.updateState(agent.getLangraphConfig(sessionId), {
  messages: [newMessage],
  goto: 'specific-node',
});
```

### Knowledge Base RAG

You can query knowledge bases directly from code by importing the RAG utilities:

```typescript
import { retrieveFromKnowledgeBase, retrieveAndGenerateFromKnowledgeBase } from '@minded-ai/mindedjs';
```

These functions are useful when you want full control over retrieval timing (for example in `TRIGGER_EVENT`) and how the retrieved context is applied.

See [Knowledge Base RAG API](/sdk/knowledge-base-rag) for function signatures, filtering, and citations.

## State Management Best Practices

### 1. Partial Updates

Always use partial updates to avoid overwriting existing state:

```typescript
// ✅ Good - Partial update
await agent.updateState({
  sessionId,
  state: {
    memory: {
      user: { name: 'Jane' },
    },
  },
});

// ❌ Avoid - Don't replace entire memory
await agent.updateState({
  sessionId,
  state: {
    memory: completeMemoryObject, // This might lose other fields
  },
});
```

### 2. Atomic Operations

Group related updates together:

```typescript
// ✅ Good - Single atomic update
await agent.updateState({
  sessionId,
  state: {
    memory: {
      order: { id: '123', status: 'confirmed' },
      lastAction: 'order_confirmed',
    },
    messages: [confirmationMessage],
  },
});

// ❌ Avoid - Multiple separate updates
await agent.updateState({ sessionId, state: { memory: { order: { id: '123' } } } });
await agent.updateState({ sessionId, state: { memory: { order: { status: 'confirmed' } } } });
await agent.updateState({ sessionId, state: { messages: [confirmationMessage] } });
```

### 3. State Validation

The state updates are validated against your memory schema:

```typescript
try {
  await agent.updateState({
    sessionId,
    state: {
      memory: {
        user: {
          email: 'invalid-email', // Will fail validation if email has format validation
        },
      },
    },
  });
} catch (err) {
  logger.error({ message: 'State validation failed', err });
}
```

### 4. Tool State Updates

Tools should return state updates rather than calling `updateState` directly when possible:

```typescript
// ✅ Preferred - Return state updates
async execute({ input, state }) {
  const result = await processData(input);
  return {
    result,
    state: {
      memory: {
        processedData: result.data
      }
    }
  };
}

// ⚠️ Use only when necessary - Direct update
async execute({ input, state, agent }) {
  const result = await processData(input);
  await agent.updateState({
    sessionId: state.sessionId,
    state: {
      memory: { processedData: result.data }
    }
  });
  return { result };
}
```

## Tool Execution API

### ToolExecutor

The `ToolExecutor` class enables standalone tool execution, particularly useful for browser automation and external tool invocations.

#### Setting Up Tool Execution

```typescript
import { ToolExecutor } from '@minded-ai/mindedjs';

// Create a tool executor
const toolExecutor = new ToolExecutor(agent);

// Register tools that can be executed via requests
// Only tools with allowExecutionRequests: true will be registered
toolExecutor.registerTools(tools);
```

#### Tool Configuration for Execution

Tools must explicitly opt-in to be executable via requests:

```typescript
import { Tool } from '@minded-ai/mindedjs';

const myBrowserTool: Tool<typeof schema, Memory> = {
  name: 'browserAction',
  description: 'Performs browser automation',
  input: schema,
  allowExecutionRequests: true, // Required for ToolExecutor
  execute: async ({ input, state, agent }) => {
    // Access CDP URL if available (browser-use context)
    if (state.cdp?.url) {
      // Connect to browser using Playwright or similar
      const browser = await chromium.connectOverCDP(state.cdp.url);
      const page = browser.contexts()[0].pages()[0];

      // Perform browser automation
      await page.goto(input.url);
      const title = await page.title();

      return {
        result: { pageTitle: title },
        state: {
          memory: {
            lastVisitedUrl: input.url,
          },
        },
      };
    }

    // Fallback for non-browser context
    return { result: 'Browser context not available' };
  },
};
```

#### Executing Tools

```typescript
const response = await toolExecutor.executeTool({
  toolName: 'browserAction',
  toolParams: { url: 'https://example.com' },
  sessionId: 'session-123',
  requestId: 'req-456',
  cdpUrl: 'ws://localhost:9222/devtools/browser/...', // Optional, for browser-use
});

if (response.error) {
  logger.error({ message: 'Tool execution failed', error: response.error });
} else {
  logger.info({ message: 'Tool result', result: response.result });
}
```

### CDP URL and Browser Automation

When tools are executed in a browser-use context, the Chrome DevTools Protocol (CDP) URL is available in the state:

#### Accessing CDP URL in Tools

```typescript
const browserTool: Tool<typeof schema, Memory> = {
  name: 'advancedBrowserAction',
  description: 'Advanced browser automation with direct CDP access',
  input: schema,
  allowExecutionRequests: true,
  execute: async ({ input, state, agent }) => {
    const cdp = state.cdp;

    if (!cdp?.url) {
      return {
        result: 'This tool requires browser context',
        error: 'CDP URL not available',
      };
    }

    // Use Playwright with CDP
    const browser = await playwright.chromium.connectOverCDP(cdp.url);
    const context = browser.contexts()[0];
    const page = context.pages()[0];

    // Perform complex browser automation
    await page.evaluate(() => {
      // Execute JavaScript in the browser
      document.querySelector('#submit')?.click();
    });

    // Take screenshot
    const screenshot = await page.screenshot();

    return {
      result: {
        success: true,
        screenshot: screenshot.toString('base64'),
      },
      state: {
        memory: {
          lastAction: 'browser_automation',
          timestamp: new Date().toISOString(),
        },
      },
    };
  },
};
```

#### Browser-Use Integration Flow

1. Browser-use requests tool execution via socket
2. ToolExecutor receives request with CDP URL
3. CDP info is injected into state (`state.cdp`)
4. Tool connects to browser using CDP URL from `state.cdp.url`
5. Tool performs automation and returns results
6. State updates are automatically applied

#### Security Considerations

* Tools must have `allowExecutionRequests: true` to be executable via ToolExecutor
* CDP URLs are only available during browser-use execution context
* Tools should validate CDP URL availability before attempting browser operations
* Always handle connection failures gracefully

## Integration with Other Features

### With Browser Use Tools

When using browser automation tools, state updates happen automatically through the ToolExecutor:

```typescript
// Browser tools can update state via the ToolExecutor
const toolExecutor = new ToolExecutor(agent);
toolExecutor.registerTools(browserTools);

// State updates are handled internally when tools return state changes
// The CDP info is automatically injected when available in state.cdp
```

### With Voice Sessions

Voice sessions use `updateState` for real-time corrections:

```typescript
// Voice session correction flow
const voiceSession = new VoiceSession(agent, sessionId);
// Corrections use updateState internally
```

### With Events

State can be updated in event handlers:

```typescript
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, sessionId }) => {
  if (triggerName === 'external_update') {
    await agent.updateState({
      sessionId,
      state: {
        memory: {
          lastTrigger: triggerName,
          triggerTime: new Date().toISOString(),
        },
      },
    });
  }
});
```

## Error Handling

Always handle potential errors when updating state:

```typescript
try {
  await agent.updateState({
    sessionId,
    state,
  });
} catch (err) {
  if (err.message.includes('Session not found')) {
    // Handle missing session
    logger.error({ message: 'Session does not exist', sessionId, err });
  } else if (err.message.includes('Validation')) {
    // Handle validation errors
    logger.error({ message: 'Invalid state update', err });
  } else {
    // Handle other errors
    logger.error({ message: 'Failed to update state', err });
  }
}
```

## Related Documentation

* [Memory](/sdk/memory) - Learn about memory schemas and management
* [Events](/sdk/events) - Handle agent events and triggers
* [Debugging](/sdk/debugging) - Debug state updates and transitions
* [Tools](/low-code-editor/tools) - Create tools that update state


# Debugging

MindedJS provides comprehensive debugging capabilities to help you understand and troubleshoot your agent's behavior during development and production. This guide covers various debugging techniques and tools available in the SDK.

## Debug Logging

Set the log level to debug in your environment:

```env
LOG_LEVEL=debug
```

## Debugging Logical Conditions

You can debug and breakpoint on logical conditions during development:

```typescript
// Listen to condition evaluation events
import { AgentEvents } from 'mindedjs';

agent.on(AgentEvents.ON_LOGICAL_CONDITION, async ({ edge, state, condition }) => {
  console.log('[Debug] Evaluating condition:', condition);
  console.log('[Debug] Current memory:', state.memory);
});

agent.on(AgentEvents.ON_LOGICAL_CONDITION_RESULT, async ({ condition, result, executionTimeMs }) => {
  console.log('[Debug] Result:', result);
  console.log('[Debug] Execution time:', executionTimeMs, 'ms');
});
```

## LLM Debug Callback Handler

A good practice is to periodically inspect the actual messages being sent to the LLM to ensure they match your expectations. You can breakpoint and view the final prompt messages after compilation using `LLMDebugCallbackHandler`.

### Using the LLM Debug Callback Handler

The `LLMDebugCallbackHandler` can be imported directly from the main package:

```typescript
import { Agent, LLMDebugCallbackHandler } from '@minded-ai/mindedjs';

// Create the debug handler
const debugHandler = new LLMDebugCallbackHandler();

// Configure your agent with the debug handler
const agent = new Agent({
  config: {
    ...config,
    llm: {
      ...config.llm,
      properties: {
        ...config.llm.properties,
        callbacks: [debugHandler],
      },
    },
  },
  tools,
  memorySchema,
});
```

### Advanced Usage - Custom Debug Handler

You can extend the `LLMDebugCallbackHandler` to add your own debugging logic:

```typescript
import { LLMDebugCallbackHandler } from '@minded-ai/mindedjs';

class CustomDebugHandler extends LLMDebugCallbackHandler {
  async handleLLMEnd(output: any, ...args: any[]): Promise<void> {
    // Log token usage if available
    const tokenUsage = output.llmOutput?.tokenUsage;
    console.log(`Token usage: ${tokenUsage.totalTokens}`);
  }
}
```

The `LLMDebugCallbackHandler` extends LangChain's `BaseCallbackHandler` and can override different mathods in langchain flow. See the [LangChain documentation](https://js.langchain.com/docs/modules/callbacks/).


# Memory

Memory in MindedJS allows agents to persist and share data across conversation turns and flow executions. It acts as the agent's working memory, storing context, user information, conversation state, and any other data your agent needs to remember.

## What is Memory?

Memory is a structured data store that:

* Persists throughout an agent session
* Is accessible to all nodes in your flows
* Gets automatically merged when updated
* Is validated against a schema you define
* Can store any JSON-serializable data

## Defining Memory Schema

Memory schemas are defined using [Zod](https://zod.dev/) for type safety and runtime validation.

```typescript
import { z } from 'zod';

const memorySchema = z.object({
  user: z.object({
    id: z.string(),
    name: z.string(),
    preferences: z.object({
      language: z.string().default('en'),
      timezone: z.string().default('UTC'),
    }),
  }),
  conversation: z.object({
    topic: z.string().optional(),
    urgency: z.enum(['low', 'medium', 'high']).default('medium'),
  }),
  order: z
    .object({
      id: z.string(),
      status: z.string(),
      total: z.number(),
    })
    .optional(),
});

type Memory = z.infer<typeof memorySchema>;
```

## Using Memory in Your Agent

### 1. Configure Memory Schema

```typescript
import { Agent } from 'mindedjs';

const agent = new Agent({
  memorySchema,
  config,
  tools,
});
```

### 2. Initialize Memory

Memory is typically initialized when a trigger event occurs:

```typescript
import { events } from 'mindedjs';

agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody, state }) => {
  if (triggerName === 'new_order_issue') {
    // Update state directly by reference (v2.0 change)
    if (state) {
      state.memory.user = { id: triggerBody.userId, name: triggerBody.userName };
      state.memory.order = { id: triggerBody.orderId, status: 'pending' };
    }

    return {
      isQualified: true,
    };
  }
});
```

### 3. Access Memory in Tools

Tools can read from and write to memory:

```typescript
const updateOrderStatus = {
  name: 'updateOrderStatus',
  description: 'Updates the order status in memory',
  inputSchema: z.object({
    newStatus: z.string(),
  }),
  execute: async ({ input, state }) => {
    // Read from memory
    const currentOrder = state.memory.order;

    // Update state directly by reference (v2.0 change)
    state.memory.order = {
      ...currentOrder,
      status: input.newStatus,
      lastUpdated: new Date().toISOString(),
    };

    return {
      result: `Order status updated to ${input.newStatus}`,
    };
  },
};
```

## Memory Lifecycle

1. **Initialization**: Memory starts empty `{}` and gets populated during trigger events
2. **Persistence**: Automatically persisted between conversation turns
3. **Updates**: Memory is updated by reference in tools and event handlers (v2.0 change)
4. **Validation**: All updates are validated against your schema

```typescript
// Current memory: { userId: "123", userName: "John" }
// Tool updates state directly:
//   state.memory.userName = "John Doe";
//   state.memory.email = "john@example.com";
// Result: { userId: "123", userName: "John Doe", email: "john@example.com" }
```

## Best Practices

### 1. Keep It Focused

Only store data relevant to your agent's functionality:

```typescript
// Good: Focused schema
const memorySchema = z.object({
  customerId: z.string(),
  currentIssue: z.string(),
  resolutionSteps: z.array(z.string()),
});
```

### 2. Use Optional Fields and Defaults

```typescript
const memorySchema = z.object({
  userId: z.string(),
  orderId: z.string().optional(), // Not all conversations involve orders
  conversationState: z.enum(['started', 'in_progress', 'resolved']).default('started'),
  attemptCount: z.number().default(0),
});
```

### 3. Structure Related Data

Group related fields into objects:

```typescript
const memorySchema = z.object({
  customer: z.object({
    id: z.string(),
    name: z.string(),
    tier: z.enum(['bronze', 'silver', 'gold']),
  }),
  session: z.object({
    startTime: z.string(),
    channel: z.enum(['chat', 'email', 'phone']),
  }),
});
```

## Common Patterns

### User Context Pattern

```typescript
const memorySchema = z.object({
  user: z.object({
    id: z.string(),
    profile: z.object({
      name: z.string(),
      email: z.string(),
      preferences: z.record(z.unknown()),
    }),
  }),
});
```

### Conversation State Pattern

```typescript
const memorySchema = z.object({
  conversation: z.object({
    phase: z.enum(['greeting', 'information_gathering', 'processing', 'resolution']),
    data: z.record(z.unknown()),
  }),
});
```

## Memory vs Messages vs History

* **Memory**: Structured data representing current state and context
* **Messages**: Conversation messages (AI, Human, Tool call, System etc.)
* **History**: Flow execution tracking with details about node visits, triggers, and tool calls

```typescript
// Memory: Current state
{
  orderId: "ORD-123",
  status: "processing",
  customer: { name: "John", tier: "gold" }
}

// Messages: Conversation messages
[
  { role: "human", content: "I need help with my order" },
  { role: "ai", content: "I'd be happy to help!" },
]


```

All three work together to provide complete context to your agent.

## Updating Memory Outside Agent Lifecycle

Sometimes you need to update the agent's memory from outside the normal agent flow - for example, when external events occur or when you need to programmatically modify the session state. MindedJS provides the `updateMemory` method for this purpose.

### Using updateMemory

The `updateMemory` method allows you to update the memory for a specific session:

```typescript
await agent.updateMemory(sessionId, memoryUpdate);
```

### Parameters

* `sessionId` (string): The unique identifier of the session whose memory you want to update
* `memoryUpdate` (Partial): A partial memory object containing only the fields you want to update

### Example Usage

```typescript
// Update a single field
await agent.updateMemory('session-123', {
  order: {
    status: 'shipped',
    trackingNumber: 'TRK-456789',
  },
});

// Update multiple fields
await agent.updateMemory('session-123', {
  user: {
    preferences: {
      language: 'es',
      timezone: 'America/Mexico_City',
    },
  },
  conversation: {
    urgency: 'high',
  },
});
```

### Important Notes

1. **Partial Updates**: The memory update is merged with existing memory, so you only need to provide the fields you want to change
2. **Validation**: Updates are validated against your memory schema - invalid updates will throw an error
3. **Session Must Exist**: The session ID must correspond to an active session
4. **Asynchronous**: The method returns a Promise, so make sure to await it


# Events

Events provide visibility into agent execution and enable reactive behavior. Each event type has specific input structure, output requirements, and use cases.

## History Structure

All events provide access to `state.history`, an array of `HistoryStep` objects tracking execution flow.

### HistoryStep Structure

* `step`: Sequential step number
* `type`: Step type (TRIGGER\_NODE, TOOL\_NODE, LLM\_NODE, etc.)
* `nodeId`: ID of the executed node
* `nodeDisplayName`: Human-readable node name
* `raw`: Raw data (trigger input, tool output, etc.)
* `messageIds`: Associated message IDs

Additional fields:

* `APP_TRIGGER_NODE`: `appName` identifies source application
* `TOOL_NODE`: Contains tool input/output information

**Example:**

```typescript
agent.on(events.AI_MESSAGE, async ({ state }) => {
  const triggerStep = state.history.find((step) => step.type === 'TRIGGER_NODE' || step.type === 'APP_TRIGGER_NODE');
  const toolSteps = state.history.filter((step) => step.type === 'TOOL_NODE');
  console.log(`Trigger: ${triggerStep?.nodeDisplayName}, Tools used: ${toolSteps.length}`);
});
```

## INIT

Emitted when agent's graph state is initialized (new session begins).

**Input:**

```typescript
{
  state: {
    messages: BaseMessage[];  // Empty initially
    memory: Memory;          // Initial memory from schema
    history: HistoryStep[];  // Empty initially
    sessionId: string;
    sessionType: SessionType;
  }
}
```

**Return:** `void` (handlers for side effects only)

**Example:**

```typescript
agent.on(events.INIT, async ({ state }) => {
  await initializeSessionResources(state.sessionId);
  await logSessionStart({ sessionId: state.sessionId, sessionType: state.sessionType });
  if (state.sessionType === 'VOICE') {
    await setupVoiceSession(state.sessionId);
  }
});
```

**Use cases:** Session logging, resource initialization, external service setup, debugging

## AI\_MESSAGE

Emitted when AI generates a message for the user.

**Input:**

```typescript
{
  message: string;
  state: {
    messages: BaseMessage[];
    memory: Memory;
    history: HistoryStep[];
    sessionId: string;
  }
}
```

**Return:** `void` (handlers for side effects only)

**Example:**

```typescript
agent.on(events.AI_MESSAGE, async ({ message, state }) => {
  await sendMessageToUser(message, state.sessionId);
  await websocket.send(
    JSON.stringify({
      type: 'ai_message',
      content: message,
      sessionId: state.sessionId,
    }),
  );
});
```

**Use cases:** Real-time chat UI, logging, message formatting, notifications, session management

## TRIGGER\_EVENT

Emitted when a trigger node is executed. Allows qualifying triggers and modifying state before processing.

**Input:**

```typescript
{
  triggerName: string;
  triggerBody: any;
  state: State<Memory>; // Current state (can be modified by reference)
}
```

**Return:** Must return `{ isQualified: boolean }`

**Important:** State is modified by reference (v2.0). Modify `state` directly, don't return state updates.

**Return patterns:**

```typescript
// Qualify
return { isQualified: true };

// Disqualify
return { isQualified: false };
```

**Examples:**

```typescript
// Input validation
agent.on(events.TRIGGER_EVENT, async ({ triggerBody, state }) => {
  if (!isValidInput(triggerBody)) {
    state.memory.emailSubject = triggerBody.body.subject;
    return { isQualified: false };
  }
  state.memory.validatedInput = triggerBody;
  return { isQualified: true };
});
```

```typescript
// Query a knowledge base during trigger qualification
//
// Use this pattern when you want full control over *when* retrieval happens
// and *how* the retrieved context is applied to the conversation.
import { SystemMessage } from '@langchain/core/messages';
import { v4 as uuidv4 } from 'uuid';
import { retrieveFromKnowledgeBase } from '@minded-ai/mindedjs';

agent.on(events.TRIGGER_EVENT, async ({ triggerBody, state }) => {
  const query = typeof triggerBody?.content === 'string' ? triggerBody.content : JSON.stringify(triggerBody);

  // knowledgeBaseId is optional - backend auto-selects if agent has exactly 1 KB
  // Pass knowledgeBaseId explicitly if you have multiple KBs
  const { retrievalResults } = await retrieveFromKnowledgeBase({
    environment: 'production',
    query,
    numberOfResults: 5,
  });

  if (retrievalResults.length > 0) {
    const context = retrievalResults.map((r) => `#### ${r.location.s3Location.uri}\n${r.content.text}`).join('\n\n');

    state.messages.push(
      new SystemMessage({
        id: uuidv4(),
        content: `### Knowledge Base Context\n\n${context}`,
      }),
    );
  }

  return { isQualified: true };
});
```

> **Tip:** If you want retrieval to happen automatically after `TRIGGER_EVENT`, enable “default retrieval” from the platform. See [Knowledge Bases](/platform/knowledge-bases#enable-knowledge-base-usage-in-an-agent).

> **Note:** `goto` jump occurs after current invocation completes.

**Use cases:** Input validation, data transformation, context setting, access control, routing logic

## ERROR

Emitted when an error occurs during execution. Control whether error is thrown or caught.

**Input:**

```typescript
{
  error: Error;
  state: State<Memory>;
}
```

**Return:**

```typescript
{
  throwError: boolean; // true = throw error and stop, false = catch and continue
}
```

**Important:** State is modified by reference (v2.0).

**Example:**

```typescript
agent.on(events.ERROR, async ({ error, state }) => {
  await logger.error({
    message: 'Agent execution error',
    error: error.message,
    sessionId: state.sessionId,
  });

  if (error.message.includes('rate limit')) {
    state.memory.errorRecovered = true;
    return { throwError: false }; // Continue
  }

  if (error.message.includes('authentication failed')) {
    await notificationService.alert({ type: 'critical_error', error: error.message });
    return { throwError: true }; // Stop
  }

  return { throwError: false };
});
```

**Use cases:** Error logging, recovery logic, critical error handling, monitoring, session cleanup, analytics

## ON\_LOGICAL\_CONDITION

Emitted before evaluating a logical condition on an edge.

**Input:**

```typescript
{
  edge: LogicalConditionEdge;
  state: State<Memory>;
  condition: string;
}
```

**Return:** `void` (handlers for debugging/logging only)

**Example:**

```typescript
agent.on(events.ON_LOGICAL_CONDITION, async ({ edge, condition, state }) => {
  await logger.debug({
    event: 'condition_evaluation_start',
    condition,
    edge: { source: edge.source, target: edge.target },
    sessionId: state.sessionId,
  });
});
```

**Use cases:** Debugging flow logic, performance monitoring, analytics, testing, audit logging

## ON\_LOGICAL\_CONDITION\_RESULT

Emitted after a logical condition is evaluated, providing result and execution metrics.

**Input:**

```typescript
{
  edge: LogicalConditionEdge;
  state: State<Memory>;
  condition: string;
  result: boolean;
  executionTimeMs: number;
  error?: Error;
}
```

**Return:** `void` (handlers for logging/monitoring only)

**Example:**

```typescript
agent.on(events.ON_LOGICAL_CONDITION_RESULT, async ({ condition, result, executionTimeMs, error }) => {
  if (error) {
    console.error('Condition evaluation failed:', error.message);
  }
  if (executionTimeMs > 10) {
    await logger.warn({ message: 'Slow condition detected', condition, executionTimeMs });
  }
  if (!result) {
    await analytics.track('condition_failed', { condition });
  }
});
```

**Use cases:** Performance monitoring, debugging failed conditions, flow analytics, error tracking, optimization

## ANALYTICS\_EVENT

Emitted when you call `trackAnalyticsEvent()` to send custom analytics data.

**Function signature:**

```typescript
async function trackAnalyticsEvent(eventName: string, payload: Record<string, any>, sessionId: string): Promise<void>;
```

**Handler input:**

```typescript
{
  eventName: string;
  payload: Record<string, any>;
  sessionId: string;
}
```

**Return:** `void`

**Example:**

```typescript
import { trackAnalyticsEvent } from '@minded-ai/mindedjs';

// Track custom events
agent.on(events.AI_MESSAGE, async ({ state }) => {
  if (state.memory.bookingCompleted) {
    await trackAnalyticsEvent(
      'booking_completed',
      {
        bookingId: state.memory.bookingId,
        amount: state.memory.totalAmount,
      },
      state.sessionId,
    );
  }
});

// Listen to analytics events
agent.on(events.ANALYTICS_EVENT, async ({ eventName, payload, sessionId }) => {
  // Send to additional analytics services, filter, transform, etc.
});
```

**Query analytics:** Use the [Analytics API](/api/analytics)

## TURN\_END

Emitted when a trigger invocation completes, just before returning the result. Allows customizing the return value.

**Input:**

```typescript
{
  state: State<Memory>;
}
```

**Return:**

```typescript
{
  returnValue: any; // Custom return value (optional)
}
```

**Example:**

```typescript
agent.on(events.TURN_END, async ({ state }) => {
  return {
    success: true,
    sessionId: state.sessionId,
    result: {
      message: 'Operation completed',
      metadata: {
        executionTime: state.memory.executionTime,
        stepsExecuted: state.history.length,
      },
    },
  };
});
```

**Use cases:** Response formatting, metadata addition, data extraction, API compatibility, result enrichment, response filtering


# Knowledge Base RAG API

Query knowledge bases directly from your agent code to retrieve relevant documents and generate responses with citations.

## Overview

The Knowledge Base RAG API provides two functions for interacting with knowledge bases:

* **`retrieveFromKnowledgeBase`**: Retrieve relevant documents based on a query
* **`retrieveAndGenerateFromKnowledgeBase`**: Retrieve documents and generate a response with citations

Both functions support metadata filtering, result count control, and automatic agent/environment isolation.

### Default Knowledge Base Selection

The `knowledgeBaseId` parameter is **optional**. If omitted, the backend will:

* **Exactly 1 KB** for the agent → automatically use it
* **0 KBs** → throw an error prompting you to create a knowledge base
* **>1 KBs** → throw an error requiring you to pass `knowledgeBaseId` explicitly to disambiguate

This simplifies usage for agents with a single knowledge base.

> **Before you start:** Create a knowledge base and upload documents in the platform dashboard. For agents with multiple knowledge bases, copy the `knowledgeBaseId` to pass explicitly. See [Knowledge Bases](/platform/knowledge-bases).

## retrieveFromKnowledgeBase

Retrieve relevant documents from a knowledge base.

### Function Signature

```typescript
retrieveFromKnowledgeBase(
  params: {
    knowledgeBaseId?: string;
    environment: string;
    query: string;
    numberOfResults?: number;
    filters?: KnowledgeBaseFilter;
  },
  timeoutMs?: number
): Promise<{
  retrievalResults: KnowledgeBaseRetrievalResult[];
  nextToken?: string;
}>
```

### Parameters

| Parameter                | Type                | Required | Description                                                                                                                    |
| ------------------------ | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `params.knowledgeBaseId` | string              |          | ID of the knowledge base to query. If omitted, backend auto-selects the KB if exactly one exists; errors if 0 or >1 KBs exist. |
| `params.environment`     | string              | ✓        | Environment (e.g., 'development', 'production')                                                                                |
| `params.query`           | string              | ✓        | Search query string                                                                                                            |
| `params.numberOfResults` | number              |          | Number of results to return (default: 5)                                                                                       |
| `params.filters`         | KnowledgeBaseFilter |          | Metadata filters for the query                                                                                                 |
| `timeoutMs`              | number              |          | Query timeout (default: 30000ms)                                                                                               |

### Return Value

Returns a Promise with:

* `retrievalResults`: Array of retrieval results, each containing:
  * `content.text`: Text content of the document chunk
  * `location.s3Location.uri`: URI of the source document
  * `metadata`: Associated metadata as key-value pairs
  * `score`: Relevance score (0-1)
* `nextToken`: Optional token for pagination

### Examples

**With explicit knowledgeBaseId:**

```typescript
import { retrieveFromKnowledgeBase } from '@minded-ai/mindedjs';

const results = await retrieveFromKnowledgeBase({
  knowledgeBaseId: 'kb-abc123',
  environment: 'production',
  query: 'How do I reset my password?',
  numberOfResults: 10,
  filters: {
    equals: { key: 'label1', value: 'dept:support' },
  },
});

for (const result of results.retrievalResults) {
  console.log(`Score: ${result.score}`);
  console.log(`Content: ${result.content.text}`);
  console.log(`Source: ${result.location.s3Location.uri}`);
}
```

## retrieveAndGenerateFromKnowledgeBase

Retrieve documents and generate a response using an LLM, with citations referencing source documents.

### Function Signature

```typescript
retrieveAndGenerateFromKnowledgeBase(
  params: {
    knowledgeBaseId?: string;
    environment: string;
    query: string;
    modelArn?: string;
    numberOfResults?: number;
    filters?: KnowledgeBaseFilter;
  },
  timeoutMs?: number
): Promise<{
  output: string | null;
  citations: KnowledgeBaseCitation[];
  sessionId?: string;
}>
```

### Parameters

| Parameter                | Type                | Required | Description                                                                                                                    |
| ------------------------ | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `params.knowledgeBaseId` | string              |          | ID of the knowledge base to query. If omitted, backend auto-selects the KB if exactly one exists; errors if 0 or >1 KBs exist. |
| `params.environment`     | string              | ✓        | Environment (e.g., 'development', 'production')                                                                                |
| `params.query`           | string              | ✓        | Search query string                                                                                                            |
| `params.modelArn`        | string              |          | Model ARN for generation (uses default if not specified)                                                                       |
| `params.numberOfResults` | number              |          | Number of documents to retrieve (default: 5)                                                                                   |
| `params.filters`         | KnowledgeBaseFilter |          | Metadata filters for the query                                                                                                 |
| `timeoutMs`              | number              |          | Query timeout (default: 60000ms)                                                                                               |

### Return Value

Returns a Promise with:

* `output`: Generated text response (null if no relevant documents found)
* `citations`: Array of citations, each containing:
  * `generatedResponsePart.textResponsePart.text`: The part of the response that cites this source
  * `generatedResponsePart.textResponsePart.span`: Start/end positions in the response
  * `retrievedReferences`: Array of source documents referenced
* `sessionId`: Optional session ID for conversation continuity

### Examples

**With explicit knowledgeBaseId:**

```typescript
import { retrieveAndGenerateFromKnowledgeBase } from '@minded-ai/mindedjs';

const result = await retrieveAndGenerateFromKnowledgeBase({
  knowledgeBaseId: 'kb-abc123',
  environment: 'production',
  query: 'Summarize the refund policy',
  numberOfResults: 5,
});

if (result.output) {
  console.log('Response:', result.output);

  // Process citations
  for (const citation of result.citations) {
    console.log('Cited text:', citation.generatedResponsePart.textResponsePart.text);

    for (const ref of citation.retrievedReferences) {
      console.log('  Source:', ref.location.s3Location.uri);
      console.log('  Content:', ref.content.text.substring(0, 100) + '...');
    }
  }
} else {
  console.log('No relevant documents found');
}
```

## Filtering

Both functions support metadata filters for precise document retrieval.

### Filter Types

```typescript
interface KnowledgeBaseFilter {
  equals?: { key: string; value: string };
  notEquals?: { key: string; value: string };
  greaterThan?: { key: string; value: string };
  lessThan?: { key: string; value: string };
  andAll?: KnowledgeBaseFilter[];
  orAll?: KnowledgeBaseFilter[];
}
```

### Filter Examples

```typescript
import { retrieveFromKnowledgeBase } from '@minded-ai/mindedjs';

// Filter by label1
const results = await retrieveFromKnowledgeBase({
  knowledgeBaseId: 'kb-abc123',
  environment: 'production',
  query: 'pricing information',
  filters: {
    equals: { key: 'label1', value: 'dept:sales' },
  },
});

// Combine multiple filters with andAll
const results2 = await retrieveFromKnowledgeBase({
  environment: 'production',
  query: 'pricing information',
  filters: {
    andAll: [
      { equals: { key: 'label1', value: 'dept:sales' } },
      { equals: { key: 'label2', value: 'lang:en' } }
    ],
  },
});
```


# Logging

MindedJS provides a structured logging system that helps you monitor, debug, and audit your agent's behavior. The logger is available throughout your agent code and provides consistent, contextual logging with proper PII handling.

## Using the Logger

Import the logger from MindedJS:

```ts
import { logger } from 'mindedjs';
```

The logger is also available in all tool contexts, so you can use it directly in your tools without importing.

## Log Levels

The logger supports standard log levels:

```ts
// Info level - general information
logger.info('Processing customer request');

// Warning level - potential issues
logger.warn('Customer tier not found, using default');

// Error level - errors and exceptions
logger.error('Failed to process payment');

// Debug level - detailed debugging information
logger.debug('Memory state updated');
```

## Structured Logging

Use structured logging with contextual data for better observability. Pass the message as the `msg` property:

```ts
// Basic structured logging
logger.info({
  msg: 'Order refunded',
  orderId: 'ORD-123',
  customerId: 'CUST-456',
  amount: 99.99,
});

// Include session context in tools
logger.info({
  msg: 'Tool execution started',
  sessionId: state.sessionId,
  toolName: 'refundOrder',
  orderId: input.orderId,
});
```

## Log Configuration

The logger can be configured through environment variables:

```env
# Set log level (debug, info, warn, error)
LOG_LEVEL=info

# In development, you might want debug level
LOG_LEVEL=debug
```

## Best Practices

1. **Always Include Session Context**: Include `sessionId` in all tool-related logs
2. **Use Appropriate Log Levels**: Don't use `info` for debugging information
3. **Structure Your Data**: Use objects for structured logging rather than strings
4. **Log State Changes**: Log important state transitions and memory updates
5. **Error Context**: Use `err` parameter for error objects to get proper stack traces
6. **Performance Metrics**: Log timing information for performance monitoring


# Parallel LLM Requests

Parallel LLM requests can significantly reduce latency by sending multiple identical requests and using the fastest response. This feature can reduce response times by 30-50% in scenarios with variable network conditions.

## Quick Start

The easiest way to enable parallel requests is through your `minded.json` configuration:

```json
{
  "flows": ["./src/flows"],
  "tools": ["./src/tools"],
  "agent": "./src/agent.ts",
  "llm": {
    "name": "MindedChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "numParallelRequests": 3,
      "logTimings": true
    }
  }
}
```

Your agent will automatically use parallel requests for all LLM calls:

```typescript
import { Agent } from '@minded-ai/mindedjs';
import memorySchema from './agentMemorySchema';
import config from '../minded.json';
import tools from './tools';

const agent = new Agent({
  memorySchema,
  config, // Parallel configuration is automatically applied
  tools,
});
```

## Configuration Options

### MindedChatOpenAI (Recommended)

For agents running on the Minded platform, use `MindedChatOpenAI` with parallel configuration:

```json
{
  "llm": {
    "name": "MindedChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "numParallelRequests": 3,
      "logTimings": true,
      "temperature": 0.7
    }
  }
}
```

### AzureChatOpenAI

For Azure OpenAI deployments:

```json
{
  "llm": {
    "name": "AzureChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "numParallelRequests": 3,
      "logTimings": true,
      "azureOpenAIApiVersion": "2024-02-01"
    }
  }
}
```

Required environment variables:

```env
AZURE_OPENAI_API_KEY=your_azure_key
AZURE_OPENAI_API_INSTANCE_NAME=your_instance_name
AZURE_OPENAI_API_DEPLOYMENT_NAME=your_deployment_name
```

### ChatOpenAI

For standard OpenAI API:

```json
{
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "numParallelRequests": 3,
      "logTimings": true,
      "openAIApiKey": "${OPENAI_API_KEY}"
    }
  }
}
```

## Configuration Parameters

| Parameter             | Type    | Default | Description                                   |
| --------------------- | ------- | ------- | --------------------------------------------- |
| `numParallelRequests` | number  | 1       | Number of parallel requests (2-5 recommended) |
| `logTimings`          | boolean | false   | Enable detailed timing logs                   |

## Performance Notes

* **Optimal Range**: 2-3 parallel requests usually provide the best latency/cost balance
* **Cost Impact**: You pay for all parallel requests made
* **Best Use Cases**: Variable network conditions, consistency requirements
* **Latency Reduction**: Typically 30-50% faster response times

## Monitoring Performance

When `logTimings: true` is enabled, you'll see detailed performance logs:

```
[Model] Fastest request completed { requestTime: 1.234, numParallelRequests: 3 }
[Model] Time saved using parallel requests {
  fastestRequestTime: 1.234,
  secondFastestRequestTime: 1.567,
  allFinishTime: 2.345,
  timeSaved: 1.111,
  timeSavedFromSecond: 0.333
}
```

## Advanced Usage

### Dynamic Configuration

You can adjust parallel requests based on environment:

```json
{
  "llm": {
    "name": "MindedChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "numParallelRequests": "${NODE_ENV === 'production' ? 3 : 1}",
      "logTimings": "${NODE_ENV === 'development'}"
    }
  }
}
```

### Manual Instantiation with createParallelWrapper

For advanced use cases where you need direct control over the LLM instance, you can manually apply parallel wrapping:

```typescript
import { createParallelWrapper } from '@minded-ai/mindedjs';
import { ChatOpenAI, AzureChatOpenAI } from '@langchain/openai';

// Manual wrapping for ChatOpenAI
const parallelOpenAI = createParallelWrapper(
  new ChatOpenAI({
    openAIApiKey: process.env.OPENAI_API_KEY,
    model: 'gpt-4o',
    temperature: 0,
  }),
  {
    numParallelRequests: 3,
    logTimings: true,
  },
);

// Manual wrapping for AzureChatOpenAI
const parallelAzure = createParallelWrapper(
  new AzureChatOpenAI({
    azureOpenAIApiKey: process.env.AZURE_OPENAI_API_KEY,
    azureOpenAIApiInstanceName: process.env.AZURE_OPENAI_INSTANCE!,
    azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT!,
    azureOpenAIApiVersion: '2024-02-01',
  }),
  {
    numParallelRequests: 2,
    logTimings: false,
  },
);

// Use directly with Agent (bypassing configuration)
const agent = new Agent({
  memorySchema,
  config: {
    flows: ['./flows'],
    tools: [],
    llm: parallelOpenAI as any, // Direct LLM instance
  },
  tools: [],
});
```

**Note**: The configuration-based approach is recommended for most use cases as it's simpler and more maintainable. Use manual instantiation only when you need specific control over LLM creation or are integrating with existing LangChain workflows.

## How It Works

### MindedChatOpenAI (Backend Processing)

* Parallel requests are handled on the Minded platform backend
* Multiple requests sent to Azure OpenAI from the backend
* Fastest response returned to your agent
* Optimal for production deployments

### Other LLM Providers (Client-Side Processing)

* Parallel requests handled in your application
* Multiple requests sent directly to the LLM provider
* Good for development and custom deployments

## Best Practices

1. **Start Small**: Begin with 2-3 parallel requests
2. **Monitor Costs**: Each parallel request counts toward your usage
3. **Enable Logging**: Use `logTimings: true` during development to measure improvements
4. **Environment-Specific**: Use fewer parallel requests in development
5. **Configuration Over Code**: Prefer `minded.json` configuration over manual instantiation

## Troubleshooting

### No Performance Improvement

* Check network latency variability
* Ensure `numParallelRequests > 1`
* Verify timing logs are showing multiple requests

### Increased Costs

* Reduce `numParallelRequests`
* Consider cost vs. latency trade-offs
* Monitor usage patterns

### Rate Limiting

* Lower `numParallelRequests`
* Implement backoff strategies
* Contact provider about rate limits


# Overview

The Minded REST API provides programmatic access to analytics events and knowledge base document management.

## Base URL

```
https://api.minded.com/api/v1
```

## Authentication

All API requests require an **organization API token** passed via the `Authorization` header:

```http
Authorization: Bearer md_yourTokenHere
```

### Token Characteristics

* **Scoped**: Permissions are granted per token
* **Org-bound**: Requests are authorized for the organization tied to the token
* **One-time visible**: The plaintext token is shown only when created and cannot be recovered

### Managing Tokens

In the Minded dashboard, navigate to **Organization Settings → API Tokens** to create, view, or revoke tokens.

### Token Scopes

| Scope                  | Grants Access To                |
| ---------------------- | ------------------------------- |
| `analytics`            | [Analytics API](/api/analytics) |
| `knowledge-management` | [Knowledge API](/api/knowledge) |

## Rate Limiting

Rate limits vary by deployment (default: \~10 requests/minute). Response headers indicate current limits:

| Header                | Description                  |
| --------------------- | ---------------------------- |
| `RateLimit-Limit`     | Maximum requests allowed     |
| `RateLimit-Remaining` | Requests remaining in window |
| `RateLimit-Reset`     | Seconds until limit resets   |

## Response Format

All responses are JSON. Successful responses typically include:

```json
{
  "success": true,
  "data": { ... }
}
```

## Error Codes

| Code | Description                                                                 |
| ---- | --------------------------------------------------------------------------- |
| 400  | Bad request — missing required fields or invalid parameters                 |
| 401  | Unauthorized — invalid or missing token                                     |
| 403  | Forbidden — token missing required scope                                    |
| 404  | Not found — resource does not exist or does not belong to your organization |
| 429  | Too many requests — rate limit exceeded                                     |
| 500  | Internal server error                                                       |

Error responses include details:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Missing required field: agentId"
  }
}
```


# Analytics API

Query agent analytics events for dashboards, BI tools, and audits.

**Required scope**: `analytics`

## Endpoints

| Method | Endpoint        | Description                      |
| ------ | --------------- | -------------------------------- |
| GET    | `/agents/list`  | List agents in your organization |
| POST   | `/events/agent` | Query agent analytics events     |

***

## List Agents

```http
GET /agents/list
```

Returns all agents belonging to your organization.

### Response

```json
{
  "success": true,
  "data": [
    {
      "agentId": "agent-uuid-1",
      "displayName": "My Support Agent",
      "createdAt": "2025-10-01T10:30:00.000Z"
    }
  ],
  "count": 1
}
```

### Response Fields

| Field                | Type   | Description                     |
| -------------------- | ------ | ------------------------------- |
| `data[].agentId`     | string | Unique agent identifier         |
| `data[].displayName` | string | Human-readable agent name       |
| `data[].createdAt`   | string | ISO 8601 creation timestamp     |
| `count`              | number | Total number of agents returned |

***

## Query Agent Events

```http
POST /events/agent
```

Query analytics events for a specific agent.

### Request Body

| Field                 | Type   | Required | Description                                                      |
| --------------------- | ------ | -------- | ---------------------------------------------------------------- |
| `agentId`             | string | Yes      | Agent UUID (must belong to your organization)                    |
| `filters.environment` | string | No       | `development`, `staging`, or `production`. Default: `production` |
| `filters.sessionId`   | string | No       | Filter by specific session UUID                                  |
| `filters.eventName`   | string | No       | Filter by event type                                             |
| `filters.startDate`   | string | No       | Start date (ISO 8601)                                            |
| `filters.endDate`     | string | No       | End date (ISO 8601)                                              |
| `limit`               | number | No       | Records per page (1–1000). Default: 100                          |
| `offset`              | number | No       | Records to skip. Default: 0                                      |

### Request Example

```json
{
  "agentId": "your-agent-id",
  "filters": {
    "environment": "production",
    "startDate": "2025-10-01T00:00:00Z",
    "endDate": "2025-10-31T23:59:59Z"
  },
  "limit": 100,
  "offset": 0
}
```

### Response

```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "agentId": "your-agent-id",
      "sessionId": "session-uuid",
      "environment": "production",
      "eventName": "invocation_start",
      "payload": { ... },
      "created_at": "2025-10-14T08:37:44.000Z"
    }
  ],
  "count": 1,
  "limit": 100,
  "offset": 0
}
```

### Response Fields

| Field                | Type   | Description                                |
| -------------------- | ------ | ------------------------------------------ |
| `data[].id`          | number | Event record ID                            |
| `data[].agentId`     | string | Agent UUID                                 |
| `data[].sessionId`   | string | Session UUID                               |
| `data[].environment` | string | Environment where event occurred           |
| `data[].eventName`   | string | Event type                                 |
| `data[].payload`     | object | Event-specific data                        |
| `data[].created_at`  | string | ISO 8601 timestamp when event was recorded |
| `count`              | number | Number of events in current page           |
| `limit`              | number | Page size used                             |
| `offset`             | number | Offset used                                |

## Event Types

The SDK automatically tracks built-in events. You can also emit custom events.

**Built-in events**: `invocation_start`, `invocation_error`, `node_execution`, and more.

**Custom events**: Use `trackAnalyticsEvent()` from your agent code.

See [SDK Events - ANALYTICS\_EVENT](/sdk/events#analytics_event) for details.


# Knowledge API

Manage documents within knowledge bases programmatically. Upload, update, retrieve metadata, and delete documents.

**Required scope**: `knowledge-management`

> **Prerequisite**: Knowledge bases are created in the platform dashboard. See [Knowledge Bases](/platform/knowledge-bases). This API manages documents within existing knowledge bases.

## Document Upload Flow

Uploading a document requires three steps:

```
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  1. Request     │     │  2. Upload      │     │  3. Complete    │     │  4. Poll        │
│  Upload Link    │────▶│  File           │────▶│  Upload         │────▶│  Status         │
└─────────────────┘     └─────────────────┘     └─────────────────┘     └─────────────────┘
        │                       │                       │                       │
        ▼                       ▼                       ▼                       ▼
   POST /upload-link      PUT to uploadUrl      POST /upload-complete    GET /documents
   Returns: uploadUrl,    Include headers       Returns: status          Check status
   documentId, s3Key      from response         "uploaded"               until "ready"
```

1. **Request upload link** — Call `/knowledge/documents/upload-link` to get a pre-signed upload URL
2. **Upload file** — PUT the file bytes to the returned `uploadUrl` with `requiredHeaders`
3. **Complete upload** — Call `/knowledge/documents/upload-complete` with the `uploadId`
4. **Poll status** — Query `/knowledge/documents` until `status` becomes `ready` or `failed`

## Document Status Values

| Status     | Description                                                 |
| ---------- | ----------------------------------------------------------- |
| `uploaded` | Document uploaded, processing in progress                   |
| `ready`    | Document processed successfully and available for retrieval |
| `failed`   | Processing failed; check error details                      |

## Document Identifiers

Documents can be referenced using two identifiers:

| Identifier         | Description                                                                                                                                                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `s3Key`            | System-generated document path. Returned by upload endpoints; use for subsequent operations.                                                                                                                               |
| `customDocumentId` | Optional user-defined identifier. Use this to map documents to your external system IDs (e.g., CRM record ID, ticket ID). When provided during upload, you can use it instead of `s3Key` for update and delete operations. |

## Document Labels (label1 and label2)

Documents support two optional label fields for filtering and organization:

* **`label1`**: Primary classification label
* **`label2`**: Secondary classification label

Both labels use a **`namespace:value`** format to prevent high-cardinality values:

```json
{
  "label1": "dept:support",
  "label2": "lang:en"
}
```

### Label Requirements

* **Format**: Must match `namespace:value` pattern (e.g., `topic:billing`, `tier:premium`)
* **Characters**: Only lowercase letters, numbers, hyphens, and underscores (`[a-z0-9_-]`)
* **Max length**: 50 characters
* **Restrictions**: Cannot contain UUIDs, timestamps, or ISO dates

### Label Examples

**Valid:**

* ✅ `dept:billing`
* ✅ `topic:faq`
* ✅ `lang:en-us`
* ✅ `tier:premium`

**Invalid:**

* ❌ `billing` (missing namespace)
* ❌ `Dept:Billing` (uppercase letters)
* ❌ `user:12345678-1234-1234-1234-123456789012` (UUID pattern)

Use labels to filter documents during retrieval (see [Knowledge Base RAG API](/sdk/knowledge-base-rag)).

***

## Custom Metadata (customMeta1–4)

Documents also support up to **four free-form custom metadata fields** for attaching arbitrary string data.

* **`customMeta1`** through **`customMeta4`**: Any string value, no format restriction

```json
{
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith",
  "customMeta3": "v2.1",
  "customMeta4": "legal"
}
```

### Custom Metadata Requirements

* **Format**: Free-form string, any value is accepted
* **Max length**: 500 characters per field
* **Not filterable**: Custom metadata cannot be used as query filters (unlike `label1`/`label2`)

### How Custom Metadata is Returned

Custom metadata fields are returned in **every retrieved chunk** alongside the document's text. Consumers can read them from `result.metadata.minded_internal_meta1` through `result.metadata.minded_internal_meta4` in raw retrieval responses.

> **Tip**: Use custom metadata to attach source identifiers, version numbers, author names, or any context you want to surface alongside retrieved chunks — without the strict format requirements of filter labels.

***

## Endpoints

| Method | Endpoint                                      | Description                           |
| ------ | --------------------------------------------- | ------------------------------------- |
| POST   | `/knowledge/documents/upload-link`            | Get pre-signed URL for new document   |
| POST   | `/knowledge/documents/upload-complete`        | Finalize new document upload          |
| POST   | `/knowledge/documents/update/upload-link`     | Get pre-signed URL to update document |
| POST   | `/knowledge/documents/update/upload-complete` | Finalize document update              |
| PATCH  | `/knowledge/documents/metadata`               | Update document metadata only         |
| GET    | `/knowledge/documents`                        | Get document metadata                 |
| DELETE | `/knowledge/documents`                        | Delete a document                     |

***

## Create Upload Link

```http
POST /knowledge/documents/upload-link
```

Generate a pre-signed URL to upload a new document.

### Request Body

| Field              | Type   | Required | Description                                                                      |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------- |
| `agentId`          | string | Yes      | Agent UUID                                                                       |
| `environment`      | string | Yes      | `development`, `staging`, or `production`                                        |
| `knowledgeBaseId`  | string | Yes      | Knowledge base UUID                                                              |
| `fileName`         | string | Yes      | File name with extension (e.g., `policy.pdf`). If `.zip`, triggers batch import. |
| `label1`           | string | No       | Primary label in `namespace:value` format (e.g., `dept:support`)                 |
| `label2`           | string | No       | Secondary label in `namespace:value` format (e.g., `lang:en`)                    |
| `customMeta1`      | string | No       | Free-form custom metadata field 1 (max 500 chars)                                |
| `customMeta2`      | string | No       | Free-form custom metadata field 2 (max 500 chars)                                |
| `customMeta3`      | string | No       | Free-form custom metadata field 3 (max 500 chars)                                |
| `customMeta4`      | string | No       | Free-form custom metadata field 4 (max 500 chars)                                |
| `customDocumentId` | string | No       | Your external identifier for this document                                       |

### Request Example

```json
{
  "agentId": "your-agent-id",
  "environment": "production",
  "knowledgeBaseId": "kb-abc123",
  "fileName": "refund-policy.pdf",
  "label1": "dept:support",
  "label2": "lang:en",
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith",
  "customDocumentId": "DOC-12345"
}
```

### Response

```json
{
  "uploadId": "upload-uuid",
  "uploadUrl": "https://storage.example.com/...",
  "expiresIn": 3600,
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "documentId": "document-uuid",
  "requiredHeaders": {
    "Content-Type": "application/pdf"
  }
}
```

### Response Fields

| Field             | Type   | Description                                                             |
| ----------------- | ------ | ----------------------------------------------------------------------- |
| `uploadId`        | string | Unique identifier for this upload session                               |
| `uploadUrl`       | string | Pre-signed URL for file upload (expires per `expiresIn`)                |
| `expiresIn`       | number | Seconds until `uploadUrl` expires                                       |
| `s3Key`           | string | Document path; use for subsequent operations                            |
| `documentId`      | string | System-generated document UUID (or your `customDocumentId` if provided) |
| `requiredHeaders` | object | Headers to include when uploading to `uploadUrl`                        |

### Uploading the File

After receiving the response, upload your file:

```bash
curl -X PUT "<uploadUrl>" \
  -H "Content-Type: application/pdf" \
  --data-binary @refund-policy.pdf
```

***

## Complete Upload

```http
POST /knowledge/documents/upload-complete
```

Finalize the upload and trigger document processing.

### Request Body

| Field      | Type   | Required | Description                              |
| ---------- | ------ | -------- | ---------------------------------------- |
| `uploadId` | string | Yes      | The `uploadId` from upload-link response |

### Request Example

```json
{
  "uploadId": "upload-uuid"
}
```

### Response (Document)

```json
{
  "type": "document",
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "documentId": "document-uuid",
  "createdAt": "2026-01-15T12:00:00.000Z",
  "updatedAt": "2026-01-15T12:00:00.000Z",
  "status": "uploaded"
}
```

### Response (ZIP Import)

When `fileName` ends with `.zip`, the response indicates a batch import job:

```json
{
  "type": "zip_import",
  "zipJobId": "job-uuid",
  "status": "pending",
  "s3Key": "agents/<agentId>/production/<kbId>/archive.zip"
}
```

### Response Fields

| Field        | Type   | Description                                                      |
| ------------ | ------ | ---------------------------------------------------------------- |
| `type`       | string | `document` for single files, `zip_import` for archives           |
| `s3Key`      | string | Document path; use for subsequent operations                     |
| `documentId` | string | Document UUID (for `document` type)                              |
| `zipJobId`   | string | Import job UUID (for `zip_import` type)                          |
| `status`     | string | Processing status (see [Status Values](#document-status-values)) |
| `createdAt`  | string | ISO 8601 creation timestamp                                      |
| `updatedAt`  | string | ISO 8601 last update timestamp                                   |

### ZIP Import with Per-File Metadata

When uploading a ZIP file, you can include per-file metadata by adding a CSV file to the archive.

**Default Behavior:**

* All files in the ZIP inherit the `label1` and `label2` values from the upload request
* Example: Upload with `label1: "dept:support"` → all files get `dept:support`

**Per-File Metadata (CSV):** Include a `.csv` file in your ZIP with this structure:

```csv
filename,label1,label2
policy.pdf,dept:legal,type:policy
guide.pdf,dept:support,type:guide
faq.html,dept:support,topic:faq
```

**CSV Format:**

* **Header row**: Optional (if present, will be ignored)
* **First column**: File identifier - either:
  * `filename`: Full filename with extension (e.g., `policy.pdf`)
  * `uniqueId`: Your custom document ID (if using `customDocumentId`)
* **Second column**: `label1` value (optional)
* **Third column**: `label2` value (optional)

**Processing Rules:**

1. Files listed in CSV get their specific labels
2. Files NOT in CSV get default labels from upload request
3. Empty label columns in CSV = no label for that field
4. CSV file itself is not processed as a document

**Example ZIP Structure:**

```
documents.zip
├── metadata.csv          ← Per-file labels
├── policy.pdf           ← Gets labels from CSV
├── guide.pdf            ← Gets labels from CSV
└── terms.pdf            ← Gets default labels from upload request
```

**Upload Request:**

```json
{
  "agentId": "your-agent-id",
  "environment": "production",
  "knowledgeBaseId": "kb-abc123",
  "fileName": "documents.zip",
  "label1": "dept:general",
  "label2": "lang:en"
}
```

**Result:**

* `policy.pdf` → `label1: "dept:legal"`, `label2: "type:policy"` (from CSV)
* `guide.pdf` → `label1: "dept:support"`, `label2: "type:guide"` (from CSV)
* `terms.pdf` → `label1: "dept:general"`, `label2: "lang:en"` (defaults from request)

***

## Create Update Upload Link

```http
POST /knowledge/documents/update/upload-link
```

Generate a pre-signed URL to replace an existing document's content.

### Request Body

| Field              | Type   | Required | Description                                                           |
| ------------------ | ------ | -------- | --------------------------------------------------------------------- |
| `agentId`          | string | Yes      | Agent UUID                                                            |
| `environment`      | string | Yes      | `development`, `staging`, or `production`                             |
| `knowledgeBaseId`  | string | Yes      | Knowledge base UUID                                                   |
| `s3Key`            | string | \*       | Document path to update. Required if `customDocumentId` not provided. |
| `customDocumentId` | string | \*       | Your external document ID. Required if `s3Key` not provided.          |
| `fileName`         | string | Yes      | New file name                                                         |
| `label1`           | string | No       | Updated primary label in `namespace:value` format                     |
| `label2`           | string | No       | Updated secondary label in `namespace:value` format                   |
| `customMeta1`      | string | No       | Free-form custom metadata field 1 (max 500 chars)                     |
| `customMeta2`      | string | No       | Free-form custom metadata field 2 (max 500 chars)                     |
| `customMeta3`      | string | No       | Free-form custom metadata field 3 (max 500 chars)                     |
| `customMeta4`      | string | No       | Free-form custom metadata field 4 (max 500 chars)                     |

> **Note**: Provide either `s3Key` or `customDocumentId`, not both.

### Request Example

```json
{
  "agentId": "your-agent-id",
  "environment": "production",
  "knowledgeBaseId": "kb-abc123",
  "customDocumentId": "DOC-12345",
  "fileName": "refund-policy-v2.pdf",
  "label1": "dept:support",
  "label2": "lang:en",
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith"
}
```

### Response

```json
{
  "uploadId": "upload-uuid",
  "uploadUrl": "https://storage.example.com/...",
  "expiresIn": 3600,
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy-v2.pdf",
  "requiredHeaders": {
    "Content-Type": "application/pdf"
  }
}
```

***

## Complete Update Upload

```http
POST /knowledge/documents/update/upload-complete
```

Finalize document update and trigger reprocessing.

### Request Body

| Field      | Type   | Required | Description                                     |
| ---------- | ------ | -------- | ----------------------------------------------- |
| `uploadId` | string | Yes      | The `uploadId` from update/upload-link response |

### Request Example

```json
{
  "uploadId": "upload-uuid"
}
```

### Response

```json
{
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy-v2.pdf",
  "documentId": "document-uuid",
  "updatedAt": "2026-01-16T12:00:00.000Z",
  "status": "uploaded"
}
```

***

## Update Metadata

```http
PATCH /knowledge/documents/metadata
```

Update document labels and/or custom metadata without re-uploading the file.

### Request Body

| Field              | Type   | Required | Description                                                                      |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------- |
| `agentId`          | string | Yes      | Agent UUID                                                                       |
| `environment`      | string | Yes      | `development`, `staging`, or `production`                                        |
| `knowledgeBaseId`  | string | Yes      | Knowledge base UUID                                                              |
| `s3Key`            | string | \*       | Document path. Required if `customDocumentId` not provided.                      |
| `customDocumentId` | string | \*       | Your external document ID. Required if `s3Key` not provided.                     |
| `label1`           | string | No       | Updated primary label in `namespace:value` format                                |
| `label2`           | string | No       | Updated secondary label in `namespace:value` format                              |
| `customMeta1`      | string | No       | Updated free-form custom metadata field 1 (max 500 chars). Pass `null` to clear. |
| `customMeta2`      | string | No       | Updated free-form custom metadata field 2 (max 500 chars). Pass `null` to clear. |
| `customMeta3`      | string | No       | Updated free-form custom metadata field 3 (max 500 chars). Pass `null` to clear. |
| `customMeta4`      | string | No       | Updated free-form custom metadata field 4 (max 500 chars). Pass `null` to clear. |

### Request Example

```json
{
  "agentId": "your-agent-id",
  "environment": "production",
  "knowledgeBaseId": "kb-abc123",
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "label1": "dept:support",
  "label2": "status:reviewed",
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith"
}
```

### Response

```json
{
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "documentId": "document-uuid",
  "label1": "dept:support",
  "label2": "status:reviewed",
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith",
  "customMeta3": null,
  "customMeta4": null,
  "updatedAt": "2026-01-16T14:00:00.000Z"
}
```

***

## Get Document

```http
GET /knowledge/documents
```

Retrieve metadata for a specific document.

### Query Parameters

| Parameter          | Type   | Required | Description                                                  |
| ------------------ | ------ | -------- | ------------------------------------------------------------ |
| `agentId`          | string | Yes      | Agent UUID                                                   |
| `environment`      | string | Yes      | `development`, `staging`, or `production`                    |
| `knowledgeBaseId`  | string | Yes      | Knowledge base UUID                                          |
| `s3Key`            | string | \*       | Document path. Required if `customDocumentId` not provided.  |
| `customDocumentId` | string | \*       | Your external document ID. Required if `s3Key` not provided. |

### Request Example

```
GET /knowledge/documents?agentId=your-agent-id&environment=production&knowledgeBaseId=kb-abc123&customDocumentId=DOC-12345
```

### Response

```json
{
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "documentId": "document-uuid",
  "status": "ready",
  "contentType": "application/pdf",
  "fileSize": 204800,
  "label1": "dept:support",
  "label2": "lang:en",
  "customMeta1": "DOC-98765",
  "customMeta2": "John Smith",
  "customMeta3": null,
  "customMeta4": null,
  "createdAt": "2026-01-15T12:00:00.000Z",
  "updatedAt": "2026-01-16T14:00:00.000Z"
}
```

### Response Fields

| Field         | Type         | Description                                                                                   |
| ------------- | ------------ | --------------------------------------------------------------------------------------------- |
| `s3Key`       | string       | Document path; use for subsequent operations                                                  |
| `documentId`  | string       | Document UUID (your `customDocumentId` if provided during upload, otherwise system-generated) |
| `status`      | string       | Processing status (see [Status Values](#document-status-values))                              |
| `contentType` | string       | MIME type of the document (e.g., `application/pdf`)                                           |
| `fileSize`    | number       | File size in bytes                                                                            |
| `label1`      | string\|null | Primary label in `namespace:value` format                                                     |
| `label2`      | string\|null | Secondary label in `namespace:value` format                                                   |
| `customMeta1` | string\|null | Free-form custom metadata field 1                                                             |
| `customMeta2` | string\|null | Free-form custom metadata field 2                                                             |
| `customMeta3` | string\|null | Free-form custom metadata field 3                                                             |
| `customMeta4` | string\|null | Free-form custom metadata field 4                                                             |
| `createdAt`   | string       | ISO 8601 creation timestamp                                                                   |
| `updatedAt`   | string       | ISO 8601 last update timestamp                                                                |

***

## Delete Document

```http
DELETE /knowledge/documents
```

Remove a document from the knowledge base.

### Request Body

| Field              | Type   | Required | Description                                                  |
| ------------------ | ------ | -------- | ------------------------------------------------------------ |
| `agentId`          | string | Yes      | Agent UUID                                                   |
| `environment`      | string | Yes      | `development`, `staging`, or `production`                    |
| `knowledgeBaseId`  | string | Yes      | Knowledge base UUID                                          |
| `s3Key`            | string | \*       | Document path. Required if `customDocumentId` not provided.  |
| `customDocumentId` | string | \*       | Your external document ID. Required if `s3Key` not provided. |

### Request Example

```json
{
  "agentId": "your-agent-id",
  "environment": "production",
  "knowledgeBaseId": "kb-abc123",
  "customDocumentId": "DOC-12345"
}
```

### Response

```json
{
  "success": true,
  "s3Key": "agents/<agentId>/production/<kbId>/refund-policy.pdf",
  "documentId": "document-uuid"
}
```


# Secrets

Secrets allow you to securely store sensitive configuration data like API keys and database credentials in your deployed MindedJS agents.

## Overview

Secrets are configured through the platform dashboard and automatically injected into `process.env` when your agent is deployed. They are only available in deployed agents, not during local development.

**Key Features:**

* Secure storage on the platform backend
* Automatic injection into `process.env` for deployed agents
* Environment-specific configuration (dev/staging/production)
* Deploy-time access only

## Usage

### Basic Access

```typescript
// Access secrets via process.env in deployed agents
const apiKey = process.env.OPENAI_API_KEY;
const databaseUrl = process.env.DATABASE_URL;

// Always validate required secrets
if (!apiKey) {
  throw new Error('OPENAI_API_KEY secret not configured');
}
```

### In Tools

```typescript
import { Tool } from 'mindedjs';

const weatherTool: Tool<{ city: string }, any> = {
  name: 'getWeather',
  description: 'Get weather information for a city',
  parameters: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'The city to get weather for' }
    },
    required: ['city']
  },
  handler: async ({ city }) => {
    const apiKey = process.env.WEATHER_API_KEY;
    
    if (!apiKey) {
      throw new Error('WEATHER_API_KEY secret not configured');
    }
    
    const response = await fetch(`https://api.weather.com/v1/current?key=${apiKey}&q=${city}`);
    return await response.json();
  }
};
```

## Configuration

### Platform Management

Configure secrets in the MindedJS platform dashboard:

* Navigate to your agent's settings
* Use the secrets management interface to add/edit secrets
* Configure different values per environment
* Changes require redeployment to take effect

### Naming Conventions

Use `UPPER_SNAKE_CASE` with descriptive names:

* `OPENAI_API_KEY`
* `DATABASE_URL`
* `WEBHOOK_SECRET`
* `JWT_SIGNING_KEY`

## Local Development

When developing locally, you can use the `.env` file (in the root of your project) to set the secrets or environment variables.

```bash
# .env file
MINDED_CONNECTION_TOKEN=abc
API_KEY=xyz
```

## Best Practices

* **Always validate** that required secrets exist before use
* **Never log** secret values in console output or logs
* **Use descriptive names** for clarity
* **Separate environments** with different secret values
* **Never commit** `.env` files or hardcode secrets
* **Regular rotation** of secrets through the platform

## Troubleshooting

* **Secret not available**: Verify it's configured in platform dashboard with exact name (case-sensitive) and agent is deployed
* **Local development**: Remember secrets only work in deployed agents - use `.env` files or fallbacks locally
* **Changes not applied**: Secret changes require redeployment to take effect


# Knowledge Bases

Knowledge bases let you upload documents (with metadata), keep them synced per environment, and query them at runtime from your agent.

## Concepts

* **Knowledge base**: A named container of documents that belongs to a specific agent.
* **Document**: A file (PDF, TXT, DOCX, etc.) plus optional metadata used for filtering.
* **Environment**: Separate datasets per environment: `development`, `staging`, `production`.

## Manage knowledge bases in the platform (UI)

### Create a knowledge base

1. Open your agent in the Minded dashboard
2. Navigate to **Knowledge Bases**
3. Click **Create knowledge base**
4. Set a **name** (and optional description) and save

### Update a knowledge base

You can update a knowledge base’s **name** and **description** from the knowledge base settings/details view.

### Delete a knowledge base

Deleting a knowledge base removes it from the agent and triggers cleanup of its documents across environments.

## Upload and manage documents (UI)

From the knowledge base view, you can upload documents and set labels and custom metadata for filtering and context enrichment.

### Document Labels

Each document supports two optional labels in `namespace:value` format:

* **label1**: Primary classification (e.g., `dept:support`, `topic:billing`)
* **label2**: Secondary classification (e.g., `lang:en`, `tier:premium`)

Labels can be used to **filter documents during retrieval** — only chunks from matching documents are returned.

### Custom Metadata

Each document also supports up to **four free-form custom metadata fields** (Custom Metadata 1–4). Unlike labels, these:

* Accept any string value (no namespace format required)
* Are limited to **500 characters** each
* Are **not filterable** — they cannot be used to restrict retrieval results
* Are **returned alongside every retrieved chunk** in the raw retrieval response

Use custom metadata to attach source identifiers, version numbers, author names, or other context you want available at retrieval time.

Typical flow:

* Upload a document (and optionally set labels and/or custom metadata)
* Wait for ingestion/sync to complete
* Use the platform “Query” / “Generate” actions to test retrieval and citations

## Manage documents via API

For programmatic document management (upload, update, metadata, delete), use the Knowledge API:

* Base URL: `https://api.minded.com/api/v1`
* Scope required: `knowledge-management`

See the full reference in [Knowledge API](/api/knowledge).

## Platform knowledge APIs (advanced)

These endpoints are used by the Minded dashboard. They are helpful for internal automation and debugging.

> **Note:** The dashboard APIs are not the same as the customer-facing `/api/v1/*` APIs. They require dashboard authentication and are mounted under the dashboard service.

### Knowledge base CRUD

All routes are scoped to an agent via `:agentId`.

* **List knowledge bases**
  * **GET** `/dashboard/agents/:agentId/knowledge/bases`
* **Create a knowledge base**
  * **POST** `/dashboard/agents/:agentId/knowledge/bases`
  * Body:

    ```json
    {
      "name": "Support KB",
      "description": "Policies and FAQs"
    }
    ```
* **Get a knowledge base**
  * **GET** `/dashboard/agents/:agentId/knowledge/bases/:knowledgeBaseId`
* **Update a knowledge base**
  * **PATCH** `/dashboard/agents/:agentId/knowledge/bases/:knowledgeBaseId`
  * Body:

    ```json
    {
      "name": "Support KB (v2)",
      "description": "Updated description"
    }
    ```
* **Delete a knowledge base**
  * **DELETE** `/dashboard/agents/:agentId/knowledge/bases/:knowledgeBaseId`

### Query and generate (platform testing endpoints)

These endpoints are commonly used by the dashboard UI to test retrieval and “retrieve + generate”.

* **Retrieve only**
  * **POST** `/dashboard/agents/:agentId/knowledge/:environment/kb/:knowledgeBaseId/query`
  * Body:

    ```json
    {
      "query": "What is the refund policy?",
      "numberOfResults": 5
    }
    ```
* **Retrieve and generate (with citations)**
  * **POST** `/dashboard/agents/:agentId/knowledge/:environment/kb/:knowledgeBaseId/generate`
  * Body:

```json
{
  "query": "Summarize the refund policy",
  "numberOfResults": 5,
  "modelArn": "optional-model-identifier",
  "filters": {
    "equals": { "key": "label1", "value": "dept:support" }
  }
}
```

If you want to query from agent code (recommended for runtime usage), use the SDK methods documented in [Knowledge Base RAG API](/sdk/knowledge-base-rag).

## Enable knowledge base usage in an agent

There are two common patterns: **automatic default retrieval** configured from the platform, and **manual retrieval** from code.

### Option A: Automatic default retrieval (platform configuration)

The platform can configure “RAG defaults” for your agent. When enabled, MindedJS will:

* Run retrieval automatically **after `TRIGGER_EVENT`**
* Retrieve from one or more configured knowledge bases
* Filter by `minScore` (if provided)
* Inject the retrieved context into the conversation as a **system message**

#### Default retrieval settings

Default retrieval is configured **per environment** (`development`, `staging`, `production`). Each environment can include:

* `enabled`: turn default retrieval on/off
* `knowledgeBases`: list of knowledge bases to query on every trigger
  * `knowledgeBaseId`: the KB ID to query
  * `environment` (optional): override which KB environment to query (defaults to the current environment)
  * `numberOfResults` (optional): how many chunks to retrieve
  * `minScore` (optional): discard results below this similarity score threshold
  * `filters` (optional): metadata filters applied to retrieval

#### Filter operators and templating

Default retrieval filters support these operators:

* `equals`
* `notEquals`
* `greaterThan`
* `lessThan`

Filter values can be either:

* A **plain string** (example: `"policy"`)
* A **memory chip pattern** in the exact form: `{state.memory.<path>}`
  * Example: `{state.memory.language}`
  * Nested values are supported with dot notation: `{state.memory.user.locale}`
  * If the memory value is missing (`null`/`undefined`), that filter entry is **skipped**

Multiple filter entries are combined with **AND** semantics.

#### Example default retrieval configuration

This is an example of the underlying shape (shown here for clarity; in practice it’s configured via the platform UI):

```json
{
  "production": {
    "enabled": true,
    "knowledgeBases": [
      {
        "knowledgeBaseId": "kb-abc123",
        "numberOfResults": 5,
        "minScore": 0.6,
        "filters": [
          { "key": "label1", "operator": "equals", "value": "dept:support" },
          { "key": "label2", "operator": "equals", "value": "{state.memory.language}" }
        ]
      }
    ]
  }
}
```

### Option B: Manual retrieval from code (e.g., in `TRIGGER_EVENT`)

If you need full control over *when* retrieval happens and *how* results are applied, you can:

Import directly from the SDK:

* `retrieveFromKnowledgeBase(...)`
* `retrieveAndGenerateFromKnowledgeBase(...)`

See:

* [Knowledge Base RAG API](/sdk/knowledge-base-rag)
* [Events → TRIGGER\_EVENT](/sdk/events#trigger_event) (includes an example of querying a knowledge base during trigger qualification)


# PII Masking

## Overview

The Minded platform provides built-in protection for Personally Identifiable Information (PII) through an advanced masking mechanism. This feature ensures that sensitive data remains secure throughout your application's lifecycle while preserving the contextual integrity of your business logic.

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-8556c77a23f66724c1b728bd0fc2c1eece911402%2FPII-masking.png?alt=media" alt="PII Masking Flow Diagram"><figcaption></figcaption></figure>

## How to Use PII Masking

### Step 1: Configure PII Masking in the Minded Platform

To enable PII masking, go to the agent configuration panel and toggle **"Enable PII Masking"**. Then, configure the masking behavior according to your needs by specifying:

* **Entity Types**: Define which data entities should be treated as PII. Any entity that requires masking must be explicitly listed.
* **Detection Model**: By default, Minded uses OpenAI's GPT-4.1-mini for PII detection. Optionally, you can provide a custom Azure OpenAI deployment. Be sure to include the corresponding API key in the environment's secrets.
* **TTL Settings**: TTL (Time-to-Live) determines how long masked data is stored for a specific `sessionId`. The default TTL is 14 hours.
* **Trigger SessionId Field**: Specify the property name in your incoming webhook payload that contains the session ID. This field is used to identify and group PII data per session. It must be a single word with no special characters (only letters, numbers, and underscores allowed).

Once enabled, PII masking is automatically applied at critical points within the platform:

* All webhook payloads are masked upon receipt.
* App integrations undergo automatic unmasking before requests and re-masking after responses.

### Step 2: Use PII Masking via the Minded SDK

Minded provides automatic PII masking capabilities for HTTP requests within your agent workflows. When a trigger is received with a sessionId, the PII masking functionality is automatically initialized and made available to your custom code through the agent's PII gateway.

#### PII Gateway for HTTP Requests

The PII gateway enables secure HTTP communication by automatically handling PII masking/unmasking for your API calls. It provides a complete HTTP client interface similar to popular libraries like Axios, supporting all common HTTP methods.

The PII masking gateway ensures:

* **Automatic Unmasking**: Requests are automatically unmasked before being sent to external APIs
* **Secure Routing**: All requests are routed through Minded's secure gateway infrastructure
* **Response Masking**: API responses are automatically masked before being returned to your agent
* **Session Isolation**: PII data is properly isolated per session using the sessionId
* **Error Handling**: Comprehensive error handling for network issues and API failures

> **Note:** API error responses are **not** masked. The PII gateway requires a valid sessionId to function properly.

#### Available HTTP Methods

The PII gateway supports all standard HTTP methods:

```ts
// GET request
const userData = await agent.piiGateway.get(sessionId, 'https://api.example.com/user/123');

// POST request
const createResult = await agent.piiGateway.post(sessionId, 'https://api.example.com/users', {
  name: 'John Doe',
  email: 'john@example.com',
});

// PUT request
const updateResult = await agent.piiGateway.put(sessionId, 'https://api.example.com/user/123', {
  email: 'newemail@example.com',
});

// DELETE request
const deleteResult = await agent.piiGateway.delete(sessionId, 'https://api.example.com/user/123');

// PATCH request
const patchResult = await agent.piiGateway.patch(sessionId, 'https://api.example.com/user/123', {
  email: 'updated@example.com',
});
```

#### Example Usage in Tools

```ts
// POST request example - within a custom tool
import { Tool } from '@minded-ai/mindedjs';
import { logger } from '@minded-ai/mindedjs';

const modelParams = z.object({
  prev: z.string().describe('The email to update'),
  new: z.string().describe('The new email address'),
});

const updateEmailTool: Tool<any, any> = {
  name: 'updateEmail',
  description: 'Update the email',
  input: modelParams,
  execute: async ({ input, state, agent }) => {
    try {
      logger.info('Updating email via PII gateway', {
        sessionId: state.sessionId,
      });

      const response = await agent.piiGateway.post(state.sessionId, 'https://www.acme.com/update_email', {
        oldEmail: input.prev,
        newEmail: input.new,
      });

      logger.info('Email update successful', {
        sessionId: state.sessionId,
        status: response.status,
      });

      return {
        result: 'Email updated successfully',
        state: {
          memory: {
            emailUpdated: true,
            lastEmailUpdate: new Date().toISOString(),
          },
        },
      };
    } catch (err) {
      logger.error({
        message: 'Email update failed',
        sessionId: state.sessionId,
        err,
      });
      throw err;
    }
  },
};
```

#### Request Configuration

You can also pass additional configuration options:

```ts
const response = await agent.piiGateway.post(
  sessionId,
  'https://api.example.com/data',
  { data: 'example' },
  {
    headers: {
      Authorization: 'Bearer token',
      'Content-Type': 'application/json',
    },
    params: {
      version: 'v1',
      format: 'json',
    },
  },
);
```

#### How It Works

1. **Trigger Received**: When your agent receives a trigger with a sessionId, the PII masking gateway is automatically initialized and available via `agent.piiGateway`
2. **Session Binding**: The PII gateway is bound to the specific session context, ensuring proper PII isolation
3. **Request Processing**: When you make HTTP requests through the gateway:
   * Request data is automatically unmasked using session-specific mappings
   * The request is sent to the target API with real, unmasked data
   * The response is received and automatically masked before being returned
4. **Error Handling**: Network errors and API errors are properly handled and logged

## How PII Masking Works

### Input Processing

The masking workflow begins as data enters the Minded platform:

1. The platform receives input (e.g. JSON object or string) tagged with a unique `sessionId`.
2. PII entities are identified using the configured detection model.
3. Detected entities are replaced with structured placeholders such as:
   * `<PERSON1>`, `<PERSON2>` for names
   * `<PHONE1>` for phone numbers
   * `<ADDRESS1>`, `<ADDRESS2>` for addresses
   * Other entity types as defined in your configuration
4. A secure internal PII database (PII-DB) manages the mapping:
   * Each `sessionId` has its own isolated mapping store
   * Example mapping:

     ```
     "Kate" → <PERSON1>
     "Josh Wildon" → <PERSON2>
     ```
   * Consistent masking/unmasking is maintained throughout the session
   * Data is automatically purged from the PII-DB based on TTL settings

### Session ID Configuration

The **Trigger SessionId Field** setting in the UI specifies which field in your webhook payload contains the session identifier:

* **Field Name**: Must be a valid property name (letters, numbers, underscores only)
* **Examples**: `sessionId`, `session_id`, `conversationId`, `userId`
* **Purpose**: Groups all PII data for a specific user session together
* **Requirements**:
  * Single word identifier
  * No special characters except underscores
  * Must exist in every webhook payload

Example webhook payload where we set **Trigger SessionId Field** to **webhookId**:

```json
{
  "webhookId": "user_12345_conversation_67890",
  "customerName": "John Doe",
  "email": "john.doe@example.com",
  "orderDetails": "Need help with order #ABC123"
}
```

### Mask Consistency

The platform guarantees deterministic masking within a session:

* Each unique entity always receives the same placeholder
* New placeholders are generated only for previously unseen entities
* Cross-reference logic maintains consistent relationships between data
* TTL should be aligned with your agent's session lifecycle for optimal performance

### Logging and Storage

* All logs within the platform are written **only with masked data**
* Raw, unmasked data is **never** logged


# Operator

The Operator lets an agent run autonomous web actions using a live Chromium, guided by an LLM of choice. It supports structured inputs/outputs, live view previews, optional country proxying, and an on-prem mode.

## What it does

* **Plans + Executes**: The node first asks the LLM to propose parameters (based on your input schema) and compiles your prompt. It then starts a browser session and runs the task to completion.
* **Live session**: Shows a live view preview of the browser session.
* **Structured I/O**: Define `inputSchema` to guide the LLM’s tool-call arguments and `outputSchema` to hint desired output structure.
* **Tool access**: Exposes agent tools that allow execution requests to the browser agent.

## Configuration fields (node)

* **prompt (required)**: Natural-language instructions for the browser agent. Supports variables from the state object.
* **model (optional)**: Preferred LLM (UI defaults to `gpt-5.2`).
* **inputSchema (optional)**: Array of fields `{ name, type: 'string'|'number', description?, required? }`.
* **outputSchema (optional)**: Array of fields `{ name, type: 'string'|'number', description?, required? }`. Sent to the browser agent to shape outputs.
* **proxyConfig (optional)**: Unified proxy configuration object supporting Minded-managed proxies or custom proxy servers. Disabled when running on‑prem.
* **hooks (optional)**: Array of `{ name }`. Passed to the browser agent.

## On‑prem mode

By default, the browser agent runs on the cloud. For customers who want to run the browser agent on their own infrastructure & network, on-prem mode is supported out of the box by running the on-prem kit on a PC / cloud VM.

Please contact us to assist you with on-prem setup at <support@minded.com>.

### How it works

The on-prem kit acts as a CDP server that launches chromium locally and exposes the protocol via websocket to the Minded backend service thus while the browser agent runs on the cloud, the browser is running locally inside the customer's network allowing it to access internet services without whitelisting.

### Installation

1. Enable on-prem mode in the node configuration (onPrem: true)
2. Run the docker image with the agent ID and environment variables Production:

```
docker run --name --pull minded-browser-onprem -e AGENT_ID=[Agent ID] public.ecr.aws/o9t8x5z4/browser-use-onprem-kit:latest
```

## Proxy

The browser agent can be configured to use a proxy server. This is useful when the browser agent needs to access the internet through a proxy server or route traffic through a specific geographic location.

### Proxy Configuration Options

The `proxyConfig` field supports three configuration types:

**1. Minded Proxy - Trusted IP** (for IP whitelisting):

```yaml
proxyConfig:
  provider: minded
  mode: trusted_ip
```

**2. Minded Proxy - Region** (with country selection):

```yaml
proxyConfig:
  provider: minded
  mode: region
  countryCode: US # ISO-3166-1 alpha-2 country code (e.g., "US", "GB", "DE", "IL")
```

**3. Custom Proxy Server**:

```yaml
proxyConfig:
  provider: custom
  server: http://proxy.example.com:8080
  username: user # Optional
  password: pass # Optional
```

**Note**: Proxy configuration is disabled when running on‑prem mode.

## MFA

The browser agent can be configured to use MFA. Currently TOTP is supported out of the box for MFA login flow.

## Tools

Operators can leverage **tools**—reusable functions that the agent can call during a browser session to perform specific actions (e.g., extract data, interact with APIs, update memory, etc.). Tools are defined using the platform’s [Tool interface](/low-code-editor/tools), and can be made available to the browser agent for use within your browser task flows.

### How tools work in operators

* **Tool calls**: During a browser task, the LLM can decide to call any tool that is registered and exposed to the agent. The tool receives validated input, the current state, and the agent instance.
* **Structured input/output**: Tools use Zod schemas for input validation and can return structured results, which are sent back to the LLM and/or merged into agent memory.
* **Tools available to browser tasks**: Tools marked as `allowExecutionRequests: true` are available to all nodes, including browser tasks.

### Tool execution in operators

You can mention tool names in your browser task prompt to encourage the LLM to use them, or let the LLM decide when to call them based on context.

When the LLM calls a tool during a browser task:

* The tool’s `execute` function runs with the provided input and current state.
* Any returned `state` is merged into the agent’s memory/state.
* The `result` is sent back to the LLM and can be used in subsequent reasoning or actions.

For more on tool structure and best practices, see [Tools documentation](/low-code-editor/tools).

### Browser manipulation inside tool

You can manipulate the browser using a package of choice (e.g. puppeteer, playwright, etc.) by using the CDP URL that is passed to the tool in the state object.

## Operator node example

```yaml
# Flow YAML snippet illustrating a browserTask node
- name: 444f2347-dcc0-4c4e-b0af-3743033c8f5e
  type: browserTask
  displayName: Search product price in Amazon
  prompt: |
    Your task is to search for a product in Amazon and return the price of the product.

    1. Navigate to amazon.com
    2. Search for "iphone 15"
    3. Return the price of the product
  model: gpt-5.2
  inputSchema: []
  outputSchema: []
  proxyConfig:
    provider: minded
    mode: region
    countryCode: US
  onPrem: false
```


# SSO (Single Sign-On)

## Overview

The Minded platform supports SAML-based Single Sign-On (SSO) as an **enterprise feature** for authentication. This allows organizations to integrate their existing identity provider (IdP) with the Minded platform for seamless and secure user authentication.

## Configuration

Setting up SSO involves exchanging configuration details between Minded (as the Service Provider) and your organization's Identity Provider.

### From Minded (Service Provider → IdP)

When initiating SSO setup, Minded will provide the following configuration details:

* **Identifier (Entity ID)**: A unique identifier for the Minded platform as a service provider
* **Reply URL**: The endpoint where your IdP will send SAML responses after authentication

### From Customer (IdP → Service Provider)

Once SSO is configured on your organization's side, you will need to provide Minded with:

* **Identity Provider (IdP) metadata**: XML metadata file containing your IdP's configuration
* **SAML signing certificates**: Certificates used to verify the authenticity of SAML assertions

## Support

To setup SSO configuration please contact the Minded support team.


# On-prem Deployment

## **Minded On-prem Deployment**

Minded supports on-prem deployment of the Operator, allowing AI Agents to run against your internal tools and APIs while ensuring that sensitive data such as PII never leaves your cloud.

## **Architecture**

Minded is designed to democratize agent development while maintaining enterprise-grade security. You can keep sensitive data such as PII in your VPC, while Minded manages the orchestration and tooling.

The architecture is split into two planes:

* **Control Plane**: the Minded platform where agents are built, orchestrated, and monitored.
* **Agentic Plane**: where the agent’s compute and data live, ensuring tasks run securely next to your systems, and can be safely deployed on your cloud.

The following diagram describes the overall Minded architecture.

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-8c457c77a44c05426f9b7300617aae826819b9bd%2Fon-prem.svg?alt=media" alt="Minded On-prem Architecture Diagram"><figcaption></figcaption></figure>

### Control Plane

#### Overview

The control plane forms the backbone of the Minded Platform. It runs entirely in Minded’s VPC and includes the key services that power agent development and orchestration:

* **Minded Studio** – a web application for building agents in both code and no-code.
* **Code (GitHub integration)** – source repository where agent logic is versioned and managed.
* **Copilot** – an AI assistant for agent creation and improvement.
* **Operator** – a lightweight service that executes instructions using LLMs inside a browser that is triggered by the Agent Runner.
* **PII Masker** – an inline service that redacts sensitive data before any payload leaves the VPC, as well as unmask the data when there is a business need such as responding to a customer request.

The control plane communicates with the Agentic Plane in a PII-redacted manner, ensuring that sensitive data never leaves the customer’s environment.

#### Deployment and Security

The control plane is operated by Minded in AWS, within Minded’s managed VPC. It orchestrates the Agentic Plane using **least-privilege IAM roles** and scoped API endpoints. No customer PII is transmitted or stored in the control plane.

#### Observability and Monitoring

Customers monitor and troubleshoot their agents through **Minded Studio**, which provides execution traces, logs, and performance analytics. Data exposed in Studio is always scrubbed of PII, with sensitive context available only inside the Agentic Plane.

### Agentic Plane

#### **Overview**

The Agentic Plane runs in the customer’s VPC and contains both the **compute** and the **data** for agents. This ensures that sensitive operations, such as accessing internal systems or handling PII, remain under the customer’s control. The control plane orchestrates tasks but does not directly access data.

#### **Components of the Agentic Plane**

The Agentic Plane includes the following components:

* **Agent Runner** – a containerized service responsible for executing agent logic and coordinating with the Agentic Plane.
* **Browser (Chromium)** – a session-scoped Chromium instance for interacting with websites and applications.
* **AI Agent (ECS)** – a containerized compute layer on Amazon ECS that connects to the LLM and the browser to complete tasks.
* **Storage (Amazon S3)** – local bucket for logs, screenshots, and artifacts. Additionally Minded supports Postgres and Redis instances that store metadata, configuration, and state.

#### **Deployment and Security**

The Agentic Plane is deployed inside the customer’s VPC using **Terraform bundles** or **CloudFormation templates**. Resources run with **least-privilege IAM roles**, and no inbound traffic is enabled. Sensitive artifacts (logs, screenshots) remain in S3 within the customer’s environment.

All communication between the control plane and Agentic Plane is encrypted (TLS) and PII-masked. This ensures the control plane has the orchestration context it needs without accessing sensitive information.

## **Deployment**

Minded’s on-premises deployment model is designed to be simple for your DevOps team while keeping security controls in your hands. To achieve this, resources are split into two categories:

### Glacier Resources

Glacier resources are provisioned once and remain static. These include foundational infrastructure such as:

* **Amazon S3 buckets** for storing logs, screenshots, and artifacts.
* **Amazon ECR repositories** for hosting operator images.
* **VPC networking resources** required for isolation and connectivity.

Glacier resources are created during the initial deployment and generally do not require further changes.

### Dynamic Resources

Dynamic resources are permissions and roles that are granted to Minded in a **least-privileged** fashion. These enable Minded to safely manage, troubleshoot, and update the **Operator** inside your VPC without your DevOps team needing to intervene. Examples include:

* IAM roles with scoped permissions for ECS task updates.
* Limited access policies for pulling container images from ECR.
* Runtime orchestration permissions for launching new tasks.

### Terraform Plan

Minded provides a [Terraform Plan](https://github.com/minded-ai/onprem-terraform) to automate setup, with minimal effort from DevOps teams and virtually no required maintenance from your side . The plan performs two steps:

1. **Provision glacier resources** – S3, ECR, and networking resources required for the operator.
2. **Delegate minimal access** – IAM roles and policies that allow Minded to deploy updates securely.

### Ongoing On-Prem Operator Updates

Operator updates are fully managed by Minded with no involvement required from your DevOps or security team. Updates follow this process:

1. Minded uploads a new operator image to **Amazon ECR**.
2. Using the pre-granted IAM role, Minded creates a new **Amazon ECS Task** inside your VPC.
3. The ECS task replaces the running operator, ensuring you are always up to date with the latest version.

### Ongoing Agent Updates

Agents themselves can be updated in two ways:

* **Code updates** – by pushing directly to the GitHub repository that stores agent logic.
* **No-code updates** – via the Minded Studio platform.

After changes are applied, agents are explicitly deployed to production from the platform. A new version of the **Agent Runner** is rolled out in the Agentic Plane, which then communicates with your Operator at runtime.

## **Security**

Minded’s architecture is built around strict security controls, with clear separation of responsibilities between infrastructure that Minded manages and resources that remain fully within your VPC.

### Separation of Duties

* **Glacier infrastructure** (for example, S3 buckets and ECR repositories) is provisioned once and managed by your DevOps team.
* **Dynamic deployment access** is delegated to Minded through scoped IAM roles, allowing us to update the operator without requiring ongoing involvement from your team.

### Least Privilege Enforcement

* **Scoped IAM policies** limit Minded’s access to only the resources needed for deployment and updates.
* **Explicit role ARNs** define trust boundaries for cross-account operations.
* **IP restrictions on S3 buckets** ensure that only traffic from your environment is accepted.

### Auditability

* All cross-account actions performed by Minded are logged in **AWS CloudTrail** under the assumed role.
* Customers can review and audit these logs at any time.

### No Public Exposure

* No resources in the Agentic Plane are publicly accessible.
* Access is tied to your private networking controls such as **VPNs** or specific **AWS principals**.

### How Minded Keeps Operator PII Secure

Minded ensures that sensitive information, such as PII, never leaves your environment:

* **Logs and Screenshots** – All operator logs and browser screenshots are stored in your **Amazon S3 bucket**, within your VPC boundary.
* **LLM Calls** – Decisions and browser actions are governed by an LLM, but all calls are routed to your selected LLM deployment (for example, **Amazon Bedrock**, **Azure OpenAI**, or **Anthropic in VPC**). This ensures that model inference runs entirely in your environment, not in Minded’s.
* **Redaction** – The **PII Masker** enforces data minimization before any metadata leaves your VPC, ensuring observability without exposure.

## **High Availability and Disaster Recovery**

Minded’s architecture is designed for resilience across both the control plane and the Agentic Plane.

### High Availability

* The **control plane** runs in Minded’s managed VPC with multi-zone deployments across AWS regions. Services such as **Postgres**, **Redis**, and **Agent Worker** are deployed with failover to ensure continuity.
* The **Agentic Plane** runs inside your VPC using **Amazon ECS** for compute and **Amazon S3** for storage. ECS tasks are distributed across availability zones to reduce the risk of single-zone outages.

### Disaster Recovery

* **State Restoration** – Agent run state, metadata, and orchestration traces are stored in **Postgres** and **S3**, enabling Minded to restore the state of ongoing or historical agent runs.
* **Step-Level Troubleshooting** – Execution traces are logged to **S3** in your VPC, allowing you to replay or inspect each step of an agent run without exposing PII.
* **Minimal Recovery Time** – New ECS tasks can be provisioned quickly from **ECR images**, ensuring rapid recovery in the event of a failure.

Together, these mechanisms ensure that agent execution can continue with minimal disruption, while preserving full observability and auditability of every step.


# Security Architecture

Minded AI agents automate complex workflows in customer environments while maintaining enterprise-grade security. This architecture enables powerful automation capabilities against your sensitive systems and data, with multiple layers of protection to ensure your infrastructure remains secure.

## **Security Approach**

Minded is designed to provide maximum automation power while maintaining zero-trust security. You can automate mission-critical workflows with confidence, while Minded ensures every action is verified, logged, and restricted through multiple security layers.

***

## 🏗️ System Overview

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-25f436049354ee27c864c97633278ed4ba75a609%2Fsystem-overview.svg?alt=media" alt="Minded System Overview Diagram"><figcaption></figcaption></figure>

**Key Principles**:

* **Zero Trust**: Every action verified, logged, and restricted
* **Least Privilege**: Agents only access what they need, when they need it
* **Defense in Depth**: Multiple security layers protect against breaches
* **Tenant Isolation**: Complete separation between customer environments

***

## 🛡️ Security Layers

### Layer 1: Network Isolation

***

**VPC Configuration:**

* All inbound traffic: BLOCKED
* Outbound traffic: WHITELISTED IPs only
* Internal pod-to-pod: ISOLATED by tenant + namespace

**IP Whitelisting:**

* Customer provides allowed IP ranges
* Minded agent can ONLY connect to those IPs
* Dynamic IP updates via Minded platform

**Traffic Monitoring:**

* All network requests logged
* Alerts on unauthorized connection attempts
* Automatic blocking of suspicious patterns

### Layer 2: Authentication & Authorization

***

**Storage:**

* Credentials encrypted at rest (AES-256-GCM)
* Keys stored in AWS Key Management Service (KMS)

**Runtime:**

* Credentials decrypted only during execution
* Never stored in memory after use
* Automatic expiration/rotation

**Access Control:**

* Agent-specific credentials (not shared)
* MFA support for sensitive operations
* Role-based access control (RBAC)
* Immediate credential revocation capability

### Layer 3: RPA Security Controls

***

**URL Restrictions:**

* Whitelist of allowed URLs/domains
* Regex pattern matching for dynamic URLs
* Block attempts to modify URL parameters
* Prevent URL redirection attacks

**Action Limitations:**

* Predefined set of allowed actions
* Parameter validation against schemas
* Read-only mode for sensitive operations
* Block file upload/download outside scope

**Parameter Integrity:**

* Hash-based verification of parameters
* Signature validation for critical actions
* Block modification of flow-defined values
* Audit trail for all parameter changes

### Layer 4: Runtime Security

***

**Pod/Container Security:**

* Non-root user execution
* Read-only file system (except temp dirs)
* Resource limits (CPU, memory, network)
* Security context constraints
* Regular image scanning for vulnerabilities

**Process Monitoring:**

* Antivirus/antimalware scanning
* Behavioral analysis for anomalies

**Lifecycle Management:**

* Auto-shutdown when idle (configurable timeout)
* No persistent state on disk
* Fresh instance per execution (optional)

**Data Protection:**

* Encryption in transit (TLS 1.2)
* Encryption at rest (all storage volumes)
* Secure deletion of temporary data

### Layer 5: Monitoring & Audit

***

**Audit Logging:**

* Every action logged with timestamp
* User identity, agent ID, action type
* Input/output parameters (sanitized)
* Log retention policy (90 days default)

**Real-time Monitoring:**

* Dashboard for active agent sessions
* Resource utilization tracking
* Error rate and failure monitoring
* Network traffic analysis

**Alerting:**

* Unauthorized access attempts
* Unusual job triggers
* Credential access outside business hours
* Failed authentication attempts
* Suspicious parameter modifications
* Policy violations

**Integration:**

* Email/Slack alerts
* Customer SOC integration

***

## 1️⃣ Credential Lifecycle Flow

**How credentials are managed from creation to revocation**

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-7dc69c58da7a2ad66e3fa937b7457142ffc2e736%2Fcredentials-lifecycle.svg?alt=media" alt="Minded Credentials Lifecycle Flow Diagram"><figcaption></figcaption></figure>

**Key Security Features**:

* Credentials never stored in plaintext
* Encryption keys managed separately from encrypted data
* Credentials only decrypted at runtime, never persisted
* Immediate revocation capability
* Complete audit trail

***

## 2️⃣ RPA Execution Flow Security Controls

**How we run security checks for RPA action executions**

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-3b58cab229316f35a667e6209fb8800a530bd0cc%2Frpa-execution-flow.svg?alt=media" alt="Minded RPA Execution Flow Security Diagram"><figcaption></figcaption></figure>

**Default checklist for action verification**:

1. Is the URL on your allowed list?
2. Is this action permitted for this agent?
3. Are the parameters valid and properly formatted?
4. Have the parameters been tampered with?
5. Does the agent have permission to do this?
6. Log the request before running
7. Double-check the URL before making the request
8. Clean up any unsafe input
9. Block any attempts to inject code
10. Remove sensitive data from the response
11. Log the result after completion

**What happens when a check doesn't pass**:

1. The action is blocked immediately
2. All the details are logged
3. Extreme violations may suspend the agent until further investigation

***

## 3️⃣ Agent Lifecycle Security

**From deployment to shutdown - security at every stage**

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-5d400f1acd8ffc0d29610a2419e3b1cb24d766f3%2Fagent-lifecycle-security.svg?alt=media" alt="Minded Agent Lifecycle Security Diagram"><figcaption></figcaption></figure>

### Idle Timeout Configuration

Default: 30 minutes of inactivity Options:

* Always-on (for critical agents)
* Custom timeout (5-120 minutes)
* Immediate shutdown after task

Benefits:

* Reduce attack surface
* Lower resource costs
* Fresh state for each execution

***

## 4️⃣ Tenant Isolation Architecture

**Ensuring complete separation between customer environments**

<figure><img src="https://3102911747-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHU1mpSPLlxAD3fAGAhf0%2Fuploads%2Fgit-blob-0da38e6e8f4435d3ef492ee63f16d28967aad150%2Ftenant-isolation-overview.svg?alt=media" alt="Minded Tenant Isolation Architecture Diagram"><figcaption></figcaption></figure>

### Isolation Guarantees

**Network:**

* Separate K8s namespaces
* Network policies prevent cross-tenant traffic
* Separate VPCs (enterprise tier)

**Data:**

* Separate encryption keys per tenant
* Logical database isolation (schemas/tables)
* Physical database isolation (enterprise tier)

**Compute:**

* No pod sharing between tenants
* Resource quotas per tenant
* Dedicated nodes (enterprise tier)

**Credentials:**

* Cannot access other tenant's credentials
* Cannot list other tenant's agents
* Cannot execute actions on other tenant's behalf

**Audit Logs:**

* Separate log streams per tenant
* Cannot view other tenant's logs
* Cannot tamper with other tenant's logs

***

## 5️⃣ Infrastructure Security

**How we protect the systems running your agents**

Think of each agent as running in its own secure, locked room. Here's what that means in practice:

* **No elevated privileges** - Agents run as standard users, never as administrators. They can't make system-level changes.
* **Nothing can be modified** - The agent's environment is read-only. Even if something tried to tamper with it, the changes wouldn't stick.
* **Complete isolation** - Agents can't see or touch the underlying infrastructure. They operate in their own sealed environment.
* **Guardrails on resources** - Each agent has strict limits on computing power and memory. They can't consume more than allocated.
* **Regularly checked for vulnerabilities** - We continuously scan our systems for security issues and patch them promptly.

***

## 6️⃣ Agent Behavior Verification & Action Limits

**Ensuring agents only perform intended actions within defined boundaries**

### Webhook Security

Every webhook that triggers an agent is verified before processing:

* **Signed payloads** - All webhooks include an HMAC-SHA256 signature that we validate
* **Timestamp checks** - We reject webhooks older than 5 minutes to prevent replay attacks
* **Source validation** - Webhooks must come from whitelisted IPs with valid API keys

### What Gets Logged

Every agent action creates a complete audit trail:

* Unique ID for tracking the specific invocation
* Start and end timestamps
* How it was triggered (webhook, schedule, or manual)
* Input parameters (with sensitive data removed)
* All actions performed and their results
* Any resources that were created or modified

### Reconciliation

We continuously verify that agents only do what they're supposed to:

* Compare expected actions against actual actions
* Alert immediately if there are discrepancies
* Generate daily reconciliation reports for review

***

## See Also

For more information on related security and operational topics, see:

* [**Secrets Management**](/platform/secrets) - Detailed guide on storing and managing credentials securely
* [**Operator Documentation**](/platform/operator) - Operational procedures and best practices for running agents
* [**On-Premise Deployment**](/platform/on-prem) - Security considerations for self-hosted Minded installations

***

## Security FAQs

### Q: What happens if an agent tries to access systems outside my whitelist?

The request is blocked immediately at multiple layers. First, our security gateway checks the URL against your whitelist. Even if that somehow fails, network policies at the infrastructure level block the connection. All attempts are logged, alerts are triggered, and repeated violations will suspend the agent automatically.

### Q: How do you protect my credentials?

Credentials are encrypted using AES-256 before storage, with encryption keys managed separately in AWS KMS. They're only decrypted in memory during execution and cleared immediately after use. Even if someone gained access to our database, they'd only find encrypted data that's useless without the keys.

### Q: Can one customer's agent access another customer's data?

No. Each customer operates in a completely isolated environment with separate namespaces, encryption keys, and network policies. There's no pathway for one tenant's agent to reach another tenant's resources - the infrastructure physically prevents it.

### Q: How do you prevent malicious code from running?

All action parameters are validated against schemas and checked for tampering using signatures. Inputs are sanitized to remove dangerous content. We scan all container images for vulnerabilities before deployment and monitor runtime behavior for anomalies.

### Q: What visibility do I have into agent activity?

Every action is logged with full details including timestamps, parameters, and results. You can access audit logs, set up alerts, and integrate with your existing monitoring tools. You'll know exactly what your agents are doing at all times.


# Timers

Timers allow you to schedule delayed actions and handle time-based events in your MindedJS agents.

## Overview

The timer system provides three main functions:

* `resetTimer` - Set or reset a timer for a specific session
* `cancelTimer` - Cancel an existing timer
* `onTimer` - Register handlers for timer events

## Setting Timers

Use `resetTimer` to create or update a timer:

```typescript
import { resetTimer } from '@minded-ai/mindedjs';

await resetTimer({
  sessionId: 'session-123',
  seconds: 30,
  timerName: 'inactivity-followup',
  payload: {
    message: 'Are you still there?',
    attemptNumber: 1
  }
});
```

## Canceling Timers

Cancel a timer before it triggers:

```typescript
import { cancelTimer } from '@minded-ai/mindedjs';

await cancelTimer('session-123', 'inactivity-followup');
```

## Handling Timer Events

Register handlers that execute when timers trigger:

```typescript
import { onTimer } from '@minded-ai/mindedjs';

onTimer({
  timerName: 'inactivity-followup',
  handler: async ({ sessionId, payload }) => {
    // Re-engage with the user
    await agent.invoke({
      triggerName: 'timerFollowup',
      triggerBody: { 
        message: payload.message,
        attemptNumber: payload.attemptNumber 
      },
      sessionId
    });
  }
});
```


# Document Processing

Parse and extract data from images, PDFs, Word documents, spreadsheets, and more using AI-powered document processing. This tool handles both single and multiple document parsing with optional data extraction.

## Overview

**Supported formats:** Images (JPG, PNG, GIF, BMP, WebP, TIFF), Documents (PDF, DOC, DOCX, TXT, RTF, ODT), Spreadsheets (XLS, XLSX, CSV, ODS), Presentations (PPT, PPTX, ODP), Web formats (HTML, HTM, MD, XML)

**Available extraction modes:**

1. **Structured Extraction with Schema**: Extract data into a predefined Zod schema using AI
2. **Structured Extraction with Prompt**: Guide extraction using custom prompts
3. **Raw Text Extraction**: Parse document and extract plain text without AI processing
4. **Multiple Document Processing**: Process multiple documents at once - content is concatenated for extraction

## Processing Modes

Document processing supports two modes: **managed** (default) and **local**. In managed mode documents are uploaded to Minded cloud and processed there, providing secure API key storage, automatic cost tracking, and no SDK configuration. Local mode processes documents directly in your SDK using LlamaCloud.

To use local mode, set the `DOCUMENT_PROCESSING_MODE` environment variable to `local` and provide a `LLAMA_CLOUD_API_KEY`:

```bash
DOCUMENT_PROCESSING_MODE=local
LLAMA_CLOUD_API_KEY=your_llama_cloud_api_key
```

## Document Quality

When using managed mode, you can control the quality of document parsing via the `quality` option. This maps to `DocumentQuality` exported from `@minded-ai/mindedjs`:

| Value        | Credit cost | Description                                                                                                                                                                          |
| ------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `'advanced'` | 3x          | **Default.** High-quality OCR and parsing. Handles complex layouts, diagrams, images, scanned documents, and most document types with high accuracy. Recommended for production use. |
| `'standard'` | 1x          | Faster processing, lower cost. Suitable for text-based documents.                                                                                                                    |

The `quality` option is available on both the `parseDocument` and `parseDocumentAndExtractStructuredData` functions, as well as on the `minded-parse-documents` flow node. It has no effect in local processing mode.

## Using in Flows

Document processing includes built-in AI extraction - use the node's `prompt` and `outputSchema` properties to specify what data to extract. No additional extraction tool is needed.

**Available properties:**

* `parameters.documentSource` (string or array, required): URL or file path to a single document, or array of URLs/file paths to process multiple documents. When an array is provided, documents are parsed and concatenated with double newlines.
* `parameters.returnStructuredOutput` (boolean, optional, default: `false`): Set to `true` to enable AI-powered extraction, `false` for raw text only. When `true`, requires either `prompt` or `outputSchema` (or both)
* `quality` (`'standard'` | `'advanced'`, optional, default: `'advanced'`): Controls OCR/parsing quality in managed mode. `'advanced'` (default) provides high-quality extraction supporting complex layouts, diagrams, and images at 3x the credit cost of `'standard'`. Use `'standard'` for simple, clean text-based documents where speed and cost matter. Has no effect in local processing mode.
* `prompt` (string, optional): Instructions for AI-powered extraction. Ignored when `returnStructuredOutput` is `false`
* `outputSchema` (schema object, optional): Define the structure of extracted data for structured extraction. Ignored when `returnStructuredOutput` is `false`

### Examples

Raw text extraction without AI processing:

```yaml
name: Main flow
nodes:
  - name: 'parse-invoice'
    type: appTool
    displayName: 'Parse Invoice'
    actionKey: 'minded-parse-documents'
    actionName: 'Parse Document'
    appName: 'Minded'
    parameters:
      documentSource: '{state.memory.invoiceUrl}'
      returnStructuredOutput: false
```

Structured extraction with schema and prompt:

```yaml
name: Main flow
nodes:
  - name: 'parse-uploaded-document'
    type: appTool
    displayName: 'Parse Uploaded Document'
    actionKey: 'minded-parse-documents'
    actionName: 'Parse Document'
    appName: 'Minded'
    parameters:
      documentSource: '{state.memory.filePath}'
      returnStructuredOutput: true
    prompt: 'Extract invoice number, amount, date, and vendor'
    outputSchema:
      - name: invoiceNumber
        type: string
        description: Invoice number
      - name: amount
        type: number
        description: Total amount
      - name: date
        type: string
        description: Invoice date
      - name: vendor
        type: string
        description: Vendor name
```

Unstructured extraction guided by prompt only:

```yaml
name: Main flow
nodes:
  - name: 'extract-names-addresses'
    type: appTool
    displayName: 'Extract Names and Addresses'
    actionKey: 'minded-parse-documents'
    actionName: 'Parse Document'
    appName: 'Minded'
    parameters:
      documentSource: '{state.memory.uploadedFile}'
      returnStructuredOutput: true
    prompt: 'Extract all names and addresses from this document'
```

Processing multiple documents with structured extraction:

```yaml
name: Main flow
nodes:
  - name: 'parse-multiple-invoices'
    type: appTool
    displayName: 'Parse Multiple Invoices'
    actionKey: 'minded-parse-documents'
    actionName: 'Parse Document'
    appName: 'Minded'
    parameters:
      documentSource: '{state.memory.invoiceUrls}'
      # Also possible to pass an array as JSON with different items, including items from other arrays:
      # documentSource: '["{state.memory.invoiceFilePath}", "{state.memory.invoiceUrls[0]}"]
      returnStructuredOutput: true
    prompt: 'Extract all invoice data from the provided documents'
    outputSchema:
      - name: invoices
        type: array
        description: Array of invoice data
        items:
          - name: invoiceNumber
            type: string
            description: Invoice number
          - name: amount
            type: number
            description: Total amount
          - name: date
            type: string
            description: Invoice date
```

## Programmatic Usage

The SDK provides three main functions for document processing:

1. **`parseDocumentAndExtractStructuredData`** - Parse one or more documents and optionally extract structured data with AI

   ```typescript
   type parseDocumentAndExtractStructuredData = <T>(options: {
     documentSources: string[],               // Required: Array of URLs/file paths. The results of multiple documents are concatenated.
     sessionId: string,                       // Required: Session identifier
     returnStructuredOutput: boolean,         // Required: Enable/disable AI extraction
     llm?: BaseLanguageModel,                 // Optional: LLM instance (required when returnStructuredOutput is true)
     outputSchema?: ZodType<T>,               // Optional: Zod schema for structured extraction
     outputSchemaPrompt?: string,             // Optional: Instructions for extraction
     processingMode?: DocumentProcessingMode, // Optional: Processing mode (default: DocumentProcessingMode.MANAGED)
     llamaCloudApiKey?: string,               // Optional: API key for local mode
     quality?: DocumentQuality,               // Optional: 'standard' | 'advanced' (default: 'advanced'). Managed mode only.
   }) => Promise<{
     rawContent?: string,                     // Concatenated content when multiple documents provided
     structuredContent?: T | string           // Extracted from concatenated content when multiple documents provided
   }>
   ```
2. **`parseDocument`** - Parse document and extract raw text only

   ```typescript
   type parseDocument = (options: {
     documentSource: string,                  // Required: URL or file path
     sessionId: string,                       // Required: Session identifier
     processingMode?: DocumentProcessingMode, // Optional: Processing mode (default: DocumentProcessingMode.MANAGED)
     llamaCloudApiKey?: string,               // Optional: API key for local mode
     quality?: DocumentQuality,               // Optional: 'standard' | 'advanced' (default: 'advanced'). Managed mode only.
   }) => Promise<{
     rawContent?: string,
     metadata?: { fileSize?: number, fileType: string, processingTime: number, contentLength: number }
   }>
   ```
3. **`extractStructuredDataFromString`** - Extract structured data from already parsed text

   ```typescript
   type extractStructuredDataFromString = <T>(options: {
     content: string,                  // Required: Text content to extract from
     llm: BaseLanguageModel,           // Required: LLM instance
     sessionId: string,                // Required: Session identifier
     schema?: ZodType<T>,              // Optional: Zod schema for structured extraction
     prompt?: string                   // Optional: Instructions for extraction
   }) => Promise<T | string>
   ```

### Structured Extraction with Schema

Extract data matching a predefined Zod schema.

{% hint style="info" %}
**Note:** Use `nullable()` instead of `optional()` for fields that are not required.
{% endhint %}

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';
import { z } from 'zod';

// Invoice processing with structured schema
const invoiceSchema = z.object({
  invoiceNumber: z.string(),
  vendor: z.string(),
  amount: z.number(),
  dueDate: z.string(),
  lineItems: z.array(
    z.object({
      description: z.string(),
      quantity: z.number(),
      unitPrice: z.number(),
    }),
  ),
});

const result = await parseDocumentAndExtractStructuredData({
  documentSources: ['./invoice.pdf'],
  sessionId: state.sessionId,
  returnStructuredOutput: true,
  llm: agent.llm,
  outputSchema: invoiceSchema,
  outputSchemaPrompt: 'Extract all invoice details including line items',
});

console.log(result.structuredContent); // Typed data matching your schema
console.log(result.rawContent); // Original raw text
```

### Structured Extraction with Prompt Only

Extract data using AI with a custom prompt, without a predefined schema.

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';

// Contract analysis with prompt guidance
const result = await parseDocumentAndExtractStructuredData({
  documentSources: ['./contract.pdf'],
  sessionId: state.sessionId,
  returnStructuredOutput: true,
  llm: agent.llm,
  outputSchemaPrompt: 'Extract parties involved, key dates, payment terms, and termination clauses',
});

console.log(result.structuredContent); // String or object with extracted information
```

### Raw Text Extraction

Parse document without AI processing to get plain text only.

```typescript
import { parseDocument } from '@minded-ai/mindedjs';

const result = await parseDocument({
  documentSource: './document.pdf',
  sessionId: state.sessionId,
});

console.log(result.rawContent); // Raw extracted text
console.log(result.metadata); // File size, type, processing time, content length
```

### Using URLs

All functions accept URLs in addition to file paths via the `documentSource` and `documentSources` parameters.

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';
import { z } from 'zod';

const result = await parseDocumentAndExtractStructuredData({
  documentSources: ['https://example.com/invoice.pdf'],
  sessionId: state.sessionId,
  returnStructuredOutput: true,
  llm: agent.llm,
  outputSchema: z.object({
    invoiceNumber: z.string(),
    amount: z.number(),
    dueDate: z.string(),
  }),
});
```

### Processing Multiple Documents

Process multiple documents by providing an array of URLs or file paths. Documents are parsed in parallel and their content is concatenated with double newlines before optional structured extraction.

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';
import { z } from 'zod';

// Extract data from multiple invoices into an array
const invoiceSchema = z.array(
  z.object({
    invoiceNumber: z.string(),
    vendor: z.string(),
    amount: z.number(),
    date: z.string(),
  })
);

const result = await parseDocumentAndExtractStructuredData({
  documentSources: [
    './invoice1.pdf',
    './invoice2.pdf',
    './invoice3.pdf',
  ],
  sessionId: state.sessionId,
  returnStructuredOutput: true,
  llm: agent.llm,
  outputSchema: invoiceSchema,
  outputSchemaPrompt: 'Extract invoice data from all provided invoices',
});

console.log(result.structuredContent); // Array of invoice data
console.log(result.rawContent); // Concatenated text from all invoices
```

You can also extract raw text from multiple documents without structured extraction:

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';

const result = await parseDocumentAndExtractStructuredData({
  documentSources: [
    'https://example.com/doc1.pdf',
    'https://example.com/doc2.pdf',
  ],
  sessionId: state.sessionId,
  returnStructuredOutput: false,
});

console.log(result.rawContent); // Concatenated text from both documents
```

### Identity Document Verification

Extract personal information from ID documents with structured validation.

```typescript
import { parseDocumentAndExtractStructuredData } from '@minded-ai/mindedjs';
import { z } from 'zod';

const idSchema = z.object({
  documentType: z.enum(['passport', 'driver_license', 'national_id']),
  fullName: z.string(),
  dateOfBirth: z.string(),
  documentNumber: z.string(),
  expiryDate: z.string().nullable(),
  address: z.string().nullable(),
});

const result = await parseDocumentAndExtractStructuredData({
  documentSources: ['./id-card.jpg'],
  sessionId: state.sessionId,
  returnStructuredOutput: true,
  llm: agent.llm,
  outputSchema: idSchema,
  outputSchemaPrompt: 'Extract personal information from this ID document',
});
```

### Extract from Already Parsed Content

Use `extractStructuredDataFromString` to extract structured data from text you've already obtained.

```typescript
import { extractStructuredDataFromString } from '@minded-ai/mindedjs';
import { z } from 'zod';

// With schema for structured extraction
const structured = await extractStructuredDataFromString({
  content: 'Invoice #12345\nTotal: $500.00\nDate: 2024-01-15',
  llm: agent.llm,
  schema: z.object({
    invoiceNumber: z.string(),
    totalAmount: z.number(),
    date: z.string(),
  }),
  sessionId: state.sessionId,
});
// Returns: {invoiceNumber: "12345", totalAmount: 500, date: "2024-01-15"}

// Without schema for unstructured extraction
const unstructured = await extractStructuredDataFromString({
  content: 'Invoice #12345\nTotal: $500.00\nDate: 2024-01-15',
  llm: agent.llm,
  prompt: 'Extract the invoice number, total amount, and date',
  sessionId: state.sessionId,
});
// Returns: String or object with LLM's analysis
```

### Local Processing Mode

By default, document processing uses the Minded cloud service. To process documents locally using LlamaCloud, set the `DOCUMENT_PROCESSING_MODE` environment variable and provide a LlamaCloud API key:

```bash
# Processing mode: 'managed' (default) or 'local'
DOCUMENT_PROCESSING_MODE=local

# Required for local mode:
LLAMA_CLOUD_API_KEY=your_llama_cloud_api_key
```

You can also pass the API key and processing mode directly to the function:

```typescript
import { parseDocument, DocumentProcessingMode } from '@minded-ai/mindedjs';

const result = await parseDocument({
  documentSource: './contract.pdf',
  processingMode: DocumentProcessingMode.LOCAL,
  sessionId: state.sessionId,
  llamaCloudApiKey: process.env.LLAMA_CLOUD_API_KEY,
});
```

## Supported File Types

| Category      | Extensions                                  | Notes                                    |
| ------------- | ------------------------------------------- | ---------------------------------------- |
| Images        | .jpg, .jpeg, .png, .gif, .bmp, .webp, .tiff | Direct processing                        |
| Documents     | .pdf, .doc, .docx, .txt, .rtf, .odt         | Requires LlamaCloud for advanced formats |
| Spreadsheets  | .xls, .xlsx, .csv, .ods                     | Requires LlamaCloud for binary formats   |
| Presentations | .ppt, .pptx, .odp                           | Requires LlamaCloud                      |
| Web           | .html, .htm, .md, .xml                      | Basic text extraction                    |


# Data Extraction

Extract structured data from unstructured text using AI. The `minded-extraction` tool uses LLM capabilities to parse content and return data in a predefined format.

## Overview

* **Structured Extraction with Zod Schema**: Define exact data structure
* **Prompt-based Extraction**: Extract information using custom prompts
* **Validation and Retries**: Automatic validation with configurable retry logic
* **Structured Output Support**: Uses LLM native structured output when available

## Using in Flows

```yaml
- id: extractCustomerInfo
  type: tool
  toolName: minded-extraction
  prompt: Extract customer name, email, and phone number from the message
```

### Tool Parameters

| Parameter    | Type    | Description                            | Required |
| ------------ | ------- | -------------------------------------- | -------- |
| content      | string  | Text to extract from                   | Yes      |
| schema       | object  | Zod-compatible schema                  | No       |
| systemPrompt | string  | Custom instructions                    | No       |
| examples     | array   | Input/output examples                  | No       |
| strictMode   | boolean | Enable validation (default: true)      | No       |
| maxRetries   | number  | Retry attempts on failure (default: 3) | No       |
| defaultValue | any     | Fallback value                         | No       |

### Overriding Parameters in Flows

```yaml
- name: 'Extract Customer Info'
  type: 'tool'
  toolName: 'minded-extraction'
  parameters:
    content: '{state.memory.rawText}'
    schema:
      name:
        type: 'string'
        description: 'Customer full name'
      email:
        type: 'string'
        description: 'Email address'
        required: false
      phone:
        type: 'string'
    systemPrompt: 'Extract contact information from the text'
    strictMode: true
    maxRetries: 3
```

**Available schema field properties:**

* `type`: `'string'`, `'number'`, `'boolean'`, `'array'`, or `'object'`
* `description`: Optional field description
* `required`: Optional boolean (defaults to true)

## Programmatic Usage

```typescript
import { extract, createExtractor } from '@minded-ai/mindedjs';
import { z } from 'zod';

// Direct extraction
const result = await extract(
  content,
  {
    schema: z.object({
      name: z.string(),
      age: z.number(),
    }),
    systemPrompt: 'Extract person details',
  },
  agent.llm,
);

// Create reusable extractor
const extractor = createExtractor(schema, { systemPrompt: 'Extract data' });
const result = await extractor(content, agent.llm);
```

## How It Works

1. **With Structured Output Support**: Uses LLM's `withStructuredOutput` for direct schema-compliant extraction
2. **Fallback Mode**: Generates prompt with schema description, parses JSON, validates against Zod schema, retries with error feedback
3. **Non-strict Mode**: Skips validation for flexible extraction


# Classifier

### Classifier Tool

The `minded-classifier` tool categorizes text using AI. It can be used in flows or directly in code.

#### Using in Flows

Use the tool in your flows by referencing `minded-classifier`:

```yaml
- id: classifyUserIntent
  type: tool
  toolName: minded-classifier
  prompt: |
    Classify the user's message into one of these categories:
    - Technical Issue: Problems with the system
    - Billing: Payment or pricing questions
    - Feature Request: Asking for new features
```

#### Tool Parameters

| Parameter     | Type             | Description                           | Required |
| ------------- | ---------------- | ------------------------------------- | -------- |
| content       | string           | Text content to classify              | Yes      |
| classes       | array            | Classes (strings, tuples, or objects) | No\*     |
| systemPrompt  | string           | Custom system prompt                  | No       |
| includeReason | boolean          | Include reasoning (default: true)     | No       |
| outputFormat  | 'json' \| 'text' | Output format (default: 'json')       | No       |
| defaultClass  | string           | Fallback class                        | No       |
| defaultReason | string           | Fallback reason                       | No       |

\*Classes can be provided in parameters or stored in agent memory.

#### Overriding Classifier Parameters in Flows

You can override classifier parameters directly in your flow YAML for deterministic classification:

```yaml
- name: 'Classify Support Ticket'
  type: 'tool'
  toolName: 'minded-classifier'
  parameters:
    # Dynamic - from state
    content: '{state.memory.ticketDescription}'
    # Fixed - predefined classes
    classes:
      - name: 'urgent'
        description: 'Requires immediate attention'
      - name: 'standard'
        description: 'Normal priority'
      - name: 'low'
        description: 'Can be handled later'
    # Fixed - configuration
    includeReason: true
    defaultClass: 'standard'
    # Let AI infer: systemPrompt (optional override)
```

### Using as a Utility

#### Import the utilities

```typescript
import { toolsLibrary } from '@minded-ai/mindedjs';

const { classify, createClassifier, createConversationClassifier, multiClassify } = toolsLibrary.tools['minded-classifier'];
```

#### Direct classification

```typescript
const result = await classify(
  "I can't access my account",
  {
    classes: [
      { name: 'Technical', description: 'Technical issues' },
      { name: 'Account', description: 'Account access problems' },
    ],
    includeReason: true,
  },
  agent.llm,
);

console.log(result);
// { class: 'Account', reason: 'User cannot access their account', confidence: 0.95 }
```

#### Create reusable classifiers

```typescript
// Simple classifier with string classes
const sentimentClassifier = createClassifier(['positive', 'negative', 'neutral']);

// Classifier with descriptions
const intentClassifier = createClassifier([
  ['greeting', 'User is greeting or saying hello'],
  ['question', 'User is asking a question'],
  ['complaint', 'User is complaining or expressing dissatisfaction'],
  ['goodbye', 'User is saying goodbye or ending conversation'],
]);

// Use the classifier
const sentiment = await sentimentClassifier('This is amazing!', agent.llm);
const intent = await intentClassifier('Hello there!', agent.llm);
```

#### Conversation classification

```typescript
const conversationClassifier = createConversationClassifier([
  { name: 'Resolved', description: 'Issue was resolved successfully' },
  { name: 'Escalated', description: 'Needs escalation to human agent' },
  { name: 'Pending', description: 'Awaiting more information' },
]);

const result = await conversationClassifier(messages, agent.llm);
```

#### Multi-label classification

```typescript
const topics = await multiClassify(
  'The app crashes on login and the UI looks outdated',
  {
    classes: [
      { name: 'Bug', description: 'Software bugs or crashes' },
      { name: 'UI/UX', description: 'User interface issues' },
      { name: 'Authentication', description: 'Login or auth problems' },
    ],
    maxClasses: 3,
  },
  agent.llm,
);

console.log(topics);
// [
//   { class: 'Bug', reason: 'App crashes', confidence: 0.9 },
//   { class: 'Authentication', reason: 'Login issues', confidence: 0.85 },
//   { class: 'UI/UX', reason: 'Outdated UI', confidence: 0.7 }
// ]
```

### Configuration

#### `ClassifierConfig` interface

```typescript
interface ClassifierConfig {
  classes: ClassDefinition[];
  systemPrompt?: string;
  outputFormat?: 'json' | 'text';
  includeReason?: boolean;
  defaultClass?: string;
  defaultReason?: string;
}
```


# Storage

The `storage` module provides utility functions for saving and retrieving document records between sessions. Records are scoped to the agent and persist across sessions.

## Import

```typescript
import { toolsLibrary } from '@minded-ai/mindedjs';

const { saveRecord, getRecords, deleteRecord } = toolsLibrary.storage;
```

## Basic Operations

**Save a record:**

```typescript
const recordId = await saveRecord(state.sessionId, {
  invoiceId: 'INV-12345',
  status: 'processed',
  amount: 1500.00,
}, 3600); // Optional: TTL in seconds
```

**Retrieve records:**

```typescript
// Simple equality
const records = await getRecords({ status: 'processed' });
```

**Delete a record:**

```typescript
await deleteRecord('record-id-123');
```

## Record Structure

* `id`: Unique identifier
* `agentId`: Automatically set to the agent ID
* `sessionId`: Session where the record was created
* `data`: Your document data
* `createdAt`: Creation timestamp
* `updatedAt`: Last update timestamp

## Notes

* Records are agent-scoped (only accessible by the same agent)
* Filters use MongoDB query syntax and apply to the `data` field (no `data.` prefix needed)
* All operations require a valid `sessionId`
* Records persist across sessions


# Zendesk

The Zendesk integration enables your Minded agents to seamlessly interact with Zendesk tickets, conversations, and customer data. This integration provides a complete interface for building intelligent customer support agents that can handle ticket management, customer communication, and workflow automation.

## Overview

The Zendesk integration provides:

* **Ticket Management** - Create, update, and manage ticket status
* **Customer Communication** - Send messages and comments
* **Conversation Handling** - Access conversation history and context
* **Tag Management** - Add and manage ticket tags
* **Custom Fields** - Set and update custom field values
* **Search Capabilities** - Search tickets and conversations
* **Real-time Updates** - Receive and respond to Zendesk events

## Quick Start

### 1. Basic Setup

```typescript
import { Agent } from '@minded/sdk';
import { zendesk } from '@minded/sdk/interfaces/zendesk';
import { events } from '@minded/sdk';

const agent = new Agent({
  memorySchema: yourMemorySchema,
  config: mindedConfig,
  tools: yourTools,
});

// Initialize Zendesk interface
zendesk.init(agent);

// Handle AI messages - send them to Zendesk
agent.on(events.AI_MESSAGE, async ({ message, state }) => {
  await zendesk.sendMessage(state.sessionId!, message);
});
```

### 2. Trigger Configuration

Configure your flow with a Zendesk trigger:

```yaml
# flows/zendeskFlow.yaml
name: Zendesk Support Flow
nodes:
  - name: Zendesk-Trigger
    type: trigger
    triggerType: app
    appTriggerId: zendesk-interface
    appTriggerName: Zendesk Minded Conversation
    appName: Zendesk

  - name: Support-Agent
    type: promptNode
    prompt: |
      You are a helpful customer support agent. 
      Respond to the customer's inquiry professionally and helpfully.

edges:
  - source: Zendesk-Trigger
    target: Support-Agent
    type: stepForward
```

## Available Methods

### Core Communication

#### `sendMessage(sessionId: string, message: string)`

Send a message to the customer in the Zendesk conversation.

```typescript
await zendesk.sendMessage(sessionId, 'Hello! How can I help you today?');
```

#### `sendComment(sessionId: string, comment: string)`

Add an internal comment to the ticket (not visible to customer).

```typescript
await zendesk.sendComment(sessionId, 'Customer issue escalated to tier 2');
```

#### `sendPublicComment(sessionId: string, comment: string)`

Add a public comment to the ticket (visible to customer).

```typescript
await zendesk.sendPublicComment(sessionId, 'Issue resolved - closing ticket');
```

### Conversation Management

#### `getConversation(sessionId: string)`

Retrieve the complete conversation context.

```typescript
const conversation = await zendesk.getConversation(sessionId);
console.log(conversation);
```

#### `getConversationMessages(sessionId: string)`

Get all messages in the conversation.

```typescript
const messages = await zendesk.getConversationMessages(sessionId);
```

### Ticket Operations

#### `getTicket(sessionId: string)`

Retrieve ticket information and metadata.

```typescript
const ticket = await zendesk.getTicket(sessionId);
console.log(`Ticket status: ${ticket.status}`);
```

#### `createTicket(params)`

Create a new ticket with specified parameters.

```typescript
await zendesk.createTicket({
  sessionId: sessionId,
  subject: 'Customer inquiry about order #12345',
  ticketBody: 'Customer is asking about their order status',
  priority: 'normal',
  tags: ['order-inquiry', 'customer-support'],
  customFields: [
    { id: 123, value: 'urgent' },
    { id: 456, value: 'premium-customer' },
  ],
});
```

#### `markTicketAsSolved(sessionId: string)`

Mark a ticket as solved.

```typescript
await zendesk.markTicketAsSolved({ sessionId });
```

#### `markTicketAsOpen(sessionId: string)`

Reopen a closed ticket.

```typescript
await zendesk.markTicketAsOpen({ sessionId });
```

### Tag and Field Management

#### `addTag(sessionId: string, tag: string)`

Add a tag to the ticket.

```typescript
await zendesk.addTag(sessionId, 'escalated');
```

#### `setCustomFields(sessionId: string, fields: any[])`

Set custom field values on the ticket.

```typescript
await zendesk.setCustomFields({
  sessionId,
  fields: [
    { id: 123, value: 'high-priority' },
    { id: 456, value: 'technical-issue' },
  ],
});
```

#### `search(sessionId: string, query: string)`

Search tickets and conversations.

```typescript
const results = await zendesk.search({
  sessionId,
  query: 'customer:john.doe@example.com status:open',
});
```

## Complete Example

Here's a comprehensive example of a Zendesk customer support agent:

### Project Structure

```
zendesk-agent/
├── minded.json
├── agentMemorySchema.ts
├── zendeskAgent.ts
├── flows/
│   └── supportFlow.yaml
└── tools/
    ├── escalateIssue.ts
    ├── lookupOrder.ts
    └── index.ts
```

### Configuration

**`minded.json`:**

```json
{
  "flows": ["./flows"],
  "tools": ["./tools"],
  "agent": "./zendeskAgent.ts",
  "llm": {
    "name": "AzureChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7
    }
  }
}
```

### Memory Schema

**`agentMemorySchema.ts`:**

```typescript
import { z } from 'zod';

export default z.object({
  // Customer information
  customerName: z.string().optional(),
  customerEmail: z.string().optional(),
  customerId: z.string().optional(),

  // Issue tracking
  issueType: z.enum(['billing', 'technical', 'general', 'escalation']).optional(),
  issueDescription: z.string().optional(),
  priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),

  // Ticket information
  ticketId: z.string().optional(),
  ticketStatus: z.string().optional(),
  ticketTags: z.array(z.string()).optional(),

  // Resolution tracking
  resolution: z.string().optional(),
  resolved: z.boolean().optional(),
  escalated: z.boolean().optional(),

  // Conversation context
  conversationStage: z.enum(['greeting', 'information_gathering', 'issue_resolution', 'closing']).optional(),
});
```

### Agent Implementation

**`zendeskAgent.ts`:**

```typescript
import { Agent } from '@minded/sdk';
import { zendesk } from '@minded/sdk/interfaces/zendesk';
import { events } from '@minded/sdk';
import { HumanMessage } from '@langchain/core/messages';
import memorySchema from './schema';
import tools from './tools';
import mindedConfig from './minded.json';

const agent = new Agent({
  memorySchema,
  config: mindedConfig,
  tools,
});

// Initialize Zendesk interface
zendesk.init(agent);

// Handle AI messages
agent.on(events.AI_MESSAGE, async ({ message, state }) => {
  await zendesk.sendMessage(state.sessionId!, message);
});

// Handle trigger events from Zendesk
agent.on(events.TRIGGER_EVENT, async ({ triggerBody, sessionId }) => {
  // Qualify the trigger and set initial context
  const isValidTrigger = triggerBody.content && triggerBody.content.trim().length > 0;

  if (!isValidTrigger) {
    return { isQualified: false };
  }

  return {
    isQualified: true,
    messages: [new HumanMessage({ content: triggerBody.content })],
    sessionId,
    memory: {
      conversationStage: 'greeting',
      customerEmail: triggerBody.customerEmail,
      ticketId: triggerBody.ticketId,
    },
  };
});

// Handle escalation events
agent.on(events.MEMORY_UPDATE, async ({ memory, state }) => {
  if (memory.escalated && !memory.resolved) {
    await zendesk.addTag(state.sessionId!, 'escalated');
    await zendesk.sendComment(state.sessionId!, `Issue escalated: ${memory.issueDescription}`);
  }
});

export default agent;
```

### Flow Definition

**`flows/supportFlow.yaml`:**

```yaml
name: Zendesk Support Flow
nodes:
  - name: Zendesk-Trigger
    type: trigger
    triggerType: app
    appTriggerId: zendesk-interface
    appTriggerName: Zendesk Minded Conversation
    appName: Zendesk

  - name: Support-Agent
    type: promptNode
    prompt: |
      You are a professional customer support agent. 

      Customer Context:
      - Email: {memory.customerEmail}
      - Issue Type: {memory.issueType}
      - Priority: {memory.priority}
      - Stage: {memory.conversationStage}

      Guidelines:
      1. Be empathetic and professional
      2. Gather necessary information about the issue
      3. Provide clear solutions or next steps
      4. Escalate complex issues when needed
      5. Always confirm resolution before closing

      If this is the first interaction, greet the customer and ask about their issue.

  - name: Escalate-Issue
    type: tool
    toolName: escalateIssue

  - name: Resolve-Issue
    type: promptNode
    prompt: |
      The issue has been resolved. Please:
      1. Summarize the solution provided
      2. Ask if the customer needs anything else
      3. Thank them for their patience

edges:
  - source: Zendesk-Trigger
    target: Support-Agent
    type: stepForward

  - source: Support-Agent
    target: Escalate-Issue
    type: promptCondition
    prompt: Customer issue requires escalation to a specialist

  - source: Support-Agent
    target: Resolve-Issue
    type: promptCondition
    prompt: Customer issue has been resolved successfully

  - source: Escalate-Issue
    target: Support-Agent
    type: stepForward

  - source: Resolve-Issue
    target: Support-Agent
    type: promptCondition
    prompt: Customer has additional questions or issues
```

### Tools Implementation

**`tools/escalateIssue.ts`:**

```typescript
import { Tool } from '@minded/sdk';
import { zendesk } from '@minded/sdk/interfaces/zendesk';
import { z } from 'zod';

const escalateIssue: Tool<any, any> = {
  name: 'escalateIssue',
  description: 'Escalate a customer issue to a specialist',
  inputSchema: z.object({
    reason: z.string().describe('Reason for escalation'),
    priority: z.enum(['high', 'urgent']).describe('Escalation priority'),
  }),
  func: async ({ reason, priority }, { memory, sessionId }) => {
    // Add escalation tag
    await zendesk.addTag(sessionId!, 'escalated');

    // Set priority
    await zendesk.setCustomFields({
      sessionId: sessionId!,
      fields: [{ id: 123, value: priority }], // Assuming field ID 123 is priority
    });

    // Add internal comment
    await zendesk.sendComment(sessionId!, `ESCALATED: ${reason} - Priority: ${priority}`);

    return {
      success: true,
      message: `Issue escalated with priority: ${priority}`,
      memory: {
        ...memory,
        escalated: true,
        priority: priority,
      },
    };
  },
};

export default escalateIssue;
```

## Best Practices

### 1. Error Handling

Always implement proper error handling for Zendesk operations:

```typescript
agent.on(events.AI_MESSAGE, async ({ message, state }) => {
  try {
    await zendesk.sendMessage(state.sessionId!, message);
  } catch (err) {
    logger.error({ message: 'Failed to send message to Zendesk', err });
    // Implement fallback or retry logic
  }
});
```

### 2. Memory Management

Use memory to maintain context across the conversation:

```typescript
// Track conversation progress
memory.conversationStage = 'information_gathering';

// Store important customer data
memory.customerEmail = triggerBody.customerEmail;
memory.issueType = 'billing';
```

### 3. Trigger Qualification

Implement proper trigger qualification to handle only relevant events:

```typescript
agent.on(events.TRIGGER_EVENT, async ({ triggerBody, sessionId }) => {
  // Only process triggers with actual content
  if (!triggerBody.content || triggerBody.content.trim().length === 0) {
    return { isQualified: false };
  }

  // Skip automated messages
  if (triggerBody.isAutomated) {
    return { isQualified: false };
  }

  return { isQualified: true };
});
```

### 4. Tag Management

Use tags consistently for tracking and automation:

```typescript
// Add tags based on issue type
if (memory.issueType === 'billing') {
  await zendesk.addTag(sessionId, 'billing-issue');
}

// Track resolution status
if (memory.resolved) {
  await zendesk.addTag(sessionId, 'resolved-by-ai');
}
```

### 5. Custom Fields

Leverage custom fields for enhanced workflow automation:

```typescript
// Set custom fields for reporting and automation
await zendesk.setCustomFields({
  sessionId,
  fields: [
    { id: 123, value: memory.priority },
    { id: 456, value: memory.issueType },
    { id: 789, value: 'ai-handled' },
  ],
});
```

## Integration Architecture

The Zendesk integration follows this architecture:

1. **Trigger Events** - Zendesk sends events to your agent
2. **Agent Processing** - Your agent processes the event and determines response
3. **Zendesk API Calls** - Agent makes calls back to Zendesk via the interface
4. **Real-time Updates** - Changes are reflected in Zendesk immediately

This architecture ensures seamless bi-directional communication between your Minded agent and Zendesk, enabling sophisticated customer support automation while maintaining full control over the conversation flow.

## Troubleshooting

### Common Issues

1. **Connection Issues**: Ensure your Zendesk credentials are properly configured
2. **Permission Errors**: Verify your Zendesk API permissions include required scopes
3. **Rate Limiting**: Implement proper rate limiting and retry logic
4. **Session Management**: Ensure sessionId is properly maintained across requests

### Debug Mode

Enable debug logging to troubleshoot issues:

```typescript
// Add debugging to your agent
agent.on(events.AI_MESSAGE, async ({ message, state }) => {
  console.log('Sending message to Zendesk:', { message, sessionId: state.sessionId });
  await zendesk.sendMessage(state.sessionId!, message);
});
```

For additional support, consult the [Minded platform documentation](https://github.com/minded-ai/mindedjs/blob/main/docs/platform/README.md) or contact support.


# Order Refund Flow

A complete example demonstrating how to build an intelligent customer support agent that can handle order refunds with sophisticated routing and business logic.

## Overview

This example showcases:

* **Trigger handling** with qualification logic
* **Prompt-based routing** for intelligent conversation flow
* **Tool integration** for business logic execution
* **Memory management** for maintaining conversation state
* **Error handling** and edge cases

## Complete Implementation

### Project Structure

```
order-refund-agent/
├── minded.json          # Agent configuration
├── agentMemorySchema.ts           # Memory type definition
├── agent.ts            # Main agent implementation
├── flows/
│   └── refundFlow.yaml # Flow definition
└── tools/
    ├── lookupOrder.ts  # Order lookup tool
    ├── processRefund.ts # Refund processing tool
    └── index.ts        # Tool exports
```

### 1. Agent Configuration

Create your `minded.json` configuration file (see [Project Configuration](/getting-started/project-configuration) for detailed documentation):

**`minded.json`:**

```json
{
  "flows": ["./flows"],
  "agent": "./agent.ts",
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7
    }
  }
}
```

### 2. Memory Schema

**`agentMemorySchema.ts`:**

```ts
import { z } from 'zod';

const memorySchema = z.object({
  // Customer information
  customerName: z.string().optional(),
  customerEmail: z.string().optional(),
  customerId: z.string().optional(),

  // Order information
  orderId: z.string().optional(),
  orderInfo: z
    .object({
      id: z.string(),
      status: z.string(),
      total: z.number(),
      items: z.array(z.string()),
      purchaseDate: z.string(),
      canRefund: z.boolean(),
    })
    .optional(),

  // Conversation state
  conversationStage: z.enum(['greeting', 'gathering_info', 'processing_refund', 'complete']).optional(),

  // Refund information
  refundReason: z.string().optional(),
  refundAmount: z.number().optional(),
  refundProcessed: z.boolean().optional(),

  // Metadata
  timestamp: z.string().optional(),
  customerQuery: z.string().optional(),
});

export type Memory = z.infer<typeof memorySchema>;
export default memorySchema;
```

### 3. Flow Definition

**`flows/refundFlow.yaml`:**

```yaml
name: 'Advanced Order Refund Flow'
nodes:
  # Entry point
  - type: trigger
    triggerType: manual
    name: Customer Support Trigger

  # Initial customer interaction
  - type: promptNode
    name: Support Agent
    prompt: |
      You are a helpful customer support agent specializing in order issues and refunds.

      Current customer context:
      - Query: {{memory.customerQuery}}
      - Stage: {{memory.conversationStage}}

      If this is the first interaction:
      1. Greet the customer warmly
      2. Ask for their order ID if they mention order issues
      3. Ask for their name and email for verification

      If you have order information:
      - Customer: {{memory.customerName}}
      - Order ID: {{memory.orderId}}
      - Order Status: {{memory.orderInfo.status}}
      - Order Total: ${{memory.orderInfo.total}}

      Be empathetic, professional, and focused on resolving their issue.
    llmConfig:
      name: ChatOpenAI
      properties:
        model: gpt-4o
        temperature: 0

  # Order lookup functionality
  - type: tool
    name: Lookup Order
    toolName: lookupOrder

  # Junction for decision making
  - type: junction
    name: Refund Decision Point

  # Refund processing
  - type: tool
    name: Process Refund
    toolName: processRefund

  # Final confirmation
  - type: promptNode
    name: Refund Confirmation
    prompt: |
      Great news! Your refund has been processed successfully.

      Refund Details:
      - Order ID: {{memory.orderId}}
      - Refund Amount: ${{memory.refundAmount}}
      - Reason: {{memory.refundReason}}

      You can expect to see the refund in your original payment method within 3-5 business days.

      Is there anything else I can help you with today?

edges:
  # Initial flow
  - source: Customer Support Trigger
    target: Support Agent
    type: stepForward

  # Route to order lookup when customer provides order ID
  - source: Support Agent
    target: Lookup Order
    type: promptCondition
    prompt: 'Customer has provided an order ID and wants to look up order information'

  # After order lookup, go to decision point
  - source: Lookup Order
    target: Refund Decision Point
    type: stepForward

  # Decision point routing
  - source: Refund Decision Point
    target: Process Refund
    type: logicalCondition
    condition: "({ memory }) => memory.orderInfo?.canRefund && memory.customerQuery?.toLowerCase().includes('refund')"

  - source: Refund Decision Point
    target: Support Agent
    type: logicalCondition
    condition: '({ memory }) => !memory.orderInfo?.canRefund'

  # After refund processing
  - source: Process Refund
    target: Refund Confirmation
    type: stepForward

  # Allow continued conversation
  - source: Refund Confirmation
    target: Support Agent
    type: promptCondition
    prompt: 'Customer has additional questions or needs more help'
```

### 4. Tools Implementation

**`tools/lookupOrder.ts`:**

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

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

const lookupOrderTool: Tool<typeof schema, Memory> = {
  name: 'lookupOrder',
  description: 'Look up order details by order ID and optionally verify with customer email',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Simulate API call to order system
      const orderInfo = await fetchOrderFromAPI(input.orderId, input.customerEmail);

      if (!orderInfo) {
        throw new Error(`Order ${input.orderId} not found or email doesn't match`);
      }

      // Determine if refund is allowed based on business rules
      const canRefund = determineRefundEligibility(orderInfo);

      return {
        memory: {
          orderId: input.orderId,
          orderInfo: {
            ...orderInfo,
            canRefund,
          },
          conversationStage: 'gathering_info',
          customerId: orderInfo.customerId,
        },
      };
    } catch (err) {
      logger.error({ message: 'Order lookup failed:', err });
      return {
        memory: {
          orderId: input.orderId,
          conversationStage: 'gathering_info',
        },
      };
    }
  },
};

// Simulate order API
async function fetchOrderFromAPI(orderId: string, email?: string) {
  // Simulate database lookup
  const orders = {
    '12345': {
      id: '12345',
      customerId: 'cust_123',
      customerEmail: 'john@example.com',
      status: 'delivered',
      total: 89.99,
      items: ['Wireless Headphones', 'Phone Case'],
      purchaseDate: '2024-01-15T10:30:00Z',
    },
    '67890': {
      id: '67890',
      customerId: 'cust_456',
      customerEmail: 'jane@example.com',
      status: 'shipped',
      total: 199.99,
      items: ['Laptop Stand', 'Wireless Mouse'],
      purchaseDate: '2024-01-20T14:20:00Z',
    },
  };

  const order = orders[orderId];
  if (!order) return null;

  // Verify email if provided
  if (email && order.customerEmail !== email) {
    return null;
  }

  return order;
}

// Business rules for refund eligibility
function determineRefundEligibility(orderInfo: any): boolean {
  const purchaseDate = new Date(orderInfo.purchaseDate);
  const now = new Date();
  const daysSincePurchase = Math.floor((now.getTime() - purchaseDate.getTime()) / (1000 * 60 * 60 * 24));

  // Refund rules:
  // - Must be within 30 days
  // - Order must be delivered
  // - Order total must be less than $500 (higher amounts need manager approval)
  return daysSincePurchase <= 30 && orderInfo.status === 'delivered' && orderInfo.total < 500;
}

export default lookupOrderTool;
```

**`tools/processRefund.ts`:**

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

const schema = z.object({
  orderId: z.string(),
  customerName: z.string(),
  reason: z.string(),
  amount: z.number().optional(), // Optional partial refund amount
});

const processRefundTool: Tool<typeof schema, Memory> = {
  name: 'processRefund',
  description: 'Process a refund for the customer order',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Determine refund amount
      const refundAmount = input.amount || memory.orderInfo?.total || 0;

      // Validate refund amount
      if (refundAmount > (memory.orderInfo?.total || 0)) {
        throw new Error('Refund amount cannot exceed order total');
      }

      // Process the refund (simulate payment processor API)
      const refundResult = await processRefundWithPaymentProcessor({
        orderId: input.orderId,
        amount: refundAmount,
        reason: input.reason,
      });

      // Log the refund for auditing
      await logRefundTransaction({
        orderId: input.orderId,
        customerId: memory.customerId,
        amount: refundAmount,
        reason: input.reason,
        processedBy: 'ai-agent',
        timestamp: new Date().toISOString(),
      });

      console.log(`✅ Refund processed: Order ${input.orderId}, Amount: $${refundAmount}`);

      return {
        memory: {
          refundAmount,
          refundReason: input.reason,
          refundProcessed: true,
          conversationStage: 'complete',
        },
      };
    } catch (err) {
      logger.error({ message: 'Refund processing failed:', err });

      return {
        memory: {
          refundAmount: 0,
          refundReason: input.reason,
          refundProcessed: false,
          conversationStage: 'gathering_info', // Go back to gathering info
        },
      };
    }
  },
};

// Simulate payment processor API
async function processRefundWithPaymentProcessor(params: any) {
  // Simulate API call delay
  await new Promise((resolve) => setTimeout(resolve, 1000));

  // Simulate occasional failures for testing
  if (Math.random() < 0.1) {
    throw new Error('Payment processor temporarily unavailable');
  }

  return {
    refundId: `ref_${Date.now()}`,
    status: 'processed',
    amount: params.amount,
  };
}

// Simulate audit logging
async function logRefundTransaction(transaction: any) {
  console.log('📝 Logging refund transaction:', transaction);
  // In real implementation, this would write to a database or audit system
}

export default processRefundTool;
```

**`tools/index.ts`:**

```ts
import lookupOrderTool from './lookupOrder';
import processRefundTool from './processRefund';

export default [lookupOrderTool, processRefundTool];
```

### 5. Main Agent Implementation

**`agent.ts`:**

```ts
import { Agent } from 'mindedjs/src/agent';
import { HumanMessage } from '@langchain/core/messages';
import { events } from 'mindedjs/src/index';
import memorySchema, { Memory } from './schema';
import tools from './tools';
import config from './minded.json';

// Create the agent
const agent = new Agent<Memory>({
  memorySchema,
  config,
  tools,
});

// Handle AI messages (responses to user)
agent.on(events.AI_MESSAGE, async ({ message, memory }) => {
  console.log('🤖 Agent:', message);
  if (memory.conversationStage) {
    console.log(`   Stage: ${memory.conversationStage}`);
  }
});

// Handle trigger events with qualification logic
agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
  if (triggerName === 'Customer Support Trigger') {
    // Qualify the trigger - only handle customer service related queries
    const lowerQuery = triggerBody.toLowerCase();
    const serviceKeywords = ['order', 'refund', 'help', 'support', 'issue', 'problem'];

    const isServiceQuery = serviceKeywords.some((keyword) => lowerQuery.includes(keyword));

    if (!isServiceQuery) {
      console.log('❌ Query not qualified for customer service');
      return { isQualified: false }; // Disqualify this trigger
    }

    console.log('✅ Customer service query qualified');

    // Transform the request and initialize memory
    return {
      isQualified: true,
      memory: {
        customerQuery: triggerBody,
        conversationStage: 'greeting',
        timestamp: new Date().toISOString(),
      },
      messages: [new HumanMessage(triggerBody)],
    };
  }

  return { isQualified: false }; // Unknown trigger
});

// Example usage function
async function runRefundExample() {
  console.log('🚀 Starting Order Refund Flow Example\n');

  const examples = [
    {
      description: 'Happy path - valid refund request',
      query: "Hi, I need to return my order #12345. The headphones don't work properly.",
    },
    {
      description: 'Order lookup required',
      query: 'I want to check the status of my recent order and possibly get a refund.',
    },
    {
      description: 'Non-service query (should be disqualified)',
      query: "What's the weather like today?",
    },
  ];

  for (const example of examples) {
    console.log(`\n📋 Example: ${example.description}`);
    console.log(`User: ${example.query}\n`);

    try {
      const result = await agent.invoke({
        triggerBody: example.query,
        triggerName: 'Customer Support Trigger',
        sessionId: `session_${Date.now()}`,
      });

      console.log('✅ Flow completed successfully');
      console.log('Final memory state:', JSON.stringify(result.memory, null, 2));
    } catch (err) {
      logger.error({ message: '❌ Flow execution failed:', err });
    }

    console.log('\n' + '─'.repeat(50));
  }
}

// Run the example if this file is executed directly
if (require.main === module) {
  runRefundExample().catch(console.error);
}

export { agent, runRefundExample };
```

## Running the Example

1. **Install dependencies:**

```bash
npm install @minded-ai/mindedjs langchain
```

2. **Set up environment:**

```bash
export OPENAI_API_KEY="your-openai-api-key"
```

3. **Run the agent:**

```bash
npx ts-node agent.ts
```

## Expected Output

```
🚀 Starting Order Refund Flow Example

📋 Example: Happy path - valid refund request
User: Hi, I need to return my order #12345. The headphones don't work properly.

✅ Customer service query qualified
🤖 Agent: Hello! I'm sorry to hear you're having trouble with your headphones. I'd be happy to help you with a return. I see you mentioned order #12345. Could you please provide your email address so I can look up your order details?

✅ Refund processed: Order 12345, Amount: $89.99
📝 Logging refund transaction: {...}
🤖 Agent: Great news! Your refund has been processed successfully...

✅ Flow completed successfully
```

## Key Features Demonstrated

1. **Intelligent Qualification**: The trigger only accepts customer service related queries
2. **Conversation State Management**: Memory tracks the conversation stage and relevant information
3. **Business Logic Integration**: Tools implement real business rules for refund eligibility
4. **Error Handling**: Graceful handling of failed API calls and invalid requests
5. **Audit Trail**: All refund transactions are logged for compliance
6. **Type Safety**: Full TypeScript type safety throughout the flow

This example provides a solid foundation for building production-ready customer service agents with MindedJS!


# Your First Eval

At Minded, we strong believe in the [Agent Development Lifecycle (ADLC)](https://www.minded.com/blog/adlc-loop), our agentic take on the Software Development Lifecycle (SDLC), a.k.a. how to ship reliable agents fast.

In this guide we’ll vibe-code an agent that sets the stage for the last-mile prompt engineering needed to go live to production - you’ll get a fresh from the oven agent

## The ADLC flow

Here’s how an [Agent Squad](https://www.minded.com/blog/agent-squad) team up from 0 to 1

<figure><img src="https://github.com/minded-ai/mindedjs/blob/main/docs/getting-started/.gitbook/assets/ADLC.png" alt=""><figcaption></figcaption></figure>

## Step by Step

### Define Minimum Viable Agent (MVA)

**Owner: Agent Product Manager**

Define the absolute minimum the agent needs to do before going live.

For the most part that means answering the following questions

If possible handoff to human reps anything that’s outside of that scope.

| Aspect                  | Example response for an ecommerce support agent            |
| ----------------------- | ---------------------------------------------------------- |
| Initial use case        | Order status                                               |
| Interface               | Zendesk messaging                                          |
| How to interact with it | sending SMS to the support number                          |
| Handoff mechanism       | Assign CS Human tag on Zendesk upon escalation             |
| Metric                  | Deflection rate - % of support tickets solved by the agent |

### Prepare E2E evals of real world cases

**Owner: Domain Expert**

Prepare end-to-end examples of how humans solve real world cases, each example should ideally include:

1. Screen recording of the different apps operated throughout the resolution
2. Explanation of the decision making process behind the resolution
3. If you’re using a knowledge base, then list all the relevant articles from it that are used for this resolution
4. List all other data sources used throughout the resolution (such as admin panels)

#### Best Practices to collect evals

1. Observe a domain expert doing the job “shadowing” them and notice how operate multiple systems and interact with end users
2. When observing the domain expert ask them WHY they did what they did?
3. How did they collect data for each of their decision?
4. How did they know which data to collect and which system to operate?
5. Do they follow a documented procedure? Which one?
6. When do they need to escalate to a supervisor
7. Focus on a niche use case initially and aim to go deep - observing multiple levels of back-and-forth between the end user and the domain expert

### Identify data sources per each eval

**Owner: Agent Engineer**

This step is critical to build agents that take actions and make decisions:

1. Identify the data sources available to the AI Agent to be able to reproduce the decision making process and/or actions of the end-to-end evals of the previous step. The most obvious data source are the interface (often that’s the customer support CRM such as Zendesk), and the admin panel (often that’s the human operator dashboard, such as Retool), however often these data sources can get tricky from 3rd party
2. Map each data source into an API endpoint and ensure the relevant fields are available from the API
3. When an API is not available consider alternatives such as Computer Use, and make sure the authentication could be enabled for an AI Agent.

### Run the agent locally

**Owner: Agent Engineer**

Now is the time to make sure all the tools and knowledge are accessible for the agent

1. Setup the local development environment (see getting started guide)
2. Add tools per each data source
3. Add knowledge per each knowledge source
4. Add a prompt node to the agent that uses each of the above tools and knowledge bases and make sure that each of them are being called by the agent

### Prompt Engineering

**Owner: Agent Engineer**

Next you’re going to solve each of the above evals by retrying again and again.

You are probably going to run a similar process to each of the eval examples:

1. Invoke the trigger - ideally in a way that lets the agent actually pull the data it needs for the example
2. Run the scenario in a step by step manner, whenever you run into an issue troubleshoot, solve it, and retry
   1. Evaluate the steps, normally evaluation covers:
      1. Agent tone of voice
      2. Tools accuracy - are the input parameters configured correctly?
      3. Did the agent invoke the right tool at the right time? with the right parameters?
      4. Did the agent take necessary actions?
      5. Did it retrieve the right knowledge base articles? Did it parse them correctly?
   2. If there were any issues there are usually two ways to go about it - solve it with code, or solve it with prompt (see our prompt engineering best practices)
   3. After you’ve applied a fix - retry the last step, until you’re able to reach the desired state
   4. If you weren’t able to solve it - consider escalating similar cases in the future, to let humans take care of cases where the agent is not perfect yet
   5. Repeat until the example reaches its final step successfully
3. Now that the example run fully it is time to review post-run logs, actions and analytics

### Agent Warm up

**Owner: Agent Engineer**

**Warm up** is everything that happens from the moment the agent wakes up to the first node execution. This is the window where we inject critical context, like `user_id` , or qualifying triggers to ensure the agent is laser-focused on the task it needs to resolve.

1. Qualify the trigger - review the data schema of the trigger and make your mind if some of those data points should indicate the agent is not qualified for certain scenarios. Remember it’s usually best to launch a reliable agent at small scale (see MVA above).
2. Enrich context - some context should or could be introduced to the agent upfront. It could be parameters `user_id` , or some tools (for example pulling order details for an ecommerce support agent)

### QA

**Owner: Domain Expert**

This is the final OK before going live. This is where the expert “stress test” the agent for more edge cases, and evaluates the results.

If the agent is good enough - then awesome time to deploy. Otherwise, it’s time for more evals 💪🏻

### Publish

**Owner: Agent Engineer**

Hit deploy on the platform and follow our deployment instructions for more details.


