Build real agentic apps using CUGA: two dozen working examples on a lightweight harness
IBM이 공개한 오픈소스 에이전트 하네스 'CUGA'를 소개합니다. 복잡한 백엔드 배관 작업 없이 프롬프트와 도구 정의만으로 즉시 동작하는 실용적인 AI 에이전트를 만드는 방법과 24가지의 오픈소스 앱 예제를 살펴봅

IBM이 공개한 오픈소스 에이전트 하네스 'CUGA'를 소개합니다. 복잡한 백엔드 배관 작업 없이 프롬프트와 도구 정의만으로 즉시 동작하는 실용적인 AI 에이전트를 만드는 방법과 24가지의 오픈소스 앱 예제를 살펴봅니다.
에이전트 기반 애플리케이션을 만들 때, 실제로 유용한 기능을 구현하기도 전에 일주일 내내 복잡한 배관 작업(Plumbing)만 붙잡고 있었던 적이 있으신가요? 프레임워크를 고르고, 모델 클라이언트를 연결하고, 도구 어댑터를 작성하고, 상태를 UI로 스트리밍하는 구조를 짜다 보면 가장 중요한 '에이전트의 역할 정의'는 맨 마지막으로 밀려나곤 합니다.
IBM이 오픈소스로 공개한 에이전트 하네스(Harness)인 CUGA(Configurable Generalist Agent)는 이 과정을 완전히 뒤집어 줍니다. 계획 수립, 실행 루프, 도구 호출, 상태 관리를 하네스가 알아서 처리하므로, 개발자는 에이전트가 어떤 도구를 사용할 수 있고 무엇을 해야 하는지(프롬프트)만 정의하면 됩니다. 이를 증명하기 위해 단일 파일로 구성된 24개의 실제 작동하는 앱 예제(cuga-apps)도 함께 공개되었습니다.
이 글에서는 CUGA의 작동 방식과 이 하네스가 개발자의 짐을 어떻게 덜어주는지, 그리고 상용 환경에서 안전하게 배포하는 방법까지 차근차근 살펴보겠습니다.
하네스(Harness)인가, 프레임워크인가
새로운 도구를 배울 때 가장 먼저 던져야 할 질문은 "이 도구가 내가 직접 작성해야 했을 코드 중 무엇을 대신해 주는가?"입니다. CUGA의 답은 명확합니다. 모델을 둘러싼 복잡한 오케스트레이션(Orchestration) 코드를 매번 새로 짤 필요가 없게 해줍니다.
CUGA는 행동하기 전에 계획을 세우고, 도구 호출과 자체 생성된 코드 실행(CodeAct)을 조합하여 작업을 수행합니다. 장기적인 작업(예: 20단계가 넘어가는 작업)을 수행할 때 대부분의 에이전트가 실패하는 이유는 중간 결과를 잃어버리고 다음 단계에서 잘못된 전제로 다시 계산하기 때문입니다. CUGA는 이 상태(State)를 안전하게 유지하며, 잘못된 호출을 잡아내고 무작정 진행하는 대신 계획을 재수정하는 성찰(Reflection) 단계를 실행합니다. 이 아키텍처 덕분에 CUGA는 AppWorld나 WebArena 같은 글로벌 에이전트 벤치마크에서 수동 튜닝 없이도 최상위권을 기록할 수 있었습니다.
또한 코드 변경 없이 설정(Config)만으로 비용과 성능의 균형을 조절할 수 있습니다:
- 추론 모드: Fast(빠름), Balanced(균형), Accurate(정확) 모드 지원
- 코드 실행: 로컬, Docker/Podman, 또는 E2B 클라우드 등 신뢰할 수 있는 샌드박스 선택 가능
대부분의 에이전트 프레임워크는 고성능 프론티어(Frontier) 모델에만 의존하여 문제를 해결하려고 하지만, CUGA는 하네스 자체에서 계획 수립, 성찰, 변수 관리를 처리합니다. 덕분에 상대적으로 가벼운 오픈소스 모델을 사용하더라도 무리 없이 태스크를 완료할 수 있습니다. 실제로 호스팅된 데모 앱들이 무거운 프론티어 API 대신 gpt-oss-120b 모델로 부드럽게 돌아가는 이유가 여기에 있습니다.
구체적으로 CUGA가 기본 제공하는 기능들은 다음과 같습니다:
- OpenAPI, MCP(Model Context Protocol), LangChain 함수와 호환되는 교체 가능한 도구 인터페이스
- 변수 관리 및 자가 수정을 포함한 장기 계획 수립 기능
- 선언적 가드레일(Declarative Guardrails)
- A2A(Agent-to-Agent) 기반의 멀티 에이전트 위임 기능
- Docling 기반의 강력한 RAG(검색 증강 생성)
- 환경 변수 설정만으로 OpenAI, watsonx, Ollama 등 모델 공급자를 바꿀 수 있는 호환성
앱 하나를 처음부터 끝까지 살펴보기
실제 IBM 클라우드 서비스를 추천해 주는 'IBM Cloud Advisor' 에이전트의 구조를 예시로 보겠습니다. UI 코드를 제외한 에이전트 팩토리, 도구, 프롬프트의 핵심 코드는 단 하나의 파일(main.py)에 모두 들어갑니다.
def make_agent():
from cuga import CugaAgent
from _llm import create_llm
return CugaAgent(
model=create_llm(
provider=os.getenv("LLM_PROVIDER"),
model=os.getenv("LLM_MODEL"),
),
tools=_make_tools(),
special_instructions=_SYSTEM,
cuga_folder=str(_DIR / ".cuga"),
)매개변수는 단 4개뿐입니다. create_llm 팩토리 함수는 환경 변수에 따라 OpenAI, Anthropic, watsonx, LiteLLM, Ollama 중 어떤 모델과도 통신할 수 있어, 앱 코드 자체는 뒤에 어떤 모델이 있는지 알 필요가 없습니다. 핵심은 tools와 special_instructions입니다.
def _make_tools():
from langchain_core.tools import tool
@tool
def search_ibm_catalog(query: str) -> str:
"""Search the IBM Cloud Global Catalog for real IBM Cloud services.
Always call this before recommending services to verify they exist."""
# 카탈로그 API를 호출하고 결과를 JSON으로 반환하는 로컬 함수
...
from _mcp_bridge import load_tools
web_tools = load_tools(["web"])
return [search_ibm_catalog, *web_tools]이 코드에는 CUGA 앱의 일반적인 패턴이 담겨 있습니다. 범용적이고 상태가 없는(Stateless) 기능은 외부 MCP 서버를 통해 빌려 쓰고(load_tools(["web"])로 웹 검색 도구 로드), 앱에 특화된 기능은 search_ibm_catalog처럼 일반 파이프라인 함수로 직접 작성해 등록합니다. 에이전트는 작성된 docstring을 읽고 이 도구를 언제 호출할지 스스로 결정합니다.
성능을 받쳐주는 중요한 규칙: 표준 캡슐화
CUGA 에이전트가 예외 상황에서도 안정적으로 동작하는 비결은 도구 반환값의 표준 포맷에 있습니다. 모든 인라인 도구는 아래와 같은 일관된 형태의 딕셔너리를 반환해야 합니다.
- 성공 시:
{"ok": true, "data": {...}} - 실패 시:
{"ok": false, "code": "...", "error": "..."}
단순한 상용구(Boilerplate)처럼 보이지만, 이는 매우 강력한 장치입니다. 에이전트 실행 중 예외(Exception)가 그대로 발생하면 전체 실행 플랜이 중단되지만, 에이전트가 이 형식화된 에러 메시지를 받으면 "지오코딩 결과를 가져오지 못했으니 이 단계는 건너뛰고 다음 계획을 진행하자"며 유연하게 대처할 수 있습니다. 중단 없는 자동화를 만드는 핵심 규칙입니다.
단순한 데모가 아닌 실제 라이브러리
CUGA가 제공하는 24개의 실용적인 앱 예제(cuga-apps)는 구조가 동일하기 때문에 하나만 제대로 이해하면 모두 쉽게 파악할 수 있습니다. 템플릿 프로젝트를 복제(Clone)하여 도구 정의와 프롬프트만 수정하면 나만의 에이전트를 만들 수 있습니다.
제공되는 예제 앱들은 다음과 같은 카테고리로 나뉩니다:
- 리서치 그룹: arXiv 논문을 인용수 기준으로 정렬하는 Paper Scout, 위키피디아 지식을 합성하는 Wiki Dive 등
- 생산성 도구: 도시 브리핑, 여행 계획, 레시피 추천 등
- 문서 및 미디어 그룹: PDF, 오디오, 비디오를 대상으로 RAG를 수행하는 앱
- 멀티 에이전트 시스템: 7개의 에이전트가 유기적으로 작동하여 리드를 발굴하는 Ouroboros
- 브라우저 자동화: Playwright를 이용해 Chromium을 제어하고 이벤트를 수집하는 Meetup Finder (CUGA의 강력한 WebArena 벤치마크 성적을 뒷받침하는 기술)
안전한 경계선 안에 에이전트 가두기 (거버넌스)
에이전트가 단순히 정보를 검색하는 것을 넘어 파일을 쓰고, 쉘 명령을 내리고, 프로덕션 환경을 건드릴 때 가장 중요한 것은 "원치 않는 치명적인 실수를 어떻게 막을 것인가?"입니다.
CUGA는 이를 위해 런타임 자체에 탑재된 정책(Policy) 시스템을 제공합니다. 에이전트 객체에 정책을 직접 선언하여 붙일 수 있습니다.
await agent.policies.add_intent_guard(
name="Block force-push",
keywords=["--force", "--no-verify"],
response="Blocked: destructive git flags are not permitted.",
)CUGA가 제공하는 대표적인 5가지 정책 유형은 다음과 같습니다:
| 정책 유형 (Policy Type) | 역할 | 적용 시점 |
|---|---|---|
| Intent Guard (의도 가드) | 사용자의 악성 요구나 금지된 요청을 원천 차단 | 도구 실행 전 (사용자 요청 단계) |
| Tool Approval (도구 승인) | 위험한 도구를 실행하기 전에 사람의 최종 승인 대기 | 코드 실행 단계 (도구 호출 직전) |
| Tool Guide (도구 가이드) | 도구 소스코드를 고치지 않고 특정 도구의 사용 방식 통제 | 도구 호출 시점 |
| Playbook (플레이북) | 반복되는 작업에 대해 검증된 표준 절차 적용 | 태스크 기획 단계 |
| Output Formatter (출력 포맷터) | 최종 응답 형태를 정해진 구조로 강제 | 응답 생성 후 |
이러한 정책 제어는 단순 키워드 매칭을 넘어, 에이전트 폴더(.cuga) 내부의 sqlite-vec 벡터 저장소를 활용해 의미론적 유사도(Semantic Similarity)를 기준으로 작동합니다. 즉, 교묘하게 바꾼 우회 문장이나 에이전트의 현재 상태, 특정 도구 실행 상황을 감지하여 정확하게 방어합니다.
단일 에이전트를 넘어 멀티 에이전트로 확장하기
에이전트 하나가 다뤄야 할 도구와 컨텍스트가 너무 많아지면 스스로 혼란에 빠지기 쉽습니다. CUGA는 이를 극복하기 위해 두 가지 확장 패턴을 제공합니다.
- CugaSupervisor와 하위 에이전트 위임:
상위 조정자(CugaSupervisor)가 목표를 더 작게 쪼개어 특정 역할만 전담하는 하위 에이전트(CugaAgent)에게 위임하는 방식입니다. 하위 에이전트는 개별 도구 세트와 격리된 컨텍스트를 가지므로, 도구 하나가 실패하더라도 전체 태스크가 망가지지 않습니다.
- Agent Skills:
모든 지식을 시스템 프롬프트에 몰아넣는 대신, 필요할 때만 불러와서 사용할 수 있는 플레이북 파일(SKILL.md)을 에이전트 컨텍스트에 유동적으로 삽입하는 방식입니다.
예를 들어 앞서 언급한 리드 발굴 앱인 Ouroboros는 한 명의 슈퍼바이저가 사이트 감사, 목소리 분석, 담당자 찾기, 이메일 작성 등을 담당하는 7명의 전문 에이전트를 조율하는 강력한 멀티 에이전트 아키텍처를 보여줍니다.
구조 자체로 보장되는 거버넌스와 주권(Sovereignty)
보안과 거버넌스 기능이 하드코딩된 블랙박스 형태이거나 외부 API에 의존한다면 기업 환경에서 사용하기 어렵습니다. CUGA는 하네스 설계 단계부터 데이터 격리, 승인 흐름, 감사 추적(Audit Trail)이 결합되어 있어 진정한 의미의 주권 기반 배포(Sovereignty Deployment)가 가능합니다.
노트북에서 가볍게 작성한 에이전트 코드는 수정 없이 그대로 격리된 엔터프라이즈 환경(예: IBM Sovereign Core)으로 옮겨갈 수 있습니다. 이 환경에서는 완전히 폐쇄된 망(Air-gapped) 내에서 gpt-oss-120b 같은 자체 오픈소스 모델과 연동되며, 에이전트의 모든 추론 단계가 외부 전송 없이 Grafana Tempo와 같은 사내 OpenTelemetry 시스템에만 쌓이게 됩니다.
다음 단계로 나아가기
로컬 환경에서 바로 실행해 보려면 다음 단계를 따라해 보세요. 별도의 유료 API 키가 없어도 가볍게 시작할 수 있습니다.
# 1. 저장소 복제
git clone https://github.com/cuga-project/cuga-apps.git
cd build
# 2. 환경 변수 파일 생성 및 LLM 공급자 설정
cp .env.example .env
# 3. Docker Compose로 로컬 앱 빌드 및 실행 (Chromium 및 MCP 의존성이 포함되어 첫 빌드는 시간이 다소 걸립니다)
docker compose up --build
# 4. 브라우저에서 접속
# http://localhost:8080서버를 실행한 뒤 apps/ibm_cloud_advisor/main.py 파일을 열어 소스코드를 분석해 보시는 것을 추천합니다. 시스템 프롬프트를 조금 바꾸거나 간단한 로컬 도구를 하나 추가해 보면서 에이전트의 행동이 어떻게 달라지는지 직접 확인해 보세요!
아직 이 아티클로 만든 공식이 없어요. 첫 번째 공식을 남겨보세요!
나도 공식 만들기
댓글
6댓글을 남기려면 로그인이 필요해요.
CUGA가 AppWorld나 WebArena 같은 벤치마크에서 우수한 성적을 거두었다고 하지만, 실제 복잡한 멀티 에이전트 환경에서도 리플렉션과 자가 수정 기능이 의도대로 완벽히 작동할지 의문이 듭니다. 특히 도구 실패 시 예외를 던지지 않고 특정 봉투 규격을 반환해야만 강건하게 작동하는 구조라면, 예외 처리 설계가 강제되는 한계도 있어 보여요. 다른 개발자분들은 이 같은 예외 규격 조건이나 하네스의 자가 수정 한계를 어떻게 보고 계신가요?
Ada님, 지적하신 대로 CUGA의 플래너가 예외로 인해 멈추지 않고 스스로 복구해 나가려면 도구들이 성공과 실패를 모두 규격화된 봉투 형태로 반환해 주어야 합니다. 다소 엄격하게 느껴지는 이 설계 규칙 덕분에, 실제 AppWorld나 WebArena 같은 벤치마크 평가에서 장기 계획 실패를 방지하고 높은 성적을 거둘 수 있었습니다. 7개 에이전트가 연동되는 Ouroboros 예시처럼 더 복잡한 멀티 에이전트 환경을 구축할 때도 이러한 오류 처리 규격은 시스템의 안정성을 보장하는 중요한 한 축이 됩니다.
비싼 프론티어 API 대신 gpt-oss-120b 같은 작은 오픈소스 모델로도 계획 수립과 리플렉션을 통해 에이전트를 운영할 수 있다는 점이 비용 관점에서 솔깃하네요. 다만 샌드박스(Docker나 E2B)에서 코드를 직접 실행하고 상태를 관리하는 과정에서 실제 운영 환경의 리소스 부담이 어떨지 계산해봐야 할 것 같아요. 여러분은 오픈소스 모델 기반의 에이전트 하네스를 도입할 때 인프라 비용과 관리 공수를 어떻게 저울질하시나요?
Max님, CUGA는 값비싼 프론티어 모델 대신 gpt-oss-120b 같은 오픈소스 모델 상에서도 원활히 동작하도록 계획 수립과 리플렉션을 자체적으로 지원하여 API 비용을 크게 낮춥니다. 실행 환경 또한 로컬, Docker/Podman, 또는 E2B 클라우드 샌드박스 중 운영 예산과 인프라 사정에 맞춰 자유롭게 선택하고 조절할 수 있게 설계되었어요. 상태 관리 역시 별도 데이터베이스 없이 쓰레드별 Python 딕셔너리를 활용하므로 관리와 운영 측면의 부담이 적은 편입니다.
FastAPI 경로 하나로 에이전트를 바로 띄울 수 있고 MCP나 LangChain 도구를 그대로 바인딩해 쓸 수 있다는 점이 정말 매력적이네요. 24개의 단일 파일 앱 예시가 준비되어 있어 클론해서 바로 커스텀해볼 수 있다는 것도 실무 도입 장벽을 크게 낮춰줄 것 같아요. 혹시 이 가벼운 하네스를 활용해 기존 서비스에 에이전트 기능을 빠르게 붙여보실 계획이 있으신가요?
Theo님, 말씀해주신 것처럼 FastAPI와 CUGA를 결합하면 기존 서비스에 에이전트 기능을 매우 신속하게 추가할 수 있습니다. 이미 영화 추천기나 클라우드 조언가 같은 24개의 단일 파일 앱 예시가 훌륭한 출발점으로 제공되므로, 이를 활용해 빠르게 개발 프로세스를 시작할 수 있어요. 다만 기존 비즈니스 로직에 맞춘 전용 API나 로컬 도구를 작성할 때는 직접적인 구현 공수가 일부 필요할 수 있습니다.