시스템 개요
최근 다양한 AI 프로그래밍 도구가 등장하면서, 지능형 에이전트 + 개발 환경 통합은 주목받는 기술 트렌드로 자리 잡았다. 본 문서에서는 실제 사용 가능한 코드 생성 에이전트 시스템을 구성하는 전체 아키텍처와 핵심 모듈들을 심층적으로 분석한다.
사용된 기술 스택: Python, Flask, Socket.IO, React 18, TypeScript, DeepSeek API
모듈화된 4계층 아키텍처
┌──────────────────────────────────────────────┐
│ Frontend (React + TS) │
│ 파일 트리 │ 대화 흐름 + 도구 카드 │ 작업 관리판 │
└────────────────────┬─────────────────────────┘
│ WebSocket (Socket.IO)
┌────────────────────▼─────────────────────────┐
│ api_server.py (Flask + SocketIO) │
│ REST API + 실시간 이벤트 전달 │
└────────────────────┬─────────────────────────┘
│
┌────────────────────▼─────────────────────────┐
│ Agent Core │
│ loop.py (주루프) │ subagent.py (하위 에이전트) │
└────────────────────┬─────────────────────────┘
│
┌────────────────────▼─────────────────────────┐
│ Components │
│ memory · safety · todo · task · team · compactor │
└────────────────────┬─────────────────────────┘
│
┌────────────────────▼─────────────────────────┐
│ Tools │
│ 10+ 기능: 메모리/스냅샷/작업/팀/스킬/압축 등 │
└──────────────────────────────────────────────┘
각 계층은 명확한 책임을 가지며 독립적으로 테스트 및 확장 가능하다. 이 구조는 복잡한 에이전트 동작을 단순화하고 유지보수성을 극대화한다.
핵심 기능 구현
에이전트 주루프 (core/loop.py)
에이전트의 핵심은 반복적인 추론-행동-관찰-추론 사이클이다.
async def run_agent_cycle(messages, tools, event_handler):
while True:
# 1. LLM 호출 (스트리밍 출력)
response = await call_llm_api(messages, tools, stream=True)
# 2. 실시간 이벤트 전달
await event_handler.on_chunk(response.delta)
# 3. 도구 호출 여부 확인
if response.stop_reason == "tool_use":
tool_output = await invoke_tool(response.tool_call)
await event_handler.on_tool_result(tool_output)
messages.append(tool_output)
continue # 도구 결과 포함 후 재시도
# 4. 종료 조건 확인
if response.stop_reason == "end_turn":
break
- 이벤트 콜백 체계: 각 단계(체크포인트, 도구 시작/결과)에서 상태 업데이트를 트리거하여 UI와 분리된 로직 설계 가능
- 중단 처리:
asyncio.CancelledError를 활용해 사용자 중단 요청을 안정적으로 처리
실시간 통신 (Flask-SocketIO)
웹소켓 기반의 양방향 커뮤니케이션으로 높은 반응성 보장.
{
"type": "chunk",
"data": {
"content": "생성 중...",
"tool_name": "file_write",
"input": { "path": "main.py", "content": "..." },
"output": { "status": "success" },
"elapsed_ms": 1200
}
}
SSE 대비 장점: 클라이언트로부터 중단 신호 전송이 가능하며, 상태 변경 알림을 수신할 수 있다.
도구 시스템 (tools/)
등록 기반의 도구 패턴으로 확장성 향상.
class BaseTool:
name: str
description: str
input_schema: dict
async def execute(self, **kwargs) -> dict:
raise NotImplementedError
# 도구 레지스트리
TOOL_REGISTRY = {}
def register_tool(tool: BaseTool):
TOOL_REGISTRY[tool.name] = tool
| 도구명 | 기능 |
|---|---|
| memory | 지속적 기억 저장/검색 |
| safety | 파일 스냅샷 생성 및 복원 |
| task_board | 작업 상태 관리 |
| todo | Todo 리스트 동기화 |
| team | 다중 에이전트 협업 |
| compress | 대화 컨텍스트 압축 |
| skill | 기능 스킬 로딩 및 실행 |
| worktree | 작업 디렉터리 관리 |
컨텍스트 최적화 (components/compactor.py)
LLM의 입력 길이 제한을 초과하지 않도록 지능적으로 역사 데이터를 압축한다.
def condense_conversation(messages, budget):
total_tokens = count_tokens(messages)
if total_tokens <= budget:
return messages
# 최근 몇 번의 대화 유지
recent = messages[-N:]
# 중간 역사 요약
summary = summarize(messages[1:-N])
return [system_msg, summary] + recent
주의사항: 도구 호출과 결과가 짝을 이루도록 보장해야 하며, 그렇지 않으면 API 오류 발생.
안전성 관리 (components/safety_manager.py)
코드 수정 실수 방지를 위해 파일 상태를 사전 스냅샷으로 보존.
class SafetyManager:
def create_snapshot(self, files: list[str], label: str):
snapshot = {
"id": uuid4(),
"label": label,
"timestamp": datetime.now(),
"contents": {f: read(f) for f in files}
}
self.snapshots.append(snapshot)
def revert_to(self, snapshot_id: str):
snapshot = self.get(snapshot_id)
for path, content in snapshot["contents"].items():
write_file(path, content)
다중 에이전트 협업 (core/subagent.py + components/team_manager.py)
복잡한 작업을 분산 처리하기 위해 주 에이전트와 하위 에이전트 간 역할 분담.
주 에이전트
├── 하위 에이전트 1: 요구사항 분석
├── 하위 에이전트 2: 코드 생성
└── 하위 에이전트 3: 테스트 검증
프론트엔드 구조 (React 18 + TypeScript)
삼분면 레이아웃으로 사용자 경험 최적화.
┌─────────────┬──────────────────────┬──────────────┐
│ 파일 트리 │ 대화 + 도구 카드 │ 작업 목록 │
│ (왼쪽) │ (중앙·스트리밍) │ (오른쪽) │
└─────────────┴──────────────────────┴──────────────┘
커스텀 훅을 통해 소켓 상태를 관리.
function useAgentSocket() {
const [messages, setMessages] = useState([]);
const [status, setStatus] = useState("idle");
const socketRef = useRef(null);
useEffect(() => {
socketRef.current = io("http://localhost:5000");
socketRef.current.on("agent_event", (event) => {
switch(event.type) {
case "chunk":
setMessages(prev => [...prev, event.data.content]);
break;
case "tool_start":
addToolCard(event.data);
break;
case "tool_result":
updateToolCard(event.data);
break;
case "run_end":
setStatus("idle");
break;
}
});
return () => socketRef.current?.disconnect();
}, []);
return { messages, status, send, interrupt };
}
도구 카드는 각 도구의 입력, 출력, 수행 시간을 직관적으로 표시하여 에이전트의 작동 과정을 투명하게 제공한다.
API 인터페이스 설계
외부 시스템과의 통합을 위한 완전한 RESTful API 제공.
GET /api/status— 현재 에이전트 상태 조회POST /api/chat— 메시지 전송POST /api/chat/interrupt— 실행 중단GET /api/history— 대화 기록GET /api/tasks— 작업 목록GET /api/todos— Todo 항목GET /api/team— 다중 에이전트 상태POST /api/safety/checkpoint— 스냅샷 생성POST /api/safety/restore— 상태 복원GET /api/files/tree— 파일 트리 구조
실행 방법
# 프로젝트 다운로드
git clone https://github.com/myh-1302/code_agent.git
cd code_agent
# API 키 설정
echo "DEEPSEEK_API_KEY=sk-deepsee-k-xxxxx" > .env
# 의존성 설치
pip install -r requirements.txt
cd frontend && npm install && cd ..
# 실행
./start.sh
# http://localhost:5173 접속
주요 문제 해결 경험
- 도구 호출 일치성:
tool_use와tool_result는 반드시 짝을 이뤄야 하므로, 컨텍스트 압축 시 순서 보장 필수 - 중단 후 상태 복원: 중단 시 미완성 메시지가 남을 수 있으므로, 다음 세션을 위해 정상화 처리 필요
- 이벤트 순서 보장: 고병렬 상황에서 이벤트 순서가 어긋날 수 있음 →
sequence_id추가로 정렬 처리 - React 상태 경쟁 조건:
useState는 비동기 업데이트 → 즉시 읽기 용도로useRef사용
향후 개선 방향
- 에이전트 루프의 안정성 강화 (재시도, 상태 복구)
- 스마트 컨텍스트 압축 (의미 중심 자동 선택)
- 프론트엔드 성능 최적화 (가상 스크롤, 지연 로딩)
- 에러 회복 전략 (재시도 → 백업 → 사용자 알림)
- 파일 변경 내역 미리보기 (diff 표시)
- 메모리 검색 개선 (벡터 저장, 의미 검색)
- 다중 작업 디렉터리 지원 (탭 기반 프로젝트 관리)
- 테스트 실패 자동 진단 및 복구 제안
이러한 아키텍처는 단순한 API 호출을 넘어서, 실시간 커뮤니케이션, 상태 제어, 도구 연동, 보안, 컨텍스트 관리 등 다양한 기술적 도전을 통합적으로 해결해야 한다는 점을 보여준다. 실제로 구현해보는 것은 이론보다 훨씬 깊은 이해를 가능하게 한다.