AX 노트 · 방법

설계 3안을 심사자 3명이 채점했다: 시장 심사자 혼자였다면 카톡 열기마다 죽는 안이 1위였다

설계 심사자는 관점이 다른 셋을 두고 점수 합으로 승자를 정하십시오. 시장 관점 심사자 한 명만 있었다면 카톡에서 문서를 열 때마다 죽는 안이 1위였습니다.

작성 SugarMount 에이전트와 일하는 법 읽는 시간 14분 최종 업데이트

출시 속도를 우선한 설계안은 카카오톡에서 받은 문서를 화면 없는 액티비티(Theme.NoDisplay)로 받아 비동기로 복사하게 돼 있었습니다. 이 구조는 targetSdk 23 이상 앱이 Android 6.0 이상에서 돌 때 IllegalStateException을 냅니다. 2026년 10월 4일 밤, HWP·HWPX·PDF 문서 뷰어의 코드를 한 줄도 쓰기 전에 돌린 설계 워크플로에서 심사자 3명 중 2명이 이 결함을 지적했습니다. 에이전트 45개가 59분 28초 동안 8,799,884토큰을 썼고, 1위 안을 바탕으로 합친 최종안에서 결함 23건이 더 나왔습니다. 저장소에는 그 진입 코드가 한 번도 커밋되지 않았고, 앱은 10월 10일 Play에 공개됐습니다.

근거는 유틸리티 앱 저장소의 git 기록과 설계 문서, 같은 저장소의 Claude Code 세션 기록에 남은 워크플로 스크립트·결과·하위 에이전트 기록입니다(2026-10-26에 다시 셈). 매출 추정과 가격은 적지 않았습니다.

목차 (11)
  1. 코드 전 59분: 조사, 반박, 설계 3안, 심사, 비평
  2. 설계 프롬프트 3개는 우선순위 한 문장만 달랐습니다
  3. speed안의 진입: NoDisplay 테마에서 비동기 복사
  4. 심사자 1은 크래시가 있는 speed안을 1위로 골랐습니다
  5. 최종안 결함 23건: 맞지 않는 일정, 돌릴 수 없는 게이트
  6. NoDisplay 진입은 한 번도 커밋되지 않았습니다
  7. NoDisplay 진입을 처음 제안한 것은 반박 검증자였습니다
  8. 스크립트 골격과 따라 하는 순서
  9. 880만 토큰과 59분이 아까운 경우
  10. 다음에 확인할 것
  11. 자주 묻는 질문

코드 전 59분: 조사, 반박, 설계 3안, 심사, 비평

10월 4일 저녁 문서 뷰어를 시작할 때 있던 것은 시장 리서치와 엔진 스파이크 문서뿐이었고, 코드는 0줄이었습니다(엔진 측정은 안드로이드 한글 뷰어 만들기: rhwp + WebView를 문서 222개로 실측한 기록에 있습니다). 메인 세션이 21시 34분에 Claude Code Workflow 스크립트 하나를 작성해 돌렸습니다.

스크립트는 다섯 단계입니다. 12개 영역(경쟁 앱, HWP 엔진, PDF 엔진, 인텐트·진입, 받은 문서함, 수익화, UX, 코드베이스, 스토어 검색, 품질, 보안, 범위)을 에이전트 12개가 따로 조사하고, 영역마다 검증자 2명이 보고서를 깨려고 합니다. 검증 보고 24개 중 하나라도 반박(refuted)으로 판정한 발견 16개는 설계 단계에 넘기기 전에 지웠습니다. 남은 자료로 설계 에이전트 3개가 우선순위만 다른 안을 쓰고, 심사자 3명이 채점하고, 1위 안에 다른 안의 아이디어를 이식한 최종안을 비평가 1명이 점검했습니다. 마지막 에이전트가 문서 5개(분석, PRD, 화면 계약, 아키텍처, 구현 계획)를 맡았습니다.

단계 그림: 조사 12개, 검증 보고 24개와 반박된 발견 16개 삭제, 설계 3안, 심사자 3명 합계 95·104·98, 최종안 비평 결함 23건.
코드를 쓰기 전 59분 동안 에이전트 45개가 조사, 반박, 설계 경쟁, 심사, 비평을 차례로 맡았습니다.

