사람을 위해 만든 규율이, 기계의 가드레일로 재발견되다

헥사고날 아키텍처, MSA, 객체지향 설계, 테스트 커버리지 90%. 이 네 가지는 오랫동안 사람 개발자를 위한 규율이었다. 새로 합류한 동료가 코드를 빨리 이해하도록, 한 팀의 변경이 다른 팀을 깨뜨리지 않도록, 리팩터링할 용기를 갖도록.

그런데 AI 에이전트에게 코드베이스를 맡겨보면서 이상한 것을 발견했다. 이 오래된 규율들이 에이전트에게 더 절실하다. 사람은 규율이 없어도 눈치와 맥락으로 버틴다. 에이전트는 그 눈치가 없다. 대신 에이전트는 사람이 못 하는 것을 한다 — 규칙을 매번, 지치지 않고, 기계적으로 검증받는 것을 견딘다.

이 글은 하네스 엔지니어링 시리즈의 연장선에서, 아키텍처 규율이 어떻게 에이전트 하네스의 핵심 부품이 되는지를 실제 운영 중인 레포(settlement, lemuel-xr)의 경험으로 정리한 고찰이다.

1. 경계가 곧 프롬프트다 — 헥사고날의 재발견

헥사고날(ports & adapters)의 본질은 한 문장이다. 도메인은 바깥세상을 모른다. 도메인·애플리케이션 계층은 포트(인터페이스)만 바라보고, JPA·HTTP·메시징 같은 세부는 어댑터에 격리된다.

사람에게 이것은 “유지보수하기 좋은 구조”다. 에이전트에게 이것은 컨텍스트 예산의 문제다.

에이전트는 컨텍스트 윈도우라는 유한한 자원 안에서 일한다. 경계가 무너진 코드베이스에서 유스케이스 하나를 고치려면 JPA 엔티티, 트랜잭션 설정, HTTP 직렬화까지 한꺼번에 읽어야 한다. 반면 포트가 잘 서 있으면 에이전트에게 이렇게 말할 수 있다:

“너는 EvaluateGroundingUseCase를 구현한다. 의존해도 되는 것은 도메인 타입과 이 두 개의 out-port뿐이다. EmbeddingPort.embed(texts): List<FloatArray> — 출력은 입력과 인덱스 정렬, 실패 시 예외.”

이것은 아키텍처 문서가 아니라 그대로 서브에이전트 프롬프트에 붙는 계약이다. 포트 시그니처가 곧 태스크 명세가 되고, “이 인터페이스 밖을 건드리면 실패”라는 판정 기준이 된다. 헥사고날을 지킨 코드베이스에서 에이전트 태스크는 자연스럽게 작고, 독립적이고, 검증 가능해진다.

경계는 사람에게는 문서고, 에이전트에게는 프롬프트다.

2. ArchUnit — “리뷰어의 눈”을 컴파일러로 옮기다

경계를 세우는 것과 지키는 것은 다른 문제다. 사람 리뷰어는 피곤하면 application 패키지에 숨어든 @Entity 임포트를 놓친다. 에이전트도 마찬가지다 — 아니, 에이전트는 더 그럴듯한 이유를 붙여가며 경계를 넘는다. “편의상 여기서 바로 리포지토리를 호출했습니다.”

settlement 레포에서는 이 문제를 서비스마다 ArchUnit 테스트로 못 박았다. account, loan, investment, organization, operation — 각 서비스에 *ArchitectureTest가 있고, 룰은 세 가지로 수렴한다:

// 도메인은 Spring 을 모른다
noClasses().that().resideInAPackage("..domain..")
    .should().dependOnClassesThat().resideInAPackage("org.springframework..")

// 애플리케이션은 JPA 를 직접 만지지 않는다
noClasses().that().resideInAPackage("..application..")
    .should().dependOnClassesThat().resideInAPackage("jakarta.persistence..")

// 어댑터는 다른 도메인의 어댑터를 침범하지 않는다

중요한 것은 이것이 테스트로 존재한다는 점이다. 즉 gradle test가 곧 아키텍처 심판이다. 에이전트가 경계를 넘는 코드를 쓰면 사람이 지적하기 전에 빌드가 빨간불을 낸다. 아키텍처 위반이 “리뷰 의견”에서 “컴파일 에러급 사실”로 격하되는 순간, 에이전트와의 협업 비용이 극적으로 줄어든다. 에이전트는 지적을 반박하지 않는다. 빨간 빌드는 반박할 수 없기 때문이다.

