> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kapso.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent node

> AI agent that can use tools and hold conversations

Embedded AI agent that can use tools, access data, and have multi-turn conversations with users.

## Execution behavior

Unlike other nodes that execute and immediately advance, agent nodes maintain workflow execution at the node until the agent explicitly calls the `complete_task` tool. This allows:

* **Multi-turn conversations**: Agent can exchange multiple messages with the user
* **Stateful execution**: Maintains conversation context throughout
* **Tool orchestration**: Decides when to call tools, send messages, or complete
* **Dynamic input**: New user messages are automatically injected into the agent's conversation
* **Controlled completion**: Workflow only advances when agent determines task is done

## Configuration

* `id`: Unique node identifier
* `system_prompt`: Instructions for the agent's behavior
* `provider_model_id`: AI model to use
* `temperature`: Model creativity, 0.0-1.0 (default: 0.0)
* `max_iterations`: Maximum tool calls/responses (default: 80)
* `max_tokens`: Maximum tokens per response (default: 8192)
* `reasoning_effort`: For o1 models - low, medium, high (optional)
* `flow_agent_webhooks`: Custom API tools (optional)
* `flow_agent_function_tools`: Deployed functions as tools (optional)
* `flow_agent_mcp_servers`: MCP server tools (optional, HTTP streamable only)
* `enabled_default_tools`: Built-in tools enabled for the agent node
* `default_tool_configs`: Optional configuration for built-in tools
* `sandbox_enabled`: Enables the remote sandbox for this node
* `sandbox_network_mode`: Sandbox outbound policy, `allow_all` or `allow_list`
* `sandbox_allowed_outbound_hosts`: Extra outbound hosts allowed when using `allow_list`
* `flow_agent_resources`: Repository resources mounted into the sandbox
* `observer_prompt_mode`: Behavior when outbound disabled (advanced, see below)
* `message_delivery_mode`: Message delivery behavior, `auto_send_assistant_text` or `tool_only`

### Message delivery mode

Use `message_delivery_mode` to control whether normal assistant text is sent to the WhatsApp user.

* `auto_send_assistant_text`: Normal assistant text is sent automatically. This is the default.
* `tool_only`: Normal assistant text is kept internal. The agent must call `send_notification_to_user` for every user-visible message, including questions.

When using `tool_only`, enable `send_notification_to_user` and `enter_waiting`. Call `enter_waiting` after sending a question that needs a reply.

### Emitting project events

Enable the built-in `emit_event` tool when an agent should record outcomes, labels, scores, or other project events during its work.

In the dashboard, you can choose which event definitions the agent may emit:

* Select one event definition when the agent should only fill properties for that event.
* Select multiple event definitions to give the agent a small allowlist.
* Leave the selection empty to allow the agent to emit any valid project event.

When using the Platform API directly, include `emit_event` in `enabled_default_tools`. To restrict the agent to specific event definitions, also set `default_tool_configs.emit_event.event_definition_ids`:

```json theme={null}
{
  "node_type": "agent",
  "config": {
    "system_prompt": "Score the conversation and record the result.",
    "provider_model_id": "uuid",
    "enabled_default_tools": ["emit_event", "complete_task"],
    "default_tool_configs": {
      "emit_event": {
        "event_definition_ids": ["880e8400-e29b-41d4-a716-446655440003"]
      }
    }
  }
}
```

Agent-emitted events follow the same validation, plan, usage, and loop-prevention rules as other workflow event emission. See [Events](/docs/platform/events) for limits and behavior.

## Workflow library example

```javascript theme={null}
workflow.addNode("support_agent", {
  type: "agent",
  systemPrompt: "Help the customer resolve their support request. Be concise.",
  providerModel: "gpt-5",
  temperature: 0.1,
  maxIterations: 20,
  enabledDefaultTools: ["get_whatsapp_context", "contact_conversations", "complete_task"],
  functionTools: [
    {
      name: "lookup_order",
      description: "Look up an order by order ID",
      functionSlug: "lookup-order",
      inputSchema: {
        type: "object",
        properties: {
          order_id: { type: "string" }
        },
        required: ["order_id"]
      }
    }
  ],
  webhooks: [
    {
      name: "create_ticket",
      description: "Create a support ticket",
      url: "https://api.example.com/tickets",
      method: "POST",
      headers: {
        "X-API-Key": "${ENV:SUPPORT_API_KEY}"
      },
      bodyTemplate: {
        message: "{{last_user_input}}"
      }
    }
  ]
});
```

