S
STONI
AI Agent
Multi-Agent Systems
Software Architecture
Microservices
DDD
Orchestration
Claude Code
CrewAI
LangGraph
AutoGen

AI 에이전트 아키텍처의 진화: 소프트웨어 엔지니어링으로의 회귀

AI 에이전트 아키텍처의 진화: 소프트웨어 엔지니어링으로의 회귀

"역사는 반복되지 않지만, 운율을 맞춘다" - 마크 트웨인

서론: 새로운 패러다임의 등장

2022년 11월, ChatGPT의 등장은 소프트웨어 개발 방식에 대한 근본적인 질문을 던졌다. "이제 코드를 작성하는 방식이 완전히 바뀔 것인가?" 많은 이들이 프로그래밍의 종말을 예언했고, 또 다른 이들은 단순한 과대광고라고 일축했다.

그로부터 3년이 지난 2026년 초, 우리는 흥미로운 현상을 목격하고 있다. AI 에이전트 시스템의 발전 방향이 소프트웨어 엔지니어링이 지난 50년간 걸어온 길과 놀라울 정도로 유사하다는 것이다.

  • 경계(Boundary) 정의: 각 에이전트의 책임 범위를 명확히 구분
  • 역할(Role) 분담: 전문화된 에이전트들의 협업
  • 오케스트레이션(Orchestration): 에이전트 간 작업 흐름 조정
  • 통신 프로토콜(Communication Protocol): 표준화된 메시지 교환 방식

이것은 마이크로서비스 아키텍처, 도메인 주도 설계(DDD), 서비스 지향 아키텍처(SOA)에서 우리가 이미 배운 교훈들이다. 새로운 기술이지만, 오래된 지혜가 다시 필요한 시점이다.

이 글에서는 GenAI의 출현부터 현재까지 AI 에이전트 시스템이 어떻게 진화해왔는지, 각 단계의 한계는 무엇이었는지, 그리고 왜 우리가 결국 전통적인 소프트웨어 아키텍처 패턴으로 회귀하고 있는지를 살펴본다.


1세대: 단일 프롬프트 시대 (2022-2023 초)

ChatGPT의 등장과 초기 활용

2022년 말, ChatGPT는 개발자들에게 마법과도 같았다. 자연어로 질문하면 코드가 나왔고, 버그를 설명하면 해결책을 제시했다. 초기 활용 패턴은 단순했다:

사용자: "Python으로 CSV 파일을 읽어서 평균을 계산하는 코드 작성해줘"
AI: [코드 생성]
사용자: [복사 → 붙여넣기 → 실행]

이 시기의 특징:

  • 단발성 상호작용: 하나의 프롬프트, 하나의 응답
  • 컨텍스트 독립성: 각 요청이 독립적
  • 수동 통합: 개발자가 직접 결과를 복사하여 사용

한계의 발견

곧 한계가 명확해졌다:

  1. 컨텍스트 제한: 긴 코드베이스나 복잡한 요구사항을 한 번에 처리 불가
  2. 일관성 부족: 같은 질문에 다른 답변, 스타일 불일치
  3. 복잡한 작업 실패: 여러 단계가 필요한 작업에서 중간에 길을 잃음
  4. 에러 복구 불가: 생성된 코드에 오류가 있어도 스스로 수정 못함

개발자들은 깨달았다: "이것은 도구일 뿐, 완전한 해결책이 아니다."


2세대: 프롬프트 엔지니어링과 체인 (2023)

프롬프트의 과학화

한계를 극복하기 위해 "프롬프트 엔지니어링"이라는 새로운 분야가 등장했다. 핵심 기법들:

Chain-of-Thought (CoT)

"단계별로 생각해보자:
1. 먼저 요구사항을 분석하고
2. 필요한 함수들을 나열하고
3. 각 함수를 구현하고
4. 테스트 케이스를 작성하자"

Few-shot Learning

"예시 1: 입력 X → 출력 Y
예시 2: 입력 A → 출력 B
이제 입력 Z에 대해 출력을 생성해줘"

Role Prompting

"당신은 20년 경력의 시니어 Python 개발자입니다.
PEP 8 스타일 가이드를 준수하고..."

프레임워크의 등장: LangChain과 LlamaIndex

2023년 중반, 체계적인 프레임워크들이 등장했다:

LangChain의 핵심 개념:

  • Chain: 여러 LLM 호출을 순차적으로 연결
  • Memory: 대화 컨텍스트 유지
  • Prompt Template: 재사용 가능한 프롬프트 구조
# LangChain 초기 패턴
from langchain import LLMChain, PromptTemplate

template = "다음 코드를 {language}로 변환: {code}"
prompt = PromptTemplate(template=template, input_variables=["language", "code"])
chain = LLMChain(llm=llm, prompt=prompt)

result = chain.run(language="Rust", code="def hello(): print('hi')")

LlamaIndex는 문서 검색과 질의응답에 특화:

  • 대용량 문서를 인덱싱
  • 관련 정보만 추출하여 LLM에 전달
  • 답변 생성

한계: 선형성의 벽

이 접근법도 한계가 있었다:

  1. 선형적 처리: A → B → C 순서로만 진행, 병렬 처리 불가
  2. 에러 핸들링 부재: 중간 단계 실패 시 전체 체인 중단
  3. 확장성 문제: 체인이 길어질수록 관리 복잡도 급증
  4. 동적 분기 불가: 조건에 따른 다른 경로 실행 어려움

개발자들은 더 유연한 구조가 필요함을 인식했다.

3세대: 도구 사용과 RAG (2023 중반-2024 초)

Function Calling의 혁명

2023년 6월, OpenAI가 Function Calling을 발표하면서 게임이 바뀌었다. AI가 단순히 텍스트를 생성하는 것을 넘어, 실제 도구를 사용할 수 있게 되었다.

작동 방식:

# 도구 정의
tools = [
    {
        "name": "get_weather",
        "description": "특정 도시의 날씨 정보 조회",
        "parameters": {
            "city": {"type": "string", "description": "도시 이름"}
        }
    },
    {
        "name": "execute_code",
        "description": "Python 코드 실행",
        "parameters": {
            "code": {"type": "string", "description": "실행할 코드"}
        }
    }
]

# AI가 도구 선택 및 호출
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "서울 날씨 알려줘"}],
    tools=tools
)

# AI 응답: "get_weather" 함수를 city="Seoul"로 호출하라는 지시

이제 AI는:

  • 파일 시스템 접근
  • API 호출
  • 데이터베이스 쿼리
  • 코드 실행
  • 외부 서비스 통합

이 모든 것을 자율적으로 수행할 수 있게 되었다.

RAG: 지식의 확장

Retrieval-Augmented Generation(RAG)은 또 다른 돌파구였다:

문제: LLM은 학습 데이터 이후의 정보나 비공개 문서를 모른다 해결: 필요한 정보를 검색해서 프롬프트에 포함

# RAG 파이프라인
def rag_query(question, knowledge_base):
    # 1. 질문과 관련된 문서 검색
    relevant_docs = vector_search(question, knowledge_base)
    
    # 2. 검색된 문서를 컨텍스트로 포함
    context = "\n".join(relevant_docs)
    prompt = f"컨텍스트: {context}\n\n질문: {question}"
    
    # 3. LLM으로 답변 생성
    answer = llm.generate(prompt)
    return answer

활용 사례:

  • 사내 문서 기반 Q&A 시스템
  • 최신 기술 문서 참조
  • 코드베이스 이해 및 탐색

한계: 복잡도의 증가

