---
title: "Quick start"
description: "Deploy Ankole, set up enterprise identity and models, connect an IM channel, and complete a real Agent conversation."
url: "https://ankole.agentbull.com/en-US/docs/quickstart/"
lang: "en-US"
---

> Documentation index for AI Agents: https://ankole.agentbull.com/en-US/llms.txt

# Quick start

## How Ankole is deployed

A private Ankole deployment instance has one control plane and one or more Agent Computer Workers. The control plane owns durable domain state and supervision as the management platform; a Worker provides the execution environment and acts as the Agent's work computer.

One Worker can serve several Agents. In finance or another environment that needs strict isolation, give each Agent a dedicated Worker, just as a work computer can be shared by several interns or assigned to one colleague.

> **💡 Did you know?**
>
> Even when several Agents share one Worker, each Agent runs in a separate sandbox. The sandbox provides basic process and file-system isolation and reduces interference between Agents. It is a lightweight isolation layer, not an absolute security boundary.

Each instance also needs PostgreSQL and persistent disk storage. A single-host deployment can use a local or virtual disk. A Kubernetes deployment needs NFS or another shared volume that supports ReadWriteMany.

<a id="terminology"></a>

### Terms used in this guide

This page defines the terms used in Ankole user documentation and interfaces. The names in parentheses match third-party platforms, configuration fields, and APIs. Later sections use the short form.

| Canonical term | Meaning | Short form |
|---|---|---|
| **Private deployment instance** | One complete Ankole system that an enterprise deploys and manages | Instance |
| **Principal** | A person, Agent, or system service that can have an identity and permissions in Ankole | Principal |
| **Identity Provider (IdP)** | An external identity source that provides Console SSO and synchronizes employees, contacts, and organization structure | IdP |
| **Chat platform** | An external conversation platform such as Slack, Teams, Lark/Feishu, DingTalk, WeCom, Telegram, Discord, LINE, WhatsApp, or a mailbox | Platform |
| **Channel Provider** | One chat application or bot configuration that receives messages and sends Agent replies | Channel Provider |
| **Signal Routing Rule (Signal Binding)** | The rule that sends messages or events from a signal source to an Agent | Routing rule |
| **LLM Provider** | A configuration that stores a model service endpoint, credentials, and available models | LLM Provider |
| **Background Agent Jobs profile (internal key: `coding`)** | Selects the AIGateway provider and model for Background Agent Jobs; normal conversations do not select it by code volume | Background Agent Jobs |

## Let an Agent complete the setup

You can send this prompt to Codex, Claude Code, or another Agent that can operate a terminal:

```text
Use https://ankole.agentbull.com/en-US/docs/quickstart/index.md to help me deploy and configure a private Ankole deployment instance. First, inspect my environment and recommend Docker Compose, Kubernetes, or installing from source. Then help me set up the Identity Provider (IdP), LLM Provider, Agent, Channel Provider, and Signal Routing Rule (Signal Binding). If I did not specify an IdP and IM platform, ask which identity source and chat platform I want to use. Do not choose them for me. Do not expose secrets in chat or command output. You are finished only when I receive a real Agent reply in my selected IM.
```

<a id="deployment"></a>

## 1. Deploy Ankole

Use Docker Compose for one host or Kubernetes for an enterprise deployment. Install from source for development and debugging.

**Choose a deployment method**

- Docker Compose · Single-host recommended
- Kubernetes · Enterprise recommended
- Install from source

### Docker Compose · Single-host recommended

The best starting point for most teams. One Linux, macOS, or Windows host with Docker runs PostgreSQL, the control plane, one Agent Computer Worker, and Caddy HTTPS.

**Before you start**

- A Linux, macOS, or Windows host that can run Linux amd64 or arm64 containers
- Docker Engine with the Compose plugin, or Docker Desktop
- Persistent disk, a DNS name, and ports 80 and 443

#### Basic setup · Install with Docker Compose

Keep provider API keys out of this deployment file. You will add them in the Console after sign-in.

##### 1. Get the deployment package

```bash
git clone https://github.com/AgentBull/ankole.git
cd ankole/tools/deploy/docker-compose
cp .env.example .env
chmod 600 .env
```

##### 2. Generate three independent secrets

Copy and run this command. Then paste its complete output into .env.

```bash
printf '%s\n' \
  '# PostgreSQL database password' \
  "POSTGRES_PASSWORD=$(openssl rand -hex 32)" \
  '' \
  '# ANKOLE master encryption key' \
  "ANKOLE_SECRET_BASE=$(openssl rand -hex 32)" \
  '' \
  '# Authentication key shared by the ANKOLE control plane and Agent Workers' \
  "ANKOLE_RUNTIME_FABRIC_WORKER_AUTH_KEY=$(openssl rand -hex 32)"
```

##### 3. Set the public host

Set ANKOLE_HOST to the DNS name that points to this host. Set ACME_EMAIL to an address that can receive certificate notices.

- `ANKOLE_HOST`: `ankole.example.com` — The HTTPS host that people and provider callbacks will use.
- `ACME_EMAIL`: `ops@example.com` — Contact address for Caddy certificate management.

##### 4. Start and verify the stack

Compose waits for PostgreSQL, runs migrations, stores the Worker key, and then starts the control plane, Worker, and Caddy.

```bash
docker compose pull
docker compose up -d
docker compose ps
```

##### 5. Open the first setup

Open https://<ANKOLE_HOST>/setup and enter the activation code.

```bash
docker compose logs control-plane | grep "SETUP ACTIVATION CODE"
```

#### Advanced settings · Production control, backup, and local certificates

Use these settings before you move the first deployment into production.

##### 1. Pin a verified image pair

The control-plane and Worker tags move together after RuntimeFabric verification. For a controlled rollout, pin both images to digests from the same source revision.

- `ANKOLE_CONTROL_PLANE_IMAGE` — Control-plane image or immutable digest.
- `ANKOLE_WORKER_IMAGE` — Worker image or immutable digest from the same verified pair.
- `ANKOLE_POSTGRESQL_IMAGE` — Optional immutable digest for the bundled database image.

##### 2. Back up before every upgrade

Also snapshot the ankole_agents_data volume. Test the database and Agent Home restore together on a separate host.

```bash
docker compose exec -T postgresql \
  pg_dump -U ankole -d ankole -Fc \
  > "ankole-$(date +%Y%m%d).dump"
```

##### 3. Upgrade with a short interruption

```bash
docker compose pull
docker compose down
docker compose up -d --force-recreate
docker compose ps
```

> docker compose down keeps named volumes. docker compose down -v deletes PostgreSQL, Agent Home, and Caddy data. Do not use -v unless permanent deletion is intended and a tested backup exists.

##### 4. Use a local-only HTTPS host

Set ANKOLE_HOST=ankole.localhost for a local test. After the first start, copy Caddy’s root certificate and trust it on every client.

```bash
docker compose cp \
  caddy:/data/caddy/pki/authorities/local/root.crt \
  ./ankole-local-ca.crt
```

