AI 코딩 에이전트는 deprecated SDK 호출을 새 API로 바꾸고, import를 정리하고, 테스트를 고치는 일을 빠르게 해냅니다. 이 속도는 매력적이지만, 수백 파일 변경이 “컴파일된다”는 사실만으로 안전해지지는 않습니다. fetchUser(id)가 client.users.get({ id })로 바뀌었더라도 timeout 기본값, retry 조건, null 처리, 권한 scope, pagination 동작이 바뀌면 시스템의 의미는 달라집니다.

그래서 최근의 대규모 API 전환은 AI에게 전역 치환을 시키는 문제보다 구조화된 변환(codemod)과 행동 보존 증거(semantic diff)를 운영하는 문제에 가깝습니다. AI 생성 큰 변경을 Reviewable Stack으로 나누는 방법, OpenAPI overlays로 API 설명을 통제하는 방법, Test Evidence Pipeline, 정책 기반 점진 배포가 만나는 지점이기도 합니다. 빠르게 바꾼 코드를 사람이 이해 가능한 단위로 검증하고, 예상한 행동만 배포하는 것이 핵심입니다.

이 글에서 얻는 것

  • AI 에이전트의 텍스트 수정, AST codemod, 사람이 판단해야 하는 의미 변경의 경계를 구분합니다.
  • API migration에서 보존해야 할 동작을 contract와 fixture로 적는 방법을 배웁니다.
  • compile·unit test·semantic diff·canary를 어떤 순서로 놓아야 하는지 판단합니다.
  • 일괄 변환을 멈추고 수동 검토로 전환해야 할 숫자 기준을 가져갑니다.

핵심 개념/이슈

1) 대량 수정의 단위는 파일이 아니라 변환 규칙이다

예를 들어 HTTP client 라이브러리를 교체한다고 해 봅시다. 단순한 호출은 아래처럼 기계적으로 바뀔 수 있습니다.

// before
const result = await legacy.get(`/orders/${id}`, { timeout: 800 });

// after
const result = await client.get({ path: `/orders/${id}`, timeoutMs: 800 });

하지만 실전 코드에는 dynamic URL, wrapper 함수, callback 기반 error handler, 테스트 mock, middleware, 타입 우회가 섞여 있습니다. 문자열을 찾고 바꾸는 방식은 주석과 유사한 identifier까지 건드리거나, import alias 때문에 일부 호출을 놓칩니다. AI 에이전트가 문맥을 읽어 보완할 수는 있지만, 매 파일마다 다른 추론을 하면 변환 규칙이 재현되지 않습니다.

codemod는 AST(추상 구문 트리)에서 “특정 symbol의 특정 call signature”만 찾고, 정해진 출력 형태로 바꾸는 프로그램입니다. 좋은 codemod의 산출물은 patch만이 아닙니다.

산출물답하는 질문예시
matcher무엇을 바꿨는가legacy.get의 named import와 alias 호출만 선택
transform어떤 형태로 바꿨는가option key와 response unwrap 규칙
fixture정상·경계 입력에서 결과가 맞는가generic, nested call, wrapper, no-op 사례
report무엇을 바꾸지 못했는가dynamic property access, parse error, custom wrapper
policy누가 예외를 판단하는가auth, billing, migration directory는 자동 수정 금지

AI 에이전트는 이 체계에서 codemod의 초안을 만들고, 불확실한 사례를 분류하고, migration report를 설명하는 데 유용합니다. 하지만 “에이전트가 96%를 고쳤다”는 결과가 96%의 안전성을 뜻하지는 않습니다. 분모가 호출 수인지 서비스 수인지, high-risk 경로가 몇 개인지, 남은 4%가 어떤 의미인지가 더 중요합니다.

2) semantic diff는 출력이 같다는 주장에 조건을 붙인다

AST diff는 구조가 바뀌었다는 것을 알려 주고, compiler는 타입과 문법이 맞는지 알려 줍니다. 둘 다 런타임의 의미를 완전히 보장하지 않습니다. HTTP client 전환이라면 최소한 request method, path/query encoding, header precedence, timeout/retry, response parsing, error mapping을 비교해야 합니다. 데이터 접근 계층이라면 transaction 경계, 정렬, null semantics, pagination, N+1 query 수가 대상입니다.

semantic diff는 같은 fixture 입력을 old/new 경로에 흘려 보내고, 의도적으로 같아야 하는 관찰값과 달라도 되는 관찰값을 명시하는 검사입니다. 예를 들어 새 client가 user-agent header를 바꾸는 것은 허용할 수 있지만, Idempotency-Key 누락이나 429 재시도 횟수 증가는 허용하지 않을 수 있습니다.

