AI 에이전트의 구조와 제품 개발 원칙

생성형 AI를 애플리케이션에 적용할 때 흔히 프롬프트 작성부터 시작합니다.
좋은 프롬프트를 입력하면 모델이 필요한 답을 만들어 줄 것이라는 접근입니다.

간단한 요약, 분류, 문장 생성 정도라면 이 방식으로도 충분합니다. 그러나 코드 수정, 장애 분석, 데이터 조회, 배포, 검증처럼 여러 단계가 필요한 작업은 단일 모델 호출만으로 안정적으로 처리하기 어렵습니다.

복잡한 문제를 해결하려면 모델이 필요한 정보를 추가로 찾고, 도구를 실행하고, 결과를 확인한 뒤 다음 행동을 결정할 수 있어야 합니다.
이 과정에서 워크플로우와 에이전트, 세션과 턴, 도구와 작업 큐, 가드레일과 훅 같은 개념이 등장합니다.

이 글에서는 AI 에이전트가 내부적으로 어떤 구조로 동작하는지 살펴보고, 이를 실제 제품 개발에 적용할 때 무엇을 고려해야 하는지 정리합니다.


1. 모델 단일 호출만으로는 부족한 이유

1.1 모델은 주어진 지식 범위 안에서만 답할 수 있다

LLM이 답변을 생성할 때 활용할 수 있는 정보는 크게 두 가지입니다.

  • 사전 학습 과정에서 습득한 지식
  • 현재 요청의 컨텍스트에 포함된 정보

모델이 학습하지 않은 사내 정책, 최근 장애 기록, 고객 데이터, 현재 소스 코드 상태는 별도로 전달하지 않는 한 알 수 없습니다.

예를 들어 다음과 같은 요청을 모델에 전달한다고 가정해 보겠습니다.

최근 발생한 결제 실패의 원인을 분석해 주세요.

이 요청만으로는 정확한 분석이 어렵습니다.
최소한 다음 정보가 추가로 필요합니다.

  • 애플리케이션 로그
  • 결제 API 응답
  • 데이터베이스 상태
  • 최근 배포 내역
  • 재현 조건
  • 관련 소스 코드

결국 복잡한 문제는 한 번의 프롬프트로 끝나는 것이 아니라, 필요한 정보를 단계적으로 수집하고 검증하는 과정이 필요합니다.


1.2 프롬프트에는 표현 가능한 정보량의 한계가 있다

복잡한 업무 상황을 하나의 프롬프트에 완전하게 기술하기는 어렵습니다.

요구사항, 예외 조건, 운영 정책, 권한 규칙, 기존 설계 의도, 과거 장애 사례를 모두 한 번에 작성하려 하면 프롬프트는 지나치게 길어집니다.
반대로 설명을 줄이면 중요한 조건이 누락됩니다.

따라서 실제 문제 해결에서는 사람이 모든 정보를 처음부터 완성된 형태로 제공하기보다, 모델이 작업을 진행하면서 필요한 정보를 추가로 요청하거나 직접 조회하도록 구성하는 편이 효율적입니다.

초기 요청
→ 필요한 정보 식별
→ 로그 또는 코드 조회
→ 결과 분석
→ 추가 정보 요청
→ 최종 판단

1.3 출력의 비결정성을 고려해야 한다

LLM은 동일한 입력을 받더라도 항상 같은 결과를 반환하지 않습니다.

샘플링, 디코딩 설정, 컨텍스트 상태에 따라 표현뿐 아니라 문제 해결 과정도 달라질 수 있습니다. 특히 모델이 직접 도구와 경로를 선택하는 에이전트 구조에서는 실행 과정 자체가 매번 달라질 수 있습니다.

따라서 다음과 같은 요구가 있는 업무에는 별도의 통제 장치가 필요합니다.

  • 동일한 입력에 대해 동일한 절차가 요구되는 업무
  • 법적·보안상 검증이 필요한 업무
  • 금전이나 개인정보를 다루는 업무
  • 장애 복구처럼 실패 비용이 큰 업무

1.4 프롬프트만으로 할루시네이션을 막을 수는 없다

프롬프트에 “사실만 답하라”, “모르면 모른다고 답하라”는 지시를 넣더라도 할루시네이션을 완전히 제거할 수는 없습니다.

오류를 줄이기 위해서는 프롬프트 문구뿐 아니라 실행 구조에서 통제해야 합니다.

예를 들면 다음과 같습니다.

  • 답변 전에 데이터베이스나 문서를 조회하도록 강제
  • 출처가 없는 내용은 출력하지 않도록 검사
  • 실행 전 권한 검증
  • 생성 결과를 별도 검증 모델이나 규칙 엔진으로 확인
  • 허용된 도구만 호출하도록 제한
  • 위험한 명령은 사람의 승인을 요구

핵심은 모델을 무조건 신뢰하는 것이 아니라, 모델이 실수해도 시스템 전체가 잘못되지 않도록 구조를 설계하는 것입니다.


2. 워크플로우와 에이전트의 차이

워크플로우와 에이전트를 구분하는 가장 명확한 기준은 제어 흐름을 누가 결정하느냐입니다.

코드가 흐름을 결정하면 워크플로우이고, 모델이 흐름을 결정하면 에이전트입니다.


2.1 워크플로우

워크플로우에서는 개발자가 실행 순서와 분기를 코드로 정의합니다.

입력 수신
→ 분류
→ 관련 문서 검색
→ 답변 생성
→ 결과 검증
→ 응답 반환

모델은 각 단계에서 정해진 역할만 수행하며, 다음 단계로 이동할지는 애플리케이션 코드가 결정합니다.

대표적인 워크플로우 패턴은 다음과 같습니다.

