나만의 AI 하네스 루프 시스템(AI Harness Loop System) 구축하기
요약
자체 호스팅 멀티 에이전트 시스템을 구축하며 겪은 설계 실패와 교훈을 다룹니다. 모델 자체보다 모델을 안전하고 반복 가능하게 만드는 '하네스(harness)'와 루프 설계의 중요성을 강조합니다.
핵심 포인트
- 에이전트는 모델을 안전하게 실행하는 시스템인 '하네스'가 핵심임
- 단순 모델 사용과 모델 주변의 루프 구축은 근본적으로 다름
- 설계 오류는 코딩 문제보다 시스템 구조 설계 문제에서 주로 발생함
- 에이전트 간 위임 및 상태 관리의 신뢰성 확보가 필수적임
원문은 mauriciojuba.com에서 처음 게시되었습니다.
ARTICLE · AGENTS · SYSTEMS
자체 호스팅되는 멀티 에이전트 시스템(multi-agent system)을 구축하고 폐기하며 AI 설계를 위해 배운 점들.
나의 홈랩(homelab)은 6대의 머신에서 약 24개의 서비스를 실행하고 있으며, 저는 이것이 스스로를 관리하기를 원했습니다.
작업은 지루하고 끊임없이 반복됩니다. 디스크가 가득 차고, 인증서가 만료됩니다. 컨테이너가 죽어도 일주일 동안 아무도 알아채지 못합니다. 여기에 에이전트(agent)를 투입하는 것은 당연한 아이디어였고, 그래서 시도해 보았습니다. 첫 번째 시도는 실패했습니다. 두 번째도 마찬가지였습니다. 세 번째는 그 나름의 문제들이 있었습니다. 약 4개월에 걸쳐 저는 네 번째 버전이 안정화되기 전까지 세 가지 버전을 구축하고 폐기했습니다.
제가 실패에 대해 쓰는 이유는 그곳에 배움이 있었기 때문입니다. 잡무는 결국 처리되었습니다. 제 기억에 남은 것은 동일한 실수들이 각 재작성 과정에서 다른 형태로 계속 반복되었다는 점과, 그중 상당수가 코딩의 문제라기보다는 설계(design)의 문제였다는 점입니다. 제가 배운 대부분의 내용은 결과적으로 직장에서 AI 제품을 설계하는 방식을 바꾸어 놓았습니다.
아래는 각 버전의 내용과 무엇이 그것을 망가뜨렸는지, 그리고 살아남은 버전은 어떤 모습인지에 대한 설명입니다.
왜 굳이 자신만의 하네스(harness)를 구축하는가
AI 도구를 다루는 데는 두 가지 방법이 있습니다. 하나는 그것을 사용하는 것입니다. 채팅창을 열고, 답을 얻고, 다음으로 넘어가는 것이죠. 다른 하나는 모델 주변의 루프(loop)를 직접 구축하는 것입니다. 이는 모델이 언제 실행될지, 어떤 컨텍스트(context)를 받을지, 무엇을 건드릴 수 있는지, 그리고 실제로 작동했는지 어떻게 확인할지를 결정하는 것을 의미합니다.
두 번째 방식에 유용한 교훈이 담겨 있으며, 대부분의 설계자들이 건너뛰는 방식이기도 합니다. 모델을 사용하는 것은 그것이 무엇을 만들어낼 수 있는지를 보여줍니다. 루프를 구축하는 것은 그것을 무엇에 신뢰하고 맡길 수 있는지, 그리고 아무도 감독하지 않을 때 어떻게 행동하는지를 보여줍니다. 저는 적어도 한 번은 그 과정에 시간을 투자하지 않고서는 좋은 AI 제품을 설계할 수 없다고 생각합니다.
에이전트(agent)는 대부분 하네스(harness)입니다. 모델은 단지 엔진일 뿐입니다. 그것을 안전하고 반복 가능하게 만드는 모든 것은 그 주변에 구축한 시스템입니다.
V1 / V2: 24개의 에이전트, 잘못된 엔진
첫 번째 버전은 너무 야심 찼습니다. 24개의 특화된 에이전트(agent)가 각각 자신의 컨테이너(container) 내에서 동작하며, 데이터베이스 메시지 큐(database message queue)를 통해 조정되었습니다. 그 위에는 이들이 무엇을 하고 있는지 보여주는 창 역할을 하는 그룹 채팅(group chat)이 자리 잡고 있었습니다. 한 에이전트는 비서실장(chief of staff) 역할을 수행했습니다. 요청을 받아 전문가들에게 부분을 나누어 전달하고, 답변을 다시 하나로 모으는 역할을 했습니다.
조직도상으로는 훌륭해 보였습니다. 하지만 실제로 작동하는 것은 거의 없었습니다.
가장 최악의 버그는 에이전트 간의 위임(delegation)이 어디에도 도달하지 못한다는 것이었습니다. send() 함수는 그룹 채팅에 상태 라인을 게시한 뒤, 실제 작업을 운반해야 할 큐(queue)에 쓰기도 전에 반환(return)되어 버렸습니다. 그래서 채팅창은 활동으로 스크롤되었지만, 실제 채널은 비어 있는 상태로 유지되었습니다. 모든 위임은 누락되었고, 채팅창이 마치 괜찮은 것처럼 보이게 만들었기 때문에 몇 주 동안이나 그 상태가 지속되었습니다.
DELEGATION · send()
버그: send()가 큐에 쓰기도 전에 채팅에 게시하고 반환되었습니다. 작업을 운반하는 채널은 비어 있는 동안 채팅은 활동으로 가득 찼습니다.
그 아래에는 세 가지 실패가 더 있었습니다:
일치하지 않는 ID
답변이 요청의 ID 대신 자신의 ID를 태그하여 돌아왔습니다. 그래서 데이터베이스에는 도달했지만, 요청한 사용자와 결코 연결될 수 없었습니다.
큐에서의 경합 상태 (Race condition)
두 명의 컨슈머(consumer)가 동일한 행(row)을 가져갔고, 오케스트레이터(orchestrator)가 때때로 사용자에게 전달되어야 할 답변을 삼켜버렸습니다.
미화된 라우터 (Glorified router)
"비서실장"은 요청을 분류하고 전달할 뿐이었습니다. 요약도, 판단도, 자신만의 목소리도 없었습니다.
취약한 엔진
모든 에이전트는 동일한 작은 무료 모델(free model)을 실행했습니다. 깔끔하게 배포되었지만 추론(reasoning)은 형편없었습니다. 빈 답변, 무시된 컨텍스트(context), 확신에 찬 헛소리뿐이었습니다.
약 3주가 지났을 때, 마침내 위임 기능이 작동하기 시작했습니다. 작동하는 순간, 유휴 상태로 있던 22개의 하트비트 타이머(heartbeat timer)가 한꺼번에 작동하며 시스템을 보고서로 뒤덮어 버렸습니다. 저는 모든 것을 롤백(roll back)하고 첫 번째 사후 분석(postmortem) 보고서를 작성했습니다. 그 보고서의 마지막 문장은 이러했습니다. "완벽하게 작동했습니다. 엔진이 틀렸을 뿐입니다."
V1이 내게 가르쳐준 것
초록불은 아무것도 증명하지 않는다
요청을 처음부터 끝까지 추적하지 않았다면, 요청이 도착하지 않은 것으로 간주하십시오. 채팅창에는 몇 주 동안 "완료"라고 떠 있었지만, 실제로 전달된 것은 아무것도 없었습니다.
모델이 기반이다
약한 모델을 둘러싼 깨끗한 하네스 (Harness)는 빈 껍데기에 불과합니다. 플랫폼을 구축하기 전에 모델과 그 접근 권한을 먼저 정리하십시오.
유능한 소수의 에이전트가 승리한다
역량이 부족한 24개의 에이전트를 사용하는 것은, 그들이 업무를 통해 돌려준 가치보다 이를 조정하는 데 더 많은 비용이 들었습니다.
엔드 투 엔드 (End-to-end) 테스트가 없으면 진실도 없다
위의 모든 버그는 단 하나의 검증 방식이 제가 채팅창을 읽는 것뿐이었기 때문에 몇 주 동안이나 지속되었습니다.
V3: 세 개의 심층 에이전트, 그리고 배포의 함정
두 번째 재작성은 진정한 도약이었습니다. 저는 타입이 지정된 런타임 (Typed runtime)으로 전환했고, 유능한 에이전트를 3개로 줄였으며, 각 에이전트 뒤에 폴백 체인 (Fallback chain)을 갖춘 적절한 모델을 배치했습니다: 첫 번째 클라우드 제공업체, 그다음 다른 제공업체, 그리고 둘 다 다운되면 로컬 모델을 사용하는 방식입니다.
이 버전은 대부분 잘 작동했습니다. 큐 (Queue)에 대한 행 단위 잠금 (Row-level locking) 덕분에 각 작업이 정확히 하나의 워커 (Worker)에게 할당되었고, 큐는 더 이상 문제가 되지 않았습니다. 저는 모든 에이전트를 사용자와 대화하는 빠른 '외부 뇌 (Outer brain)'와 도구를 실행하는 느린 '내부 뇌 (Inner brain)'로 분리하여 타임아웃 (Timeout) 문제를 해결했습니다. 공유된 블랙보드 (Blackboard)를 통해 에이전트들이 서로 직접 호출하지 않고도 협업할 수 있게 했습니다. V1에서 발생했던 10가지 실패 중 7가지가 사라졌습니다.
빠름. 사용자를 마주함.
느림. 도구를 실행함.
에이전트를 둘로 나누면서 타임아웃이 끝났습니다. 외부 뇌가 즉시 답변하는 동안 내부 뇌는 몇 분 동안 계속 작업을 수행합니다.
그 후 이것은 V4를 형성하게 만든 새로운 교훈을 가르쳐 주었습니다. 모든 변경 사항에는 배포 (Deploy)가 필요했습니다.
에이전트의 역량을 결정하는 수치들은 모두 하드코딩된 상수 (Hardcoded constants)였습니다. 수행할 수 있는 단계의 수, 도구 출력의 읽기 범위, 작성할 수 있는 토큰 (Token) 수, 포기하기 전까지의 시간 등이 모두 빌드 (Build) 과정에 포함되어 있었습니다. 일반적인 작업 세션은 다음과 같이 진행되었습니다:
배포 루프 (THE DEPLOY LOOP)
저는 한 오후 동안 네 가지 서로 다른 상수(constants)를 대상으로 그 루프를 네 번 돌았습니다. 에이전트(agent)는 자신이 실패했다는 것을 인지하고 그 이유를 설명할 수는 있었지만, 그 무엇도 고칠 수는 없었습니다. 왜냐하면 수정 사항은 에이전트가 접근할 수 없는 서버의 컴파일된 코드(compiled code) 안에 있었기 때문입니다. 에이전트는 똑똑했지만 완전히 막혀 있는 상태였습니다.
더 조용한 실패가 저를 더 괴롭혔습니다. 저는 감사 에이전트(auditor agent)에게 18개의 문서를 읽고 각각을 인증하라고 요청했습니다. 에이전트는 단 하나의 파일도 열지 않고 18개 모두를 인증했습니다. 프롬프트(prompt)에는 먼저 읽으라고 지시되어 있었습니다. 에이전트는 노력을 아끼기 위해 그 단계를 건너뛰었고, 시스템 내의 그 어떤 것도 그 순서를 강제하지 않았습니다. 모델에게 지시사항이란 코드가 이를 의무화하지 않는 한 선택 사항이기 때문에, 모델은 그 지시를 선택 사항으로 취급했습니다.
NODEPLOY: 에이전트가 할 수 있는 일을 변경하는 작업은 결코 배포(deploy)를 필요로 해서는 안 됩니다. 배포는 엔진 자체를 위한 것입니다. 능력(capability), 제한(limits), 그리고 동작(behaviour)은 실행 중인 시스템이 읽고 스스로 다시 쓸 수 있는 파일 안에 존재해야 합니다.
특정 단계가 반드시 일어나야 한다면, 런타임(runtime)이 이를 실행하도록 만들어야 합니다. 모델이 스스로 지시를 따를 것이라고 의존하는 순간, 당신은 이미 패배한 것입니다.
V4: 그려진 루프
V4를 위해 저는 작업 방식을 바꾸었습니다. 코드를 작성하기 전에 루프를 먼저 그렸습니다. 이전 버전들은 버그와는 크게 상관없는 방식으로 계속 실패했습니다. 구조가 잘못되었던 것이며, 잘못된 구조를 버그 수정(bug-fix)만으로 해결할 수는 없습니다.
다섯 개의 그림이 전체 아키텍처(architecture)를 포괄합니다. 작업 단위가 이 그림들을 통과하는 순서대로 나열하겠습니다.
게이트웨이(The gateway): 모든 것은 메시지다
그 어떤 것도 가공되지 않은 상태로 에이전트에게 도달하지 않습니다. 사람으로부터 오든 다른 에이전트로부터 오든, 모든 메시지는 먼저 하나의 게이트웨이를 거칩니다. 게이트웨이는 메시지에 딱 필요한 만큼의 컨텍스트(context)를 입힙니다: 누가 말하고 있는지, 마지막 몇 차례의 대화 내용, 그리고 관련 프로필(profile)입니다. 채널 어댑터(channel adapter)는 작지만 중요한 지연(delay)을 추가합니다. 어댑터는 당신이 타이핑이나 녹음을 멈춘 후 몇 초가 지날 때까지 기다렸다가 작업을 수행하므로, 에이전트가 미완성된 메시지에 반응하는 일은 절대 발생하지 않습니다.
핵심은 균일성(uniformity)입니다. 채팅 앱의 사용자와 보고를 수행하는 서브 에이전트(sub-agent) 모두 동일한 방식과 동일한 형식으로 도달하므로, 에이전트가 이들을 다르게 처리할 필요가 전혀 없습니다.
추론 루프(The reasoning loop): 행동하기 전에 결정하라
매 틱(tick)마다 에이전트는 파일로부터 자신의 컨텍스트(context)를 재구축합니다. 서사적인 파일들은 에이전트의 정체성과 목소리를 유지합니다. 구조화된 파일들은 에이전트의 도구(tools), 기술(skills), 협력자(collaborators), 정책(policy) 및 제한 사항(limits)을 보유합니다. 그런 다음 에이전트는 단 하나의 결정을 내립니다: 위임(delegate), 추론(reason), 또는 행동(act). 가벼운 대화(Small talk)는 별도의 메커니즘 없이 즉시 답변으로 이어지는 빠른 경로를 따릅니다. 실질적인 모든 사항은 이전 버전에는 없었던 단계를 트리거합니다.
에이전트는 메시지를 받자마자 즉시 행동하지 않습니다. 무언가를 수행해야 할 때, 에이전트는 자신에게 보내는 메시지를 블랙보드(blackboard)에 이야기 형태로 작성하며, 이후 실행 루프(execution loop)가 이를 가져갑니다. 결정하는 것과 실행하는 것은 두 개의 분리된 장소에서 일어납니다. 이러한 분리 덕분에 시스템을 검사할 수 있고, 문제가 발생했을 때 시스템을 재개할 수 있습니다.
블랙보드(The blackboard): 계획을 세운 후, 네 가지 질문을 던져라
블랙보드(The blackboard)는 V3의 직접 메시징(direct messaging) 방식을 대체했습니다. 에이전트들이 서로를 호출하는 대신, 공유된 표면(shared surface)에 작업 내용을 게시합니다. 에이전트는 완료 정의(definition of done)가 포함된 스토리(story)를 게시하고 다음 단계로 넘어갑니다. 해당 작업을 처리할 수 있는 에이전트가 이를 가져갑니다.
계획되지 않은 스토리는 각각 고유의 완료 정의(definition of done), 예산(budget), 우선순위(priority), 그리고 다른 작업과의 의존성(dependencies)을 가진 작은 태스크(task)들로 분해됩니다. 태스크를 시작하기 전, 에이전트는 유능한 사람이 하는 것과 동일한 네 가지 체크를 수행합니다: '내가 전에 이것을 해본 적이 있는가?', '내가 할 수 있는가?', '다른 누군가가 더 잘할 수 있는가?', 그리고 '더 간단한 방법이 있는가?'입니다. 이 검토를 통과하면, 해당 태스크 하나를 실행하기 위해 수명이 짧은 서브 에이전트(sub-agent)를 생성합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기