migration_contract:
  scope: orders-read-client-v2
  invariants:
    - method_and_canonical_path_equal
    - auth_scope_equal
    - status_and_error_class_equal
    - p95_latency_regression_under_10_percent
  allowed_differences:
    - response_header.server
    - trace_id
  stop_conditions:
    - idempotency_key_missing: 1
    - auth_denial_rate_over: 0.1_percent
    - unknown_behavior_cases_over: 2_percent

이렇게 적으면 리뷰어의 질문이 “AI가 잘 바꿨나?”에서 “이 불변 조건이 서비스의 실제 위험을 충분히 덮나?”로 바뀝니다. 변경된 API description도 OpenAPI overlays와 함께 관리하면, 코드만 전환되고 SDK 문서나 generated client가 남는 drift를 줄일 수 있습니다.

3) 실패를 3종류로 나누지 않으면 자동화가 위험해진다

대규모 migration에서 실패를 전부 “codemod failure”로 합치면 대응이 늦습니다. 적어도 다음처럼 나눠야 합니다.

  1. 변환 실패: parser가 읽지 못했거나 matcher가 대상 호출을 확신하지 못했다. 이 경우는 no-op으로 남기고 수동 큐로 보냅니다.
  2. 정적 검증 실패: type error, lint, import cycle, build failure가 발생했다. transform 규칙 또는 repository convention을 수정해야 합니다.
  3. 행동 검증 실패: fixture나 shadow traffic에서 response, error, 권한, 비용, latency가 달라졌다. 이 경우는 단순 patch가 아니라 API contract를 다시 판단해야 합니다.

특히 3번을 AI가 “더 그럴듯한 코드”로 덮지 않게 해야 합니다. old client의 retry가 우연히 외부 결제 중복을 막고 있었을 수 있고, 새 client의 default redirect 정책이 SSRF 방어를 약화시킬 수 있습니다. 이런 문제는 API 변경의 rollback budget과 Agentic PR Governance처럼 변경 승인 기준으로 올려야 합니다.

실무 적용

1) migrate → verify → roll out을 분리한 5단계 운영

처음부터 전체 repository에 agent를 실행하지 말고, 다음 다섯 단계를 분리하세요.

단계주요 작업다음 단계 통과 조건
0. 인벤토리symbol 사용처, wrapper, high-risk directory, owner를 수집호출의 95% 이상이 owner·risk와 연결됨
1. codemodAST 변환과 no-op report 생성parse/transform 실패가 2% 미만, 금지 경로 0건 수정
2. 정적 검증typecheck, build, focused test 수행신규 build/type 오류 0건
3. 행동 검증fixture, contract test, shadow/sample 비교invariant 실패 0건, unknown case가 기준 이하
4. 제한 rollout1~5% traffic 또는 서비스 한 곳에 적용error·latency·비용 regression이 stop condition 미만

수치는 팀의 위험에 맞게 조정해야 하지만, 시작점으로 public write API는 unknown case 0%, internal read API는 2% 미만을 둘 수 있습니다. 결제, 권한, 개인정보, database migration은 사용처 비율이 아니라 owner의 명시 승인과 별도 integration test를 통과해야 합니다. 98% 자동 변환 성공이라는 숫자가 high-risk 2%를 상쇄하지는 않습니다.

2) 에이전트에게 주는 작업 계약을 좁힌다

AI 에이전트는 “SDK v1을 v2로 모두 바꿔라”보다 아래처럼 검증 가능한 작업에서 더 안정적입니다.

목표: packages/api-client의 legacy.get(symbol import) 호출만 client.get으로 변환한다.
수정 금지: auth/, billing/, migrations/, generated/.
불변 조건: method, canonical URL, timeout, auth header, idempotency key.
허용 차이: trace header 이름, import 정렬.
검증: codemod fixture → pnpm typecheck → OrdersClientContractTest.
산출물: patch, no-op 목록, 애매한 wrapper 목록, 실행한 명령의 결과 요약.

이 contract는 agent를 통제하기 위한 문서일 뿐 아니라 리뷰어와 CI가 공유할 인터페이스입니다. Reviewable Stack처럼 첫 PR에는 fixture와 observability만, 다음 PR에는 low-risk transform, 마지막 PR에는 traffic 전환을 넣으면 실패 원인을 좁히기 쉽습니다. agent가 생성한 helper나 fallback은 “자동 생성”이라는 이유로 예외 취급하지 말고, 일반 production code와 같은 ownership·test 기준을 적용하세요.

3) semantic diff를 서비스 비용까지 확장한다