도구와 RAG는 강력했지만 새로운 문제를 낳았다:

  1. 도구 선택 오류:

    • 잘못된 도구 선택 (날씨 API 대신 뉴스 API 호출)
    • 파라미터 오류 (잘못된 형식, 누락된 필수값)
  2. 컨텍스트 관리 복잡도:

    • 검색된 문서가 너무 많으면 토큰 한계 초과
    • 관련 없는 문서가 섞이면 답변 품질 저하
  3. 에러 전파:

    • 도구 실행 실패 시 복구 메커니즘 부재
    • 연쇄적 실패 (A 실패 → B 실패 → C 실패)
  4. 디버깅 어려움:

    • 어느 단계에서 잘못되었는지 추적 곤란
    • 재현 불가능한 오류

개발자들은 더 체계적인 제어가 필요함을 깨달았다.


4세대: 에이전트 프레임워크 (2024)

자율 에이전트의 등장

2024년은 "에이전트의 해"였다. 핵심 아이디어: AI가 스스로 계획하고 실행하고 평가하는 루프

ReAct (Reasoning + Acting) 패턴:

1. Thought: "사용자가 데이터 분석을 원한다. 먼저 데이터를 로드해야 한다"
2. Action: load_csv("data.csv")
3. Observation: "1000행, 5열의 데이터 로드됨"
4. Thought: "이제 기술 통계를 계산하자"
5. Action: calculate_statistics(data)
6. Observation: "평균: 42.5, 표준편차: 12.3"
7. Thought: "시각화를 만들어야 한다"
8. Action: create_plot(data, type="histogram")
9. Observation: "plot.png 생성됨"
10. Thought: "작업 완료"

대표 프레임워크들

AutoGPT (2023년 3월):

  • 목표 설정 → 자율 실행 → 결과 평가 루프
  • 파일 읽기/쓰기, 웹 검색, 코드 실행
  • 장기 메모리 (벡터 DB 활용)

BabyAGI (2023년 4월):

  • 작업 목록 생성 및 우선순위 지정
  • 작업 실행 및 결과 저장
  • 새로운 작업 생성 (동적 계획)

LangGraph (2024년):

  • 그래프 기반 워크플로우
  • 조건부 분기, 루프, 병렬 실행
  • 상태 관리 및 체크포인트
# LangGraph 예시
from langgraph.graph import StateGraph

# 상태 정의
class AgentState(TypedDict):
    messages: List[str]
    next_action: str

# 노드 정의
def plan_node(state):
    # 계획 수립
    return {"next_action": "execute"}

def execute_node(state):
    # 실행
    return {"next_action": "evaluate"}

def evaluate_node(state):
    # 평가
    return {"next_action": "done"}

# 그래프 구성
workflow = StateGraph(AgentState)
workflow.add_node("plan", plan_node)
workflow.add_node("execute", execute_node)
workflow.add_node("evaluate", evaluate_node)

workflow.add_edge("plan", "execute")
workflow.add_edge("execute", "evaluate")
workflow.add_conditional_edges("evaluate", 
    lambda s: s["next_action"],
    {"done": END, "plan": "plan"}
)

한계: 신뢰성의 벽

자율 에이전트는 인상적이었지만 프로덕션에는 부적합했다:

  1. 무한 루프:

    • 목표 달성 실패 시 같은 시도 반복
    • 종료 조건 불명확
  2. 비용 폭증:

    • 수백 번의 LLM 호출
    • 예측 불가능한 토큰 사용량
  3. 신뢰성 문제:

    • 같은 입력에 다른 결과
    • 중요한 단계 건너뛰기
    • 환각(hallucination)으로 인한 잘못된 판단
  4. 제어 불가:

    • 중간 개입 어려움
    • 디버깅 거의 불가능
    • 예측 불가능한 동작

실제 사례:

목표: "경쟁사 분석 보고서 작성"

AutoGPT 실행:
- 웹 검색 50회
- 파일 생성 20개
- 코드 실행 30회
- 3시간 후에도 완료 안됨
- 비용: $47
- 결과: 불완전한 초안

개발자들은 깨달았다: "완전 자율은 답이 아니다. 구조화된 협업이 필요하다."

5세대: 멀티 에이전트 시스템 (2024 중반-2025)

역할 분담의 발견

단일 에이전트의 한계를 극복하기 위해, 개발자들은 전문화된 여러 에이전트의 협업이라는 아이디어로 전환했다.

핵심 통찰:

"만능 에이전트 하나보다, 전문화된 에이전트 여럿이 낫다"

이것은 소프트웨어 팀의 구조와 유사하다:

  • 기획자: 요구사항 분석, 작업 분해
  • 개발자: 코드 작성
  • 테스터: 품질 검증
  • 리뷰어: 코드 리뷰
  • 문서 작성자: 문서화

대표 프레임워크: CrewAI

CrewAI (2024년)는 이 개념을 구현한 대표적 프레임워크다:

from crewai import Agent, Task, Crew

# 에이전트 정의
researcher = Agent(
    role="연구원",
    goal="주제에 대한 심층 조사",
    backstory="10년 경력의 기술 리서처",
    tools=[web_search, read_document]
)

writer = Agent(
    role="작가",
    goal="명확하고 설득력 있는 글 작성",
    backstory="기술 블로그 전문 작가",
    tools=[write_file, format_markdown]
)

reviewer = Agent(
    role="리뷰어",
    goal="품질 검증 및 개선 제안",
    backstory="시니어 에디터",
    tools=[check_grammar, verify_facts]
)

# 작업 정의
research_task = Task(
    description="AI 에이전트 아키텍처 트렌드 조사",
    agent=researcher,
    expected_output="조사 보고서"
)

writing_task = Task(
    description="조사 내용을 바탕으로 블로그 글 작성",
    agent=writer,
    context=[research_task],  # 이전 작업 결과 참조
    expected_output="블로그 초안"
)

review_task = Task(
    description="글 검토 및 개선",
    agent=reviewer,
    context=[writing_task],
    expected_output="최종 블로그"
)

# 크루 구성 및 실행
crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, writing_task, review_task],
    process="sequential"  # 순차 실행
)

result = crew.kickoff()

CrewAI의 특징:

  • 역할 기반 설계: 각 에이전트가 명확한 역할과 목표
  • 컨텍스트 공유: 이전 작업 결과를 다음 에이전트에게 전달
  • 도구 전문화: 각 에이전트가 자신의 역할에 맞는 도구만 사용
  • 프로세스 제어: Sequential, Hierarchical 등 다양한 실행 방식

Microsoft AutoGen: 대화 기반 협업

AutoGen은 다른 접근을 취했다: 에이전트 간 대화를 통한 문제 해결

from autogen import AssistantAgent, UserProxyAgent

# 어시스턴트 에이전트 (코드 작성)
assistant = AssistantAgent(
    name="코더",
    llm_config={"model": "gpt-4"},
    system_message="Python 전문가. 깔끔한 코드 작성"
)

# 사용자 프록시 (코드 실행)
user_proxy = UserProxyAgent(
    name="실행자",
    code_execution_config={"work_dir": "coding"},
    human_input_mode="NEVER"
)

# 대화 시작
user_proxy.initiate_chat(
    assistant,
    message="피보나치 수열 계산 함수를 작성하고 테스트해줘"
)

실행 흐름:

실행자: "피보나치 수열 계산 함수를 작성하고 테스트해줘"

코더: "알겠습니다. 다음 코드를 작성했습니다:
```python
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 테스트
for i in range(10):
    print(f"F({i}) = {fibonacci(i)}")

실행해주세요."

실행자: [코드 실행] "출력: F(0) = 0, F(1) = 1, F(2) = 1, ..."

코더: "완료되었습니다. 추가로 메모이제이션을 적용하면 성능을 개선할 수 있습니다."


**AutoGen의 강점**:
- **자연스러운 협업**: 대화를 통한 문제 해결
- **반복적 개선**: 피드백 루프
- **그룹 채팅**: 3개 이상의 에이전트 협업 가능

### LangGraph: 복잡한 워크플로우

**LangGraph**는 그래프 구조로 복잡한 에이전트 워크플로우를 표현:

```python
from langgraph.graph import StateGraph, END

