---
slug: mobile-app
title: "모바일 엔지니어 한 명 없이 Raft 모바일 앱을 만든 방법"
language: ko
canonicalUrl: https://raft.build/ko/resources/raft-on-raft/mobile-app/
datePublished: 2026-11-09
dateModified: 2026-11-09
---

> 출처: Raft on Raft, raft.build · https://raft.build/ko/resources/raft-on-raft/mobile-app/

11월 2일 오후 4시 25분, 저희 피드백 시스템에 티켓이 몇 건 들어왔어요. TestFlight 외부 테스터 한 분이 특정 대화를 열기만 하면 Raft iOS 버전이 강제 종료된다는 걸 발견했는데, 7초 사이에 두 번이나 꺼졌어요.

30분 뒤, Lagoon이 채널에서 수정이 들어간 새 빌드를 외부 테스터에게 배포할지 저에게 물었어요.

스레드의 메시지를 위로 올려 보니, 티켓 분류를 맡은 Agent가 이 신고를 진작에 모바일 "담당자"인 Lagoon에게 넘겨 둔 상태였어요(Lagoon은 "사람"이 아니라 Agent예요). Lagoon은 원인을 금방 찾아냈어요. 앱에서 LaTeX 수식을 렌더링하는 라이브러리에 폰트가 누수되는 버그가 있어서, 메시지에 수식이 많아지면 메모리가 바닥났던 거예요. **제가 이 메시지를 봤을 때는 이미 문제가 고쳐져 있었어요.** 사용자 기기에 아직 예전 테스트 빌드가 설치돼 있었을 뿐이에요.

오후 4시 57분, 저는 한마디로 답했어요. "배포해." 3분 뒤, 새 빌드가 외부 테스터들에게 전달됐어요.

티켓이 들어와서 수정이 나가기까지, 전체 과정에서 사람이 해야 했던 일은 제가 보낸 이 한마디뿐이었어요. 이게 저희 모바일 개발의 일상이에요. 모바일 팀 전체에서 사람은 저 한 명뿐이에요. 이게 어떻게 가능한지 지금부터 이야기해 볼게요.

## 1. 팀은 어떻게 꾸렸나요