- [Full Compose operations guide](https://github.com/AgentBull/ankole/blob/main/tools/deploy/docker-compose/README.md)

### Kubernetes · Enterprise recommended

Use the Helm chart when you already operate Kubernetes and need cluster scheduling, an HTTPS Ingress, and shared Agent Home storage.

**Before you start**

- Kubernetes 1.27 or later and Helm 3 or later
- Linux amd64 or arm64 nodes
- An HTTPS Ingress and ReadWriteMany storage for Agent Home

#### Basic setup · Install the Helm chart

The shortest path uses the bundled PostgreSQL 18 image, which includes pg_search and vector.

##### 1. Get the chart and create the namespace

```bash
git clone https://github.com/AgentBull/ankole.git
cd ankole
kubectl create namespace ankole
```

##### 2. Create the bootstrap Secret

```bash
POSTGRES_PASSWORD="$(openssl rand -hex 24)"
ANKOLE_SECRET_BASE="$(openssl rand -hex 32)"
ANKOLE_WORKER_AUTH_KEY="$(openssl rand -hex 24)"

kubectl -n ankole create secret generic ankole-bootstrap \
  --from-literal="POSTGRES_PASSWORD=${POSTGRES_PASSWORD}" \
  --from-literal="ANKOLE_SECRET_BASE=${ANKOLE_SECRET_BASE}" \
  --from-literal="ANKOLE_RUNTIME_FABRIC_WORKER_AUTH_KEY=${ANKOLE_WORKER_AUTH_KEY}" \
  --from-literal="DATABASE_URL=ecto://ankole:${POSTGRES_PASSWORD}@ankole-postgresql:5432/ankole"

unset POSTGRES_PASSWORD ANKOLE_SECRET_BASE ANKOLE_WORKER_AUTH_KEY
```

##### 3. Create values-production.yaml

Replace the host, Ingress details, and both StorageClass names. Keep fullnameOverride when you use the DATABASE_URL shown above.

```yaml
fullnameOverride: ankole

secrets:
  existingSecret: ankole-bootstrap

controlPlane:
  publicHost: ankole.example.com

worker:
  agents:
    persistence:
      storageClass: nfs-rwx
      size: 100Gi

postgresql:
  enabled: true
  persistence:
    storageClass: standard
    size: 50Gi

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: ankole.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: ankole-tls
      hosts:
        - ankole.example.com
```

##### 4. Install and wait

```bash
helm upgrade --install ankole ./tools/deploy/helm/ankole-agent \
  --namespace ankole \
  --values values-production.yaml \
  --wait \
  --timeout 15m
```

##### 5. Verify the workloads

```bash
kubectl -n ankole get pods
kubectl -n ankole rollout status deployment/ankole-control-plane --timeout=10m
kubectl -n ankole rollout status deployment/ankole-worker --timeout=10m
```

##### 6. Open the first setup

Open https://ankole.example.com/setup and enter the activation code.

```bash
kubectl -n ankole logs deployment/ankole-control-plane \
  -c control-plane | grep "SETUP ACTIVATION CODE"
```

#### Advanced settings · External PostgreSQL, image policy, and cluster security

Review these items before a controlled production rollout.

##### 1. Use an external PostgreSQL server

The server must run PostgreSQL 18 or later, preload pg_search, and make pg_search and vector available to the application database owner.

- `postgresql.enabled`: `false` — Disables the bundled database.
- `DATABASE_URL` — Store the external database URL in ankole-bootstrap.

```sql
SHOW server_version_num;
SHOW shared_preload_libraries;

SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name IN ('pg_search', 'vector')
ORDER BY name;
```

##### 2. Confirm the Worker security profile

Strong bubblewrap isolation needs SYS_ADMIN, an unconfined seccomp profile, and an unmasked /proc. The chart sets them, but the cluster admission policy must allow them.

> Treat Worker nodes as a trusted first-party compute boundary. Use a dedicated node pool or an approved equivalent sandbox when cluster policy rejects this profile.

##### 3. Pin and upgrade matching images

Pin the control-plane and Worker digests from the same verified source revision. Back up PostgreSQL and Agent Home before helm upgrade.

> A Helm rollback does not reverse a database migration. Restore the database backup when an application rollback also needs an older schema.

##### 4. Read the complete chart contract

- [Full Helm deployment guide](https://github.com/AgentBull/ankole/blob/main/tools/deploy/helm/ankole-agent/README.md)

### Install from source

Use the source path for development, debugging, and local evaluation. It starts PostgreSQL, Phoenix, the Console, frontend assets, and one managed Docker Worker.

**Before you start**

- macOS or Linux; use WSL2 on Windows
- An account that can install system packages
- Docker Desktop or Docker Engine

#### Basic setup · Run the development environment

##### 1. Clone the repository

Run the remaining commands from the repository root.

```bash
git clone https://github.com/AgentBull/ankole.git
cd ankole
git status --short
```

##### 2. Install the pinned toolchain

The script installs build packages, Docker, Rust, Elixir and Erlang, and the Bun version pinned by the repository.

```bash
bash tools/devkit/scripts/env-setup.sh
```

##### 3. Open a new terminal and verify every boundary

On macOS, start Docker Desktop first. On Linux, sign out and back in if the installer added your user to the docker group.

```bash
bun --version
elixir --version
rustc --version
cargo clippy --version
docker compose version
docker info
```

##### 4. Install dependencies and initialize PostgreSQL

```bash
bun install
bun run services:start
bun run services:status
bun run control-plane:setup
```

##### 5. Start Ankole

Keep this terminal open. Open http://localhost:4000 and use the activation code printed by the development server.

```bash
bun dev
```

#### Advanced settings · Local checks, shutdown, and forwarded origins

These controls help when you develop against the complete runtime or use a remote workspace.

##### 1. Read the activation code in another terminal

```bash
bun run kit show bootstrap-activation-code
```

##### 2. Check the visible runtime boundaries

Stop the control plane and Worker with Ctrl+C in the bun dev terminal. Stop PostgreSQL separately when you finish.

```bash
bun run services:status
curl -I http://localhost:4000/
docker ps --filter name=ankole-dev-agent-computer
```

```bash
bun run services:stop
```

##### 3. Use Codespaces or another forwarded origin

Register the forwarded HTTPS origin in the IdP. A localhost callback and a forwarded callback are different URLs, so verify sign-in against the URL the browser uses.

> Use this path for development. Use Docker Compose or Kubernetes for a production deployment.

<a id="identity-providers"></a>

## 2. Set up the identity provider first

Ankole is designed for private deployment inside an enterprise. Each enterprise operates one instance. Inside that instance, Agents, employees, and system services are represented as Principals, and the authorization module manages their permissions.

Like Hermes Agent and OpenClaw, Ankole connects to chat channels. It also connects to the enterprise identity source so it can synchronize employees, directory contacts, and the organization structure. Configure an Identity Provider (IdP) before you configure the chat channel.

| Setting | What it controls | Configure it where |
|---|---|---|
| **Identity Provider (IdP)** | Console SSO and the employees, contacts, organization structure, and permission groups synchronized from the enterprise directory | `/setup` first, then **Console → Identity Providers** |
| **Channel Provider** | The IM app or bot that receives messages and sends Agent replies | **Console → Signal Routing** |

The identity source and chat channel can use different platforms. Employees can sign in through Google Workspace and talk to the Agent in Slack. Entra ID can supply identity while Lark, Slack, DingTalk, Teams, or WeCom carries the conversation.

### Complete the setup for your IdP

Select the identity source that your enterprise uses. Each tab starts in the provider console and ends with the first sign-in and directory sync.

If you enable the adapter for the first time, restart the control plane after the first sign-in. This restart starts the plugin background work, such as directory connections and Graph subscriptions.

**Choose an identity provider**

- Slack
- Microsoft Entra ID
- Google Workspace
- Lark / Feishu
- DingTalk
- WeCom

#### Slack

Use one Slack app for Console sign-in and for workspace member and user-group sync. The default setup needs an OAuth client, a Bot Token, and an App Token.

**Before you start**

- A workspace administrator who can create and install a Slack app
- A public HTTPS address for Ankole
- A workspace member account for the first sign-in

##### Basic setup · Set up the Slack IdP

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select Slack Adapter on the plugin page, save the selection, and then select Slack.

Keep Configuration ID (Provider ID) as slack-main. Use Copy to copy the login callback URL. Do not type or change this URL by hand.

###### 2. Create the Slack app

Open Slack API Your Apps and select Create New App → From scratch. Enter an app name and select the workspace that holds your employees.

Open Basic Information → App Credentials. Copy the Client ID and Client Secret.

- [Open Slack Your Apps](https://api.slack.com/apps)

###### 3. Register the login callback

Open OAuth & Permissions → Redirect URLs. Select Add New Redirect URL, paste the complete URL from Ankole, and save it.

Ankole requests the openid, profile, and email sign-in scopes by default. Do not replace them with chat bot scopes.

- [Slack sign-in guide](https://api.slack.com/authentication/sign-in-with-slack)

###### 4. Grant directory access and get the Bot Token

On OAuth & Permissions, add the three scopes below under Bot Token Scopes. Then select Install to Workspace and approve the installation.

- `users:read` — Read member profiles
- `users:read.email` — Read member email addresses
- `usergroups:read` — Read user groups and their members

> Copy the Bot User OAuth Token after installation. It must start with xoxb-. Reinstall the app after each scope change.

###### 5. Get the App Token and enable Socket Mode

Open Basic Information → App-Level Tokens. Select Generate Token and Scopes, add the scope below, and copy the generated xapp- token. Then open Socket Mode and turn on Enable Socket Mode.

- `connections:write` — Let the App Token open a Socket Mode connection

###### 6. Subscribe to directory events

Open Event Subscriptions and enable events. Under Subscribe to bot events, add each of the five events below.

- `team_join` — A member joins the workspace
- `user_change` — A member profile changes
- `subteam_created` — A user group is created
- `subteam_updated` — A user group changes
- `subteam_members_changed` — A user-group membership changes

- [Slack Socket Mode guide](https://api.slack.com/apis/connections/socket)

###### 7. Enter the Slack values in Ankole

Keep Sync directory and Sync directory changes on. Select Validate configuration and sign in. Then complete the Slack authorization.

- Client ID: the Client ID from Basic Information
- Client Secret: the Client Secret on the same page
- Workspace ID: optional. Enter the T… value after /client/ in the Slack web URL to preselect a sign-in workspace; it does not limit directory sync
- Bot User OAuth Token: the xoxb- token
- App-Level Token: the xapp- token

###### 8. Verify sign-in and the first sync

After Slack returns to Ankole, this user becomes the first root administrator. Open Console → Identity Providers, select slack-main, and select Run full sync.

When the sync finishes, check Console → Principals and Principal groups for the workspace members and Slack user groups.

##### Advanced settings · Sign-in only, realtime sync, and credential updates

Turn off the default directory sync only when you do not need the Slack directory.

###### 1. Use Slack only for sign-in

When you turn off Sync directory, the Bot User OAuth Token is not required and realtime sync also turns off. The Client ID and Client Secret are still required.

###### 2. Use full sync without realtime events

Keep Sync directory on and turn off Sync directory changes. This setup needs the Bot User OAuth Token but does not need the App-Level Token. Member changes appear after the next full sync.

###### 3. Update scopes or tokens

After a Slack scope or Bot Token change, reinstall the app and update the token in Identity. Update the App Token after rotation, or Socket Mode cannot connect.

#### Microsoft Entra ID

Register a single-tenant Entra app. It signs users into the Console, reads users and groups, and receives directory changes through Microsoft Graph.

**Before you start**

- An Entra administrator who can register apps and grant admin consent
- A public HTTPS address for Ankole
- A tenant member account for the first sign-in

##### Basic setup · Set up Microsoft Entra ID

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select Microsoft 365 Adapter on the plugin page, save the selection, and then select Entra ID.

Keep Configuration ID (Provider ID) as entra-id-main. Use Copy to copy the login callback URL.

###### 2. Register a single-tenant app

Open the Microsoft Entra admin center. Go to Entra ID → App registrations → New registration. Enter a name and select Accounts in this organizational directory only.

Under Redirect URI, select Web and paste the complete callback URL from Ankole. Then select Register.

- [Microsoft app registration guide](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)

###### 3. Copy the three app values

On the app Overview page, copy Application (client) ID and Directory (tenant) ID.

Open Certificates & secrets → Client secrets → New client secret. Copy the Value as soon as you create it. Do not copy the Secret ID. The Value is hidden after you leave the page.

###### 4. Grant Microsoft Graph permissions

Open API permissions → Add a permission → Microsoft Graph. Add the three permissions below. Then select Grant admin consent for <organization>. Confirm that all three show granted status.

- `User.Read` — Delegated permission
- `User.Read.All` — Application permission
- `Group.Read.All` — Application permission

- [Microsoft Graph permission reference](https://learn.microsoft.com/en-us/graph/permissions-reference)

###### 5. Enter the Entra values in Ankole

Keep Sync directory and Sync directory changes on. Select Validate configuration and sign in. Use an account from this tenant.

- Directory (tenant) ID: the value from the app Overview page
- Application (client) ID: the value from the app Overview page
- Client secret value: the secret Value
- Ankole public URL: https://<ANKOLE_HOST>, not an internal container or cluster address

###### 6. Verify sign-in and the first sync

After sign-in, this user becomes the first root administrator. Open Console → Identity Providers, select entra-id-main, and select Run full sync.

When the sync finishes, check Principals and Principal groups for the tenant users and groups.

##### Advanced settings · Graph notifications, guests, and group filters

Microsoft Graph must reach Ankole from the public internet for realtime sync.

###### 1. Make the notification endpoint reachable

When Sync directory changes is on, Ankole public URL must be a valid public HTTPS address. Ankole creates /webhooks/v1/entra-id/entra-id-main/directory under it and manages the Graph subscriptions.

- [Microsoft Graph change notification guide](https://learn.microsoft.com/en-us/graph/change-notifications-overview)

###### 2. Turn off realtime sync without public ingress

If Graph cannot reach this instance, turn off Sync directory changes before you save. Full sync still works, and Ankole public URL is no longer required.

###### 3. Include guests or filter groups only when needed

Include guest users is off by default. Synced groups filter accepts a Microsoft Graph OData $filter. Complete an unfiltered sync first, so you do not mistake a filter result for a permission failure.

#### Google Workspace

Google sign-in and directory access use separate credentials. An OAuth client handles sign-in. A service account with domain-wide delegation syncs users and groups.

**Before you start**

- A Google Workspace super administrator
- An account that can manage a Google Cloud project
- A public HTTPS address for Ankole

##### Basic setup · Set up Google Workspace

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select Google Workspace Adapter on the plugin page, save the selection, and then select Google Workspace.

Keep Configuration ID (Provider ID) as google-workspace-main. Use Copy to copy the login callback URL.

###### 2. Prepare the Google Cloud project

Open Google Cloud Console and select or create a project owned by your organization. In the API Library, find and enable Admin SDK API.

Open Google Auth platform and complete the app information. Set Audience to Internal, so the app is for employees in this Workspace.

- [Open Google Cloud Console](https://console.cloud.google.com/)

###### 3. Create the OAuth client for sign-in

Open Google Auth platform → Clients → Create client. Select Web application as the application type.

Under Authorized redirect URIs, paste the complete callback URL from Ankole. Create the client, and copy its Client ID and Client Secret.

- [Google OAuth client guide](https://developers.google.com/workspace/guides/create-credentials)

###### 4. Create the service account for directory sync

Open IAM & Admin → Service Accounts and create a service account. Open its details, enable Google Workspace domain-wide delegation, and record its numeric Client ID.

Open Keys → Add key → Create new key and select JSON. You will paste the complete JSON file into Ankole. Do not commit this file to the repository.

- [Google service account guide](https://developers.google.com/identity/protocols/oauth2/service-account)

###### 5. Approve domain-wide delegation in Workspace

Open admin.google.com. Go to Security → Access and data control → API Controls → Manage Domain Wide Delegation, and select Add new.

Enter the numeric service-account Client ID. OAuth scopes is one field, so copy the complete line below, paste it, and authorize it.

**Copy all OAuth scopes**

```text
https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/admin.directory.group.readonly,https://www.googleapis.com/auth/admin.directory.group.member.readonly
```

- [Google Directory API authorization guide](https://developers.google.com/workspace/admin/directory/v1/guides/authorizing)

###### 6. Enter the Google values in Ankole

Keep Sync directory on. Select Validate configuration and sign in. Use a Workspace account from an allowed domain.

- OAuth client ID and OAuth client secret: credentials from the Web application OAuth client
- Allowed Workspace domains: a Workspace domain such as example.com, without @
- Service account JSON key: the complete contents of the JSON key file
- Delegated administrator email: a Workspace administrator that can read users and groups

###### 7. Verify sign-in and the first sync

After sign-in, this user becomes the first root administrator. Open Console → Identity Providers, select google-workspace-main, and select Run full sync.

When the sync finishes, check Principals and Principal groups for the Workspace users, groups, and memberships.

##### Advanced settings · Domain boundary, sync timing, and service accounts

Google Workspace supports full sync only. It does not send realtime directory events to this adapter.

###### 1. Keep Allowed Workspace domains narrow

Enter only the Workspace domains that the enterprise uses. Ankole also requires a Google-verified email and a Workspace hosted-domain claim. A consumer gmail.com account has no such claim, so Ankole rejects it even after Google authenticates it.

###### 2. Run another full sync after a directory change

The Google Workspace adapter does not support realtime sync. After you add an employee, change a group, or suspend an account, run full sync again in Console → Identity Providers.

###### 3. Reduce and rotate service-account access

After the first verification, you can use a dedicated administrator with user and group read access as Delegated administrator email. When you rotate the JSON key, update Service account JSON key in Ankole.

#### Lark / Feishu

Create a custom enterprise app. Its App ID and App Secret provide sign-in, employee and department sync, and realtime directory events over a long connection.

**Before you start**

- An administrator who can create and publish a custom enterprise app
- An enterprise administrator who can approve directory permissions
- A Lark or Feishu employee account for the first sign-in

##### Basic setup · Set up the Lark or Feishu IdP

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select Lark Adapter on the plugin page, save the selection, and then select Lark or Feishu.

Keep Configuration ID (Provider ID) as lark-main. Use Copy to copy the login callback URL.

###### 2. Create the custom enterprise app

Use Feishu Open Platform for a Feishu tenant or Lark Developer for an international Lark tenant. Create a custom enterprise app.

Open Credentials & Basic Info and copy the App ID and App Secret.

- [Open Feishu Open Platform](https://open.feishu.cn/app)
- [Open Lark Developer](https://open.larksuite.com/app)

###### 3. Register the login callback

Open Development Configuration → Security Settings → Redirect URLs and add the complete URL from Ankole.

The callback must match exactly. A different scheme, host, port, or provider ID causes sign-in to fail.

- [Feishu web sign-in guide](https://open.feishu.cn/document/sso/web-application-end-user-consent/guide)

###### 4. Import sign-in and directory permissions

Open Permissions and select the bulk import or export action. Paste the JSON below and confirm the import. If a permission needs enterprise approval, wait until its status is active.

**Bulk import JSON**

```text
{
  "scopes": {
    "tenant": [
      "contact:contact.base:readonly",
      "contact:department.base:readonly",
      "contact:department.organize:readonly",
      "contact:user.base:readonly",
      "contact:user.department:readonly",
      "contact:user.email:readonly",
      "contact:user.employee_id:readonly"
    ],
    "user": [
      "contact:user.employee_id:readonly"
    ]
  }
}
```

###### 5. Configure long-connection directory events

Open Events & Callbacks → Event Configuration and select the long-connection or WebSocket delivery option. Add the seven events below.

- `contact.user.created_v3`
- `contact.user.updated_v3`
- `contact.user.deleted_v3`
- `contact.department.created_v3`
- `contact.department.updated_v3`
- `contact.department.deleted_v3`
- `contact.scope.updated_v3`

###### 6. Set the availability range and publish

Add the first administrator and every department that you need to sync to the app availability range. Create and publish an app version. Unpublished callback, permission, and event changes do not apply to employees.

###### 7. Enter the Lark or Feishu values in Ankole

The default directory sync settings usually do not need changes. Select Validate configuration and sign in. Then open Console → Identity Providers and run a full sync for lark-main.

Check Principals and Principal groups for the employees, departments, and memberships.

- App ID and App Secret: values from Credentials & Basic Info
- Service region: select Feishu (Mainland China) for Feishu or Lark (Global) for Lark

##### Advanced settings · Long connections, app availability, and chat apps

The control plane opens the long connection. You do not need a public Feishu webhook.

###### 1. Use full sync without realtime events

Under Advanced settings (usually no changes needed), turn off Sync directory changes if you do not want directory events. Employee and department changes then appear after the next full sync.

###### 2. Use the app availability range as the sign-in boundary

The app availability range controls who can sign in. After you add a department or employee, update the range and publish a new version.

###### 3. Use separate apps for identity and chat

You can use one custom app for both roles, but separate apps are better for normal use. Keep sign-in and directory permissions on the IdP app. Keep bot permissions on the chat app. Both apps can use one platformSubjectNamespace for the same organization.

#### DingTalk

Create an internal enterprise app. The same Client ID and Client Secret provide sign-in, organization directory access, and realtime changes over Stream.

**Before you start**

- A DingTalk administrator who can create and publish an internal app
- An administrator who can approve directory API access
- An employee account in the organization for the first sign-in

##### Basic setup · Set up the DingTalk IdP

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select DingTalk Adapter on the plugin page, save the selection, and then select DingTalk.

Keep Configuration ID (Provider ID) as dingtalk-main. Use Copy to copy the login callback URL.

###### 2. Create the internal enterprise app

Open the DingTalk developer console. Go to App Development → Internal Enterprise Apps and create an app.

Open Basic Information → Credentials. Copy the Client ID, formerly AppKey, and the Client Secret, formerly AppSecret.

- [Open the DingTalk developer console](https://open-dev.dingtalk.com/)

###### 3. Register the login callback

Open Development Configuration → Security Settings → Redirect URL. Paste the complete URL from Ankole and save it.

Keep the sign-in scope as openid corpid. The corpid claim limits sign-in to the selected DingTalk organization.

###### 4. Request sign-in and directory API permissions

Open Permission Management → Contacts Management. Request read access for the operations below. Set the directory range to all employees, or include every department that must sign in and sync.

- `Contact.User.Read` — Read personal contact information
- `Get a user ID by unionId`
- `Get user details`
- `Get child department IDs`
- `Get department details`
- `Get basic department user information`

###### 5. Subscribe to directory changes through Stream

Open Events & Callbacks and select Stream mode. Add the ten events below. They cover employee, department, administrator, and organization changes.

- `user_add_org`
- `user_modify_org`
- `user_leave_org`
- `user_active_org`
- `org_dept_create`
- `org_dept_modify`
- `org_dept_remove`
- `org_admin_add`
- `org_admin_remove`
- `org_remove`

- [DingTalk employee change Stream guide](https://open.dingtalk.com/document/orgapp/personnel-platform-employee-change-event-stream)

###### 6. Set the availability range and publish

Add the first administrator and the departments that you need to sync to the app availability range. Create and publish a version that contains the callback, permissions, and event settings.

###### 7. Enter the DingTalk values in Ankole

Keep Sync directory and Sync directory changes on. Select Validate configuration and sign in. Ankole checks the credentials before it opens DingTalk sign-in.

After sign-in, open Console → Identity Providers and run a full sync for dingtalk-main. Check Principals and Principal groups for the employees and departments.

- Client ID (AppKey): Client ID from the credentials page
- Client Secret (AppSecret): Client Secret from the same page

##### Advanced settings · Stream, directory range, and page size

The control plane opens the Stream connection. You do not need a public webhook.

###### 1. Use full sync without Stream events

Turn off Sync directory changes if you do not want directory events. Employee and department changes then appear after the next full sync.

###### 2. Check the directory permission range

If the sync misses people or departments, check the directory range in Permission Management. An approved API permission can still return incomplete data when the range excludes it.

###### 3. Keep the default page size first

The default page size is 50, and the allowed range is 1–100. Complete one full sync with the default. Change it only for a measured rate-limit or response-size problem.

#### WeCom

A self-built app carries the QR sign-in, and the dedicated Contacts-sync secret imports members and departments through periodic full sync. Sign-in and directory API calls both require a fixed egress IP.

**Before you start**

- A WeCom corp super administrator account
- An Ankole deployment with a fixed egress IP
- A member account in the corp for the first sign-in

##### Basic setup · Set up the WeCom IdP

###### 1. Copy the callback URL from Ankole

Open https://<ANKOLE_HOST>/setup and enter the activation code. Select WeCom Adapter on the plugin page, save the selection, and then select WeCom.

Keep Configuration ID (Provider ID) as wecom-main. Use Copy to copy the login callback URL.

###### 2. Record the Corp ID

Open the WeCom admin console. Go to My Company → Company information and copy the Corp ID.

- [Open the WeCom admin console](https://work.weixin.qq.com/wework_admin/)

###### 3. Create a self-built app with its trusted domain and trusted IP

Go to App Management → Self-built → Create app. Open the app details and copy the AgentId and Secret.

On the same page, set two values: add the Ankole domain as the trusted domain for web authorization, and add the deployment egress IP to the trusted-IP list.

> A missing trusted-IP entry fails sign-in and API calls with error 60020. A missing trusted domain blocks the redirect after the QR scan.

###### 4. Enable Contacts sync and record its dedicated secret

Go to Security & Administration → Management tools → Contacts sync. Enable API sync, copy the dedicated secret, and register its own trusted IP.

Since June 2022 the ordinary app secret no longer returns member names and other profile fields. Without this dedicated secret, directory sync is unavailable and signed-in users keep bare user ids.

###### 5. Enter the WeCom values in Ankole

Keep Enable login and Sync directory on. Select Validate configuration and sign in, and complete the WeCom QR sign-in.

- Corp ID: the value from Company information
- Self-built app AgentId / Secret: the two values from the app details
- Contacts-sync Secret: the dedicated secret from the Contacts sync page

###### 6. Confirm sign-in and the first sync

The first signed-in member becomes the first root administrator. Open Console → Identity Providers, select wecom-main, and run a full sync.

Check Principals and Principal groups for the members and departments.

##### Advanced settings · Sync cadence, trusted IPs, and sign-in only

WeCom sends no realtime directory events. Changes converge through periodic full sync.

###### 1. Run a full sync after directory changes

WeCom does not push directory changes to Ankole. After you add members or change departments, run a full sync in Console → Identity Providers, or wait for the next periodic sync.

###### 2. Update both trusted-IP lists when the egress IP changes

The self-built app and Contacts sync each keep their own trusted-IP list. After a migration or egress-IP change, update both lists, or sign-in and sync both fail with error 60020.

###### 3. Use sign-in without directory sync

Turn off Sync directory and the Contacts-sync secret becomes optional. Signed-in Principals then keep bare WeCom account ids without names or department groups.

<a id="llm-providers"></a>

## 3. Add an LLM Provider and create an Agent

An Agent model profile stores a model reference, so add the LLM Provider first. Sign in to the Console. Open **Providers → New provider**, choose the provider kind, give it a stable Provider ID, enter the endpoint and credential fields, and save.

Provider credentials stay encrypted in the control plane. Do not put them in deployment environment files or Agent files.

Open **Agents → New Agent**. Give the Agent a stable UID, a clear display name, and a mission that says what it owns and what counts as an acceptable result.

Then configure its model profiles:

| Profile | First-run use |
|---|---|
| `primary` | Main reasoning model |
| `light` | Short and frequent work |
| `heavy` | Hard synthesis |

All three are required before the Agent can run. For the first conversation, you can bind the same known-good provider and model to all three. Split them only after the end-to-end path works.

### Advanced settings · Optional Agent profiles and Brain maintenance

Configure Agent capability profiles only where needed. Brain keeps retrieval models instance-wide.

#### 1. Add optional Agent capability profiles

- `Background Agent Jobs` — Uses the coding profile for durable Background Agent Jobs. Normal conversations do not select it because a message contains a lot of code.
- `vision_fallback` — Fallback when the primary model cannot inspect an image.
- `web_search / web_fetch` — Providers for web discovery and page retrieval.
- `image_generate` — Model for image generation.

#### 2. Select the Brain maintainer and retrieval models

Open Console → AppConfigure → Brain. Select an active Agent responsible for Brain maintenance. All Brain model calls run as this Agent and attribute usage to it. Disabling it stops model calls and local URL fetching until it is enabled or replaced. Edit its model profiles from the linked Agent page.

- `brain.maintainer_agent_uid` — Uses the selected Agent’s light profile for extraction, heavy for Dreaming, and web_fetch for URL Sources. Missing web_fetch falls back to local ankole-browser.
- `brain.embedding_model` — Enables vector retrieval. Select a Provider and model, and enter its embedding dimensions.
- `brain.rerank_model` — Reranks fused search results. Leave it empty to keep the fusion order.

- [Brain guide](https://ankole.agentbull.com/en-US/docs/brain/index.md)
- [AppConfigure reference](https://ankole.agentbull.com/en-US/docs/app-configuration/index.md)

<a id="chat-channels"></a>

## 4. Connect a chat channel and create its signal routing rule

Enable the chat platform's Control Plane Plugin in the Console, and then create the bot or application on that platform. Use separate apps for the IdP and chat roles, even when both use the same platform. This separation keeps sign-in and directory permissions away from bot permissions. It also separates credential rotation and app releases.

One chat app usually represents one bot identity. If several Agents need different bot names, avatars, or permissions, create several apps on that platform. After you prepare an app, create its routing rule in the Console and connect it to the intended Agent.

Slack, Teams, Lark/Feishu, DingTalk, and WeCom are enterprise platforms: their users come from the directory that the IdP synchronized. Telegram, Discord, LINE, and WhatsApp are consumer IMs: their users have no employee record, so an administrator maps each new sender to an account under **Identity → Pending mappings** before the Agent serves them; WhatsApp maps a sender by itself when a known account already owns the phone number. Email connects a dedicated mailbox, and a sender is known only through an explicit email identity binding.

**Choose a channel provider**

- Slack · Default
- Microsoft Teams
- Lark / Feishu
- DingTalk
- WeCom
- Telegram
- Discord
- LINE
- WhatsApp
- Email

### Slack · Default

Slack uses Socket Mode. Ankole opens an outbound WebSocket, so the chat path does not need a public Slack webhook.

**Before you start**

- Permission to create and install a Slack app
- A test channel or direct message

#### Basic setup · Prepare a Slack app

##### 1. Create the app and enable Socket Mode

Create a Slack app from scratch. Under Basic Information, create an App-Level Token, add the scope below, and then enable Socket Mode.

- `connections:write` — Let the App Token open a Socket Mode connection

##### 2. Subscribe to chat and channel-state events

Open Event Subscriptions → Subscribe to bot events. Add each event below. This set matches the message, reaction, and channel-state capabilities implemented by the Slack adapter.

- `app_mention` — Receive channel @-mentions
- `message.channels` — Receive public-channel messages
- `message.groups` — Receive private-channel messages
- `message.im` — Receive direct messages
- `message.mpim` — Receive multi-person direct messages
- `reaction_added` — Receive added reactions
- `reaction_removed` — Receive removed reactions
- `member_joined_channel` — Sync members who join a channel
- `member_left_channel` — Sync members who leave a channel
- `channel_rename` — Sync channel renames
- `group_rename` — Sync private-channel renames
- `channel_deleted` — Sync channel deletion
- `channel_archive` — Sync channel archival

##### 3. Enable direct messages

Open App Home → Show Tabs. Turn on Display Messages tab and select Allow users to send Slash commands and messages from the messages tab. The Messages tab alone shows the surface but does not let users send direct messages to the Agent.

##### 4. Enable Slack-native interactivity

Open Interactivity & Shortcuts and enable Interactivity. Socket Mode delivers Block Kit button actions over the existing WebSocket, so a public Request URL is not required.

##### 5. Grant the complete chat scopes and install the app

Under OAuth & Permissions → Bot Token Scopes, add each scope below. This set covers only Slack APIs that Ankole calls. Do not grant unused assistant, workflow, document, or meeting scopes.

- `app_mentions:read` — Read messages that mention the bot
- `channels:read` — Sync public channels and their members
- `channels:history` — Read public-channel messages and reconcile replies
- `groups:read` — Sync private channels and their members
- `groups:history` — Read private-channel messages and reconcile replies
- `im:read` — Sync direct-message conversations
- `im:history` — Read direct messages and reconcile replies
- `mpim:read` — Sync multi-person direct messages and their members
- `mpim:history` — Read multi-person direct messages and reconcile replies
- `chat:write` — Send, update, and delete bot messages
- `reactions:read` — Receive reaction changes
- `reactions:write` — Add and remove reactions
- `files:read` — Read files attached to messages
- `files:write` — Upload files sent by the Agent
- `users:read` — Identify channel members and exclude bot accounts

> After a scope change, reinstall the app to the workspace and write the new Bot Token to Ankole.

##### 6. Collect the Console fields

- `botToken`: `xoxb-…` — Bot User OAuth Token. The xoxb- prefix is required.
- `appToken`: `xapp-…` — App-Level Token for Socket Mode. The xapp- prefix is required.

#### Advanced settings · Message policy, subject mapping, and Slack as IdP

Slack delivers events, but the signal routing rule still decides how Ankole handles unaddressed messages.

##### 1. Choose the policy for messages that do not mention the Agent

The events and scopes above let Slack deliver complete conversations, but they do not replace the Ankole signal routing policy. Start with addressed_only. Select observe_all or may_intervene only when the Agent must observe or intervene without a direct mention.

##### 2. Add identity permissions when one app also serves as the Slack IdP

Use a separate IdP app in production. If one app must serve both roles, add the Bot scopes and directory events below.

- `users:read.email` — Sync member email addresses
- `usergroups:read` — Sync user groups and their members
- `team_join` — Receive member-join events
- `user_change` — Receive member-profile changes
- `subteam_created` — Receive user-group creation
- `subteam_updated` — Receive user-group updates
- `subteam_members_changed` — Receive user-group membership changes

##### 3. Set the subject namespace

- `platformSubjectNamespace`: `slack-main` — Use one namespace per Slack workspace. Share it with the Slack IdP config when Slack fills both roles.
- `userName`: `Slack` — Display name used for outbound messages.

##### 4. Create a separate Slack IdP app

For normal use, create another Slack app for SSO and directory sync. Keep identity permissions on the IdP app and bot permissions on the chat app. Both records can use one platformSubjectNamespace for the same workspace.

### Microsoft Teams

Teams sends Bot Framework webhooks. A public HTTPS endpoint with a trusted certificate is mandatory.

**Before you start**

- An Entra ID tenant
- A public HTTPS Ankole host
- Permission to register and install a Teams bot

#### Basic setup · Prepare a Teams bot

##### 1. Register the app and bot

Create an Entra app registration and an Azure Bot resource. The application client ID becomes appID; create a client secret for appPassword.

##### 2. Set the Bot Framework messaging endpoint

The appID path segment must match the App ID in the signal routing rule.

```text
https://<ANKOLE_HOST>/webhooks/v1/teams/<appID>/messages
```

##### 3. Choose the tenant mode

- `botTenancy`: `single_tenant` — Recommended starting mode for one enterprise tenant.
- `tenantID` — Entra tenant GUID. Required for a single-tenant bot.

##### 4. Collect the Console fields

- `appID` — Azure Bot registration Microsoft App ID. It must be a GUID.
- `appPassword` — Microsoft App client secret.

#### Advanced settings · Cross-tenant bots, Entra identity, and directory webhooks

Expand when the bot serves more than one Entra tenant or Entra ID also supplies SSO.

##### 1. Serve several Entra tenants with one Bot Framework app

Set botTenancy to multi_tenant only when the Azure Bot registration allows several Entra tenants. The adapter uses botframework.com for the bot token tenant. This Microsoft app-registration mode does not change the boundary of the Ankole deployment instance.

##### 2. Share the subject namespace when Entra also supplies identity

- `platformSubjectNamespace`: `entra-id-main` — Use the same namespace in the Teams channel and Entra ID provider when they represent the same organization.
- `userName`: `Teams` — Display name used for outbound messages.

##### 3. Keep Graph directory sync in the IdP

Entra full and realtime directory sync belong to the IdP record. Graph notifications use a separate directory webhook and still require the public HTTPS host.

### Lark / Feishu

Lark and Feishu use an outbound long connection. The chat path needs Internet access but does not need a public inbound webhook.

**Before you start**

- Permission to create an enterprise custom app
- A test user in the app availability scope

#### Basic setup · Prepare a Lark or Feishu app

##### 1. Create the app and enable bot capability

Create an enterprise custom app, enable its bot capability, and add the test user to the availability scope.

##### 2. Import the complete chat scopes

Open Permissions, select the bulk import or export action, paste the JSON below, and confirm the import. This set matches the bot, message, reaction, file, card, room, and membership APIs that the adapter calls. Do not add directory write, document, meeting, or urgent-message permissions to the chat app.

**Bulk import JSON**

```text
{
  "scopes": {
    "tenant": [
      "application:bot.basic_info:read",
      "cardkit:card:write",
      "im:chat:read",
      "im:chat.members:bot_access",
      "im:chat.members:read",
      "im:message:send_as_bot",
      "im:message:readonly",
      "im:message:update",
      "im:message:recall",
      "im:message.group_at_msg:readonly",
      "im:message.p2p_msg:readonly",
      "im:message.reactions:read",
      "im:message.reactions:write_only",
      "im:resource"
    ],
    "user": []
  }
}
```

##### 3. Choose long connection and add the implemented events

In Events and Callbacks, choose long connection and add each event below. Keep the Ankole control plane running while the platform detects the client.

- `im.message.receive_v1` — Receive messages sent to the bot
- `im.message.recalled_v1` — Receive message recall events
- `im.message.reaction.created_v1` — Receive added reactions
- `im.message.reaction.deleted_v1` — Receive removed reactions
- `im.chat.member.bot.added_v1` — Sync rooms that add the bot
- `im.chat.member.bot.deleted_v1` — Sync rooms that remove the bot
- `im.chat.member.user.added_v1` — Sync members who join a room
- `im.chat.member.user.deleted_v1` — Sync members who leave a room
- `im.chat.updated_v1` — Sync room changes
- `im.chat.disbanded_v1` — Sync room deletion
- `card.action.trigger` — Receive interactive-card actions

##### 4. Publish a version and collect the Console fields

- `appID` — Enterprise custom app ID.
- `appSecret` — Enterprise custom app secret.
- `domain`: `feishu or lark` — Use feishu for Feishu.cn and lark for Larksuite.com.

#### Advanced settings · Full-room context, separate identity, and subject mapping

Expand when the Agent must observe the room or Lark also supplies SSO.

##### 1. Let the Agent observe unaddressed group messages

Add the permission below before you use observe_all or may_intervene. Start with addressed_only while you verify the first conversation.

- `im:message.group_msg` — Read group messages that do not mention the bot

##### 2. Create a separate Lark IdP app

For normal use, create another custom app for SSO and directory sync. Keep sign-in and directory permissions on the IdP app and bot permissions on the chat app. Both records can use one platformSubjectNamespace for the same organization.

##### 3. Set the shared adapter fields

- `platformSubjectNamespace`: `lark-main` — Share this value with the Lark IdP config when both records represent the same organization.
- `userName`: `Lark / Feishu` — Display name used for outbound messages.

##### 4. Use one Lark app for each enabled binding

Create a separate Lark or Feishu app for each binding. A disabled binding releases its app.

- One Agent can enable several Lark bindings.
- Each enabled binding must use a different domain and appID pair.

### DingTalk

DingTalk uses Stream mode. One AppKey and AppSecret pair authenticates the robot and opens the event connection.

> **DingTalk limits some Ankole features**
>
> In a group chat, an Agent cannot read the full conversation history. It receives only messages that explicitly @-mention it. DingTalk cards are template-hosted, so streaming card replies need one AI card template built on the card platform; without it, replies stay plain Markdown.
>
> These limits reduce Ankole’s functionality and user experience. They also leave the long-term memory system with incomplete context. Prefer another chat channel when possible.

**Before you start**

- Permission to create an enterprise-internal DingTalk app and robot
- A test conversation

#### Basic setup · Prepare a DingTalk robot

##### 1. Create an enterprise-internal app and robot

Enable the robot capability and make the app available to the test user. DingTalk delivers direct messages and group messages that @ the robot.

##### 2. Enable Stream mode and publish the app

The same application credentials open the Stream connection, so you do not need a public message webhook.

##### 3. Collect the Console fields

- `clientId` — Enterprise app Client ID, also called AppKey and used as the Stream clientId.
- `clientSecret` — Enterprise app Client Secret, also called AppSecret and used as the Stream clientSecret.
- `group_message_mode`: `addressed_only` — The only mode DingTalk can deliver for group messages.
- `cardTemplateId` — AI card template id for streaming card replies. Leave it empty for the first test; the advanced section below shows how to build the template.

#### Advanced settings · AI card template, multiple Agents, and identity settings

Expand when you want streaming AI card replies, plan several DingTalk Agents, or also use DingTalk as an identity source.

##### 1. Create the AI card template and add its variables

DingTalk cards are template-hosted: the layout lives on the DingTalk card platform, and Ankole only writes values into a fixed set of variables. Build one template per DingTalk organization; budget about twenty minutes for the first one. Before you start, the Agent must already reply with plain text, and the app needs the interactive card instance write and AI card streaming update permissions.

Open the DingTalk developer console → Card Platform → New template and choose the AI card category. Only this category has the AI card container, which draws the writing indicator and the finished and failed states. Add each variable below with exactly this name; a name that does not match leaves its area empty on every reply.

- `state` — Text. One status line, such as the running tool label.
- `answer` — Markdown (streaming). The reply body, rewritten in full on every frame.
- `thought` — Markdown. The transient thinking draft, blanked when the reply ends.
- `plan` — Text. The plan and its completed-of-total count.
- `activity` — Text. Running tool calls, blanked when the reply ends.
- `results` — Text. One line per structured result.
- `receipts` — Text. One line per recorded side effect.
- `actions` — Text. A JSON list of buttons, empty when the Agent asks nothing.
- `meta` — Text. Trigger, card number, counts, elapsed time.

##### 2. Lay out the components for each card state

Configure the input, completed, and failed layouts in the AI card component. Put a Markdown component bound to answer in each layout that must show the reply, and enable streaming in the input layout. Put meta and state at the top, plan in a text component, thought and activity in folded areas, and results and receipts in text components. Put an action area bound to actions in both the input and completed layouts when decision buttons must remain visible, and pass each button value through unchanged.

DingTalk selects the layout from its native AI card lifecycle. Ankole sends isFinalize to enter the completed state and isError to enter the failed state. Do not create or bind flowStatus or flowStatusVar.

> A card state is blank when its layout has no Markdown component bound to answer. Publish the template again after each layout change.

##### 3. Publish the template, enter the id, and verify

Associate the template with the enterprise-internal app that owns the robot, publish it, copy the template id, and paste it into cardTemplateId on the routing rule. The change applies to the next reply; there is nothing to restart.

To verify, send a message that produces several sentences: a card must appear and grow while the Agent writes, and when the reply ends the indicator stops and the thinking and activity areas are blanked. Then ask something that needs a decision — a button must appear, and pressing it continues the turn; a stale button that no longer answers a pending question is ignored. A long reply seals a card at about 2.5 KB and continues on a new one, each card is a new message in the conversation, and rich structures such as tables render as text. All of this is expected under the platform limits.

- The card is blank: the current input, completed, or failed layout has no component bound to answer, or the changed template was not published.
- One area is always empty: that variable name does not match the table, or the component is not bound to it.
- The answer appears only at the end or not at all: the answer block is not a Markdown streaming block.
- The writing indicator remains after the reply ends: check the control-plane logs and confirm that DingTalk accepted the streaming update with isFinalize or isError.
- Replies are plain Markdown messages: the template id is empty, the template is not published to this app, or DingTalk rejected the card content — check the control-plane logs for param.contentUnsafe or param.cardNotExist. When the card path fails permanently, the reply degrades to plain Markdown once and is still delivered.
- Buttons appear but pressing one does nothing: the action area does not pass each button value through unchanged.

##### 4. Respect the connection ownership rules

Create a separate robot and credential pair for each additional Agent.

- One Agent can have at most one enabled DingTalk binding.
- One clientId can be bound to only one Agent.

##### 5. Keep DingTalk identity separate

DingTalk can also supply OIDC and directory sync, but that is an IdP record. A chat binding does not make DingTalk the Console login provider.

- `platformSubjectNamespace`: `dingtalk-main` — Share it with the DingTalk IdP only when both records represent the same organization.

### WeCom

A WeCom AI bot sends and receives messages over one outbound long connection, so you do not need a public message webhook. The platform allows exactly one long connection per bot.

> **WeCom is the most limited chat channel**
>
> In a group, the Agent receives only messages that explicitly @-mention the bot. Images, voice, files, and video arrive in direct messages only, and a voice message arrives as the platform transcript only. Sent messages cannot be recalled, edited, or given emoji reactions. The Agent cannot start a fresh conversation: the user must message the bot first, and the reply window after an inbound message is 24 hours. Streaming replies work only when replying to a user, one streaming message must finish within 10 minutes so long answers split, and sends are limited to about 30 messages per minute per conversation. A card button can change only within 5 seconds after a click. There is no realtime directory sync.
>
> Every limit comes from the platform itself and Ankole cannot work around it. The long-term memory system also builds context only from the message fragments the Agent receives. Prefer Lark/Feishu, Slack, Teams, or DingTalk when possible.

**Before you start**

- A WeCom corp super administrator who can create the AI bot
- A test conversation

#### Basic setup · Prepare a WeCom bot

##### 1. Create the AI bot as a corp super administrator

Open the WeCom admin console, create the AI bot in API mode, and record the Bot ID and the Secret shown next to it.

> The bot must be created by a corp super administrator. Otherwise the user ids in messages are encrypted and can never join the directory or sign-in identities.

##### 2. Do not share the bot with another program

The platform allows exactly one long connection per bot. If another program connects with the same Bot ID, each connection kicks the other. When Ankole gets kicked, it parks and waits instead of fighting for the line.

##### 3. Collect the Console fields

- `botId` — The AI bot Bot ID.
- `secret` — The long-connection secret shown next to the Bot ID.
- `group_message_mode`: `addressed_only` — The only mode WeCom can deliver for group messages.

#### Advanced settings · Proactive delivery, identity mapping, and multiple Agents

Before the Agent can speak first, the user must message the bot in that conversation once.

##### 1. Unlock proactive delivery

Proactive messages, such as scheduled-job results, reach only conversations the user has activated. Have each user send the bot one message first; the Agent cannot start a fresh conversation. Proactive sends deliver one complete Markdown message without streaming.

##### 2. Set the identity mapping fields

- `platformSubjectNamespace`: `wecom-main` — Share it with the WeCom IdP only when both records represent the same corp.
- `userName`: `企业微信 / WeCom` — The name shown on outbound bot messages.

##### 3. Use a separate bot for each Agent

Create a separate AI bot for each additional Agent.

- One Agent can have at most one enabled WeCom binding.
- One Bot ID can be bound to only one Agent.

### Telegram

Telegram uses Bot API long polling. Ankole calls getUpdates over an outbound connection, so the chat path does not need a public webhook. Telegram is a consumer IM: its users are not directory employees, so plan the identity mapping before the first message.

**Before you start**

- A Telegram account that can talk to @BotFather
- A test private chat or group

#### Basic setup · Prepare a Telegram bot

##### 1. Create the bot with @BotFather

Send /newbot to @BotFather, choose a display name and a username that ends in bot, and copy the token that BotFather returns. The token is the only credential the routing rule needs.

##### 2. Let the bot read group messages

In @BotFather, open Bot Settings → Group Privacy and turn privacy mode off. With privacy mode on, a group delivers only /command@bot commands and replies to the bot, so an @-mention does not reach the Agent, and observe_all or may_intervene never sees other messages. A change of this setting takes effect after the bot is removed from and added to the group again.

##### 3. Remove an old webhook

Ankole polls the Bot API, and Telegram refuses to poll while a webhook is set for the token. If another program used this token with a webhook, delete it before you enable the rule. Ankole reports webhook_configured and does not delete a webhook that another system owns.

```bash
curl -s "https://api.telegram.org/bot<botToken>/deleteWebhook"
```

##### 4. Collect the Console fields

- `botToken`: `123456789:AA…` — The bot token from @BotFather. One token can belong to only one enabled routing rule.
- `group_message_mode`: `addressed_only` — Start here. A direct message, an @-mention, a /command@bot, or a reply to the bot addresses the Agent.

#### Advanced settings · Identity mapping, forum topics, and platform limits

Expand to map Telegram users to accounts and to learn which Telegram features the Agent cannot use.

##### 1. Map Telegram users to accounts

A Telegram user has no employee record, so account auto-mapping fails for every new sender. Keep When account auto-mapping fails on Manual review: the sender receives one fixed notice, appears under Identity → Pending mappings, and an administrator binds the Telegram identity to an existing account, for example a local-password account. The user then sends the message again. Select Create a standalone account only for an open bot where anyone may talk to the Agent.

##### 2. Know how conversations map to sessions

- A private chat, a group, and a supergroup each form one Agent session.
- Each forum topic in a supergroup is a separate session.
- Channel posts, messages from other bots, and anonymous or guest senders are ignored.

##### 3. Respect the platform limits

- The Bot API cannot download a file larger than 20 MB. The Agent sees the file name and size but cannot read the content.
- Telegram sends no event when a user deletes a message, so the deleted text stays in the session context.
- When Telegram may have accepted a send that the connection lost, Ankole does not repeat the send automatically. It applies the possible-duplicate flow instead of posting a second reply.

##### 4. Use a separate bot for each Agent

One bot token can be bound to only one enabled routing rule. Create another bot in @BotFather for each additional Agent.

### Discord

Discord uses the bot Gateway over an outbound WebSocket, so the chat path does not need a public webhook. Discord is a consumer IM: its users are not directory employees, so plan the identity mapping before the first message.

**Before you start**

- A Discord account that can create an application in the Developer Portal
- A server where you can invite the bot, or a direct message for the test

#### Basic setup · Prepare a Discord bot

##### 1. Create the application and its bot

Open the Discord Developer Portal, create a New Application, open the Bot page, and select Reset Token. Copy the token at once; Discord shows it only one time.

##### 2. Enable the message content intent

On the Bot page, under Privileged Gateway Intents, turn on Message Content Intent. Without it, Discord delivers a server message that does not mention the bot with empty content, so the Agent reads only direct messages and messages that mention it, and observe_all or may_intervene cannot see the conversation. Ankole reads the application flag before it connects and asks for the intent only when the application has it.

> Server Members Intent and Presence Intent are not required. An application in more than 100 servers needs Discord verification before it can keep the message content intent.

##### 3. Invite the bot to the server

Open OAuth2 → URL Generator, select the bot scope, and add the permissions below. Open the generated URL and select the server. This set covers only the Discord APIs that Ankole calls.

- `View Channels` — See the channels the bot is allowed into
- `Send Messages` — Post Agent replies
- `Send Messages in Threads` — Reply inside a thread
- `Read Message History` — Reply to and edit earlier messages
- `Attach Files` — Upload files sent by the Agent
- `Add Reactions` — Add and remove reactions

##### 4. Leave the Interactions Endpoint URL empty

On the General Information page, keep Interactions Endpoint URL empty. When it is set, Discord posts button clicks to that URL instead of the Gateway, so the buttons in an Agent reply never reach Ankole.

##### 5. Collect the Console fields

- `botToken` — The bot token from the Bot page. One token can belong to only one enabled routing rule.
- `group_message_mode`: `addressed_only` — Start here. A direct message, an @-mention, or a reply to the bot addresses the Agent.

#### Advanced settings · Identity mapping, threads, and platform limits

Expand to map Discord users to accounts and to learn which Discord features the Agent cannot use.

##### 1. Map Discord users to accounts

A Discord user has no employee record, so account auto-mapping fails for every new sender. Keep When account auto-mapping fails on Manual review: the sender receives one fixed notice, appears under Identity → Pending mappings, and an administrator binds the Discord identity to an existing account, for example a local-password account. The user then sends the message again. Select Create a standalone account only for an open server where anyone may talk to the Agent.

##### 2. Know how conversations map to sessions

- A direct message and each server channel form one Agent session.
- Each thread is a separate session.
- Messages from other bots, webhook posts, system notices, and messages with no text or attachment are ignored.
- Agent text never notifies a user, a role, or everyone. Ankole disables mention parsing on every message it sends.

##### 3. Respect the platform limits

- Ankole downloads an attachment of at most 25 MB. The Agent sees the name and size of a larger file but cannot read the content.
- Discord sends no event that Ankole uses when a user deletes a message, so the deleted text stays in the session context.
- A reply is split at 2,000 characters. A card shows at most 25 buttons.
- When Discord may have accepted a send that the connection lost, Ankole does not repeat the send automatically. It applies the possible-duplicate flow instead of posting a second reply.

##### 4. Use a separate bot for each Agent

One bot token can be bound to only one enabled routing rule. Create another application for each additional Agent.

### LINE

LINE posts Messaging API webhooks to Ankole. A public HTTPS endpoint with a trusted certificate is mandatory. LINE is a consumer IM: its users are not directory employees, so plan the identity mapping before the first message.

> **LINE limits some Ankole features**
>
> Every Agent reply is a push message, because a LINE reply token expires one minute after the webhook and an Agent turn is usually longer. Push messages count against the monthly message plan of the Official Account, so the plan must cover the expected traffic. A LINE bot cannot edit, unsend, or react to a message, cannot send files, and shows no live progress before the final answer.
>
> Every limit comes from the platform itself and Ankole cannot work around it.

**Before you start**

- A LINE Developers provider and permission to create a Messaging API channel
- A public HTTPS Ankole host
- A LINE account for the test

#### Basic setup · Prepare a LINE Official Account

##### 1. Create the Messaging API channel and collect its credentials

In the LINE Developers Console, create a Messaging API channel under your provider. Copy the Channel ID and the Channel secret from Basic settings, and issue a long-lived channel access token on the Messaging API tab.

##### 2. Create the routing rule before you verify the webhook

Save and enable the routing rule in the Console first. Ankole answers a webhook for an unknown Channel ID with status 404, so the Verify button in the LINE Developers Console fails until the rule exists.

##### 3. Set the webhook URL and verify it

On the Messaging API tab, enter this URL, turn on Use webhook, and select Verify. The channelId path segment must match the Channel ID in the routing rule. Ankole checks the x-line-signature of every request with the channel secret and rejects a wrong signature with status 401.

```text
https://<ANKOLE_HOST>/webhooks/v1/line/<channelId>/events
```

##### 4. Let the bot answer alone

In LINE Official Account Manager, open Response settings. Set the response method to bot, and turn off auto-response and greeting messages, so the account does not answer next to the Agent. For group chats, enable Allow bot to join group chats in the LINE Developers Console.

##### 5. Collect the Console fields

- `channelId` — The Messaging API Channel ID. One channel can belong to only one enabled routing rule.
- `channelSecret` — The Channel secret from Basic settings. Ankole uses it to verify webhook signatures.
- `channelAccessToken` — A long-lived channel access token from the Messaging API tab.
- `group_message_mode`: `addressed_only` — Start here. LINE delivers every group message, so observe_all and may_intervene also work without extra permissions.

#### Advanced settings · Identity mapping, group replies, and platform limits

Expand to map LINE users to accounts and to learn how the Agent behaves in groups.

##### 1. Map LINE users to accounts

A LINE user has no employee record, so account auto-mapping fails for every new sender. Keep When account auto-mapping fails on Manual review: the sender receives one fixed notice and appears under Identity → Pending mappings with the LINE display name, and an administrator binds the LINE identity to an existing account, for example a local-password account. The user then sends the message again. A LINE user ID belongs to the LINE Developers provider that owns the channel, so channels under different providers need separate mappings.

##### 2. Know how the Agent behaves in groups

- A one-to-one chat, a group, and a multi-person chat each form one Agent session. LINE has no threads.
- In a group, an @-mention of the bot or a quote of one of its messages addresses the Agent. A group reply quotes the asker.
- A message that a user unsends is removed from the session context.

##### 3. Respect the platform limits

- A reply is split at 5,000 characters, and one request sends at most five messages. A clarification shows at most four buttons.
- Ankole downloads a received file of at most 25 MB. The Agent sees the name and size of a larger file but cannot read the content.
- The Agent cannot send files. Its text reply is still delivered.
- LINE answers an exhausted monthly plan with status 429. Only a plan change or the next month clears it.

##### 4. Use a separate channel for each Agent

One Messaging API channel can belong to only one enabled routing rule. Create another channel for each additional Agent.

### WhatsApp

WhatsApp posts Cloud API webhooks to Ankole. A public HTTPS endpoint with a trusted certificate is mandatory. WhatsApp is a consumer IM, but a sender whose phone number already belongs to a known person is mapped without any administrator action.

> **WhatsApp limits some Ankole features**
>
> Meta closes the customer service window 24 hours after the user’s newest message or button reply. A reply after that, such as a scheduled report, stops with a clear failure and never reaches the user; an operator can retry it from the Signal Routing page after the user writes again. Group chats, template messages, message edits, message deletions, and a live progress preview are not available.
>
> Every limit comes from the platform itself and Ankole cannot work around it.

**Before you start**

- A Meta App with the WhatsApp product and a WhatsApp Business Account that owns a phone number
- A public HTTPS Ankole host
- A WhatsApp account for the test

#### Basic setup · Prepare a WhatsApp Business phone number

##### 1. Connect the WhatsApp product and collect the identifiers

In the Meta App Dashboard, add the WhatsApp product to the App and connect the WhatsApp Business Account that owns the phone number. Copy the App ID and the App secret from App settings → Basic, and the Phone number ID from WhatsApp → API Setup.

##### 2. Issue a permanent System User token

In Meta Business Suite, create a System User, give it the whatsapp_business_messaging permission on the App, and generate a token with no expiry. A user token expires and stops the Agent.

##### 3. Create the routing rule before you verify the callback

Choose a verify token, enter it and the other fields in the Console, and save and enable the routing rule. Meta verifies the callback URL against Ankole, so the rule must exist first.

##### 4. Set the callback URL and subscribe to messages

Open WhatsApp → Configuration, enter this URL and the same verify token, and select Verify and save. Then subscribe the App to the messages webhook field; no other field is used. Ankole checks the x-hub-signature-256 of every request with the App secret and rejects a wrong signature with status 401.

```text
https://<ANKOLE_HOST>/webhooks/v1/whatsapp/<appId>/events
```

##### 5. Collect the Console fields

- `appId` — The Meta App ID. Several phone numbers of one App can each serve their own Agent, and their rules must share the same App secret and verify token.
- `appSecret` — The App secret from App settings → Basic. Ankole uses it to verify webhook signatures.
- `verifyToken` — A value you choose. Enter the same value in the Meta callback configuration.
- `phoneNumberId` — The Phone number ID from WhatsApp → API Setup. One number can belong to only one enabled routing rule.
- `accessToken` — The permanent System User access token.
- `group_message_mode`: `addressed_only` — The only mode. WhatsApp delivers one-to-one chats only, and every message addresses the Agent.

#### Advanced settings · Identity mapping, replies, and platform limits

Expand to learn how WhatsApp users become accounts and which WhatsApp features the Agent cannot use.

##### 1. Know how WhatsApp users become accounts

The sender’s WhatsApp ID is a phone number that Meta verified. When an existing account owns that mobile number, for example from directory sync, the sender is mapped at once. Otherwise account auto-mapping fails: keep When account auto-mapping fails on Manual review, so that the sender receives one fixed notice and appears under Identity → Pending mappings with the WhatsApp profile name and the phone number, and an administrator binds the identity to an existing account. Select Create a standalone account only for an open number where anyone may talk to the Agent.

##### 2. Know how the Agent replies

- A reply quotes the user’s message. A long reply is split at 4,096 characters.
- A clarification with up to three choices shows reply buttons; more choices show one list. Every choice starts with its number.
- The Agent can send images, video, audio, and documents within the Meta size limits for each type. A file that Meta does not accept stops with a clear error, and the text still goes out.
- A message that the user deletes in WhatsApp never reaches Ankole, so its text stays in the session context.

##### 3. Respect the customer service window

Ankole checks the window before every send. A reply more than 24 hours after the user’s newest message or button reply stops with customer_service_window_closed and no request reaches Meta. Paid template messages, the only way to open a new window, are outside the current contract. When the user writes again, use Retry on the Signal Routing page to send the stopped reply.

##### 4. Keep a rule on its phone number

A chat is bound to the phone number that received it. If you move a rule to another number, replies to the older chats stop with binding_phone_number_mismatch instead of going out from a number the user never wrote to. Restore the original number to release them.

### Email

Ankole reads a dedicated mailbox over IMAP and sends the Agent’s replies over SMTP. Each email thread is one conversation. The mailbox belongs to the Agent: no person may read it with another mail client.

> **Email limits some Ankole features**
>
> A message that another mail client marks as read never reaches the Agent, so the mailbox must be dedicated. An Agent reply is one plain-text email with no live progress, no edits, and no buttons; a clarification arrives as numbered text and the person answers with a normal reply. Only password and app-password login are supported, so Gmail needs an app password and an Exchange Online mailbox, which requires OAuth, cannot be used.
>
> The control plane must reach the IMAP and SMTP servers directly; the HTTP egress proxy does not cover them.

**Before you start**

- A dedicated mailbox with IMAP and SMTP access and a password or app password
- A mail server that adds Authentication-Results headers with a DMARC result, or a private mail server that admits no outside mail

#### Basic setup · Prepare a dedicated mailbox

##### 1. Create the mailbox and its password

Create a mailbox that only the Agent uses, and enable IMAP and SMTP for it. Where the provider requires it, create an app password for Ankole instead of the account password.

> Do not open this mailbox in any other mail client. A message that another client marks as read is invisible to the Agent.

##### 2. Collect the server settings

Ankole connects to IMAP over implicit TLS, normally on port 993. SMTP uses STARTTLS, normally on port 587, or implicit TLS, normally on port 465. Ankole verifies the server certificates against the system CA store.

##### 3. Collect the Console fields

- `address`: `agent@example.com` — The mailbox address. Replies are sent from it.
- `displayName`: `Ankole Agent` — The sender name on outbound mail.
- `imapHost`: `imap.example.com` — IMAP server host.
- `imapPort`: `993` — IMAP port. Ankole uses implicit TLS.
- `smtpHost`: `smtp.example.com` — SMTP server host.
- `smtpPort`: `587` — SMTP port. Use 587 with STARTTLS or 465 with TLS.
- `smtpSecurity`: `starttls` — starttls or tls, matching the SMTP port.
- `username` — The login name, usually the mailbox address. One IMAP host and username pair can belong to only one enabled routing rule.
- `password` — The mailbox password or app password. Ankole stores it encrypted.
- `senderAuthentication`: `dmarc` — Keep dmarc. Set none only for a private mail server that admits no outside mail.

##### 4. Send a test email

Write to the mailbox from your own address. When your address is not yet bound to your account, you receive the mapping notice as a reply; bind the address under Identity → Pending mappings and send the email again.

#### Advanced settings · Sender identity, sender authentication, and threads

Expand to learn how an email sender becomes a known account and how Ankole treats threads and bulk mail.

##### 1. Know how email senders become accounts

Anyone on the internet can write to the mailbox, and a From address proves nothing by itself, so Ankole never matches an email sender by a profile email or a local sign-in email. A sender is known only through an explicit email identity binding: directory sync and provider sign-in bind the address the provider reports, so employees of a synced directory are admitted without a manual step; an administrator binds any other address from Identity → Pending mappings. Create a standalone account creates an account whose identifier is the address; use it only for an open mailbox.

##### 2. Understand sender authentication

With senderAuthentication set to dmarc, Ankole reads the Authentication-Results header that your receiving mail server adds and accepts the message only when it reports dmarc=pass for the domain of the From address. A message that fails is ignored without a notice. Set none only when the mail server is on a private network and admits no outside mail.

##### 3. Know how threads and bulk mail are handled

- A thread is placed by its In-Reply-To and References headers, not by subject. A reply from the Agent carries the thread headers that mail clients use.
- A thread whose only participants are the sender and the mailbox is a direct conversation. Other recipients make it a group conversation, and a message that names the mailbox only in Cc does not address the Agent.
- Quoted text below a reply separator is removed from a message in a known thread. The first message of a thread and a forwarded message keep their complete body.
- Mail from the mailbox itself, automatic and bulk mail, and mailing-list mail are ignored without a notice.
- A message larger than 25 MB arrives as headers only. Outbound attachments are limited to 20 MB per message.

### Complete the connection in the Console

The current release uses a direct connection. One routing rule connects one Channel Provider to one Agent. It records the adapter, app credentials, target Agent, and group-message behavior. Create a separate rule for each bot.

In the Console, open **Signal Routing → New routing rule** and set:

| Field | What to choose |
|---|---|
| **Target Agent** | The Agent you created in step 3 |
| **Adapter** | Slack, Teams, Lark/Feishu, DingTalk, WeCom, Telegram, Discord, LINE, WhatsApp, or Email |
| **Rule name** | A stable name such as `slack-main` or `lark-main` |
| **Group message mode** | Start with `addressed_only` |
| **Channel settings** | Paste the credentials and values from the channel tab |

Save the rule. The list must show it as enabled. If the form rejects a credential, fix that error before testing the IM; the adapter has not opened its connection yet.

> **💡 Did you know?**
>
> Today, one routing rule sends one signal source to one Agent. Chat apps are the most common source. Future rules can select an Agent by channel, conversation, or another condition. External systems such as Salesforce will also be able to send events that Agents can act on. This module is called Signal Routing because it handles every signal that can start Agent work, not only chat channels.

#### Advanced settings · Group behavior and subject namespaces

The safe first binding replies only when addressed.

##### 1. Choose how the Agent handles unaddressed group messages

- `addressed_only` — Ignore group messages that do not address the Agent. Use this for the first test.
- `observe_all` — Store unaddressed messages as context without starting an Agent turn.
- `may_intervene` — Let the Agent decide whether to join an unaddressed discussion.

> The provider must deliver those messages. DingTalk and WeCom support addressed_only only. Slack and Lark need extra events or scopes before they can observe a whole room.

##### 2. Treat the namespace as an identity mapping key

Use one platformSubjectNamespace per provider organization. Reuse it between IdP and Channel Provider records only when both refer to the same organization.

## 5. Talk to the Agent in the IM

Add the bot to a test conversation. In a group chat, start with an explicit @-mention:

> @Ankole What can you do, and which team are you serving?

After the first real model reply, tune the Agent mission, models, and group-chat policy for the team.

<a id="agent-not-replying"></a>

### If the Agent does not reply

Check one boundary at a time, in this order:

1. The latest provider app version is published and available to the test user.
2. The bot is installed in the channel, team, or conversation.
3. The required message event and scopes are active.
4. The routing rule is enabled and points to the intended Agent.
5. The Agent has `primary`, `light`, and `heavy` model profiles.
6. The LLM Provider credential and model selector are valid.
7. At least one Worker is ready.

For Compose, inspect `docker compose logs -f control-plane worker`. For Kubernetes, inspect the control-plane and Worker pod logs. Read only the relevant error; do not print environment variables or secrets.

Once the reply arrives, continue with [Agents](https://ankole.agentbull.com/en-US/docs/agents/index.md), [Signal routing rules](https://ankole.agentbull.com/en-US/docs/signal-bindings/index.md), or [Background Agent Jobs](https://ankole.agentbull.com/en-US/docs/background-jobs/index.md).
