AI 에이전트가 코드를 제대로 고치게 만드는 하네스 엔지니어링 경험

AI 에이전트로 리팩토링할 때 생기는 컨벤션 누락은 작업 절차를 담은 하네스와 컨텍스트를 분리한 검토 에이전트로 줄일 수 있었다. 폴더·상태 구조 설계부터 시행착오까지 정리한 경험기다.

개요

최근 커뮤니티에서 이런 글들이 보인다.

  1. 바이브 코딩으로 비개발자도 직접 서비스를 만들고 배포할 수 있는 시대가 되었다.
  2. AI로 MVP를 빠르게 구현하고, 시장에 내놓아 사용자의 반응을 확인하는 것은 합리적인 접근이라고 생각한다.
  3. 다만 그렇게 만든 서비스를 지속적으로 운영하고 확장하는 과정에서는 문제가 생긴다.
  4. 일관된 UI를 서비스에 맞게 다듬어야 하고, 기능이 늘어나면서 복잡해진 로직과 원인을 파악하기 어려운 오류도 해결해야 한다. 빠르게 만드는 것만큼, 이후에도 수정하고 확장할 수 있는 코드 기반을 갖추는 일이 중요해지는 것이다.
  5. 그래서 AI가 코드를 대신 작성하더라도, 코드의 구조를 이해하고 변경 방향을 판단하며 결과를 검증하는 개발자의 역할은 여전히 필요하다고 생각한다.
  6. 최근 AI로 빠르게 구축한 서비스의 코드를 리팩토링할 기회가 있었다. 이 글에서는 그 과정에서 어떤 기준을 세웠고, 반복적인 수정을 AI 에이전트에게 맡기면서 어떤 시행착오를 겪었는지 정리해보려고 한다.

배경

  1. 올해 3월 페이지랩스라는 스타트업에 프론트엔드 포지션으로 합류하고 처음 맡은 일은 기존 코드의 리팩토링이었다.
  2. 페이지랩스는 문항 제작부터 검토, 시험지 출력까지 교육 콘텐츠 제작에 필요한 업무를 지원하는 ERP 서비스를 운영하고 있다.
  3. 사내에서 사용하던 서비스를 SaaS 형식으로 확장하려는 시점이었고, 그 전에 앞으로 기능을 추가하고 수정할 수 있는 코드 기반을 만들어야 했다.
  4. 서비스의 규모는 이미 작지 않았다. 핵심 업무 기능이 여덟 개가 넘었고, 페이지도 수십 개에 달했다.
  5. AI를 적극적으로 활용해 빠르게 만든 서비스라서 그런지 코드를 읽어보니 파일마다 구현 방식이 달랐다.
  6. 하나의 파일에서 데이터를 가져오고, 입력값을 관리하고, 비즈니스 로직을 처리하고, 화면까지 그리는 경우가 많았다. 비슷한 버튼이나 모달도 페이지마다 따로 구현되어 있었다.
  7. 이러다보니 규칙 따위 없는 3000천 줄이 넘어가는 파일이 대다수여서 도저히 로직을 파악하기 힘들었다.
  8. 처음에는 어디서부터 손을 대야 할지 막막했다.

코드를 분석하며 반복되는 패턴 분석하기

  1. 파일 하나를 이해하려고 해도 그 안에서 다루는 일이 너무 많았다.
  2. 화면의 작은 부분을 수정하려다가 데이터 요청과 상태 변경 흐름까지 따라가야 했다.
  3. 그런데 코드를 계속 읽다 보니 반복되는 문제가 보였다.
  4. 화면과 API 호출이 한 파일에 섞여 있었고, 서버에서 가져온 데이터와 폼 입력값, 모달의 열림 여부가 모두 비슷한 방식으로 관리되고 있었다.
  5. 공통으로 쓸 수 있는 UI도 각 화면 안에 흩어져 있었다.
  6. 문제가 반복된다면 수정 또한 반복해서 처리할 수 있다고 생각했다.
  7. 모든 코드를 직접 고치는 것보다, 먼저 문제를 분류하고 기준을 정한 다음 반복적인 수정은 AI 에이전트에게 맡겨보기로 했다.