## Remote sandbox

Enable the remote sandbox when the agent needs a disposable workspace to inspect or modify repository files during execution.

When sandbox access is enabled and at least one repository has valid credentials, the agent gets these repository tools:

* `bash`
* `read`
* `list_dir`
* `write`
* `edit`

Configured repositories are cloned into `/workspace/repos/<repo-slug>` before those tools run.

<Info>Remote sandbox is in beta. Sandbox usage is free during the beta. Pricing may change later.</Info>

### Repository resources

Repository resources are separate from agent tools. They are mounted only when `sandbox_enabled` is `true`.

* v1 supports GitHub repositories only
* Each repository needs a GitHub Personal Access Token (PAT)
* Use a repository root URL like `https://github.com/org/repo` or `git@github.com:org/repo.git`
* GitHub file, subdirectory, and `tree/...` URLs are not valid here
* Saved responses do not echo the PAT back; they return metadata like `has_pat: true`

### Sandbox network

Use `sandbox_network_mode` to control outbound network access from the sandbox:

* `allow_all`: allow all outbound hosts
* `allow_list`: only allow `sandbox_allowed_outbound_hosts`

When GitHub repositories are attached, Kapso automatically adds the GitHub hosts needed to clone and read them. You only need to add extra hosts your workflow depends on.

### Example

```json theme={null}
{
  "node_type": "agent",
  "config": {
    "system_prompt": "Inspect the repository and summarize the architecture.",
    "provider_model_id": "uuid",
    "max_iterations": 20,
    "message_delivery_mode": "auto_send_assistant_text",
    "sandbox_enabled": true,
    "sandbox_network_mode": "allow_list",
    "sandbox_allowed_outbound_hosts": ["api.example.com"],
    "flow_agent_resources": [
      {
        "resource_type": "github_repository",
        "repo_url": "https://github.com/org/repo",
        "branch": "main",
        "pat": "github_pat_..."
      }
    ]
  }
}
```

## Custom tools

Extend agent capabilities with external integrations.

### Webhook tools

Call external APIs during agent execution. Configure URL, method, headers, and body with variable interpolation.

### Function tools

Call deployed functions (Cloudflare Workers) as agent tools. Each function tool has:

* **Name**: Tool identifier the agent calls (letters, numbers, underscores, dashes)
* **Description**: Tells the agent when to use this tool
* **Function**: Select a deployed function
* **Input Schema**: Define the arguments the agent can pass

**Payload structure:**

```json theme={null}
{
  "input": { ... },              // tool arguments from the agent
  "execution_context": { ... },  // flow vars, system, context, metadata
  "flow_info": { ... },          // flow id, name, step_id
  "flow_events": [ ... ],        // most recent 10 events
  "whatsapp_context": { ... }    // present for WhatsApp runs
}
```

The agent only controls `input`. Kapso automatically injects the rest.

Read it in your function like this:

```javascript theme={null}
async function handler(request, env) {
  const body = await request.json();
  const input = body.input || {};
  const executionContext = body.execution_context || {};
  const vars = executionContext.vars || {};

  return new Response(JSON.stringify({
    vars: {
      customer_email: input.email || vars.customer_email || null
    }
  }), {
    headers: { "Content-Type": "application/json" }
  });
}
```

**Response format:**

Return JSON. Include a `vars` object to update flow variables:

```json theme={null}
{
  "vars": {
    "lead_saved": true,
    "lead_id": "abc123"
  }
}
```

### MCP servers

MCP server URLs and headers support variable substitution:

```
# URL with variables
https://api.example.com/mcp/{{system.customer.external_customer_id}}

# Headers with env and context
Authorization: Bearer ${ENV:MCP_API_KEY}
X-Customer-Id: {{system.customer.id}}
X-Phone: {{context.phone_number}}
```

