
AI를 병렬로 실행하는 도구를 만들었더니, AI가 거짓말을 하지 않게 된 이야기
요약
AI 에이전트의 작업 관리 시 발생하는 거짓말과 충돌 문제를 해결하기 위해 개발된 Lattice 도구를 소개합니다. Lattice는 코드 구조를 분석하여 작업 간 영향 범위를 예측하고, 겹치는 경우 리팩터링을 통해 작업을 분리하여 병렬 실행을 가능하게 합니다.
핵심 포인트
- AI가 스스로 작성하는 Markdown 공정표는 실제 작업 상태와 불일치할 위험이 있음
- Lattice는 작업 설명과 코드 구조를 대조하여 영향 범위를 사전에 예측함
- 작업 범위가 겹칠 경우 리팩터링을 통해 코드를 먼저 분리하여 충돌을 방지함
- Git worktree를 활용한 일회용 작업 복사본 기반의 안전한 병렬 실행 구현
끝났다고 말하지만, 아무것도 끝나 있지 않다
AI에게 개발을 시킬 때, 공정표는 Markdown으로 작성했다. 할 일을 체크박스로 나열하고, 완료되면 체크하도록 했다. 흔히 볼 수 있는 형태라고 생각한다.
이것이 제대로 작동하지 않는다.
"끝났습니다"라는 말을 듣고 확인하러 가면, 체크박스가 절반도 채워져 있지 않다. 하겠다고 했던 기능이 들어있지 않다. 작업 도중에 다른 과제가 나오면 새로운 Markdown 파일이 늘어난다. 시간이 지나면 공정표인지 메모인지 알 수 없는 파일들이 흩어져서, 지금 어디까지 진행되었는지 알 수 없게 된다. 무언가는 진행되고 있지만, 진척 상황을 물어봐도 요점을 파악하기 어렵다.
AI가 쓸 수 있는 것은, AI를 구속하지 않는다
이유는 명확하다.
Markdown 공정표는 AI 스스로가 쓰고, AI 스스로가 읽는다. 다시 쓸 수 있는 것은, 쓰는 본인에 대한 제약이 되지 않는다. 공정의 실체는 AI의 기억 속에 있고, Markdown은 그 복사본일 뿐이다. 대화가 길어져 기억이 압축되면, 실체가 먼저 사라지고 복사본만 남는다. 남은 복사본을 보고 "여기까지 끝났습니다"라고 말한다. 거짓말을 하고 있다는 자각도 없다. 그 시점에서 "채워져 있지 않다"라는 정보가 본인 안에 없기 때문이다.
Markdown이 늘어나는 것도 같은 맥락에서 온다.
작업 중에 다른 과제가 나왔을 때, 기존 공정에 포함시키려면 의존 관계(Dependency)를 다시 측정해야 한다. 새로운 파일을 만드는 것은 순식간이다. 그래서 더 저렴한 쪽이 선택된다.
내가 만들고 싶었던 것은, 병렬이었다
내가 하고 싶었던 것은 AI를 여러 개체 동시에 실행하는 것이었다.
Claude Code든 Codex든, 한 개체에게 순차적으로 시키는 한, 대기 시간은 작업 수만큼 쌓인다. 동시에 실행하면 빨리 끝난다. 같은 코드에 여러 개체가 동시에 손을 대는 이상, 작업 분할 방식을 잘못 선택하면 두 번째와 세 번째 개체가 같은 파일을 두고 다투게 되어, 한쪽의 변경 사항이 다른 쪽에 덮어씌워진다.
충돌을 피하는 방법 자체는 알고 있다. 건드리는 위치가 겹치지 않도록 작업을 나누면 된다. 문제는 그 나누는 방식을 인간의 직감에 의존하고 있었다는 점이다. 작업 설명을 읽고 "이것과 이것은 다른 부분을 건드릴 테니 동시에 진행할 수 있겠지"라고 판단한다. 판단이 틀리면, 실행한 후에 망가진다.
영향 범위를 측정하고, 겹친다면 분리한 뒤에 배포한다
그래서 만든 것이 Lattice라는 도구다. 동작 방식은 다음과 같다.
- 앞으로 할 작업의 설명과 코드 구조를 대조하여, 각각의 작업이 코드의 어느 부분을 건드리는지 예측한다.
- 예측한 범위가 겹치지 않는 작업을 동시에 실행해도 좋은 조합으로 추출한다.
- 겹치는 경우, 리팩터링(Refactoring)으로 분리할 수 있다면 먼저 분리한다. 동작이 변하지 않았음을 확인한 뒤, 다시 한번 범위를 재측정한다.
- 재측정된 결과로 공정표 자체를 다시 만든다.
3번이 핵심이다. 작업 관리 도구는 보통 코드 구조를 움직일 수 없다는 전제하에 작업을 나열한다. 겹치면 순차적으로 진행하게 된다. Lattice는 코드 쪽을 움직인다. 같은 곳을 두고 다툰다면, 다투지 않아도 되는 형태로 먼저 바꿔버린다.
코드를 변경하는 작업은 일회용 작업 복사본(git의 worktree) 내에서 수행한다. 원래 브랜치에는 손대지 않는다. 검증을 통과하면 수용하고, 통과하지 못하면 버린다. 코드가 바뀌면 그 이전에 만든 공정표와 그 이전에 AI에게 전달했던 설명은 무효화하고, 새로운 버전으로 다시 만든다. 오래된 전제를 바탕으로 실행 중인 AI가 남아 있으면 거기서부터 망가지기 때문이다.