# 멀티 에이전트 그래프
workflow = StateGraph(AgentState)

# 에이전트 노드들
workflow.add_node("supervisor", supervisor_agent)
workflow.add_node("researcher", research_agent)
workflow.add_node("coder", coding_agent)
workflow.add_node("tester", testing_agent)

# 슈퍼바이저가 작업 분배
workflow.add_edge("supervisor", "researcher")
workflow.add_edge("supervisor", "coder")

# 조건부 라우팅
workflow.add_conditional_edges(
    "tester",
    lambda state: "coder" if state["tests_failed"] else END,
    {"coder": "coder", END: END}
)

workflow.set_entry_point("supervisor")

LangGraph의 특징:

  • 명시적 제어 흐름: 그래프로 워크플로우 시각화
  • 조건부 분기: 상태에 따른 동적 라우팅
  • 체크포인트: 중간 상태 저장 및 복구
  • 스트리밍: 실시간 진행 상황 모니터링

한계: 조정의 복잡성

멀티 에이전트 시스템도 새로운 도전 과제를 가져왔다:

  1. 조정 오버헤드:

    • 에이전트 간 통신 비용
    • 컨텍스트 동기화 문제
    • 작업 분배 및 병합의 복잡도
  2. 통신 복잡도:

    • 메시지 형식 불일치
    • 정보 손실 (에이전트 A → B 전달 시)
    • 순환 참조 및 데드락
  3. 일관성 문제:

    • 에이전트 간 상태 불일치
    • 동시성 제어 부재
    • 트랜잭션 개념 없음
  4. 디버깅 악몽:

    • 어느 에이전트가 문제인지 파악 어려움
    • 에이전트 간 상호작용 추적 곤란
    • 재현 불가능한 버그

실제 문제 사례:

시나리오: 5개 에이전트가 협업하여 웹 애플리케이션 개발

문제:
- 기획 에이전트: "사용자 인증 필요"
- 백엔드 에이전트: JWT 기반 인증 구현
- 프론트 에이전트: 세션 기반 인증 구현 (불일치!)
- 테스트 에이전트: 어느 것을 테스트해야 할지 혼란
- 문서 에이전트: 두 가지 방식 모두 문서화 (혼란 가중)

결과: 통합 실패, 수동 개입 필요

개발자들은 깨달았다: "에이전트를 많이 만드는 것만으로는 부족하다. 아키텍처가 필요하다."

6세대: 아키텍처 패턴의 재발견 (2025-2026)

전환점: 소프트웨어 엔지니어링으로의 회귀

2025년 중반부터, AI 에이전트 커뮤니티에서 흥미로운 변화가 일어났다. 개발자들이 전통적인 소프트웨어 아키텍처 패턴을 AI 에이전트 시스템에 적용하기 시작한 것이다.

핵심 깨달음:

"AI 에이전트 시스템도 결국 분산 소프트웨어 시스템이다. 우리는 이미 50년간 이런 시스템을 만들어왔다."

실전 사례 1: Claude Code Agent Teams (2026년 2월)

Anthropic이 Opus 4.6과 함께 발표한 Agent Teams는 이 패러다임의 완벽한 예시다.

아키텍처:

graph TD
    TL[Team Lead<br/>Coordinator<br/>- 작업 분해 및 할당<br/>- 진행 상황 모니터링<br/>- 결과 통합]
    T1[Teammate 1]
    T2[Teammate 2]
    T3[Teammate 3]
    STL[Shared Task List]
    
    TL --> T1
    TL --> T2
    TL --> T3
    T1 --> STL
    T2 --> STL
    T3 --> STL
    
    style TL fill:#4299e1,stroke:#2c5282,color:#fff
    style T1 fill:#48bb78,stroke:#2f855a,color:#fff
    style T2 fill:#48bb78,stroke:#2f855a,color:#fff
    style T3 fill:#48bb78,stroke:#2f855a,color:#fff
    style STL fill:#ed8936,stroke:#c05621,color:#fff

핵심 설계 원칙:

  1. 명확한 역할 분리:

    • Team Lead: 오케스트레이터 (조정자)
    • Teammates: 워커 (실행자)
    • 각자의 책임 범위가 명확
  2. 독립적 컨텍스트:

    • 각 teammate는 독립된 컨텍스트 윈도우
    • 메모리 격리로 간섭 방지
    • 병렬 실행 가능
  3. 표준화된 통신:

    • Shared Task List: 작업 큐
    • Peer-to-peer messaging: 직접 통신
    • Status updates: 진행 상황 공유
  4. 동적 작업 할당:

    • Teammates가 능동적으로 작업 선택
    • 부하 분산 자동화
    • 우선순위 기반 스케줄링

실제 사용 예시:

# Agent Teams 활성화
export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1

# 대규모 리팩토링 작업
$ claude-code "레거시 코드베이스를 TypeScript로 마이그레이션"

Team Lead: "작업을 분석했습니다. 50개 파일, 예상 시간 2시간"
Team Lead: "3개 teammate 생성, 작업 분배 중..."

Teammate-1: "src/auth 모듈 변환 시작"
Teammate-2: "src/api 모듈 변환 시작"
Teammate-3: "src/utils 모듈 변환 시작"

[병렬 실행]

Teammate-1: "auth 모듈 완료, 테스트 통과"
Teammate-2: "api 모듈 완료, 타입 정의 추가"
Teammate-3: "utils 모듈 완료, 문서 업데이트"

Team Lead: "모든 작업 완료. 통합 테스트 실행 중..."
Team Lead: "마이그레이션 성공. 커밋 준비 완료"

소프트웨어 패턴과의 유사성:

  • 마이크로서비스: 각 teammate는 독립적 서비스
  • 메시지 큐: Shared Task List는 작업 큐
  • 오케스트레이션: Team Lead는 오케스트레이터
  • 서비스 메시: Peer-to-peer messaging

실전 사례 2: oh-my-opencode (2025-2026)

oh-my-opencode는 OpenCode를 멀티 에이전트 오케스트레이션 플랫폼으로 변환하는 플러그인이다.

아키텍처 설계:

graph TD
    MA[Main Agent<br/>Coordinator<br/>Model: Claude Opus 4.6<br/>전체 작업 조율, 의사결정]
    P[Planner<br/>GPT-4]
    R[Researcher<br/>Gemini]
    C[Coder<br/>Claude]
    D[Debugger<br/>GPT-4o]
    
    Tools[Shared Tools<br/>- LSP<br/>- AST-Grep<br/>- File System<br/>- Terminal]
    
    MA --> P
    MA --> R
    MA --> C
    MA --> D
    
    P --> Tools
    R --> Tools
    C --> Tools
    D --> Tools
    
    style MA fill:#667eea,stroke:#5a67d8,color:#fff
    style P fill:#4299e1,stroke:#2c5282,color:#fff
    style R fill:#48bb78,stroke:#2f855a,color:#fff
    style C fill:#ed8936,stroke:#c05621,color:#fff
    style D fill:#e53e3e,stroke:#c53030,color:#fff
    style Tools fill:#edf2f7,stroke:#a0aec0

핵심 특징:

  1. 역할별 모델 최적화:

    agents:
      planner:
        model: gpt-4
        role: "요구사항 분석 및 작업 계획"
        tools: [read_files, search_code]
      
      researcher:
        model: gemini-pro
        role: "문서 조사 및 API 탐색"
        tools: [web_search, read_docs]
      
      coder:
        model: claude-opus-4.6
        role: "코드 작성 및 수정"
        tools: [lsp, ast_grep, write_files]
      
      debugger:
        model: gpt-4o
        role: "버그 분석 및 수정"
        tools: [execute_code, read_logs]
    
  2. 비동기 서브에이전트:

    • 백그라운드에서 병렬 실행
    • 메인 에이전트는 블로킹 없이 다른 작업 수행
    • 결과는 콜백으로 전달
  3. 도구 전문화:

    • LSP (Language Server Protocol): 코드 인텔리전스
      • 정의로 이동, 참조 찾기
      • 자동완성, 타입 체크
    • AST-Grep: 구조적 코드 검색
      • 패턴 매칭
      • 대규모 리팩토링
  4. MCP (Model Context Protocol) 통합:

    • 표준화된 컨텍스트 공유
    • 도구 간 상호운용성

