← ~/blog
2026. 05. 07. Creative Engine

두 개의 git: 에이전트의 기억과 제품의 이력을 분리하라

에이전트가 git 이력을 '기억'으로 읽기 시작하면, 한 레포에 쌓인 제품 이력과 운영 이력은 서로의 신호를 노이즈로 만든다. 운영 평면을 교체 가능한 control-store 포트로 떼어내고 과거는 동결하는, 에이전트 시대의 형상관리 원칙.

두 개의 git: 에이전트의 기억과 제품의 이력을 분리하라

에이전트에게 작업을 맡기면, 제일 먼저 하는 일이 git loggit status를 읽는 것이다. 무엇이 최근에 바뀌었고 지금 어떤 상태인지를 스스로 파악하고 일을 시작한다. 편리하다. 이력이 곧 맥락이고, 맥락이 곧 기억이다.

그런데 며칠을 그렇게 굴려보면 레포가 묘하게 탁해진다. 제품 커밋 사이사이에 “진행 포인터 갱신”, “참조 해시 동기화”, “검증 통과” 같은, 사람이 릴리스 노트에서 결코 보고 싶지 않은 운영 잡음 커밋이 끼어든다. 반대로 에이전트가 “지난번에 이건 어떻게 결정했더라”를 되짚을 때는, 아무 상관 없는 제품 리팩터 커밋이 검색 상위로 올라온다. 둘 다 같은 레포의 git에 들어 있다. 그런데 둘은 같은 종류의 이력이 아니다. 이 글은 그 깨달음에서 출발한다.

git을 ‘기억’으로 쓰면, 한 레포에 두 개의 이력이 겹친다

우리가 운영하는 에이전트는 세션을 시작할 때마다 형상 이력을 맥락으로 주입받는다. 그래서 git 이력은 깨끗해야 한다 — 이력의 품질이 곧 에이전트가 읽는 기억의 품질이다. 이건 이 시리즈에서 여러 번 다룬 “상태를 파일에 두고 git이 추적한다”는 사상의 자연스러운 귀결이다.

문제는, 같은 레포에 성질이 다른 두 종류의 이력이 동시에 쌓인다는 것이다.

  • 제품 이력: 사람이 릴리스하고 롤백하고 감사하기 위한 산출물의 역사. 독자는 사람이고, 변동은 릴리스 단위이며, 목적은 “무엇이 배포되었나”의 추적이다.
  • 운영 이력: 에이전트가 일하면서 남기는 in-flight 포인터, 결정 로그, 검증 결과, 계획과 통신. 독자는 에이전트 자신이고, 변동은 분 단위이며, 목적은 “지금 어디까지 왔고 지난 결정이 무엇이었나”의 추적이다.

(엄밀히는 세 번째 층, 세션을 가로지르는 장기 기억도 있다. 그건 이미 레포 밖에 분리해 두었다. 그러니 “분리”는 새 발상이 아니라, 이미 시작한 방향을 한 칸 더 완성하는 일이다.)

이 둘은 독자도, 변동 주기도, 목적도 다르다. 그런데 한 브랜치 이력 위에 시간순으로 뒤섞여 쌓인다.

섞이면, 서로의 신호를 노이즈로 만든다

핵심 진단은 이것이다. 두 이력이 한 레포에 공존하면 양방향으로 오염된다.

한 방향. 운영 잡음 커밋이 제품 이력에 끼면, 사람의 릴리스·롤백 판단이 흐려진다. “이게 배포 대상인가 에이전트의 포인터 갱신인가”를 매번 가려내야 한다. 더 나쁜 건, 에이전트가 제품 맥락을 기억으로 주입받을 때 그 잡음까지 함께 읽는다는 점이다.

다른 방향. 제품 커밋이 에이전트의 운영 질의에 끼면, “지난 결정”이나 “현재 진행 상태”를 찾을 때 무관한 제품 변경이 검색을 오염시킨다. 유사도는 맞는데 맥락이 다른 결과가 올라온다. 이건 컨텍스트 윈도우를 키운다고 풀리지 않는다. 더 많이 읽힌다고 종류가 다른 두 이력이 갈라지지는 않기 때문이다. 문제는 양이 아니라 경계다.

우리가 택한 접근: 운영 평면을 떼어내고, 교체 가능한 포트로 박는다

