에이전트 하네스 설계 · 9/10
도구를 사용할 수 있게 만드는 것과 모든 도구 정의를 프롬프트에 넣는 것은 같은 일이 아닙니다. 캔버스에 연결된 도구가 몇 개 없을 때는 전체 JSON 스키마를 한꺼번에 제공해도 괜찮습니다. 하지만 연결 노드와 플랫폼 카탈로그가 늘어나면 사용자 요청보다 도구 설명이 더 길어지고, 실제로 쓰지 않을 정의가 컨텍스트 대부분을 차지합니다.
6월 29일 연결 도구를 처음부터 모두 펼치는 방식에서 검색 우선 방식으로 바꾸기 시작했습니다. 6월 30일에는 단어 빈도와 희소성을 이용해 이름·설명을 찾는 BM25 검색, 선택한 도구의 상세 정의를 불러오는 ToolSearch, 연결 자원을 지연 공개하는 경로를 SDK와 제품 실행에 연결했습니다. 7월 초에는 검색 가능한 플랫폼 소스를 실행별로 제한하고, 출력 노드의 실행 소유권과 모델에게 보여 줄 정보를 분리했습니다.
이 흐름은 처음부터 완성된 설계를 한 번에 넣은 것이 아닙니다. 도구의 존재, 상세 스키마, 실행 권한, 출력 부수효과를 각각 언제 누구에게 맡길지 차례로 분리한 과정이었습니다.
연결 도구의 존재와 상세 스키마를 나눴습니다
모든 도구를 숨기면 모델은 필요한 행동을 선택할 수 없습니다. 반대로 모든 스키마를 펼치면 컨텍스트가 커집니다. 그래서 ‘도구가 보이는가’라는 한 가지 플래그 대신 정보 수준을 나눴습니다.
현재 실행에 항상 필요한 소수의 내장 도구는 이름과 설명, 입력 스키마를 처음부터 제공합니다. 사용자가 캔버스에서 직접 연결한 도구는 선택 사실 자체가 중요하므로 이름과 짧은 설명을 먼저 보여 줍니다. 모델이 실제로 필요하다고 판단하면 ToolSearch가 상세 입력 스키마를 현재 실행 도구로 승격합니다. 플랫폼의 대규모 카탈로그는 검색 색인에서 후보를 찾은 뒤 선택한 정의만 불러옵니다.
존재를 안다
→ 이름과 설명으로 후보를 찾는다
→ 필요한 스키마를 승격한다
→ 권한과 입력을 확인한다
→ 실제 도구를 호출한다
이 방식은 도구를 감추는 것이 아닙니다. 지금 선택할 수 있는 도구의 이름과 설명으로 된 요약 목록을 먼저 주고, 호출에 필요한 상세 정의는 사용할 때 펼치는 방식입니다. 모델이 읽는 토큰을 줄이면서 사용자가 연결한 의도는 유지합니다.
검색은 이름 포함 여부만 보는 방식으로 시작하지 않았습니다. 도구 이름과 설명을 정규화하고 BM25로 후보를 정렬해 자연어 요청과 가까운 도구를 찾았습니다. 검색 결과가 실행 권한을 주는 것은 아니므로, 선택한 뒤에도 상세 정의 로딩과 호출 직전 정책 검사를 따로 거칩니다.
플랫폼 검색 범위와 사용자 연결을 다른 출처로 봤습니다
검색 경로가 생기자 어떤 소스를 탐색해도 되는지 제한할 필요가 생겼습니다. 실행별 source_allowlist는 플랫폼 카탈로그에서 자동 검색할 수 있는 도구 소스를 정합니다. 7월 2일 기본 경로를 연결된 소스 중심으로 좁히면서 불필요한 플랫폼 도구가 후보에 섞이지 않게 했습니다.
여기서 빈 허용 목록의 의미가 중요합니다. source_allowlist=[]는 플랫폼 카탈로그에서 허용된 소스가 없다는 뜻이지, 사용자가 현재 캔버스에 직접 연결한 도구도 없다는 뜻은 아닙니다. 둘은 선택 주체가 다릅니다.
- 플랫폼 카탈로그: 시스템이 검색 후보로 제안하는 자원
- 연결 도구: 사용자가 이번 실행에 명시적으로 포함한 자원
제품 연결 계층은 연결된 도구를 실행별 extra_tool_sources로 전달하고 플랫폼 허용 목록과 별도로 수집합니다. 연결 도구는 connected 출처로 이름과 설명을 먼저 제공하고, 상세 정의는 ToolSearch로 승격합니다. 연결되지 않았고 허용 목록에도 없는 플랫폼 도구는 검색 결과에 나타나지 않습니다.
초기 적용에서는 허용 목록 필터가 수집 단계 앞쪽에 놓이면서 연결된 소스까지 함께 제외되는 회귀가 생겼습니다. 목록과 짧은 설명은 보이는데 실제 검색·호출 경로에는 도구가 없는 상태였습니다. 7월 6일 수집 순서를 고쳐 명시적으로 연결된 소스는 플랫폼 카탈로그 필터와 독립적으로 유지했습니다.
도구 이름을 하나씩 예외 처리하지 않고 출처를 분리한 이유가 여기에 있습니다. ‘사용자가 연결함’과 ‘플랫폼 전체를 탐색해도 됨’은 다른 권한입니다. 한 목록으로 합치면 도구를 보여 주기 위해 검색 범위를 과도하게 열거나, 검색을 제한하려다 연결 도구까지 잃게 됩니다.
출력 노드는 실행 도구와 모델 컨텍스트에서 다르게 보였습니다
도구 공개와 같은 시기에 출력 노드의 책임도 정리했습니다. 이메일이나 메시지 노드를 일반 도구처럼 모델에게 보여 주면 모델이 send_email 호출 여부를 결정하게 됩니다. 호출하지 않으면 전달이 빠지고, 캔버스 실행기가 후속 노드를 다시 실행하면 같은 결과가 두 번 전송될 수 있습니다.
먼저 종단 실행을 하네스 내부의 메타 도구로 모았습니다. 최종 결과가 확정된 뒤 한 실행 소유자가 하네스 결과에서 이어진 종단 체인을 순서대로 처리하게 했습니다. 일반 캔버스 경로에서는 이 노드들을 제외해 한 실행 경로 안에서 중복 실행하지 않도록 했습니다. 이는 분산 시스템의 durable exactly-once 보장이 아니라, 프로세스 안의 두 실행 경로가 같은 부수효과를 함께 소유하지 않게 한 계약입니다.
그러나 내부에서 도구로 실행한다고 모델에게도 호출 도구로 보여 줄 필요는 없었습니다. 모델에 제공하는 정보는 output_channel이라는 ResourceInfo로 바꿨습니다. 모델은 결과가 이메일인지 메시지인지, 어떤 형식을 기대하는지 알고 최종 답을 구성합니다. 실제 전송 여부와 시점은 하네스가 결정합니다.
모델에게 보이는 것: output_channel 이름·용도·기대 형식
하네스가 소유한 것: 최종 결과 확정·종단 실행·중복 방지
‘출력 목적지를 도구로 만들지 않았다’고만 말하면 이 중간 설계가 사라집니다. 실제로는 실행 책임을 내부 메타 도구에 모은 뒤, 모델에게 공개하는 정보는 호출 가능한 도구가 아니라 자원 정보로 분리했습니다. 내부 실행 모델과 모델 컨텍스트가 반드시 같은 표현일 필요는 없었습니다.
채널 메타데이터에는 답을 만드는 데 필요한 값만 넣습니다. 파라미터 이름이 일반적인 비밀 패턴과 일치하는 값은 제외합니다. 이 필터가 모든 제품의 민감 필드를 자동으로 아는 것은 아니므로 새로운 채널을 연결할 때 제품 정책과 회귀 테스트로 보강해야 합니다.
이름 공개, 스키마 공개, 실행 권한을 따로 검증했습니다
도구 이름이 보인다고 이미 호출할 수 있는 것은 아닙니다. 연결된 도구는 먼저 이름과 설명으로 존재를 알립니다. 모델이 선택하면 ToolSearch가 스키마를 불러옵니다. 실제 호출 직전에는 입력 자료형과 현재 사용자의 권한, 실행 정책을 다시 확인합니다.
검증도 목록 한 화면에서 끝내지 않았습니다. 연결 도구가 짧은 목록에 들어오고, 모델이 정확한 이름을 검색하고, 해당 스키마가 다음 호출 컨텍스트에 합류하며, 실행 요청이 원래 도구 소스까지 도달하는지를 한 경로로 확인했습니다. 빈 허용 목록에서는 연결 도구가 남되 허용하지 않은 플랫폼 도구가 섞이지 않아야 했습니다.
출력 채널도 자원 목록에 나타나는지만 보지 않았습니다. 확정된 최종 답이 연결된 종단에 전달되고 한 실행 경로에서 중복 처리되지 않는지, 일반 캔버스 경로와 하네스 경로가 같은 실행 소유권을 지키는지, 비밀값이 메타데이터에서 빠지는지를 확인했습니다.
컨텍스트는 모델에게 많이 읽히는 문서가 아니라 모델과 실행 환경 사이의 인터페이스입니다. 도구는 존재를 먼저 알리고 스키마는 필요할 때 펼치며, 권한은 실행 직전에 다시 판단합니다. 출력 채널은 목적지를 알려 주되 전달 책임은 하네스가 갖습니다. 다음 편에서는 이 실행 경로에 교훈을 다시 넣을 때, 최신 피드백과 최신 후보가 실제 다음 행동까지 이어지게 만든 방법을 다룹니다.
이전 편 → 실행 간 메모리의 범위와 우선순위 설계(8편) 다음 편 → 교훈을 다음 실행에 반영할 때 최신 상태를 유지하는 방법(10편)