실제 워크플로우:

사용자: "이 프로젝트에 인증 기능 추가해줘"

Main Agent: "작업을 분석하겠습니다"
  → Planner 호출 (비동기)

Planner: "다음 단계 필요:
  1. 인증 라이브러리 조사
  2. 데이터베이스 스키마 설계
  3. API 엔드포인트 구현
  4. 프론트엔드 통합"

Main Agent: "병렬 작업 시작"
  → Researcher: "인증 라이브러리 조사" (비동기)
  → Coder: "데이터베이스 스키마 작성" (비동기)

[병렬 실행]

Researcher: "Passport.js 추천, JWT 기반"
Coder: "User 테이블 스키마 완성"

Main Agent: "다음 단계 진행"
  → Coder: "API 엔드포인트 구현"
  
Coder: [LSP로 기존 코드 분석]
Coder: [AST-Grep로 패턴 검색]
Coder: "auth.ts 작성 완료"

Main Agent: "테스트 필요"
  → Debugger: "단위 테스트 작성 및 실행"

Debugger: "모든 테스트 통과"

Main Agent: "작업 완료. 커밋 메시지 생성"

소프트웨어 패턴 적용:

  • Bounded Context (DDD):

    • 각 에이전트는 자신의 도메인에 집중
    • Planner: 계획 도메인
    • Coder: 구현 도메인
    • Debugger: 품질 도메인
  • Command Pattern:

    • 각 작업은 명령 객체로 캡슐화
    • 큐에 저장, 비동기 실행
  • Observer Pattern:

    • 서브에이전트 완료 시 메인 에이전트에 알림
    • 이벤트 기반 조정

실전 사례 3: CrewAI의 진화 (2025-2026)

CrewAI도 소프트웨어 아키텍처 패턴을 적극 도입했다.

Hierarchical Process (계층적 프로세스):

from crewai import Crew, Process

crew = Crew(
    agents=[manager, developer, tester, deployer],
    tasks=[plan_task, code_task, test_task, deploy_task],
    process=Process.hierarchical,  # 계층적 구조
    manager_llm="gpt-4"  # 매니저 에이전트 자동 생성
)

작동 방식:

Manager (자동 생성)
  │
  ├─→ Developer: "로그인 기능 구현"
  │     └─→ 완료 보고
  │
  ├─→ Tester: "로그인 테스트"
  │     └─→ 버그 발견 보고
  │
  ├─→ Developer: "버그 수정"
  │     └─→ 완료 보고
  │
  ├─→ Tester: "재테스트"
  │     └─→ 통과 보고
  │
  └─→ Deployer: "배포"
        └─→ 완료 보고

Conditional Tasks (조건부 작업):

from crewai import Task

test_task = Task(
    description="코드 테스트",
    agent=tester,
    expected_output="테스트 결과"
)

fix_task = Task(
    description="버그 수정",
    agent=developer,
    condition=lambda output: "FAILED" in output,  # 조건부 실행
    context=[test_task]
)

Memory Systems (메모리 시스템):

crew = Crew(
    agents=[...],
    tasks=[...],
    memory=True,  # 메모리 활성화
    memory_config={
        "provider": "mem0",  # 메모리 제공자
        "config": {
            "vector_store": "qdrant",
            "embedding_model": "text-embedding-3-small"
        }
    }
)

메모리 계층:

  • Short-term Memory: 현재 작업 컨텍스트
  • Long-term Memory: 과거 작업 이력 (벡터 DB)
  • Entity Memory: 중요 개체 추적 (사용자, 파일, 설정)
  • Contextual Memory: 에이전트 간 공유 컨텍스트

소프트웨어 패턴:

  • Chain of Responsibility: 계층적 작업 위임
  • Strategy Pattern: 조건에 따른 작업 선택
  • Memento Pattern: 상태 저장 및 복원

7세대: 엔터프라이즈 패턴의 완전한 적용 (2026년 현재)

Bounded Context와 Domain-Driven Design

**Domain-Driven Design (DDD)**의 핵심 개념인 Bounded Context가 AI 에이전트 설계에 적용되고 있다.

Bounded Context란?

  • 특정 도메인 모델이 유효한 경계
  • 경계 내에서는 용어와 규칙이 일관됨
  • 경계 간에는 명시적 변환 필요

AI 에이전트 적용:

graph TB
    subgraph AC[Authentication Context]
        UA[User Agent<br/>사용자 관리]
        TA[Token Agent<br/>토큰 발급/검증]
        SA[Session Agent<br/>세션 관리]
    end
    
    ACL[Anti-Corruption Layer<br/>변환 계층]
    
    subgraph PC[Payment Context]
        OA[Order Agent<br/>주문 처리]
        PA[Payment Agent<br/>결제 처리]
        IA[Invoice Agent<br/>청구서 생성]
    end
    
    AC --> ACL
    ACL --> PC
    
    style AC fill:#ebf8ff,stroke:#4299e1
    style PC fill:#f0fff4,stroke:#48bb78
    style ACL fill:#fed7d7,stroke:#e53e3e

Anti-Corruption Layer (부패 방지 계층):

class AuthToPaymentAdapter:
    """Authentication Context의 User를 Payment Context의 Customer로 변환"""
    
    def user_to_customer(self, user: User) -> Customer:
        return Customer(
            id=user.id,
            name=user.full_name,
            email=user.email,
            # Payment Context에 필요한 정보만 전달
        )

# 에이전트 간 통신
auth_agent = UserAgent()
payment_agent = OrderAgent()

user = auth_agent.get_user(user_id)
customer = AuthToPaymentAdapter().user_to_customer(user)
payment_agent.create_order(customer, items)

이점:

  • 명확한 책임: 각 컨텍스트는 자신의 도메인에만 집중
  • 독립적 진화: 한 컨텍스트 변경이 다른 컨텍스트에 영향 없음
  • 용어 일관성: 컨텍스트 내에서 명확한 의미

마이크로서비스 아키텍처와의 유사성

AI 에이전트 시스템은 마이크로서비스 아키텍처의 원칙을 따르고 있다.

마이크로서비스 원칙:

  1. 단일 책임: 각 서비스는 하나의 비즈니스 기능
  2. 독립 배포: 서비스 간 독립적 배포
  3. 분산 데이터: 각 서비스가 자신의 데이터 소유
  4. API 통신: 표준화된 인터페이스

AI 에이전트 적용:

graph LR
    RA[Research Agent<br/>- Web Search<br/>- Doc Reading<br/>- Summarization<br/><br/>Own Memory:<br/>Search Cache]
    
    CA[Coding Agent<br/>- LSP<br/>- AST<br/>- File I/O<br/><br/>Own Memory:<br/>Code Context]
    
    MB[Message Bus<br/>Event Queue]
    
    RA --> MB
    CA --> MB
    MB --> RA
    MB --> CA
    
    style RA fill:#4299e1,stroke:#2c5282,color:#fff
    style CA fill:#48bb78,stroke:#2f855a,color:#fff
    style MB fill:#ed8936,stroke:#c05621,color:#fff

실제 구현 예시 (LangGraph + Redis):

from langgraph.graph import StateGraph
from redis import Redis

# 메시지 버스
message_bus = Redis(host='localhost', port=6379)

class ResearchAgent:
    def __init__(self):
        self.memory = {}  # 독립적 메모리
    
    def execute(self, task):
        # 작업 수행
        result = self.research(task)
        
        # 결과를 메시지 버스에 발행
        message_bus.publish('research_complete', {
            'task_id': task.id,
            'result': result
        })
        
        return result

