블로그

2026년 8월 14일 · 5분 읽기

AI에게 실행을 넘기고 판정은 넘기지 않습니다

iOS 앱과 Go 백엔드, 셀프호스팅 쿠버네티스를 혼자 운영하면서 AI에게 무엇을 넘겼고 무엇을 직접 붙잡았는지 적었습니다. 근거로 넘긴 파일이 이미 사실과 달랐던 일부터 시작합니다.

  • 1인 개발
  • AI 개발
  • GitOps

2026년 8월 23일 업데이트

혼자 만들면 손이 아니라 판단이 먼저 부족해집니다. iOS 앱과 Go 백엔드, 셀프호스팅 쿠버네티스를 한 사람이 맡으면 코드를 쓰는 시간보다 지금 무엇이 사실인지 확인하는 시간이 더 듭니다. 그래서 실행은 AI에게 넘기고, 무엇을 사실로 받아들일지는 제가 정합니다.

이 글은 그 경계를 어디에 그었고 그 경계가 실제로 어디서 새는지 적습니다. 이 블로그 지면을 세우면서 확인한 것도 함께 넣었습니다.

한눈에 보기

문제 혼자 만들면 손이 아니라 판단이 먼저 부족해집니다.
결정 고객 응대와 운영에는 에이전트 6인을 두었습니다.
결과 그래서 배포 뒤에 공개 URL을 직접 열어 상태 코드와 콘텐츠 타입을 확인하는 단계는 사람 몫으로 남겼습니다.
제약 역할을 6개로 나눴다고 해서 권한을 프롬프트에 맡기지는 않았습니다.

근거로 넘긴 파일이 이미 사실과 달랐습니다

AI에게 작업을 맡길 때 저는 저장소를 근거로 줍니다. 클러스터 구성을 정리하면서 제 체크아웃의 clab-app/argocd/applications를 세어 보니 매니페스트가 10개였습니다. 같은 디렉터리를 원격 origin/main에서 세어도 10개였습니다. 개수가 같아서 그냥 넘어갈 뻔했는데, 목록을 맞춰 보니 세 개씩 달랐습니다.

어디에만 있는가 항목
제 체크아웃에만 justsend-ocr, justsend-ocr-normalizer, justsend-web-app
원격에만 discourse, justsend-ops, justsend-render

제 체크아웃은 원격보다 14개 커밋 뒤처져 있었습니다. 같은 시각 클러스터 매니페스트 저장소는 뒤처진 커밋이 없었고 매니페스트 11개가 원격과 같았습니다. 한쪽만 낡아 있었기 때문에 전체가 낡았다는 신호는 어디에도 없었습니다.

라이브 클러스터를 조회하면 ArgoCD Application이 23개이고 전부 Synced이며 Healthy입니다. 파일을 세어 얻은 23개와 라이브의 23개는 우연히 같은 수였습니다. 구성이 세 개씩 어긋난 상태로 합계가 맞았으니, 개수만 비교하는 확인은 이 어긋남을 잡지 못합니다.

여기서 남은 기준은 하나입니다. AI에게 저장소를 근거로 주면 그 근거는 저장소의 현재가 아니라 제 체크아웃의 시각입니다. 라이브 상태로만 확정되는 수치는 파일을 세는 방식으로 쓰지 않습니다.

살아 있는 시스템 지도

앱을 하나라고 부르는 순간 배선이 사라집니다

ios-prod/app/project.ymltargets를 세면 5개입니다. JustSendKitframework, JustSendapplication, JustSendShareJustSendWidgets는 각각 app-extension, JustSendTestsbundle.unit-test입니다. 제품으로 묶이는 application과 app-extension 타깃은 3개이고, 테스트와 프레임워크까지 포함한 선언은 5개입니다.

이 구분이 필요한 이유는 앱을 하나로 부르면 연결 관계가 말에서 사라지기 때문입니다. JustSendJustSendKit과 두 extension을 의존성으로 연결하고, 두 extension은 다시 JustSendKit을 사용합니다. JustSendKitjustsend-corepath: JustSendMemoryCore로 링크합니다. 원격 패키지 버전을 고정한 구조라고 부르면 현재 project.yml과 맞지 않습니다.

그래서 core에서 나온 결함을 앱 쪽 우회 코드로 덮지 않습니다. 정본인 JustSendMemoryCore를 함께 열어야 같은 결함을 두 번 고치지 않습니다.

AI에게 판정을 맡겼을 때의 실측값

app/AGENTS.md에 적어 둔 원칙은 측정하지 않은 것 위에 만들지 않는다는 것입니다. 이 원칙이 생긴 자리는 온디바이스 AI의 판정 실험입니다.

OnDeviceCapabilityProbeTests 기록을 보면, 판정을 AI에게 맡긴 실측은 200회에서 50%였습니다. 서술문의 73~87%를 실행 요청으로 오판했습니다. 같은 모델에 좁은 추출 계약만 맡겼을 때는 제목이 12/12였습니다.

