Paul Jialiang Wu agentic-portfolio English 中文 Español 日本語✉️ 무료 리스트
← 포트폴리오로 돌아가기

AI-Native 시리즈 · 에이전트형 엔지니어링

앱은 계속 “완료”라고 말했다. 거짓말이었다. 그래서 우리는 앱이 스스로를 감사하게 만들었다.

Paul Jialiang Wu · agentic-portfolio-lovat.vercel.app · 2026-08-01

1분 요약 — 이 글에서 얻어갈 것

AI 공동 개발자와 함께 만들고 있는 앱이 사용자에게 “로그인 링크를 보냈습니다!”라고 말했다 — 그 뒤에는 이메일 서비스가 아예 없었다. 버그를 하나씩 고치는 대신, 우리는 제품이 내건 모든 약속(80개)을 적어두고 그중 20개 뒤에 기계 검증을 걸었다. 정직한 스코어보드는 하루 만에 초록불 6개에서 15개로 늘었다 — 그리고 감사 체계는 아무나 들어올 수 있게 열려 있던 관리자 뒷문을 잡아냈다. 이 모든 게 작동한 건 단 하나의 규칙 덕분이었다: 증거 없이는 통과도 없다.

노인을 위한 자서전 앱, 거짓말을 한 성공 토스트, 그리고 두더지 잡기 게임을 끝낸 주장 장부에 관한 빌드인퍼블릭 이야기. 약 8분.

The app kept saying done — it was lying. A scoreboard going from 6 to 15 verified claims, with the lies it told on the left
하루짜리 정직한 감사: 앱이 내건 약속들과 실제로 증명해 낸 것들.

거짓말을 한 토스트

내가 만들고 있는 것은 新遗产传记 (뉴 레거시 바이오그래피) — 나이 든 어르신 곁에서 AI 인터뷰어가 함께 앉아, 손주가 미처 물어볼 생각도 못했던 질문들을 던지고, 그 대답을 책의 한 장(章)으로 만들어주는 앱이다. 이런 제품에서는 버그 하나가 그저 '불편함'으로 끝나지 않는다. 할머니가 자라온 강가 이야기를 소프트웨어가 잃어버린다면, 그건 다시 다운로드할 수 없는 것을 잃는 셈이다.

이 프로젝트의 공동 개발자는 AI 에이전트다. 속도만큼은 빠르다. 유닛 테스트는 전부 초록불이었다 — 135개 전부. TypeScript strict 모드도 깨끗했고, Lint 오류도 0건이었다. 그런데 내가 평범한 사용자 역할을 맡아 이메일 링크로 로그인을 시도해봤다. 화면에는 이렇게 떴다: “登录链接已发送!” — your login link has been sent, check your inbox. (번역: 로그인 링크가 발송되었습니다! 받은편지함을 확인하세요.)

이메일은 끝내 오지 않았다. 올 수가 없었다. 프로덕션 어디에도 이메일 서비스가 설정되어 있지 않았기 때문이다. 코드에는 아무도 들여다보지 않는 서버 로그에 이메일을 찍어내는 '개발용 폴백'이 있었고, 그러고는 사용자에게 성공했다고 알렸다. 우리가 믿었던 모든 게이트는 초록불이었지만, 제품은 정작 가장 중요한 그 한 사람에게 거짓말을 하고 있었다.

더 놀라운 대목은 따로 있었다. 이 제품의 핵심 기능인 '가족 초대하기' — 친척들이 생일 기념 앨범에 사진과 음성 메모를 보태는 기능 — 가 생성하는 초대 링크는 다음으로 시작했다: http://localhost:3000. 오직 내 책상 위에만 존재하는 컴퓨터를 가리키는 링크였다. 다른 도시에 사는 이모에게 그걸 공유해봐야 아무 일도 일어나지 않는다. 게다가 회원가입 페이지의 유일한 경로도 그 똑같은 유령 이메일이었다 — 즉 한동안은, 지구상 그 누구도 이 제품에 새로 가입할 수 없었다, 그런데도 모든 테스트는 계속 초록불이었다.

초록불 테스트가 우리를 구하지 못한 이유