폴더 구조 정하기

  1. AI에게 수정을 맡기려면 먼저 내가 원하는 구조부터 정해야 했다. 즉, 컨벤션을 정해야 했다.
  2. 폴더 구조부터 정헀다. 우선 코드를 기능 단위로 나누고, 프로젝트 전반에서 공유할 부분을 분리하기로 했다.
  3. 처음부터 세부 계층을 많이 두기보다는 Features와 Shared로 시작했다.
  4. 한 번에 완전한 FSD(Feature Sliced Design) 아키텍처를 채택하지 않은 이유는, 분류가 세밀해질수록 어떤 파일을 어디에 둘지 애매해질 수 있다고 생각했기 때문이다.
  5. FSD는 어렵고 예외가 많이 필요해 매번 나의 의사결정이 필요한 구조라면 에이전트에게 반복 적용을 맡기기도 어려울 것 같았다.

상태 구조 정하기

  1. 상태 관리 기준을 정할 때 많은 시간을 썼다.
  2. 각 파일에 useState가 너무 많아서 의아했다. 실제로 어떤 값을 관리하고 있는지 확인하니, 상태의 개수보다 서로 다른 성격의 값을 한곳에서 다루는 것이 문제였다.
  3. 서버에 원본이 있는 데이터도 컴포넌트가 로컬 상태로 들고 있었고, 폼 입력값과 로딩 여부, 화면의 일시적인 상태도 같은 위치에 선언되어 있었다.
  4. 같은 데이터를 여러 화면에서 사용해도 각자 따로 관리하니, 한 화면에서 수정한 내용이 다른 화면에 반영되도록 만드는 코드까지 필요했다.
  5. 그래서 상태의 소유권과 수명을 기준으로 분류하여 적용할 라이브러리를 선택했다.
  6. 서버에 원본이 있는 데이터는 서버 상태로, 제출할 때까지 관리하는 입력값은 폼 상태로, 모달의 열림 여부처럼 컴포넌트와 함께 생기고 사라지는 값은 지역 상태로 두었다.
  7. 에이전트에게 이런식으로 명확한 기준을 정해줘야 “상태를 적절히 정리해줘”라고 단순히 말하는 것보다 더 확실히 작업할 수 있다고 생각했다.
  8. 서버 상태에는 TanStack Query를 사용하고, 쿼리 키는 팩토리 패턴을 따르도록 했다.
  9. ERP 서비스는 데이터 등록·수정·삭제가 빈번하기 때문에, 변경 사항을 관련 화면에 빠르게 반영하는 것이 중요하다.
  10. 쿼리 키를 사용하면 변경된 데이터와 관련된 캐시를 명확하게 식별하고, invalidateQueries로 해당 쿼리를 무효화해 화면에서 사용 중인 데이터를 다시 조회할 수 있다.
  11. 특히 쿼리 키를 도메인별로 계층화하면 목록과 상세 등 갱신 범위를 유연하게 지정할 수 있어, 불필요한 요청을 줄이면서 관련 화면의 데이터 일관성을 유지할 수 있다.
  12. 또한 팩토리 패턴으로 키 생성 로직을 한곳에 모아 중복과 오타를 줄이고, 조회와 무효화에 동일한 규칙을 적용해 유지보수성을 높였다.
  13. 폼 상태 관리는 React Hook Form으로 통일했다.
  14. ERP 서비스는 입력 폼이 많고 구조도 복잡한데, 화면마다 입력값과 에러를 관리하는 방식이 달라 유지보수 비용이 크다고 생각했다.
  15. 이를 해결하기 위해 입력 필드 등록, 유효성 검증, 에러 표시, 제출 처리에 공통된 흐름을 적용하고, 화면마다 반복되던 상태 관리 코드를 줄이고자 해당 라이브러리를 선택했다.
  16. 또한 필요한 상태만 구독하도록 구성할 수 있어, 복잡한 폼에서 불필요한 재렌더링을 줄이는 데도 유리하다고 판단했다.
  17. 직접 만든 전용 훅을 사용할 수도 있었지만, 별도의 사용법을 계속 설명해야 하는 부담을 고려해 널리 쓰이는 API를 선택했다.
  18. 에이전트가 잘 아는 기술을 사용하는 것이 효율적이라고 생각했다.