class CodingAgent:
    def __init__(self):
        self.memory = {}  # 독립적 메모리
        
        # 메시지 구독
        pubsub = message_bus.pubsub()
        pubsub.subscribe('research_complete')
    
    def on_research_complete(self, message):
        # 리서치 완료 이벤트 처리
        research_result = message['result']
        self.generate_code(research_result)

이점:

  • 확장성: 에이전트 독립적 확장
  • 장애 격리: 한 에이전트 실패가 전체 시스템 중단 안 함
  • 기술 다양성: 각 에이전트가 다른 모델/도구 사용 가능

오케스트레이션 vs 코레오그래피

분산 시스템에서 서비스 조정 방식은 두 가지다:

1. 오케스트레이션 (Orchestration):

  • 중앙 조정자가 모든 흐름 제어
  • 명시적 순서 정의
# 오케스트레이션 예시
class OrchestratorAgent:
    def process_request(self, request):
        # 1단계: 리서치
        research = self.research_agent.execute(request)
        
        # 2단계: 코딩 (리서치 결과 전달)
        code = self.coding_agent.execute(research)
        
        # 3단계: 테스트 (코드 전달)
        test_result = self.testing_agent.execute(code)
        
        # 4단계: 조건부 분기
        if test_result.failed:
            code = self.coding_agent.fix(code, test_result)
            test_result = self.testing_agent.execute(code)
        
        # 5단계: 배포
        return self.deploy_agent.execute(code)

장점:

  • 명확한 흐름 파악
  • 중앙 집중식 로깅/모니터링
  • 디버깅 용이

단점:

  • 단일 장애점 (orchestrator 실패 시 전체 중단)
  • 확장성 제한
  • 조정자 복잡도 증가

2. 코레오그래피 (Choreography):

  • 중앙 조정자 없음
  • 에이전트들이 이벤트에 반응하여 자율적으로 행동
# 코레오그래피 예시
class ResearchAgent:
    def on_request(self, event):
        result = self.research(event.data)
        # 다음 에이전트를 직접 호출하지 않고 이벤트 발행
        event_bus.publish('research_completed', result)

class CodingAgent:
    def __init__(self):
        # 이벤트 구독
        event_bus.subscribe('research_completed', self.on_research_completed)
    
    def on_research_completed(self, event):
        code = self.generate_code(event.data)
        event_bus.publish('code_generated', code)

class TestingAgent:
    def __init__(self):
        event_bus.subscribe('code_generated', self.on_code_generated)
    
    def on_code_generated(self, event):
        result = self.test(event.data)
        if result.failed:
            event_bus.publish('test_failed', result)
        else:
            event_bus.publish('test_passed', result)

class CodingAgent:  # 재등장
    def __init__(self):
        event_bus.subscribe('test_failed', self.on_test_failed)
    
    def on_test_failed(self, event):
        fixed_code = self.fix(event.data)
        event_bus.publish('code_generated', fixed_code)

장점:

  • 느슨한 결합
  • 높은 확장성
  • 단일 장애점 없음

단점:

  • 흐름 파악 어려움
  • 디버깅 복잡
  • 순환 의존성 위험

실전 권장사항:

  • 간단한 워크플로우: 오케스트레이션
  • 복잡하고 동적인 워크플로우: 코레오그래피
  • 하이브리드: 도메인별로 오케스트레이션, 도메인 간은 코레오그래피

통신 프로토콜과 인터페이스 설계

에이전트 간 통신을 표준화하는 것이 중요해졌다.

MCP (Model Context Protocol):

  • Anthropic이 제안한 표준 프로토콜
  • AI 모델과 도구 간 통신 표준화
// MCP 메시지 형식
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/path/to/file.txt"
    }
  },
  "id": 1
}

// 응답
{
  "jsonrpc": "2.0",
  "result": {
    "content": "파일 내용..."
  },
  "id": 1
}

에이전트 간 메시지 프로토콜:

from dataclasses import dataclass
from typing import Any, Optional
from enum import Enum

class MessageType(Enum):
    REQUEST = "request"
    RESPONSE = "response"
    EVENT = "event"
    ERROR = "error"

@dataclass
class AgentMessage:
    """표준 에이전트 메시지"""
    type: MessageType
    sender: str
    receiver: Optional[str]  # None이면 브로드캐스트
    payload: Any
    correlation_id: str  # 요청-응답 매칭
    timestamp: float

# 사용 예시
message = AgentMessage(
    type=MessageType.REQUEST,
    sender="research_agent",
    receiver="coding_agent",
    payload={
        "action": "generate_code",
        "context": research_result
    },
    correlation_id="req-12345",
    timestamp=time.time()
)

인터페이스 계약 (Contract):

from abc import ABC, abstractmethod

class AgentInterface(ABC):
    """모든 에이전트가 구현해야 하는 인터페이스"""
    
    @abstractmethod
    def execute(self, task: Task) -> Result:
        """작업 실행"""
        pass
    
    @abstractmethod
    def can_handle(self, task: Task) -> bool:
        """이 작업을 처리할 수 있는지 확인"""
        pass
    
    @abstractmethod
    def get_capabilities(self) -> List[str]:
        """에이전트의 능력 목록 반환"""
        pass

class ResearchAgent(AgentInterface):
    def execute(self, task: Task) -> Result:
        # 구현
        pass
    
    def can_handle(self, task: Task) -> bool:
        return task.type in ["research", "search", "summarize"]
    
    def get_capabilities(self) -> List[str]:
        return ["web_search", "document_reading", "summarization"]

관찰 가능성과 모니터링

프로덕션 AI 에이전트 시스템에는 관찰 가능성이 필수다.

세 가지 기둥:

  1. 로깅 (Logging):
import structlog

logger = structlog.get_logger()

class ResearchAgent:
    def execute(self, task):
        logger.info(
            "agent_execution_started",
            agent="research_agent",
            task_id=task.id,
            task_type=task.type
        )
        
        try:
            result = self.research(task)
            logger.info(
                "agent_execution_completed",
                agent="research_agent",
                task_id=task.id,
                duration=result.duration,
                tokens_used=result.tokens
            )
            return result
        except Exception as e:
            logger.error(
                "agent_execution_failed",
                agent="research_agent",
                task_id=task.id,
                error=str(e)
            )
            raise
  1. 메트릭 (Metrics):
from prometheus_client import Counter, Histogram, Gauge

# 메트릭 정의
agent_requests = Counter(
    'agent_requests_total',
    'Total agent requests',
    ['agent_name', 'status']
)

agent_duration = Histogram(
    'agent_duration_seconds',
    'Agent execution duration',
    ['agent_name']
)

agent_tokens = Counter(
    'agent_tokens_total',
    'Total tokens used',
    ['agent_name', 'model']
)

# 사용
class ResearchAgent:
    def execute(self, task):
        with agent_duration.labels(agent_name='research').time():
            try:
                result = self.research(task)
                agent_requests.labels(
                    agent_name='research',
                    status='success'
                ).inc()
                agent_tokens.labels(
                    agent_name='research',
                    model='gpt-4'
                ).inc(result.tokens)
                return result
            except Exception:
                agent_requests.labels(
                    agent_name='research',
                    status='error'
                ).inc()
                raise
  1. 트레이싱 (Tracing):
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer(__name__)

class OrchestratorAgent:
    def process_request(self, request):
        with tracer.start_as_current_span("orchestrator.process") as span:
            span.set_attribute("request.id", request.id)
            
            # 리서치
            with tracer.start_as_current_span("research.execute") as research_span:
                research = self.research_agent.execute(request)
                research_span.set_attribute("tokens", research.tokens)
            
            # 코딩
            with tracer.start_as_current_span("coding.execute") as coding_span:
                code = self.coding_agent.execute(research)
                coding_span.set_attribute("lines_of_code", len(code.split('\n')))
            
            span.set_status(Status(StatusCode.OK))
            return code