프롬프트 체이닝

하나의 모델 출력을 다음 모델 호출의 입력으로 연결합니다.

문서 요약
→ 핵심 키워드 추출
→ 보고서 작성

라우팅

입력을 분류하고 미리 정의된 처리 경로로 보냅니다.

return switch (requestType) {
    case PAYMENT -> paymentHandler.handle(request);
    case REFUND -> refundHandler.handle(request);
    case DELIVERY -> deliveryHandler.handle(request);
};

병렬 처리

서로 독립적인 작업을 동시에 실행한 뒤 코드가 결과를 취합합니다.

보안 검토 ─┐
성능 검토 ─┼→ 결과 취합
코드 품질 ─┘

워크플로우는 예측 가능성과 재현성이 높습니다.
실행 경로가 코드에 고정되어 있으므로 테스트와 운영 통제가 비교적 쉽습니다.


2.2 에이전트

에이전트에서는 모델이 현재 상태를 바탕으로 다음 행동을 결정합니다.

모델은 실행 중에 다음과 같은 판단을 할 수 있습니다.

  • 어떤 도구를 사용할지
  • 어떤 파일을 읽을지
  • 추가 정보가 필요한지
  • 동일한 작업을 다시 시도할지
  • 새로운 작업으로 분리할지
  • 작업을 종료할지

일반적인 흐름은 다음과 같습니다.

요청 수신
→ 모델 판단
→ 도구 선택
→ 도구 실행
→ 결과 관찰
→ 다음 행동 판단
→ 목표 달성 시 종료

에이전트는 고정된 워크플로우보다 유연합니다.
반면 다음 문제가 발생할 수 있습니다.

  • 실행 경로를 예측하기 어렵습니다.
  • 같은 요청도 서로 다른 과정을 거칠 수 있습니다.
  • 반복 호출로 비용이 증가할 수 있습니다.
  • 잘못된 판단이 연쇄적으로 이어질 수 있습니다.
  • 테스트 범위가 넓어집니다.

따라서 모든 업무를 에이전트로 만들기보다, 불확실한 판단이 필요한 구간에만 모델의 제어권을 주는 방식이 현실적입니다.


3. 에이전트 루프의 기본 구조

에이전트의 동작을 이해하려면 세션, 턴, 이터레이션을 구분해야 합니다.

개념의미
세션하나의 문제를 해결하기 위해 유지되는 대화와 상태의 범위
하나의 요청이 시작되어 최종 응답이 반환될 때까지의 범위
이터레이션하나의 턴 안에서 모델 호출과 도구 실행이 반복되는 단위

3.1 턴

턴은 요청이 발생하면서 시작되고 최종 응답을 출력하면서 종료됩니다.

요청은 반드시 사용자가 직접 입력하는 프롬프트일 필요는 없습니다.

다음과 같은 이벤트도 새로운 요청이 될 수 있습니다.

  • 스케줄러가 생성한 작업
  • 서브에이전트가 전달한 메시지
  • 비동기 도구의 완료 이벤트
  • 목표 검증 후 자동으로 생성된 후속 요청
  • 외부 시스템의 이벤트
요청 발생
→ 턴 시작
→ 작업 수행
→ 최종 응답
→ 턴 종료

3.2 이터레이션

하나의 턴 안에서 모델은 여러 번 호출될 수 있습니다.

예를 들어 “회원가입 오류를 분석하고 수정해 주세요”라는 요청은 다음과 같이 진행될 수 있습니다.

모델 판단
→ 프로젝트 파일 검색
→ 검색 결과 수령
→ 관련 파일 읽기
→ 코드 분석
→ 코드 수정
→ 테스트 실행
→ 실패 원인 분석
→ 재수정
→ 최종 응답

이 과정에서 발생하는 각각의 모델 판단과 도구 실행 주기가 이터레이션입니다.

모델 판단
→ 도구 호출
→ 도구 실행
→ 결과 수령
→ 모델 재판단

에이전트가 긴 작업을 수행할 수 있는 이유는 하나의 답변을 오래 생성해서가 아니라, 턴 내부에서 여러 이터레이션을 반복하기 때문입니다.


3.3 세션

세션은 여러 턴을 수용하는 작업 공간입니다.

사용자는 하나의 세션에서 요구사항을 추가하고, 모델은 이전 대화와 작업 결과를 참고하면서 문제를 점진적으로 해결합니다.

세션
 ├─ 턴 1: 요구사항 분석
 ├─ 턴 2: 설계 제안
 ├─ 턴 3: 코드 수정
 └─ 턴 4: 테스트 및 검증

세션은 사람이 완성된 프롬프트를 처음부터 작성하는 대신, 모델과 협력해 문제 해결에 필요한 컨텍스트를 점진적으로 작성하는 과정으로 볼 수 있습니다.

그러나 세션이 길어진다고 항상 좋은 것은 아닙니다.


4. 긴 컨텍스트가 성능을 떨어뜨리는 이유

세션이 길어질수록 다음 정보가 함께 쌓입니다.

  • 이전 요구사항
  • 취소된 요청
  • 중간에 변경된 설계
  • 잘못된 추론
  • 실패한 코드
  • 현재는 사용하지 않는 조건
  • 서로 모순되는 지시

예를 들어 다음과 같은 대화가 모두 컨텍스트에 남을 수 있습니다.

JPA를 사용해 주세요.
→ MyBatis로 변경해 주세요.
→ 기존 JPA 구조는 유지해 주세요.
→ JPA 관련 코드는 모두 제거해 주세요.

사람도 이런 이력을 한꺼번에 전달받으면 현재 기준을 판단하기 어렵습니다. 모델도 마찬가지입니다.