Edsger Dijkstra는 1972년 튜링상 수상 강연에서 조용히 이 말을 남겼다: "program testing can be a very effective way to show the presence of bugs, but is hopelessly inadequate for showing their absence" (번역: 프로그램 테스트는 버그의 존재를 보여주는 데는 매우 효과적일 수 있지만, 버그의 부재를 보여주는 데는 절망적으로 부적합하다) [1]. 54년이 지난 지금, AI 보조 개발은 그의 경고를 무디게 만들기는커녕 오히려 더 날카롭게 만들었다 — 생성형 도구가 완성된 것처럼 보이는 코드를 만드는 데 지나치게 능하기 때문이다. 유닛 테스트는 함수를 테스트한다. 아무도 약속.

을 테스트하고 있지 않았다. 오랫동안 ML 제품을 만들어온 Hamel Husain은 같은 진단을 현대적인 언어로 이렇게 표현한다: "I've found that unsuccessful products almost always share a common root cause: a failure to create robust evaluation systems" (번역: 실패한 제품들은 거의 항상 공통된 근본 원인을 공유한다는 것을 발견했다: 견고한 평가 시스템을 구축하지 못했다는 것이다) [2]. 정작 훔쳐올 만한 가치가 있는 건 그다음 추론이다: 평가 시스템이 없으면, 실패 하나를 고쳐봐야 다른 실패가 그저 표면으로 드러날 뿐이라는 것 — 그는 이를 "a game of whack-a-mole" (번역: 두더지 잡기 게임)라 불렀다[2]. 딱 내가 겪은 그 한 주였다. 이메일 거짓말을 고치면 localhost 링크가 발견되고, 그 링크를 고치면 음성 입력에 배포된 적 없는 모델 파일이 필요하다는 게 드러났다. 두더지를 잡을 때마다 새로운 두더지가 튀어나왔다. 아무도 점수를 매기고 있지 않았기 때문이다.

멘탈 모델: 주장 장부

전체 아이디어는 이렇다. 이 글 없이도 내일 그대로 재현할 수 있을 만큼 단순하다. 당신의 제품이 내거는 모든 약속 — 버튼 하나하나, 내비게이션 항목 하나하나, 마케팅 문구 한 줄 한 줄 — 은 주장. 머신 체크가 없는 주장은 소문일 뿐이다. 그러니:

  1. 주장을 데이터로 적어라. 머릿속이나 문서가 아니라, 코드가 읽을 수 있는 파일 안에. 우리 파일에는 “亲友无需注册可上传照片”(등록 없이도 가족·친구가 사진을 올릴 수 있다)와 “新用户可以完成注册”(신규 사용자가 실제로 가입을 완료할 수 있다) 같은 항목이 들어 있다.
  2. 각 주장마다, 사용자가 하듯이 그것을 직접 실행해보는 체크를 하나씩 만들어라. 실제 브라우저가 프로덕션 사이트의 실제 폼을 채운다. 쿠키 없는 요청이 이모가 링크를 열어볼 법한 방식 그대로 초대 링크를 연다.
  3. 두 단계가 아니라 세 단계로 정직하게 점수를 매겨라: PASS, FAIL, 그리고 NOT MEASURED. 이 세 번째 등급이 핵심을 지탱한다 — 확인할 수 없었던 주장은 제외될 뿐, 슬그머니 초록색으로 세어지지 않는다.
  4. 그것을 게이트로 걸어라. 게이트가 걸린 주장이 실패하면 실행 자체가 화를 내며 멈춘다. 가짜 ✅보다 정직한 ❌이 낫다.
The claim ledger loop: promise becomes claim, claim gets a machine check against production, scored PASS / FAIL / NOT MEASURED, failures get fixed, the loop reruns
한 장의 그림으로 본 주장 장부. 세 번째 등급 — NOT MEASURED — 이 나머지 두 등급을 정직하게 만든다.

하루의 정직함이 만들어낸 결과

프로덕션을 상대로 한 첫 전체 실행에서 점수는 PASS 6, FAIL 11, NOT MEASURED 3이었다 — 머신 체크된 주장 20개를 대상으로. 앱이 대체로 쓰레기였기 때문이 아니라, 몇 달 동안 아무도 이 앱에 뭔가를 증명해보라고 요구한 적이 없었기 때문이다. 그날이 끝날 무렵에는: PASS 15개로 늘었고, 남은 FAIL들은 모호한 불길한 느낌 대신 이름 붙은, 범위가 명확한 해결 과제를 갖게 되었다.

입장료 값을 하는 세 가지 발견:

패턴과 안티패턴

통했던 패턴들:

우리가 대가를 치른 안티패턴들:

이 메커니즘의 정체는 이렇다: 생성형 도구는 그럴듯해 보이는 완성 — 완성된 것처럼 보이는 코드 — 를 최적화한다. 그 목표 어디에도 이메일이 실제로 존재해야 한다는 조건은 없다. 그러니 가짜-완료를 향한 압력은 도덕의 문제가 아니라 구조의 문제이고, 그 대응책 역시 구조적이어야 한다.

첫 번째 원칙은 한 문장으로 충분하다: 증거 없이는 통과도 없다.

월요일에 바로 훔쳐 써먹기

시간 제한을 두고 구체적으로 — 작업 세션 한 번이면 된다:

  1. (30분) 제품의 랜딩 페이지와 내비게이션을 열어보고, 그것이 하는 약속 20개를 있는 그대로 JSON 파일에 적는다.
  2. (2~3시간) 각 약속마다, 사용자가 하듯이 그것을 실행해보는 가장 단순무식한 체크를 작성한다 — 헤드리스 브라우저나 curl로, 스테이징이 아닌 프로덕션에, 스테이징이 아니라 프로덕션을 대상으로 검사한다. 각 주장에는 다음 표시를 단다gate: true/false.
  3. (10분) 실행한다. 팀이 볼 수 있는 곳에 정직한 표를 공개한다 — PASS, FAIL, NOT MEASURED.
  4. 성공은 숫자 하나로 측정한다: 게이트가 걸린 FAIL이 0건이고, 이 실행이 CI에 연결되어 조용히 썩어갈 수 없는 상태. 확신하던 릴리스를 막아서는 순간, 제대로 작동한다는 걸 알게 될 것이다.

우리 것은 레포 안에 있으며, 이름은eval/claims.json + eval/run.mjs — 주장 20개, 명령어 하나, 정직한 출력이다. 이 패턴은 내가 상시 규율로 지키는 OEC 루프(Observe → Evaluate → Control)의 한 적용 사례다: 관찰 가능성이 평가에 앞서고, 통제 훅이 없는 평가는 그저 스코어보드일 뿐이다.

소프트웨어에 관한 이야기가 아닌 부분

이 일이 일어난 앱은 실제로 운영 중이다: AI 인터뷰어가 표준중국어로 대화를 나누며 기억을 따라 부엌까지 들어가서 — 지난주 진행한 테스트 인터뷰 하나는 대추와 말린 용안 냄새가 나는 기억이었다 — 그것을 한 챕터로 써낸다. 여기에 실제 데이터를 바탕으로 "내 책은 얼마나 진행됐나요?"라는 질문에 답하는 동반 에이전트까지 갖췄다. 이번 주 이 앱은 어떤 기능보다 드문 것을 하나 얻었다: 자기 자신에 대해 진실을 말하는 습관이다. 언젠가는 기록해야지 하고 미뤄둔 부모님의 이야기가 있다면, 여기 있다 — 그리고 이 앱이 지금 할 수 있는 것과 아직 할 수 없는 것을 보여주는 스코어보드는 공개되어 있다. 제품을 판다면 바로 이런 방식으로 팔아야 한다고, 나는 생각한다.


참고 문헌

  1. Dijkstra, E. W. (1972). The Humble Programmer (ACM 튜링상 강연), EWD340: "program testing can be a very effective way to show the presence of bugs, but is hopelessly inadequate for showing their absence." (번역: 프로그램 테스트는 버그의 존재를 보여주는 데는 매우 효과적일 수 있지만, 버그의 부재를 보여주기에는 절망적일 만큼 부적합하다.) cs.utexas.edu/~EWD/transcriptions/EWD03xx/EWD340.html
  2. Husain, H. (2024). Your AI Product Needs Evals. "I've found that unsuccessful products almost always share a common root cause: a failure to create robust evaluation systems"(번역: 나는 실패하는 제품들이 거의 항상 하나의 공통된 근본 원인을 공유한다는 것을 발견했다: 견고한 평가 시스템을 만들지 못한 실패다); 증상에 대해서는: "Addressing one failure mode led to the emergence of others, resembling a game of whack-a-mole."(번역: 하나의 실패 양상을 해결하면 다른 것들이 나타났고, 마치 두더지 잡기 게임과 같았다.) hamel.dev/blog/posts/evals

AI-Native 시리즈 더 보기

AI-Native 시리즈의 일부다. 이 글에 등장하는 모든 내용은 공개된 평가 원장에서 그대로 재현 가능하다 — 실패 사례까지 포함해서. 증거 없이는 통과도 없다. Publish 버튼을 누르는 건 결국 당신이다.