분산 트레이싱 시각화:

Request ID: req-12345
│
├─ orchestrator.process (2.5s)
│  │
│  ├─ research.execute (1.2s)
│  │  ├─ web_search (0.8s)
│  │  └─ summarize (0.4s)
│  │
│  ├─ coding.execute (1.0s)
│  │  ├─ lsp.analyze (0.3s)
│  │  └─ generate_code (0.7s)
│  │
│  └─ testing.execute (0.3s)

이를 통해:

  • 병목 지점 파악
  • 에러 발생 위치 추적
  • 에이전트 간 의존성 시각화

핵심 통찰: 역사는 반복된다

소프트웨어 공학 50년의 교훈

AI 에이전트 시스템의 진화를 보면, 소프트웨어 공학이 지난 50년간 겪은 여정과 놀라울 정도로 유사하다.

1970년대: 모놀리식 애플리케이션

  • 하나의 거대한 프로그램
  • 모든 기능이 하나의 프로세스에
  • AI 1세대: 단일 프롬프트

1980년대: 구조적 프로그래밍

  • 함수와 모듈로 분리
  • 재사용 가능한 컴포넌트
  • AI 2세대: 프롬프트 체인

1990년대: 객체 지향 프로그래밍

  • 캡슐화, 상속, 다형성
  • 명확한 인터페이스
  • AI 3세대: 도구 사용

2000년대: 서비스 지향 아키텍처 (SOA)

  • 독립적 서비스들의 조합
  • 표준 프로토콜 (SOAP, REST)
  • AI 4세대: 에이전트 프레임워크

2010년대: 마이크로서비스

  • 작고 독립적인 서비스
  • 분산 시스템
  • AI 5세대: 멀티 에이전트

2020년대: 클라우드 네이티브

  • 오케스트레이션 (Kubernetes)
  • 관찰 가능성
  • 서비스 메시
  • AI 6-7세대: 아키텍처 패턴 적용

반복되는 문제들

각 단계에서 마주친 문제들도 유사하다:

소프트웨어 공학AI 에이전트 시스템
스파게티 코드프롬프트 체인 복잡도
타이트 커플링에이전트 간 강한 의존성
단일 장애점중앙 오케스트레이터 실패
확장성 문제에이전트 수 증가 시 성능 저하
디버깅 어려움에이전트 상호작용 추적 곤란
일관성 문제에이전트 간 상태 불일치

반복되는 해결책들

그리고 해결책도 유사하다:

소프트웨어 공학AI 에이전트 시스템
모듈화에이전트 역할 분리
인터페이스 정의메시지 프로토콜 표준화
의존성 주입도구 주입
이벤트 버스에이전트 메시지 버스
서비스 디스커버리에이전트 레지스트리
로드 밸런싱작업 분배
서킷 브레이커에이전트 실패 격리
분산 트레이싱에이전트 실행 추적

AI 시스템도 결국 소프트웨어다

이 모든 것이 시사하는 바는 명확하다:

AI 에이전트 시스템도 결국 소프트웨어 시스템이다.

AI가 특별해 보이지만, 시스템 설계 관점에서는:

  • 입력을 받아 처리하고 출력을 내는 컴포넌트
  • 다른 컴포넌트와 통신하는 분산 시스템
  • 상태를 관리하고 에러를 처리해야 하는 프로그램

따라서:

  • 소프트웨어 공학의 원칙이 그대로 적용됨
  • 검증된 아키텍처 패턴을 활용할 수 있음
  • 기존 도구와 방법론을 재사용 가능

검증된 패턴의 재적용

우리는 바퀴를 다시 발명할 필요가 없다. 이미 검증된 패턴들을 적용하면 된다:

1. SOLID 원칙:

  • Single Responsibility: 각 에이전트는 하나의 책임
  • Open/Closed: 새 에이전트 추가는 쉽게, 기존 에이전트 수정은 최소화
  • Liskov Substitution: 같은 역할의 에이전트는 교체 가능
  • Interface Segregation: 에이전트는 필요한 인터페이스만 구현
  • Dependency Inversion: 구체적 구현이 아닌 추상화에 의존

2. 디자인 패턴:

  • Factory: 에이전트 생성 로직 캡슐화
  • Strategy: 다양한 LLM 모델 전략
  • Observer: 에이전트 상태 변화 감지
  • Command: 작업을 객체로 캡슐화
  • Chain of Responsibility: 에이전트 체인

3. 아키텍처 패턴:

  • Layered Architecture: 프레젠테이션, 비즈니스, 데이터 계층
  • Event-Driven Architecture: 이벤트 기반 에이전트 통신
  • CQRS: 읽기/쓰기 에이전트 분리
  • Saga Pattern: 분산 트랜잭션 관리

실전 가이드: 프로덕션 AI 에이전트 시스템 설계

이제 실전에서 어떻게 적용할지 구체적으로 살펴보자.

1. 경계 설정 원칙

질문: "어디까지가 하나의 에이전트인가?"

원칙:

  1. 단일 책임 원칙:

    • 하나의 에이전트는 하나의 명확한 역할
    • "그리고(AND)"가 들어가면 분리 고려
    ❌ 나쁜 예: "리서치하고 코드 작성하는 에이전트"
    ✅ 좋은 예: "리서치 에이전트" + "코딩 에이전트"
    
  2. 응집도 최대화:

    • 관련된 기능은 함께
    • 에이전트 내부 통신 > 에이전트 간 통신
    # 높은 응집도
    class DataAnalysisAgent:
        def load_data(self): ...
        def clean_data(self): ...
        def analyze_data(self): ...
        def visualize_data(self): ...
    
    # 낮은 응집도 (분리 필요)
    class MixedAgent:
        def load_data(self): ...
        def send_email(self): ...  # 다른 도메인!
        def analyze_data(self): ...
        def deploy_model(self): ...  # 다른 도메인!
    
  3. 결합도 최소화:

    • 에이전트 간 의존성 최소화
    • 인터페이스를 통한 통신
    # 타이트 커플링 (나쁨)
    class CodingAgent:
        def __init__(self):
            self.research_agent = ResearchAgent()  # 직접 의존
        
        def generate_code(self, topic):
            research = self.research_agent.research(topic)  # 직접 호출
            return self.code_from_research(research)
    
    # 루즈 커플링 (좋음)
    class CodingAgent:
        def __init__(self, message_bus):
            self.message_bus = message_bus
            self.message_bus.subscribe('research_complete', self.on_research)
        
        def on_research(self, message):
            research = message.payload
            code = self.code_from_research(research)
            self.message_bus.publish('code_generated', code)
    
  4. 크기 제한:

    • 에이전트가 너무 크면 분리
    • 경험적 기준: 도구 5-10개, 프롬프트 500-1000 토큰

2. 역할 정의 방법론

단계별 접근:

Step 1: 도메인 분석

프로젝트: "자동화된 코드 리뷰 시스템"

도메인 식별:
- 코드 분석
- 품질 검증
- 보안 검사
- 성능 분석
- 문서 생성

Step 2: 역할 추출

각 도메인에서 역할 도출:

코드 분석:
  - 코드 파서: 코드 구조 분석
  - 패턴 탐지기: 안티패턴 찾기

품질 검증:
  - 테스트 분석가: 테스트 커버리지 확인
  - 스타일 검사자: 코딩 스타일 검증

보안 검사:
  - 취약점 스캐너: 보안 이슈 탐지
  - 의존성 검사자: 라이브러리 취약점 확인

성능 분석:
  - 복잡도 분석가: 시간/공간 복잡도 계산
  - 병목 탐지기: 성능 이슈 찾기

문서 생성:
  - 요약 작성자: 변경 사항 요약
  - 제안 생성자: 개선 제안 작성

Step 3: 에이전트 정의

from dataclasses import dataclass
from typing import List