컨텍스트 길이가 늘어나면 단순히 토큰 수만 증가하는 것이 아니라, 중요하지 않은 정보와 모순된 정보가 함께 증가합니다.
이를 컨텍스트 희석 또는 컨텍스트 오염으로 볼 수 있습니다.


4.1 컨텍스트를 무한히 유지해서는 안 된다

긴 세션을 계속 유지하면 다음과 같은 문제가 생길 수 있습니다.

  • 이전 지시와 현재 지시를 혼동합니다.
  • 이미 폐기한 설계를 다시 적용합니다.
  • 동일한 파일을 반복해서 수정합니다.
  • 자동 요약 과정에서 세부 조건이 손실됩니다.
  • 토큰 비용은 증가하지만 결과 품질은 낮아집니다.

일부에서는 컨텍스트 사용량을 시계에 비유해 4시와 7시를 경험적인 경계로 설명하기도 합니다.

  • 4시 지점까지는 비교적 안정적으로 작업합니다.
  • 7시 지점부터는 품질 저하가 뚜렷해질 수 있습니다.
  • 9시 이후에는 새로운 세션으로 전환하는 편이 낫다는 관점입니다.

이는 모든 모델에 적용되는 공식 법칙은 아닙니다. 모델 종류, 작업 복잡도, 프롬프트 구성에 따라 달라지는 경험적 가이드입니다.

중요한 것은 정확한 비율이 아니라 다음 원칙입니다.

하나의 세션에서 모든 요구사항을 끝없이 처리하지 말고, 작업의 목적과 범위가 달라지면 새로운 세션으로 분리해야 합니다.


4.2 세션을 분리해야 하는 시점

다음과 같은 경우에는 새로운 세션을 여는 편이 좋습니다.

  • 요구사항이 크게 변경된 경우
  • 기존 설계 방향을 폐기한 경우
  • 구현 단계에서 검토 단계로 전환한 경우
  • 특정 장애 원인만 독립적으로 조사할 경우
  • 이전 대화의 가정에 영향을 받지 않는 검증이 필요한 경우
  • 모델이 같은 실수를 반복하기 시작한 경우

새 세션을 열 때는 기존 대화를 전부 복사하기보다 현재 유효한 결정만 정리해 전달해야 합니다.

현재 확정 사항
1. Spring Boot 4와 Java 21을 사용한다.
2. 결제 모듈 외 다른 모듈은 수정하지 않는다.
3. 공개 API 스펙은 유지한다.
4. 기존 JPA 방식은 폐기하고 MyBatis를 사용한다.
5. 수정 후 통합 테스트를 실행한다.

컨텍스트는 많이 주는 것이 목적이 아니라, 필요한 정보를 정확히 주는 것이 목적입니다.


5. 서브에이전트는 독립된 작업 세션이다

서브에이전트를 여러 명의 AI 직원처럼 표현하기도 하지만, 구현 관점에서는 별도의 세션에 가깝습니다.

메인 모델이 도구를 통해 새로운 세션을 생성하고, 해당 세션에 일부 작업을 위임하는 구조입니다.

메인 세션
 ├─ 코드 분석 서브세션
 ├─ 보안 검토 서브세션
 └─ 테스트 검토 서브세션

5.1 컨텍스트를 공유하는 서브에이전트

기존 요구사항과 설계 배경을 알아야 하는 작업이라면 메인 세션의 컨텍스트 일부를 전달할 수 있습니다.

현재까지 확정된 설계 결정과 변경 이력을 참고하여
결제 모듈의 트랜잭션 경계를 검토해 주세요.

이 방식은 기존 맥락을 유지할 수 있지만, 메인 세션에 잘못된 가정이나 불필요한 정보가 많다면 그대로 영향을 받습니다.


5.2 독립된 컨텍스트를 사용하는 서브에이전트

객관적인 검토가 필요하다면 빈 컨텍스트에서 시작하는 편이 낫습니다.

이 코드만 기준으로 보안, 동시성, 트랜잭션 문제를 검토해 주세요.
기존 대화나 설계 의도는 참고하지 마세요.

동일한 모델을 사용하더라도 컨텍스트가 다르면 다른 결과를 낼 수 있습니다.

따라서 서브에이전트를 사용하는 핵심 목적은 모델 수를 늘리는 것이 아니라, 문제와 컨텍스트를 분리하는 데 있습니다.


6. Goal을 통한 여러 턴의 자동 실행

일반적인 세션은 하나의 턴이 끝나면 다음 요청을 기다립니다.

반면 Goal이 설정된 세션에서는 턴이 끝날 때마다 모델이 목표 달성 여부를 평가할 수 있습니다.

작업 수행
→ 턴 종료
→ 목표 달성 여부 평가
→ 미달성
→ 후속 요청 생성
→ 다음 턴 수행

예를 들어 목표를 다음과 같이 설정할 수 있습니다.

모든 테스트가 통과하고,
정적 분석에서 심각한 오류가 없으며,
기존 공개 API가 유지될 때까지 작업을 계속한다.

이 경우 모델은 코드 수정만 하고 종료하지 않고, 테스트와 검증 결과를 확인한 뒤 목표가 충족되지 않으면 새로운 작업을 생성할 수 있습니다.

다만 목표가 지나치게 추상적이면 반복이 끝나지 않을 수 있습니다.

나쁜 목표는 다음과 같습니다.

코드를 완벽하게 개선한다.

완료 조건을 판단할 수 없기 때문입니다.

목표는 검증 가능한 조건으로 작성해야 합니다.

1. 전체 단위 테스트가 통과해야 한다.
2. 결제 모듈 외 파일은 변경하지 않는다.
3. 응답 시간은 기존 대비 10% 이상 악화되지 않아야 한다.
4. 공개 API 스펙은 변경하지 않는다.