방향은 분명하다. 운영 substrate를 제품 레포에서 떼어, 대상 프로젝트 바깥의 자기만의 저장소에 둔다. 장기 기억을 레포 밖에 둔 것과 같은 결이다. 다만 우리가 단순한 “폴더 분리”에서 멈추지 않은 지점이 여기다.

운영 이력을 control-store라는 추상 포트 뒤에 둔다. 기본 백엔드는 자기 자신의 git이다 — 신규 인프라가 0이고, “git=기억” 메커니즘을 그대로 재사용한다. 하지만 포트로 정의해 두면, 오늘은 git이어도 내일은 DB나 별도 컨텍스트 서비스로 백엔드를 갈아끼울 수 있다. 자산은 구현이 아니라 경계다.

제품 평면과 운영 평면은 공유 브랜치 이력이 아니라 안정적 ID로 느슨하게 연결한다.

product-repo/          # 제품 평면 — 사람이 읽는 깨끗한 이력
  └─ commit <commit-sha>

control-store/         # 운영 평면 — 에이전트가 읽고 쓰는 이력 (포트 뒤)
  └─ decision: <stable-id>
        refersTo: <commit-sha>   # 공유 브랜치가 아니라 ID로 연결

운영 쪽 결정은 제품 커밋의 SHA를 anchor로 인용한다. 두 평면은 한 이력을 공유하지 않고, 서로를 가리킬 뿐이다.

분리를 실현하는 데는 세 가지 길이 있고, 트레이드오프가 다르다.

접근분리도비용평가
별도 control 저장소가장 깨끗·비침투적셋업 한 번권고
orphan 브랜치 + worktree중간낮음거버넌스가 반쯤 제품일 때의 과도기
디렉토리 컨벤션 + 필터낮음 (한 이력에 공존)가장 쌈근본 해결 아님

세 번째는 가장 싸지만 두 이력이 여전히 한 git에 공존하므로, 우리가 진단한 문제를 풀지 못한다. 첫 번째는 셋업 비용을 한 번 치르는 대신 분리가 가장 깨끗하다. 그래서 첫 번째를 권고한다.

마지막으로 마이그레이션. 현재 운영 내용은 새 control-store로 옮기되, 과거 git 이력은 재작성하지 않고 동결하고 baseline SHA로 참조한다. 기존 결정들이 이미 커밋 해시를 anchor로 인용하고 있어서, history rewrite는 그 anchor를 전부 깨뜨리기 때문이다 — 고위험·저가치다. 과거는 얼려 두고, 새 평면은 거기서부터 시작한다.

정리가 아니라 계약이다

처음엔 그저 “레포가 좀 지저분하네” 하는 정리 문제로 보였다. 한 번 치우면 끝날 일 같았다. 그런데 들여다보니, 이건 에이전트가 형상 이력을 1급 입력으로 읽는 모든 프로젝트에서 똑같이 반복되는 구조적 문제였다. 에이전트가 git을 기억으로 쓰는 한, 어느 팀에서나 제품 신호와 운영 잡음은 같은 이력 위에서 충돌한다.

그래서 우리는 이걸 한 번 치우는 대신, “제품 형상관리 평면과 에이전트 운영 형상관리 평면을 분리한다”는 원칙으로 박았다. 일회성 청소가 아니라 프레임워크가 지키는 계약으로. 정직하게 말하면 이 원칙은 지금 정착시키는 중이고, 모든 경계가 아직 깔끔하지는 않다. 하지만 방향은 확정됐다.

코드베이스를 에이전트에게 맡긴다는 것

에이전트에게 당신의 코드베이스를 맡기는 순간, 에이전트는 그 형상 이력을 기억으로 읽기 시작한다. 그때 제품의 신호와 에이전트 운영의 잡음을 구분하는 형상관리 설계가 없으면, 둘은 서로를 오염시킨다 — 깨끗해야 할 제품 이력은 더러워지고, 에이전트의 기억은 노이즈에 묻힌다. 우리는 이 경계 문제를 우리 작업에서 직접 부딪히며 설계했고, 같은 원칙을 당신의 제품과 운영에 이식할 수 있다.

#thesis #engineering #agent-ops #version-control #context-engineering #ai-infrastructure
← 목록으로