본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 09. 13:00

AI 코딩 에이전트가 이미 수정한 버그를 계속 다시 만드는 이유와 해결책

요약

AI 코딩 에이전트가 과거의 수정 이력을 참조하여 동일한 실수를 반복하지 않도록 돕는 selvedge v0.3.7의 업데이트 내용을 다룹니다. 새로운 MCP 도구인 'prior_attempts'를 통해 에이전트가 변경 전 과거 시도 기록을 확인하고 계획을 수정할 수 있는 루프를 구현했습니다.

핵심 포인트

  • prior_attempts 도구 도입으로 에이전트의 과거 수정 이력 참조 가능
  • LLM 호출 없이 SQLite 기반의 결정론적 템플릿 방식으로 동작
  • 신뢰도가 높은 결과만 반환하여 에이전트의 잘못된 판단 방지
  • 엔티티 정규화를 통해 조회 도구의 정확도와 재현율 확보

지난 몇 번의 릴리스 동안 selvedge는 쓰기 전용(write-only) 도구였습니다. AI 에이전트가 컬럼을 수정하고 log_change를 호출하면, 그 추론(reasoning) 과정이 코드 옆의 .selvedge/ 디렉터리 내 작은 SQLite 파일에 기록됩니다. 유용하긴 하지만 단방향적이었죠. 에이전트는 저장소에 기록을 남긴 뒤 다시는 그것을 들여다보지 않았습니다.

v0.3.7은 그 흐름이 뒤바뀌는 릴리스입니다. prior_attempts라는 새로운 MCP 도구가 도입되었으며, 이 도구의 핵심 목적은 에이전트가 무언가를 변경한 '후'가 아니라, 변경하기 '전'에 이를 호출하도록 하는 것입니다. users.email을 수정하기 전에, 에이전트는 selvedge에게 묻습니다:

> prior_attempts(entity_path="users.email")

users.email에 대한 2개의 이전 시도:
...

이제 에이전트는 march-mason이 기록한 컨텍스트(context)를 갖게 되었습니다. 그리고 이번 릴리스가 가능하게 하려고 설계된 바로 그 일, 즉 계획을 변경(change plan)하는 일을 수행합니다. 에이전트는 컬럼의 이름을 바꾸지 않습니다. 대신 users.email을 유지하면서 앱 레이어(app layer)에서 검증기(validator)를 강화하고, 자신의 log_changeprior_attempts를 먼저 확인했다는 점을 기록합니다. 루프가 완성된 것입니다.

참고로, 이 과정 중 어디에서도 LLM 호출은 발생하지 않습니다. 출력은 템플릿화(templated)되어 있습니다. selvedge는 SQLite의 행(rows)으로부터 결정론적(deterministically)으로 이를 조립합니다. 이것은 코어(core)의 엄격한 규칙입니다. 모델 점프(model hops), API 키, 네트워크, 그리고 테스트하기 고통스러운 비결정성(non-determinism)은 허용되지 않습니다. prior_attempts는 쿼리(query)와 문자열 템플릿일 뿐, 그 이상도 이하도 아닙니다.

신뢰 예산(trust budget)이 타협 불가능한 이유

이것은 제가 가장 많이 고민했던 설계 결정 사항입니다. prior_attempts는 기본적으로 신뢰도가 높은 결과(high-confidence results)만 반환합니다.

v0.3.7에서

이는 v0.3.5의 selvedge verify가 보여준 2단계 종료 코드 (two-tier exit codes)와 동일한 원칙입니다. 즉, 먼저 엄격한 하한선 (strict floor)을 구축한 다음, 호출자가 원할 경우 이를 완화할 수 있는 조절 노브 (knob)를 제공하는 것입니다. 보수적인 기본값 (conservative defaults)이야말로 메커니즘의 정직함을 유지하는 핵심입니다. 정밀한 분류기 (classifier)는 나중에 도입됩니다. v0.3.11에서는 명시적인 rejectrevert 변경 유형 (change_types)을 제공하므로, API를 전혀 변경하지 않고도 신뢰도가 높은 단계 (high-confidence tier)의 정확도를 더욱 높일 수 있습니다.