컨벤션 기준 문서화

  1. 이외에도 타입 매칭, API, 컴포넌트 등 다양한 컨벤션을 정의하고 문서화했다.
  2. 내가 페이지마다 수정 방향을 설명하지 않아도 에이전트가 문서를 참고해 반복 작업을 수행할 수 있기를 기대했다.
  3. 이때까지는 이런식으로 기준을 정하는 일이 가장 중요하고, 이제 에이전트에게 시키면 리팩토링이 끝날 줄 알았다.

시행착오 1) 컨벤션 누락 발생 → 장기 실행 에이전트를 위한 효과적인 하네스

https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents

  1. 작업 단위는 하나의 페이지로 잡았다. 서비스 전체를 한 번에 바꾸기에는 범위가 너무 넓었고, 페이지를 기준으로 하면 어떤 기능을 수정했는지 직접 확인하기도 수월하다고 생각했다.
  2. 첫 시도에서는 단순히 “컨벤션 문서들을 읽고 대상 페이지의 모든 파일에 적용하라”고 요청했다.
  3. 결과는 기대와 달랐다. 에이전트는 작업을 마쳤다고 보고했지만, 직접 코드를 열어보니 일부 컨벤션이 적용되지 않았다.
  4. 처음에는 프롬프트의 표현이 충분히 명확하지 않았다고 생각해서 “빠짐없이”, “반드시 전부” 같은 키워드를 붙여 다시 요청해봤다. 하지만 비슷한 누락이 계속됐다.
  5. 같은 누락이 반복되면서, 에이전트가 해야 할 일을 빠뜨리지 않게 하려면 어떻게 요청해야 할지 고민했다.
  6. 그때 나는 “내 요구사항을 전부 적용하라”는 말은 작업의 목표를 전달할 뿐, 여러 파일과 컨벤션을 어떤 순서로 확인하며 진행할지는 에이전트에게 맡겨두고 있었다.
  7. 그래서 작업을 시작하기 전에 확인할 항목을 나열하고, 하나씩 처리하면서 남은 항목을 확인하도록 에이전트에게 생각하도록 시키면 누락을 줄일 수 있겠다고 생각했다.
  8. 이를 위해 작업 시작 위치인 page.tsx에서 import를 따라 대상 파일 목록을 만들고, 파일별로 확인할 컨벤션을 매트릭스로 정리하라는 지침을 추가했다.
  9. 에이전트가 이 표를 기준으로 각 항목을 처리하고 체크하면서, 아직 처리하지 않은 항목으로 작업을 이어가도록 했다.
  10. 매트릭스를 적용한 뒤, 에이전트가 작업하는 과정과 실제 코드에서도 누락이 줄었는지 직접 확인했다.
  11. 에이전트는 작업 시작 전, 범위를 우선 파악한 뒤에 컨벤션 정합 매트릭스를 작성했다. 그리고 작업 시간 동안 체크 리스트를 스스로 처리하며 컨벤션을 적용해갔다.
  12. 결과를 확인해보니 이전보다 누락이 확연히 줄어들었다.
  13. 하지만 여전히 완료로 체크된 항목 중에도 컨벤션이 적용되지 않은 부분이 남아 있었다.
  14. 예를 들어 파일을 Features 구조에 맞춰 옮겼지만 내부 코드에는 컨벤션을 적용하지 않았는데, 해당 항목은 ‘정합’으로 표시되어 있었다.
  15. 매트릭스로 작업할 항목을 정해주는 것만으로는 충분하지 않았다.
  16. 에이전트가 항목을 체크했더라도, 실제 코드를 기준에 맞게 수정했는지는 다시 검증하는 단계가 필요했다.
  17. 그리고 그 검증은 “별도의 컨텍스트”가 필요하다고 생각했다.