@dataclass
class AgentDefinition:
    name: str
    role: str
    goal: str
    backstory: str
    tools: List[str]
    model: str
    max_iterations: int

# 예시
code_parser = AgentDefinition(
    name="code_parser",
    role="코드 구조 분석가",
    goal="코드의 구조와 의존성을 파악하여 다른 에이전트가 이해할 수 있는 형태로 변환",
    backstory="10년 경력의 컴파일러 엔지니어. AST와 정적 분석 전문가",
    tools=["ast_parser", "lsp_client", "dependency_analyzer"],
    model="gpt-4",
    max_iterations=3
)

vulnerability_scanner = AgentDefinition(
    name="vulnerability_scanner",
    role="보안 취약점 탐지 전문가",
    goal="코드에서 보안 취약점을 찾아내고 심각도를 평가",
    backstory="OWASP Top 10 전문가. 화이트햇 해커 출신",
    tools=["semgrep", "bandit", "cve_database"],
    model="gpt-4",
    max_iterations=5
)

Step 4: 상호작용 설계

워크플로우:

1. 코드 파서 → 코드 구조 분석
   ↓
2. 병렬 실행:
   - 패턴 탐지기 → 안티패턴 찾기
   - 테스트 분석가 → 커버리지 확인
   - 취약점 스캐너 → 보안 이슈 탐지
   - 복잡도 분석가 → 성능 분석
   ↓
3. 요약 작성자 → 결과 통합
   ↓
4. 제안 생성자 → 개선 제안

3. 오케스트레이션 패턴 선택

의사결정 트리:

워크플로우가 선형적인가?
├─ Yes → Sequential (순차)
│         예: 리서치 → 코딩 → 테스트 → 배포
│
└─ No → 병렬 작업이 있는가?
    ├─ Yes → 병렬 작업이 독립적인가?
    │   ├─ Yes → Parallel (병렬)
    │   │         예: 여러 파일 동시 분석
    │   │
    │   └─ No → 조정이 필요한가?
    │       ├─ Yes → Hierarchical (계층적)
    │       │         예: 매니저가 작업 분배
    │       │
    │       └─ No → Event-Driven (이벤트 기반)
    │                 예: 에이전트가 이벤트에 반응
    │
    └─ No → 동적 분기가 있는가?
        ├─ Yes → Conditional (조건부)
        │         예: 테스트 실패 시 수정
        │
        └─ No → Sequential

패턴별 구현:

Sequential (순차):

from crewai import Crew, Process

crew = Crew(
    agents=[agent1, agent2, agent3],
    tasks=[task1, task2, task3],
    process=Process.sequential
)

Parallel (병렬):

import asyncio

async def parallel_execution(agents, task):
    results = await asyncio.gather(*[
        agent.execute_async(task) for agent in agents
    ])
    return results

Hierarchical (계층적):

crew = Crew(
    agents=[worker1, worker2, worker3],
    tasks=[task1, task2, task3],
    process=Process.hierarchical,
    manager_llm="gpt-4"  # 매니저 자동 생성
)

Event-Driven (이벤트 기반):

class EventBus:
    def __init__(self):
        self.subscribers = {}
    
    def subscribe(self, event_type, handler):
        if event_type not in self.subscribers:
            self.subscribers[event_type] = []
        self.subscribers[event_type].append(handler)
    
    def publish(self, event_type, data):
        if event_type in self.subscribers:
            for handler in self.subscribers[event_type]:
                handler(data)

event_bus = EventBus()

# 에이전트 등록
event_bus.subscribe('code_analyzed', coding_agent.on_analysis)
event_bus.subscribe('code_generated', testing_agent.on_code)
event_bus.subscribe('test_failed', coding_agent.on_failure)

4. 통신 프로토콜 설계

메시지 형식 표준화:

from enum import Enum
from dataclasses import dataclass
from typing import Any, Optional
import uuid
from datetime import datetime

class MessageType(Enum):
    TASK_REQUEST = "task_request"
    TASK_RESPONSE = "task_response"
    EVENT = "event"
    ERROR = "error"
    HEARTBEAT = "heartbeat"

class Priority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

@dataclass
class Message:
    """표준 메시지 형식"""
    id: str
    type: MessageType
    sender: str
    receiver: Optional[str]  # None = broadcast
    payload: Any
    priority: Priority
    correlation_id: Optional[str]  # 요청-응답 매칭
    timestamp: datetime
    metadata: dict
    
    @classmethod
    def create_request(cls, sender, receiver, payload, priority=Priority.MEDIUM):
        return cls(
            id=str(uuid.uuid4()),
            type=MessageType.TASK_REQUEST,
            sender=sender,
            receiver=receiver,
            payload=payload,
            priority=priority,
            correlation_id=str(uuid.uuid4()),
            timestamp=datetime.now(),
            metadata={}
        )
    
    @classmethod
    def create_response(cls, request_msg, sender, payload):
        return cls(
            id=str(uuid.uuid4()),
            type=MessageType.TASK_RESPONSE,
            sender=sender,
            receiver=request_msg.sender,
            payload=payload,
            priority=request_msg.priority,
            correlation_id=request_msg.correlation_id,
            timestamp=datetime.now(),
            metadata={"request_id": request_msg.id}
        )

에러 처리 프로토콜:

from enum import Enum

class ErrorCode(Enum):
    INVALID_INPUT = "INVALID_INPUT"
    TOOL_FAILURE = "TOOL_FAILURE"
    TIMEOUT = "TIMEOUT"
    RATE_LIMIT = "RATE_LIMIT"
    MODEL_ERROR = "MODEL_ERROR"
    UNKNOWN = "UNKNOWN"

@dataclass
class ErrorMessage:
    code: ErrorCode
    message: str
    details: dict
    recoverable: bool
    retry_after: Optional[int]  # seconds

# 사용
try:
    result = agent.execute(task)
except ToolException as e:
    error = ErrorMessage(
        code=ErrorCode.TOOL_FAILURE,
        message=str(e),
        details={"tool": e.tool_name, "args": e.args},
        recoverable=True,
        retry_after=5
    )
    message_bus.publish('error', error)

재시도 전략:

import time
from functools import wraps

def retry_with_backoff(max_retries=3, base_delay=1, max_delay=60):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except RecoverableError as e:
                    if attempt == max_retries - 1:
                        raise
                    
                    delay = min(base_delay * (2 ** attempt), max_delay)
                    logger.warning(
                        f"Attempt {attempt + 1} failed, retrying in {delay}s",
                        error=str(e)
                    )
                    time.sleep(delay)
        return wrapper
    return decorator

class Agent:
    @retry_with_backoff(max_retries=3)
    def execute(self, task):
        # 실행 로직
        pass

5. 실전 체크리스트

프로덕션 AI 에이전트 시스템을 설계할 때 확인해야 할 항목들:

아키텍처 설계:

  • 각 에이전트의 경계가 명확한가?
  • 에이전트 간 책임이 중복되지 않는가?
  • 에이전트 간 의존성이 최소화되어 있는가?
  • 순환 의존성이 없는가?
  • 확장 가능한 구조인가?

통신 설계:

  • 메시지 형식이 표준화되어 있는가?
  • 에러 처리 프로토콜이 정의되어 있는가?
  • 재시도 전략이 있는가?
  • 타임아웃이 설정되어 있는가?
  • 메시지 순서가 보장되어야 하는가?

신뢰성:

  • 단일 장애점이 없는가?
  • 에이전트 실패 시 격리되는가?
  • 부분 실패를 처리할 수 있는가?
  • 롤백 메커니즘이 있는가?
  • 서킷 브레이커가 구현되어 있는가?

관찰 가능성:

  • 모든 에이전트가 로그를 남기는가?
  • 메트릭이 수집되는가?
  • 분산 트레이싱이 가능한가?
  • 대시보드가 있는가?
  • 알림 시스템이 있는가?