Supported: `{{vars.*}}`, `{{system.*}}`, `{{context.*}}`, `${ENV:KEY}`

<Warning>
  URLs resolving to localhost or private IPs will fail in production (SSRF protection).
</Warning>

## Observer mode

When a workflow runs with outbound messages disabled (`allow_outbound: false`), the agent operates in "observer mode". The `observer_prompt_mode` setting controls how the agent behaves:

* `interactive_chat` (default): Agent chats with the operator via the Workflow Chat sidebar in the inbox. Use for workflows where human review or input is needed.
* `analysis_only`: Agent runs non-interactively with no chat interface. Use for background analysis or logging workflows.

The Workflow Chat sidebar appears in the inbox when viewing conversations with active observer-mode executions.

## Built-in tools

<AccordionGroup>
  <Accordion title="send_notification_to_user" icon="message">
    Send a message to the user without waiting for a response.

    **Parameters:**

    * `message` (string, required): The text message to send

    **Usage:** Send progress updates, confirmations, or notifications
  </Accordion>

  <Accordion title="send_media" icon="image">
    Send media files to the user via WhatsApp.

    **Parameters:**

    * `media_url` (string, required): URL of the media file
    * `media_type` (string, required): "image", "video", "audio", or "document"
    * `caption` (string, optional): Caption for the media

    **Usage:** Share images, documents, or other media content
  </Accordion>

  <Accordion title="get_execution_metadata" icon="info">
    Access flow execution context and variables.

    **Parameters:** None

    **Returns:** Flow variables, execution context, and metadata

    **Usage:** Access stored data and flow state information
  </Accordion>

  <Accordion title="get_whatsapp_context" icon="phone">
    Get WhatsApp conversation details.

    **Parameters:** None

    **Returns:** Phone number, conversation ID, and contact information

    **Usage:** Access user contact details for personalization
  </Accordion>

  <Accordion title="contact_conversations" icon="messages-square">
    List or read older WhatsApp conversations for the current contact.

    **Parameters:**

    * `action` (string, required): Use `"list"` to return older conversations, then `"read"` to inspect one conversation
    * `conversation_id` (string, required for `read`): Conversation ID returned by `list`
    * `limit` (number, optional): Limits conversations for `list` or messages for `read`

    **Returns:** Matching contact details, prior conversation IDs, statuses, timestamps, and message snippets

    **Usage:** Give an agent context from earlier support conversations before it answers a returning WhatsApp contact
  </Accordion>

  <Accordion title="save_variable" icon="database">
    Store data for use in later flow steps.

    **Parameters:**

    * `variable_name` (string, required): Variable name to save
    * `value` (string, required): Value to store. The tool receives this as a string and parses it using `value_type`
    * `value_type` (string, required): One of `string`, `integer`, `float`, `boolean`, `array`, or `object`

    **Type rules:**

    * Use `integer` for whole numbers, e.g. `value: "42"`
    * Use `float` for decimals, e.g. `value: "19.99"`
    * Use `boolean` with `value: "true"` or `value: "false"`; `yes`/`no`, `1`/`0`, and `on`/`off` are also accepted
    * Use `array` with a valid JSON array string, e.g. `value: "[\"small\", \"medium\"]"`
    * Use `object` with a valid JSON object string, e.g. `value: "{\"plan\":\"pro\",\"seats\":3}"`
    * Do not use the legacy `key` parameter

    **Usage:** Save user data, API responses, or calculated values
  </Accordion>

  <Accordion title="get_variable" icon="eye">
    Retrieve previously stored data.

    **Parameters:**

    * `variable_name` (string, required): Variable name to retrieve. Use `"*"` to retrieve all variables
    * `include_metadata` (boolean, optional): Include inferred type and size metadata

    Do not use the legacy `key` parameter.

    **Usage:** Access data saved in earlier steps
  </Accordion>

  <Accordion title="get_current_datetime" icon="clock">
    Get the current date and time.

    **Parameters:** None

    **Returns:** Current timestamp in ISO format

    **Usage:** Time-based logic and timestamp generation
  </Accordion>

  <Accordion title="complete_task" icon="check">
    Complete the agent's task and continue the flow.

    **Parameters:** None

    **Usage:** Signal task completion and advance to next step
  </Accordion>

  <Accordion title="handoff_to_human" icon="user">
    Transfer the conversation to a human agent.

    **Parameters:**

    * `reason` (string, optional): Reason for handoff

    **Usage:** Escalate complex issues to human support
  </Accordion>

  <Accordion title="enter_waiting" icon="pause-circle">
    Pause the workflow execution and enter waiting state. The conversation can resume when the user sends a new message.

    **Parameters:** None

    **Usage:** Wait for user input mid-conversation without completing the task

    **How it works:**

    * Agent pauses execution at the current node
    * Workflow enters "waiting" state
    * When user sends a new message, workflow resumes and the agent continues from where it left off
    * Agent retains full conversation context after resuming

    **Note:** This tool is required by default for new workflows (created after Feb 5, 2026). Legacy workflows can enable it as an optional tool.
  </Accordion>

  <Accordion title="ask_about_file" icon="file-magnifying-glass">
    Analyze files and answer questions about their content. Supports PDFs, images, text files, and Office documents (.docx, .xlsx, .pptx).

    **Parameters:**

    * `file_url` (string, required): Kapso file URL (use WhatsApp `media_data.url` from `get_whatsapp_context`)
    * `question` (string, required): What you want to know about the file

    **Returns:** Answer text, filename, MIME type

    **Limits:**

    * Max file size: 30MB
    * Office docs: Text extracted (DOCX paragraphs, XLSX first 10 sheets/50 rows, PPTX first 40 slides)
    * Legacy formats (.doc/.xls/.ppt) not supported - convert to modern format first

    **Usage:** Summarize documents, extract data from spreadsheets, analyze images
  </Accordion>

  <Accordion title="Dynamic webhook tools" icon="webhook">
    Custom tools for external API integration.

    **Parameters:** Defined by webhook configuration

    **Usage:** Call external APIs, fetch data, trigger actions
  </Accordion>
