---
title: "빠른 시작"
description: "Ankole을 배포하고, 엔터프라이즈 identity와 모델을 설정하고, IM 채널을 연결한 뒤 실제 Agent 대화를 완료합니다."
url: "https://ankole.agentbull.com/ko-KR/docs/quickstart/"
lang: "ko-KR"
---

> AI Agent용 문서 색인: https://ankole.agentbull.com/ko-KR/llms.txt

# 빠른 시작

## Ankole 배포 방식

프라이빗 Ankole deployment instance는 control plane 하나와 하나 이상의 Agent Computer Worker로 구성됩니다. control plane은 지속적인 도메인 상태와 감독을 소유하는 관리 플랫폼입니다. Worker는 Agent의 실행 환경을 제공하고 Agent의 작업 컴퓨터 역할을 합니다.

하나의 Worker는 여러 Agent를 서비스할 수 있습니다. 금융 등 엄격한 격리가 필요한 환경에서는 각 Agent에 전용 Worker를 주세요. 작업 컴퓨터는 여러 인턴이 공유하거나 한 명의 동료에게 할당할 수 있습니다.

> **💡 Did you know?**
>
> 여러 Agent가 하나의 Worker를 공유해도 각 Agent는 별도 sandbox에서 실행됩니다. sandbox는 기본적인 프로세스와 파일 시스템 격리를 제공하고 Agent 간 간섭을 줄입니다. 가벼운 격리 계층이지 절대적인 보안 경계는 아닙니다.

각 instance에는 PostgreSQL과 영속 디스크 저장소도 필요합니다. 단일 호스트 배포는 로컬 또는 가상 디스크를 사용할 수 있습니다. Kubernetes 배포에는 ReadWriteMany를 지원하는 NFS 또는 다른 공유 볼륨이 필요합니다.

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

### 이 가이드에서 사용하는 용어

이 페이지는 Ankole 사용자 문서와 인터페이스에 쓰이는 용어를 정의합니다. 괄호 안의 이름은 타사 플랫폼, configuration 필드, API와 일치합니다. 이후 섹션은 짧은 형식을 사용합니다.

| 정식 용어 | 의미 | 짧은 형식 |
|---|---|---|
| **Private deployment instance** | 엔터프라이즈가 배포하고 관리하는 하나의 완전한 Ankole 시스템 | Instance |
| **Principal** | Ankole에서 identity와 권한을 가질 수 있는 사람, Agent 또는 시스템 서비스 | Principal |
| **Identity Provider (IdP)** | Console SSO를 제공하고 직원, 연락처, 조직 구조를 동기화하는 외부 identity 소스 | IdP |
| **Chat platform** | Slack, Teams, Lark/Feishu, DingTalk, WeCom, Telegram, Discord, LINE, WhatsApp 또는 메일함 같은 외부 대화 플랫폼 | Platform |
| **Channel Provider** | 메시지를 받고 Agent 답변을 보내는 하나의 채팅 앱 또는 bot 설정 | Channel Provider |
| **Signal Routing Rule (Signal Binding)** | 시그널 소스에서 Agent로 메시지나 이벤트를 보내는 규칙 | Routing rule |
| **LLM Provider** | 모델 서비스 endpoint, 자격 증명, 사용 가능한 모델을 저장하는 설정 | LLM Provider |
| **Background Agent Jobs profile (internal key: `coding`)** | Background Agent Jobs용 AIGateway provider와 모델을 선택합니다. 일반 대화는 코드 양으로 이것을 선택하지 않습니다 | Background Agent Jobs |

## Agent가 setup을 완료하게 하기

Codex, Claude Code 또는 터미널을 조작할 수 있는 다른 Agent에 이 prompt를 보낼 수 있습니다:

```text
https://ankole.agentbull.com/ko-KR/docs/quickstart/index.md 를 사용하여 프라이빗 Ankole 배포 인스턴스를 배포하고 구성하도록 도와주세요. 먼저 내 환경을 검사하고 Docker Compose, Kubernetes, 또는 소스로부터의 설치 중 하나를 추천해 주세요. 그런 다음 Identity Provider(IdP), LLM Provider, Agent, Channel Provider, Signal Routing Rule(Signal Binding) 설정을 도와주세요. 내가 IdP와 IM 플랫폼을 지정하지 않았다면 어떤 identity 소스와 채팅 플랫폼을 사용할지 물어봐 주세요. 저를 대신해 선택하지 마세요. chat이나 명령 출력에 secrets를 노출하지 마세요. 내가 선택한 IM에서 실제 Agent 답변을 받았을 때만 작업을 완료하세요.
```

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

## 1. Ankole 배포

단일 호스트에는 Docker Compose를, 엔터프라이즈 배포에는 Kubernetes를 사용하세요. 개발과 디버깅은 소스에서 설치합니다.

**Choose a deployment method**

- Docker Compose · 단일 호스트 권장
- Kubernetes · 엔터프라이즈 권장
- 소스에서 설치하기

### Docker Compose · 단일 호스트 권장

대부분의 팀에 가장 좋은 시작점입니다. Docker가 설치된 Linux, macOS, Windows 호스트 한 대로 PostgreSQL, control plane, Agent Computer Worker 한 개, Caddy HTTPS를 실행할 수 있습니다.

**시작하기 전에**

- Linux amd64 또는 arm64 컨테이너를 실행할 수 있는 Linux, macOS, Windows 호스트
- Compose 플러그인이 포함된 Docker Engine, 또는 Docker Desktop
- 영구 디스크, DNS 이름, 그리고 80 포트와 443 포트

#### 기본 설정 · Docker Compose로 설치하기

provider API key는 이 배포 파일에 넣지 마세요. 사인인 후 Console에서 추가하게 됩니다.

##### 1. 배포 패키지 가져오기

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

##### 2. 독립된 secrets 세 개 생성하기

이 명령을 복사해서 실행하세요. 그런 다음 출력 전체를 .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. 공개 호스트 설정하기

ANKOLE_HOST를 이 호스트를 가리키는 DNS 이름으로 설정하세요. ACME_EMAIL에는 인증서 안내를 받을 수 있는 주소를 설정하세요.

- `ANKOLE_HOST`: `ankole.example.com` — 사용자와 provider callback이 사용할 HTTPS 호스트입니다.
- `ACME_EMAIL`: `ops@example.com` — Caddy 인증서 관리를 위한 연락 주소입니다.

##### 4. 스택 시작 및 확인하기

Compose는 PostgreSQL을 기다렸다가 migration을 실행하고 Worker key를 저장한 뒤, control plane, Worker, Caddy를 시작합니다.

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

##### 5. 첫 설정 열기

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력하세요.

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

#### 고급 설정 · 프로덕션 제어, 백업, 로컬 인증서

첫 배포를 프로덕션으로 전환하기 전에 이 설정을 사용하세요.

##### 1. 검증된 이미지 쌍 고정하기

control-plane과 Worker 태그는 RuntimeFabric 검증 후 함께 이동합니다. 통제된 rollout을 위해 두 이미지를 모두 동일한 소스 리비전의 digest로 고정하세요.

- `ANKOLE_CONTROL_PLANE_IMAGE` — control-plane 이미지 또는 불변 digest입니다.
- `ANKOLE_WORKER_IMAGE` — 동일한 검증된 쌍의 Worker 이미지 또는 불변 digest입니다.
- `ANKOLE_POSTGRESQL_IMAGE` — 번들 데이터베이스 이미지의 선택적 불변 digest입니다.

##### 2. 업그레이드 전마다 백업하기

또한 ankole_agents_data 볼륨의 스냅샷을 만드세요. 별도의 호스트에서 데이터베이스와 Agent Home 복원을 함께 테스트하세요.

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

##### 3. 짧은 중단과 함께 업그레이드하기

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

> docker compose down은 명명된 볼륨을 유지합니다. docker compose down -v는 PostgreSQL, Agent Home, Caddy 데이터를 삭제합니다. 영구 삭제를 의도했고 테스트된 백업이 있는 경우가 아니라면 -v를 사용하지 마세요.

##### 4. 로컬 전용 HTTPS 호스트 사용하기

로컬 테스트를 위해 ANKOLE_HOST=ankole.localhost로 설정하세요. 첫 시작 후 Caddy의 root 인증서를 복사하고 모든 클라이언트에서 신뢰하도록 하세요.

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

