-
에이전트가 답만 뱉던 시절은 끝났다 — OpenClaw가 message 도구로 능동 발신과 채널 액션을 연 방법IT 2026. 7. 15. 23:00
오픈소스 개인 AI 어시스턴트 OpenClaw를 텔레그램·슬랙에 붙여 쓰다 보면 자연스럽게 한 가지를 기대하게 된다. "에이전트가 먼저 말을 걸 수도 있으면 좋겠다." 보고서가 완성되면 알아서 공지를 띄우고, 위험한 작업 전엔 "이거 진행할까요?" 하고 승인 버튼을 보내고, 그래프 이미지를 첨부로 던지고, 때로는 답을 다른 채널로 보내는 것 — 이 모든 게 한 번의 답변 텍스트 반환만으로는 불가능하다. OpenClaw는 이걸
message라는 도구 하나로 푼다. 에이전트가 인바운드 채널(메시지가 들어온 통로)로 답장을 보내거나, 공지·승인 요청·웹앱 액션·첨부 전송 같은 채널 액션을 능동적으로 수행하게 하는 도구다. 이 글은 그 도구가 왜 필요했고, 어떤 구조로 풀었으며, 도구 프로필에서 messaging이 빠지면 어떤 함정에 빠지는지를 따라간다.배경 — "답변 텍스트 반환"만으로는 부족한 순간들
가장 단순한 에이전트 설계에서, 모델은 사용자 질문을 받아 답변 텍스트 하나를 돌려준다. 호스트가 그 텍스트를 받아 메시지가 들어온 채널로 되돌려보낸다. 이걸 OpenClaw 문서는
message_tool_only정책에서 "현재 소스 채널로의 최종 가시 응답(visible reply)"이라고 부른다 — 사용자가 텔레그램에서 물으면 답이 텔레그램으로 돌아가는 결정적(deterministic, 모델이 고르는 게 아니라 규칙이 정하는) 흐름이다.다이어그램 설명. 이 그림은 "답변 텍스트 하나만 반환"하는 가장 단순한 에이전트 흐름을, 사용자 질문에서 채널 회신까지 보여준다. 질문이 한 채널로 들어오면 에이전트가 한 턴을 돌려 텍스트 한 덩이를 내놓고, 그게 들어온 그 채널로 되돌아가는 것으로 대화가 닫힌다. 이 패턴을 쓰는 이유는 단순함과 안전 — 모델이 마음대로 아무 채널에나 메시지를 뿌리지 못하게 막는다. 하지만 '여기서 끝' 노드가 말하듯, 이 구조에서는 한 턴에 한 덩이의 텍스트가 전부다. 놓치기 쉬운 함정은 "그 정도면 챗봇으로 충분하지 않나"라는 직관이다 — 질문에 답만 하는 봇이라면 충분하다. 그러나 에이전트가 능동적으로 일하기 시작하면(긴 작업을 단계별로 중계하고, 결과 이미지를 보내고, 위험한 행동 전 승인을 받고, 한 채널의 알림을 다른 채널로 라우팅하는 순간) 이 "한 턴 한 덩이" 모델은 곧바로 벽에 부딪힌다.
핵심 문제 — 능동적 소통을 막는 네 가지 벽
"답변 텍스트 하나 반환"만 가능한 에이전트는 실제 업무 시나리오에서 정확히 네 군데에서 막힌다. 각각이 사용자가 봇에게 기대하는 평범한 동작인데도 그렇다.
다이어그램 설명. 이 그림은 단일 텍스트 반환 모델이 부딪히는 네 가지 벽을 보여준다. 첫째, 긴 작업을 하면서 "1단계 끝, 2단계 시작" 같은 여러 개의 메시지를 나눠 보낼 수 없다(한 턴에 한 덩이뿐이라). 둘째, 차트 이미지나 PDF 같은 첨부를 답변에 실을 수 없다(텍스트만 돌려주니까). 셋째, "진행할까요? [예] [아니오]" 같은 승인 버튼이나 웹앱을 여는 채널 액션을 보낼 수 없다. 넷째, 슬랙에서 시킨 작업의 결과 알림을 텔레그램 개인 DM으로 보내는 식의 다른 채널 전송이 불가능하다. 이 네 가지가 핵심인 이유는 모두 "에이전트가 수동적으로 답만 한다"의 반대편 — 능동적으로 발신한다는 점에서 같은 뿌리를 갖기 때문이다. 함정은 이 넷을 따로따로 기능 추가로 보는 것이다. 실제로는 "에이전트가 답변 반환값을 넘어 채널에 직접 행동할 수 있는가"라는 단 하나의 능력 문제이고, OpenClaw는 그래서 네 개의 기능이 아니라 도구 하나로 묶어 푼다.
해결 방법 — message 도구와 채널 액션으로 능동 발신을 연다
OpenClaw는 이 네 벽을
message라는 도구(tool — 모델이 직접 호출할 수 있는, 타입이 정해진 함수) 하나로 무너뜨린다. 도구 개요 문서는message를 "에이전트가 답장이나 채널 액션을 보내야 할 때 쓰는" 메시징·채널 카테고리의 대표 도구로 분류한다. 핵심 전환은 이렇다 — 답변이 반환값(턴이 끝나며 한 번 돌려주는 결과)이 아니라, 에이전트가 턴 도중 원할 때 원하는 만큼 호출하는 행동이 된다.다이어그램 설명. 이 그림은 같은 에이전트가 한 턴 안에서
message도구를 여러 번 호출해 능동적으로 발신하는 모습을 보여준다. 에이전트에서 네 갈래로 갈라지는 호출이 각각 — 진행 상황 텍스트 보내기, 그래프 이미지 첨부해 보내기, 승인 버튼 같은 채널 액션 보내기, 답을 다른 채널(텔레그램)로 라우팅하기 — 이고, 모두 채널 어댑터로 모여 각 채널이 이해하는 형식으로 변환되어 나간다. 이 패턴을 쓰는 이유는 앞서 본 네 벽이 사실 "에이전트가 채널에 능동적으로 행동할 수 있는가"라는 한 문제였기 때문이다 — 그래서 답도 도구 하나로 통일된다. 흐름을 읽을 때 핵심은 화살표가 여러 개라는 점이다. 단일 반환값 모델에서는 에이전트→답이 화살표 하나였는데, 여기서는 에이전트가 턴 도중 필요할 때마다 호출을 쏜다. 놓치기 쉬운 함정은 "이러면 모델이 아무 채널에나 마음대로 메시지를 뿌리는 거 아니냐"는 우려인데, 다음 절에서 보듯 이 능력은 도구 프로필과 채널 권한으로 게이트(gate, 통과 여부를 거르는 관문)된다.채널 액션 — 텍스트를 넘어 버튼·웹앱·승인까지
message가 단순히 "텍스트를 여러 번 보낸다"에서 그쳤다면 그건 출력 통로를 하나 더 연 것에 불과하다. 진짜 도약은 채널 액션이다 — 텍스트가 아닌 구조화된 상호작용(버튼, 웹앱 열기, 승인 요청, 선택지)을 채널이 이해하는 형태로 보내는 것. 단, OpenClaw 아키텍처 규칙상 채널은 전송만 담당하고 제품 로직을 갖지 않는다. 그래서 액션은 채널이 문자열을 보고 추측하는 게 아니라, 코어/플러그인이 타입이 정해진 표현 액션(typed presentation action)으로 선언하고 채널 어댑터가 그걸 자기 방식으로 매핑한다.다이어그램 설명. 이 그림은 에이전트가 보내려는 액션이 종류별로 갈라진 뒤 채널 어댑터에서 합쳐져 각 채널 형식으로 변환되는 흐름을 보여준다. '액션 의도 선언' 단계에서 분기하여 승인·명령·웹앱·선택 중 하나로 갈라지고, 네 갈래 모두 채널 어댑터로 모여 실제 표현(텔레그램이면 인라인 버튼)으로 매핑된다. 이 구조가 핵심인 이유는 OpenClaw 규칙이 못 박은 두 가지 때문이다 — 첫째, 채널은 "전송 전용"이라 제품 명령 트리나 메뉴를 소유하지 않는다. 둘째, 승인·명령·URL·웹앱·선택 액션은 채널 인코딩 전까지 서로 구별돼야 한다는 규칙이 있어, 채널이 문자열을 보고 "이 값이 슬래시로 시작하니 명령이겠지" 하고 추측하지 못하게 막는다. 놓치기 쉬운 함정은 "버튼은 채널이 알아서 만드는 것"이라는 오해다 — 실제로는 코어가 타입으로 선언하고 채널은 그저 매핑할 뿐이며, 채널이 그 액션을 지원하지 않으면(원시 콜백을 못 받으면) 일반 텍스트로 폴백(fallback, 능력이 모자랄 때 한 단계 낮은 형식으로 떨어뜨림)한다. 그래서 같은 "승인 요청"이 버튼을 지원하는 채널엔 버튼으로, 아닌 채널엔 텍스트로 일관되게 나간다.
CLI로도 같은 능력 — openclaw agent --deliver
이 능동 발신은 대화 안에서만 일어나는 게 아니다. 인바운드 채팅 메시지 없이 스크립트나 명령줄에서 에이전트 턴을 돌리고 그 답을 채널로 보내는 길도 있다.
openclaw agent명령이 그것이며,--deliver플래그를 붙이면 결과를 채널로 전달한다 — cron 작업, 모니터링 알림, 테스트 같은 프로그래밍 방식 발신에 쓴다.# 들어온 채널과 다른 채널로 결과를 보낸다 # ops 에이전트로 턴을 돌리고, 그 결과를 텔레그램 @admin 으로 라우팅 openclaw agent --agent ops --message "Alert" \ --deliver \ # 결과를 채널로 전송 (안 붙이면 그냥 출력만) --reply-channel telegram \ # 들어온 채널이 아니라 텔레그램으로 덮어쓰기 --reply-to "@admin" # 전달 대상 지정코드 설명. 이 명령은 대화 밖에서 능동 발신을 일으키는 실제 예시다. ops 에이전트로 한 턴을 돌린 결과를, 들어온 채널이 아니라
--reply-channel telegram으로 다른 채널에 라우팅해 보낸다 — 앞서 본 "벽 4 — 다른 채널 불가"를 정확히 무너뜨리는 동작이다.--deliver를 빼면 결과가 전송되지 않고 화면 출력만 되므로, 채널로 실제 보내려면 이 플래그가 필수다.함정 — 도구 프로필에서 messaging이 빠지면
여기까지 보면 "
message도구가 있으니 첨부도 답장도 다 되겠지"라고 생각하기 쉽다. 그러나 OpenClaw에서 모델이 어떤 도구를 보느냐는 도구 프로필(tool profile)과 정책이 모델 호출 전에 결정한다. 도구 정책 문서는 못 박는다 — "정책이 도구를 제거하면 그 턴에 모델은 그 도구의 스키마를 받지 못한다." 즉 messaging 카테고리가 활성 프로필에서 빠지면, 모델은message도구 자체를 못 보고 첨부·답장·채널 액션이 통째로 막힌다.다이어그램 설명. 이 그림은 한 턴이 시작될 때 도구가 정책 관문을 거쳐 모델에게 보일지 말지가 갈리는 흐름을 보여준다. '에이전트 턴 시작' 단계에서 관문(활성 프로필·허용/거부 목록·채널 권한·플러그인 가용성)을 통과하는데, 메시징이 포함되면 모델이 도구를 보고 능동 발신이 열리고, 빠지면 도구가 아예 안 보여 첨부와 답장이 통째로 막힌다. 이 패턴이 중요한 이유는 도구가 "있는데 작동 안 함"이 아니라 "모델 눈에 처음부터 존재하지 않음"이기 때문이다 — 정책 필터링은 모델 호출 전에 일어나므로, 프로필에서 빠진 도구의 스키마는 프롬프트에 들어가지도 않는다. 봇이 답장을 못 하거나 첨부를 못 보낼 때, 원인은 코드 버그보다 활성 프로필에 messaging이 빠졌거나 채널 권한이 막힌 정책 문제인 경우가 많다. 능동 발신을 켜는 것과 똑같이, 끄는 것도 정책 한 줄이다.
결과 — 무엇이 좋아졌나
message도구가 들어오면서, 앞서 본 네 가지 벽이 이렇게 정리된다.이전 (답변 텍스트 1개 반환) 이후 (message 도구 + 채널 액션) 한 턴에 한 덩이 — 진행 상황을 나눠 못 보냄 턴 도중 message를 여러 번 호출해 단계별 중계 텍스트만 — 이미지·PDF 첨부 불가 첨부를 실어 보냄 (채널 능력에 맞춰 변환) 버튼·승인·웹앱 같은 리치 액션 불가 타입 선언된 채널 액션 → 채널별 매핑/폴백 들어온 채널로만 답해야 함 reply-channel로 다른 채널에 라우팅 (CLI 포함) 정리하면, OpenClaw의
message도구는 에이전트를 "질문에 답만 하는 챗봇"에서 "채널에 능동적으로 행동하는 행위자"로 바꾸는 단 하나의 전환점이다. 답이 턴 끝의 반환값이 아니라 턴 도중의 행동이 되면서, 같은 에이전트가 진행 상황을 단계별로 중계하고, 결과 이미지를 첨부하고, 위험한 작업 전 승인 버튼을 띄우고, 알림을 다른 채널로 라우팅한다 — 그리고 이 모든 능력은 도구 프로필에서 messaging을 넣고 빼는 것만으로 켜고 끌 수 있다. 결국 이건 "AI가 무엇을 아느냐"만큼이나 "그 앎을 가지고 채널에서 무엇을 할 수 있느냐"가 에이전트의 쓸모를 결정한다는 설계 판단이다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
외부 시스템마다 도구를 손으로 만들면 안 끝난다 — OpenClaw가 MCP로 남의 도구를 꽂아 쓰는 법 (0) 2026.07.17 텍스트만 뱉는 에이전트는 그림을 못 그린다 — OpenClaw가 미디어 생성을 프로바이더 추상화로 푼 방법 (1) 2026.07.17 지금 시키는 일만 하던 에이전트가 미래의 자기에게 일을 거는 법 — OpenClaw cron 도구 (1) 2026.07.16 세션이 끝나면 다 잊어버리는 AI — OpenClaw가 memory 도구로 의미 기반 기억을 만든 방법 (0) 2026.07.16 큰 작업을 혼자 떠안지 않는 에이전트 — OpenClaw가 하위 작업을 격리 세션으로 떼어내는 법 (0) 2026.07.16 LLM에게 파일을 통째로 다시 쓰게 두면 안 되는 이유 — OpenClaw가 파일 편집을 4개 도구로 쪼갠 방법 (0) 2026.07.15 AI 에이전트에게 셸을 쥐여 주는 순간 — OpenClaw가 exec 실행과 승인 게이트로 위험을 길들인 방법 (0) 2026.07.15 fetch로는 로그인·동적 페이지를 못 읽는다 — OpenClaw가 격리 브라우저로 푼 방법 (0) 2026.07.14 페이지 한 장 읽자고 브라우저를 통째로 띄울 건가 — OpenClaw가 web_fetch로 본문만 가볍게 긁는 방법 (0) 2026.07.14 LLM은 어제 일을 모른다 — OpenClaw가 에이전트에 웹 검색을 붙인 방법 (0) 2026.07.14