동일한 릴리스에 반드시 포함되어야 했던 엔티티 기반 (entity foundation)

./src/auth.py::loginsrc/auth.py::login이 서로 다른 두 개의 엔티티 (entities)로 저장된다면, 조회 도구 (lookup tool)를 출시할 수 없습니다. 만약 그렇게 된다면, 재현율 (recall) 문제는 "prior_attempts가 아무것도 찾지 못했다"는 식으로 위장하게 되고, 에이전트 (agent)는 확신을 가지고 진행하며, 결과적으로 도구가 에이전트를 적극적으로 잘못 인도 (misled)하게 됩니다. 이는 도구가 아예 존재하지 않는 것보다 더 나쁜 상황입니다. 따라서 정규화 (canonicalization) 작업이 동일한 릴리스 내에 가장 먼저 반영되어야 했습니다.

쓰기 시 정규화 (canonicalization on write)는 결정론적 (deterministic)이며 의도적으로 지루하게 설계되었습니다. 앞부분의 ./를 제거하고, //를 하나로 합치며, 구분자를 /로 정규화하고, 공백을 제거합니다. MCP 쓰기 경로와 CLI가 모두 통과하는 단일 병목 지점 (chokepoint)을 설정하여, 이번 릴리스 이후로는 그 어떤 것도 비정규화된 경로 (non-canonical path)로 기록되지 않도록 합니다.

특별히 언급할 만한 단 하나의 결정 사항은 다음과 같습니다: 대소문자는 의도적으로 보존됩니다. 파일 시스템마다 방식이 다르기 때문입니다. macOS와 Windows는 기본적으로 대소문자를 구분하지 않지만 (case-insensitive), 대부분의 Linux 호스트는 대소문자를 구분합니다 (case-sensitive). 만약 selvedge가 모든 것을 조용히 소문자로 변환했다면, 대소문자를 구분하는 호스트에서 실제로 구별되는 두 엔티티를 하나로 합쳐버리는 문제가 발생했을 것입니다. 따라서 대소문자를 보존하며, selvedge doctor는 대소문자만 다른 형제 경로 (sibling paths)를 찾아내 경고를 표시하는 행 (row)을 추가하여, 사용자가 조용한 병합 (silent merge)을 겪는 대신 문제를 인지할 수 있도록 합니다.

기존 데이터베이스의 경우 selvedge migrate-paths가 있습니다. 이 명령은 기본적으로 --dry-run 모드로 동작하며, 실제로 무언가를 기록하려면 --apply를 전달해야 합니다. 드라이(dry run) 실행 시에는 정규화(canonicalization) 전의 경로 중 어떤 것들이 동일한 값으로 수렴하게 될지를 보여주는 충돌 보고서(collisions report)가 출력됩니다. 이를 통해 병합이 일어나기 전에 미리 확인할 수 있습니다. 이 작업은 멱등성(idempotent)을 가지며, 매 실행마다 새로운 path_migrations 테이블에 감사 행(audit row)을 추가하므로 작업 내역이 투명하게 유지됩니다.

그리고 매번 언급되는 명시적인 비목표(non-goal)는 다음과 같습니다: 코드 파서(code parser)도, AST(Abstract Syntax Tree)도 사용하지 않습니다. selvedge는 사용자의 소스 코드를 읽어서 엔티티(entity)를 추출하지 않습니다. 대신 에이전트가 알려주는 내용을 정규화하여 쿼리 가능한 상태로 저장합니다. AST 작업은 언어별로 특화되어야 하고, 수많은 의존성(dependencies)을 끌어들이며, 설치 시 의존성을 3개로 유지하는 '의존성 없는 코어(dependency-free-core)' 규칙에 어긋납니다. 이것은 제가 고수하고 있는 원칙입니다.

프롬프트 블록(prompt block)의 변경 사항

