
CUGA를 사용하여 실제 에이전트 앱 구축하기: 경량 하네스를 활용한 24가지 작동 예시
요약
IBM의 오픈 소스 에이전트 하네스인 CUGA를 활용하여 복잡한 에이전트 앱 구축 과정을 단순화하는 방법을 소개합니다. CUGA는 계획, 실행 루프, 도구 호출 등 번거로운 배관 작업을 자동화하여 개발자가 도구와 프롬프트에만 집중할 수 있게 돕습니다.
핵심 포인트
- CUGA는 에이전트 구축에 필요한 오케스트레이션 및 상태 관리 자동화
- 계획, 실행, 성찰(reflection) 단계를 통해 긴 작업에서도 높은 정확도 유지
- 설정을 통해 비용과 지연 시간 사이의 추론 모드(Fast, Balanced, Accurate) 조절 가능
- Docker, E2B 등 다양한 샌드박스 환경에서 안전한 코드 실행 지원
요약(TL;DR)— 에이전트를 구축하는 것은 대부분 배관 작업(plumbing)과 같습니다: 도구(tools), 상태(state), 가드레일(guardrails), 그리고 하나의 에이전트에서 여러 개로 확장하는 작업 등입니다. IBM의 엔터프라이즈용 에이전트 하네스(Agent Harness)인 CUGA (pip install cuga, Configurable Generalist Agent의 약자)는 이 작업을 처리해주므로, 여러분은 도구 목록과 프롬프트(prompt)만 작성하면 됩니다. 우리는 이를 증명하기 위해 24개의 단일 파일 앱을 구축했습니다. 여기에서 하나를 처음부터 끝까지 읽어본 후, 동일한 에이전트가 코드 재작성 없이 프로덕션 환경에서 어떻게 독립적(sovereign)이면서도 통제된(governed) 방식으로 실행되는지 확인해 보세요.
대부분의 에이전트 기반 앱(agentic apps)은 에이전트가 유용한 일을 하기 전에 일주일간의 배관 작업부터 시작합니다. 프레임워크를 선택하고, 모델 클라이언트(model client)를 연결하고, 도구 어댑터(tool adapters)를 작성하고, UI로 상태를 스트리밍할 방법을 구축하며, 그 과정 어딘가에서 에이전트가 실제로 무엇을 위한 것인지 결정하게 됩니다. 흥미로운 부분은 가장 마지막에 나타납니다.
CUGA는 이를 뒤집습니다. CUGA는 계획(planning), 실행 루프(execution loop), 도구 호출(tool calls), 그리고 상태 배관(state plumbing)을 대신 처리해 주는 IBM의 오픈 소스 에이전트 하네스(agent harness)입니다. 남는 것은 실제로 여러분의 몫인 부분입니다: 에이전트가 접근할 수 있는 도구가 무엇인지, 그리고 에이전트에게 무엇을 하라고 지시할 것인지입니다. 이것이 실제로는 어떤 느낌인지 보여주기 위해, 우리는 cuga-apps를 구축했습니다: 영화 추천기부터 IBM Cloud 아키텍처 어드바이저에 이르기까지, 각각 하나의 CugaAgent를 래핑(wrapping)한 단일 FastAPI 파일로 구성된 24개의 작고 작동 가능한 앱입니다. 이 앱들은 읽고 복사할 수 있도록 존재합니다. 라이브 갤러리를 클릭하여 살펴볼 수 있습니다.
이 기사는 그중 하나를 살펴보고, 하네스가 여러분의 부담을 덜어주는 부분이 무엇인지 명시하며, 프로덕션 환경을 위해 통제(governed)가 필요할 때 동일한 코드가 어디로 가는지 보여줍니다. 먼저 배워야 할 새로운 프레임워크는 없습니다. FastAPI 라우트(route)를 작성해 본 적이 있다면 모든 줄을 읽을 수 있습니다.
이 분야의 모든 것에 던져야 할 공정한 질문은 그것이 여러분이 작성하지 않아도 되게끔 무엇을 아껴주느냐는 것입니다. CUGA의 답변은 다음과 같습니다: 매번 다시 구축해야 했을 model 주변의 오케스트레이션(orchestration)을 아껴줍니다.
CUGA는 행동하기 전에 계획을 세운 다음, 도구 호출(tool calls)과 생성된 코드(CodeAct)를 혼합하여 실행합니다. 20단계에 달하는 긴 작업에서 대부분의 에이전트를 망가뜨리는 요인은 중간 결과물을 놓치고 다음 턴에서 이를 (종종 틀리게) 다시 도출하는 것입니다. CUGA는 해당 상태를 유지하며, 잘못된 호출을 포착하고 무작정 진행하는 대신 다시 계획을 세울 수 있는 성찰(reflection) 단계를 실행합니다. 이러한 메커니즘 덕분에 CUGA는 수동으로 튜닝하는 방식이 아니라 AppWorld 및 WebArena와 같은 에이전트 벤치마크에서 정상을 차지할 수 있었습니다.
또한 코드 대신 설정(config)을 통해 비용/지연 시간(cost/latency)의 트레이드오프(tradeoff)를 설정할 수 있습니다. Fast, Balanced, Accurate 추론 모드를 제공하며, 신뢰하는 샌드박스(로컬, Docker/Podman 또는 E2B 클라우드)에서 코드를 실행합니다. 동일한 에이전트 정의를 사용하면서 다이얼(dial)만 조절하는 방식입니다. 이 다이얼은 들리는 것보다 훨씬 중요합니다. 대부분의 하네스(harness)는 최첨단 모델(frontier model)이 밑단에 있다고 가정하고 계획이 어긋날 때 모델에 의존하여 복구하지만, CUGA는 그 작업을 스스로 수행합니다. 계획, 성찰 단계, 긴 실행 과정을 궤도에 유지시키는 변수 추적(variable-tracking) — 이것이 바로 모델이 직접 감당해야 했을 부하를 대신 짊어지는 하네스의 역할이며, 이를 통해 평소라면 버티지 못했을 더 작은 오픈 웨이트(open-weight) 모델이 성능을 유지할 수 있게 합니다. 이것이 호스팅된 앱들이 최첨단 API 대신 gpt-oss-120b에서 실행되는 이유입니다. 호출할 수 있는 가장 큰 모델을 사용하는 것이 일반적인 선택이지만, CUGA의 선택은 더 작은 오픈 모델로도 충분하다는 것입니다.
개별 구성 요소 중 CUGA에만 고유한 것은 없습니다. 차이점은 이 요소들이 미리 조립되어 제공되므로, 직접 연결하는 대신 설정만 하면 된다는 점입니다. 사용자가 접하는 API는 매우 작습니다. 도구 목록과 프롬프트(prompt)를 사용하여 CugaAgent를 구축한 다음, await agent.invoke(...)를 호출하기만 하면 됩니다. 그 아랫줄의 모든 것이 바로 하네스입니다.
구체적으로 말하자면, 이는 교체 가능한 도구들(OpenAPI, MCP, LangChain 함수 모두 동일한 방식으로 바인딩됨), 변수 관리 및 자기 수정(self-correction)을 포함한 장기 계획(long-horizon planning) (07/25 - 02/26의 #1 on AppWorld 및 02/25 - 09/25의 WebArena 이면에 있는 메커니즘), 선언적 가드레일(declarative guardrails), A2A를 통한 멀티 에이전트 위임(multi-agent delegation), Docling 기반의 RAG, 그리고 단일 환경 변수(one-env-var) 제공자 전환(pip install cuga 후 OpenAI, watsonx, Ollama 등)을 의미합니다. 이 각각은 그렇지 않았다면 직접 구축해야 했을 기능들입니다. 이름의 첫 단어가 그 역할을 수행합니다: Configurable(설정 가능); 어려운 부분들은 처리되어 있으므로, 당신의 업무는 오직 태스크(task)뿐입니다.
여기 IBM Cloud 어드바이저가 있습니다. 이는 아키텍처에 적합한 실제 IBM Cloud 서비스를 추천하는 에이전트입니다. 전체 구성은 에이전트 팩토리(agent factory), 도구(tools), 프롬프트(prompt)가 포함된 main.py 파일 하나와 작은 UI로 이루어져 있습니다.
전체 에이전트는 다음과 같습니다:
def make_agent():
from cuga import CugaAgent
from _llm import create_llm
...
네 개의 인자(arguments)가 있습니다. 모델은 환경 변수에 따라 OpenAI, Anthropic, watsonx, LiteLLM 또는 Ollama와 통신하는 작은 팩토리(create_llm)에서 가져옵니다. 앱 코드 내의 그 어떤 것도 어떤 모델이 뒤에 있는지 알지 못합니다. cuga_folder는 이 앱이 상태(state)와 모든 정책(policies)을 유지하는 곳입니다. 앱을 전달하는 두 개의 인자는 tools와 special_instructions입니다.
도구들은 로컬 함수와 호스팅된 함수를 혼합합니다:
def _make_tools():
from langchain_core.tools import tool
@tool
...
모든 앱에 걸쳐 유지되는 패턴이 여기에 있습니다: MCP 도구와 인라인(inline) 도구 간의 분리입니다. 범용적이고 상태가 없는(stateless) 기능들은 공유 MCP 서버로부터 제공됩니다; load_tools(["web"])는 당신이 아무것도 호스팅하지 않고도 웹 검색을 가져옵니다. 이 앱에 특화된 모든 것은 search_ibm_catalog와 같이 일반적인 Python 함수로 인라인 정의되며, 이 함수의 독스트링(docstring)은 에이전트가 언제 이를 호출할지 결정하기 위해 읽는 정보가 됩니다. 당신은 자신만의 도구 하나를 작성하고 나머지는 빌려 쓰는 것입니다.
클라우드 어드바이저(cloud advisor)의 프롬프트는 에이전트에게 어떤 서비스를 명명하기 전에 카탈로그를 검색할 것, 설계 내 각 서비스의 역할을 포함하여 3개에서 7개의 서비스를 추천할 것, 그리고 절대로 존재하지 않는 서비스 이름을 만들어내지 말 것을 지시합니다. 마지막 규칙은 그 가치를 충분히 증명합니다. 존재하지 않는 IBM Cloud 서비스를 추천하는 에이전트는 에이전트가 없는 것보다 못하기 때문에, 프롬프트는 모든 추천이 먼저 카탈로그 조회(catalog lookup)를 거치도록 강제합니다. 명시적인 "지어내지 마시오" 규칙과 함께 순차적인 단계로 작성된 프롬프트는 제대로 작동하지만, 페르소나(persona)로 작성된 프롬프트는 방황합니다.
이것이 바로 앱입니다. 하나의 도구(tool), 하나의 절차(procedure), 그리고 네 줄의 생성자(constructor)가 전부입니다. 그 주변을 둘러싼 FastAPI 라우트(routes)는 평범한 웹 코드입니다. 브라우저는 /ask로 질문을 전송하고, 라이브 패널은 상태(state)를 확인하기 위해 /session/{thread_id} 엔드포인트(endpoint)를 폴링(poll)합니다. 데이터베이스는 없습니다. 상태는 thread_id별 Python 딕셔너리(dict)이며, 에이전트가 자신의 도구를 통해 쓰기(write)만 할 수 있습니다. 에이전트가 실행 도중 도구를 호출하는 순간, 패널이 다시 그려집니다. UI는 로직의 복사본이 아니라, 에이전트가 변이(mutate)시킨 상태를 보여주는 뷰(view)입니다.
간과하기 쉽지만 알고 보면 핵심적인 디테일이 하나 있습니다. 모든 인라인 도구(inline tool)는 동일한 작은 엔벨로프(envelope)를 반환해야 한다는 점입니다. 성공 시에는 {"ok": true, "data": {...}} 형태를 띠고, 실패 시에는 {"ok": false, "code": "...", "error": "..."} 형태를 띱니다.
이것이 상용구(boilerplate)처럼 보일 수 있지만, 그렇지 않습니다. CUGA의 플래너(planner)는 선언된(declared) 실패(예: "지오코딩(geocoding) 결과가 없으니 해당 섹션을 건너뛰고 계속 진행하세요")는 우아하게 처리하지만, 선언되지 않은(undeclared) 실패에는 제대로 대응하지 못합니다. 선언되지 않은 실패의 경우, 가공되지 않은 스택 트레이스(stack trace)가 계획 도중에 떠올라 실행이 탈선하게 됩니다. 여러 앱을 살펴보면, 안정적으로 작동하는 앱들은 도구가 에이전트에게 가공되지 않은 예외(bare exception)를 던지지 않는 앱들이었습니다. 지루한 관례처럼 보일 수 있지만, 이것이 회복 가능한 에이전트와 완전히 무너져 버리는 에이전트를 가르는 차이입니다.
위에서 언급한 분리 방식이 효과를 발휘하는 이유는 범용적인 절반이 이미 어딘가에서 실행되고 있기 때문입니다. 앱들이 반복적으로 요청하는 기능들 — 웹 검색 (web search), Wikipedia/arXiv, 지오코딩 (geocoding) 및 날씨, 금융 시세 (finance quotes) 등 — 은 인증이 필요 없는 IBM Code Engine에서 호스팅되는 **7개의 공개 MCP 서버 (36개 도구)**에 구현되어 있습니다. 작은 브릿지가 이들의 URL을 자동으로 해결하며, 라이브 갤러리에는 에이전트에 연결하기 전에 폼(form)을 통해 이들 중 어떤 것이든 호출해 볼 수 있는 MCP Tool Explorer가 포함되어 있습니다.
24개의 세련된 앱이 존재하는 이유는 개별 앱 하나하나보다 더 중요한 의미를 갖습니다. 클라우드 어드바이저 (cloud advisor)를 읽었다면, 나머지 모든 앱도 읽은 것이나 다름없기 때문입니다. 이들은 하나의 골격 (skeleton)을 공유합니다. 영화 추천기는 IBM 카탈로그 도구를 knowledge MCP 서버로 교체하고, 웹 리서처는 거의 전적으로 web에 의존합니다. 따라서 cuga-apps는 사실상 시작점을 모아놓은 카탈로그입니다. 저장소 (repo)를 클론하고, 당신의 아이디어와 가장 유사한 앱을 찾아 도구 목록과 프롬프트 (prompt)를 수정하면 됩니다 (HOW_TO_BUILD_AN_APP_FAST.md 및 ADDING_AN_APP.md에서 정확한 방법을 안내합니다). 몇몇 앱은 코딩 어시스턴트 (coding assistant)에게 하나의 사양 파일 (spec file)과 한 줄짜리 요약을 전달하여 생성되기도 했습니다. 모델이 재현할 수 있을 정도로 규칙적이라는 것은 당신도 충분히 배울 수 있다는 의미입니다. 무엇인가를 클론하기 전에 라이브 갤러리에서 모든 앱을 하나씩 클릭하며 살펴볼 수 있습니다.
이 앱들은 또한 다양한 제품군(families)으로 퍼져 있어, 당신이 무엇을 만들든 이미 당신에게 필요한 부분을 실행해 볼 수 있는 앱이 하나쯤은 존재합니다. 연구 클러스터(Paper Scout는 인용 횟수에 따라 arXiv 논문을 순위 매기며, Wiki Dive와 Web Researcher는 인용된 내용을 합성합니다), 일상 생산성 세트(도시 브리핑, 여행, 레시피, 트레일), PDF, 오디오, 비디오에 대해 RAG (Retrieval-Augmented Generation)를 수행하는 문서 및 미디어 그룹, 실시간 메트릭을 모니터링하는 운영(ops) 코너, 그리고 실제 IBM 제품 문서에 대한 엔터프라이즈 예시가 있습니다. Ouroboros는 7개의 에이전트로 구성된 리드 생성(lead-gen) 시스템이며, 멀티 에이전트(multi-agent) 구조를 확인하려면 이를 열어보세요. 그리고 Meetup Finder는 Playwright를 통해 헤드리스 크로미움(headless Chromium)을 구동하여 Meetup, Luma, Eventbrite(모두 공개 검색 API를 폐쇄했습니다)에서 구조화된 이벤트를 가져옵니다. 브라우저 자동화(browser automation)를 확인하려면 이를 열어보세요. 이것이 CUGA가 시작된 지점이자, 강력한 WebArena 결과의 핵심 동력입니다.
클론하기 전에 두 가지 주의사항이 있습니다. 실제 카탈로그는 바깥쪽 디렉토리가 아니라 내부의 cuga-apps/cuga-apps/apps/ 디렉토리에 있습니다. 또한 모든 앱이 동일하게 다듬어져 있지는 않으므로, UI는 이를 ship-ready(출시 준비 완료), for-later(나중에), 또는 exploratory(탐색용)로 태그하며 기본값은 ship-ready입니다. 작동 가능한 기준점(baseline)을 찾으려면 cloud advisor나 movie recommender부터 시작하세요.
카탈로그를 검색하는 데모 에이전트는 위험 부담이 적습니다. 하지만 동일한 패턴을 파일을 작성하거나, 셸 명령(shell commands)을 실행하거나, 운영 환경(production)을 건드리는 대상에 적용하면 질문이 달라집니다. 즉, 에이전트가 당신이 후회할 만한 행동을 하지 못하도록 어떻게 막을 것인가 하는 문제입니다.
CUGA는 이를 나중에 추가하는 래퍼(wrapper)가 아니라 런타임(runtime)에서 해결합니다. 오픈 소스 에이전트는 정책 시스템(policy system)을 제공하며, 당신은 동일한 에이전트 객체에 정책을 부착할 수 있습니다:
await agent.policies.add_intent_guard(
name="Block force-push",
keywords=["--force", "--no-verify"],
...
이것이 Intent Guard(의도 가드)이며, 6가지 정책 유형 중 하나입니다. 각 유형은 팀이 에이전트를 풀어주기 전에 던지는 질문에 답합니다:
Intent Guard (의도 가드)— 요청을 즉시 거부할 수 있는가?Tool Approval (도구 승인)— 위험한 도구가 실행되기 전에 사람의 승인을 위해 일시 중지할 수 있는가?Tool Guide (도구 가이드)— 특정 도구를 다시 작성하지 않고도 사용 방식을 유도할 수 있는가?Playbook (플레이북)— 반복되는 작업에 대해 검증된 절차를 고정할 수 있는가?Output Formatter (출력 포맷터)— 최종 응답을 요구되는 형식으로 강제할 수 있는가?
여섯 번째 유형인 CustomPolicy는 위의 유형 중 어느 것도 적합하지 않을 때 사용하는 탈출구(escape hatch)입니다. 타이밍을 정확히 맞추는 것이 중요한데, 이는 모든 과정이 하나의 단계로 이루어지지 않기 때문입니다. Intent Guard는 에이전트가 도구를 선택하기 전에 요청을 확인하고, Tool Approval은 에이전트가 코드를 생성한 후에 해당 코드가 어떤 도구를 사용하는지 검사하며, Output Formatter는 최종 메시지가 생성된 직후에 실행됩니다. 트리거(Trigger)는 키워드 매칭을 넘어섭니다. 트리거는 sqlite-vec 저장소에 보관되어 의미론적(semantically)으로 매칭되므로, 정책은 단순히 정확한 키워드가 아니라 사용자의 의도에 따라 실행됩니다. 의미론적 유사성, 에이전트 상태, 또는 특정 도구의 실행 여부에 따라 매칭됩니다. 정책 자체는 생성자(constructor)에서 생성된 .cuga 폴더 내에 존재하며, 별도의 설정 파일로 분리되어 떠돌지 않고 코드와 함께 버전 관리됩니다.
작동 예시를 보려면 Ouroboros를 열어보세요. 이는 7개의 에이전트로 구성된 리드 생성(lead-gen) 앱으로, 슈퍼바이저(supervisor)에 세 가지 정책(intent guard, tool guide, output formatter)을 부착하고 있습니다. 따라서 이 앱은 하나의 파일 내에서 거버넌스(governance)와 멀티 에이전트(multi-agent) 구조를 동시에 보여주는 유일한 예시입니다.
앱이 단일 채팅 루프를 벗어나게 되면 두 가지 확장 기능이 중요해집니다. 한 에이전트가 자신의 컨텍스트(too many tools, too much evidence to keep straight)에 압도될 상황(도구가 너무 많거나 정리해야 할 증거가 너무 많은 경우)이 되면, 작업을 분할합니다. CugaSupervisor는 전문화된 CugaAgent에게 작업을 위임합니다.
각 전문가는 자신만의 도구(tools), 프롬프트(prompt), 그리고 격리된 컨텍스트(isolated context)를 가지며, 감독관(supervisor)은 오직 어떤 전문가에게 하위 작업(subtask)을 넘겨줄지에 대해서만 추론합니다. 하단에 얼마나 많은 도구가 있더라도 감독관의 계획 영역(planning surface)은 작게 유지되며, 불안정한 도구(flaky tool)가 발생하더라도 전체 실행이 아닌 단 하나의 위임(delegation)만 실패하게 됩니다. 전문가는 반드시 로컬(local)에 있을 필요도 없습니다. A2A를 통해 접근 가능한 외부 에이전트(external agent)일 수도 있으며, 동일한 방식으로 위임될 수 있습니다. 새로운 기능을 추가한다는 것은 코디네이터(coordinator)를 다시 작성하는 것이 아니라, 전문가를 추가하는 것을 의미합니다.
다른 확장 패키지들은 도구가 아닌 노하우(know-how)를 다룹니다. SKILL.md 파일이 포함된 폴더인 Agent Skills는 에이전트가 작업 수행에 필요할 때만 컨텍스트로 불러오는 플레이북(playbook) 역할을 하므로, 하나의 프롬프트가 에이전트가 알아야 할 모든 정보를 전부 담고 있을 필요가 없습니다. 두 방식 모두 동일한 빌딩 블록(building blocks: 도구, 프롬프트, 상태, 정책)을 사용하지만, 한 단계 더 높은 수준에서 구성될 뿐입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Hugging Face Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기