7. 도구는 기능이 아니라 제어권이다

AI 에이전트의 도구를 단순히 파일 읽기나 검색 기능으로만 보면 부족합니다.

모델에게 어떤 기능을 도구로 제공하느냐에 따라 모델이 제어할 수 있는 범위가 달라집니다.


7.1 코드에 넣은 기능과 도구로 제공한 기능

같은 기능이라도 코드에 고정하면 애플리케이션이 제어합니다.

if (currentTaskCompleted()) {
    executeNextTask();
}

반면 다음 작업을 생성하는 기능을 도구로 제공하면 모델이 실행 여부를 결정합니다.

create_next_request(
  task = "통합 테스트 실패 원인을 분석한다"
)

전자는 워크플로우이며, 후자는 에이전트적 제어입니다.

모델에게 다음 기능을 도구로 제공할 수 있습니다.

  • 파일 읽기와 수정
  • 터미널 실행
  • 웹 검색
  • 새로운 세션 생성
  • 서브에이전트 호출
  • 요청 큐에 작업 추가
  • 다음 턴 예약
  • 목표 설정
  • 스케줄러 등록
  • 모델 변경
  • 컨텍스트 압축
  • 플러그인 설치

도구가 늘어날수록 모델의 자율성도 커지지만, 실패했을 때의 영향 범위도 넓어집니다.

따라서 에이전트 설계의 핵심 질문은 다음과 같습니다.

모델에게 어디까지 판단권과 실행권을 넘길 것인가?


8. 요청 큐와 작업 큐

에이전트가 여러 작업을 처리한다고 해서 모델이 실제 프로그램의 반복문을 직접 작성하는 것은 아닙니다.

내부 시스템에서는 모델이 생성한 요청이나 도구 결과를 큐에 넣고, 에이전트 런타임이 이를 순차적으로 처리합니다.

공통 작업 큐
 ├─ USER_REQUEST
 ├─ TOOL_RESULT
 ├─ SUB_AGENT_RESULT
 ├─ SCHEDULED_TASK
 └─ GOAL_RETRY

초기에는 다음과 같이 큐가 기능별로 분리될 수 있습니다.

  • 사용자 요청 큐
  • 도구 실행 큐
  • 비동기 작업 완료 큐
  • 서브에이전트 응답 큐
  • 스케줄러 이벤트 큐

그러나 큐가 많아질수록 상태 관리가 복잡해집니다.
따라서 공통 큐 하나를 사용하고 메시지 타입으로 이벤트를 구분하는 구조도 고려할 수 있습니다.

public sealed interface AgentEvent {
    record UserRequest(String content) implements AgentEvent {}
    record ToolResult(String toolId, Object result) implements AgentEvent {}
    record SubAgentResult(String sessionId, String result) implements AgentEvent {}
    record ScheduledTask(String taskId) implements AgentEvent {}
}

중요한 것은 큐의 개수가 아니라 다음 사항입니다.

  • 이벤트 순서를 보장할 것인지
  • 중복 이벤트를 어떻게 처리할 것인지
  • 실패한 작업을 재시도할 것인지
  • 타임아웃을 어떻게 관리할 것인지
  • 세션별 메시지를 어떻게 분리할 것인지

9. 전용 도구와 범용 도구

AI 코딩 에이전트는 도구를 제공하는 방식에 따라 크게 두 방향으로 나눌 수 있습니다.


9.1 전용 도구 방식

파일 검색, 읽기, 수정, 패치, 테스트 실행 등 목적별 도구를 제공합니다.

search_files
read_file
edit_file
run_tests
inspect_git_diff

장점은 다음과 같습니다.

  • 모델의 선택 범위를 줄일 수 있습니다.
  • 입력과 출력 형식을 엄격하게 통제할 수 있습니다.
  • 권한을 세밀하게 제한할 수 있습니다.
  • 작은 모델도 비교적 안정적으로 사용할 수 있습니다.
  • 잘못된 명령을 실행할 가능성을 줄일 수 있습니다.

단점도 있습니다.

  • 도구가 많아질수록 선택이 어려워집니다.
  • 기능이 겹치는 도구 사이에서 충돌할 수 있습니다.
  • 새로운 기능마다 도구를 추가해야 할 수 있습니다.
  • 도구 설명과 스키마 관리가 복잡해집니다.

9.2 범용 도구 방식

Shell이나 터미널처럼 강력한 도구 몇 개만 제공하고 모델이 명령을 조합하도록 합니다.

shell("grep -R \"PaymentService\" ./src")
shell("./gradlew test")
shell("git diff --stat")

장점은 다음과 같습니다.

  • 도구 구성이 단순합니다.
  • 다양한 작업에 대응할 수 있습니다.
  • 별도 도구를 계속 추가하지 않아도 됩니다.
  • 기존 CLI 생태계를 그대로 활용할 수 있습니다.

단점도 분명합니다.

  • 모델의 터미널 사용 능력이 충분해야 합니다.
  • 잘못된 명령으로 파일이나 환경을 손상시킬 수 있습니다.
  • 권한과 실행 범위를 통제하기 어렵습니다.
  • 작은 모델에는 추론 부담이 큽니다.
  • 명령 실행 결과를 안정적으로 해석해야 합니다.

전용 도구와 범용 도구 중 하나가 항상 우월한 것은 아닙니다. 모델의 성능, 실행 환경, 보안 수준, 작업 복잡도에 따라 선택해야 합니다.


10. 도구와 스킬의 역할 분리

도구에는 일반적으로 이름, 설명, 입력 스키마 정도만 제공됩니다.