성능:

  • 병목 지점이 파악되었는가?
  • 병렬 실행이 가능한 부분이 병렬화되었는가?
  • 캐싱 전략이 있는가?
  • 토큰 사용량이 최적화되었는가?
  • 비용 예측이 가능한가?

보안:

  • 에이전트 간 인증이 있는가?
  • 민감한 데이터가 암호화되는가?
  • 권한 관리가 되는가?
  • 감사 로그가 남는가?
  • 레이트 리미팅이 있는가?

결론: 온고지신(溫故知新)

새로운 기술, 오래된 지혜

AI 에이전트 시스템의 진화를 추적하면서, 우리는 흥미로운 패턴을 발견했다. 기술은 새롭지만, 우리가 마주한 문제와 해결책은 놀라울 정도로 익숙하다.

2022년: "AI가 모든 것을 바꿀 것이다!" 2023년: "프롬프트 엔지니어링이 새로운 프로그래밍이다!" 2024년: "자율 에이전트가 개발자를 대체할 것이다!" 2025-2026년: "아, 결국 소프트웨어 아키텍처가 필요하구나..."

이것은 실패가 아니다. 성숙의 과정이다.

모든 혁신적 기술은 이런 과정을 거친다:

  1. 과대광고 (Hype): 무한한 가능성에 대한 환상
  2. 환멸 (Disillusionment): 현실의 한계 직면
  3. 통합 (Integration): 기존 지식과의 융합
  4. 성숙 (Maturity): 실용적 적용

AI 에이전트 시스템은 지금 3단계에서 4단계로 넘어가고 있다.

핵심 교훈

1. 기본 원칙은 변하지 않는다

50년 전 Edsger Dijkstra가 말한 "관심사의 분리(Separation of Concerns)"는 여전히 유효하다. AI 에이전트든 마이크로서비스든, 복잡한 시스템을 관리 가능한 부분으로 나누는 것이 핵심이다.

2. 추상화는 강력하다

"에이전트"라는 추상화는 강력하다. 하지만 그 아래에는 여전히:

  • 입력과 출력
  • 상태 관리
  • 에러 처리
  • 통신 프로토콜

이런 기본 요소들이 있다. 추상화를 이해하되, 그 아래를 무시하지 말아야 한다.

3. 복잡도는 사라지지 않는다

AI가 많은 것을 자동화하지만, 시스템 복잡도는 사라지지 않는다. 단지 다른 곳으로 이동할 뿐이다:

  • 코드 복잡도 → 프롬프트 복잡도
  • 함수 호출 → 에이전트 통신
  • 디버깅 → 에이전트 추적

복잡도를 관리하는 방법을 알아야 한다.

4. 검증된 패턴을 활용하라

바퀴를 재발명하지 말자. 소프트웨어 공학이 50년간 축적한 지혜를 활용하자:

  • SOLID 원칙
  • 디자인 패턴
  • 아키텍처 패턴
  • 모니터링과 관찰 가능성

이것들은 AI 시스템에도 그대로 적용된다.

5. 도구는 바뀌어도 원칙은 남는다

오늘은 GPT-4, Claude, Gemini다. 내일은 다른 모델일 것이다. 하지만:

  • 명확한 책임 분리
  • 느슨한 결합
  • 높은 응집도
  • 명시적 인터페이스

이런 원칙들은 변하지 않는다.

다음 단계는 무엇인가?

AI 에이전트 시스템의 미래를 예측하는 것은 어렵다. 하지만 소프트웨어 공학의 역사를 보면 힌트를 얻을 수 있다.

단기 (2026-2027):

  • 표준화: MCP 같은 프로토콜의 광범위한 채택
  • 도구 성숙: 더 나은 디버깅, 모니터링 도구
  • 베스트 프랙티스: 검증된 패턴의 문서화
  • 프레임워크 통합: 주요 프레임워크들의 수렴

중기 (2027-2029):

  • 플랫폼화: 에이전트 오케스트레이션 플랫폼 (Kubernetes for Agents?)
  • 서비스 메시: 에이전트 간 통신 인프라
  • 관찰 가능성: 분산 트레이싱, 메트릭, 로깅의 통합
  • 거버넌스: 에이전트 행동 제어 및 감사

장기 (2029+):

  • 자가 최적화: 에이전트 시스템이 스스로 성능 최적화
  • 자가 치유: 장애 자동 감지 및 복구
  • 적응형 아키텍처: 워크로드에 따라 동적으로 재구성
  • 메타 에이전트: 에이전트 시스템을 설계하는 에이전트

하지만 이 모든 것의 기반은 여전히:

  • 명확한 아키텍처
  • 표준화된 통신
  • 관찰 가능성
  • 신뢰성

즉, 우리가 이미 알고 있는 것들이다.

실천 가이드

지금 당장 할 수 있는 것:

  1. 기존 시스템 리팩토링:

    • 단일 에이전트를 역할별로 분리
    • 명시적 인터페이스 정의
    • 통신 프로토콜 표준화
  2. 관찰 가능성 추가:

    • 구조화된 로깅 도입
    • 메트릭 수집 시작
    • 트레이싱 구현
  3. 문서화:

    • 각 에이전트의 역할과 책임 명시
    • 통신 프로토콜 문서화
    • 아키텍처 다이어그램 작성
  4. 테스트:

    • 에이전트 단위 테스트
    • 통합 테스트
    • 카오스 엔지니어링 (장애 주입 테스트)
  5. 학습:

    • 소프트웨어 아키텍처 책 읽기
    • 마이크로서비스 패턴 학습
    • 분산 시스템 이해

마치며

AI 에이전트 시스템의 진화는 우리에게 중요한 교훈을 준다:

혁신은 과거를 부정하는 것이 아니라, 과거의 지혜 위에 새로운 것을 쌓는 것이다.

우리는 새로운 도구를 가졌다. LLM은 강력하고, 에이전트는 유용하다. 하지만 이것들을 효과적으로 사용하려면, 우리는 여전히:

  • 좋은 아키텍처가 필요하다
  • 명확한 설계가 필요하다
  • 체계적인 테스트가 필요하다
  • 지속적인 모니터링이 필요하다

즉, 좋은 소프트웨어 엔지니어링이 필요하다.

AI 시대에도, 아니 AI 시대이기에, 소프트웨어 공학의 기본 원칙은 더욱 중요해졌다.

온고지신(溫故知新) - 옛것을 익히고 그것을 미루어서 새것을 안다.

이것이 바로 AI 에이전트 아키텍처의 진화가 우리에게 주는 메시지다.


참고 자료

논문 및 연구

  • Yao et al. (2023). "ReAct: Synergizing Reasoning and Acting in Language Models"
  • Wang et al. (2024). "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation"
  • LangChain Team (2024). "LangGraph: Building Stateful Multi-Agent Applications"

프레임워크 문서

블로그 및 아티클

  • Andrew Ng (2024). "Agentic Design Patterns"
  • Anthropic (2026). "Building Effective Agents"
  • Anthropic (2026). "Claude Opus 4.6 and Agent Teams"

소프트웨어 아키텍처 고전

  • Martin Fowler. "Patterns of Enterprise Application Architecture"
  • Eric Evans. "Domain-Driven Design"
  • Sam Newman. "Building Microservices"
  • Gregor Hohpe. "Enterprise Integration Patterns"

온라인 리소스


저자 노트: 이 글은 2026년 2월 현재의 AI 에이전트 생태계를 기반으로 작성되었습니다. 기술은 빠르게 발전하지만, 여기서 다룬 아키텍처 원칙들은 시간이 지나도 유효할 것입니다.

라이선스: 이 글은 CC BY-SA 4.0 라이선스 하에 배포됩니다. 자유롭게 공유하고 수정할 수 있으나, 출처를 밝혀주시고 같은 라이선스로 배포해주세요.

피드백: 의견이나 제안이 있으시면 언제든 연락 주세요. 함께 배우고 성장합시다.

Clickable cat