
자기 진화는 멋대로 본체를 다시 쓰지 않는다: AMA-teras의 worktree 격리와 이와토(Iwato) 게이트
요약
AI 코딩 에이전트 AMA-teras의 '자기 진화' 메커니즘을 설명합니다. 무분별한 코드 수정을 방지하기 위해 git worktree를 통한 격리, 자동화된 검증(typecheck, 테스트, 스모크 테스트), 그리고 최종 인간 승인 단계를 거치는 안전한 워크플로우를 다룹니다.
핵심 포인트
- git worktree를 활용하여 본체 코드와 후보 구현을 격리함
- typecheck, 테스트, 실제 기동 스모크 테스트를 통한 다중 검증
- 생성, 검증, 승인 단계를 분리하여 시스템 안정성 확보
- 최종적인 코드 승격 및 외부 공개는 반드시 인간의 승인을 거침
도입: 자기 진화라는 말의 오해
AMA-teras에서는 「자기 진화 (Self-evolution)」라는 말을 사용하고 있습니다.
하지만, 이것은 「AI가 멋대로 본체를 다시 쓰는 것」을 의미하지 않습니다.
부족한 기능을 마주했을 때, 구현안을 생성하고, 격리된 git worktree에서 검증하며, typecheck · 테스트 · 실제 기동 스모크 테스트 (Smoke test)를 통과한 뒤, 마지막에 인간이 승인해야 본체로 승격된다는 의미입니다.
외부로의 발신이나 쓰기도 마찬가지입니다.
Zenn으로의 공개, GitHub Release 공개 등, 외부로 나가는 조작은 승인 다이얼로그를 거칩니다. 초안 작성까지는 자동화할 수 있어도, 공개 버튼을 누르는 것은 인간입니다.
자기 진화의 중심에 있는 것은 「자동으로 변하는 것」이 아니라, 「안전하게 후보를 만들고, 안전하게 검증하며, 안전하게 승격할 수 있는 것」입니다.
AMA-teras의 자기 진화 플로우
부족한 기능을 마주함
AMA-teras는 대화하면서 계획, 구현, 디버깅, 테스트를 진행하는 Electron 기반의 데스크톱 AI 코딩 에이전트입니다.
작업 중에 「지금의 도구로는 부족하다」는 상황을 마주할 때가 있습니다.
그때 갑자기 본체를 변경하는 것이 아니라, 우선 추가해야 할 기능이나 수정 사항을 후보로 취급합니다.
스스로 구현을 생성함
후보에 대해 AMA-teras는 구현을 생성합니다.
여기서 생성되는 것은 아직 본체가 아닙니다.
본체에 섞이기 전의 「후보」입니다.
이 단계에서 중요한 것은, 생성했다는 사실과 채택한다는 판단을 분리하는 것입니다.
생성할 수 있었기 때문에 채택하는 것이 아니라, 검증을 통과하고 인간이 승인해야 비로소 승격할 수 있습니다.
격리된 git worktree에서 검증함
생성물은 격리된 git worktree에 놓입니다.
본체의 작업 트리 (Working tree)에 직접 섞지 않음으로써, 후보 구현이 망가지더라도 본체를 망가뜨리지 않습니다.
실패한 후보는 후보로서 실패합니다. 본체의 상태와는 분리됩니다.
typecheck · 테스트 · 실제 기동 스모크 테스트를 통과함
후보는 적어도 다음의 검증 게이트를 통과합니다.
- typecheck
- 테스트 (Test)
- 실제 기동 스모크 테스트 (Smoke test)
typecheck만으로는 부족합니다.
테스트만으로도 부족합니다.
실제로 기동하여 동작하는지도 확인합니다.
PROGRESS.md에는 테스트로는 잡아내지 못했던 실제 앱 경로의 버그도 기록되어 있습니다.
M72에서는 Bluesky의 검색만 401 에러가 발생하는 문제가 있었습니다. 원인은 app password가 아니라, BlueskyReader의 기본 fetchImpl이 두 번째 인자인 Authorization 헤더를 버리고 있었던 것이었습니다. 테스트는 모두 fetch를 주입 (Injection)하고 있었기 때문에, 운영 환경에서만 기본값을 사용하여 검색만 인증 없이 날아가고 있었습니다.
이 수정에서는 bluesky / zenn / hn / hatena의 기본값을 수정하고, 주입이 없는 경로, 즉 실제 앱의 경로를 테스트에서 고정하고 있습니다. 나아가 실제 기기에서 순회 성공과 검색 10건을 확인했습니다.
「테스트를 통과했다」뿐만 아니라, 「실제 앱의 경로가 통과했다」는 것을 확인할 필요가 있다는 기록입니다.
본체로의 승격은 인간이 승인함
검증을 통과한 후보도 자동으로 본체에 승격되는 것은 아닙니다.
본체로의 승격은 인간의 승인을 거칩니다.
AMA-teras의 자기 진화는 생성, 검증, 승인을 분리하고 있습니다.
이러한 분리가 있기 때문에, 「스스로 도구를 만드는 것」과 「멋대로 본체를 다시 쓰는 것」은 같지 않습니다.
최근의 실례
이 기사에서는 제공된 PROGRESS.md 발췌본에서 확인할 수 있는 범위 내에서만 다룹니다.
job-38 / job-42 / job-47, M94, core 프롬프트로의 환원에 대해서는 이 발췌본 내에 검증할 수 있는 기록이 없으므로 본문에서는 다루지 않습니다.
M73: Zenn 기사 공개를 앱에서 할 수 있도록 함
M73에서는 Zenn 기사의 「기사화」가 published: false로 커밋하는 단계에서 멈춰 있었다는 점이 기록되어 있습니다.
GitHub Release는 스마트폰에서 공개할 수 있는 반면, Zenn 기사는 커밋 후의 공개 단계까지 손이 닿지 않았습니다.
그래서 zenn-repo에 publish-article이 추가되었습니다.
이 조작은 이와토(Iwato) 게이트에서 전문을 확인한 뒤 실행됩니다.
승인 후에 published: true로 설정하여 push합니다.
여기서도 외부로 나가는 조작은 자동 실행이 아니라 승인을 거칩니다.
M74: 스마트폰 승인 경로에서 중복 커밋이 발생함
M74에서는 스마트폰을 통한 승인 경로에서 중복이 발생한 사례가 기록되어 있습니다.
실질적인 피해로 23:05:05, 23:05:07, 23:05:10, 23:06:26의 흐름으로, 5초 사이에 3개의 커밋이 Zenn으로 전송되어 동일한 기사가 2개나 공개 리포지토리(Public Repository)에 올라갔습니다.
원인은 M69에서 도입된 쌍둥이 요약(Twin Summary) 기능이 batchRespond에만 적용되어 있었고, 스마트폰은 단 한 장이라도 bulkRespond를 통과할 수 있었기 때문입니다.
게다가 초안의 「게시 완료」 표시는 실행 완료 후에 붙기 때문에, 본문 생성 과정의 몇 초 동안 중복 방지(Duplicate Guard)를 경합(Race Condition)으로 빠져나갔습니다.
수정 사항으로 bulkRespond의 직렬화(Serialization)와 쌍둥이 요약 기능이 구현되었습니다.
이는 승인 다이얼로그가 있더라도 승인 경로가 여러 개라면 반드시 동일한 가드(Guard)를 통과해야 한다는 교훈을 주는 실패 사례입니다.
M76: 「보냈다고 생각한 것」과 「읽을 수 있는 것」은 달랐다
M76에서는 3개를 published: true로 설정하여 push했음에도 불구하고, Zenn이 동기화한 것은 2개뿐이었다는 사실이 기록되어 있습니다.
나머지 1개는 Zenn 측에서 403, 즉 존재하지만 비공개 상태여서 아무도 읽을 수 없는 상태였습니다.
하지만 장부(Ledger)는 실제로 공개된 2개를 「공개 대기」 상태로 남겨두고, Zenn이 동기화하지 않은 1개를 「게시 완료(Posted)」라고 기록하고 있었습니다.
수정 사항으로 ZennReader.isLive(slug)가 추가되었습니다.
- 200: 읽을 수 있음
- 403 / 404: 읽을 수 없음
- 도달 실패: 불명
장부는 이제 실제로 읽을 수 있는 것만 posted 상태로 만들게 되었습니다.
공개 버튼 또한, push를 했다고 해서 곧바로 「공개했다」라고 말하지 않도록 변경되었습니다.
M77・M81・M83: 재배포(Redeploy)도 안전한 방향으로
M77에서는 Zenn의 게시물 수 상한 제한으로 인해, 기사가 published: true임에도 불구하고 아무도 읽을 수 없는 상태로 굳어버린 사례가 기록되어 있습니다.
이 상태에서는 앱 상에서는 공개 대기로 나타나지 않고 공개 버튼도 뜨지 않지만, 정작 세상에는 공개되지 않은 상태가 됩니다.
이에 따라 redeploy-article, zennStuck(), 그리고 PC/스마트폰의 「재배포(Redeploy)」 버튼이 추가되었습니다.
다만 M81에서 빈 커밋(Empty Commit)으로는 Zenn이 아무것도 재배포하지 않는다는 사실이 밝혀졌습니다.
Zenn은 변경된 파일을 보고 기사를 동기화하기 때문에, 빈 커밋으로는 동기화 대상이 존재하지 않았던 것입니다.
수정 사항으로 기사 파일 자체의 끝에 줄바꿈 문자 하나만 차이(Diff)를 두도록 했습니다. 본문 내용은 바꾸지 않았습니다.
또한 M83에서는 재배포를 누를수록 상황이 더 느려지는 문제가 기록되었습니다.
Zenn은 최근 24시간 내에 5개 이상의 게시를 차단합니다. 재배포 역시 배포의 일종이므로, 병목이 발생했을 때 버튼을 누를수록 대기열(Window)에 쌓이게 되어 문제를 해결할 수 없게 됩니다.
실기기에서는 24시간 동안 상한의 두 배인 10개가 쌓여 있었습니다.
수정 사항으로, 누르기 전에 git log로 대기열을 확인하여 상한에 도달해 있다면 실행하지 않도록 했습니다.
또한, 언제 자리가 나는지 표시하도록 했습니다.
M85〜M87: 릴리스 공개 버튼이 사라졌던 문제
v1.2.0의 초안 릴리스(Draft Release)는 완료되었으나, 앱 화면에 공개 버튼이 나타나지 않는 문제가 있었습니다.
M85-1에서는 releaseInfo의 useEffect 의존성 배열이 [repo]뿐이었기 때문에, 앱을 열어둔 채로 초안 릴리스를 만들면 UI가 기동 시점의 pendingDraft: null 상태를 계속 유지한다는 사실을 알게 되었습니다.
수정 사항으로 PC와 스마트폰 모두 30초마다 정보를 다시 가져오도록 했습니다.
M85-2에서는 IPC 허가 목록(Allowlist)이 draft / posted / discarded만 알고 있어서, M57에서 추가된 staged를 에러로 처리하지 않고 버리고 있었다는 사실이 밝혀졌습니다.
수정 사항으로, 모르는 값은 묵인하지 않고 명확하게 거부하도록 했습니다.
M86에서는 공개 버튼이 「공개 가능한 상태가 되는 순간 사라지는」 현상이 발견되었습니다.
「초안 릴리스를 공개」 버튼은 초안 카드(Draft Card)의 조작 행(Action Row)에 있었고, 해당 조작 행은 status === 'draft'일 때만 그려지도록 되어 있었습니다.
하지만 릴리스 상태가 된 초안은 staged가 됩니다.
그 결과, 공개해야 할 상태가 되는 순간 버튼이 사라졌던 것입니다.
수정 사항으로, 버튼을 초안 카드에서 분리하여 독립된 섹션으로 만들었습니다.
M87에서는 공개된 릴리스를 장부가 계속 「공개 대기」라고 말하는 문제가 기록되어 있습니다.
애초에 어떤 초안(draft)이 어떤 릴리스인지 대조할 수 있는 열쇠가 없었습니다.
수정 사항으로 OperationsDraft.tag를 추가하여, 릴리스 생성 시 태그를 기억하도록 했습니다.
또한, GitHub의 1차 정보인 draft=false를 확인하여 장부를 다시 작성하도록 했습니다.
v1.2.0는 2026-07-14 14:39 JST
Discussion

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