도구 설명이 지나치게 길어지면 모델이 도구를 언제 선택해야 하는지 판단하기 어려워질 수 있습니다.
반대로 설명이 짧으면 복잡한 사용 방법과 예외 조건을 전달하기 어렵습니다.

이를 보완하기 위해 실행 기능과 사용 지침을 분리할 수 있습니다.

실행 기능
→ Shell 도구

사용 방법과 절차
→ Skill 문서

예를 들면 다음과 같습니다.

도구
- shell

스킬
- spring-deployment.md
- kubernetes-rollback.md
- database-migration.md
- incident-analysis.md

스킬 문서에는 다음 내용을 포함할 수 있습니다.

  • 언제 해당 스킬을 사용해야 하는지
  • 실행 절차
  • 명령어 예제
  • 실패 시 처리 방법
  • 금지된 작업
  • 검증 방법
  • 롤백 절차

이 구조는 실행 도구를 단순하게 유지하면서도 구체적인 작업 절차를 제공할 수 있다는 장점이 있습니다.

다만 동일한 기능을 도구 설명과 스킬 문서에 중복해서 작성하면 모델의 선택이 오히려 불안정해질 수 있습니다. 역할을 분명히 나눠야 합니다.


11. 도구의 비동기 실행

도구 실행은 대부분 비동기적으로 처리할 수 있습니다.

특히 다음 작업은 오래 걸릴 가능성이 높습니다.

  • 전체 테스트 실행
  • 대규모 코드 검색
  • 빌드와 배포
  • 외부 API 호출
  • 데이터 분석
  • 서브에이전트 작업
  • 장시간 수행되는 터미널 명령

이 작업들을 에이전트의 메인 루프에서 동기적으로 기다리면 전체 세션이 멈출 수 있습니다.


11.1 인메모리 실행

도구를 에이전트 본체와 같은 프로세스에서 실행하는 방식입니다.

장점:

  • 호출 비용이 작습니다.
  • 객체를 직접 전달할 수 있습니다.
  • 별도의 직렬화 과정이 적습니다.
  • 구현이 단순합니다.

단점:

  • 도구가 블로킹되면 본체도 멈출 수 있습니다.
  • 도구의 메모리 누수나 예외가 전체 애플리케이션에 영향을 줄 수 있습니다.
  • 임의의 코드 실행 도구에는 적합하지 않습니다.

11.2 별도 프로세스 실행

도구를 독립된 프로세스로 실행하는 방식입니다.

장점:

  • 도구 실패가 본체에 직접 영향을 주지 않습니다.
  • CPU와 메모리 자원을 분리할 수 있습니다.
  • 타임아웃과 강제 종료가 쉽습니다.
  • 다양한 언어나 프로그램을 연동할 수 있습니다.

단점:

  • 프로세스 생성 비용이 발생합니다.
  • 입력과 출력을 직렬화해야 합니다.
  • 진행 상태와 취소 이벤트를 별도로 전달해야 합니다.
  • 스트리밍 프로토콜이 필요할 수 있습니다.

CLI와 외부 프로그램을 실행하는 도구라면 별도 프로세스가 안전한 경우가 많습니다.


12. 장시간 작업을 기다리는 방법

도구 실행이 오래 걸릴 때는 두 가지 방식이 있습니다.


12.1 현재 턴에서 계속 기다리는 방식

도구가 완료될 때까지 현재 턴을 유지합니다.

도구 실행
→ 상태 대기
→ 완료 이벤트
→ 모델 재호출

장점:

  • 작업이 끝나면 즉시 후속 판단을 할 수 있습니다.
  • 처리 과정이 하나의 턴 안에 유지됩니다.

단점:

  • 작업 시간이 길면 턴이 오래 유지됩니다.
  • 사용자의 추가 요청을 처리하기 어려울 수 있습니다.
  • 네트워크 연결과 스트리밍 관리가 복잡합니다.

12.2 턴을 종료하고 스케줄러에 맡기는 방식

도구 실행을 시작한 뒤 현재 턴을 종료합니다.

스케줄러가 작업 상태를 주기적으로 확인하고, 완료되면 새로운 요청을 생성합니다.

작업 시작
→ 현재 턴 종료
→ 스케줄러가 상태 확인
→ 작업 완료
→ 세션 재개

장점:

  • 대기 중에도 다른 요청을 처리할 수 있습니다.
  • 장시간 작업에 적합합니다.
  • 세션 연결을 오래 유지하지 않아도 됩니다.

단점:

  • 작업 상태를 영속적으로 저장해야 합니다.
  • 세션 재개 로직이 필요합니다.
  • 중복 실행과 완료 이벤트의 멱등성을 처리해야 합니다.

13. 도구는 결과 외의 메시지도 만들 수 있다

도구는 단순히 실행 결과만 반환하지 않을 수 있습니다.

다음과 같은 부수 효과를 발생시킬 수 있습니다.

  • 진행 상태 업데이트
  • 사용자에게 중간 메시지 전송
  • 시스템 리마인더 삽입
  • 새로운 서브에이전트 생성
  • 다음 작업을 큐에 등록
  • 권한 승인 요청
  • 실패 복구 요청 생성

예를 들어 여러 단계로 진행되는 데이터 마이그레이션 도구는 각 단계가 끝날 때마다 상태를 전달할 수 있습니다.

스키마 검증 완료
→ 데이터 복사 30% 진행
→ 데이터 복사 100% 완료
→ 무결성 검증 시작
→ 검증 실패 3건 발견

모델이 긴 작업에서 목표를 잊지 않도록 다음과 같은 리마인더를 삽입할 수도 있습니다.

현재 목표:
1. 원본 데이터는 변경하지 않는다.
2. 실패한 레코드는 별도 파일로 저장한다.
3. 전체 검증이 끝나기 전에는 운영 테이블을 교체하지 않는다.

