
Helium Agent 설계하기
요약
Helium Agent는 Python 기반의 가볍고 터미널 중심적인 AI 에이전트 설계 방식을 소개합니다. 복잡한 프레임워크 대신 최소한의 복잡성을 지향하며, XML 태그 기반의 프롬프트 도구 호출 방식을 통해 모델 불가지론적 특성을 확보했습니다.
핵심 포인트
- Python 기반의 가볍고 오버헤드가 적은 터미널 중심 에이전트
- XML 태그를 활용한 프롬프트 기반 도구 호출로 모델 호환성 극대화
- RAG, 에이전트 루프, 계층적 하위 에이전트 위임 기능 포함
- 의도적으로 평면적인 아키텍처를 채택하여 코드 파악 용이성 증대
1. Helium Agent란 무엇인가?
Helium Agent는 Python으로 작성된 가볍고 터미널 중심적인 AI 에이전트입니다.
Codex나 OpenCode를 떠올리되, 오버헤드(overhead)가 없는 형태라고 생각하면 됩니다. PyPI에 게시되어 있으며 (pip install helium-agent), 어느 디렉토리에서나 helium .으로 실행할 수 있고, 클라우드나 로컬을 가리지 않고 모든 OpenAI 호환 LLM 엔드포인트(endpoint)와 작동합니다.
핵심 기능:
• 도구 호출 (tool calling) 기능이 포함된 범용 채팅
• 에이전트 루프 (agentic loop)를 통한 장기 실행 코딩 작업
• 다중 소스 증거 수집 및 인용을 통한 심층 조사
• 파일 기반 Q&A를 위한 RAG (Retrieval-Augmented Generation, 검색 증강 생성)
• 세션 간 지속적인 메모리
• 계층적 하위 에이전트 위임 (Hierarchical subagent delegation)
• SKILL.md 파일을 통한 플러그인 시스템
설계 철학은 '최소 실행 가능한 복잡성 (minimum viable complexity)'입니다. 모든 기능은 추측에 기반한 추상화 없이, 작동하는 가장 단순한 방식으로 구현되었습니다.
2. 아키텍처 개요
아키텍처는 의도적으로 평면적(flat)입니다. 프레임워크도, 의존성 주입 (dependency injection) 컨테이너도, 이벤트 버스 (event bus)도 없습니다. 모듈들은 서로를 직접 임포트합니다 (순환 참조가 발생할 수 있는 경우에는 지연 임포트 (lazy imports)를 사용합니다). 이는 버그가 아니라 의도된 기능입니다. 코드베이스를 한 번에 파악하며 탐색할 수 있습니다.
3. 설계 선택 사항
3.1 프롬프트 기반 도구 호출 (Prompt-Based Tool Calling)
결정: Helium은 OpenAI의 함수 호출 (function calling) API를 사용하지 않습니다. 대신, LLM이 시스템 프롬프트 (system prompt)를 통해 도구 호출을 <action>{"tool": "...", "args": {...}}</action> XML 태그 형태로 출력하도록 지시합니다.
이유:
• 모델 불가지론적 (Model agnostic). 지시사항을 따를 수 있는 모든 LLM(Large Language Model)과 작동합니다 — 로컬 llama.cpp 모델, OpenRouter 무료 티어, 미세 조정된 (fine-tuned) 모델 등. 제공업체가 특정 도구 호출 스키마 (tool-calling schema)를 지원할 필요가 없습니다.
• 완전한 제어 (Full control). 도구 프롬프트 (tool prompt)는 일반 문자열이며, 코드를 수정하지 않고도 편집할 수 있습니다. 새로운 도구를 추가하는 것은 함수와 설명을 추가하는 것을 의미하며, 스키마 생성이나 API 협상이 필요하지 않습니다.
• 더 간단한 디버깅 (Simpler debugging). LLM의 가공되지 않은 (raw) 출력이 사람이 읽을 수 있는 형태입니다. 모델이 정확히 무엇을 시도했는지 확인할 수 있습니다.
트레이드오프 (Trade-off): JSON 추출이 취약해집니다. 모델이 약간 잘못된 형식의 JSON을 출력하거나, 마크다운 코드 블록으로 감싸거나, 추가 텍스트를 포함할 수 있습니다. 이로 인해 utils/parser.py에 extract_json()이 도입되었으며, 이는 75줄에 달하는 폴백 (fallback) 연쇄 구조를 가집니다:
이 방식은 실제 작동에는 문제가 없으나 유지보수 측면에서 부담이 됩니다. 새로운 모델에서 발생하는 모든 새로운 엣지 케이스 (edge case)는 또 다른 폴백 분기를 의미하기 때문입니다.
3.2 에이전틱 루프 (Agentic Loop)에서의 의존성 주입 (Dependency Injection)
결정: AgenticLoop는 LLM 및 도구 모듈을 직접 임포트(import)하는 대신, 두 개의 호출 가능한 객체 (callables)인 ask_model과 execute_tool_call을 인자로 받습니다.
class AgenticLoop:
def __init__(self, ask_model, execute_tool_call, max_turns=6):
self.ask_model = ask_model
...
이유: 이 단 하나의 결정이 시스템 전체의 조합 가능성 (composability)을 가능하게 합니다:
- 일반 채팅 (general chat) 루프는 표준 LLM과 도구 실행을 사용합니다.
- 코딩 워크플로우 (coding workflow) (
/code)는 도구 자동 승인 및max_turns=30설정으로AgenticLoop를 생성합니다. - **하위 에이전트 (Subagents)**는 필터링된 도구 세트(부모가 허용한 도구만 포함)를 사용하여 자신만의
AgenticLoop를 생성합니다. - **스킬 (Skills)**은 자신의
SKILL.md본문을 시스템 프롬프트 (system prompt)에 주입하고 새로운AgenticLoop를 실행합니다.
서브클래스 (subclasses), 전략 패턴 (strategy pattern), 설정 객체 (configuration objects)도 필요 없습니다. 오직 두 개의 호출 가능한 객체만 있으면 됩니다.
3.3 전역 가변 상태 (Global Mutable State, 설계 의도)
결정 사항: 여러 모듈이 모듈 수준의 싱글톤 (singleton)을 사용합니다:
core/llm.py내의conversation_history(list)tools/memory_ops.py내의_managertools/todo_tools.py내의_todo_listtools/subagent_tools.py내의_manager
이유: Helium은 단일 사용자, 단일 스레드(single-threaded) 터미널 에이전트입니다. 어떤 시점에도 정확히 하나의 대화, 하나의 메모리 저장소, 하나의 할 일 목록(todo list)만 존재합니다. 전역 상태(Global state)는 이러한 현실을 가장 단순하게 표현하는 방식입니다.
트레이드오프 (Trade-off): 이는 동시성 (concurrency)을 배제합니다. 두 개의 서브에이전트 (subagents)를 병렬로 실행할 수 없는데, 이들이 동일한 대화 기록과 도구 상태를 공유하기 때문입니다. 현재로서는 수용 가능한 수준이지만, 비동기 (async) 지원을 위해 가장 먼저 변경해야 할 부분입니다.
3.4 3단계 권한 모델 (Three-Tier Permission Model)
- safe (안전) — 자동 실행. 읽기, 검색, 메모리 조회, 할 일 목록 쿼리.
- risky (위험) — 사용자 확인 필요. 파일 쓰기, bash, 앱 실행.
- conditional (조건부) (bash 전용) —
is_command_safe()가 명령 문자열을 검사합니다.ls,cat,pwd는 안전합니다.rm,mv,chmod는 위험합니다.
--nuclear / --auto-approve 플래그는 모든 검사를 우회합니다. CI(지속적 통합)에는 유용하지만, 프로덕션(production) 환경에서는 위험합니다.
이것이 중요한 이유: LLM은 도구 호출 (tool calls)을 환각 (hallucinate)할 수 있습니다. 권한 게이트 (permission gates)가 없다면, 혼란에 빠진 모델이 파일을 삭제하거나 임의의 명령을 실행할 수 있습니다. 3단계 시스템은 해롭지 않은 작업은 자유롭게 흐르게 두면서, 피해를 줄 수 있는 모든 행위는 차단합니다.
3.5 SDK 미사용 — 순수 HTTP만 사용
결정 사항: 모든 LLM 통신은 수동 SSE 파싱을 포함한 requests.post()를 사용합니다. OpenAI SDK, httpx, 또는 어떠한 추상화 계층 (abstraction layer)도 사용하지 않습니다.
이유:
- 더 적은 의존성 (Fewer dependencies).
requirements.txt가 작게 유지됩니다. 각 의존성은 잠재적인 장애 지점 (breakage point)이 될 수 있습니다. - 완전한 투명성 (Full transparency). API로 무엇이 전송되고 무엇이 돌아오는지 정확히 확인할 수 있습니다.
- 제공자 유연성 (Provider flexibility). OpenAI 형식의 채팅 완성 (chat completions)을 수락하는 모든 엔드포인트가 작동합니다. SDK 버전 고정이나 API 호환성 매트릭스 (compatibility matrix)를 고려할 필요가 없습니다.
트레이드오프 (Trade-off): 수동 SSE 파싱 (SSE parsing)은 까다롭습니다. utils/check_llm_api.py에 있는 stream_openrouter_response()가 청크 전송 인코딩 (chunked transfer encoding), data: [DONE] 마커, 그리고 에러 응답을 처리합니다. 이는 잘 테스트된 SDK라면 대신 처리해 주었을 코드입니다.
3.6 SKILL.md 플러그인 시스템 (Plugin System)
결정 사항: 스킬 (Skills)은 YAML 프론트매터 (YAML frontmatter)가 포함된 마크다운 (markdown) 파일이며, ~/.config/helium-agent/skills/ 및 .helium/skills/에서 발견됩니다.
---
name: caveman
trigger: /caveman
...
이유:
- 코드 불필요 (Zero code required). 누구나 마크다운 파일을 작성함으로써 스킬을 생성할 수 있습니다.
- 두 가지 유형: 슬래시 명령어 (Slash commands,
/name으로 트리거됨)와 문맥적 스킬 (contextual skills, 관련이 있을 때 시스템 프롬프트에 주입됨)이 있습니다. - 스킬 범위 도구 (Skill-scoped tools). 스킬은
allowed_tools를 선언하여 해당 스킬의 문맥 내에서 LLM이 할 수 있는 작업을 제한할 수 있습니다.
이것은 가능한 가장 단순한 플러그인 시스템입니다. Python 엔트리 포인트 (entry points), 등록 API (registration APIs), 마크다운 자체를 제외한 설정 파일이 전혀 없습니다.
4. 오케스트레이션 (Orchestration): 모든 것이 결합되는 방식
4.1 에이전틱 루프 (The Agentic Loop)
Helium의 핵심은 LLM과 도구 시스템 사이를 교대로 전환하는 턴 기반 루프 (turn-based loop)입니다:
주요 파라미터 (Key parameters):
- 일반 채팅 (General chat):
max_turns = 6. 제어 불능의 루프(runaway loops) 없이 몇 번의 도구 호출 (tool calls)을 수행하기에 충분한 수준입니다. - 코딩 워크플로우 (Coding workflow):
max_turns = 30. 검증을 포함한 다중 파일 편집 (multi-file edits)을 수행하기에 충분히 긴 수준입니다. - 온도 (Temperature):
0.3. 일관성을 유지할 만큼 낮으면서도 자연스러운 언어를 생성할 만큼 높은 수준입니다. - 히스토리 (History):
MAX_HISTORY = 10. 마지막 10개의 메시지가 유지됩니다. 그 이전 메시지들은 삭제됩니다.
루프는 다음 조건에서 종료됩니다:
- LLM이
<action>태그 없이 응답할 때 (최종 답변). max_turns에 도달했을 때 (타임아웃).- 도구 호출 (tool call)이 유효하지 않고 LLM이 복구할 수 없을 때.
4.2 리서치 파이프라인 (The Research Pipeline)
심층 리서치 (Deep research)는 Helium에서 가장 복잡한 오케스트레이션 (orchestration)입니다. 이는 반복 (iteration) 과정을 포함하는 다단계 파이프라인입니다:
이중 제공자 검색 (Dual-provider search, DuckDuckGo + SearxNG)은 중복성 확보를 위한 조치입니다. 한 제공자가 다운되었거나 속도 제한 (rate-limited)에 걸리면, 다른 제공자가 그 공백을 메웁니다. SourcePolicy는 고품질의 근거를 우선시하기 위해 소스 유형별로 URL에 점수를 매깁니다 (공식 문서 > 블로그 > 포럼).
4.3 서브에이전트 시스템 (The Subagent System)
6월 15일 세션에서 추가된 서브에이전트 (subagent) 시스템은 계층적 위임 (hierarchical delegation)을 가능하게 합니다:
핵심 설계 결정 사항:
- AgenticLoop (에이전트 루프) 재사용. 새로운 실행 엔진을 만들지 않습니다. 서브에이전트(subagent)는 메인 에이전트와 동일한 루프를 실행하되, 필터링된 도구 세트(tool set)만 사용합니다.
- 매니저 계층에서의 도구 필터링 (Tool filtering).
_wrap_execute_tool()이 도구 호출을 가로채서 서브에이전트의allowed_tools에 포함되지 않은 모든 호출을 거부합니다. LLM은 자신이 제한되었다는 사실을 알지 못합니다. - ID는 고유하지만, 이름은 고유하지 않습니다. 서로 다른 ID를 가진 다섯 개의 "researcher" 서브에이전트를 가질 수 있습니다. 이를 통해 동일한 역할의 여러 인스턴스를 생성할 수 있습니다.
- 지연 임포트 (Lazy imports).
tools/registry.py는 순환 의존성 체인(registry → subagent_tools → subagent_manager → llm → registry)을 피하기 위해 서브에이전트 도구를 지연 임포트합니다.
4.4 메모리 시스템 (The Memory System)
3계층 접근 방식의 의미는 다음과 같습니다:
- **플랫 스토어 (Flat store)**는 키워드 검색을 잘 처리합니다 ("내가 선호하는 언어가 무엇인가?").
- **지식 그래프 (Knowledge graph)**는 SPO 트리플렛(triplets)을 통해 관계 쿼리("내가 무엇을 선호하는가?")를 처리합니다.
- **대화 스토어 (Conversation store)**는 장기 메모리를 오염시키지 않으면서 세션 컨텍스트(session context)를 제공합니다.
세 가지 모두 단일 SQLite 연결을 공유합니다. 메모리는 프로젝트 디렉토리 내에서 "지속적(persistent)"이지만(memory.db로 저장됨), 프로젝트 간에는 공유되지 않습니다.
5. 실수와 방지 방법
5.1 순환 임포트 지옥 (Circular Import Hell)
발생한 문제: 서브에이전트 시스템을 추가할 때, tools/registry.py → tools/subagent_tools.py → core/subagent_manager.py → core/llm.py → tools/registry.py로 이어지는 임포트 체인이 순환 의존성을 생성하여 시작 시 크래시(crash)가 발생했습니다.
해결책: tools/registry.py에 지연 임포트(Lazy imports) 적용:
def _create_subagent_lazy(*args, **kwargs):
from tools.subagent_tools import create_subagent
return create_subagent(*args, **kwargs)
방지 방법:
- 새로운 모듈을 추가하기 전에 임포트 그래프 (import graph)를 그리세요.
- 만약 A → B → C → A와 같은 순환 참조가 불가피하다면, 의존성이 가장 적은 지점(보통 리프 모듈 (leaf module))에서 사이클을 끊으세요.
- 도구 시스템 (tool systems)의 경우, 직접적인 임포트 (direct imports) 대신 레지스트리/옵저버 패턴 (registry/observer pattern)을 고려하세요.
5.2 JSON 추출의 취약성 (JSON Extraction Fragility)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기




