자율 AI 개발 루프로 Ada 컴파일러 만들기
최근 저는 Ada로 작성한 Ada 컴파일러 adac을 개발하고 있습니다.
현재 이 컴파일러는 Ada 2022의 아주 작은 부분집합만 지원합니다. 분리된 프런트엔드, 추상 구문 트리, 의미 분석, 자체 중간 표현, 네이티브 백엔드를 거쳐 최소한의 프로시저를 컴파일할 수 있습니다. 네이티브 백엔드는 현재 x86-64 Linux와 FreeBSD용 프로그램을 생성합니다.
이 설명은 기술적으로는 정확하지만, 제가 이 프로젝트에서 가장 흥미롭게 생각하는 부분은 빠져 있습니다.
저에게 adac은 또 하나의 Ada 컴파일러를 만들기 위한 시도만은 아닙니다. 동시에 자율적인 AI 보조 소프트웨어 개발에 관한 실험이기도 합니다.
핵심 질문은 다음과 같습니다.
저장소 자체가 아키텍처, 엔지니어링 규칙, 검증 요구 사항, 작업 선택 정책, 중단 조건을 정의할 때, AI 에이전트는 시스템 프로젝트에서 일관된 장기 진척을 만들어 낼 수 있을까요?
이는 거대한 프롬프트 하나로 AI에게 컴파일러를 생성하라고 요구하는 것과 다릅니다. 무언가 동작해 보일 때까지 생성된 코드를 별다른 검토 없이 받아들이는 방식과도 다릅니다.
목표는 엔지니어링 규율을 제거하는 것이 아닙니다. AI 에이전트가 그 규율 안에서 작업할 수 있도록 충분한 원칙을 저장소에 인코딩하는 것입니다.
운영 계약으로서의 저장소
대부분의 소프트웨어 저장소에는 소스 코드와 테스트, 그리고 때로는 로드맵이 들어 있습니다. adac에서는 저장소가 개발 작업이 어떤 방식으로 진행되어야 하는지도 정의합니다.
프로젝트 문서는 아키텍처 경계, 소유권 규칙, 실패 범주, 지원 대상, 검증 요구 사항, 마일스톤 완료 기준을 설명합니다.
AGENTS.md 파일은 자율 개발 루프를 정의합니다. 일반적인 구현 지시는 대략
다음과 같은 절차를 시작할 수 있습니다.
- 작업 트리, 최근 커밋, 로드맵, 관련 문서를 점검합니다.
- 알려진 기준 상태를 만들기 위해 저장소 전체 검사를 실행합니다.
- 아직 충족되지 않은 가장 이른 로드맵 선행 조건을 찾습니다.
- 그 선행 조건을 진전시키는 가장 작은 완전한 작업 항목을 선택합니다.
- 계약, 성공 기준, 영향받는 경계, 필요한 리팩터링을 정의합니다.
- 계약이 변경되는 경우 구현 전에 문서를 갱신합니다.
- 변경을 완전한 수직 슬라이스로 구현합니다.
- 긍정 및 부정 테스트를 추가합니다.
- 집중 테스트를 실행한 뒤 전체 검증 스위트를 실행합니다.
- 전체 diff를 검토하고 일관되며 검증을 통과한 변경만 커밋합니다.
- 로드맵을 다시 평가하고 다음 작업 항목으로 계속 진행합니다.
구현이 이미 승인된 경우 에이전트는 계획만 작성하고 멈추어서는 안 됩니다. 문서화된 중단 조건에 도달할 때까지 작업을 계속합니다.
중단 조건도 명시되어 있습니다. 예를 들어 기존 계약만으로는 아키텍처 결정을 안전하게 도출할 수 없거나, 파괴적 작업에 승인이 필요하거나, 사용자의 변경과 선택한 작업이 겹치거나, 현재 환경에서 결과를 검증할 수 없을 때는 작업을 중단해야 합니다.
이 점이 중요한 이유는 중단 규칙이 없는 자율 개발은 진정한 자율 엔지니어링이라고 보기 어렵기 때문입니다. 그것은 단지 감독되지 않은 수정에 가깝습니다.
“컴파일러를 생성하라”가 아닙니다
컴파일러는 얕은 구현을 쉽게 허용하지 않기 때문에 이 실험에 적합한 대상입니다.
파서가 일부 문법을 받아들이는 것만으로는 충분하지 않습니다. 하나의 언어 기능은 여러 구별되는 단계를 통과해야 할 수 있습니다.
source
-> lexer
-> parser
-> AST
-> semantic analysis
-> custom IR
-> backend
-> executable
기능이 파서에만 존재한다면 완성된 기능이라고 할 수 없습니다.
그 기능은 올바르게 표현되고, 검증되고, 낮은 수준으로 변환되고, 출력되고, 테스트되어야 합니다. 소스 프로그램이 잘못된 경우에는 적절한 진단과 함께 거부되어야 합니다.
내부 컴파일러 오류는 일반적인 소스 진단과 구별되어야 합니다. 지원하지 않는 대상은 오해를 부르는 출력을 만들기 전에 실패해야 합니다. 부분 빌드 산출물을 성공 결과로 공개해서도 안 됩니다.
이러한 요구 사항은 에이전트가 서로 연결되지 않은 코드 조각으로 진척의 환상을 만들어 내기 어렵게 합니다.
저장소는 수직 기능 슬라이스를 명시적으로 요구합니다. 구현된 각 기능은 필요한 모든 컴파일러 단계를 진단과 테스트까지 포함하여 통과해야 합니다.
이 규칙은 AI가 생성한 시스템 코드에서 흔히 나타나는 실패 형태를 막기 위한 것입니다. 즉, 그럴듯해 보이는 구성 요소는 많지만 실제로 신뢰할 수 있는 시스템을 이루지 못하는 상황을 방지합니다.
최근 작업: 기능보다 기반 시설
겉으로 보이는 Ada 부분집합은 의도적으로 작게 유지하고 있습니다.
현재 adac은 null과 return 같은 단순 문장을 포함하는 최소한의
프로시저를 컴파일할 수 있습니다. 실용적인 Ada 프로그램을 작성하기에는
전혀 충분하지 않으며, 이를 사용할 수 있는 범용 컴파일러라고 소개할
단계도 아닙니다.
최근 개발은 더 안전한 확장에 필요한 기반 시설을 마련하는 데 집중했습니다.
그 작업에는 다음이 포함됩니다.
- 명시적인 컴파일 컨텍스트
- 컨텍스트가 소유하는 진단 상태
- 구문 노드, 심볼, 의미 엔터티를 위한 안정적인 식별자
- 구문 객체에 연결된 소스 범위
- 컴파일러 단계 경계에서의 검증
- 제한된 소스 입력
- 제한된 AST 노드 생성
- 제한된 인터닝 심볼 증가
- 잘못되었거나 과도한 입력의 통제된 거부
- 결정론적 테스트
- 중단된 자율 작업을 복구하기 위한 규칙
이것들은 흥미로운 사용자 기능은 아닙니다. 인상적인 데모 프로그램을 만들어 주지도 않습니다.
하지만 이러한 작업은 나중에 비용이 커질 수 있는 문제를 다룹니다. 불명확한 소유권, 프로세스 전역 상태, 제한 없는 메모리 증가, 잘못된 내부 표현, 우발적인 부분 출력, 중단 후 안전하게 재개할 수 없는 개발 세션 등이 여기에 해당합니다.
자율적으로 개발되는 시스템에서는 이러한 기반이 문법 지원 범위를 빠르게 늘리는 것보다 더 중요할 수 있습니다.
AI 에이전트는 기능을 빠르게 추가할 수 있습니다. 더 어려운 문제는 각 추가 작업 뒤에 저장소가 다음 에이전트 실행이 점검하고, 이해하고, 검증하고, 계속해서 작업할 수 있는 상태로 남도록 보장하는 것입니다.
자원 제한도 정확성의 일부입니다
최근 개발 영역 중 하나는 명시적인 자원 제한이었습니다.
컴파일러는 이제 소스 문자 수, AST 노드 수, 서로 다른 인터닝 심볼 수에 대해 제한 정책을 가집니다. 이러한 제한은 숨겨진 전역 동작이 아니라 컴파일 컨텍스트에 속합니다.
이처럼 작은 컴파일러에 지나치게 이른 강화처럼 보일 수도 있지만, 여기에는 여러 목적이 있습니다.
첫째, 소스 코드는 신뢰할 수 없는 입력으로 다루어야 합니다. 잘못되었거나 적대적인 소스 파일이 내부 구조를 제한 없이 증가시킬 수 있어서는 안 됩니다.
둘째, 자원 고갈에는 정의된 실패 경로가 필요합니다. 메모리 할당기의 비정상 종료, 잘못된 컴파일러 상태, 부분적으로 공개된 출력 파일로 이어져서는 안 됩니다.
셋째, 명시적 제한은 동작을 테스트할 수 있게 만듭니다. 테스트에서는 의도적으로 작은 예산을 사용하고, 내부 저장소를 손상시키지 않은 채 정확한 경계에서 컴파일러가 실패하는지 검증할 수 있습니다.
마지막으로 자원 계약은 자율 에이전트에 더 명확한 틀을 제공합니다. 실패 가능성이 보일 때마다 모호한 방어 검사를 덧붙이는 대신, 정의된 소유권과 관찰 가능한 동작을 갖춘 문서화된 정책을 보존해야 합니다.
책임은 여전히 인간에게 있습니다
이를 자율 개발이라고 부른다고 해서 인간이 사라지는 것은 아닙니다.
저는 여전히 장기 방향을 정의합니다. 어떤 제품 목표가 중요한지 결정합니다. 아키텍처 선택을 검토하고, 저장소 계약을 조정하며, 필요할 때 작업을 중단하거나 방향을 바꿉니다.
이 실험은 AI가 어떤 소프트웨어가 존재해야 하는지를 독립적으로 결정할 수 있는지를 알아보는 것이 아닙니다.
이 실험은 인간이 모든 개별 수정을 직접 선택하고 감독하지 않아도 AI가 엔지니어링 루프의 의미 있는 부분을 수행할 수 있는지를 알아보는 것입니다.
유용한 자율 에이전트는 다음과 같은 일을 할 수 있어야 합니다.
- 오래된 대화 맥락에 의존하지 않고 현재 상태를 점검해야 합니다.
- 문서화된 선행 조건에서 작업을 선택해야 합니다.
- 리팩터링이 구조적으로 필요한 시점을 인식해야 합니다.
- 계약과 구현을 함께 갱신해야 합니다.
- 전체 결과를 검증해야 합니다.
- 일관된 변경만 커밋해야 합니다.
- 중단 후 작업을 복구할 수 있어야 합니다.
- 결정을 내릴 권한이 없을 때 중단해야 합니다.
이는 “AI가 스스로 소프트웨어를 만들 수 있다”는 주장보다 좁은 주장입니다. 하지만 그만큼 더 구체적이고 검증할 수 있습니다.
왜 Ada인가요?
Ada가 이 프로젝트에 흥미로운 언어인 데에는 여러 이유가 있습니다.
Ada는 명시적인 인터페이스, 강한 타입, 제약된 표현, 정확성에 대한 신중한 추론을 장려합니다. 이러한 특성은 컴파일러 구현에 유용하지만, AI가 생성한 변경을 제약하려 할 때도 유용합니다.
Ada는 또한 성숙한 언어 명세를 가지고 있으며, 그 공동체는 정확한 의미론, 이식성, 안전성, 장기 유지보수성을 중요하게 여기는 경향이 있습니다.
따라서 성공한 장난감 예제 뒤에 숨기 어렵습니다. adac이 유용한 컴파일러로 성장하려면 문법 인식 이상의 것이 필요합니다. 신뢰할 수 있는 의미 분석, 검증된 중간 표현, 올바른 런타임 동작, 정의된 ABI 경계, 여러 대상에 대한 규율 있는 지원이 필요합니다.
그런 점에서 Ada는 이 개발 실험에 유난히 잘 맞습니다.
장기 방향
장기 목표는 자체적인 대상 독립 중간 표현을 갖춘 셀프 호스팅, 크로스 플랫폼 Ada 컴파일러를 만드는 것입니다.
로드맵에는 다음이 포함됩니다.
- 더 넓은 Ada 2022 프런트엔드 지원
- 스칼라 타입, 표현식, 제어 흐름, 서브프로그램 호출
- 타입이 있는 중간 표현과 제어 흐름 중간 표현
- 신뢰할 수 있는 네이티브 백엔드
- 패키지와 분리 컴파일
- 복합 타입과 저장 모델
- 예외, 제네릭, 태그드 타입
- 단계적 셀프 호스팅
- WebAssembly를 포함한 추가 대상
- 선택적 LLVM 백엔드
- 스타일 검사기와 포매터 같은 도구
로드맵은 날짜가 아니라 역량을 기준으로 합니다. 마일스톤은 계약, 구현, 검증기, 진단, 테스트가 문서화된 종료 기준을 만족할 때만 완료됩니다.
이 구분은 중요합니다. 자율 개발은 많은 커밋을 만들거나 피상적인 기능 이름을 체크하는 데 최적화되어서는 안 됩니다. 검증된 역량을 만들어 내는 데 최적화되어야 합니다.
제가 실제로 검증하는 것
이 방법이 효과가 있다고 주장하기에는 아직 이릅니다.
컴파일러는 여전히 작고, 가장 어려운 언어 기능의 상당수는 앞으로 남아 있습니다. 이름 해석, 오버로드 해석, 패키지, 제네릭, 예외, 저장소 관리, 분리 컴파일, ABI 세부 사항, 셀프 호스팅은 아키텍처와 개발 과정 모두에 훨씬 큰 압력을 줄 것입니다.
흥미로운 실패는 명백한 코드 생성 오류의 형태로 나타나지 않을 수도 있습니다.
다음과 같은 모습으로 나타날 수 있습니다.
- 각각은 합리적으로 보이는 수많은 커밋을 거치며 발생하는 아키텍처 표류
- 더 이상 구현과 일치하지 않는 문서
- 계약이 아니라 예제만 검증하는 테스트
- 장기 유지보수성을 해치는 국소 수정
- 충분한 근거 없이 수행되는 과도한 리팩터링
- 큰 diff를 피하기 위해 필요한 리팩터링을 하지 않는 문제
- 중단해야 할 때도 계속되는 자율 루프
- 미완성 작업과 완료된 작업을 구별하지 못하는 에이전트
이것들이 바로 저장소에 정의된 과정이 드러내기를 바라는 문제들입니다.
따라서 adac은 동시에 두 개의 프로젝트라고 할 수 있습니다.
하나는 Ada 컴파일러입니다.
다른 하나는 얼마나 많은 엔지니어링 판단을 저장소 상태, 실행 가능한 검증, AI 에이전트를 위한 명시적 운영 규칙으로 표현할 수 있는지를 알아보려는 시도입니다.
컴파일러는 눈에 보이는 결과물이 될 것입니다.
하지만 개발 이력은 그보다 더 흥미로운 산출물이 될지도 모릅니다.
소스 코드는 GitHub에서 공개되어 있습니다.