Lattice가 구성한 의존 공정도의 일부. 카드는 공정, 가는 선은 의존 관계, 굵은 선은 최장 의존 사슬(Longest Dependency Chain)이다.
이 도표는 손으로 그린 것이 아니다. AI와 상담하여 공정을 등록하는 시점에 의존 관계로부터 자동으로 구축된다.
공정을 AI가 다시 쓸 수 없는 곳으로 옮겼다
범위를 측정하려면 공정이 기계가 읽을 수 있는 형태여야 한다. Markdown 문장으로는 측정할 수 없다. 그래서 Lattice는 공정을 전용 보관 장소에 두기로 했다.
공정이 보관 장소에 올라감으로써, 쓰기에 조건을 붙일 수 있게 되었다.
완료에는 증거가 필요하다. 작업을 done 상태로 만들려면, 성과물을 가리키는 기술 파일과 Git 오브젝트를 첨부해야 한다. 기록할 때 실물이 있는지 확인하며, 없으면 통과되지 않는다.
누가 썼는지가 필요하다. 상태를 변경하는 조작은 터미널, 세션, 에이전트라는 세 가지 식별자가 환경 변수에 갖춰져 있어야 한다. 하나라도 누락되면 ACTOR_UNRESOLVED로 거부되며, 보관 장소는 단 1바이트도 변하지 않는다.
부분적인 수정이 불가능하다. 공정 (Process)의 구성을 바꾸려면, 변경 후의 전체를 만들어 한 번의 거래 (Transaction)로서 다시 내보내야 한다. 불리한 항목 하나를 덤으로 지운다는 식의 조작은 구문 (Syntax)으로서 존재하지 않는다. 이전에 있었던 작업이 증거 없이 사라져 있으면, 그 지점에서 거부된다.
구분 (Delimiter)은 승인 없이 닫히지 않는다. 매달려 있는 작업이 전부 끝나더라도, 구분은 gate_ready라는 상태로 멈춘다. 거기서 더 나아가려면, 확인했다는 기록과 증거를 남길 필요가 있다.
이 모든 것을 AI에게 "조심해"라고 말하지 않았다. 지키지 않으면 쓰기가 통과되지 않는 곳으로 공정을 옮겼다.
시험 삼아, 식별자 (Identifier)를 제외한 상태로 완료를 기록해 본다.
lattice todo done --plan phase-control-live-gantt --task 020 --evidence .lattice/evidence.json
돌아오는 것은 이것이며, 공정은 완료되지 않는다.
{
"schema": "lattice.cli_error.v2",
"code": "ACTOR_UNRESOLVED",
...
실제로는 한 줄로 반환된다. 무엇이 부족한지, 그리고 다음에 무엇을 해야 하는지가 기계가 읽을 수 있는 형태로 들어있다. 이 전후로 보관 장소의 파일 해시 (Hash)를 취해 보니, 같은 값 그대로였다.
운용 과정에서 증거가 부족해 튕겨 나간 장면은 개별적으로는 기억나지 않는다. 사용하는 도구에 불편함이 있으면 그 자리에서 고치라는 규칙을 dotagents (자신의 도구들을 모아서 관리하는 메커니즘)에 넣어두었기에, 막히는 부분을 발견할 때마다 Lattice 쪽을 수정하고 있었다. 7월 15일에 첫 커밋 (Commit)을 찍고, 12일 동안 731 커밋, 버전은 0.29.0, 설계 판단 기록은 89개. 그동안 정말 만들고 싶었던 것은 멈춰 있지만 말이다.
속도는 모르겠다
병렬로 실행해서 빨라졌는지는 측정하지 못했다. 공정을 짜서 범위를 측정하는 것보다, Markdown에 대충 쓰게 하는 편이 더 빨리 끝나는 장면도 있다. 얼마나 단축했다는 숫자는 손에 쥐고 있지 않다.
확실히 변한 것은, 하겠다고 말한 작업이 실제로 완료되게 되었다는 점이다. 건너뛰어지지 않는다. 채워지지 않았는데 끝났다는 말을 듣는 일이 확실히 줄었다.
특허를 냈다
이 메커니즘으로 특허를 출원했다. 2026년 7월 27일에 출원했으며, 출원 번호는 특원 2026-178950, 발명의 명칭은 "정보 처리 장치, 소프트웨어 개발 제어 방법 및 프로그램", 청구항은 12개다.
중심에 있는 것은, 작업의 설명과 코드의 구조로부터 영향 범위를 추정하여, 겹치지 않으면 그대로 나열하고, 리팩토링 (Refactoring)으로 지울 수 있는 중복이라면 지운 뒤에 나열하며, 그 계획에 따라 여러 개발 에이전트 (Agent)에게 동시에 시키는 흐름이다. 실제로 제출한 청구항 1은 다음과 같이 적혀 있다.

출원한 특허 청구 범위의 청구항 1.
출원서에는 이 이후의 내용도 썼다. 동시에 실행하는 도중에 충돌 (Collision)이 발생할 경우, 영향을 받은 작업을 멈추고, 멈춘 범위에 대해 계획을 다시 세운다. 실제로 변경된 장소를 관측하여, 추정한 범위 밖으로 나가면 실행 시의 충돌로 취급한다. 이 부분도 구현이 들어가 있으며, 움직이면서 다듬는 중이다.
현상황
공정은 보관 장소가 정본 (Source of truth)이며, Markdown에는 가지지 않는다. 지금 어떻게 되어 있는지는 도구에게 물어본다.
lattice status --json
lattice todo status --json
진행 상황은 공정도로 본다. 열람 중에도 업데이트가 반영되는 것을 세울 수 있다.
lattice todo gantt serve --port 0

전체 표시를 했을 때. 74개 공정과 그 의존 관계 (Dependency)가 한 장에 들어온다.

화면의 오른쪽 절반. 공정 상태의 내역, 지금 바로 동시에 시작할 수 있는 공정 수, 구분(Delimiter)별 감사 (Audit) 진행 상황이 나온다.
오른쪽 절반에는 공정 상태의 내역과 구분별 감사의 진행 상황이 나온다. locked가 나타내는 것은 감사의 대기 순번이다. 공정의 시작은 의존 관계만으로 결정되므로, 뒤쪽 구분에 속하는 공정이라도 전제 조건이 갖춰져 있으면 시작된다. 여러 구분이 동시에 진행되는 경우도 있다.
공정 관리를 AI의 손에서 빼앗아 오는 형식이 맞는지는 조금 더 써봐야 알 수 있을 것이다. 현재로서는 내 환경에서는 효과가 있다.
Discussion

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