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

# AI Routes

> Deploy AI agents as LoT Routes — then reach them over MQTT interaction topics or CALL AGENT from Actions, solo or as a multi-agent team

## AI Agents in LoT

Your broker already moves sensor data, drives PLCs, and stores history. **AI agents** add something new: specialists that *reason* over that world — in plain language, on the same machine, with the same MQTT and Routes you already defined.

In Coreflux, each agent is a **LoT Route** with `TYPE AGENT`. One block gives you the model, the persona, the tools, and the guardrails. Spin up an operator that answers shift questions. Add a classifier that never sees a chat window. Run five agents on one broker, each with its own brain and boundaries — no sidecar service, no copy-paste into a separate chatbot.

**Defining the Route is only half the story.** The other half is *who gets to knock on the door*: operators on MQTT topics the Route owns, or LoT Actions that dial the agent when something happens on the plant floor. Same agent definition — two front doors. Use one, use both, mix them across a team of Routes.

<Tip>
  **Like hiring for the control room, then choosing desk phone vs. internal line.** `DEFINE ROUTE … WITH TYPE AGENT` is the hire. `INTERACTION_TOPIC` is the desk line for people and apps. `CALL AGENT` is what your Actions use when a sensor fires or a timer ticks — no public chat required.
</Tip>

### When to use AI Routes