조사는 0~16분, 검증은 조사가 끝난 영역부터 7~26분에 돌았고, 설계에 6분, 심사에 2분 반, 합성·비평·문서 작성에 25분이 들었습니다.

설계 프롬프트 3개는 우선순위 한 문장만 달랐습니다

모든 에이전트는 같은 공통 맥락을 받았습니다. 저장소 구성(minSdk 29, targetSdk 36), 앞선 리서치 문서의 위치, 목표 문장, 그리고 주장마다 [검증]·[추정]·[미확인]을 붙이고 파일 수정·설치·커밋·기기 조작은 하지 말라는 규칙입니다. 단계마다 출력은 JSON 스키마로 고정했습니다.

단계지시의 요지출력
검증한 명은 늘 사실 확인 렌즈: 근거를 직접 재현하지 못하면 refuted나 uncertain. 다른 한 명은 영역별 렌즈(시장, 구현 가능성, Android 플랫폼, 정책, 사용자)발견마다 confirmed/refuted/uncertain, 권고마다 keep/change/drop, 빠진 것
설계검증된 자료만 쓴다. 검증자의 반론에 답한다. uncertain인 발견은 검증 작업 없이 설계의 기둥으로 쓰지 않는다. 우선순위 한 문장(speed, quality, growth)범위, 아키텍처, 화면, 품질 게이트, 마일스톤과 일수, 위험
심사페르소나 하나. 5개 축(1등 가능성, 실현성, 신뢰성, 수익, 정책 안전)을 0~10점. 가혹하게, 치명 결함을 찾고, 승자와 이식할 아이디어를 적는다점수, 승자, 이식 목록, 치명 결함
비평R1 기능마다 담당 마일스톤과 테스트가 있는가, 위험마다 완화나 검증 작업이 있는가, 광고 규칙을 테스트로 강제하는가, 개발자 1명과 Claude 기준으로 견적이 현실적인가, 사람만 할 일이 목록에 있는가결함마다 blocker/major/minor와 고칠 방법

우선순위 문장은 셋이었습니다. speed는 "10월 수요 창 안에 2~3주 만에 내부 트랙, 단 안 열리는 일이 없고 문서에 광고가 없을 것", quality는 "저사양 폰에서도 모두 제대로 빨리 열리고 회귀·크래시 가드가 가장 강할 것", growth는 "신뢰 규칙 안에서 설치·재방문·수익을 최대로"입니다. 심사자 페르소나는 한국 모바일 시장에서 상위 유틸리티 앱을 낸 사람, 저사양 Samsung 기기의 크래시율과 APK 크기를 책임지는 Android 엔지니어, Play·AdMob 정책과 개인정보 검토자였습니다. 승자는 스크립트가 세 심사자의 합계로 정했습니다. 다수결이었어도 심사자 2·3이 고른 quality안이 이겼고, 설계를 speed안 하나만 받았다면 NoDisplay 진입이 그대로 구현 대상이 됐을 겁니다.

speed안의 진입: NoDisplay 테마에서 비동기 복사

speed안은 마일스톤 9개, 20.5일을 잡았습니다. 진입은 EntryActivity 하나였습니다. 테마는 Theme.NoDisplay이고, 문서 MIME 필터가 모두 이 액티비티로 연결됩니다. 이 액티비티가 Dispatchers.IO에서 URI를 받고, 앞 64KB로 형식을 판별하고, SHA-256을 계산하며 파일을 앱 안에 복사한 뒤 리더를 띄우고 끝납니다. 1초가 넘으면 진행 표시와 취소 버튼을 보여 줍니다.

NoDisplay 액티비티는 onResume()이 끝나기 전에 finish()를 불러야 합니다. targetSdkVersion 23 이상 앱을 Android 6.0 이상에서 돌리면, 그러지 않은 NoDisplay 액티비티는 "did not call finish() prior to onResume() completing" 메시지와 함께 IllegalStateException으로 죽습니다. 2015년 11월 CommonsWare 블로그가 이 동작을 알리면서, onCreate()에서 일을 끝내고 바로 finish()하는 액티비티는 괜찮아 보인다고 적었습니다. 이 앱은 targetSdk 36이라, 비동기 복사가 onResume() 뒤에 끝나는 speed안대로라면 카톡에서 "다른 앱으로 열기"를 누를 때마다 앱이 죽습니다.

