---
title: "SignalsGateway"
description: "공유 작업(shared work)의 수신 계층 — 채팅, 웹훅, 프로바이더 이벤트가 소스 사실을 실행 상태로 만들지 않고 액터 이벤트가 되는 방식을 설명합니다."
url: "https://ankole.agentbull.com/ko-KR/docs/signals-gateway/"
lang: "ko-KR"
---

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

# SignalsGateway

SignalsGateway는 공유 작업의 진입 표면입니다. 한쪽에서 채팅 메시지, 웹훅, 프로바이더 이벤트, 예약된 알림이 들어오면, 다른 쪽에서는 세션을 깨울 준비가 된 정규화된 durable 액터 이벤트가 나옵니다. 게이트웨이의 역할은 다양한 프로바이더를 하나의 형태로 바꾸고, 원래 프로바이더 사실을 그 뒤에 따라오는 실행과 분리해 두는 것입니다.

이 페이지는 실제 수신 경로, 사용자용 라우팅 규칙 뒤에 있는 Signal Binding 모델, 그리고 미러링과 waking 사이의 경계를 설명합니다. 진실의 원천(source of truth)은 `Ankole.SignalsGateway` 모듈과 그 하위 모듈인 `Ingress`, `Projection`, `Bindings`입니다.

## 지키는 계약

모든 프로바이더에 공통으로 성립하는 두 가지 속성이 있으며, 이것이 게이트웨이가 별개의 계층으로 존재하는 이유입니다.

- **소스 사실은 사실로 남습니다.** 미러링된 항목(entry)은 프로바이더의 현재 모습 — 누가 어떤 채널에서 언제 무슨 말을 했는지 — 을 기록합니다. 이것은 실행 상태가 아닙니다. 에이전트의 turn은 이러한 사실 자체가 아니라 사실의 프로젝션(projection)을 대상으로 실행됩니다.
- **Waking은 조건부입니다.** 수용된 모든 사실이 액터를 깨우는 것은 아닙니다. 필터링된 시그널은 성공적인 no-op(`status: :filtered`)이며, 액터 런타임은 수용된 사실이 실제로 새 액터 이벤트를 만들어냈을 때만 시작됩니다.

이 분리가 중요한 이유는 프로바이더마다 동작이 다르고, 재시도하거나 재전송하며, 에이전트가 보아야 하지만 반응할 필요는 없는 이벤트를 보내기 때문입니다. 게이트웨이가 이러한 차이를 흡수함으로써 액터 런타임은 하나의 깨끗한 입력 스트림만 보게 됩니다.

## 수신 파이프라인

모든 수신 사실은 프로바이더와 무관하게 동일한 고정 파이프라인을 거칩니다.

1. **라우팅 규칙을 해석합니다.** 게이트웨이는 `agent_uid`와 `binding_name`으로 내부 Signal Binding을 조회합니다. 규칙이 없으면 경로가 없으므로 해당 사실은 거부됩니다.
2. **사실을 구성합니다.** 프로바이더 고유의 페이로드는 `FactNormalizer`를 통해 entry, reaction, action, lifecycle 유형의 사실로 정규화됩니다. “delete”나 “recall” 같은 프로바이더별 이름은 액터가 마주하는 하나의 종류로 합쳐집니다.
3. **라우팅 필터를 적용합니다.** 규칙의 필터가 이 사실이 범위 안에 있는지 결정합니다. 일치하지 않으면 `{:ok, %{status: :filtered}}`를 반환하는데, 이것은 오류가 아니라 성공입니다.
4. **수용하고 미러링합니다.** 수용된 사실은 채널 미러를 upsert하고 항목 프로젝션을 기록합니다. 여기서 프로바이더 사실이 durable한 행이 됩니다.
5. **필요할 때 액터 이벤트를 넘겨줍니다.** 수용된 사실이 액터를 깨워야 한다면 `ActorEvent` 행이 세션 큐에 추가됩니다. 예외는 reaction입니다. reaction은 미러만 갱신하고 액터 이벤트를 만들지 않습니다.

수용 과정의 잠금 순서는 고정되어 있습니다. 채널, 그다음 세션, 마지막으로 액터 이벤트 순서입니다. 따라서 같은 채널의 동시 사실은 결정적으로 처리됩니다.

## 수신 사실의 종류

게이트웨이는 `Ingress`를 통해 네 가지 구체적인 종류를 수용하며, 각각은 정규화된 액터 지향 계약에 매핑됩니다.