14. 턴 루프를 제어하는 훅

에이전트의 메인 루프에 보안, 검증, 로깅, 리라이팅 기능을 직접 추가하기 시작하면 코드가 빠르게 복잡해집니다.

이를 방지하기 위해 주요 이벤트 사이에 훅을 배치할 수 있습니다.

요청 수신
→ 요청 훅
→ 모델 호출 전 훅
→ 모델 응답
→ 도구 호출 훅
→ 도구 실행
→ 도구 결과 훅
→ 응답 완료 훅

14.1 요청 훅

사용자나 시스템의 요청을 수신한 직후 실행합니다.

활용 예:

  • 프롬프트 리라이팅
  • 요청 유효성 검사
  • 악성 입력 검사
  • 사용자 권한 확인
  • 컨텍스트 압축 필요 여부 판단

14.2 모델 호출 전 훅

모델에 컨텍스트를 전달하기 직전에 실행합니다.

활용 예:

  • 민감정보 제거
  • 시스템 지침 삽입
  • 현재 목표 리마인더 추가
  • 토큰 길이 검사
  • 폐기된 메시지 제거

14.3 응답 스트림 훅

모델의 응답을 스트리밍으로 수신하는 동안 실행합니다.

활용 예:

  • 금지된 내용 탐지
  • 비정상 출력 차단
  • 중간 응답 필터링
  • 반복 출력 감지
  • 스트림 중단

14.4 도구 호출 요청 훅

모델이 도구를 실행하려 할 때 호출합니다.

활용 예:

  • 도구 인자 보정
  • 실행 권한 검사
  • 경로 접근 제한
  • 위험 명령 탐지
  • 사용자 승인 요청
  • 실행 차단

예를 들어 다음 명령은 자동 실행을 막아야 합니다.

rm -rf /
DROP DATABASE production;
kubectl delete namespace production

14.5 도구 결과 수집 훅

도구 실행 결과를 모델에 전달하기 전에 호출합니다.

활용 예:

  • 여러 결과 병합
  • 오류 메시지 정규화
  • 민감정보 제거
  • 반복 호출 감지
  • 출력 길이 축소
  • 실패 여부 판단

14.6 응답 완료 훅

모델이 최종 응답을 생성했을 때 실행합니다.

활용 예:

  • 목표 달성 여부 검사
  • 후속 턴 생성
  • 결과 포맷 검증
  • 컨텍스트 압축
  • 감사 로그 저장
  • 사용자 응답 필터링

훅을 사용하면 핵심 턴 루프는 단순하게 유지하면서 정책과 통제 기능을 독립적으로 확장할 수 있습니다.


15. AI가 만든 코드는 더 낯설어질 수 있다

모델 학습에서는 사람이 작성한 정답만 사용되는 것이 아닙니다.

모델이 다양한 문제 해결 궤적을 생성하고, 테스트나 평가를 통해 성공한 결과만 선별해 다시 학습에 활용할 수 있습니다.

문제 제시
→ 여러 해결 과정 생성
→ 테스트 또는 채점
→ 성공한 궤적 선별
→ 학습 데이터로 활용

이 방식이 확대되면 모델은 사람이 일반적으로 선택하는 해법과 다른 방식으로 문제를 풀 가능성이 있습니다.

코드가 테스트를 통과하더라도 다음과 같은 문제가 생길 수 있습니다.

  • 구조가 지나치게 복잡합니다.
  • 의도를 설명하기 어렵습니다.
  • 유지보수성이 낮습니다.
  • 특정 테스트에만 맞춘 구현일 수 있습니다.
  • 운영 환경에서 예상하지 못한 문제가 발생할 수 있습니다.

인간이 이해하기 어려운 코드가 곧바로 더 좋은 코드를 의미하지는 않습니다.

제품 코드에는 정답 여부 외에도 다음 조건이 필요합니다.

  • 읽을 수 있어야 합니다.
  • 수정할 수 있어야 합니다.
  • 검증할 수 있어야 합니다.
  • 장애 원인을 추적할 수 있어야 합니다.
  • 다른 개발자가 이어서 관리할 수 있어야 합니다.

따라서 AI가 생성한 코드는 테스트 통과만으로 승인해서는 안 됩니다.


16. AI 코딩에서 아키텍처가 더 중요해지는 이유

AI는 새로운 프로그램을 빠르게 생성하는 데 강합니다.
그러나 실제 제품 개발의 대부분은 새로 만드는 일이 아니라 기존 코드를 지속적으로 변경하는 일입니다.

실제 환경에서는 다음과 같은 변경이 반복됩니다.

  • 정책 변경
  • 고객 요청
  • 장애 수정
  • 신규 기능 추가
  • 성능 개선
  • 보안 패치
  • 외부 API 변경
  • 데이터 구조 변경

코드 구조가 뒤엉켜 있으면 작은 수정 요청도 여러 모듈에 영향을 줍니다.
에이전트는 관련 파일을 넓게 수정하고, 예상하지 못한 회귀 오류를 만들 수 있습니다.

이 문제를 줄이는 가장 현실적인 방법은 변경 범위를 구조적으로 격리하는 것입니다.


16.1 폴더와 모듈을 변경 경계로 사용한다

예를 들어 의료 시스템을 다음과 같이 분리할 수 있습니다.

patient/
  registration/
  consultation/
  billing/

insurance/
  claim/
  settlement/

common/
  security/
  audit/

AI에게 전체 프로젝트를 맡기는 대신 수정 범위를 제한합니다.

billing/payment 모듈만 수정해 주세요.