* **One agent, one job** — chat helper, classifier, or scheduled production/maintenance/solar report; each gets its own Route
* **Multi-agent teams** — split models, prompts, and permissions by role ([Multi-Agent Deployments](#multi-agent-deployments))
* **Ask the plant, don't only dashboard it** — "how many sensors are reporting?" before you wire another panel
* **MQTT-facing chat** — `INTERACTION_TOPIC` on the Route, plus MQTT ask/reply topics (your Action or custom app)
* **Hands-off automation** — `CALL AGENT` when a topic fires, a timer ticks, or a pipeline needs a verdict

<Note>
  First time? Build both patterns in ten minutes: [Creating Agents in Coreflux](/v2.0/quick-start/creating-agents). Then come back here for providers, tools, and production detail.
</Note>

***

## Complete Example (Copy & Deploy)

Pick one tab to ship something fast, or deploy **both** on the same broker. Each example is self-contained — you do not need to read the rest of this page first.

<Note>
  **Prerequisites (both tabs):** Broker running and an [OpenAI API key](https://platform.openai.com/api-keys) stored on the broker as a secret (once):

  ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
  KEEP SECRET "OPENAI_KEY" WITH "sk-..."
  ```

  The examples below reference it with `GET SECRET "OPENAI_KEY"`. Prefer local inference? Swap to `PROVIDER "ollama"` — see [Picking a Provider](#picking-a-provider).
</Note>

<Tabs>
  <Tab title="Talk with your data" icon="comments">
    **Pattern:** [Route interaction topics](#pattern-1-route-interaction-topics) — the AGENT Route owns the model and `INTERACTION_TOPIC`; a thin Action exposes **ask/reply MQTT topics** you can hit from MQTT Explorer or the HUB [Data Viewer](/v2.0/coreflux-hub/mqtt/data-viewer).

    <Note>
      The [HUB AI Assistant](/v2.0/coreflux-hub/ai-assistant) (Coreflux icon in the dock) is a **separate** built-in agent installed by the AI Setup wizard — it is not wired to `PlantAssistant` or your custom `INTERACTION_TOPIC`.
    </Note>

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE PlantAssistant WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "openai"
            WITH MODEL "gpt-5.5"
            WITH API_KEY GET SECRET "OPENAI_KEY"
            WITH BROKER_TOOLS "true"
            WITH INTERACTION_MODE "interactive"
            WITH INTERACTION_TOPIC "plant/assistant/interact"
            WITH SYSTEM_PROMPT "You are the plant assistant. Use broker tools to answer from live MQTT data. Be concise."

    DEFINE ACTION AskPlantAssistant
    ON TOPIC "plant/assistant/ask" DO
        CALL AGENT "PlantAssistant.execute"
            WITH (task = PAYLOAD)
            RETURN AS {reply}

        PUBLISH TOPIC "plant/assistant/reply" WITH {reply}
    ```

    <Steps>
      <Step title="Deploy Route and Action">
        Paste both blocks into **Coreflux HUB** (LoT Editor → Routes, then Actions), a LoT Notebook, or your usual deploy path. Confirm **PlantAssistant** (AGENT) and **AskPlantAssistant** (Action) are healthy.
      </Step>

      <Step title="Send a question over MQTT">
        In **MQTT Explorer** or HUB **Data Viewer** (MQTT app), publish your question to `plant/assistant/ask` and subscribe to `plant/assistant/reply`:

        | Direction | Topic                   | Example payload                           |
        | --------- | ----------------------- | ----------------------------------------- |
        | Publish   | `plant/assistant/ask`   | `Which MQTT topics are active right now?` |
        | Subscribe | `plant/assistant/reply` | Agent answer (plain text)                 |

        Try another live-data prompt, for example: `What is the latest payload on sensors/zone1/temperature?`
      </Step>

      <Step title="Handle follow-up questions (interactive mode)">
        If the agent needs clarification, it publishes to `plant/assistant/interact/ask`. Reply on `plant/assistant/interact/reply/{request_id}` with the `{request_id}` from that message — separate from the `plant/assistant/ask` topic your Action listens on.
      </Step>
    </Steps>

    <Check>
      You can **talk with your data** over MQTT — the agent uses broker tools, not a static chat transcript.
    </Check>
  </Tab>

  <Tab title="Automatic report" icon="clock">
    **Pattern:** [`CALL AGENT` in LoT Actions](#pattern-2-call-agent-in-lot-actions) — LoT runs the agent on a schedule; no chat UI.

    Example: **hourly production report** from live `production/` MQTT data — throughput, off-target lines, and follow-ups for supervisors.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE ProductionReporter WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "openai"
            WITH MODEL "gpt-5.4-mini"
            WITH API_KEY GET SECRET "OPENAI_KEY"
            WITH BROKER_TOOLS "true"
            WITH INTERACTION_MODE "autonomous"
            WITH SYSTEM_PROMPT "You write production shift reports. Use broker tools on production-related topics. Bullet points: throughput, downtime signals, quality risks."

    DEFINE ACTION HourlyProductionReport
    ON EVERY 1 HOUR DO
        CALL AGENT "ProductionReporter.execute"
            WITH (task = "Hourly production report: summarise output and efficiency from production/ topics, flag lines or batches that look off-target, note anything that needs follow-up before the next shift.", use_history = "false")
            RETURN AS {report}

        PUBLISH TOPIC "reports/production/hourly" WITH {report}
    ```

    <Note>
      `ProductionReporter` omits `INTERACTION_TOPIC` — only the Action triggers it. Maintenance or solar: reuse the pattern with `maintenance/` or `solar/` topics. More use cases: [Creating Agents in Coreflux](/v2.0/quick-start/creating-agents).
    </Note>

    <Steps>
      <Step title="Deploy Route and Action">
        Add **ProductionReporter** under Routes, then **HourlyProductionReport** under Actions.
      </Step>

      <Step title="Subscribe to the report">
        Subscribe to `reports/production/hourly` in MQTT Explorer or your dashboard.
      </Step>

      <Step title="Wait or speed up the test">
        Publish test data under `production/` if needed. Temporarily use `ON EVERY 30 SECONDS`, verify, then restore `ON EVERY 1 HOUR`.
      </Step>
    </Steps>

    <Check>
      **Automatic production reporting** — LoT triggers the agent; supervisors read the output topic.
    </Check>
  </Tab>
</Tabs>

<Tip>
  Run **both tabs** together when you want shift chat (`PlantAssistant`) and an hourly production report (`ProductionReporter`) on the same broker. Everything below covers providers, tools, multi-agent teams, and production settings in depth.
</Tip>

***

## Two Ways to Interact

You write the agent once in LoT. **How the world talks to it** splits two ways — pick the door that fits, or run both on the same broker.

| Pattern                                                             | Where it lives                                                                 | Best for                                                                               |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| **[Route interaction topics](#pattern-1-route-interaction-topics)** | `INTERACTION_TOPIC` on the AGENT Route (+ MQTT topics your Action or app uses) | People, custom chat UIs, MQTT Explorer, HUB Data Viewer                                |
| **[`CALL AGENT` in Actions](#pattern-2-call-agent-in-lot-actions)** | LoT `CALL AGENT "RouteName.execute"` inside an Action                          | Sensor pipelines, timers, branching logic, structured jobs with no public chat surface |

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
flowchart TB
    subgraph define [Define once in LoT]
        Route[AGENT Route<br/>model, tools, prompt]
    end

    subgraph mqtt [Pattern 1 — Route topics]
        Client[App, HUB, or MQTT client]
        Prefix[INTERACTION_TOPIC prefix]
        Client <-->|ask / reply| Prefix
        Prefix --> Route
    end

    subgraph lot [Pattern 2 — LoT Action]
        Trigger[ON TOPIC or ON EVERY]
        Action[Action body]
        Trigger --> Action
        Action -->|CALL AGENT| Route
    end
```

You can mix them: an **Operator** Route with `INTERACTION_TOPIC` for shift handover chat, plus a **Classifier** Route that only ever runs via `CALL AGENT` from a sensor Action.

***

## Minimal Example

The shortest possible AI Route points at a local Ollama model and nothing else. It has no tools yet — it simply answers questions using the LLM:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Assistant WITH TYPE AGENT
    ADD AGENT_CONFIG
        WITH MODEL "llama3.2"
```

<Note>
  This default assumes Ollama is running locally at `http://localhost:11434`. Pull the model first with `ollama pull llama3.2` if you haven't already. If the broker runs in Docker, use the Ollama container hostname in Compose (e.g. `http://ollama:11434`) or `http://host.docker.internal:11434` when Ollama is on the host.
</Note>

***

## Picking a Provider

AI Routes support four providers out of the box. Pick the one that matches where your model lives — local or cloud. Cloud providers need an API key, which you should store as a secret rather than pasting into the Route.

<Tabs>
  <Tab title="Ollama (local)">
    Runs entirely on your machine — no API key, no cloud dependency. Good for getting started and for privacy-sensitive workloads. The broker default is `llama3.2`; for stronger local quality in 2026, many teams use `qwen3:8b` or `qwen3:4b` on the same Ollama endpoint.

    <Note>
      **Prerequisite:** [Install Ollama](https://ollama.com/download) and have it running before you deploy this Route. Pull your model in Ollama first (e.g. `ollama pull qwen3:8b`) — the `MODEL` value must match a model already available in `ollama list`. Coreflux connects to your Ollama endpoint; it does not install Ollama or download models for you. Lightweight models may work for a quick test, but often are not capable enough for reliable agent work or good answer quality.

      If the broker runs in **Docker**, `localhost` inside the container is not the host—set `BASE_URL` to the reachable Ollama endpoint. With **Docker Compose**, use the Ollama service name (e.g. `http://ollama:11434`). If Ollama runs on the host, use `http://host.docker.internal:11434` (Docker Desktop) or the host's LAN IP.
    </Note>

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Assistant WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "ollama"
            WITH BASE_URL "http://localhost:11434"
            WITH MODEL "qwen3:8b"
    ```
  </Tab>

  <Tab title="OpenAI">
    Uses OpenAI's GPT-5 generation (e.g. `gpt-5.4-mini` for cost-sensitive work, `gpt-5.5` for frontier reasoning). Store the API key as a secret so it never appears in your Route definition.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Assistant WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "openai"
            WITH MODEL "gpt-5.4-mini"
            WITH API_KEY GET SECRET "OPENAI_KEY"
    ```
  </Tab>

  <Tab title="Anthropic">
    Uses Claude 4.6/4.7 models. Same secret-based pattern — `claude-sonnet-4-6` for most production agents, `claude-opus-4-7` when you need maximum reasoning depth.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Assistant WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "anthropic"
            WITH MODEL "claude-sonnet-4-6"
            WITH API_KEY GET SECRET "ANTHROPIC_KEY"
    ```
  </Tab>

  <Tab title="Mistral">
    Uses Mistral's hosted models — `mistral-large-latest` (Large 3) for quality, `mistral-small-latest` when you want lower cost and latency. Works the same way: set the provider and point to a stored API key.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Assistant WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "mistral"
            WITH MODEL "mistral-large-latest"
            WITH API_KEY GET SECRET "MISTRAL_KEY"
    ```
  </Tab>
</Tabs>

<Tip>
  Model IDs change often. Use the exact slug from your provider's catalog (OpenAI model guide, Anthropic model overview, Mistral API list, `ollama list`). Set secrets with `KEEP SECRET "OPENAI_KEY" WITH "sk-..."` — they never appear in logs or stored Route definitions.
</Tip>

***

## Core Configuration

These are the parameters most users will touch. The `AGENT` route has more advanced knobs (history pruning, tracing, token limits, confirmation flows), but you don't need them on day one.

| Parameter           | Type    | Default      | What it does                                                         |
| ------------------- | ------- | ------------ | -------------------------------------------------------------------- |
| `PROVIDER`          | string  | `ollama`     | Which LLM backend to use: `ollama`, `openai`, `anthropic`, `mistral` |
| `MODEL`             | string  | `llama3.2`   | Model name for the chosen provider                                   |
| `API_KEY`           | string  | —            | API key for cloud providers (use `GET SECRET`)                       |
| `TEMPERATURE`       | number  | `0.7`        | Creativity: `0.0` is deterministic, `1.0+` is more creative          |
| `SYSTEM_PROMPT`     | string  | *(built-in)* | Sets the agent's persona and rules                                   |
| `BROKER_TOOLS`      | boolean | `false`      | Give the agent built-in tools to read/write MQTT and Routes          |
| `MCP_ROUTES`        | string  | —            | Comma-separated names of MCP Routes the agent can call               |
| `INTERACTION_MODE`  | string  | `autonomous` | `autonomous` runs to completion, `interactive` can ask you questions |
| `INTERACTION_TOPIC` | string  | *(auto)*     | Base MQTT topic for interaction messages                             |
| `AGENT_MODE`        | string  | `agent`      | `agent` (full control) or `insight` (read-only exploration)          |

<Note>
  More options exist for production deployments — conversation history pruning, execution tracing, tool confirmation, max iteration limits. Start with the basics above, then add them as you need them.
</Note>

***

## What Tools Can the Agent Use?

A fresh agent only knows how to chat. To make it actually *do* things, you give it tools. There are three sources, and you can mix all three.

### Broker Tools

Setting `BROKER_TOOLS true` unlocks built-in tools so the agent can interact with your broker directly — no extra configuration required.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Assistant WITH TYPE AGENT
    ADD AGENT_CONFIG
        WITH MODEL "llama3.2"
        WITH BROKER_TOOLS "true"
```

With broker tools enabled, the agent can:

* **List MQTT topics** and peek at their latest payloads
* **Read a specific topic** on demand
* **Publish messages** to any topic
* **List your Routes** to see what's connected

This is the fastest way to get an agent that can answer questions like *"what's the current temperature on factory/sensor/01?"* or *"which Routes are running right now?"*.

### MCP Tools

MCP (Model Context Protocol) servers expose tools like file systems, Slack, Google Drive, or custom integrations. Any [MCP Route](./mcp-routes) you've defined can be wired into an AI Route with `MCP_ROUTES`:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Assistant WITH TYPE AGENT
    ADD AGENT_CONFIG
        WITH MODEL "llama3.2"
        WITH MCP_ROUTES "FileMcp, SlackMcp"
```

The agent discovers every tool exposed by those MCP servers automatically. If `SlackMcp` offers a `send-message` tool, the agent can call it when appropriate.

### Your Other Routes

The agent also sees every other Route you've already defined. If you have a PostgreSQL Route with an `InsertReading` event, the agent automatically gets a tool named `SensorDB.trigger_InsertReading`. Same for Modbus, REST, MongoDB, and so on — you don't need to re-declare anything.

<Tip>
  Define your data Routes first, then add the AI Route. The agent inherits all of them as tools the moment it starts.
</Tip>

***

## Insight vs. Agent Mode

Not every AI Route should be able to publish messages or trigger equipment. Use `AGENT_MODE` to control how much power the agent has:

| Mode              | What it can do                                                 | Good for                                          |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------- |
| `insight`         | Read-only: list topics, read payloads, explore Routes          | Exploring a broker, answering "what's connected?" |
| `agent` (default) | Full: read **and** write, trigger Routes, modify configuration | Automation, chat assistants, hands-on operators   |

<Tip>
  **Start in `insight` mode while you experiment.** The agent can still explain your broker and read data — but it can't accidentally publish to production topics while you're tuning the prompt.
</Tip>

***

## Pattern 1: Route Interaction Topics

Set `INTERACTION_TOPIC` on the AGENT Route and the broker wires **interaction traffic** under that prefix (for example follow-up questions on `{prefix}/ask` and replies on `{prefix}/reply/{request_id}` in interactive mode).

To **start** a conversation from MQTT Explorer or the HUB [Data Viewer](/v2.0/coreflux-hub/mqtt/data-viewer), add a thin Action that listens on your ask topic, runs `CALL AGENT "YourRoute.execute"`, and publishes the answer — as in the [Complete Example](#complete-example-copy--deploy) chat tab. Custom apps can implement the same prefix without that Action if they speak the broker's interaction protocol.

<Note>
  The [HUB AI Assistant](/v2.0/coreflux-hub/ai-assistant) is not your custom AGENT Route — it is the dock chat installed via **AI Setup** (`BrokerAgent`). Use it to build dashboards and LoT; use `INTERACTION_TOPIC` + MQTT (or your UI) for agents you define in LoT.
</Note>

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE Operator WITH TYPE AGENT
    ADD AGENT_CONFIG
        WITH MODEL "llama3.2"
        WITH BROKER_TOOLS "true"
        WITH INTERACTION_MODE "interactive"
        WITH INTERACTION_TOPIC "plant/operator"
        WITH INTERACTION_TIMEOUT "120"
        WITH SYSTEM_PROMPT "You are the plant assistant. Help operators using broker tools."
```

| Setting               | Role                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------ |
| `INTERACTION_TOPIC`   | Base MQTT prefix for this Route's chat (here: `plant/operator`)                                  |
| `INTERACTION_MODE`    | `interactive` lets the agent pause and ask follow-ups; `autonomous` runs each task to completion |
| `INTERACTION_TIMEOUT` | Seconds to wait for a human reply in interactive mode before continuing                          |

When the agent needs clarification in **interactive** mode, traffic uses subtopics under your prefix:

| Direction      | Topic                                    | What happens                                   |
| -------------- | ---------------------------------------- | ---------------------------------------------- |
| Agent → client | `{INTERACTION_TOPIC}/ask`                | Agent publishes a follow-up question           |
| Client → agent | `{INTERACTION_TOPIC}/reply/{request_id}` | Your UI or MQTT client answers; work continues |

Example with `INTERACTION_TOPIC "plant/operator"`: subscribe to `plant/operator/ask`, publish replies to `plant/operator/reply/{request_id}`.

<Tip>
  Use **Route topics** when humans or external apps drive the agent. Use **`CALL AGENT`** when LoT logic drives it — you do not need both on every Route.
</Tip>

***

## Pattern 2: `CALL AGENT` in LoT Actions

Keep the AGENT Route as the brain, but invoke it **from LoT** with `CALL AGENT "RouteName.execute"`. The Action's trigger (`ON TOPIC`, `ON EVERY`, and so on) decides *when* the agent runs; the `task` argument carries *what* to do. The result lands in a variable you can branch on or publish anywhere.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION ClassifyReading
ON TOPIC "sensors/+/reading" DO
    CALL AGENT "Classifier.execute"
        WITH (task = PAYLOAD, use_history = "false")
        RETURN AS {label}

    PUBLISH TOPIC "sensors/classified" WITH {label}
```

| Argument      | Typical use                                                                               |
| ------------- | ----------------------------------------------------------------------------------------- |
| `task`        | Instruction or payload text (required)                                                    |
| `session_id`  | Same id on the next call continues one thread (per device, ticket, or line)               |
| `use_history` | `"false"` for one-off labels; `"true"` when follow-up calls should remember earlier steps |

Specialist and watchdog agents often **omit** `INTERACTION_TOPIC` so nothing listens on a public chat prefix — they only run when an Action calls them.

You *can* bridge `CALL AGENT` to custom MQTT topics if you want a bespoke ask/reply pair, but those topics belong to **your Action**, not the Route's built-in interaction surface:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION AskAssistant
ON TOPIC "assistant/ask" DO
    CALL AGENT "Assistant.execute"
        WITH (task = PAYLOAD)
        RETURN AS {reply}

    PUBLISH TOPIC "assistant/reply" WITH {reply}
```

Here `assistant/ask` and `assistant/reply` are Action-defined topics; the agent still runs via `CALL AGENT`, not via `INTERACTION_TOPIC`.

<Note>
  Running **several AI Routes**? Pass `session_id` and `use_history` on each `CALL AGENT` so parallel automation jobs do not share the wrong memory. See [Multi-Agent Deployments](#multi-agent-deployments).
</Note>

***

## How It All Fits Together

The diagram below shows both patterns on one broker: MQTT clients use the Route's `INTERACTION_TOPIC`; LoT Actions use `CALL AGENT`. Optional tool sources are the same in either case.

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
flowchart TB
    subgraph callers [Callers]
        MQTT[MQTT client or HUB]
        LotAction[LoT Action]
    end

    subgraph route [AGENT Route Assistant]
        Prefix[INTERACTION_TOPIC<br/>plant/operator]
        AgentCore[LLM + tools]
        Prefix --> AgentCore
        LotAction -->|CALL AGENT| AgentCore
    end

    MQTT <-->|chat prefix| Prefix

    BrokerTools[Broker tools]
    MCP[MCP Routes]
    Others[Other Routes]

    AgentCore --> BrokerTools
    AgentCore --> MCP
    AgentCore --> Others
```

Every tool box is optional. A minimal agent with only `MODEL` still answers — it just cannot read your broker until you enable tools.

***

## Multi-Agent Deployments

You are not limited to a single AI Route. The same Coreflux broker can run **several agents at once** — each one a normal Route with its own name, model, system prompt, and permissions. That matters whether you are learning the product or sizing it for production: you can give **operators** a conversational helper, give **automation** a fast classifier that never sees a chat window, and keep **guests** on a read-only view — without buying extra middleware or spinning up separate services for each role.

From a buyer's perspective, multi-agent is how you **match cost to workload** (local models for high-volume tasks, cloud models where quality matters most) and **match risk to audience** (full control for trusted staff, insight-only for everyone else). From a newcomer's perspective, it is simply "define another `DEFINE ROUTE … WITH TYPE AGENT` block with a different name" — the broker runs them side by side.

<Note>
  **Think of it like staffing a control room with specialists instead of one generalist** — a friendly voice for the team at the desk, a quiet expert that only labels sensor readings, and a scheduled check-in that writes a health summary. They share the same broker, not the same job description.
</Note>

### Why give the broker more than one agent?

One catch-all assistant is hard to tune and expensive to run everywhere. Splitting the work pays off because:

* **Personalities differ.** A chat assistant should be warm and conversational; a data classifier should be terse and deterministic.
* **Access levels differ.** The Route that can trigger equipment should not be the same one you hand to a visitor.
* **Models and costs differ.** A premium cloud model is a poor fit for every sensor reading — a local model can handle volume for free while heavier reasoning stays on the model you pay for.
* **Memory should stay isolated.** The person at shift handover should not inherit someone else's half-finished conversation by accident.

### How people and software reach each agent

Map each Route to [Pattern 1](#pattern-1-route-interaction-topics) or [Pattern 2](#pattern-2-call-agent-in-lot-actions) — or use both patterns on different Routes in the same project:

| Entry                                | Pattern               | Typical setup                                                                               |
| ------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------- |
| Operators, chat apps, MQTT clients   | **Route topics**      | `INTERACTION_TOPIC` + ask/reply Action or custom UI; often `INTERACTION_MODE "interactive"` |
| Sensor pipelines, timers, batch jobs | **`CALL AGENT`**      | `ON TOPIC` / `ON EVERY` Action, no `INTERACTION_TOPIC` on the Route                         |
| Background-only specialists          | **`CALL AGENT` only** | `autonomous` mode, omit `INTERACTION_TOPIC` — no public chat prefix                         |

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
CALL AGENT "Classifier.execute"
    WITH (task = {job}, session_id = {deviceId}, use_history = "false")
    RETURN AS {result}
```

### Conversations and memory

Each **named AI Route** keeps its own conversation state. Two different Routes never share the same chat history — they are separate assistants. **Within** one Route, a **session id** (from your MQTT topic path when using interactive chat, or from `session_id` on `CALL AGENT`) keeps parallel conversations apart: two operators, two browser tabs, or two machines can talk to the same Route without their threads colliding. If you omit `session_id`, the broker still runs the task using its default session behaviour for that call — pass an explicit `session_id` when you need a reliable multi-step thread (for example one id per device or per ticket).

### How multi-agent traffic can look

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
flowchart LR
    Human[Operator or app]
    ChatTopic[MQTT chat prefix]
    OpRoute[Operator AI Route]

    SensorEvent[Sensor or event]
    LotAction[LoT Action]
    WorkerRoute[Specialist AI Route]
    ResultTopic[Result topic]

    Human -->|question| ChatTopic
    ChatTopic --> OpRoute
    OpRoute -->|answer| Human

    SensorEvent --> LotAction
    LotAction -->|CALL AGENT| WorkerRoute
    WorkerRoute --> ResultTopic
```

### Four agent archetypes

Most teams mix a few of these patterns — use one on a small setup, combine several when roles split.

**How to read the tabs:** **Operator** and **Reader** are usually **MQTT-facing** (people or HUB). **Specialist** is typically **LoT-only** (no chat topic). **Watchdog** is triggered on a **schedule** from LoT but does not need its own chat line.

<Tabs>
  <Tab title="Operator">
    The staff-facing assistant. Lives on a chat topic, has broker tools, and runs in interactive mode so it can ask follow-up questions. This is the agent your team talks to.

    **Entry:** MQTT under your `INTERACTION_TOPIC` prefix (via a thin ask/reply Action or a custom UI).

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Operator WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "openai"
            WITH MODEL "gpt-5.5"
            WITH API_KEY GET SECRET "OPENAI_KEY"
            WITH BROKER_TOOLS "true"
            WITH INTERACTION_MODE "interactive"
            WITH INTERACTION_TOPIC "plant/operator"
            WITH SYSTEM_PROMPT "You are the plant assistant. Help operators understand what is happening and take action when asked."
    ```

    **Best for:** chat UIs, control-room copilots, customer-facing assistants.
  </Tab>

  <Tab title="Specialist">
    A silent workhorse with no chat surface — no `INTERACTION_TOPIC`, no MQTT entry point. The only way in is a `CALL AGENT` from another LoT Action. Perfect for structured, repeatable tasks like classification or extraction.

    **Entry:** LoT only. Keep `INTERACTION_MODE` **`autonomous`** (the default) and skip `INTERACTION_TOPIC` so the Route does not listen for public chat traffic.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Classifier WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "ollama"
            WITH MODEL "qwen3:8b"
            WITH INTERACTION_MODE "autonomous"
            WITH TEMPERATURE "0.0"
            WITH SYSTEM_PROMPT "Classify sensor readings as NORMAL, WARNING, or CRITICAL. Reply with only the label."
    ```

    Then drive it from any Action that needs a label:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ACTION ClassifySensor
    ON TOPIC "sensors/+/reading" DO
        SET "device" WITH TOPIC POSITION 2
        SET "reading" WITH (PAYLOAD AS STRING)
        CALL AGENT "Classifier.execute"
            WITH (task = "Reading from " + {device} + ": " + {reading})
            RETURN AS {label}
        PUBLISH TOPIC "sensors/" + {device} + "/class" WITH {label}
    ```

    **Best for:** classification, tagging, extraction — anything called from automation, not humans.
  </Tab>

  <Tab title="Watchdog">
    A scheduled sentinel. It wakes up on a timer, inspects broker state via broker tools, and publishes a summary or alert. No human triggers it.

    **Entry:** A timed LoT Action that calls `CALL AGENT` — no dedicated chat topic required.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Watchdog WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "ollama"
            WITH MODEL "qwen3:4b"
            WITH INTERACTION_MODE "autonomous"
            WITH BROKER_TOOLS "true"
            WITH SYSTEM_PROMPT "You are a health monitor. Check all routes for errors and publish a concise alert summary if any are unhealthy."
    ```

    Wire it to a timed Action so it runs unattended:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ACTION HourlyHealthCheck
    ON EVERY 1 HOUR DO
        CALL AGENT "Watchdog.execute"
            WITH (task = "Check all routes for errors and summarise the broker health.")
            RETURN AS {report}
        PUBLISH TOPIC "plant/health/hourly" WITH {report}
    ```

    **Best for:** health checks, scheduled reports, overnight summaries.
  </Tab>

  <Tab title="Reader">
    The agent you can hand to anyone. It runs in `insight` mode, so it can read every topic and Route but cannot write anything — even if asked nicely. Safe for visitors, dashboards, and read-only audiences.

    **Entry:** MQTT read-only chat on your `INTERACTION_TOPIC` — safe for guests because `AGENT_MODE` is `insight`.

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    DEFINE ROUTE Reader WITH TYPE AGENT
        ADD AGENT_CONFIG
            WITH PROVIDER "openai"
            WITH MODEL "gpt-5.4-mini"
            WITH API_KEY GET SECRET "OPENAI_KEY"
            WITH BROKER_TOOLS "true"
            WITH AGENT_MODE "insight"
            WITH INTERACTION_MODE "interactive"
            WITH INTERACTION_TOPIC "plant/reader"
            WITH SYSTEM_PROMPT "You are a read-only plant observer. Answer questions about current state. You cannot change anything."
    ```

    **Best for:** management dashboards, visitor demos, on-call engineers who need to look but not touch.
  </Tab>
</Tabs>

### Mixing providers across agents

`PROVIDER` and `MODEL` are set per Route, so every agent in the team can run on a different brain. Swap any of them by redeploying a single Route definition — no broker restart, no infrastructure change.

| Use case                     | Suggested model                                   | Why                                                           |
| ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------- |
| High-stakes operator chat    | OpenAI `gpt-5.5` or Anthropic `claude-opus-4-7`   | Frontier reasoning, handles ambiguity and multi-step tool use |
| Balanced production chat     | OpenAI `gpt-5.4` or Anthropic `claude-sonnet-4-6` | Strong quality at lower cost than top-tier models             |
| Low-latency classifiers      | Ollama `qwen3:8b` (local)                         | Fast, private, good tool-following for structured labels      |
| Scheduled operations reports | OpenAI `gpt-5.4-mini`                             | Production, maintenance, or solar digests on a timer          |
| Management read portal       | OpenAI `gpt-5.4-mini`                             | Cost-effective for low-volume read queries                    |

### Smart habits when you run several agents

* **Name Routes by role** — `Line3Classifier` or `VisitorGuide`, not `Agent2`, so your team and your LoT Actions stay readable.
* **Give each worker only the tools it needs** — A classifier rarely needs broker-wide publish access or MCP; fewer tools mean faster runs and fewer surprises.
* **Remember that cost adds up** — Each Route can call a cloud model on its own schedule. More agents and more `CALL AGENT` traffic multiply tokens and rate limits.
* **Match power to trust** — Use [Insight vs. Agent Mode](#insight-vs-agent-mode) for read-only audiences; reserve full `agent` mode for staff Routes that must act.
* **Add confirmation where mistakes hurt** — `TOOL_EXECUTION_MODE "confirm"` on any Route that should pause before a destructive tool runs.

### Per-agent safety rails

Safety settings apply **per Route**, so a strict assistant and a powerful one can live on the same broker. Pair the ideas in [Insight vs. Agent Mode](#insight-vs-agent-mode) with the habits above: lock read-only audiences to `insight`, keep trusted operators in full `agent` mode, and use `TOOL_EXECUTION_MODE "confirm"` only where you need a human gate before risky tools. Trace topics, iteration limits, and history pruning are also per agent — turn tracing on for the Route you are debugging so the rest of the team stays quiet.

***

## Best Practices

<AccordionGroup>
  <Accordion title="Store API keys as secrets">
    Never paste API keys directly into a Route definition. Use `KEEP SECRET "MY_KEY" WITH "sk-..."` once, then reference it with `WITH API_KEY GET SECRET "MY_KEY"`. Secrets are never logged or exposed in stored definitions.
  </Accordion>

  <Accordion title="Start in insight mode">
    When you're still shaping the system prompt or picking a model, run the agent with `WITH AGENT_MODE "insight"`. It can still read topics and explore Routes, but it can't publish or trigger anything — so a bad prompt won't affect production data.
  </Accordion>

  <Accordion title="Write a focused system prompt">
    The default prompt is generic. Tell the agent *who it is* and *what it should refuse to do*:

    ```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
    WITH SYSTEM_PROMPT "You are a factory assistant. Answer questions about the production line using broker tools. Do not publish to any topic starting with 'control/' without explicit confirmation."
    ```
  </Accordion>

  <Accordion title="Keep iteration limits sane">
    `MAX_ITERATIONS` caps how many tool calls the agent can make per task. The default (`10`) is fine for most questions. Lift it only if you have complex multi-step workflows — runaway loops are both slow and expensive on cloud providers.
  </Accordion>
</AccordionGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Ollama not reachable">
    * Make sure Ollama is running: `ollama list` should respond without errors
    * Confirm the model is pulled: `ollama pull llama3.2`
    * If Ollama is on a different machine, set `WITH BASE_URL "http://host:11434"` explicitly
    * If the broker runs in **Docker**, `localhost` points at the container—not the host. With **Docker Compose**, use the Ollama service (e.g. `http://ollama:11434`). If Ollama is on the host, try `http://host.docker.internal:11434` (Docker Desktop) or the host LAN IP
    * Check firewall rules between the broker and the Ollama host
  </Accordion>

  <Accordion title="The agent never calls any tool">
    * Confirm `BROKER_TOOLS "true"` is set (or that `MCP_ROUTES` lists valid Routes)
    * Smaller local models sometimes ignore tools — try a more capable pull (e.g. `qwen3:8b` over `llama3.2`, or switch to a cloud model such as `gpt-5.4-mini` or `claude-sonnet-4-6`)
    * Sharpen the `SYSTEM_PROMPT` so the agent knows it *should* use tools for data questions
  </Accordion>

  <Accordion title="Cloud provider returns 401 or 403">
    * Verify the secret actually exists: publishing `LIST SECRETS` via the command console should show its name
    * Check you're using the right `PROVIDER` — Anthropic keys don't work for OpenAI and vice versa
    * For Anthropic, make sure your key has access to the specific model you set in `MODEL`
  </Accordion>

  <Accordion title="The reply is empty or gets cut off">
    * Raise `MAX_TOKENS` — long answers need more room (default is `4096`)
    * Check the model actually supports the request (some smaller models struggle with long context)
    * If you chained several tool calls, the agent may have hit `MAX_ITERATIONS` — increase it and retry
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Creating Agents in Coreflux" icon="robot" href="/v2.0/quick-start/creating-agents">
    Hands-on tutorial — chat over MQTT, automatic reports, broker tools, and multi-agent layouts in minutes.
  </Card>

  <Card title="MCP Routes" icon="brain" href="./mcp-routes">
    Plug external tools — Slack, file systems, Google Drive, custom servers — into your agent.
  </Card>
</CardGroup>