설계 단계에는 돌려 볼 코드가 없었습니다. 코드로 옮겨도 컴파일은 되고, Robolectric처럼 액티비티 생명주기를 돌리는 테스트나 기기에서 VIEW 인텐트로 열어야 드러납니다. 심사자 2는 "쉽게 고칠 수 있지만 그대로 구현하면 치명적"이라고 썼고, 심사자 3은 Theme.Translucent.NoTitleBar로 바꾸라고 했습니다.

최종안은 VIEW를 리더 액티비티가 직접 받게 하고, 공유(SEND)는 Translucent 테마의 SendEntryActivity가 onCreate() 안에서 넘기고 끝나게 고쳤습니다. 아키텍처 문서에는 NoDisplay 진입을 버린 대안으로 적었고, PRD 수용 기준에 "Theme.NoDisplay 액티비티 0개"를 넣었습니다.

심사자 1은 크래시가 있는 speed안을 1위로 골랐습니다

심사자speedqualitygrowth고른 안
1 시장 경험자343332speed
2 Android 엔지니어293534quality
3 정책·개인정보 검토자323632quality
합계9510498quality
막대그림: 설계 3안의 심사 합계는 speed안 95점, quality안 104점, growth안 98점.
합계에서 1위 quality안과 2위 growth안의 차이는 6점이었습니다.

심사자 1은 치명 결함을 10개 적었는데 NoDisplay는 없었습니다. 세 심사자는 speed안의 1등 가능성에 8, 7, 8점을 줬습니다. quality안은 신뢰성 9점을 세 명에게서 받았지만 마일스톤 합이 44일이라, 세 명 모두 10월 수요 시기를 놓친다고 지적했습니다. growth안(39일)은 품질 게이트끼리 숫자가 모순됐습니다. 미리보기로 바꾸는 시한이 한 곳은 6초, 상태 기계는 8초 안내와 30초 강제 종료였습니다.

심사자 3은 speed안과 growth안의 앱 잠금 설정 BIOMETRIC_STRONG|DEVICE_CREDENTIAL이 API 28~29에서 지원되지 않는다는 것도 잡았습니다. minSdk가 29이니, 그대로 갔다면 Android 10 기기에서 유료 기능인 앱 잠금이 동작하지 않았을 겁니다. 세 심사자의 치명 결함 25개는 분석 문서에서 9행으로 묶였고, 행마다 처리 방법이 붙었습니다.

최종안 결함 23건: 맞지 않는 일정, 돌릴 수 없는 게이트

합성 에이전트는 quality안에서 시작해 speed안의 범위 축소와 병렬화를 이식하고 치명 결함을 모두 고쳤습니다. 그 결과물을 비평가가 체크리스트로 보자 차단 2건, 주요 10건, 경미 11건이 나왔습니다.

차단 1번은 일정 산수입니다. 임계 경로의 마일스톤 일수를 더하면 23작업일이라, 주말 없이 일해도 내부 트랙 목표일을 이미 넘었습니다. 차단 2번은 출시 게이트 하나가 실행될 수 없다는 지적이었습니다. 게이트는 shell 스크립트를 Firebase Test Lab 기기에서 통과시키라고 요구했는데, Test Lab에는 adb shell이 없습니다. 이 게이트는 둘로 나뉘었고, 첫 구현 마일스톤(M1)에 릴리스 빌드를 계측 테스트하는 스파이크가 들어갔습니다.

주요 결함 중에는 암호 문서 건이 있었습니다. 최종안은 빠른 첫 화면을 위해 HWP 파일 안의 미리보기 이미지를 먼저 보여 주는데, 조사 단계 실험에서 암호 문서에도 그 이미지가 들어 있었습니다. 그대로 두면 비밀번호를 넣기 전에 첫 쪽이 보입니다. 이 건은 기능 요구사항 하나와 아키텍처 절 하나가 됐고, 23건 모두 분석 문서에 반영 위치가 표로 남아 있습니다.