- **Entry** — 채널에 도착하는 메시지 또는 게시물. 기본적인 wake 경로입니다. IM 진입 정책은 수신자가 지정되지 않은 그룹 메시지가 `may_intervene` 이벤트(에이전트가 발언할 수 있음)를 만들지, `addressed` 이벤트(에이전트가 직접 호출됨)를 만들지 결정합니다. 개입 판단의 동작 방식, 답장 소속(reply attribution), 채널 상시 지시(standing order)는 [주변 개입](https://ankole.agentbull.com/ko-KR/docs/ambient-intervention/index.md)을 참고하세요.
- **Entry removed** — 프로바이더의 삭제 또는 회수(recall). 액터 지향 계약은 항상 `signal.entry.removed`이며, 프로바이더 고유의 라이프사이클 이름은 진단용으로만 유지됩니다.
- **Reaction** — 기존 항목에 대한 이모지 또는 투표 변경. 미러만 갱신하며 액터를 깨우지 않습니다. 게이트웨이가 미러링한 적 없는 항목에 대한 reaction은 무시됩니다(`:ignored_unknown_entry`). 오류로 취급되지 않습니다.
- **Action** — 카드 버튼 클릭 같은 프로바이더가 발생시킨 상호작용. 답장 상호작용 중복 제거를 거칩니다. 중복 클릭은 `:duplicate_action`, 오래된 클릭은 `:stale_action`을 반환하고, 수용된 클릭은 `signal.action.invoked` 이벤트가 됩니다.

## 채널, 항목, 답장 모드

**channel**은 항목이 존재하는 프로바이더 쪽 컨테이너입니다. IM 다이렉트 메시지나 그룹, 웹훅 엔드포인트, 이슈, 알림 스트림이 그 예입니다. 채널 행은 프로바이더 고유의 채널 id를 키로 하는 순수한 외부 사실 미러이므로, 이벤트마다 insert하는 대신 upsert로 기록됩니다. 채널 행은 `reply_mode` — `:none`, `:channel`, `:entry` — 를 기록하며, 아웃박스는 이 값을 읽고 답장을 새 채널 게시물로 보낼지 특정 항목에 대한 스레드 답장으로 보낼지 결정합니다.

**entry**는 채널 안의 콘텐츠 한 단위입니다. 메시지 하나, 게시물 하나, 이벤트 하나가 그 예입니다. 에이전트 turn이 읽는 것은 항목 프로젝션입니다. 액터 이벤트도 아니고, 소스 페이로드를 그대로 둔 것도 아닙니다.

## 라우팅 규칙 모델

Signal Binding으로 저장되는 시그널 라우팅 규칙은 운영자가 선택한 이름 아래에서 하나의 프로바이더 어댑터를 하나의 Agent에 연결합니다. 규칙은 Agent에 속하며 Console 범위의 라우트로 관리됩니다.

| 메서드 | 경로 | 용도 |
|---|---|---|
| `GET` | `/signal-adapters` | 이 배포 인스턴스가 선언한 어댑터 목록 |
| `GET` | `/signal-bindings` | 라우팅 규칙 목록(`?agent=`로 Agent 필터) |
| `PUT` | `/agents/:agent_uid/signal-bindings/:adapter_id/:binding_name` | 라우팅 규칙 생성 또는 교체 |
| `PATCH` | `/agents/:agent_uid/signal-bindings/:binding_name` | 라우팅 규칙 갱신 |
| `DELETE` | `/agents/:agent_uid/signal-bindings/:binding_name` | 라우팅 규칙 제거 |

규칙은 사용하는 어댑터, 구성 참조, 필터 규칙, 그룹 메시지 정책, `enabled` 플래그를 담습니다. 규칙을 비활성화하면 규칙을 삭제하지 않고도 새 사실이 해당 Actor를 깨우지 않게 됩니다. 사용할 수 없게 된 규칙은 `unavailable_reason`을 기록하므로 운영자는 규칙이 멈춘 이유를 확인할 수 있습니다.

어댑터는 하드코딩되지 않습니다. 부팅 시 `signals_gateway.adapter` 계약 아래 플러그인 레지스트리에서 해석되므로, 사용 가능한 프로바이더 집합은 이 배포 인스턴스의 플러그인이 선언하는 대로 결정됩니다. 어떤 선언도 제공하지 않는 어댑터 id에 대한 요청은 `signal_adapter_not_found`를 반환합니다.

## 프로바이더 웹훅 수신

이벤트를 푸시하는 프로바이더는 하나의 정문을 통해 게이트웨이에 도달합니다.

```text
POST /webhooks/v1/:handler_id/:instance_id/:kind
```

이 라우트는 의도적으로 모든 인증 파이프라인밖에 있습니다. 세션도, CSRF도, bearer 토큰도, Accept 협상도 없습니다. 프로바이더는 자기 방식대로 헤더를 보내기 때문입니다. 프로바이더를 인증하는 일은 선언된 핸들러의 몫입니다. Bot Framework JWT, Graph `clientState`, 또는 프로바이더가 서명에 사용하는 무엇이든 그것입니다.

컨트롤러는 라우팅만 합니다. 선언된 `signals_gateway.webhook_handler` 플러그인을 해석하고, 핸들러가 선언한 `kind` 화이트리스트를 적용하며, 핸들러의 응답 지시를 렌더링합니다. 알 수 없는 핸들러나 선언되지 않은 kind는 페이로드를 되돌려 보내지 않고 `404`를 반환하고, 실패한 핸들러는 일반 메시지와 함께 `500`을 반환합니다. 정규화된 사실로 `Ingress`를 호출하는 것은 핸들러 자신입니다.

## Agent가 만든 웹훅 위임

Agent가 만든 콜백 기능은 별도의 수신 경로를 사용합니다.

```text
POST /webhooks/v1/event-callbacks/wh_<token>
```

이 경로는 프로바이더 핸들러를 사용하지 않습니다. URL 자체가 그것을 만든 Agent 세션으로 가는 하나의 wake-up 경로를 승인합니다. 본문의 사실을 인증하지는 않습니다.

SignalsGateway는 토큰 다이제스트만 저장합니다. 엔드포인트를 잠그고, 원샷(one-shot) 또는 상시(standing) 전달 의미론을 적용하며, 같은 PostgreSQL 트랜잭션 안에서 `webhook.received`를 추가합니다. 그러면 Worker가 신뢰할 수 없는 데이터 경계 안에서 제한된 헤더와 본문 데이터 집합을 프로젝션합니다. Agent는 결과에 영향을 주는 행동을 하기 전에 현재 외부 시스템을 읽습니다.

엔드포인트 생성, 목록, 취소 명령은 turn 로컬 Worker 브리지를 사용합니다. Console은 엔드포인트를 나열하고 취소할 수 있지만, 생성하거나 콜백 URL을 공개할 수는 없습니다. 사용자 흐름, GitHub 라이프사이클, 전달 계약, 운영상 한계는 [Webhook 위임](https://ankole.agentbull.com/ko-KR/docs/webhook-delegations/index.md)을 참고하세요.

## 아웃박스: 바깥으로 나가는 답장

수신은 게이트웨이의 절반입니다. 나머지 절반은 아웃박스로, 에이전트의 답장을 프로바이더 쪽 전송으로 바꿉니다. 아웃박스는 채널의 `reply_mode`를 읽고 전송 작업 — 채널 게시물 또는 스레드 항목 답장 — 을 선택하며, 수신이 사용하는 것과 같은 어댑터 계약을 거칩니다. 에이전트가 turn 중에 커밋한 부수 효과는 이 경로로 전달되며, 그 durable 기록은 라이브 Worker가 아니라 그것을 만들어낸 액터 이벤트와 함께 보관됩니다.

## SignalsGateway가 아닌 것

SignalsGateway는 프로바이더 클라이언트가 아닙니다. 모든 채팅 플랫폼에 열린 연결을 유지하지 않습니다. 그것은 어댑터와 어댑터의 장기 연결 Worker 몫입니다. 임의의 작업을 위한 큐도 아닙니다. 세션의 액터 지향 이벤트만을 위한 큐입니다. 그리고 에이전트 실행이 일어나는 곳도 아닙니다. 액터 이벤트가 추가되고 나면, 세션을 깨우고 turn을 실행하는 것은 Actor Runtime의 몫입니다. 게이트웨이의 경계는 프로바이더 사실을 durable 액터 입력으로 바꾸는 변환과, 액터 답장을 프로바이더 전송으로 다시 바꾸는 변환입니다.

## 다음 단계

- 깨어난 액터 이벤트가 어떻게 실행되는지는 [AIGateway API](https://ankole.agentbull.com/ko-KR/docs/ai-gateway/index.md)와 [아키텍처 개요](https://ankole.agentbull.com/ko-KR/docs/architecture/index.md)를 참고하세요.
- Channel Provider와 라우팅 규칙을 구성하는 방법은 [빠른 시작](https://ankole.agentbull.com/ko-KR/docs/quickstart/index.md)을 참고하세요.