시행착오2) 자체 검토에서도 남은 누락 → 검토 에이전트의 컨텍스트 분리

https://www.anthropic.com/engineering/building-effective-agents#workflow-evaluator-optimizer

  1. 어떻게 하면 에이전트가 일을 다 끝냈다는 착각에 빠지지 않도록 설계할지 고민했다.
  2. 메인 에이전트의 컨텍스트에는 코드를 수정한 과정과 함께 “이 파일은 적용 완료”라는 판단이 쌓여 있었다.
  3. 같은 대화에서 재검토를 요청하면, 앞서 작성한 완료 기록을 전제로 코드를 확인할 수도 있겠다고 생각했다.
  4. 그래서 이전 작업 과정과 완료 판단을 전달받지 않은 별도의 컨텍스트가 필요하다고 판단했다.
  5. 이를 위해 convention-auditor라는 검토용 에이전트를 만들었다.
  6. 메인 에이전트는 컨벤션을 적용하고, 검토 에이전트는 실제 코드에서 컨벤션 위반을 찾도록 역할을 나눴다.
  7. 적용할 컨벤션이 이미 문서로 정리되어 있었기 때문에, 검토자에게도 동일한 기준을 전달할 수 있었다.
  8. 여기서 가장 신경 쓴 부분은 검토 에이전트에게 어떤 정보를 넘길지였다.
  9. 검토에 필요한 맥락이 부족하면 파일의 역할이나 작업 범위를 잘못 이해할 수 있었다.
  10. 반대로 메인 에이전트의 판단을 전부 전달하면 컨텍스트를 분리한 목적이 흐려질 것 같았다.
  11. 그래서 작업 범위, 해당 라우트에서 사용하는 파일 목록과 각 파일의 역할, 적용 대상 컨벤션을 전달했다.
  12. 어떤 코드를 무슨 기준으로 확인해야 하는지는 알려주되, 메인 에이전트가 작성한 매트릭스는 전달하지 않았다.
  13. 매트릭스에는 이미 ‘완료’, ‘정합’이라는 판단이 들어 있었다. 이 표를 먼저 보여주면 검토 에이전트도 그 판단에 영향을 받을 수 있다고 생각했다.
  14. 검토자가 코드와 컨벤션을 직접 대조해 위반 목록을 만들게 하고, 그 결과를 받은 뒤 메인 에이전트의 작업 내역과 비교하도록 했다.
  15. 검토 에이전트가 위반 목록을 반환하면 메인 에이전트가 해당 내용을 확인하고 부족한 부분을 수정했다.
  16. 수정 결과는 다시 검토하도록 연결했다. 한 번의 완료 보고로 작업을 끝내던 흐름에, 별도 검토에서 나온 피드백을 반영하는 과정을 추가한 것이다.
  17. 다만 이 과정을 계속 반복하도록 두지는 않았다. 검토와 수정은 최대 3라운드로 제한하고, 그 이후에도 해결되지 않는 항목은 사용자에게 보고하도록 했다.
  18. 반복해도 해결되지 않는 경우에는 수정 횟수보다 판단 기준을 확인할 필요가 있다고 생각했다.
  19. 컨벤션이 모호해 두 에이전트의 해석이 갈릴 수도 있고, 해당 코드에 예외가 필요하거나 검토 에이전트가 정상적인 코드를 위반으로 판단했을 수도 있었다.
  20. 이런 상황에서 계속 자동으로 수정하게 하면 같은 부분을 되돌려 고치거나 불필요한 변경을 만들 수 있었다.
    • 예를 들면, Features와 Shared의 경계에서 그런 문제가 생길 수 있다. 여러 화면에서 재사용하는 컴포넌트이면서 특정 도메인의 타입과 로직에 의존하는 경우, 재사용 여부를 우선하면 Shared에 두게 되고, 도메인 의존성을 우선하면 Features에 두게 된다. 컨벤션에 두 기준의 우선순위가 없다면, 에이전트가 파일을 옮겨도 다른 기준에서는 계속 위반으로 볼 수 있었다. 이런 상황은 수정 횟수를 늘리기 전에 사람이 기준을 정해야 한다고 생각했다.