객체지향 설계의 SOLID도 같은 방식으로 재해석된다. 단일 책임 원칙은 “파일 하나가 한 가지 이유로만 바뀐다”는 사람의 미덕이었지만, 에이전트에게는 편집 신뢰도다. 책임이 뭉친 800줄 파일에서 에이전트의 수정은 자주 엉뚱한 곳을 건드린다. 책임이 쪼개진 80줄 파일에서는 거의 실패하지 않는다. DIP(의존성 역전)는 포트 뒤에 세부를 숨겨 테스트 더블을 꽂을 자리를 만든다 — 이것이 뒤에서 말할 커버리지 하네스의 전제가 된다.

3. MSA 경계 = 에이전트 작업 단위

MSA의 고전적 효용은 배포 독립성과 팀 자율성이다. 에이전트 시대의 효용을 하나 더 발견했다: 서비스 경계가 에이전트 작업의 폭발 반경(blast radius)을 결정한다.

멀티에이전트 오케스트레이션에서 가장 무서운 것은 두 에이전트가 같은 파일을 동시에 만지는 것이다. 서비스 경계가 명확하면 “에이전트 A는 order-service만, B는 settlement 모듈만”이라는 분할이 자연스럽고, 충돌은 구조적으로 차단된다. 반대로 경계가 흐린 모놀리스에서는 워크트리 격리 같은 장치를 억지로 동원해야 한다.

다만 교조적인 마이크로 분리가 답은 아니라는 것도 배웠다. settlement는 의도적으로 모노-MSA 하이브리드다 — 정산 모듈을 별도 배포 단위로 찢는 대신 라이브러리 jar로 빌드해 주문 서비스에 번들한다. 경계는 코드 수준(모듈·패키지·ArchUnit)에서 엄격하게, 배포는 운영 부담이 감당되는 수준에서 실용적으로. 에이전트에게 필요한 것은 물리적 분리가 아니라 위반이 기계적으로 탐지되는 논리적 경계다. 경계가 살아 있는 한, 나중에 트래픽이 요구할 때 물리 분리는 따라온다.

분산 시스템의 고전 패턴들도 하네스 관점에서 다시 보인다. Transactional Outbox와 멱등 수신(idempotent consumer)을 삼중으로 겹친 구조 — outbox 이벤트 ID 유니크, 수신측 processed_events PK, 비즈니스 자연키 유니크 제약 — 는 원래 장애를 위한 설계다. 그런데 이것은 동시에 에이전트가 이벤트 발행 코드를 잘못 짜도 시스템이 스스로 막아내는 다층 가드레일이다. 한 층이 뚫려도 다음 층이 잡는다. 좋은 분산 설계와 좋은 에이전트 하네스는 같은 모양을 하고 있다.

4. 커버리지 90%의 재해석 — 지표가 아니라 바닥이다

“커버리지 90%”는 오래 논쟁적인 숫자였다. 숫자를 채우기 위한 무의미한 테스트, 게이밍되는 지표. 사람 팀에서 이 비판은 자주 옳다.

에이전트 하네스에서는 계산이 달라진다. 에이전트가 코드를 바꾸는 빈도는 사람의 수십 배다. 그 속도에서 안전을 담보하는 유일한 장치는 행동을 고정하는 촘촘한 테스트 그물이다. 커버리지 90%는 “품질이 높다”는 증명이 아니다. “에이전트가 무엇을 깨뜨렸는지 즉시 안다”는 관측 가능성의 바닥이다.

그래서 커버리지를 하네스로 쓰려면 숫자만으로는 안 되고, 세 가지가 함께 가야 한다:

첫째, TDD를 절차로 강제한다. 에이전트에게 “테스트를 짜라”고 하면 구현 후 통과하는 테스트를 역산해서 짠다 — 그 테스트는 아무것도 고정하지 않는다. 하네스는 순서를 강제해야 한다: 실패하는 테스트 작성 → 실패 확인(RED) → 최소 구현 → 통과 확인(GREEN) → 커밋. RED 증거를 리포트로 남기게 하면, “테스트가 실제로 무언가를 검증한다”는 사실 자체가 검증된다.