앱 코드는 오픈 소스([botiverse/mobile](https://github.com/botiverse/mobile))이고, 이 글에 나오는 테스트와 규칙은 모두 저장소에서 찾을 수 있어요. 먼저 코드 이야기부터 할게요. 앱은 [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html)과 [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/)으로 작성해서, 코드베이스 하나로 Android와 iOS 버전을 빌드해요. 문제 하나를 한 번만 고치면 모든 플랫폼에 반영된다는 게 장점이에요. 앞에서 말한 수식 크래시도 이렇게 고쳤어요.

조직 면에서는, 새로운 문제가 생길 때마다 Agent를 하나 더 두고 한 영역을 독립적으로 맡겼어요. 아래 그림과 같아요.

- **10월 18일 이전**: 린저우(林舟)가 주로 릴리스를 맡았어요.
- **10월 18일**: Lagoon이 합류해서 Tenny(Agent CTO)가 맡긴 첫 일을 받았어요. 웹 버전과 앱을 기능별로 하나하나 비교하는 일이었어요. 같은 날, 피드백 분류를 맡은 Agent DiRenJie도 합류해서, 매일 Android와 iOS 피드백을 유형별로 나누고 중복을 없앤 뒤 해당 담당자에게 넘기고 있어요.
- **10월 19일**: Lagoon이 앱에 빠진 컴포넌트 목록을 만들어, 공용 컴포넌트를 전담하는 Agent에게 넘겼어요.
- **10월 24일**: 디자인 시스템 Agent가 합류해서, 화면이 제대로 그려졌는지 판단하는 일을 맡았어요.
- **10월 26일**: iOS 검수 Agent가 합류해서, 시뮬레이터에서 직접 눌러 보고 문제를 재현하는 일을 맡았어요.
- **10월 31일**: 릴리스에 필요한 권한은 모두 린저우가 있는 별도의 서버에 있었는데, 서버를 넘나들며 확인을 주고받기에는 너무 느렸어요. 그래서 릴리스도 Lagoon이 맡게 됐고, 인수인계 문서에 따라 공동 채널에서 인수인계를 마쳤어요.

## 2. 개발과 테스트: 코드를 한 줄씩 보는 대신 증거를 봐요

개발과 테스트는 원래 별개의 일이었어요. 하지만 Agent 시대가 되면서 코드는 거의 다 Agent가 쓰고 사람이 직접 손대는 일은 드물어졌고, 그만큼 테스트의 비중도 커졌어요. 이 관점은 예전에 "[신뢰는 Code Review 안에 있지 않다](/ko/resources/blog/trust-doesnt-live-in-the-code-review/)"라는 글에서 이야기한 적이 있어요. 코드 대부분을 Agent가 쓰고 사람이 더 이상 한 줄씩 리뷰하지 않게 되면, 신뢰를 더 이상 코드 리뷰에 기댈 수 없어요. 사람이 시스템의 동작을 예측할 수 있게 해 주는 것에 기대야 해요. 예를 들면 검증을 견뎌 내는 테스트 스위트, 그리고 버그를 고칠 때 먼저 그 버그를 겨냥한 테스트가 빨간색에서 초록색으로 바뀌게 하는 것 같은 거예요.

모바일이 딱 그런 상황이에요. 일주일에 300개가 넘는 변경이 코드베이스에 머지되는데, 거의 전부 Agent가 Stamp로 올린 거예요. (Stamp는 저희 코드 도구예요. Agent는 Stamp에서 자기 계정으로 코드를 푸시하고, PR을 열고, 리뷰하고, 머지해요. 승인은 승인한 시점의 코드 버전에만 유효해서, 코드가 또 바뀌면 다시 승인받아야 해요.) 이렇게 많은 변경을 제가 한 줄씩 볼 수는 없어요. 제가 안심할 수 있는 건 아래 세 겹의 증거 덕분이에요.

### 1. 스크린샷 비교: 기준 이미지와 비교하고, 웹 버전과도 비교해요

UI를 바꾸는 PR에는 모두 "스크린샷 베이스라인"이 한 세트씩 들어 있어요. CI가 컴포넌트를 라이트 모드, 다크 모드, 좁은 화면, 넓은 화면으로 한 번씩 그려서 기준 이미지로 저장해요. 그 뒤로 누구의 변경이든 화면을 달라지게 하면 비교가 실패하고, 작성자는 이 변화가 의도한 것이라고 설명한 뒤 기준 이미지를 다시 기록해야 해요.

앱 스크린샷을 웹 버전과 픽셀 단위로 비교하기도 하는데, [비교할 때마다 나오는 보고서는 공개돼 있어요](https://botiverse.github.io/mobile/visual-testing/). 웹 버전이 저희 디자인 기준이라서, 앱은 최대한 웹 버전과 똑같아 보여야 해요. 처음 체계적으로 비교했을 때는 픽셀 단위 평균 유사도가 68.8%밖에 안 됐는데, 가장 최근에 화면 85개를 비교했을 때는 평균 87.6%까지 올라왔어요. 아직 차이가 큰 화면은 하나씩 저에게 올라오고, 제가 최종 결정을 내려요.

작은 예를 하나 들어 볼게요. 한번은 사이드바의 읽지 않음 빨간 점이 웹 버전과 맞지 않는 걸 발견했어요. 알아보니 빨간 점의 위치를 잡는 기준 요소가 잘못돼 있었어요. 웹 버전은 빨간 점을 아이콘의 오른쪽 위 모서리에 붙이는데, 앱은 버튼 사각형 전체의 모서리에 붙여서, 빨간 점이 두 버튼 사이에 떠 있었던 거예요. 고친 뒤에는 사이드바의 스크린샷 베이스라인 6장(라이트, 다크, 컴팩트 모드 등)을 모두 다시 기록했어요. 이전 베이스라인과 새 베이스라인을 비교해 보면 차이가 바로 보여요.

![사이드바의 읽지 않음 빨간 점: 고치기 전에는 두 버튼 사이에 떠 있고, 고친 뒤에는 아이콘 오른쪽 위에 붙어 있어요](/raft-on-raft/mobile-app/ko/rail-before-after.png)

### 2. 가상 휴대폰이 앱을 직접 써 보게 해요

스크린샷 말고도 CI에는 흔치 않은 테스트가 하나 더 있어요. 클라우드에 가상 Android 휴대폰(Android 에뮬레이터)을 띄우고, 앱을 설치한 다음, 사람처럼 누르고 타이핑하고 나서 화면의 픽셀을 측정해요.

!["첫 글자 입력" 테스트: @ 기호를 입력하면 추천 창이 뜨고, 창을 닫은 뒤 입력창 위쪽이 깨끗한지 확인해요. 스크린샷은 testbed 클라우드 휴대폰에서 찍었고, CI에서는 같은 동작이 Android 에뮬레이터에서 자동으로 실행돼요.](/raft-on-raft/mobile-app/ko/virtual-phone-mention.png)

예를 들어 "첫 글자 입력"이라는 테스트 묶음이 있어요. 입력창에 @ 기호를 하나 치면, 앱은 "누구를 @할까요?" 하는 추천 창을 띄워요. 추천 창을 닫고 나면, 테스트는 입력창 위쪽 영역이 정말 깨끗해졌는지, 잔상이나 미처 닫히지 않은 팝업이 남아 있지 않은지 확인해요.

일반적인 단위 테스트는 함수의 반환값이 맞는지만 확인해요. **이 테스트는 사용자가 최종적으로 보는 화면이 맞는지를 확인해요.**

CI에서 돌아가는 건 에뮬레이터예요. 실제 기기로 확인해야 하는 문제가 생기면, Agent는 testbed에서 클라우드 휴대폰을 한 대 빌려요. testbed는 저희 내부의 클라우드 실기기 플랫폼이에요. 실제 Android 휴대폰이 있어서, Agent가 원격으로 빌드를 설치하고, 조작하고, 스크린샷을 찍으면서 문제를 재현하고 검증할 수 있어요.

### 3. 레드-그린 검증: 테스트 자체를 믿을 수 있다는 걸 증명해요

세 번째 겹의 증거가 가장 흥미로워요. "테스트를 통과했다"는 말만으로는 부족해요. 테스트 자체도 고장 날 수 있으니까요. 그래서 두 가지를 증명하라고 요구해요. **수정을 빼면 테스트가 빨간색이 되고, 수정을 넣으면 테스트가 초록색이 돼요.**

10월 30일, 앞에서 말한 "첫 글자 입력" 테스트가 갑자기 모든 PR에서 실패했어요.

저희는 이걸 간헐적인 문제로 치고 그냥 다시 돌리는 대신, 먼저 main의 커밋 기록에서 이진 탐색을 했어요. 가운데 커밋 하나에서 테스트를 돌려 결과가 좋은지 나쁜지 보고, 문제가 있는 쪽 절반에서 다시 찾는 식이에요. 몇 번 반복하자 변경 하나로 좁혀졌어요. 그 변경은 입력창 너비를 321dp에서 302dp로 줄였어요(dp는 Android의 길이 단위로, 대략 화면의 점 하나에 해당해요). 그 바람에 테스트가 샘플링하는 영역에 배경색 띠가 하나 더 들어가게 됐어요.

이제 숫자를 볼게요. 이 테스트의 규칙은 그 영역에서 기대한 대로 나온 픽셀이 92% 이상이어야 한다는 거예요(비율 ≥ 0.92). 사실 앱 자체는 멀쩡했고, 테스트가 재는 위치가 어긋나 있었어요. 아무것도 바꾸지 않고 두 번 돌렸을 때 결과는 둘 다 0.9136으로 똑같았어요. 간헐적인 문제가 아니라 매번 똑같이 일어나는 문제라는 뜻이에요. 테스트의 샘플링 영역만 조정하자 비율이 0.9777이 됐어요.

이 숫자들로 몇 가지를 알 수 있어요. 0.9136은 "빨간색"이고, 이 테스트가 실제로 문제를 잡아낸다는 걸 증명해요. 0.9777은 "초록색"이고, 수정이 문제를 정확히 겨냥했다는 걸 증명해요. 두 번의 0.9136이 완전히 같으니, 운이 작용했을 가능성도 배제돼요. 이번 일로 두 가지를 기억하게 됐어요. 첫째, 테스트 자체도 고장 날 수 있어요. 그날 앱 코드는 한 줄도 틀리지 않았고, 틀린 건 테스트였어요. 둘째, 테스트가 빨간색이 됐다고 간헐적인 문제로 치고 바로 다시 돌리면 안 돼요. 먼저 매번 빨간색인지, 숫자가 매번 같은지 봐야 해요. 매번 똑같이 빨간색이라면 반드시 원인이 있어요.

!["첫 글자 입력" 테스트의 비율이에요. 0.92 이상이면 통과예요. 두 번의 0.9136이 완전히 같으니 간헐적인 문제가 아니고, 빨간색에서 초록색으로 바뀌었으니 수정이 문제를 정확히 겨냥한 거예요.](/raft-on-raft/mobile-app/ko/red-green-ratio.png)

이 아이디어 자체는 새로운 게 아니에요. 테스트 주도 개발의 "레드-그린"과 같은 이야기예요. 저희가 한 일은 이걸 모든 PR의 요건으로 적어 둔 거예요. Agent는 자기 PR을 스스로 승인할 수 있지만, 같은 리뷰 증거 안에 어떤 테스트를 돌렸는지 밝히고, 전후 스크린샷을 붙이고, "역방향 검증" 칸을 채워야 해요. 수정을 빼면 어떤 테스트가 빨간색이 되는지, 어떤 모습으로 빨간색이 되는지 적는 칸이에요. API 계약이나 권한을 바꾸는 변경, 또는 요청량을 눈에 띄게 늘리는 변경은 스스로 승인할 수 없고, 반드시 Tenny에게 리뷰를 받아야 해요. 저는 코드를 읽지 않아요. 이 칸만 보면 이 수정에 대응하는 테스트가 있는지 판단할 수 있어요.

## 3. 프로세스 자동화

### 1. 매일 낮 12시, 앱을 웹 버전에 맞춰요

앱은 웹 버전과 똑같이 유지해야 하는데, 웹 버전은 매일 새로 릴리스돼요. 그래서 모바일 담당자 Lagoon은 매일 베이징 시간 낮 12시에 울리는 리마인더를 스스로 설정했어요. 시간이 되면 웹 버전의 최신 정식 릴리스와 앱을 한 번 대조해 보고, 앱이 따라가야 할 작업을 정리해서 우선순위대로 채널에 올린 다음, 하나씩 처리해요.

### 2. 크래시: 보고부터 수정까지

처음에 이야기한 일을 하나씩 뜯어 보면, 사실 정해진 파이프라인이에요.

1. **보고.** 앱에 크래시가 나거나 사용자가 "피드백"을 누르면, Hands가 티켓을 자동으로 받아요. 티켓에는 앱이 직접 묶어 보낸 진단 로그도 함께 붙어 있어요.
2. **분류.** 피드백 분류 Agent가 매일 Hands에서 Android와 iOS 티켓을 가져와 유형을 나누고, 심각도를 정하고, 중복을 없앤 다음 해당 담당자에게 넘겨요.
3. **Symbolication(심볼리케이션).** 해당 빌드의 심볼 파일로 크래시 주소를 함수 이름과 코드 줄로 바꿔요. 크래시 기록에는 메모리 주소만 있어서 사람이 읽을 수 없어요. 심볼리케이션을 거쳐야 어느 줄에서 크래시가 났는지 알 수 있어요.
4. **하나뿐인 근본 원인 찾기.** 그날 들어온 티켓 세 건 중 두 건은 바이트 단위까지 똑같았어요. 같은 크래시가 두 번 보고된 거예요. 세 번째 건은 7초 뒤에 앱을 다시 열었다가 또 크래시가 난 거였어요. 근본 원인은 폰트 누수 하나뿐이었어요.
5. **수정이 어느 빌드에 들어 있는지 확인.** 이 단계는 기억에 기대지 않고, 빌드에 쓰인 커밋을 하나하나 대조해요. 분류 Agent가 한번은 이걸 바로잡기도 했어요. 1.18.0의 6번째 빌드에는 그보다 나중에 머지된 다른 수정이 들어 있지 않으니, 그렇게 적으면 안 된다고요.
6. **사용자에게 전달.** Lagoon은 먼저 저에게 묻고, 제가 동의하면 Hands로 빌드를 TestFlight 외부 그룹에 배포해요. 티켓은 바로 닫지 않아요. 실제 사용자가 한동안 써 보고 같은 종류의 크래시가 다시 나오지 않아야 정말 고쳐진 걸로 봐요.

![크래시 보고부터 수정까지 6단계 중 사람이 필요한 건 마지막 단계뿐이에요.](/raft-on-raft/mobile-app/ko/crash-pipeline.png)

### 3. 릴리스 과정 자동화

버전 번호를 어떻게 올릴지는 나중에 제 요청으로 [저장소에 적어 뒀어요](https://github.com/botiverse/mobile/blob/main/.agents/skills/raft-release-versioning/SKILL.md). Agent는 매번 릴리스하기 전에 이 규칙에 따라 스스로 판단해요.

- **minor(1.X.0)**: 새 기능이 있을 때 올려요. 일주일에 한 번, main에서 테스트가 모두 통과하고 내부 빌드가 스모크 테스트를 통과한 뒤에 릴리스 브랜치를 따요.
- **patch(1.X.Y)**: 크래시, 데이터 유실, 이전 버전보다 나빠진 부분처럼 이미 배포된 버전의 문제만 고쳐요. 수정은 먼저 main에 들어가고, 그다음 릴리스 브랜치로 체리픽해요. 새 기능은 끼워 넣지 않아요.
- **build(빌드 번호만 바꿈)**: 사용자가 변화를 느낄 수 없는 경우예요. 다시 빌드하거나 심사에 다시 제출할 때처럼요.

또 하나의 방식은 변경 로그예요. 버전마다 저장소에 자기 폴더가 있어요. [1.20.0](https://github.com/botiverse/mobile/tree/v1.20.0/change-logs/1.20.0/changesets)처럼요. 어떤 변경이 이 버전에 들어가야 하면, 그 변경을 만든 Agent가 머지할 때 폴더에 작은 파일을 하나 넣어요. 무엇을 바꿨는지, 어느 부분에 속하는지, 어느 플랫폼에 들어가는지를 중국어와 영어로 각각 한두 문장씩 적어요. 사용자가 볼 수 있는 변화만 적고, 코드 세부 사항은 적지 않아요. 지금 1.20.0 폴더에는 이런 파일이 57개 있어요.

릴리스 당일, 릴리스를 맡은 Agent는 커밋 기록을 뒤지지 않고 이 폴더만 봐요. 작은 파일들을 모아 Android와 iOS 릴리스 노트를 각각 만들고, 배포 채널마다 요구 사항에 맞춰 다시 써요. 예를 들어 Google Play는 500자 제한이 있고, TestFlight에는 "이번에 테스트할 내용"을 적어야 해요. 만든 빌드는 먼저 내부 테스트에만 배포하고, 제가 설치해서 써 보고 OK해야 외부 테스터와 스토어에 배포해요.

이렇게 하면 변경 로그가 변경 하나하나와 함께 조금씩 쌓여요. 릴리스할 때 이번 버전에서 대체 뭐가 바뀌었는지 되짚어 볼 필요도 없고, 어떤 Agent가 한 일이 빠질 일도 없어요.

### 4. 릴리스, 모니터링, 티켓 모두 Hands에서

지금까지 이야기한 일은 실제로 거의 다 Hands를 거쳐요.

- **릴리스**: CI가 빌드한 Android와 iOS 패키지는 Hands에 올라가요. Android 인앱 업데이트는 Hands에서 바로 배포해요. Google Play 정식 버전으로 올리기, App Store 심사 제출(이름, 부제, 키워드, 릴리스 노트 입력, 빌드 선택, 사전 점검, 제출), TestFlight 외부 그룹 배포도 모두 Agent가 Hands 명령으로 처리해요. 사람이 Play Console이나 App Store Connect를 열 필요가 없어요.
- **모니터링**: 크래시와 ANR은 모두 Hands로 들어와요. CI가 mapping 파일과 dSYM도 함께 올리기 때문에 크래시 기록은 자동으로 심볼리케이션돼요. TestFlight 테스터가 보고한 크래시도 Hands에서 가져와요.
- **티켓**: 앱의 "피드백"은 Hands 티켓으로 들어가고, 진단 로그와 스크린샷이 함께 붙어요. Agent는 티켓을 직접 읽고 원인을 찾아요. 처음에 이야기한 수식 크래시도 이렇게 들어왔어요.

빌드를 넘긴 뒤에도 스토어 쪽 결과를 사람이 지켜볼 필요가 없어요. Hands가 Google Play, App Store, TestFlight의 상태 변화를 [저희 릴리스 채널에 올려 줘요](https://hands.build/docs/store-release-notifications/). 제출 완료, 심사 대기, 심사 통과, 출시, 거절까지 단계마다 메시지가 하나씩 와요. App Store와 TestFlight는 변화가 생기는 즉시 오고, Google Play는 Hands가 5분마다 확인해요. Agent는 메시지를 보고 바로 다음 일을 이어 가니, 스스로 주기적으로 확인하러 갈 필요가 없어요. 예를 들어 1.22.0 Android 버전은 Lagoon이 오후 3시 38분에 Google Play 정식 버전으로 올리자 채널에 바로 "Release submitted"가 왔어요. 오후 4시 10분에 Google 심사를 통과하자 "Available on track"이 왔고, Lagoon은 그걸 보고 이 버전을 기다리던 사람과 Agent에게 알려 줬어요.

![릴리스 채널에 Hands가 올린 Google Play 상태: 먼저 "Release submitted", 심사를 통과한 뒤 "Available on track".](/raft-on-raft/mobile-app/ko/store-notifications.png)

## 4. 사람은 어디에서 관문을 지키나요

제가 직접 결정하는 곳은 세 군데뿐이에요. 나머지는 모두 테스트와 증거에 맡겨요.

**첫째, 배포할지 말지.** Hands 정식 채널, Google Play 테스트 버전과 정식 버전, TestFlight 외부 그룹, App Store까지, 외부로 나가는 모든 배포는 그 버전 하나하나에 대해 제가 OK해야 해요. 내부 배포 채널은 저에게 묻지 않아도 돼요. 처음에 나온 "배포해"가 바로 그 예예요. 제가 OK하기 전에, 그 빌드는 먼저 스모크 테스트를 통과해야 하고, 무엇을 테스트했고 결과가 어땠는지 항목마다 기록해야 해요. "잘 돼요" 한 줄만 적으면 인정되지 않아요.

**둘째, 제대로 만들었는지.** 제가 시간을 가장 많이 쓰는 부분이에요. Agent가 기능을 다 만들었다고 하면, 정말 쓸 수 있는지 확인해야 해요. 생각한 단계대로 끝까지 진행되고, 엣지 케이스에서도 문제가 없어야 해요. 그래서 Agent는 작업을 넘길 때 화면 녹화를 붙여서 핵심 동작을 처음부터 끝까지 보여 줘요. 녹화로 알 수 없는 건 testbed에서 검증 세션을 열고 저에게 링크를 보내요. 그러면 저는 브라우저에서 그 클라우드 휴대폰을 직접 조작해서 손수 눌러 봐요. 제가 빌드를 설치할 필요도 없어요. 화면이 제대로 보이는지도 테스트로 만들기 어려운 부분이라, 이것도 제가 스크린샷과 녹화를 보고 판단해요.

![testbed 검증 세션: 사람이 브라우저에서 클라우드에 있는 실제 Android 휴대폰을 직접 조작해요.](/raft-on-raft/mobile-app/ko/testbed-verify.png)

가끔은 화면을 보면 그냥 뭔가 이상한데, 어디가 이상한지는 말로 설명하기 어려울 때가 있어요. 그럴 때는 "왜 이렇게 해야 하지?"로 돌아가서 생각해요. 한번은 Mac에서 App Store의 iPad 버전을 쓰다가, 태블릿에서와 모양이 다르다는 느낌을 받았어요. 알아보니 Compose 버전은 마우스가 감지되기만 하면 "데스크톱 크기"로 바뀌어서, 글자와 버튼이 모두 작아지는 거였어요. 이 규칙은 좀 이상하다고 느꼈어요. 글자 크기가 왜 마우스와 엮여 있어야 할까요? 그래서 처음부터 하나씩 뜯어서 생각해 봤어요.

- **글자 크기**는 화면이 얼마나 큰지, 사람이 화면에서 얼마나 떨어져 있는지에 달려 있어요. 그래서 창 너비만 봐요. 너비가 744 이상이면 태블릿과 Mac이 같은 대화면용 글자 크기를 써요.
- **버튼 크기**는 손가락으로 쓰는지 포인터로 쓰는지에 달려 있어요. 손가락으로 조작하면 최소 44는 돼야 하고, 트랙패드라면 32까지 줄여도 돼요. 입력 방식을 봐야 하는 건 이것 하나뿐이에요.

![둘 다 너비 1024: 글자 크기는 창 너비를 따르고, 버튼 크기는 입력 방식을 따라요](/raft-on-raft/mobile-app/ko/scale-compare.png)

**셋째, 아키텍처 방향.** 큰 방향은 제가 정해요. 예를 들어 앱에서 고를 수 있는 런타임은 원래 클라이언트에 하드코딩돼 있어서, 웹 버전에 런타임이 하나 추가될 때마다 앱도 새 버전을 릴리스해야 했어요. 저는 한마디 물었어요. 이미 스키마로 돼 있는데, 왜 백엔드가 관리하면 안 될까요? 그렇게 방향이 정해졌어요. 백엔드가 서버마다 런타임 목록을 내려 주고, 앱은 받은 대로 보여 주는 거예요. 요청 한도, 권한, API 계약 같은 더 세세한 트레이드오프는 Agent CTO인 Tenny가 리뷰해요. 코드를 쓰는 Agent가 혼자 결정할 수는 없어요.

제 주의력은 이 세 곳에만 써요.

## 이렇게 일하고 싶은 팀을 위한 제안 3가지

1. **증거부터 갖추고, 그다음에 권한을 넘기세요.** 에뮬레이터 테스트, 스크린샷 베이스라인, 레드-그린 검증을 먼저 갖춰야 Agent가 스스로 PR을 승인하게 할 수 있어요. 이런 증거 없이 권한부터 넘기면, 문제가 생겨도 알아차리기 어려워요.
2. **사람은 판단만 하고, 단순 노동은 맡지 마세요.** 저는 코드를 읽지 않고, 빌드도 만들지 않고, 티켓도 분류하지 않아요. 배포할지 말지, 제대로 만들었는지, 아키텍처 방향만 챙겨요. 이런 팀에서는 사람의 주의력이 가장 부족한 자원이니, Agent가 대신할 수 없는 곳에 써야 해요.
3. **한 번 빠진 함정은 모두 정해진 방식으로 굳혀 두세요.** 테스트가 엉뚱한 곳을 재고 있었을 때, 저희는 역방향 검증을 추가했어요. 이런 방식은 저장소와 메모리 파일에 적혀 있어서, 다음 Agent가 이어받아도 같은 실수를 반복하지 않아요.

## 이 글에 나온 기능

- **[스레드](https://docs.raft.build/features/messaging/channels/#your-channels)**: 메시지 하나에 달린 하위 대화예요. 팔로우한 사람과 Agent만 답글을 받아서, 채널 자체는 깔끔하게 유지돼요.
- **[서버](https://docs.raft.build/features/server/)**: Raft에서 팀이 일하는 공간이에요. 서버의 사람, Agent, 채널, 작업은 모두 그 서버에 속하고, 한 사람이 여러 서버에 참여할 수 있어요.
- **[공동 채널](https://docs.raft.build/features/messaging/joint-channels/)**: 여러 Raft 서버가 함께 쓰는 채널 하나예요. 자기 Raft 서버가 있는 고객이나 파트너와 협업할 때 써요. 양쪽 모두 상대 서버에 참여할 필요가 없고, 상대의 다른 채널도 볼 수 없어요.
- **[Stamp](https://stamp.build/)**: Agent가 코드 작업에 쓰는 Git 게이트웨이예요. 각 Agent가 사람의 GitHub 계정을 빌리지 않고 자기 계정으로 코드를 푸시하고, PR을 열고, 리뷰하고, 머지해요. 승인은 승인한 시점의 코드 버전에만 유효해요.
- **[스크린샷 비교 보고서](https://botiverse.github.io/mobile/visual-testing/)**: 비교할 때마다 나오는 공개 보고서예요. 화면마다 앱 스크린샷과 웹 버전을 나란히 놓고 유사도와 차이를 표시해 두어서, 누구나 열어 볼 수 있어요.
- **[testbed](undefined)**: 저희 내부의 클라우드 실기기 플랫폼이에요. 실제 Android 휴대폰이 있어서, Agent가 원격으로 빌드를 설치하고 조작하고 스크린샷을 찍을 수 있어요. 검증 세션을 열어 사람에게 링크를 보낼 수도 있고, 그러면 사람은 브라우저에서 그 휴대폰을 직접 조작해요.
- **[리마인더](https://docs.raft.build/features/agents/reminders/)**: Agent가 스스로 맞춰 두는 알람이에요. 시간이 되면 그 Agent만 깨우고, Agent는 원래 메시지나 스레드로 돌아가 일을 이어 가요. 한 번만 울리게 할 수도, 주기적으로 반복하게 할 수도 있어요.
- **[Hands](https://hands.build/)**: 저희가 직접 만든 릴리스·피드백 플랫폼이에요. Agent는 Hands로 앱을 빌드하고 각 배포 채널에 빌드를 배포해요. 사용자 피드백과 크래시 리포트도 티켓으로 여기에 들어와요.
- **[메모리](https://docs.raft.build/features/agents/#memory)**: Agent는 여러분의 선호, 내린 결정, 진행 중인 작업을 자기 컴퓨터의 노트에 적어 두고, 새로 시작할 때마다 먼저 읽어요. 기억했으면 하는 게 있으면 그냥 말해 주세요.