결과 및 정리

  1. 이번 경험에서는 리팩토링 기준을 문서화하고, 이를 AI 에이전트가 반복해서 적용하고 검토하는 절차까지 구축했다.
  2. 작업 범위 파악, 파일별 컨벤션 매트릭스 작성, 코드 수정, 별도 에이전트의 검토, 피드백 반영을 하나의 흐름으로 연결했다.
  3. 직접 검수한 범위에서는 컨벤션 누락이 눈에 띄게 줄었다. 초기에는 에이전트의 완료 보고를 받은 뒤에도 내가 누락된 항목을 찾아 다시 요청해야 했다.
  4. 체크리스트와 별도 검토. 단계를 도입한 이후에는 검토 에이전트가 발견한 위반을 메인 에이전트가 수정하도록 했고, 최종 검수에서는 대부분의 컨벤션이 적용된 것을 확인했다.
  5. 이 과정을 “스킬”로 묶어 여러 페이지에 반복 적용했다. 페이지가 바뀔 때마다 작업 순서와 검토 방식을 다시 설명할 필요 없이, 동일한 절차로 리팩토링을 진행할 수 있었다. 개별 페이지의 코드 개선과 함께, 이후 작업에도 재사용할 수 있는 리팩토링 절차를 남겼다.
  6. 이 경험을 통해 AI 에이전트의 작업 단위와 전달 정보를 정하고, 결과를 검토해 다시 수정하는 흐름을 설계하는 역량을 쌓을 수 있었다.
  7. 특히 누락이 발생했을 때 요청 문구를 수정하는 데서 그치지 않고, 작업 추적과 검토 구조를 바꾸어 관찰된 문제를 개선했다.

아쉬웠던 점

  1. 최종 결과는 만족스러웠지만, 컨벤션이 적용됐는지 매번 직접 코드를 확인해야 했다는 점은 아쉬웠다.
  2. 에이전트가 반복적인 수정을 수행하더라도, 내가 모든 파일을 다시 읽어야 한다면 페이지가 늘어날수록 검토에 드는 시간도 함께 늘어난다.
  3. 직접 검토하면서 누락이 줄었다는 것은 확인했지만, 어떤 컨벤션이 얼마나 자주 빠졌는지 수치로 남기지는 못했다.
  4. 그래서 작업 방식을 바꾼 효과를 정량적으로 비교하거나, 이후 스킬을 수정했을 때 이전보다 나아졌는지 같은 기준으로 평가하기는 어려웠다.
  5. 다음에는 컨벤션 중 기계적으로 판정할 수 있는 항목부터 자동으로 확인하고 싶다. 예를 들어 허용하지 않은 경로의 import가 있는지, 쿼리 키를 정해진 팩토리를 통해 생성하는지처럼 코드에서 확인할 수 있는 규칙은 린트나 정적 분석으로 검사하는 방식이다.
  6. 그렇게 해야 검토 단계를 추가한 만큼 효과가 있었는지, 어떤 규칙에서 여전히 누락이 발생하는지 더 구체적으로 알 수 있다.
  7. 이번에는 이런 평가 체계까지 만들지는 못했다. 우선 직접 검수하며 결과를 확인했지만, 앞으로 같은 방식을 더 넓게 사용하려면 반복적인 확인 작업도 자동화할 필요가 있다고 느꼈다.

댓글