NoDisplay 진입은 한 번도 커밋되지 않았습니다

10월 5일 새벽 첫 구현 워크플로(M1·M2)가 ViewerArchitectureTest에 NoDisplay 금지 테스트를 넣었습니다. 같은 워크플로의 리뷰 에이전트는 이 테스트를 피해 가는 길 두 개를 찾았습니다. 액티비티가 상속하는 <application> 테마와, 스타일 항목 android:windowNoDisplay=true입니다. 지금 테스트는 액티비티를 띄우지 않고 매니페스트에서 테마를 지정한 요소 전부와 스타일의 parent·windowNoDisplay 항목을 검사하고, 두 경우를 하나씩 넣어 테스트가 실패하는지 확인했습니다. 가드 제거 확인 스크립트에도 SendEntryActivity의 테마를 NoDisplay로 바꿔 이 테스트가 실패하는지 보는 행이 있습니다.

git log -S NoDisplay를 돌리면 커밋 하나만 나옵니다. 10월 8일 내부 트랙 빌드 1.0.0 (2)를 담은 ca1ade4이고, 금지 테스트와 설계 문서가 여기서 처음 들어왔습니다. 10월 9일에는 소유자가 Galaxy S23에서 다른 카톡 계정으로 HWPX, HWP5, PDF, HWP3 문서를 받아 열었고 모두 정상이었습니다(3a3dfc6).

NoDisplay 진입을 처음 제안한 것은 반박 검증자였습니다

인텐트 영역의 조사 보고에는 NoDisplay가 없었습니다. 이 진입 구조는 그 보고를 검증한 사실 확인 검증자가 권고 수정안으로 넣은 것입니다. "VIEW·SEND로 들어오면 아주 가벼운 트램펄린(Theme.NoDisplay, excludeFromRecents, noHistory)이 받는다"는 문장과, 그 트램펄린이 64KB 판별과 복사를 시작한다는 설명이 검증 자료에 들어갔고, speed안이 그대로 따랐습니다. quality안과 growth안은 NoDisplay 테마를 쓰지 않았습니다.

심사자 3은 NoDisplay 지적에 "[검증, Android 6.0 동작 변경]"을 붙였는데, 그 에이전트의 도구 호출은 결과 제출 1회뿐이었습니다. 문서를 열어 보지 않고 붙인 표시였고, 내용은 맞았습니다(이 글을 쓰며 CommonsWare 원문으로 확인). 설계 에이전트 3개와 심사자 3은 저장소를 열지 않고 프롬프트에 붙인 검증 자료만으로 일했고, 심사자 1·2는 git 기록과 스파이크 파일을 한두 번 조회했습니다.

일정 견적은 반대 방향으로 틀렸습니다. 분석 문서는 원래 목표였던 11월 3일 production을 지킬 수 없다며 내부 트랙 11월 2일, production 11월 9일, 최악 11월 13~16일로 적었습니다. 실제 내부 트랙은 10월 8일, Play 공개는 10월 10일이었습니다. 공개 시점에도 결제 복원, API 29 앱 잠금, TalkBack 기기 확인 같은 마일스톤 완료 조건 일부가 열려 있었습니다. 비평가에게 "개발자 1명과 Claude" 기준으로 견적을 보라고 시켰는데, 이후 구현은 마일스톤 한두 개씩 별도 워크플로가 맡았습니다.

문서 작성 에이전트는 다섯 문서를 맡아 네 개를 썼습니다. 하네스가 보고서 파일 쓰기를 막았고, 에이전트는 우회하지 않고 본문을 응답에 담아 돌려줬습니다. 메인 세션이 그 본문을 저장했고, 워크플로 기록에는 45개 모두 '완료'로 남았습니다.

스크립트 골격과 따라 하는 순서

아래는 실제 스크립트를 줄인 골격입니다. 프롬프트 본문은 생략했고, dropRefuted·argmax·sumTotals 같은 이름은 설명용으로 붙였습니다(원본은 인라인 코드). 함수는 Claude Code Workflow 스크립트의 agent, parallel, pipeline입니다.

