Discord 채널 및 Telegram 토픽을 위한 ACP 영구 바인딩
상태: 초안
요약
다음을 매핑하는 영구 ACP 바인딩을 도입합니다:
- Discord 채널(및 필요 시 기존 스레드)
- Telegram 그룹/슈퍼그룹의 포럼 토픽(
chatId:topic:topicId)
이를 장기 ACP 세션에 매핑하며, 바인딩 상태는 명시적 바인딩 타입을 사용하는 최상위 bindings[] 항목에 저장됩니다.
이를 통해 고트래픽 메시징 채널에서의 ACP 사용을 예측 가능하고 안정적으로 만들어, 사용자가 codex, claude-1, claude-myrepo 같은 전용 채널/토픽을 생성할 수 있습니다.
배경
현재 스레드 기반 ACP 동작은 일시적인 Discord 스레드 워크플로에 최적화되어 있습니다. Telegram은 동일한 스레드 모델이 없으며, 그룹/슈퍼그룹의 포럼 토픽이 있습니다. 사용자는 임시 스레드 세션뿐 아니라, 채팅 인터페이스에서 안정적이고 항시 활성화된 ACP “워크스페이스”를 원합니다.
목표
- 다음에 대한 영구 ACP 바인딩 지원:
- Discord 채널/스레드
- Telegram 포럼 토픽(그룹/슈퍼그룹)
- 바인딩 진실의 원천을 설정 기반으로 유지.
/acp,/new,/reset,/focus및 전달 동작을 Discord와 Telegram 간에 일관되게 유지.- 임시 사용을 위한 기존 임시 바인딩 흐름 보존.
비목표
- ACP 런타임/세션 내부의 전면 재설계.
- 기존 임시 바인딩 흐름 제거.
- 첫 번째 반복에서 모든 채널로 확장.
- 이 단계에서 Telegram 채널 다이렉트 메시지 토픽(
direct_messages_topic_id) 구현. - 이 단계에서 Telegram 비공개 채팅 토픽 변형 구현.
UX 방향
1) 두 가지 바인딩 타입
- 영구 바인딩: 설정에 저장, 시작 시 조정, “명명된 워크스페이스” 채널/토픽용.
- 임시 바인딩: 런타임 전용, 유휴/최대 기간 정책에 의해 만료.
2) 명령 동작
/acp spawn ... --thread here|auto|off는 계속 사용 가능.- 명시적 바인딩 생명주기 제어 추가:
/acp bind [session|agent] [--persist]/acp unbind [--persist]/acp status에 바인딩이persistent인지temporary인지 표시.
- 바인딩된 대화에서
/new와/reset은 바인딩된 ACP 세션을 제자리에서 초기화하고 바인딩은 유지.
3) 대화 식별
- 정규 대화 ID 사용:
- Discord: 채널/스레드 ID.
- Telegram 토픽:
chatId:topic:topicId.
- Telegram 바인딩을 단독 토픽 ID로만 키로 사용하지 않음.
설정 모델 (제안)
최상위 bindings[]에 명시적 type 구분자를 사용하여 라우팅과 영구 ACP 바인딩 설정을 통합:
{
"agents": {
"list": [
{
"id": "main",
"default": true,
"workspace": "~/.openclaw/workspace-main",
"runtime": { "type": "embedded" },
},
{
"id": "codex",
"workspace": "~/.openclaw/workspace-codex",
"runtime": {
"type": "acp",
"acp": {
"agent": "codex",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-a",
},
},
},
{
"id": "claude",
"workspace": "~/.openclaw/workspace-claude",
"runtime": {
"type": "acp",
"acp": {
"agent": "claude",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
},
],
},
"acp": {
"enabled": true,
"backend": "acpx",
"allowedAgents": ["codex", "claude"],
},
"bindings": [
// 라우트 바인딩 (기존 동작)
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
// 영구 ACP 대화 바인딩
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
"acp": {
"label": "codex-main",
"mode": "persistent",
"cwd": "/workspace/repo-a",
"backend": "acpx",
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
"acp": {
"label": "claude-repo-b",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1001234567890:topic:42" },
},
"acp": {
"label": "tg-codex-42",
"mode": "persistent",
},
},
],
"channels": {
"discord": {
"guilds": {
"111111111111111111": {
"channels": {
"222222222222222222": {
"enabled": true,
"requireMention": false,
},
"333333333333333333": {
"enabled": true,
"requireMention": false,
},
},
},
},
},
"telegram": {
"groups": {
"-1001234567890": {
"topics": {
"42": {
"requireMention": false,
},
},
},
},
},
},
}
최소 예시 (바인딩별 ACP 오버라이드 없음)
{
"agents": {
"list": [
{ "id": "main", "default": true, "runtime": { "type": "embedded" } },
{
"id": "codex",
"runtime": {
"type": "acp",
"acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" },
},
},
{
"id": "claude",
"runtime": {
"type": "acp",
"acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" },
},
},
],
},
"acp": { "enabled": true, "backend": "acpx" },
"bindings": [
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1009876543210:topic:5" },
},
},
],
}
참고사항:
bindings[].type은 명시적:route: 일반 에이전트 라우팅.acp: 매칭된 대화에 대한 영구 ACP 하네스 바인딩.
type: "acp"의 경우,match.peer.id가 정규 대화 키:- Discord 채널/스레드: 원시 채널/스레드 ID.
- Telegram 토픽:
chatId:topic:topicId.
bindings[].acp.backend은 선택 사항. 백엔드 폴백 순서:bindings[].acp.backendagents.list[].runtime.acp.backend- 전역
acp.backend
mode,cwd,label은 동일한 오버라이드 패턴을 따름 (바인딩 오버라이드 -> 에이전트 런타임 기본값 -> 전역/기본 동작).- 임시 바인딩 정책을 위해 기존
session.threadBindings.*및channels.discord.threadBindings.*유지. - 영구 항목은 원하는 상태를 선언; 런타임이 실제 ACP 세션/바인딩으로 조정.
- 대화 노드당 하나의 활성 ACP 바인딩이 의도된 모델.
- 하위 호환성:
type이 없는 항목은 레거시 항목으로route로 해석.
백엔드 선택
- ACP 세션 초기화는 이미 spawn 중 구성된 백엔드 선택을 사용 (현재
acp.backend). - 이 제안은 spawn/조정 로직을 확장하여 타입화된 ACP 바인딩 오버라이드를 우선시:
bindings[].acp.backend: 대화별 로컬 오버라이드.agents.list[].runtime.acp.backend: 에이전트별 기본값.
- 오버라이드가 없으면 현재 동작 유지 (
acp.backend기본값).
현재 시스템에서의 아키텍처 적합성
기존 컴포넌트 재사용
SessionBindingService가 이미 채널 무관 대화 참조를 지원.- ACP spawn/bind 흐름이 이미 서비스 API를 통한 바인딩을 지원.
- Telegram이 이미
MessageThreadId와chatId를 통해 토픽/스레드 컨텍스트를 전달.
신규/확장 컴포넌트
- Telegram 바인딩 어댑터 (Discord 어댑터와 병렬):
- Telegram 계정별 어댑터 등록,
- 정규 대화 ID로 조회/목록/바인딩/언바인딩/터치.
- 타입화된 바인딩 리졸버/인덱스:
bindings[]를route와acp뷰로 분리,resolveAgentRoute는route바인딩에만 유지,acp바인딩에서만 영구 ACP 의도 해석.
- Telegram 인바운드 바인딩 해석:
- 라우트 확정 전에 바인딩된 세션 해석 (Discord는 이미 수행).
- 영구 바인딩 조정기:
- 시작 시: 설정된 최상위
type: "acp"바인딩 로드, ACP 세션 존재 확인, 바인딩 존재 확인. - 설정 변경 시: 안전하게 델타 적용.
- 시작 시: 설정된 최상위
- 전환 모델:
- 채널 로컬 ACP 바인딩 폴백을 읽지 않음,
- 영구 ACP 바인딩은 최상위
bindings[].type="acp"항목에서만 소싱.
단계적 전달
1단계: 타입화된 바인딩 스키마 기반
- 설정 스키마를 확장하여
bindings[].type구분자 지원:route,acp와 선택적acp오버라이드 객체 (mode,backend,cwd,label).
- 에이전트 스키마를 런타임 디스크립터로 확장하여 ACP 네이티브 에이전트 표시 (
agents.list[].runtime.type). - route 대 ACP 바인딩을 위한 파서/인덱서 분리 추가.
2단계: 런타임 해석 + Discord/Telegram 동등성
- 최상위
type: "acp"항목에서 영구 ACP 바인딩 해석:- Discord 채널/스레드,
- Telegram 포럼 토픽 (
chatId:topic:topicId정규 ID).
- Telegram 바인딩 어댑터 구현 및 Discord와 인바운드 바인딩 세션 오버라이드 동등성 구현.
- 이 단계에서 Telegram 다이렉트/비공개 토픽 변형은 포함하지 않음.
3단계: 명령 동등성 및 리셋
- 바인딩된 Telegram/Discord 대화에서
/acp,/new,/reset,/focus동작 정렬. - 설정된 대로 리셋 흐름에서 바인딩이 유지되도록 보장.
4단계: 강화
- 더 나은 진단 (
/acp status, 시작 시 조정 로그). - 충돌 처리 및 상태 점검.
가드레일 및 정책
- 현재와 동일하게 ACP 활성화 및 샌드박스 제한 준수.
- 교차 계정 유출 방지를 위해 명시적 계정 범위 지정(
accountId) 유지. - 모호한 라우팅 시 폐쇄적 실패.
- 채널 설정별 멘션/접근 정책 동작을 명시적으로 유지.
테스트 계획
- 단위:
- 대화 ID 정규화 (특히 Telegram 토픽 ID),
- 조정기 생성/업데이트/삭제 경로,
/acp bind --persist및 unbind 흐름.
- 통합:
- 인바운드 Telegram 토픽 -> 바인딩된 ACP 세션 해석,
- 인바운드 Discord 채널/스레드 -> 영구 바인딩 우선순위.
- 회귀:
- 임시 바인딩 계속 작동,
- 바인딩되지 않은 채널/토픽은 현재 라우팅 동작 유지.
미결 질문
- Telegram 토픽에서
/acp spawn --thread auto가 기본적으로here여야 하는가? - 영구 바인딩은 바인딩된 대화에서 항상 멘션 게이팅을 우회해야 하는가, 아니면 명시적
requireMention=false가 필요한가? /focus가/acp bind --persist의 별칭으로--persist를 가져야 하는가?
출시
- 대화별 옵트인으로 출시 (
bindings[].type="acp"항목 존재). - Discord + Telegram만으로 시작.
- 다음 예시와 함께 문서 추가:
- “에이전트당 하나의 채널/토픽”
- “다른
cwd를 가진 동일 에이전트에 여러 채널/토픽” - “팀 명명 패턴 (
codex-1,claude-repo-x)”.