</AccordionGroup>

## External inputs

When a workflow is resumed via API or receives input from non-WhatsApp sources (e.g., Slack replies, API payloads), the agent automatically tags these as external inputs to distinguish them from direct user messages.

**How it works:**

External inputs are wrapped in `<external_input>` tags when presented to the agent:

```xml theme={null}
<external_input>
{"status": "approved", "comments": "Looks good"}
</external_input>
```

The agent's system prompt includes context that these inputs are from internal teams or external systems, not the WhatsApp user. This helps the agent:

* Understand the input source
* Adapt its tone (e.g., acknowledge internal team input differently than user messages)
* Make better decisions about what to communicate to the end user

**Triggering external inputs:**

External inputs are automatically created when:

* Resuming a workflow via the Platform API resume endpoint with a payload
* Using Slack integration to provide internal team responses
* Triggering workflows via API with initial data

**Example workflow:**

```mermaid theme={null}
graph LR
    A[API trigger] --> B[Agent<br/>Process order]
    B --> C[Wait for approval]
    C --> D[Slack reply<br/>External input]
    D --> B
    B --> E[Send confirmation]
```

When the Slack reply arrives, it's tagged as an external input so the agent knows it's from your team, not the customer.

## How it works

1. **Starts conversation**: Uses system prompt and conversation history
2. **Tool access**: Can call built-in tools and custom webhooks
3. **Multi-turn**: Continues until calls `complete_task` or needs user input
4. **Message injection**: New user messages are automatically injected during conversation
5. **External input tagging**: API payloads and non-WhatsApp inputs are wrapped in `<external_input>` tags
6. **Workflow control**: Returns `next` edge when task completed, `wait` when needs input

## Usage patterns

**Support workflow**

```mermaid theme={null}
graph LR
    A[Wait for response] --> B[Agent<br/>Handle request]
    B --> C[Send text<br/>Resolution]
    B --> D[Handoff<br/>To human]
```

**Data processing**

```mermaid theme={null}
graph LR
    A[Agent<br/>Get order info] --> B[Function<br/>Update database]
    B --> C[Send text<br/>Confirmation]
```