- [전체 Compose 운영 가이드](https://github.com/AgentBull/ankole/blob/main/tools/deploy/docker-compose/README.md)

### Kubernetes · 엔터프라이즈 권장

이미 Kubernetes를 운영하고 있으며 클러스터 스케줄링, HTTPS Ingress, 공유 Agent Home 스토리지가 필요하다면 Helm chart를 사용하세요.

**시작하기 전에**

- Kubernetes 1.27 이상과 Helm 3 이상
- Linux amd64 또는 arm64 노드
- Agent Home용 HTTPS Ingress와 ReadWriteMany 스토리지

#### 기본 설정 · Helm chart 설치하기

가장 짧은 경로는 번들 PostgreSQL 18 이미지를 사용하며, 여기에는 pg_search와 vector가 포함되어 있습니다.

##### 1. chart 가져오기 및 namespace 생성하기

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

##### 2. 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. values-production.yaml 만들기

호스트, Ingress 세부 사항, 두 StorageClass 이름을 바꾸세요. 위에 표시된 DATABASE_URL을 사용한다면 fullnameOverride를 유지하세요.

```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. 설치하고 대기하기

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

##### 5. 워크로드 확인하기

```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. 첫 설정 열기

https://ankole.example.com/setup을 열고 활성화 코드를 입력하세요.

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

#### 고급 설정 · 외부 PostgreSQL, 이미지 정책, 클러스터 보안

통제된 프로덕션 rollout 전에 다음 항목을 검토하세요.

##### 1. 외부 PostgreSQL 서버 사용하기

해당 서버는 PostgreSQL 18 이상을 실행하고 pg_search를 preload하며, 애플리케이션 데이터베이스 소유자가 pg_search와 vector를 사용할 수 있게 해야 합니다.

- `postgresql.enabled`: `false` — 번들 데이터베이스를 비활성화합니다.
- `DATABASE_URL` — 외부 데이터베이스 URL을 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. Worker 보안 프로필 확인하기

강력한 bubblewrap 격리에는 SYS_ADMIN, unconfined seccomp 프로필, unmasked /proc가 필요합니다. chart가 이를 설정하지만, 클러스터 admission 정책이 이를 허용해야 합니다.

> Worker 노드를 신뢰되는 first-party 컴퓨팅 경계로 취급하세요. 클러스터 정책이 이 프로필을 거부한다면 전용 node pool 또는 승인된 동등 sandbox를 사용하세요.

##### 3. 일치하는 이미지 고정 및 업그레이드하기

동일한 검증된 소스 리비전의 control-plane과 Worker digest를 고정하세요. helm upgrade 전에 PostgreSQL과 Agent Home을 백업하세요.

> Helm rollback은 데이터베이스 migration을 되돌리지 않습니다. 애플리케이션 rollback에 이전 스키마도 필요하다면 데이터베이스 백업을 복원하세요.

##### 4. 전체 chart 계약 읽기

- [전체 Helm 배포 가이드](https://github.com/AgentBull/ankole/blob/main/tools/deploy/helm/ankole-agent/README.md)

### 소스에서 설치하기

소스 경로는 개발, 디버깅, 로컬 평가에 사용하세요. 이 경로는 PostgreSQL, Phoenix, Console, 프런트엔드 자산, 관리형 Docker Worker 하나를 시작합니다.

**시작하기 전에**

- macOS 또는 Linux; Windows에서는 WSL2 사용
- 시스템 패키지를 설치할 수 있는 계정
- Docker Desktop 또는 Docker Engine

#### 기본 설정 · 개발 환경 실행하기

##### 1. 리포지토리 복제하기

나머지 명령은 리포지토리 루트에서 실행하세요.

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

##### 2. 고정된 툴체인 설치하기

이 스크립트는 빌드 패키지, Docker, Rust, Elixir와 Erlang, 그리고 리포지토리가 고정한 Bun 버전을 설치합니다.

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

##### 3. 새 터미널을 열고 모든 경계 확인하기

macOS에서는 먼저 Docker Desktop을 시작하세요. Linux에서 설치 관리자가 사용자 계정을 docker 그룹에 추가했다면 로그아웃 후 다시 로그인하세요.

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

##### 4. 의존성 설치 및 PostgreSQL 초기화하기

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

##### 5. Ankole 시작하기

이 터미널을 열어 둔 상태에서 http://localhost:4000을 열고 개발 서버가 출력한 활성화 코드를 사용하세요.

```bash
bun dev
```

#### 고급 설정 · 로컬 확인, 종료, 전달된 origin

이러한 제어는 완전한 런타임을 대상으로 개발하거나 원격 workspace를 사용할 때 도움이 됩니다.

##### 1. 다른 터미널에서 활성화 코드 읽기

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

##### 2. 표시되는 런타임 경계 확인하기

bun dev 터미널에서 Ctrl+C로 control plane과 Worker를 중지하세요. 작업을 마치면 PostgreSQL은 별도로 중지하세요.

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

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

##### 3. Codespaces 또는 다른 전달된 origin 사용하기

전달된 HTTPS origin을 IdP에 등록하세요. localhost callback과 전달된 callback은 서로 다른 URL이므로, 브라우저가 사용하는 URL로 사인인을 확인하세요.

> 이 경로는 개발용입니다. 프로덕션 배포에는 Docker Compose 또는 Kubernetes를 사용하세요.

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

## 2. identity provider를 먼저 설정

Ankole은 엔터프라이즈 내부의 프라이빗 배포를 위해 설계되었습니다. 각 엔터프라이즈는 instance 하나를 운영합니다. 그 instance 안에서 Agent, 직원, 시스템 서비스는 Principal로 표현되고, 권한 모듈이 그들의 권한을 관리합니다.

Hermes Agent와 OpenClaw처럼 Ankole도 채팅 채널에 연결됩니다. 또한 엔터프라이즈 identity 소스에 연결하여 직원, 디렉터리 연락처, 조직 구조를 동기화합니다. 채널을 설정하기 전에 Identity Provider(IdP)를 먼저 설정하세요.

| 설정 | 제어 대상 | 설정 위치 |
|---|---|---|
| **Identity Provider (IdP)** | Console SSO와 엔터프라이즈 디렉터리에서 동기화된 직원, 연락처, 조직 구조, 권한 그룹 | 먼저 `/setup`, 그다음 **Console → Identity Providers** |
| **Channel Provider** | 메시지를 받고 Agent 답변을 보내는 IM 앱 또는 bot | **Console → Signal Routing** |

identity 소스와 채팅 채널은 서로 다른 플랫폼을 사용할 수 있습니다. 직원은 Google Workspace로 로그인하고 Slack에서 Agent와 대화할 수 있습니다. Entra ID가 identity를 제공하고 Lark, Slack, DingTalk, Teams, WeCom이 대화를 운반할 수 있습니다.

### IdP setup 완료

엔터프라이즈가 사용하는 identity 소스를 선택하세요. 각 tab은 provider console에서 시작해 첫 로그인과 디렉터리 동기화로 끝납니다.

adapter를 처음 활성화했다면 첫 로그인 후 control plane을 재시작하세요. 이 재시작이 디렉터리 연결과 Graph subscription 같은 플러그인 백그라운드 작업을 시작합니다.

**Choose an identity provider**

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

#### Slack

Console 사인인과 workspace 멤버 및 사용자 그룹 동기화에 Slack 앱 하나를 사용합니다. 기본 설정에는 OAuth 클라이언트, Bot Token, App Token이 필요합니다.

**시작하기 전에**

- Slack 앱을 만들고 설치할 수 있는 workspace 관리자
- Ankole용 공개 HTTPS 주소
- 첫 사인인에 사용할 workspace 멤버 계정

##### 기본 설정 · Slack IdP 설정하기

###### 1. Ankole에서 callback URL 복사하기

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력하세요. 플러그인 페이지에서 Slack Adapter를 선택하고 저장한 다음, Slack을 선택하세요.

Configuration ID(Provider ID)를 slack-main으로 유지하세요. Copy를 사용해 로그인 callback URL을 복사하세요. 이 URL을 직접 입력하거나 바꾸지 마세요.

###### 2. Slack 앱 만들기

Slack API Your Apps를 열고 Create New App → From scratch를 선택하세요. 앱 이름을 입력하고 직원이 속한 workspace를 선택하세요.

Basic Information → App Credentials를 열고 Client ID와 Client Secret을 복사하세요.

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

###### 3. 로그인 callback 등록하기

OAuth & Permissions → Redirect URLs를 열고 Add New Redirect URL을 선택한 다음, Ankole에서 가져온 전체 URL을 붙여 넣고 저장하세요.

Ankole은 기본적으로 openid, profile, email 사인인 scopes를 요청합니다. 이를 chat bot scopes로 바꾸지 마세요.

- [Slack 사인인 가이드](https://api.slack.com/authentication/sign-in-with-slack)

###### 4. 디렉터리 액세스를 부여하고 Bot Token을 받습니다

OAuth & Permissions에서 Bot Token Scopes 아래에 아래의 세 scope를 추가합니다. 그런 다음 Install to Workspace를 선택하고 설치를 승인합니다.

- `users:read` — 구성원 프로필 읽기
- `users:read.email` — 구성원 이메일 주소 읽기
- `usergroups:read` — 사용자 그룹과 그 구성원 읽기

> 설치 후 Bot User OAuth Token을 복사합니다. 이 token은 반드시 xoxb-로 시작해야 합니다. scope를 변경할 때마다 앱을 다시 설치합니다.

###### 5. App Token을 받고 Socket Mode를 사용 설정합니다

Basic Information → App-Level Tokens를 엽니다. Generate Token and Scopes를 선택하고 아래의 scope를 추가한 다음, 생성된 xapp- token을 복사합니다. 그런 다음 Socket Mode를 열고 Enable Socket Mode를 켭니다.

- `connections:write` — App Token이 Socket Mode 연결을 열 수 있게 합니다

###### 6. 디렉터리 이벤트를 구독합니다

Event Subscriptions를 열고 이벤트를 사용 설정합니다. Subscribe to bot events 아래에 아래의 다섯 이벤트를 각각 추가합니다.

- `team_join` — 구성원이 workspace에 가입함
- `user_change` — 구성원 프로필이 변경됨
- `subteam_created` — 사용자 그룹이 생성됨
- `subteam_updated` — 사용자 그룹이 변경됨
- `subteam_members_changed` — 사용자 그룹 멤버십이 변경됨

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

###### 7. Ankole에 Slack 값을 입력합니다

Sync directory와 Sync directory changes를 켠 상태로 유지합니다. Validate configuration을 선택하고 로그인합니다. 그런 다음 Slack 인증을 완료합니다.

- Client ID: Basic Information의 Client ID
- Client Secret: 같은 페이지의 Client Secret
- Workspace ID: 선택 사항입니다. Slack 웹 URL에서 /client/ 뒤의 T… 값을 입력하면 로그인할 workspace를 미리 선택할 수 있습니다. 디렉터리 동기화를 제한하지는 않습니다
- Bot User OAuth Token: xoxb- token
- App-Level Token: xapp- token

###### 8. 로그인과 최초 동기화를 확인합니다

Slack이 Ankole로 돌아오면 이 사용자가 첫 번째 루트 관리자가 됩니다. Console → Identity Providers를 열고 slack-main을 선택한 다음 Run full sync를 선택합니다.

동기화가 끝나면 Console → Principals와 Principal groups에서 workspace 구성원과 Slack 사용자 그룹을 확인합니다.

##### 고급 설정 · 로그인 전용, 실시간 동기화, credential 갱신

Slack 디렉터리가 필요 없는 경우에만 기본 디렉터리 동기화를 끕니다.

###### 1. Slack을 로그인에만 사용합니다

Sync directory를 끄면 Bot User OAuth Token이 필요 없어지고 실시간 동기화도 꺼집니다. Client ID와 Client Secret은 여전히 필요합니다.

###### 2. 실시간 이벤트 없이 전체 동기화를 사용합니다

Sync directory는 켜 두고 Sync directory changes를 끕니다. 이 구성에는 Bot User OAuth Token이 필요하지만 App-Level Token은 필요하지 않습니다. 구성원 변경은 다음 전체 동기화 후에 나타납니다.

###### 3. scope 또는 token을 갱신합니다

Slack scope나 Bot Token을 변경한 뒤에는 앱을 다시 설치하고 Identity에서 token을 갱신합니다. rotation 후 App Token을 갱신하지 않으면 Socket Mode가 연결되지 않습니다.

#### Microsoft Entra ID

단일 테넌트 Entra 앱을 등록합니다. 이 앱은 사용자를 Console에 로그인시키고, 사용자와 그룹을 읽으며, Microsoft Graph를 통해 디렉터리 변경을 수신합니다.

**시작하기 전에**

- 앱을 등록하고 관리자 동의를 승인할 수 있는 Entra 관리자
- Ankole의 공개 HTTPS 주소
- 최초 로그인용 테넌트 구성원 계정

##### 기본 설정 · Microsoft Entra ID 설정

###### 1. Ankole에서 콜백 URL을 복사합니다

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력합니다. 플러그인 페이지에서 Microsoft 365 Adapter를 선택하고 선택을 저장한 다음 Entra ID를 선택합니다.

Configuration ID(Provider ID)를 entra-id-main으로 유지합니다. Copy를 사용하여 로그인 콜백 URL을 복사합니다.

###### 2. 단일 테넌트 앱을 등록합니다

Microsoft Entra 관리 센터를 엽니다. Entra ID → App registrations → New registration으로 이동합니다. 이름을 입력하고 Accounts in this organizational directory only를 선택합니다.

Redirect URI에서 Web을 선택하고 Ankole의 전체 콜백 URL을 붙여넣습니다. 그런 다음 Register를 선택합니다.

- [Microsoft 앱 등록 가이드](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)

###### 3. 앱의 세 가지 값을 복사합니다

앱 Overview 페이지에서 Application (client) ID와 Directory (tenant) ID를 복사합니다.

Certificates & secrets → Client secrets → New client secret을 엽니다. 생성 직후 Value를 복사합니다. Secret ID는 복사하지 마십시오. Value는 페이지를 떠난 뒤에는 표시되지 않습니다.

###### 4. Microsoft Graph 권한을 부여합니다

API permissions → Add a permission → Microsoft Graph를 엽니다. 아래의 세 권한을 추가합니다. 그런 다음 Grant admin consent for <organization>을 선택합니다. 세 권한 모두 granted 상태인지 확인합니다.

- `User.Read` — 위임된 권한
- `User.Read.All` — 애플리케이션 권한
- `Group.Read.All` — 애플리케이션 권한

- [Microsoft Graph 권한 참조](https://learn.microsoft.com/en-us/graph/permissions-reference)

###### 5. Ankole에 Entra 값을 입력합니다

Sync directory와 Sync directory changes를 켠 상태로 유지합니다. Validate configuration을 선택하고 로그인합니다. 이 테넌트의 계정을 사용합니다.

- Directory (tenant) ID: 앱 Overview 페이지의 값
- Application (client) ID: 앱 Overview 페이지의 값
- Client secret value: 클라이언트 시크릿의 Value
- Ankole public URL: https://<ANKOLE_HOST>. 내부 컨테이너나 클러스터 주소는 사용할 수 없습니다

###### 6. 로그인과 최초 동기화를 확인합니다

로그인 후 이 사용자가 첫 번째 루트 관리자가 됩니다. Console → Identity Providers를 열고 entra-id-main을 선택한 다음 Run full sync를 선택합니다.

동기화가 끝나면 Principals와 Principal groups에서 테넌트 사용자와 그룹을 확인합니다.

##### 고급 설정 · Graph 알림, 게스트, 그룹 필터

실시간 동기화를 위해 Microsoft Graph가 공개 인터넷에서 Ankole에 도달할 수 있어야 합니다.

###### 1. 알림 endpoint가 도달 가능하도록 설정합니다

Sync directory changes가 켜져 있으면 Ankole public URL은 유효한 공개 HTTPS 주소여야 합니다. Ankole은 그 아래에 /webhooks/v1/entra-id/entra-id-main/directory를 만들고 Graph 구독을 관리합니다.

- [Microsoft Graph 변경 알림 가이드](https://learn.microsoft.com/en-us/graph/change-notifications-overview)

###### 2. 공개 ingress 없이 실시간 동기화를 끕니다

Graph가 이 인스턴스에 도달할 수 없다면 저장하기 전에 Sync directory changes를 끕니다. 전체 동기화는 계속 동작하며 Ankole public URL도 더 이상 필요하지 않습니다.

###### 3. 게스트 포함 또는 그룹 필터는 필요한 경우에만 사용합니다

Include guest users는 기본적으로 꺼져 있습니다. Synced groups filter는 Microsoft Graph OData $filter를 허용합니다. 먼저 필터 없이 전체 동기화를 완료하여, 필터 결과를 권한 실패로 오해하지 않도록 합니다.

#### Google Workspace

Google 로그인과 디렉터리 액세스는 별도의 credential을 사용합니다. OAuth client가 로그인을 처리하고, 도메인 전체 위임이 설정된 service account가 사용자와 그룹을 동기화합니다.

**시작하기 전에**

- Google Workspace 슈퍼 관리자
- Google Cloud 프로젝트를 관리할 수 있는 계정
- Ankole의 공개 HTTPS 주소

##### 기본 설정 · Google Workspace 설정

###### 1. Ankole에서 콜백 URL을 복사합니다

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력합니다. 플러그인 페이지에서 Google Workspace Adapter를 선택하고 선택을 저장한 다음 Google Workspace를 선택합니다.

Configuration ID(Provider ID)를 google-workspace-main으로 유지합니다. Copy를 사용하여 로그인 콜백 URL을 복사합니다.

###### 2. Google Cloud 프로젝트를 준비합니다

Google Cloud Console을 열고 조직이 소유한 프로젝트를 선택하거나 만듭니다. API Library에서 Admin SDK API를 찾아 사용 설정합니다.

Google Auth platform을 열고 앱 정보를 완료합니다. Audience를 Internal로 설정하여 이 앱이 이 Workspace의 직원 전용 앱이 되게 합니다.

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

###### 3. 로그인용 OAuth client를 만듭니다

Google Auth platform → Clients → Create client를 엽니다. 애플리케이션 유형으로 Web application을 선택합니다.

Authorized redirect URIs 아래에 Ankole의 전체 콜백 URL을 붙여넣습니다. 클라이언트를 만들고 Client ID와 Client Secret을 복사합니다.

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

###### 4. 디렉터리 동기화용 service account를 만듭니다

IAM & Admin → Service Accounts를 열고 service account를 만듭니다. 세부 정보를 열어 Google Workspace 도메인 전체 위임을 사용 설정하고 숫자로 된 Client ID를 기록합니다.

Keys → Add key → Create new key를 열고 JSON을 선택합니다. JSON 파일의 전체 내용을 Ankole에 붙여넣게 됩니다. 이 파일을 리포지토리에 커밋하지 마십시오.

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

###### 5. Workspace에서 도메인 전체 위임을 승인합니다

admin.google.com을 엽니다. Security → Access and data control → API Controls → Manage Domain Wide Delegation으로 이동하여 Add new를 선택합니다.

숫자로 된 service account Client ID를 입력합니다. OAuth scopes는 하나의 필드이므로 아래의 전체 줄을 복사하여 붙여넣고 승인합니다.

**모든 OAuth scope 복사**

```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 권한 부여 가이드](https://developers.google.com/workspace/admin/directory/v1/guides/authorizing)

###### 6. Ankole에 Google 값을 입력합니다

Sync directory를 켠 상태로 유지합니다. Validate configuration을 선택하고 로그인합니다. 허용된 도메인의 Workspace 계정을 사용합니다.

- OAuth client ID와 OAuth client secret: Web application OAuth client의 credential
- Allowed Workspace domains: @ 없이 example.com과 같은 형식의 Workspace 도메인
- Service account JSON key: JSON 키 파일의 전체 내용
- Delegated administrator email: 사용자와 그룹을 읽을 수 있는 Workspace 관리자

###### 7. 로그인과 최초 동기화를 확인합니다

로그인 후 이 사용자가 첫 번째 루트 관리자가 됩니다. Console → Identity Providers를 열고 google-workspace-main을 선택한 다음 Run full sync를 선택합니다.

동기화가 끝나면 Principals와 Principal groups에서 Workspace 사용자, 그룹, 멤버십을 확인합니다.

##### 고급 설정 · 도메인 경계, 동기화 시점, service account

Google Workspace는 전체 동기화만 지원합니다. 이 adapter에 실시간 디렉터리 이벤트를 보내지 않습니다.

###### 1. Allowed Workspace domains를 최소로 유지합니다

엔터프라이즈가 사용하는 Workspace 도메인만 입력합니다. Ankole은 Google이 확인한 이메일과 Workspace hosted-domain claim도 요구합니다. 일반 consumer gmail.com 계정에는 이러한 claim이 없으므로, Google이 인증한 후에도 Ankole은 이 계정을 거부합니다.

###### 2. 디렉터리 변경 후 전체 동기화를 다시 실행합니다

Google Workspace adapter는 실시간 동기화를 지원하지 않습니다. 직원을 추가하거나 그룹을 변경하거나 계정을 일시 중지한 뒤에는 Console → Identity Providers에서 전체 동기화를 다시 실행합니다.

###### 3. service account 액세스를 줄이고 키를 교체합니다

첫 번째 검증 후에는 사용자와 그룹 읽기 권한이 있는 전용 관리자를 Delegated administrator email로 사용할 수 있습니다. JSON 키를 교체할 때는 Ankole의 Service account JSON key를 갱신합니다.

#### Lark / Feishu

사용자 지정 엔터프라이즈 앱을 만듭니다. 앱의 App ID와 App Secret이 로그인, 직원 및 부서 동기화, 그리고 long connection을 통한 실시간 디렉터리 이벤트를 제공합니다.

**시작하기 전에**

- 사용자 지정 엔터프라이즈 앱을 만들고 게시할 수 있는 관리자
- 디렉터리 권한을 승인할 수 있는 엔터프라이즈 관리자
- 최초 로그인용 Lark 또는 Feishu 직원 계정

##### 기본 설정 · Lark 또는 Feishu IdP 설정

###### 1. Ankole에서 콜백 URL을 복사합니다

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력합니다. 플러그인 페이지에서 Lark Adapter를 선택하고 선택을 저장한 다음 Lark 또는 Feishu를 선택합니다.

Configuration ID(Provider ID)를 lark-main으로 유지합니다. Copy를 사용하여 로그인 콜백 URL을 복사합니다.

###### 2. 사용자 지정 엔터프라이즈 앱을 만듭니다

Feishu tenant는 Feishu Open Platform을, 국제 Lark tenant는 Lark Developer를 사용합니다. 사용자 지정 엔터프라이즈 앱을 만듭니다.

Credentials & Basic Info를 열고 App ID와 App Secret을 복사합니다.

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

###### 3. 로그인 콜백을 등록합니다

Development Configuration → Security Settings → Redirect URLs를 열고 Ankole의 전체 URL을 추가합니다.

콜백은 정확히 일치해야 합니다. scheme, host, port 또는 provider ID가 다르면 로그인이 실패합니다.

- [Feishu 웹 로그인 가이드](https://open.feishu.cn/document/sso/web-application-end-user-consent/guide)

###### 4. 로그인 및 디렉터리 권한을 가져옵니다

Permissions를 열고 일괄 가져오기 또는 내보내기 작업을 선택합니다. 아래의 JSON을 붙여넣고 가져오기를 확인합니다. 권한에 엔터프라이즈 승인이 필요하면 상태가 active가 될 때까지 기다립니다.

**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. long-connection 디렉터리 이벤트를 구성합니다

Events & Callbacks → Event Configuration을 열고 long-connection 또는 WebSocket 전달 옵션을 선택합니다. 아래의 일곱 이벤트를 추가합니다.

- `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. 가용 범위를 설정하고 게시합니다

첫 번째 관리자와 동기화가 필요한 모든 부서를 앱 가용 범위에 추가합니다. 앱 버전을 만들고 게시합니다. 게시되지 않은 콜백, 권한, 이벤트 변경은 직원에게 적용되지 않습니다.

###### 7. Ankole에 Lark 또는 Feishu 값을 입력합니다

기본 디렉터리 동기화 설정은 보통 변경할 필요가 없습니다. Validate configuration을 선택하고 로그인합니다. 그런 다음 Console → Identity Providers를 열고 lark-main에 대해 전체 동기화를 실행합니다.

Principals와 Principal groups에서 직원, 부서, 멤버십을 확인합니다.

- App ID와 App Secret: Credentials & Basic Info의 값
- Service region: Feishu는 Feishu (Mainland China), Lark는 Lark (Global)을 선택합니다

##### 고급 설정 · long connection, 앱 가용 범위, chat 앱

control plane이 long connection을 엽니다. 공개 Feishu webhook은 필요하지 않습니다.

###### 1. 실시간 이벤트 없이 전체 동기화를 사용합니다

Advanced settings에서(보통 변경할 필요가 없습니다) 디렉터리 이벤트가 필요 없다면 Sync directory changes를 끕니다. 그러면 직원 및 부서 변경은 다음 전체 동기화 후에 나타납니다.

###### 2. 앱 가용 범위를 로그인 경계로 사용합니다

앱 가용 범위는 로그인할 수 있는 대상을 제어합니다. 부서나 직원을 추가한 뒤에는 범위를 갱신하고 새 버전을 게시합니다.

###### 3. identity와 chat에 별도의 앱을 사용합니다

하나의 사용자 지정 앱으로 두 역할을 모두 처리할 수도 있지만, 일반적인 사용에서는 별도의 앱이 더 좋습니다. 로그인 및 디렉터리 권한은 IdP 앱에 유지하고, bot 권한은 chat 앱에 유지합니다. 두 앱은 같은 조직에 대해 동일한 platformSubjectNamespace를 사용할 수 있습니다.

#### DingTalk

내부 엔터프라이즈 앱을 만듭니다. 동일한 Client ID와 Client Secret이 로그인, 조직 디렉터리 액세스, 그리고 Stream을 통한 실시간 변경을 제공합니다.

**시작하기 전에**

- 내부 앱을 만들고 게시할 수 있는 DingTalk 관리자
- 디렉터리 API 액세스를 승인할 수 있는 관리자
- 최초 로그인용 조직 내 직원 계정

##### 기본 설정 · DingTalk IdP 설정

###### 1. Ankole에서 콜백 URL을 복사합니다

https://<ANKOLE_HOST>/setup을 열고 활성화 코드를 입력합니다. 플러그인 페이지에서 DingTalk Adapter를 선택하고 선택을 저장한 다음 DingTalk를 선택합니다.

Configuration ID(Provider ID)를 dingtalk-main으로 유지합니다. Copy를 사용하여 로그인 콜백 URL을 복사합니다.

###### 2. 내부 엔터프라이즈 앱을 만듭니다

DingTalk 개발자 콘솔을 엽니다. App Development → Internal Enterprise Apps로 이동하여 앱을 만듭니다.

Basic Information → Credentials를 엽니다. Client ID(이전 명칭 AppKey)와 Client Secret(이전 명칭 AppSecret)을 복사합니다.

- [DingTalk 개발자 콘솔 열기](https://open-dev.dingtalk.com/)

###### 3. 로그인 콜백을 등록합니다

Development Configuration → Security Settings → Redirect URL을 엽니다. Ankole의 전체 URL을 붙여넣고 저장합니다.

로그인 scope를 openid corpid로 유지합니다. corpid claim은 로그인을 선택한 DingTalk 조직으로 제한합니다.

###### 4. 로그인 및 디렉터리 API 권한을 요청합니다

Permission Management → Contacts Management를 엽니다. 아래 작업에 대한 읽기 액세스를 요청합니다. 디렉터리 범위를 모든 직원으로 설정하거나, 로그인과 동기화가 필요한 모든 부서를 포함하도록 합니다.

- `Contact.User.Read` — 개인 연락처 정보 읽기
- `Get a user ID by unionId`
- `Get user details`
- `Get child department IDs`
- `Get department details`
- `Get basic department user information`

###### 5. Stream을 통해 디렉터리 변경을 구독합니다

Events & Callbacks를 열고 Stream mode를 선택합니다. 아래의 열 개 이벤트를 추가합니다. 이 이벤트는 직원, 부서, 관리자, 조직 변경을 다룹니다.

- `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 직원 변경 Stream 가이드](https://open.dingtalk.com/document/orgapp/personnel-platform-employee-change-event-stream)

###### 6. 가용 범위를 설정하고 게시합니다

첫 번째 관리자와 동기화가 필요한 부서를 앱 가용 범위에 추가합니다. 콜백, 권한, 이벤트 설정이 포함된 버전을 만들고 게시합니다.

###### 7. Ankole에 DingTalk 값을 입력합니다

Sync directory와 Sync directory changes를 켠 상태로 유지합니다. Validate configuration and sign in을 선택합니다. Ankole은 DingTalk 로그인 화면을 열기 전에 자격 증명을 확인합니다.

로그인 후 Console → Identity Providers를 열고 dingtalk-main에 대해 full sync를 실행합니다. Principals와 Principal groups에서 직원과 부서를 확인합니다.

- Client ID (AppKey): 자격 증명 페이지의 Client ID
- Client Secret (AppSecret): 같은 페이지의 Client Secret

##### 고급 설정 · Stream, 디렉터리 범위, 페이지 크기

control plane이 Stream 연결을 엽니다. 공용 webhook이 필요하지 않습니다.

###### 1. Stream 이벤트 없이 full sync 사용

디렉터리 이벤트가 필요하지 않으면 Sync directory changes를 끕니다. 그러면 직원과 부서 변경은 다음 full sync 후에 반영됩니다.

###### 2. 디렉터리 권한 범위 확인

동기화에서 사람이나 부서가 누락되면 Permission Management에서 디렉터리 범위를 확인합니다. 승인된 API 권한도 범위에 포함되지 않으면 불완전한 데이터를 반환할 수 있습니다.

###### 3. 먼저 기본 페이지 크기 유지

기본 페이지 크기는 50이고 허용 범위는 1–100입니다. 기본값으로 full sync를 한 번 완료합니다. 측정된 rate-limit 또는 응답 크기 문제가 있을 때만 변경합니다.

#### WeCom

자체 구축 앱(self-built app)이 QR 로그인을 담당하고, 전용 Contacts-sync secret이 주기적인 full sync로 구성원과 부서를 가져옵니다. 로그인과 디렉터리 API 호출 모두 고정된 egress IP가 필요합니다.

**시작하기 전에**

- WeCom 기업 초관리자 계정
- 고정 egress IP가 있는 Ankole 배포
- 최초 로그인을 위한 기업 내 구성원 계정

##### 기본 설정 · WeCom IdP 설정

###### 1. Ankole에서 콜백 URL 복사

https://<ANKOLE_HOST>/setup을 열고 activation code를 입력합니다. 플러그인 페이지에서 WeCom Adapter를 선택하고, 선택을 저장한 다음 WeCom을 선택합니다.

Configuration ID(Provider ID)를 wecom-main으로 유지합니다. Copy를 사용하여 로그인 콜백 URL을 복사합니다.

###### 2. Corp ID 기록

WeCom 관리 콘솔을 엽니다. My Company → Company information으로 이동하여 Corp ID를 복사합니다.

- [WeCom 관리 콘솔 열기](https://work.weixin.qq.com/wework_admin/)

###### 3. 신뢰 도메인과 trusted IP를 가진 자체 구축 앱 만들기

App Management → Self-built → Create app으로 이동합니다. 앱 세부 정보를 열고 AgentId와 Secret을 복사합니다.

같은 페이지에서 두 값을 설정합니다. 웹 인증용 신뢰 도메인으로 Ankole 도메인을 추가하고, 배포의 egress IP를 trusted-IP 목록에 추가합니다.

> trusted-IP 항목이 없으면 로그인과 API 호출이 오류 60020으로 실패합니다. 신뢰 도메인이 없으면 QR 스캔 후 리디렉션이 차단됩니다.

###### 4. Contacts sync 활성화 및 전용 secret 기록

Security & Administration → Management tools → Contacts sync로 이동합니다. API sync를 켜고 전용 secret을 복사한 다음 자체 trusted IP를 등록합니다.

2022년 6월 이후 일반 앱 secret은 더 이상 구성원 이름과 기타 프로필 필드를 반환하지 않습니다. 이 전용 secret이 없으면 디렉터리 동기화를 사용할 수 없고, 로그인한 사용자는 사용자 ID만 남게 됩니다.

###### 5. Ankole에 WeCom 값 입력

Enable login과 Sync directory를 켠 상태로 유지합니다. Validate configuration and sign in을 선택하고 WeCom QR 로그인을 완료합니다.

- Corp ID: Company information에서 가져온 값
- Self-built app AgentId / Secret: 앱 세부 정보에서 가져온 두 값
- Contacts-sync Secret: Contacts sync 페이지의 전용 secret

###### 6. 로그인과 첫 full sync 확인

첫 번째로 로그인한 구성원이 첫 번째 루트 관리자가 됩니다. Console → Identity Providers를 열고 wecom-main을 선택한 다음 full sync를 실행합니다.

Principals와 Principal groups에서 구성원과 부서를 확인합니다.

##### 고급 설정 · 동기화 주기, trusted IP, 로그인 전용

WeCom은 실시간 디렉터리 이벤트를 보내지 않습니다. 변경 사항은 주기적인 full sync로 수렴됩니다.

###### 1. 디렉터리 변경 후 full sync 실행

WeCom은 디렉터리 변경을 Ankole로 푸시하지 않습니다. 구성원을 추가하거나 부서를 변경한 후 Console → Identity Providers에서 full sync를 실행하거나, 다음 주기적 동기화를 기다립니다.

###### 2. egress IP가 변경되면 두 trusted-IP 목록 모두 업데이트

자체 구축 앱과 Contacts sync는 각각 자체 trusted-IP 목록을 유지합니다. 마이그레이션 또는 egress-IP 변경 후 두 목록을 모두 업데이트하지 않으면 로그인과 동기화가 모두 오류 60020으로 실패합니다.

###### 3. 디렉터리 동기화 없이 로그인 사용

Sync directory를 끄면 Contacts-sync secret은 선택 사항이 됩니다. 그러면 로그인한 Principals는 이름이나 부서 그룹 없이 WeCom 계정 ID만 유지합니다.

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

## 3. LLM Provider 추가 및 Agent 생성

Agent model profile은 모델 참조를 저장하므로 LLM Provider를 먼저 추가하세요. Console에 로그인합니다. **Providers → New provider**를 열고 provider 종류를 선택한 다음 안정적인 Provider ID를 지정하고 endpoint와 credential 필드를 입력한 뒤 저장합니다.

Provider 자격 증명은 control plane에서 암호화된 채 유지됩니다. deployment 환경 파일이나 Agent 파일에 넣지 마세요.

**Agents → New Agent**를 엽니다. Agent에 안정적인 UID, 명확한 표시 이름, 그리고 무엇을 담당하고 무엇이 수용 가능한 결과인지 말해주는 mission을 지정하세요.

그런 다음 model profile을 설정합니다:

| Profile | 첫 실행 용도 |
|---|---|
| `primary` | 주 추론 모델 |
| `light` | 짧고 잦은 작업 |
| `heavy` | 어려운 종합 |

Agent가 실행되려면 세 가지가 모두 필요합니다. 첫 대화에서는 검증된 provider와 모델을 세 profile 모두에 바인딩할 수 있습니다. 엔드투엔드 경로가 동작한 뒤에만 분리하세요.

### 고급 설정 · 선택적 Agent profile과 Brain 유지보수

Agent 기능 profile은 필요할 때만 설정합니다. Brain은 검색 모델만 인스턴스 전체에 유지합니다.

#### 1. 선택적 Agent 기능 profile 추가

- `Background Agent Jobs` — coding profile로 영속적인 Background Agent Jobs를 실행합니다. 메시지에 코드가 많아도 일반 대화는 이 profile을 선택하지 않습니다.
- `vision_fallback` — 기본 모델이 이미지를 검사할 수 없을 때의 폴백(fallback)입니다.
- `web_search / web_fetch` — 웹 탐색과 페이지 검색을 위한 provider입니다.
- `image_generate` — 이미지 생성을 위한 모델입니다.

#### 2. Brain 유지보수 Agent와 검색 모델 선택

Console → AppConfigure → Brain을 여세요. 활성 Brain 유지보수 Agent를 선택합니다. 모든 Brain 모델 호출은 이 Agent의 ID로 실행되고 사용량도 이 Agent에 귀속됩니다. 비활성화하면 다시 활성화하거나 교체할 때까지 모델 호출과 로컬 URL 가져오기가 중지됩니다. 연결된 Agent 페이지에서 모델 profile을 편집합니다.

- `brain.maintainer_agent_uid` — 선택한 Agent의 light profile로 지식을 추출하고, heavy profile로 Dreaming을 실행하며, web_fetch profile로 URL Source를 가져옵니다. web_fetch가 설정되지 않으면 로컬 ankole-browser를 사용합니다.
- `brain.embedding_model` — 벡터 검색을 활성화합니다. Provider와 모델을 선택하고 embedding 차원 수를 입력합니다.
- `brain.rerank_model` — 융합된 검색 결과를 rerank합니다. 비워 두면 융합 결과 순서를 유지합니다.

- [Brain 가이드](https://ankole.agentbull.com/ko-KR/docs/brain/index.md)
- [AppConfigure 참조](https://ankole.agentbull.com/ko-KR/docs/app-configuration/index.md)

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

## 4. 채팅 채널 연결 및 시그널 라우팅 규칙 생성

Console에서 채팅 플랫폼의 Control Plane Plugin을 활성화한 다음, 해당 플랫폼에서 bot 또는 앱을 만드세요. 같은 플랫폼을 사용하더라도 IdP 역할과 채팅 역할에는 별도 앱을 사용하세요. 이 분리는 로그인·디렉터리 권한을 bot 권한과 분리합니다. 또한 credential 순환과 앱 릴리스도 분리됩니다.

하나의 채팅 앱은 보통 하나의 bot identity를 나타냅니다. 여러 Agent가 서로 다른 bot 이름, 아바타, 권한이 필요하다면 플랫폼에 앱을 여러 개 만드세요. 앱을 준비한 뒤 Console에서 라우팅 규칙을 만들고 의도한 Agent에 연결하세요.

Slack, Teams, Lark/Feishu, DingTalk, WeCom은 엔터프라이즈 플랫폼입니다. 사용자는 IdP가 동기화한 디렉터리에서 옵니다. Telegram, Discord, LINE, WhatsApp은 소비자용 IM입니다. 사용자에게 직원 레코드가 없으므로 Agent가 응답하기 전에 관리자가 **아이덴티티 → 대기 중인 매핑**에서 새 발신자마다 계정에 매핑합니다. WhatsApp은 알려진 계정이 이미 그 전화번호를 소유한 경우 발신자를 스스로 매핑합니다. Email은 전용 메일함을 연결하며, 발신자는 명시적인 이메일 identity 바인딩을 통해서만 알려집니다.

**Choose a channel provider**

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

### Slack · 기본

Slack은 Socket Mode를 사용합니다. Ankole이 아웃바운드 WebSocket을 열기 때문에 채팅 경로에 공용 Slack webhook이 필요하지 않습니다.

**시작하기 전에**

- Slack 앱을 만들고 설치할 권한
- 테스트 채널 또는 다이렉트 메시지

#### 기본 설정 · Slack 앱 준비

##### 1. 앱 생성 및 Socket Mode 활성화

Slack 앱을 처음부터 만듭니다. Basic Information에서 App-Level Token을 만들고 아래 scope를 추가한 다음 Socket Mode를 활성화합니다.

- `connections:write` — App Token이 Socket Mode 연결을 열 수 있게 합니다

##### 2. 채팅 및 채널 상태 이벤트 구독

Event Subscriptions → Subscribe to bot events를 엽니다. 아래의 각 이벤트를 추가합니다. 이 목록은 Slack adapter가 구현한 메시지, 반응, 채널 상태 기능과 일치합니다.

- `app_mention` — 채널 @멘션 수신
- `message.channels` — 공개 채널 메시지 수신
- `message.groups` — 비공개 채널 메시지 수신
- `message.im` — 다이렉트 메시지 수신
- `message.mpim` — 여러 사람 간 다이렉트 메시지 수신
- `reaction_added` — 추가된 반응 수신
- `reaction_removed` — 제거된 반응 수신
- `member_joined_channel` — 채널에 참여한 구성원 동기화
- `member_left_channel` — 채널에서 나간 구성원 동기화
- `channel_rename` — 채널 이름 변경 동기화
- `group_rename` — 비공개 채널 이름 변경 동기화
- `channel_deleted` — 채널 삭제 동기화
- `channel_archive` — 채널 아카이브 동기화

##### 3. 다이렉트 메시지 활성화

App Home → Show Tabs를 엽니다. Display Messages 탭을 켜고 Allow users to send Slash commands and messages from the messages tab을 선택합니다. Messages 탭만으로는 화면이 표시될뿐 사용자가 Agent에게 다이렉트 메시지를 보낼 수는 없습니다.

##### 4. Slack 기본 인터랙티비티 활성화

Interactivity & Shortcuts를 열고 Interactivity를 활성화합니다. Socket Mode가 기존 WebSocket을 통해 Block Kit 버튼 동작을 전달하므로 공용 Request URL이 필요하지 않습니다.

##### 5. 전체 채팅 scope 부여 및 앱 설치

OAuth & Permissions → Bot Token Scopes에서 아래의 각 scope를 추가합니다. 이 목록은 Ankole이 호출하는 Slack API만 포함합니다. 사용하지 않는 assistant, workflow, document, meeting scope는 부여하지 마십시오.

- `app_mentions:read` — 봇을 멘션한 메시지 읽기
- `channels:read` — 공개 채널과 그 구성원 동기화
- `channels:history` — 공개 채널 메시지 읽기 및 답글 조정
- `groups:read` — 비공개 채널과 그 구성원 동기화
- `groups:history` — 비공개 채널 메시지 읽기 및 답글 조정
- `im:read` — 다이렉트 메시지 대화 동기화
- `im:history` — 다이렉트 메시지 읽기 및 답글 조정
- `mpim:read` — 여러 사람 간 다이렉트 메시지와 그 구성원 동기화
- `mpim:history` — 여러 사람 간 다이렉트 메시지 읽기 및 답글 조정
- `chat:write` — 봇 메시지 전송, 업데이트, 삭제
- `reactions:read` — 반응 변경 수신
- `reactions:write` — 반응 추가 및 제거
- `files:read` — 메시지에 첨부된 파일 읽기
- `files:write` — Agent가 보내는 파일 업로드
- `users:read` — 채널 구성원 식별 및 봇 계정 제외

> scope를 변경한 후에는 워크스페이스에 앱을 다시 설치하고 새 Bot Token을 Ankole에 입력합니다.

##### 6. Console 필드 수집

- `botToken`: `xoxb-…` — Bot User OAuth Token. xoxb- 접두사가 필요합니다.
- `appToken`: `xapp-…` — Socket Mode용 App-Level Token. xapp- 접두사가 필요합니다.

#### 고급 설정 · 메시지 정책, subject 매핑, IdP로서의 Slack

Slack이 이벤트를 전달하지만, 주소가 지정되지 않은 메시지를 Ankole이 처리하는 방식은 여전히 signal routing 규칙이 결정합니다.

##### 1. Agent를 멘션하지 않는 메시지의 정책 선택

위의 이벤트와 scope를 사용하면 Slack이 전체 대화를 전달할 수 있지만, Ankole signal routing 정책을 대체하지는 않습니다. addressed_only로 시작하십시오. Agent가 직접 멘션 없이 관찰하거나 개입해야 하는 경우에만 observe_all 또는 may_intervene을 선택하십시오.

##### 2. 하나의 앱이 Slack IdP도 담당할 때 identity 권한 추가

프로덕션에서는 별도의 IdP 앱을 사용하십시오. 하나의 앱이 두 역할을 모두 담당해야 한다면 아래의 Bot scope와 디렉터리 이벤트를 추가하십시오.

- `users:read.email` — 구성원 이메일 주소 동기화
- `usergroups:read` — 사용자 그룹과 그 구성원 동기화
- `team_join` — 구성원 가입 이벤트 수신
- `user_change` — 구성원 프로필 변경 수신
- `subteam_created` — 사용자 그룹 생성 수신
- `subteam_updated` — 사용자 그룹 업데이트 수신
- `subteam_members_changed` — 사용자 그룹 구성원 변경 수신

##### 3. subject 네임스페이스 설정

- `platformSubjectNamespace`: `slack-main` — Slack 워크스페이스마다 네임스페이스 하나를 사용합니다. Slack이 두 역할을 모두 담당할 때는 Slack IdP config와 공유합니다.
- `userName`: `Slack` — 아웃바운드 메시지에 사용되는 표시 이름.

##### 4. 별도의 Slack IdP 앱 만들기

일반적인 사용에서는 SSO와 디렉터리 동기화를 위한 별도의 Slack 앱을 만듭니다. identity 권한은 IdP 앱에, bot 권한은 채팅 앱에 유지합니다. 두 레코드는 같은 워크스페이스에 대해 하나의 platformSubjectNamespace를 사용할 수 있습니다.

### Microsoft Teams

Teams는 Bot Framework webhook을 보냅니다. 신뢰할 수 있는 인증서가 있는 공용 HTTPS 엔드포인트가 필수입니다.

**시작하기 전에**

- Entra ID 테넌트
- 공용 HTTPS Ankole 호스트
- Teams 봇을 등록하고 설치할 권한

#### 기본 설정 · Teams 봇 준비

##### 1. 앱과 봇 등록

Entra 앱 등록(Entra app registration)과 Azure Bot 리소스를 만듭니다. 애플리케이션 client ID가 appID가 되고, appPassword용 client secret을 만듭니다.

##### 2. Bot Framework 메시징 엔드포인트 설정

appID 경로 세그먼트는 signal routing 규칙의 App ID와 일치해야 합니다.

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

##### 3. 테넌트 모드 선택

- `botTenancy`: `single_tenant` — 단일 엔터프라이즈 테넌트에 권장되는 시작 모드.
- `tenantID` — Entra 테넌트 GUID. single-tenant 봇에 필요합니다.

##### 4. Console 필드 수집

- `appID` — Azure Bot 등록의 Microsoft App ID. GUID여야 합니다.
- `appPassword` — Microsoft App client secret.

#### 고급 설정 · 크로스 테넌트 봇, Entra identity, 디렉터리 webhook

봇이 둘 이상의 Entra 테넌트를 지원하거나 Entra ID가 SSO도 제공할 때 확장합니다.

##### 1. 하나의 Bot Framework 앱으로 여러 Entra 테넌트 지원

Azure Bot 등록이 여러 Entra 테넌트를 허용하는 경우에만 botTenancy를 multi_tenant로 설정합니다. adapter는 bot token 테넌트에 botframework.com을 사용합니다. 이 Microsoft 앱 등록 모드는 Ankole 배포 인스턴스의 경계를 변경하지 않습니다.

##### 2. Entra가 identity도 제공할 때 subject 네임스페이스 공유

- `platformSubjectNamespace`: `entra-id-main` — Teams 채널과 Entra ID provider가 같은 조직을 나타낼 때 동일한 네임스페이스를 사용합니다.
- `userName`: `Teams` — 아웃바운드 메시지에 사용되는 표시 이름.

##### 3. Graph 디렉터리 동기화는 IdP에 유지

Entra 전체 및 실시간 디렉터리 동기화는 IdP 레코드에 속합니다. Graph 알림은 별도의 디렉터리 webhook을 사용하며 여전히 공용 HTTPS 호스트가 필요합니다.

### Lark / Feishu

Lark와 Feishu는 아웃바운드 long connection을 사용합니다. 채팅 경로에는 인터넷 접근이 필요하지만 공용 인바운드 webhook은 필요하지 않습니다.

**시작하기 전에**

- 엔터프라이즈 커스텀 앱을 만들 권한
- 앱 가용 범위(availability scope)의 테스트 사용자

#### 기본 설정 · Lark 또는 Feishu 앱 준비

##### 1. 앱 생성 및 bot 기능 활성화

엔터프라이즈 커스텀 앱을 만들고 bot 기능을 활성화한 다음 테스트 사용자를 가용 범위(availability scope)에 추가합니다.

##### 2. 전체 채팅 scope 가져오기

Permissions를 열고 일괄 가져오기/내보내기 작업을 선택한 다음 아래의 JSON을 붙여넣고 가져오기를 확인합니다. 이 목록은 adapter가 호출하는 bot, message, reaction, file, card, room, membership API와 일치합니다. 채팅 앱에 디렉터리 쓰기, 문서, 회의, 긴급 메시지 권한을 추가하지 마십시오.

**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. long connection 선택 및 구현된 이벤트 추가

Events and Callbacks에서 long connection을 선택하고 아래의 각 이벤트를 추가합니다. 플랫폼이 클라이언트를 감지하는 동안 Ankole control plane을 실행 상태로 유지합니다.

- `im.message.receive_v1` — 봇에게 보낸 메시지 수신
- `im.message.recalled_v1` — 메시지 회수 이벤트 수신
- `im.message.reaction.created_v1` — 추가된 반응 수신
- `im.message.reaction.deleted_v1` — 제거된 반응 수신
- `im.chat.member.bot.added_v1` — 봇을 추가한 방 동기화
- `im.chat.member.bot.deleted_v1` — 봇을 제거한 방 동기화
- `im.chat.member.user.added_v1` — 방에 참여한 구성원 동기화
- `im.chat.member.user.deleted_v1` — 방에서 나간 구성원 동기화
- `im.chat.updated_v1` — 방 변경 동기화
- `im.chat.disbanded_v1` — 방 삭제 동기화
- `card.action.trigger` — 인터랙티브 카드 동작 수신

##### 4. 버전 게시 및 Console 필드 수집

- `appID` — 엔터프라이즈 커스텀 앱 ID.
- `appSecret` — 엔터프라이즈 커스텀 앱 secret.
- `domain`: `feishu or lark` — Feishu.cn에는 feishu를, Larksuite.com에는 lark를 사용합니다.

#### 고급 설정 · 전체 방 컨텍스트, 별도 identity, subject 매핑

Agent가 방을 관찰해야 하거나 Lark가 SSO도 제공할 때 확장합니다.

##### 1. Agent가 지명되지 않은 그룹 메시지를 관찰하게 하기

observe_all 또는 may_intervene를 사용하기 전에 아래 권한을 추가하세요. 첫 대화를 검증하는 동안에는 addressed_only로 시작하세요.

- `im:message.group_msg` — 봇을 멘션하지 않은 그룹 메시지 읽기

##### 2. 별도 Lark IdP 앱 생성

일반적인 사용에서는 SSO와 디렉터리 동기화를 위해 또 다른 커스텀 앱을 만드세요. 로그인과 디렉터리 권한은 IdP 앱에, bot 권한은 채팅 앱에 두세요. 두 레코드가 같은 조직을 나타낼 때는 platformSubjectNamespace 하나를 공유할 수 있습니다.

##### 3. 공유 adapter 필드 설정

- `platformSubjectNamespace`: `lark-main` — 두 레코드가 같은 조직을 나타낼 때 이 값을 Lark IdP config와 공유하세요.
- `userName`: `Lark / Feishu` — 발신 메시지에 사용되는 표시 이름입니다.

##### 4. 활성 binding마다 하나의 Lark 앱 사용

binding마다 별도의 Lark 또는 Feishu 앱을 만드세요. 비활성화된 binding은 해당 앱을 해제합니다.

- 하나의 Agent에서 여러 Lark binding을 활성화할 수 있습니다.
- 활성 binding마다 서로 다른 domain과 appID 조합을 사용해야 합니다.

### DingTalk

DingTalk는 Stream 모드를 사용합니다. AppKey와 AppSecret 한 쌍이 robot을 인증하고 이벤트 연결을 엽니다.

> **DingTalk는 일부 Ankole 기능을 제한합니다**
>
> 그룹 채팅에서 Agent는 전체 대화 history를 읽을 수 없습니다. 명시적으로 @멘션한 메시지만 받습니다. DingTalk 카드는 template 기반이므로 streaming 카드 답변에는 카드 플랫폼에 구축한 AI 카드 template이 하나 필요합니다. 없으면 답변은 일반 Markdown으로 유지됩니다.
>
> 이러한 제한은 Ankole의 기능과 사용자 경험을 줄입니다. 또한 장기 memory 시스템에 불완전한 context를 남깁니다. 가능하면 다른 채팅 채널을 선호하세요.

**시작하기 전에**

- 엔터프라이즈 내부 DingTalk 앱과 robot을 만들 권한
- 테스트 대화

#### 기본 설정 · DingTalk robot 준비

##### 1. 엔터프라이즈 내부 앱과 robot 생성

robot 기능을 활성화하고 앱을 테스트 사용자에게 공개하세요. DingTalk는 직접 메시지와 robot을 @멘션한 그룹 메시지를 전달합니다.

##### 2. Stream 모드 활성화 및 앱 게시

동일한 앱 자격 증명이 Stream 연결을 열므로 공개 메시지 webhook은 필요하지 않습니다.

##### 3. Console 필드 수집

- `clientId` — 엔터프라이즈 앱 Client ID. AppKey라고도 하며 Stream clientId로 사용됩니다.
- `clientSecret` — 엔터프라이즈 앱 Client Secret. AppSecret이라고도 하며 Stream clientSecret으로 사용됩니다.
- `group_message_mode`: `addressed_only` — DingTalk가 그룹 메시지로 전달할 수 있는 유일한 모드입니다.
- `cardTemplateId` — streaming 카드 답변용 AI 카드 template id입니다. 첫 테스트에서는 비워 두세요. 아래 고급 섹션이 template 구축 방법을 보여 줍니다.

#### 고급 설정 · AI 카드 template, 다중 Agent, identity 설정

streaming AI 카드 답변을 원하거나, DingTalk Agent를 여러 개 계획하거나, DingTalk를 identity 소스로도 사용하려면 펼치세요.

##### 1. AI 카드 template 생성 및 변수 추가

DingTalk 카드는 template 기반입니다. 레이아웃은 DingTalk 카드 플랫폼에 있고 Ankole은 고정된 변수 집합에 값만 씁니다. DingTalk 조직마다 template을 하나 만들고, 첫 번째에는 약 20분을 예상하세요. 시작 전에 Agent가 이미 일반 텍스트로 답변하고 있어야 하며, 앱에는 대화형 카드 인스턴스 쓰기와 AI 카드 streaming 업데이트 권한이 필요합니다.

DingTalk 개발자 콘솔 → 카드 플랫폼 → 새 template을 열고 AI 카드 카테고리를 선택하세요. 이 카테고리만 작성 인디케이터와 완료·실패 상태를 그리는 AI 카드 컨테이너가 있습니다. 아래 각 변수를 정확히 이 이름으로 추가하세요. 이름이 일치하지 않으면 모든 답변에서 해당 영역이 비어 있습니다.

- `state` — Text. 실행 중인 tool 라벨 같은 상태 줄 하나.
- `answer` — Markdown(streaming). 답변 본문이며 매 프레임마다 전체가 다시 쓰입니다.
- `thought` — Markdown. 일시적인 사고 초안이며 답변이 끝나면 비워집니다.
- `plan` — Text. 계획과 전체 중 완료된 수.
- `activity` — Text. 실행 중인 tool 호출이며 답변이 끝나면 비워집니다.
- `results` — Text. 구조화된 결과당 한 줄.
- `receipts` — Text. 기록된 부작용당 한 줄.
- `actions` — Text. 버튼 JSON 목록이며 Agent가 물어볼 것이 없으면 비어 있습니다.
- `meta` — Text. 트리거, 카드 번호, 개수, 경과 시간.

##### 2. 각 카드 상태에 컴포넌트 배치

AI 카드 컴포넌트에서 입력 중, 완료, 실패 레이아웃을 구성하세요. 답변을 표시해야 하는 각 레이아웃에 answer에 바인딩된 Markdown 컴포넌트를 놓고 입력 중 레이아웃에서는 streaming을 켜세요. meta와 state는 상단에, plan은 텍스트에, thought와 activity는 접힌 영역에, results와 receipts는 텍스트에 배치할 수 있습니다. 결정 버튼을 계속 표시해야 하면 입력 중과 완료 레이아웃 모두에 actions에 바인딩된 액션 영역을 놓고 각 버튼 값을 그대로 전달하세요.

DingTalk는 네이티브 AI 카드 수명 주기에서 레이아웃을 선택합니다. Ankole은 isFinalize를 보내 완료 상태로, isError를 보내 실패 상태로 전환합니다. flowStatus 또는 flowStatusVar를 만들거나 바인딩하지 마세요.

> 현재 상태 레이아웃에 answer에 바인딩된 Markdown 컴포넌트가 없으면 카드가 비어 보입니다. 레이아웃을 변경할 때마다 템플릿을 다시 게시하세요.

##### 3. template 게시, id 입력, 검증

template을 robot을 소유한 엔터프라이즈 내부 앱에 연결하고 게시한 뒤, template id를 복사해 라우팅 규칙의 cardTemplateId에 붙여 넣으세요. 변경은 다음 답변부터 적용되며 재시작할 것이 없습니다.

검증하려면 여러 문장을 만드는 메시지를 보내세요. Agent가 쓰는 동안 카드가 나타나고 커져야 하며, 답변이 끝나면 인디케이터가 멈추고 생각·활동 영역이 비워집니다. 그다음 결정이 필요한 것을 물어보세요. 버튼이 나타나야 하며, 누르면 turn이 이어집니다. 더 이상 대기 중인 질문에 응답하지 않는 오래된 버튼은 무시됩니다. 긴 답변은 약 2.5KB에서 카드를 봉인하고 새 카드로 이어집니다. 각 카드는 대화의 새 메시지이며 표 같은 풍부한 구조는 텍스트로 렌더링됩니다. 이 모두는 플랫폼 제한에서 예상되는 동작입니다.

- 카드가 비어 있음: 현재 입력 중, 완료 또는 실패 레이아웃에 answer가 바인딩되지 않았거나 변경한 템플릿이 게시되지 않았습니다.
- 한 영역이 항상 비어 있음: 변수 이름이 표와 일치하지 않거나 컴포넌트가 바인딩되지 않았습니다.
- 답변이 끝에만 나타나거나 전혀 나타나지 않음: answer 블록이 Markdown streaming 블록이 아닙니다.
- 답변이 끝난 뒤에도 작성 표시기가 남아 있음: control-plane 로그를 확인하고 DingTalk가 isFinalize 또는 isError가 포함된 streaming 업데이트를 수락했는지 확인하세요.
- 답변이 일반 Markdown 메시지임: template id가 비어 있거나, template이 이 앱에 게시되지 않았거나, DingTalk가 카드 콘텐츠를 거부했습니다. control-plane 로그에서 param.contentUnsafe 또는 param.cardNotExist를 확인하세요. 카드 경로가 영구히 실패하면 답변은 한 번 일반 Markdown으로 낮아지며 그래도 전달됩니다.
- 버튼은 나타나지만 눌러도 동작하지 않음: 액션 영역이 각 버튼 값을 그대로 전달하지 않습니다.

##### 4. 연결 소유권 규칙 준수

추가 Agent마다 별도의 robot과 credential 쌍을 만드세요.

- 하나의 Agent는 활성화된 DingTalk binding을 최대 하나만 가질 수 있습니다.
- 하나의 clientId는 하나의 Agent에만 바인딩할 수 있습니다.

##### 5. DingTalk identity 분리 유지

DingTalk는 OIDC와 디렉터리 동기화도 제공할 수 있지만 그것은 IdP 레코드입니다. 채팅 binding이 DingTalk를 Console 로그인 provider로 만들지는 않습니다.

- `platformSubjectNamespace`: `dingtalk-main` — 두 레코드가 같은 조직을 나타낼 때만 DingTalk IdP와 공유하세요.

### WeCom

WeCom AI bot은 하나의 아웃바운드 long connection으로 메시지를 주고받으므로 공개 메시지 webhook이 필요하지 않습니다. 플랫폼은 bot당 정확히 하나의 long connection을 허용합니다.

> **WeCom은 가장 제한적인 채팅 채널입니다**
>
> 그룹에서 Agent는 bot을 명시적으로 @멘션한 메시지만 받습니다. 이미지, 음성, 파일, 동영상은 직접 메시지로만 도착하며, 음성 메시지는 플랫폼 전사본으로만 도착합니다. 보낸 메시지는 회수, 편집, 이모지 반응을 할 수 없습니다. Agent는 새 대화를 시작할 수 없습니다. 사용자가 먼저 bot에게 메시지를 보내야 하며, 인바운드 메시지 후 답변 창은 24시간입니다. Streaming 답변은 사용자에게 답할 때만 작동하고, 하나의 streaming 메시지는 10분 안에 끝나야 하므로 긴 답변은 분할되며, 대화당 분당 약 30개 메시지로 전송이 제한됩니다. 카드 버튼은 클릭 후 5초 안에만 변경할 수 있습니다. 실시간 디렉터리 동기화는 없습니다.
>
> 모든 제한은 플랫폼 자체에서 비롯되며 Ankole이 우회할 수 없습니다. 장기 memory 시스템도 Agent가 받는 메시지 조각에서만 context를 만듭니다. 가능하면 Lark/Feishu, Slack, Teams 또는 DingTalk를 선호하세요.

**시작하기 전에**

- AI bot을 만들 수 있는 WeCom corp 슈퍼 관리자
- 테스트 대화

#### 기본 설정 · WeCom bot 준비

##### 1. corp 슈퍼 관리자로 AI bot 생성

WeCom 관리자 콘솔을 열고 API 모드로 AI bot을 만든 다음, 옆에 표시되는 Bot ID와 Secret을 기록하세요.

> bot은 corp 슈퍼 관리자가 만들어야 합니다. 그렇지 않으면 메시지의 사용자 id가 암호화되어 디렉터리나 로그인 identity에 합류할 수 없습니다.

##### 2. 다른 프로그램과 bot을 공유하지 않기

플랫폼은 bot당 정확히 하나의 long connection을 허용합니다. 다른 프로그램이 같은 Bot ID로 연결하면 각 연결이 다른 연결을 끊습니다. Ankole이 끊기면 대신 기다리며(park) 줄다리기를 하지 않습니다.

##### 3. Console 필드 수집

- `botId` — AI bot Bot ID입니다.
- `secret` — Bot ID 옆에 표시되는 long-connection secret입니다.
- `group_message_mode`: `addressed_only` — WeCom이 그룹 메시지로 전달할 수 있는 유일한 모드입니다.

#### 고급 설정 · Proactive 전달, identity 매핑, 다중 Agent

Agent가 먼저 말을 걸려면, 사용자가 해당 대화에서 bot에게 한 번 메시지를 보내야 합니다.

##### 1. Proactive 전달 활성화

예약 job 결과 같은 proactive 메시지는 사용자가 활성화한 대화에만 도달합니다. 각 사용자가 먼저 bot에게 메시지를 한 번 보내야 합니다. Agent는 새 대화를 시작할 수 없습니다. Proactive 전송은 streaming 없이 완전한 Markdown 메시지 하나로 전달됩니다.

##### 2. identity 매핑 필드 설정

- `platformSubjectNamespace`: `wecom-main` — 두 레코드가 같은 corp를 나타낼 때만 WeCom IdP와 공유합니다.
- `userName`: `企业微信 / WeCom` — 발신 bot 메시지에 표시되는 이름입니다.

##### 3. Agent마다 별도 bot 사용

추가 Agent마다 별도의 AI bot을 만드세요.

- 하나의 Agent는 활성화된 WeCom binding을 최대 하나만 가질 수 있습니다.
- 하나의 Bot ID는 하나의 Agent에만 바인딩할 수 있습니다.

### Telegram

Telegram은 Bot API long polling을 사용합니다. Ankole이 아웃바운드 연결로 getUpdates를 호출하므로 채팅 경로에는 공개 webhook이 필요하지 않습니다. Telegram은 소비자용 IM입니다. 사용자가 디렉터리의 직원이 아니므로 첫 메시지 전에 identity 매핑을 계획하세요.

**시작하기 전에**

- @BotFather와 대화할 수 있는 Telegram 계정
- 테스트용 개인 채팅 또는 그룹

#### 기본 설정 · Telegram bot 준비

##### 1. @BotFather로 bot 생성

@BotFather에 /newbot을 보내고 표시 이름과 bot으로 끝나는 사용자 이름을 선택한 다음, BotFather가 돌려주는 token을 복사하세요. 라우팅 규칙에 필요한 자격 증명은 이 token 하나입니다.

##### 2. bot이 그룹 메시지를 읽도록 허용

@BotFather에서 Bot Settings → Group Privacy를 열고 privacy mode를 끄세요. privacy mode가 켜져 있으면 그룹은 /command@bot 명령과 bot에 대한 답장만 전달하므로 @멘션이 Agent에 도달하지 않고, observe_all이나 may_intervene은 다른 메시지를 전혀 보지 못합니다. 이 설정 변경은 bot을 그룹에서 제거하고 다시 추가한 뒤에 적용됩니다.

##### 3. 이전 webhook 제거

Ankole은 Bot API를 polling하는데, token에 webhook이 설정되어 있으면 Telegram이 polling을 거부합니다. 다른 프로그램이 이 token을 webhook과 함께 사용했다면 규칙을 활성화하기 전에 삭제하세요. Ankole은 webhook_configured를 보고하며 다른 시스템이 소유한 webhook을 삭제하지 않습니다.

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

##### 4. Console 필드 수집

- `botToken`: `123456789:AA…` — @BotFather가 발급한 bot token입니다. 하나의 token은 활성화된 라우팅 규칙 하나에만 속할 수 있습니다.
- `group_message_mode`: `addressed_only` — 여기서 시작하세요. 직접 메시지, @멘션, /command@bot, bot에 대한 답장이 Agent를 지목합니다.

#### 고급 설정 · identity 매핑, forum topic, 플랫폼 제한

Telegram 사용자를 계정에 매핑하는 방법과 Agent가 사용할 수 없는 Telegram 기능을 확인하려면 펼치세요.

##### 1. Telegram 사용자를 계정에 매핑

Telegram 사용자에게는 직원 레코드가 없으므로 새 발신자마다 계정 자동 매핑이 실패합니다. 계정 자동 매핑에 실패한 경우를 수동 검토로 유지하세요. 발신자는 고정 안내 하나를 받고 아이덴티티 → 대기 중인 매핑에 나타나며, 관리자가 Telegram identity를 기존 계정(예: 로컬 비밀번호 계정)에 연결합니다. 그 다음 사용자가 메시지를 다시 보냅니다. 독립 계정 자동 생성은 누구나 Agent와 대화할 수 있는 열린 bot에서만 선택하세요.

##### 2. 대화가 세션에 매핑되는 방식 확인

- 개인 채팅, 그룹, 슈퍼그룹은 각각 하나의 Agent 세션을 이룹니다.
- 슈퍼그룹의 각 forum topic은 별도의 세션입니다.
- 채널 게시물, 다른 bot의 메시지, 익명 또는 게스트 발신자는 무시됩니다.

##### 3. 플랫폼 제한 준수

- Bot API는 20 MB보다 큰 파일을 다운로드할 수 없습니다. Agent는 파일 이름과 크기는 보지만 내용은 읽을 수 없습니다.
- Telegram은 사용자가 메시지를 삭제해도 이벤트를 보내지 않으므로 삭제된 텍스트가 세션 context에 남습니다.
- 연결이 끊긴 전송을 Telegram이 수락했을 가능성이 있으면 Ankole은 전송을 자동으로 반복하지 않습니다. 두 번째 답장을 게시하는 대신 중복 가능성 흐름을 적용합니다.

##### 4. Agent마다 별도 bot 사용

하나의 bot token은 활성화된 라우팅 규칙 하나에만 바인딩할 수 있습니다. 추가 Agent마다 @BotFather에서 bot을 하나 더 만드세요.

### Discord

Discord는 아웃바운드 WebSocket을 통한 bot Gateway를 사용하므로 채팅 경로에는 공개 webhook이 필요하지 않습니다. Discord는 소비자용 IM입니다. 사용자가 디렉터리의 직원이 아니므로 첫 메시지 전에 identity 매핑을 계획하세요.

**시작하기 전에**

- Developer Portal에서 애플리케이션을 만들 수 있는 Discord 계정
- bot을 초대할 수 있는 서버 또는 테스트용 직접 메시지

#### 기본 설정 · Discord bot 준비

##### 1. 애플리케이션과 bot 생성

Discord Developer Portal을 열고 New Application을 만든 다음 Bot 페이지를 열어 Reset Token을 선택하세요. token은 바로 복사하세요. Discord는 token을 한 번만 보여 줍니다.

##### 2. message content intent 활성화

Bot 페이지의 Privileged Gateway Intents에서 Message Content Intent를 켜세요. 이것이 없으면 Discord는 bot을 멘션하지 않은 서버 메시지를 빈 내용으로 전달하므로 Agent는 직접 메시지와 자신을 멘션한 메시지만 읽고, observe_all이나 may_intervene은 대화를 볼 수 없습니다. Ankole은 연결 전에 애플리케이션 플래그를 읽고 애플리케이션에 이 intent가 있을 때만 요청합니다.

> Server Members Intent와 Presence Intent는 필요하지 않습니다. 100개가 넘는 서버에 있는 애플리케이션은 message content intent를 유지하려면 Discord 인증이 필요합니다.

##### 3. bot을 서버에 초대

OAuth2 → URL Generator를 열고 bot 스코프를 선택한 다음 아래 권한을 추가하세요. 생성된 URL을 열고 서버를 선택하세요. 이 권한 집합은 Ankole이 호출하는 Discord API만 포함합니다.

- `View Channels` — bot이 허용된 채널 보기
- `Send Messages` — Agent 답장 게시
- `Send Messages in Threads` — thread 안에서 답장
- `Read Message History` — 이전 메시지에 답장하고 편집
- `Attach Files` — Agent가 보낸 파일 업로드
- `Add Reactions` — 반응 추가 및 제거

##### 4. Interactions Endpoint URL 비워 두기

General Information 페이지에서 Interactions Endpoint URL을 비워 두세요. 이 값이 설정되면 Discord가 버튼 클릭을 Gateway 대신 그 URL로 게시하므로 Agent 답장의 버튼이 Ankole에 도달하지 않습니다.

##### 5. Console 필드 수집

- `botToken` — Bot 페이지의 bot token입니다. 하나의 token은 활성화된 라우팅 규칙 하나에만 속할 수 있습니다.
- `group_message_mode`: `addressed_only` — 여기서 시작하세요. 직접 메시지, @멘션, bot에 대한 답장이 Agent를 지목합니다.

#### 고급 설정 · identity 매핑, thread, 플랫폼 제한

Discord 사용자를 계정에 매핑하는 방법과 Agent가 사용할 수 없는 Discord 기능을 확인하려면 펼치세요.

##### 1. Discord 사용자를 계정에 매핑

Discord 사용자에게는 직원 레코드가 없으므로 새 발신자마다 계정 자동 매핑이 실패합니다. 계정 자동 매핑에 실패한 경우를 수동 검토로 유지하세요. 발신자는 고정 안내 하나를 받고 아이덴티티 → 대기 중인 매핑에 나타나며, 관리자가 Discord identity를 기존 계정(예: 로컬 비밀번호 계정)에 연결합니다. 그 다음 사용자가 메시지를 다시 보냅니다. 독립 계정 자동 생성은 누구나 Agent와 대화할 수 있는 열린 서버에서만 선택하세요.

##### 2. 대화가 세션에 매핑되는 방식 확인

- 직접 메시지와 각 서버 채널은 하나의 Agent 세션을 이룹니다.
- 각 thread는 별도의 세션입니다.
- 다른 bot의 메시지, webhook 게시물, 시스템 알림, 텍스트와 첨부 파일이 없는 메시지는 무시됩니다.
- Agent 텍스트는 사용자, 역할, everyone에게 알림을 보내지 않습니다. Ankole은 보내는 모든 메시지에서 멘션 파싱을 비활성화합니다.

##### 3. 플랫폼 제한 준수

- Ankole은 최대 25 MB의 첨부 파일을 다운로드합니다. 더 큰 파일은 Agent가 이름과 크기는 보지만 내용은 읽을 수 없습니다.
- Discord는 사용자가 메시지를 삭제해도 Ankole이 사용하는 이벤트를 보내지 않으므로 삭제된 텍스트가 세션 context에 남습니다.
- 답장은 2,000자에서 분할됩니다. 카드 하나에는 최대 25개의 버튼이 표시됩니다.
- 연결이 끊긴 전송을 Discord가 수락했을 가능성이 있으면 Ankole은 전송을 자동으로 반복하지 않습니다. 두 번째 답장을 게시하는 대신 중복 가능성 흐름을 적용합니다.

##### 4. Agent마다 별도 bot 사용

하나의 bot token은 활성화된 라우팅 규칙 하나에만 바인딩할 수 있습니다. 추가 Agent마다 애플리케이션을 하나 더 만드세요.

### LINE

LINE은 Messaging API webhook을 Ankole에 게시합니다. 신뢰된 인증서가 있는 공개 HTTPS endpoint가 필수입니다. LINE은 소비자용 IM입니다. 사용자가 디렉터리의 직원이 아니므로 첫 메시지 전에 identity 매핑을 계획하세요.

> **LINE은 일부 Ankole 기능을 제한합니다**
>
> LINE reply token은 webhook 후 1분이면 만료되고 Agent turn은 보통 그보다 길기 때문에 모든 Agent 답장은 push 메시지입니다. push 메시지는 Official Account의 월간 메시지 요금제에서 차감되므로 요금제가 예상 트래픽을 감당해야 합니다. LINE bot은 메시지를 편집, 전송 취소, 반응할 수 없고 파일을 보낼 수 없으며, 최종 답변 전에 실시간 진행 상황을 표시하지 않습니다.
>
> 모든 제한은 플랫폼 자체에서 비롯되며 Ankole이 우회할 수 없습니다.

**시작하기 전에**

- LINE Developers provider와 Messaging API channel을 만들 권한
- 공개 HTTPS Ankole 호스트
- 테스트용 LINE 계정

#### 기본 설정 · LINE Official Account 준비

##### 1. Messaging API channel 생성 및 자격 증명 수집

LINE Developers Console에서 provider 아래에 Messaging API channel을 만드세요. Basic settings에서 Channel ID와 Channel secret을 복사하고, Messaging API tab에서 장기 channel access token을 발급하세요.

##### 2. webhook을 검증하기 전에 라우팅 규칙 생성

먼저 Console에서 라우팅 규칙을 저장하고 활성화하세요. Ankole은 알 수 없는 Channel ID의 webhook에 상태 404로 응답하므로, 규칙이 있기 전에는 LINE Developers Console의 Verify 버튼이 실패합니다.

##### 3. webhook URL 설정 및 검증

Messaging API tab에서 이 URL을 입력하고 Use webhook을 켠 다음 Verify를 선택하세요. channelId 경로 구간은 라우팅 규칙의 Channel ID와 일치해야 합니다. Ankole은 모든 요청의 x-line-signature를 channel secret으로 확인하고 잘못된 서명은 상태 401로 거부합니다.

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

##### 4. bot만 응답하도록 설정

LINE Official Account Manager에서 Response settings를 열고 응답 방식을 bot으로 설정한 다음, 계정이 Agent 옆에서 따로 답하지 않도록 자동 응답과 인사 메시지를 끄세요. 그룹 채팅에는 LINE Developers Console에서 Allow bot to join group chats를 활성화하세요.

##### 5. Console 필드 수집

- `channelId` — Messaging API Channel ID입니다. 하나의 channel은 활성화된 라우팅 규칙 하나에만 속할 수 있습니다.
- `channelSecret` — Basic settings의 Channel secret입니다. Ankole이 webhook 서명을 검증할 때 사용합니다.
- `channelAccessToken` — Messaging API tab에서 발급한 장기 channel access token입니다.
- `group_message_mode`: `addressed_only` — 여기서 시작하세요. LINE은 모든 그룹 메시지를 전달하므로 observe_all과 may_intervene도 추가 권한 없이 동작합니다.

#### 고급 설정 · identity 매핑, 그룹 답장, 플랫폼 제한

LINE 사용자를 계정에 매핑하는 방법과 그룹에서 Agent가 동작하는 방식을 확인하려면 펼치세요.

##### 1. LINE 사용자를 계정에 매핑

LINE 사용자에게는 직원 레코드가 없으므로 새 발신자마다 계정 자동 매핑이 실패합니다. 계정 자동 매핑에 실패한 경우를 수동 검토로 유지하세요. 발신자는 고정 안내 하나를 받고 LINE 표시 이름으로 아이덴티티 → 대기 중인 매핑에 나타나며, 관리자가 LINE identity를 기존 계정(예: 로컬 비밀번호 계정)에 연결합니다. 그 다음 사용자가 메시지를 다시 보냅니다. LINE user ID는 channel을 소유한 LINE Developers provider에 속하므로, 다른 provider 아래의 channel에는 별도의 매핑이 필요합니다.

##### 2. 그룹에서 Agent가 동작하는 방식 확인

- 1:1 채팅, 그룹, 다인 채팅은 각각 하나의 Agent 세션을 이룹니다. LINE에는 thread가 없습니다.
- 그룹에서는 bot을 @멘션하거나 bot의 메시지를 인용하면 Agent를 지목합니다. 그룹 답장은 질문한 사람을 인용합니다.
- 사용자가 전송 취소한 메시지는 세션 context에서 제거됩니다.

##### 3. 플랫폼 제한 준수

- 답장은 5,000자에서 분할되며 요청 하나로 최대 다섯 개의 메시지를 보냅니다. 확인 질문에는 최대 네 개의 버튼이 표시됩니다.
- Ankole은 최대 25 MB의 수신 파일을 다운로드합니다. 더 큰 파일은 Agent가 이름과 크기는 보지만 내용은 읽을 수 없습니다.
- Agent는 파일을 보낼 수 없습니다. 텍스트 답장은 그대로 전달됩니다.
- LINE은 월간 요금제가 소진되면 상태 429로 응답합니다. 요금제 변경이나 다음 달이 되어야 해제됩니다.

##### 4. Agent마다 별도 channel 사용

하나의 Messaging API channel은 활성화된 라우팅 규칙 하나에만 속할 수 있습니다. 추가 Agent마다 channel을 하나 더 만드세요.

### WhatsApp

WhatsApp은 Cloud API webhook을 Ankole에 게시합니다. 신뢰된 인증서가 있는 공개 HTTPS endpoint가 필수입니다. WhatsApp은 소비자용 IM이지만, 전화번호가 이미 알려진 사람에게 속한 발신자는 관리자 작업 없이 매핑됩니다.

> **WhatsApp은 일부 Ankole 기능을 제한합니다**
>
> Meta는 사용자의 최근 메시지 또는 버튼 응답 후 24시간이 지나면 고객 서비스 window를 닫습니다. 그 이후의 답장(예: 예약 보고서)은 명확한 실패로 중단되고 사용자에게 도달하지 않습니다. 사용자가 다시 메시지를 보낸 뒤 운영자가 Signal Routing 페이지에서 다시 시도할 수 있습니다. 그룹 채팅, 템플릿 메시지, 메시지 편집, 메시지 삭제, 실시간 진행 상황 미리보기는 사용할 수 없습니다.
>
> 모든 제한은 플랫폼 자체에서 비롯되며 Ankole이 우회할 수 없습니다.

**시작하기 전에**

- WhatsApp 제품이 추가된 Meta App과 전화번호를 소유한 WhatsApp Business Account
- 공개 HTTPS Ankole 호스트
- 테스트용 WhatsApp 계정

#### 기본 설정 · WhatsApp Business 전화번호 준비

##### 1. WhatsApp 제품 연결 및 식별자 수집

Meta App Dashboard에서 App에 WhatsApp 제품을 추가하고 전화번호를 소유한 WhatsApp Business Account를 연결하세요. App settings → Basic에서 App ID와 App secret을, WhatsApp → API Setup에서 Phone number ID를 복사하세요.

##### 2. 영구 System User token 발급

Meta Business Suite에서 System User를 만들고 App에 대한 whatsapp_business_messaging 권한을 부여한 다음 만료 없는 token을 생성하세요. 사용자 token은 만료되어 Agent를 중단시킵니다.

##### 3. callback을 검증하기 전에 라우팅 규칙 생성

verify token을 정하고 Console에 다른 필드와 함께 입력한 다음 라우팅 규칙을 저장하고 활성화하세요. Meta가 callback URL을 Ankole에 대해 검증하므로 규칙이 먼저 있어야 합니다.

##### 4. callback URL 설정 및 messages 구독

WhatsApp → Configuration을 열고 이 URL과 같은 verify token을 입력한 다음 Verify and save를 선택하세요. 그런 다음 App을 messages webhook 필드에 구독시키세요. 다른 필드는 사용하지 않습니다. Ankole은 모든 요청의 x-hub-signature-256을 App secret으로 확인하고 잘못된 서명은 상태 401로 거부합니다.

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

##### 5. Console 필드 수집

- `appId` — Meta App ID입니다. 하나의 App에 속한 여러 전화번호가 각자 다른 Agent를 담당할 수 있으며, 그 규칙들은 같은 App secret과 verify token을 공유해야 합니다.
- `appSecret` — App settings → Basic의 App secret입니다. Ankole이 webhook 서명을 검증할 때 사용합니다.
- `verifyToken` — 직접 정하는 값입니다. Meta callback 구성에 같은 값을 입력하세요.
- `phoneNumberId` — WhatsApp → API Setup의 Phone number ID입니다. 하나의 번호는 활성화된 라우팅 규칙 하나에만 속할 수 있습니다.
- `accessToken` — 영구 System User access token입니다.
- `group_message_mode`: `addressed_only` — 유일한 모드입니다. WhatsApp은 1:1 채팅만 전달하며 모든 메시지가 Agent를 지목합니다.

#### 고급 설정 · identity 매핑, 답장, 플랫폼 제한

WhatsApp 사용자가 계정이 되는 방식과 Agent가 사용할 수 없는 WhatsApp 기능을 확인하려면 펼치세요.

##### 1. WhatsApp 사용자가 계정이 되는 방식 확인

발신자의 WhatsApp ID는 Meta가 확인한 전화번호입니다. 기존 계정이 그 휴대폰 번호를 소유하고 있으면(예: 디렉터리 동기화로) 발신자는 즉시 매핑됩니다. 그렇지 않으면 계정 자동 매핑이 실패합니다. 계정 자동 매핑에 실패한 경우를 수동 검토로 유지하세요. 발신자는 고정 안내 하나를 받고 WhatsApp 프로필 이름과 전화번호로 아이덴티티 → 대기 중인 매핑에 나타나며, 관리자가 identity를 기존 계정에 연결합니다. 독립 계정 자동 생성은 누구나 Agent와 대화할 수 있는 열린 번호에서만 선택하세요.

##### 2. Agent가 답장하는 방식 확인

- 답장은 사용자의 메시지를 인용합니다. 긴 답장은 4,096자에서 분할됩니다.
- 선택지가 최대 세 개인 확인 질문에는 답장 버튼이 표시되고, 그보다 많으면 목록 하나가 표시됩니다. 모든 선택지는 번호로 시작합니다.
- Agent는 유형별 Meta 크기 제한 안에서 이미지, 동영상, 오디오, 문서를 보낼 수 있습니다. Meta가 받지 않는 파일은 명확한 오류로 중단되며 텍스트는 그대로 전달됩니다.
- 사용자가 WhatsApp에서 삭제한 메시지는 Ankole에 도달하지 않으므로 그 텍스트는 세션 context에 남습니다.

##### 3. 고객 서비스 window 준수

Ankole은 전송 전마다 window를 확인합니다. 사용자의 최근 메시지 또는 버튼 응답 후 24시간이 지난 답장은 customer_service_window_closed로 중단되며 Meta에 요청이 전달되지 않습니다. 새 window를 열 수 있는 유일한 방법인 유료 템플릿 메시지는 현재 계약 범위 밖입니다. 사용자가 다시 메시지를 보내면 Signal Routing 페이지의 Retry로 중단된 답장을 보내세요.

##### 4. 규칙을 해당 전화번호에 유지

채팅은 그것을 수신한 전화번호에 바인딩됩니다. 규칙을 다른 번호로 옮기면 이전 채팅에 대한 답장은 사용자가 메시지를 보낸 적 없는 번호에서 나가는 대신 binding_phone_number_mismatch로 중단됩니다. 원래 번호를 복원하면 해제됩니다.

### Email

Ankole은 전용 메일함을 IMAP으로 읽고 Agent의 답장을 SMTP로 보냅니다. 각 이메일 thread가 하나의 대화입니다. 메일함은 Agent의 것이며, 어떤 사람도 다른 메일 클라이언트로 읽어서는 안 됩니다.

> **Email은 일부 Ankole 기능을 제한합니다**
>
> 다른 메일 클라이언트가 읽음으로 표시한 메시지는 Agent에 도달하지 않으므로 메일함은 전용이어야 합니다. Agent 답장은 실시간 진행 상황, 편집, 버튼이 없는 일반 텍스트 이메일 하나입니다. 확인 질문은 번호가 붙은 텍스트로 도착하고 사람은 일반 답장으로 응답합니다. 비밀번호와 앱 비밀번호 로그인만 지원하므로 Gmail에는 앱 비밀번호가 필요하고, OAuth가 필요한 Exchange Online 메일함은 사용할 수 없습니다.
>
> control plane이 IMAP과 SMTP 서버에 직접 연결할 수 있어야 합니다. HTTP egress proxy는 이를 다루지 않습니다.

**시작하기 전에**

- IMAP과 SMTP 접근이 가능하고 비밀번호 또는 앱 비밀번호가 있는 전용 메일함
- DMARC 결과가 포함된 Authentication-Results header를 추가하는 메일 서버, 또는 외부 메일을 받지 않는 사설 메일 서버

#### 기본 설정 · 전용 메일함 준비

##### 1. 메일함과 비밀번호 생성

Agent만 사용하는 메일함을 만들고 IMAP과 SMTP를 활성화하세요. provider가 요구하는 경우 계정 비밀번호 대신 Ankole용 앱 비밀번호를 만드세요.

> 이 메일함을 다른 메일 클라이언트에서 열지 마세요. 다른 클라이언트가 읽음으로 표시한 메시지는 Agent에 보이지 않습니다.

##### 2. 서버 설정 수집

Ankole은 IMAP에 implicit TLS로 연결하며 보통 포트 993을 사용합니다. SMTP는 STARTTLS(보통 포트 587) 또는 implicit TLS(보통 포트 465)를 사용합니다. Ankole은 서버 인증서를 시스템 CA 저장소로 검증합니다.

##### 3. Console 필드 수집

- `address`: `agent@example.com` — 메일함 주소입니다. 답장은 이 주소에서 발송됩니다.
- `displayName`: `Ankole Agent` — 발신 메일의 보낸 사람 이름입니다.
- `imapHost`: `imap.example.com` — IMAP 서버 호스트입니다.
- `imapPort`: `993` — IMAP 포트입니다. Ankole은 implicit TLS를 사용합니다.
- `smtpHost`: `smtp.example.com` — SMTP 서버 호스트입니다.
- `smtpPort`: `587` — SMTP 포트입니다. STARTTLS에는 587을, TLS에는 465를 사용하세요.
- `smtpSecurity`: `starttls` — SMTP 포트에 맞는 starttls 또는 tls입니다.
- `username` — 로그인 이름이며 보통 메일함 주소입니다. 하나의 IMAP 호스트와 username 조합은 활성화된 라우팅 규칙 하나에만 속할 수 있습니다.
- `password` — 메일함 비밀번호 또는 앱 비밀번호입니다. Ankole은 이를 암호화하여 저장합니다.
- `senderAuthentication`: `dmarc` — dmarc를 유지하세요. 외부 메일을 받지 않는 사설 메일 서버에서만 none으로 설정하세요.

##### 4. 테스트 이메일 전송

본인 주소에서 메일함으로 메일을 보내세요. 본인 주소가 아직 계정에 연결되지 않았다면 매핑 안내를 답장으로 받습니다. 아이덴티티 → 대기 중인 매핑에서 주소를 연결하고 이메일을 다시 보내세요.

#### 고급 설정 · 발신자 identity, 발신자 인증, thread

이메일 발신자가 알려진 계정이 되는 방식과 Ankole이 thread와 대량 메일을 처리하는 방식을 확인하려면 펼치세요.

##### 1. 이메일 발신자가 계정이 되는 방식 확인

인터넷의 누구나 메일함에 메일을 보낼 수 있고 From 주소만으로는 아무것도 증명되지 않으므로, Ankole은 이메일 발신자를 프로필 이메일이나 로컬 로그인 이메일로 매칭하지 않습니다. 발신자는 명시적인 이메일 identity 바인딩을 통해서만 알려집니다. 디렉터리 동기화와 provider 로그인은 provider가 알려준 주소를 바인딩하므로 동기화된 디렉터리의 직원은 수동 단계 없이 허용되며, 그 밖의 주소는 관리자가 아이덴티티 → 대기 중인 매핑에서 바인딩합니다. 독립 계정 자동 생성은 주소를 식별자로 하는 계정을 만들므로 열린 메일함에서만 사용하세요.

##### 2. 발신자 인증 이해

senderAuthentication이 dmarc이면 Ankole은 수신 메일 서버가 추가한 Authentication-Results header를 읽고 From 주소의 도메인에 대해 dmarc=pass가 보고된 경우에만 메시지를 받습니다. 실패한 메시지는 안내 없이 무시됩니다. 메일 서버가 사설 네트워크에 있고 외부 메일을 받지 않는 경우에만 none으로 설정하세요.

##### 3. thread와 대량 메일 처리 방식 확인

- thread는 제목이 아니라 In-Reply-To와 References header로 배치됩니다. Agent의 답장에는 메일 클라이언트가 사용하는 thread header가 포함됩니다.
- 참여자가 발신자와 메일함만인 thread는 1:1 대화입니다. 다른 수신자가 있으면 그룹 대화가 되며, 메일함을 Cc에만 넣은 메시지는 Agent를 지목하지 않습니다.
- 알려진 thread의 메시지에서는 답장 구분선 아래의 인용 텍스트가 제거됩니다. thread의 첫 메시지와 전달된 메시지는 본문 전체를 유지합니다.
- 메일함 자신이 보낸 메일, 자동 발송 메일과 대량 메일, 메일링 리스트 메일은 안내 없이 무시됩니다.
- 25 MB보다 큰 메시지는 header만 도착합니다. 발신 첨부 파일은 메시지당 20 MB로 제한됩니다.

### Console에서 연결 완료

현재 릴리스는 직접 연결을 사용합니다. 하나의 라우팅 규칙이 하나의 Channel Provider를 하나의 Agent에 연결합니다. 규칙은 adapter, 앱 자격 증명, 대상 Agent, 그룹 메시지 동작을 기록합니다. bot마다 별도의 규칙을 만드세요.

Console에서 **Signal Routing → New routing rule**을 열고 설정합니다:

| 필드 | 선택할 값 |
|---|---|
| **Target Agent** | 3단계에서 만든 Agent |
| **Adapter** | Slack, Teams, Lark/Feishu, DingTalk, WeCom, Telegram, Discord, LINE, WhatsApp 또는 Email |
| **Rule name** | `slack-main` 또는 `lark-main` 같은 안정적인 이름 |
| **Group message mode** | `addressed_only`로 시작 |
| **Channel settings** | 채널 tab의 자격 증명과 값을 붙여 넣기 |

규칙을 저장합니다. 목록에 활성화된 상태로 표시되어야 합니다. 양식이 credential을 거부하면 IM을 테스트하기 전에 그 오류를 고치세요. adapter가 아직 연결을 열지 않았습니다.

> **💡 Did you know?**
>
> 오늘날 하나의 라우팅 규칙은 하나의 시그널 소스를 하나의 Agent로 보냅니다. 채팅 앱이 가장 일반적인 소스입니다. 향후 규칙은 채널, 대화 또는 다른 조건으로 Agent를 선택할 수 있습니다. Salesforce 같은 외부 시스템도 Agent가 처리할 이벤트를 보낼 수 있게 됩니다. 이 모듈은 채팅 채널뿐 아니라 Agent 작업을 시작할 수 있는 모든 시그널을 다루기 때문에 Signal Routing이라고 부릅니다.

#### 고급 설정 · 그룹 동작과 identity 매핑

첫 설정에서는 Agent를 지명한 메시지에만 응답하게 하세요.

##### 1. Agent가 지명되지 않은 그룹 메시지를 처리하는 방식 선택

- `addressed_only` — Agent를 지명하지 않은 그룹 메시지를 무시합니다. 첫 테스트에서 사용하세요.
- `observe_all` — Agent turn을 시작하지 않고 지명되지 않은 메시지를 context로 저장합니다.
- `may_intervene` — Agent가 지명되지 않은 대화에 참여할지 스스로 결정하게 합니다.

> Provider가 해당 메시지를 전달해야 합니다. DingTalk와 WeCom은 addressed_only만 지원합니다. Slack과 Lark는 전체 방을 관찰하려면 추가 이벤트나 scope가 필요합니다.

##### 2. namespace를 identity 매핑 키로 취급

provider 조직마다 platformSubjectNamespace를 하나 사용하세요. IdP와 Channel Provider 레코드 사이에서 재사용하는 것은 둘 다 같은 조직을 가리킬 때만입니다.

## 5. IM에서 Agent와 대화

테스트 대화에 bot을 추가하세요. 그룹 채팅에서는 명시적 @멘션으로 시작하세요:

> @Ankole 무엇을 할 수 있고, 어떤 팀을 서빙하나요?

첫 실제 모델 답변이 온 뒤, 팀에 맞게 Agent mission, 모델, 그룹 채팅 정책을 조정하세요.

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

### Agent가 응답하지 않으면

한 번에 한 경계씩, 다음 순서로 확인하세요:

1. 최신 provider 앱 버전이 게시되어 테스트 사용자가 사용할 수 있습니다.
2. bot이 채널, 팀 또는 대화에 설치되어 있습니다.
3. 필요한 메시지 이벤트와 scope가 활성화되어 있습니다.
4. 라우팅 규칙이 활성화되어 의도한 Agent를 가리킵니다.
5. Agent에 `primary`, `light`, `heavy` model profile이 있습니다.
6. LLM Provider 자격 증명과 모델 선택기가 유효합니다.
7. 최소 하나의 Worker가 준비 상태입니다.

Compose에서는 `docker compose logs -f control-plane worker`를 확인하세요. Kubernetes에서는 control-plane과 Worker pod 로그를 확인합니다. 관련 오류만 읽고 환경 변수나 secret을 출력하지 마세요.

답변이 도착하면 [Agents](https://ankole.agentbull.com/ko-KR/docs/agents/index.md), [Signal 라우팅 규칙](https://ankole.agentbull.com/ko-KR/docs/signal-bindings/index.md) 또는 [Background Agent Jobs](https://ankole.agentbull.com/ko-KR/docs/background-jobs/index.md)로 계속하세요.