const verified = await pipeline(AREAS,
  a => agent(CTX + a.task, { schema: REPORT }),
  (r, a) => parallel(['fact-check', a.lens].map(l => () =>
    agent(CTX + LENS[l] + JSON.stringify(r), { schema: VERDICT }))))
const dossier = dropRefuted(verified)          // 16 findings removed
const designs = await parallel(ANGLES.map(an => () =>
  agent(CTX + an.priority + dossier, { schema: DESIGN })))
const judged = await parallel(JUDGES.map(j => () =>
  agent(CTX + j + designs + dossier, { schema: SCORE })))
const winner = argmax(sumTotals(judged))       // sum, not one judge
const final = await agent(CTX + winner + designs + judged + dossier, { schema: DESIGN })
const gaps = await agent(CTX + CHECKLIST + final + dossier, { schema: CRIT })
  1. 조사 영역마다 사실 확인 검증자 1명과 영역 렌즈 검증자 1명을 붙이고, 반박된 발견은 설계 입력에서 제외합니다.
  2. 설계안은 같은 자료, 같은 스키마로 쓰게 하고 우선순위 문장만 다르게 합니다. 마일스톤 일수를 스키마에 넣어야 안끼리 일정이 비교됩니다.
  3. 심사자는 시장, 엔지니어링, 정책처럼 관점이 다른 셋으로 두고 합계로 정합니다. 치명 결함과 이식할 아이디어를 따로 받습니다.
  4. 최종안에는 체크리스트 비평을 한 번 더 돌리고, 결함마다 반영한 문서 위치를 표로 적습니다.
  5. 치명 결함은 매니페스트·스타일을 읽는 아키텍처 테스트 같은 규칙 테스트로 고정하고, 가드를 지워 테스트가 실패하는지 봅니다.
  6. 실기기 열기처럼 사람만 할 수 있는 확인은 별도 목록으로 빼서 닫습니다.

코드를 만든 뒤의 리뷰는 다른 방식으로 돌렸습니다. 발견마다 반박을 붙이는 쪽은 에이전트 84개가 돈 릴리스 전 검토: 발견마다 3명이 반박하고, 버틴 20건만 고쳤다에 있습니다.

880만 토큰과 59분이 아까운 경우

토큰은 검증 단계가 295만으로 가장 많이 썼습니다. 설계·심사·합성 에이전트 9개는 도구를 21번 부르고 384만 토큰을 썼는데, 같은 검증 자료 JSON을 프롬프트마다 통째로 붙였기 때문입니다. 금액은 기록하지 않았습니다.

화면 하나를 더하거나 쓸 API가 정해진 기능처럼 설계가 갈릴 여지가 없으면 이 구조는 과합니다. 이번에는 새 앱, 엔진 두 개, 진입 경로, 수익화, Play 정책이 한꺼번에 걸려 있었습니다. 같은 모델이 같은 자료를 읽으니 모두가 놓치는 결함은 패널도 못 잡고, 같은 speed안에 심사자마다 29~34점을 줄 만큼 점수도 보정되지 않습니다.

다음에 확인할 것

10월 9일 기록에서 네이버 메일과 Gmail로 받은 문서를 여는 경로는 아직 실측하지 않았습니다.

자주 묻는 질문

심사자를 한 명만 두면 안 되나요?

이 실행에서는 시장 페르소나 심사자 한 명만 있었다면 NoDisplay 크래시가 있는 speed안이 34점으로 1위였습니다. 크래시는 Android 엔지니어와 정책 검토자 페르소나가 잡았습니다.

Theme.NoDisplay 액티비티는 왜 죽나요?

targetSdkVersion 23 이상 앱을 Android 6.0 이상에서 돌리면 NoDisplay 액티비티가 onResume() 전에 finish()하지 않을 때 IllegalStateException이 납니다. 비동기 작업이 필요하면 Translucent 테마를 쓰거나 실제 화면에서 받습니다.

이 글은 SugarMount가 직접 측정한 값으로 쓰고, 초안 작성에 AI 코딩 에이전트를 썼습니다.

에이전트와 일하는 법 글 전체 보기 · AX 노트 전체 보기