아키텍처 설계
왜 아키텍처가 중요한가
섹션 제목: “왜 아키텍처가 중요한가”Agent Harness는 단순한 API 래퍼가 아닙니다. 프로덕션 환경에서 에이전트를 안정적으로 운영하려면 관심사의 명확한 분리 와 교체 가능한 컴포넌트 구조 가 필수입니다. 잘못 설계된 harness는 모델을 교체하거나 툴을 추가할 때마다 전체 코드를 수정해야 하는 상황을 만듭니다.
동일한 모델을 사용해도 컨텍스트 구성, 도구, 검증 루프 같은 harness 변수를 바꾸면 벤치마크 결과가 달라질 수 있습니다. 따라서 아키텍처 결정은 일반적인 향상률을 가정하기보다, 모델과 과제 표본을 고정한 대조 실험으로 검증해야 합니다.
4계층 아키텍처
섹션 제목: “4계층 아키텍처”┌─────────────────────────────────────────────────────┐│ Entry & UI Layer ││ TUI / Web UI │ ConfigManager │ SessionManager ││ │ ApprovalManager │├─────────────────────────────────────────────────────┤│ Agent Core Layer ││ MainAgent (parameterized) │ SubAgentSpec ││ AgentDependencies │ SubAgentDeps │├─────────────────────────────────────────────────────┤│ Tool & Context Layer ││ ToolRegistry │ PromptComposer │ MCPClient ││ ContextCompactor │ EventReminder │├─────────────────────────────────────────────────────┤│ Persistence Layer ││ SessionStore │ ConfigResolver │ OperationLog ││ ProviderCache │ GitRollback │└─────────────────────────────────────────────────────┘각 계층은 아래 계층에만 의존 하며, 위 계층은 인터페이스를 통해 접근합니다. 이 단방향 의존성이 테스트 용이성과 교체 가능성을 보장합니다.
계층별 책임
섹션 제목: “계층별 책임”| 계층 | 핵심 책임 | 주요 컴포넌트 | |------|-----------|---------------| | Entry & UI | 부트스트랩, 사용자 입출력, 승인 요청 | ConfigManager, SessionManager, ApprovalManager | | Agent Core | 에이전트 생명주기, 서브에이전트 조율 | MainAgent, SubAgentSpec, Dependency Injection | | Tool & Context | 툴 실행, 프롬프트 조합, 컨텍스트 관리 | ToolRegistry, PromptComposer, MCP Client | | Persistence | 상태 저장, 설정 해석, 롤백 | SessionStore, ConfigResolver, OperationLog |
설계 원칙
섹션 제목: “설계 원칙”1. 관심사 분리 (Separation of Concerns)
섹션 제목: “1. 관심사 분리 (Separation of Concerns)”각 계층은 하나의 책임만 집니다. UI 계층은 렌더링만, Core 계층은 에이전트 로직만 담당합니다. 이 원칙을 어기면 UI가 바뀔 때 에이전트 로직도 함께 수정해야 하는 결합이 발생합니다.
# 나쁜 예: UI 계층에 에이전트 로직 혼재async function handleUserInput(input: string) { renderSpinner() const response = await llm.complete(buildPrompt(input)) // ← 잘못된 위치 renderResponse(response)}
# 좋은 예: 계층 경계 유지async function handleUserInput(input: string) { renderSpinner() const response = await agentCore.run(input) // ← 위임 renderResponse(response)}2. 의존성 주입 (Dependency Injection)
섹션 제목: “2. 의존성 주입 (Dependency Injection)”MainAgent 는 구체적인 구현 대신 인터페이스 를 받습니다. 이를 통해 테스트에서 mock을, 프로덕션에서 실제 구현을 주입할 수 있습니다.
interface AgentDependencies { llmClient: LLMClient; toolRegistry: ToolRegistry; sessionStore: SessionStore; promptComposer: PromptComposer;}
class MainAgent { constructor(private deps: AgentDependencies) {}}3. 단일 구체 클래스 (Single Concrete Class)
섹션 제목: “3. 단일 구체 클래스 (Single Concrete Class)”상속 계층 대신 생성 파라미터로 행동을 변화 시킵니다. PlannerAgent, ExecutorAgent 같은 별도 클래스를 만드는 대신, MainAgent 에 SubAgentSpec 을 전달해 역할을 정의합니다.
이 패턴의 장점은 모든 에이전트가 동일한 실행 경로를 공유하므로, 버그 수정이 전체에 즉시 반영된다는 점입니다.
Entry & UI 계층 상세
섹션 제목: “Entry & UI 계층 상세”부트스트랩 시점에 공유 매니저들을 초기화합니다.
// 부트스트랩 진입점async function bootstrap(config: AppConfig) { const configManager = new ConfigManager(config); const sessionManager = new SessionManager(configManager); const approvalManager = new ApprovalManager(configManager);
const ui = config.mode === 'tui' ? new TUIAdapter(approvalManager) : new WebUIAdapter(approvalManager);
return new Application({ configManager, sessionManager, approvalManager, ui });}UICallback 인터페이스를 통해 TUI와 Web UI가 동일한 계약을 구현합니다. Agent Core는 구체적인 UI 타입을 알 필요가 없습니다.
Persistence 계층과 설정 해석
섹션 제목: “Persistence 계층과 설정 해석”설정은 우선순위 계층 을 통해 해석됩니다.
프로젝트 로컬 (.agent/config.json) ↓ 없으면사용자 글로벌 (~/.config/agent/config.json) ↓ 없으면환경 변수 (AGENT_MODEL, AGENT_TOKEN 등) ↓ 없으면내장 기본값이 계층적 해석은 로컬 오버라이드를 허용하면서도 글로벌 기본값을 유지하게 해줍니다. CI/CD 환경에서는 환경 변수로, 개발자 로컬에서는 프로젝트 설정으로 제어할 수 있습니다.
기술 선택 기준
섹션 제목: “기술 선택 기준”아키텍처를 설계할 때 각 계층의 구현 기술을 선택하는 기준이 있습니다. 단순히 인기 있는 기술을 고르는 것이 아니라, 계층의 특성과 요구사항에 맞는 선택이 필요합니다.
계층별 기술 선택 가이드
섹션 제목: “계층별 기술 선택 가이드”| 계층 | 고려 기준 | 추천 방향 | |------|-----------|-----------| | Entry & UI | 렌더링 성능, 플랫폼 지원 범위 | TUI는 ink/blessed, Web은 React/Vue | | Agent Core | 타입 안전성, 비동기 처리 | TypeScript + async/await 패턴 | | Tool & Context | 확장성, 플러그인 지원 | 인터페이스 기반 등록 패턴 | | Persistence | 내구성, 조회 성능 | SQLite(로컬), PostgreSQL(팀) |
LLM 클라이언트 선택 기준
섹션 제목: “LLM 클라이언트 선택 기준”LLM 클라이언트는 Agent Core의 핵심 의존성입니다. 다음 기준으로 평가합니다.
1. 스트리밍 지원 여부 — 긴 응답에서 UX에 직접 영향2. 툴 호출(tool_use) 네이티브 지원 — 파싱 오버헤드 감소3. 재시도 및 백오프 내장 — 안정성 확보4. 멀티모달 지원 — 이미지/문서 처리 필요 시5. 비용 추적 API — 토큰 사용량 모니터링확장성 고려사항
섹션 제목: “확장성 고려사항”단일 에이전트 harness가 팀 환경으로 성장할 때 가장 먼저 병목이 되는 지점이 있습니다.
수평 확장 패턴
섹션 제목: “수평 확장 패턴”단일 프로세스 (초기) MainAgent ─── ToolRegistry
멀티 워커 (팀 규모) LoadBalancer ├── Worker 1: MainAgent ─── ToolRegistry (shared) ├── Worker 2: MainAgent ─── ToolRegistry (shared) └── Worker 3: MainAgent ─── ToolRegistry (shared) ↓ Persistence Layer (공유 DB)수평 확장 시 상태를 Persistence 계층에 완전히 위임 해야 합니다. Agent Core가 인메모리 상태를 갖고 있으면 워커 간 세션이 공유되지 않습니다.
툴 레지스트리의 동적 확장
섹션 제목: “툴 레지스트리의 동적 확장”프로덕션에서는 런타임에 툴을 추가하거나 비활성화해야 하는 경우가 생깁니다.
class ToolRegistry { private tools = new Map<string, Tool>();
register(tool: Tool): void { this.tools.set(tool.name, tool); }
unregister(name: string): void { this.tools.delete(name); }
getAvailableTools(context: AgentContext): Tool[] { // 컨텍스트에 따라 사용 가능한 툴 필터링 return Array.from(this.tools.values()) .filter(tool => tool.isAvailable(context)); }}이 패턴은 특정 에이전트에게만 특정 툴을 노출하거나, 위험한 툴을 사용자 승인 없이는 실행 불가능하게 만드는 데 사용합니다.
컨텍스트 압축과 메모리 관리
섹션 제목: “컨텍스트 압축과 메모리 관리”긴 대화가 이어지면 컨텍스트 윈도우가 포화됩니다. Persistence 계층과 Tool & Context 계층이 협력해 이를 해결합니다.
대화 길이가 임계치 초과 ↓ContextCompactor 활성화 ↓최근 N개 메시지 + 중요 요약 유지 ↓나머지는 SessionStore에 아카이브 ↓필요 시 검색(retrieve)으로 복원컨텍스트 압축 전략은 에이전트의 작업 특성에 따라 다릅니다. 코드 작성 에이전트는 최근 파일 변경 이력을, 연구 에이전트는 중간 결론을 우선 유지해야 합니다.
계층 간 통신 패턴
섹션 제목: “계층 간 통신 패턴”계층 간 데이터 흐름은 이벤트 기반 또는 직접 호출 두 가지 패턴으로 구현할 수 있습니다.
| 패턴 | 장점 | 단점 | 적합한 경우 | |------|------|------|-------------| | 직접 호출 | 단순하고 디버깅 쉬움 | 강한 결합 | 단일 프로세스, 소규모 | | 이벤트 버스 | 느슨한 결합, 확장 용이 | 흐름 추적 어려움 | 멀티 에이전트, 대규모 | | 메시지 큐 | 비동기 처리, 백프레셔 | 운영 복잡도 증가 | 고부하, 분산 환경 |
초기에는 직접 호출로 시작하고, 에이전트 수가 늘어나면 이벤트 버스로 전환하는 것이 일반적인 진화 경로입니다.