제약 조건:
1. 다른 모듈의 구현 파일은 변경하지 않습니다.
2. 공개 인터페이스는 유지합니다.
3. 데이터베이스 스키마는 변경하지 않습니다.
4. 수정 후 billing 모듈 테스트를 실행합니다.
5. 변경된 파일 목록과 이유를 보고합니다.

폴더 경계만 만든다고 좋은 구조가 되는 것은 아닙니다.
실제 책임과 변경 이유를 기준으로 나눠야 합니다.

  • 같은 이유로 변경되는 코드는 함께 둡니다.
  • 서로 다른 이유로 변경되는 코드는 분리합니다.
  • 외부에 노출되는 인터페이스를 명확히 합니다.
  • 내부 구현은 모듈 밖에서 직접 접근하지 못하게 합니다.

16.2 파일 수보다 변경 영향 범위가 중요하다

파일을 많이 나누는 것이 목적은 아닙니다.

중요한 것은 특정 요구사항이 들어왔을 때 변경 범위가 제한되는 구조입니다.

나쁜 구조는 다음과 같습니다.

common/
  CommonService.java
  CommonUtil.java
  CommonManager.java

서로 다른 도메인의 로직이 공통 클래스에 몰려 있으면 작은 수정도 전체 시스템에 영향을 줍니다.

반면 책임이 분리된 구조에서는 결제 수정이 회원이나 배송 코드까지 번지는 일을 줄일 수 있습니다.

payment/
  application/
  domain/
  infrastructure/

member/
  application/
  domain/
  infrastructure/

AI 코딩 환경에서는 변경 범위가 명확할수록 다음 효과가 있습니다.

  • 모델에 전달할 컨텍스트가 줄어듭니다.
  • 수정 대상 파일이 제한됩니다.
  • 테스트 범위를 좁힐 수 있습니다.
  • 회귀 오류 가능성이 감소합니다.
  • 세션이 빠르게 오염되는 것을 방지합니다.

17. 멀티 에이전트가 아키텍처를 대신할 수는 없다

에이전트를 여러 개 실행하면 한 번의 결과 품질은 높아질 수 있습니다.

예를 들어 여러 에이전트가 각각 구현, 테스트, 보안 검토를 맡고 최종 결과를 취합하는 방식입니다.

그러나 구조가 좋지 않은 프로젝트에서 에이전트 수만 늘리면 다음 문제가 발생합니다.

  • 같은 파일을 여러 에이전트가 서로 다르게 수정합니다.
  • 동일한 문제를 반복해서 분석합니다.
  • 서로 다른 결론이 충돌합니다.
  • 결과 병합 비용이 증가합니다.
  • 토큰 사용량이 급격히 늘어납니다.
  • 장애가 발생했을 때 책임 경로를 찾기 어렵습니다.
나쁜 구조 + 많은 에이전트
≠ 안정적인 제품

명확한 모듈 경계 + 제한된 작업 범위
= 지속적으로 수정 가능한 제품

멀티 에이전트는 작업 경계가 명확할 때 효과적입니다.

적합한 예:

  • 서로 독립적인 문서 분석
  • 모듈별 테스트
  • 보안·성능·품질의 병렬 검토
  • 여러 대안의 독립적인 비교

부적합한 예:

  • 동일한 파일을 여러 에이전트가 동시에 수정
  • 목표와 완료 조건이 모호한 작업
  • 모듈 경계가 없는 프로젝트
  • 결과를 검증할 수 없는 작업

작업 분해가 불분명한 멀티 에이전트는 협업 시스템이라기보다 비용이 많이 드는 반복 실행 시스템이 되기 쉽습니다.


18. 실제 제품 개발에 적용할 원칙

AI 에이전트를 실제 개발 환경에 적용할 때는 다음 원칙이 중요합니다.

첫째, 결정적인 흐름은 코드에 둡니다

결제 승인, 권한 검증, 개인정보 처리처럼 반드시 같은 절차를 따라야 하는 영역은 워크플로우로 구현합니다.

모델에게 전체 흐름을 맡기지 않습니다.


둘째, 불확실한 판단만 모델에 맡깁니다

문서 검색, 장애 원인 후보 생성, 코드 위치 탐색처럼 유연한 판단이 필요한 부분에 모델을 사용합니다.

모델의 판단 결과는 코드와 정책으로 검증합니다.


셋째, 도구의 권한을 최소화합니다

모델이 사용할 수 있는 도구와 경로를 제한합니다.

허용:
- 프로젝트 디렉터리 읽기
- 특정 모듈 수정
- 테스트 실행

금지:
- 운영 데이터베이스 접근
- 운영 배포
- 프로젝트 외부 파일 수정
- 인증 정보 출력

넷째, 작업 범위를 명확하게 지정합니다

모델에 “전체적으로 개선해 주세요”라고 요청하기보다 수정 범위와 금지 사항을 지정합니다.

수정 대상:
- payment/application
- payment/domain

변경 금지:
- public API
- database schema
- authentication module

검증:
- payment 단위 테스트
- 전체 컴파일
- git diff 확인

다섯째, 구현과 검토의 컨텍스트를 분리합니다

구현한 모델에게 같은 컨텍스트로 자체 검토를 시키면 기존 판단을 그대로 정당화할 가능성이 있습니다.

검토 작업은 별도의 세션에서 수행하는 편이 낫습니다.

구현 세션
→ 코드 작성

독립 검토 세션
→ 코드와 테스트 결과만 전달
→ 보안·동시성·트랜잭션 검토

여섯째, 종료 조건을 검증 가능하게 정의합니다

“문제를 완벽하게 해결한다”는 목표는 종료할 수 없습니다.

다음처럼 측정 가능한 조건을 사용해야 합니다.