읽히는 결론은 분명합니다. 모델이 약해서가 아니라 맡긴 일의 모양이 문제였습니다. 그래서 오케스트레이션은 코드가 소유하고, AI는 단계마다 JSON 계약 하나만 채우게 했습니다. 무엇을 할지 정하는 자리와 빈칸을 채우는 자리를 나눈 결과입니다.

역할로 좁힐 수 있는 반복만 넘깁니다

고객 응대와 운영에는 에이전트 6인을 두었습니다. 총괄, 리뷰, 커뮤니티, 메일, 지표, 운영을 각각 맡습니다. 이들은 App Store Connect, 커뮤니티 포럼, 메일 서버, 쿠버네티스 읽기, 자체 데이터베이스에 연결된 care-mcp를 사용합니다.

역할을 6개로 나눴다고 해서 권한을 프롬프트에 맡기지는 않았습니다. 역할 게이팅은 scopedServer 등록 지점에서 강제하고, leadinsight는 읽기 전용으로 두었습니다. 외부에 흔적을 남기는 쓰기는 CARE_WRITE_ENABLED, CARE_CLUSTER_WRITE, CARE_APPROVERS라는 환경 게이트와 사람 승인 흐름을 모두 지나야 합니다.

그러므로 넘긴 것은 메일과 리뷰, 커뮤니티, 지표, 초동 운영처럼 역할로 좁혀지는 반복입니다. 제품의 경계와 core 계약, 배포 매니페스트의 변경은 넘기지 않았습니다. 시크릿과 클러스터 변경은 제가 실행하고, 에이전트는 매니페스트까지만 만듭니다.

사람이 보지 않아도 서는 검사를 만듭니다

이 지면도 같은 방식으로 세웠습니다. 글은 저장소의 마크다운이고, 빌드가 허브와 세 언어의 목록, 공개된 글의 상세를 정적 HTML로 굽습니다. 여기에 검사를 추가했습니다. 색인 대상 페이지에 title·canonical·hreflang·JSON-LD 중 하나라도 없으면 빌드가 섭니다. 색인하지 않는 허브에는 noindex가 반드시 있어야 하고, 없으면 역시 섭니다.

이 검사를 넣은 이유는 head가 빠진 페이지가 조용히 배포되기 때문입니다. 화면은 정상으로 보이고 색인만 깨지므로, 사람이 확인하기까지 몇 주가 걸립니다.

검사를 붙여 두고도 세 건이 검사 밖에서 났습니다.

무엇이 났는가 어디서 왔는가
공개된 robots.txt와 사이트맵, RSS 3종이 nginx 403 생성 파일의 모드가 0600이었습니다. 제 호스트의 umask가 077이고, writeFilemode는 새로 만들 때만 적용되어 기존 파일의 모드가 남았습니다
linux/amd64 이미지 빌드가 프리렌더에서 실패 qemu 에뮬레이션 위의 Chromium이 기동하지 못했습니다. 대기를 180000밀리초까지 늘려도 같았습니다
목록 페이지의 hreflang이 초안만 있는 언어를 가리킴 공개 여부를 보지 않고 언어 목록을 만들었습니다

앞의 두 건은 코드의 논리가 아니라 배포 환경과의 접촉면에서 났습니다. 프리렌더 HTML은 컨테이너에서 새로 만들어져 0644였고 피드만 0600이었기 때문에, 블로그 경로는 200이고 피드만 403이라 원인이 한동안 가려졌습니다. 세 번째는 논리 결함이라 코드에서 고쳤습니다. 같은 종류로 상세 페이지의 x-default가 존재하지 않는 영어 URL을 가리키던 것도 함께 고쳤습니다.

그래서 배포 뒤에 공개 URL을 직접 열어 상태 코드와 콘텐츠 타입을 확인하는 단계는 사람 몫으로 남겼습니다. 빌드가 통과했다는 사실과 공개된 주소가 정상이라는 사실은 다릅니다.

이 지면의 규칙

글은 content/blog/<슬러그>/<언어>.md 하나뿐이고, frontmatter의 drafttrue면 목록과 사이트맵, RSS, 정적 생성에서 모두 빠집니다.

언어는 한국어와 영어, 일본어 세 개를 둘 수 있고 파일이 있는 언어에만 글이 존재합니다. 한국어를 원문으로 쓰고 영어판을 따로 둡니다. 일본어 파일은 없으므로 일본어 목록에는 나오지 않고 언어 대체 선언에도 들어가지 않습니다.

같은 상황을 만나면

  • AI에게 근거로 주는 저장소가 원격과 같은 시각입니까?
  • 개수만 비교하고 있습니까, 목록을 맞춰 보고 있습니까?
  • 모델에게 판정을 맡기고 있습니까, 정해진 빈칸을 채우게 하고 있습니까?
  • 역할별 권한이 프롬프트에 적혀 있습니까, 등록 지점에서 강제됩니까?
  • 빌드가 통과한 것과 공개된 주소가 정상인 것을 같은 확인으로 보고 있습니까?
  • 생성 파일의 모드가 호스트 umask에 좌우되고 있습니까?

다음 편에서는 iOS 온디바이스 AI의 가용성 판정과 컨텍스트 한도, 계약과 폴백을 기록에서 확인한 내용을 다룹니다.