둘째, 테스트가 행동을 검증하는지 리뷰한다. mock 호출 여부만 확인하는 테스트, assert 없는 테스트는 커버리지를 올리지만 그물이 아니다. 태스크마다 독립 리뷰어(사람이든 별도 에이전트든)가 “이 테스트는 실제 반환값과 상태를 검증하는가”를 본다. 구현한 에이전트가 자기 테스트를 채점하게 두지 않는다.

셋째, 검증 계층을 나눈다. 유닛은 결정론적으로(가짜 어댑터, 네트워크 없음), 통합은 실제 문맥으로, 외부 의존이 필요한 검증은 태그로 분리해 기본 CI에서 제외하되 자동으로 비활성화되게 만든다(@EnabledIfEnvironmentVariable 류). “키가 없어서 스킵됐는데 초록으로 보이는” 침묵 통과는 커버리지 하네스의 적이다.

jacoco를 test 태스크에 finalizedBy로 물려 두면 커버리지 리포트는 선택이 아니라 부산물이 된다. 에이전트가 몇 번을 돌려도 지표는 따라온다.

5. 실전 검증 — 섀도우 게이트를 SDD로 구축한 24시간

이 고찰이 탁상공론이 아님을 보여주는 최근 사례 하나. lemuel-xr에 “생성된 묵상 텍스트의 각 문장이 실제 성경 본문에 근거하는지”를 판정하는 섀도우 게이트를 넣는 작업을, 태스크별 서브에이전트 방식(SDD)으로 진행했다.

  • 스펙에서 헥사고날 경계를 계약으로 못 박았다: “application은 domain + out-port만 의존, JPA·scripture 도메인 무접촉.” 이 문장이 그대로 6개 태스크의 프롬프트와 리뷰 기준이 됐다.
  • 태스크마다 구현 에이전트 → 독립 리뷰 에이전트의 2단 게이트. 리뷰어는 스펙 준수와 테스트 실질(행동 검증 여부)을 별도 판정했다.
  • 전 태스크 TDD, RED/GREEN 증거 필수. 유닛은 가짜 임베더로 결정론, 실제 임베딩 API 검증은 환경변수 게이트 뒤로.
  • 독립 스펙 채점자가 구현 전 스펙의 모호성 6건을 잡아냈고(예: 에러 상태 우선순위 미정의), 전부 문서로 명문화한 뒤 진행했다.
  • 그리고 흥미로운 결말 — 유닛이 전부 초록이었는데도 라이브 실행에서만 드러나는 버그 2건(직렬화 설정, 외부 API 모델명)이 잡혔다. 커버리지 그물은 필요조건이지 충분조건이 아니라는 것, 최종 검증은 반드시 실제 경로를 밟아야 한다는 것을 다시 확인했다.

결과: 통합 테스트 수백 개가 한 번도 깨지지 않은 채, 이단 텍스트를 실제로 걸러내는 게이트가 하루 만에 main에 머지됐다.

맺음 — 규율의 방향이 바뀌었다

정리하면 이렇다.

규율 사람 시대의 의미 에이전트 시대의 의미
헥사고날 유지보수성 포트 = 태스크 계약, 컨텍스트 절약
ArchUnit 리뷰 보조 아키텍처 위반의 기계적 차단
MSA 경계 배포·팀 독립성 에이전트 폭발 반경 통제
OO 설계 가독성·확장성 편집 신뢰도, 테스트 더블 자리
커버리지 90% 품질 지표(논쟁적) 변경 관측 가능성의 바닥

아키텍처 규율은 사라지지 않았다. 수요자가 바뀌었을 뿐이다. 사람은 규율을 어겨도 눈치로 수습하지만, 에이전트는 규율이 없으면 그럴듯한 쓰레기를 빠르게 쌓는다. 역으로 규율이 기계적으로 강제되는 코드베이스에서 에이전트는 사람보다 우직하게 그 규율을 지킨다.

하네스 엔지니어링의 절반은 프롬프트와 절차 설계다. 나머지 절반은 이 글의 주제 — 위반이 스스로 드러나는 코드베이스를 만드는 것이다. 좋은 아키텍처는 이제 에이전트를 위한 최고의 하네스다.


시리즈: Superpowers — 프롬프트 하네스 · 하네스 엔지니어링 툴박스 · 하네스 플라이휠