1. 전체 테스트가 통과한다.
2. 정적 분석에서 Critical 오류가 없다.
3. 변경 파일이 지정된 모듈 안에만 존재한다.
4. API 스펙이 변경되지 않았다.
5. 실패 시 롤백 절차가 존재한다.

일곱째, 긴 세션을 무리하게 유지하지 않습니다

대화가 길어졌다고 해서 그 세션을 계속 유지할 이유는 없습니다.

현재 유효한 결정을 요약한 뒤 새로운 세션으로 전환하는 것이 품질과 비용 면에서 나을 수 있습니다.


마무리

LLM 단일 호출은 간단한 생성 작업에는 효과적이지만, 복잡한 문제 해결과 실제 시스템 조작에는 한계가 있습니다.

에이전트는 모델이 도구를 사용하고 결과를 관찰하며 다음 행동을 결정할 수 있도록 확장한 구조입니다.
그러나 자율성을 높이는 것만으로 안정적인 시스템이 만들어지는 것은 아닙니다.

실제 제품에서는 다음 요소가 함께 필요합니다.

  • 명확한 워크플로우와 에이전트의 역할 분리
  • 세션, 턴, 이터레이션에 대한 상태 관리
  • 제한된 도구 권한
  • 비동기 작업 큐와 스케줄러
  • 입력과 출력에 대한 가드레일
  • 이벤트별 훅
  • 독립된 검토 세션
  • 변경 영향도를 제한하는 모듈 구조
  • 검증 가능한 목표와 종료 조건

AI는 코드를 빠르게 생성할 수 있습니다.
그러나 변경을 반복해도 무너지지 않는 제품을 만들려면 구조, 경계, 검증, 운영까지 함께 설계해야 합니다.

결국 중요한 것은 모델에게 얼마나 많은 일을 맡기느냐가 아닙니다.

모델에게 어떤 정보를 제공하고, 어디까지 제어권을 주며, 실패했을 때 시스템이 어떻게 안전하게 복구되도록 만들 것인지가 핵심입니다.

정리하며 느낀 점

이번 내용을 정리하면서 AI 에이전트는 단순히 LLM에 도구를 연결한 기능이 아니라, 모델에게 어느 범위까지 판단과 실행을 맡길 것인지 설계하는 시스템이라는 점을 다시 확인할 수 있었습니다.

처음에는 워크플로우와 에이전트의 차이를 단순히 자동화 수준의 차이로 생각했습니다.
그러나 실제 차이는 제어 흐름의 위치에 있었습니다. 실행 순서와 분기를 코드가 결정하면 워크플로우이고, 모델이 현재 상황을 판단해 도구와 다음 행동을 선택하면 에이전트입니다.
에이전트의 자율성이 높아질수록 유연성은 커지지만, 비용과 비결정성, 보안, 재현성 문제도 함께 커진다는 점이 중요했습니다.

세션, 턴, 이터레이션의 구분도 에이전트의 동작을 이해하는 데 도움이 되었습니다.
겉으로는 하나의 요청을 처리하는 것처럼 보이지만, 내부에서는 모델 판단과 도구 실행, 결과 확인이 여러 차례 반복됩니다.
긴 세션을 계속 유지한다고 항상 더 좋은 결과가 나오는 것도 아니었습니다.
이전 지시와 폐기된 설계가 컨텍스트에 남으면 오히려 판단을 흐릴 수 있으므로, 현재 유효한 정보만 정리하고 작업 단위에 따라 세션을 분리하는 전략이 필요합니다.

가장 인상적이었던 부분은 도구가 단순한 실행 기능이 아니라 모델에게 넘겨주는 제어권의 범위라는 점입니다.
파일 읽기나 터미널 실행뿐 아니라 새로운 요청 생성, 서브에이전트 호출, 목표 평가, 스케줄링까지 도구로 제공하면 모델이 작업 흐름 자체를 제어할 수 있습니다.
따라서 도구를 많이 제공하는 것이 무조건 좋은 설계는 아니며, 권한과 실패 영향도를 고려해 필요한 기능만 제한적으로 열어야 합니다.

또한 AI가 코드를 빠르게 생성한다고 해서 아키텍처의 중요성이 줄어드는 것은 아니었습니다.
오히려 수정 범위가 명확하지 않은 코드에서는 작은 요청 하나에도 여러 파일이 함께 변경되고, 반복 수정 과정에서 기존 기능이 쉽게 깨질 수 있습니다.
모듈 경계, 공개 인터페이스, 테스트 범위가 명확해야 AI에게도 제한된 컨텍스트와 작업 범위를 전달할 수 있습니다.

멀티 에이전트 역시 만능 해결책은 아니라고 생각합니다.
작업을 명확하게 분리할 수 있을 때는 효과적이지만, 모호한 목표를 여러 에이전트에게 맡기면 같은 분석과 수정을 반복하면서 토큰과 시간만 소비할 수 있습니다.
에이전트 수를 늘리기 전에 문제를 분해하고, 완료 조건과 검증 방법을 먼저 정의해야 합니다.

결국 AI 에이전트를 활용할 때 중요한 것은 모델이 얼마나 많은 일을 할 수 있는지가 아닙니다. 어떤 작업을 코드가 통제하고, 어떤 판단을 모델에 맡길지, 실패했을 때 어디까지 영향을 허용할지를 설계하는 것이 핵심입니다.
AI는 구현 속도를 높여 주지만, 안정적으로 변경하고 운영할 수 있는 제품을 만드는 책임까지 대신해 주지는 않습니다.

profile
레거시를 이해하면서도 새로운 기술을 현실적으로 적용할 수 있는 백엔드 개발자가 되는 것이 목표입니다.

0개의 댓글