기능 응답이 같아도 migration이 더 많은 네트워크 호출, 로그, cache miss를 만들 수 있습니다. 그래서 canary에서는 오류율 외에 다음을 비교합니다.

  • endpoint별 request 수와 외부 API 비용: 기존 대비 10% 이상 증가는 원인 분석 전 확대 중지
  • p95/p99 latency: read API p95가 기준 대비 10%, write API p99가 **5%**를 넘으면 rollback 후보
  • retry·timeout·fallback 비율: 절대값 0.1%p 상승도 외부 dependency에서는 조사
  • authorization denial과 validation error: 새 client 도입 뒤 0.05%p 이상 상승하면 header/scope mapping 확인
  • codemod exception 비율: owner가 없는 예외가 2% 이상이면 규칙을 넓히지 말고 인벤토리부터 보강

이 지표는 하나의 배포 dashboard에서 old/new path와 version label로 비교해야 합니다. label cardinality가 커지지 않도록 individual file path나 raw user ID 대신 client version, endpoint group, error class를 사용하세요. Test Evidence Pipeline의 원칙처럼 “테스트를 했다”보다 어떤 contract가 어떤 환경에서 통과했고, 어떤 예외가 남았는지를 연결해 보존하는 편이 재시도 비용을 낮춥니다.

트레이드오프/주의점

첫째, codemod가 모든 refactor의 해답은 아닙니다. 도메인 경계가 바뀌거나 비동기 제어 흐름, 권한 모델, 데이터 model이 달라지는 전환은 AST 변환보다 설계·migration·rollout 문제입니다. 이때 대규모 자동 patch는 속도보다 숨은 결합을 증폭시킬 수 있습니다. codemod가 적용 가능한 범위를 좁게 잡는 것이 실패가 아니라 안전장치입니다.

둘째, semantic diff는 old path가 정답이라고 가정합니다. 기존 구현에 버그가 있고 새 API가 이를 고치는 전환이라면 출력이 다른 것이 올바를 수 있습니다. 그래서 allowed difference에는 제품·보안 담당자가 승인한 의도적 변경만 넣고, 비교 결과를 무조건 old path에 맞추지 마세요. regression과 개선을 구분하는 decision record가 필요합니다.

셋째, shadow traffic은 개인정보와 side effect를 갖습니다. write 요청을 새 경로에 그대로 재전송하면 중복 결제나 메일 발송이 생길 수 있습니다. read-only endpoint부터 시작하고, write는 dry-run adapter, sandbox tenant, idempotency key, 명시적 suppression을 갖춘 경우에만 제한적으로 비교해야 합니다. 응답 body를 저장할 때도 PII 마스킹과 보존 기간을 먼저 정합니다.

넷째, agent가 만든 큰 diff를 “기계적 변경”이라고 선언해 리뷰를 생략하면 안 됩니다. generated code와 generated patch는 다릅니다. 후자는 repository의 관례와 실제 production path를 건드립니다. diff size가 작아도 auth header 하나나 retry 기본값 하나가 위험할 수 있으므로, 리뷰 샘플은 파일 수가 아니라 위험 endpoint와 변환 규칙을 기준으로 뽑아야 합니다.

체크리스트 또는 연습

대규모 API migration 전 체크리스트

  • legacy symbol의 사용처, wrapper, generated code, high-risk directory, owner를 인벤토리로 만들었다.
  • codemod matcher·transform·fixture·no-op report를 version control에 함께 넣었다.
  • 불변 조건과 의도적 차이를 API contract로 명시했고, auth·timeout·retry·idempotency를 포함했다.
  • compile 성공과 runtime behavior 보존을 별도 gate로 운영한다.
  • exception·unknown case의 상한과 수동 검토 담당자를 정했다.
  • canary의 error, latency, dependency cost, fallback, authorization 지표와 rollback 조건을 합의했다.
  • write shadowing은 idempotency·suppression·PII 처리 규칙 없이는 실행하지 않는다.

연습: 한 API 호출을 안전한 변환으로 만들기

팀에서 곧 폐기할 SDK 호출 하나를 고르세요. 먼저 “어떤 AST 형태만 자동 변환할지”와 “어떤 wrapper는 no-op으로 남길지”를 세 문장으로 적습니다. 다음으로 정상 응답, 401, 429, timeout, empty result fixture 다섯 개를 old/new path에 통과시키고 비교할 필드와 허용 차이를 정하세요. 마지막으로 서비스 한 곳의 1% traffic에서 볼 지표와 멈춤 조건을 적으면, AI 에이전트에게 줄 작업은 자연스럽게 작고 검증 가능한 단위가 됩니다.

AI 시대의 migration 경쟁력은 더 많은 파일을 한 번에 바꾸는 데 있지 않습니다. 변환 규칙, 남은 불확실성, 행동 증거, 되돌림 조건을 함께 남겨 다음 변경도 더 싸게 검증할 수 있게 만드는 데 있습니다.