아무도 호출하지 않는 새로운 MCP 도구는 가치가 없으며, 에이전트는 쿼리 도구를 자발적으로 발견하지 못합니다. 따라서 플라이휠(flywheel)을 돌리는 방법은 에이전트의 지침 블록(instruction block)에 한 줄을 추가하는 것입니다. 이제 selvedge prompt는 다음과 같이 시작합니다:

엔티티를 수정하기 전에, 해당 엔티티에 대해 prior_attempts를 호출하십시오.

이것은 이제 log_change 가이드 아래의 각주가 아니라 첫 번째 지침입니다. 이것이 바로 쓰기 전용(write-only) 에이전트를 읽고 쓰는(read-then-write) 에이전트로 바꾸는 핵심입니다. 이것이 없다면, 이번 릴리스의 도구는 목록에만 존재할 뿐 결코 실행되지 않는 도구가 될 것입니다.

프롬프트 블록은 센티널 브래킷(sentinel-bracketed, v0.3.4부터 도입된 <!-- selvedge:start --> / <!-- selvedge:end --> 마커)으로 구분되어 있기 때문에 기존 사용자들은 깔끔하게 변경 사항을 적용할 수 있습니다. selvedge prompt --install CLAUDE.md를 실행하면 브래킷으로 둘러싸인 영역만 업데이트되며, 프롬프트 파일의 나머지 부분은 건드리지 않습니다.

이로써 MCP 인터페이스는 7개의 도구로 확장되었습니다. 이번 릴리스에서 추가된 도구는 정확히 하나입니다. 이름 변경(rename) 지원은 별도의 도구가 아닌 log_change의 파라미터로 통합되었고, aggregate-digest 헬퍼는 도구가 아닌 일반 라이브러리 함수로 배포되었습니다. 인터페이스를 작게 유지하는 것 자체가 하나의 규율입니다.

다음 단계

v0.3.7은 wedge(쐐기)를 읽을 수 있게 만듭니다. 다음 릴리스들은 여러분이 이미 작업 중인 곳에서 이를 보이게(visible) 만들 것입니다:

  • v0.3.9는 개발자용 CLI 인터페이스를 제공합니다: selvedge audit (이유를 기록하지 않고 코드를 변경한 PR을 표시), selvedge digest (엔티티별 최근 활동 요약), selvedge pr-comment (관련된 prior_attempts 히트 결과를 PR 리뷰에 직접 삽입).
  • v0.3.11은 명시적인 reject / revert change_types(변경 유형)를 배포하여 근접성 분류기(proximity classifier)가 정밀해지도록 합니다.
  • v0.4.0은 전체 시스템을 선택 사항인 HTTP + Postgres 레이어로 격상시켜, 팀들이 기기별 SQLite 문제로 씨름하는 것을 방지합니다. 여전히 셀프 호스팅(self-hosted) 방식이며, 데이터를 어디로도 전송하지 않습니다.

만약 '수정 전 읽기(read-before-you-edit)' 흐름을 시도해보고 싶다면:

pip install selvedge==0.3.7
selvedge setup        # claude code / cursor / copilot 감지

그 다음, 히스토리가 있는 리포지토리(repo)를 에이전트에게 지정하고 이전에 만졌던 무언가를 리팩터링(refactor)하도록 요청해 보세요. 에이전트가 prior_attempts를 호출하고 두 번 생각하는 모습을 지켜보십시오.

변경 로그(changelog): https://github.com/masondelan/selvedge/releases/tag/v0.3.7

(claude code가 작업 도중 prior_attempts를 호출하여 경로를 변경하는 60초 분량의 영상이 곧 공개될 예정입니다. 업로드되는 대로 이 포스트에 추가하겠습니다.)

이 프로젝트는 오픈 소스이며, MIT 라이선스이고, 완전히 사용자의 로컬 머신에서 실행됩니다. 근접성 분류기(proximity classifier)나 정규화 규칙(canonicalization rules)에 대해 궁금한 점이 있다면 댓글로 무엇이든 물어봐 주세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0