KKC Labs포트폴리오GYMKC

AI 트레이너를,
인터넷 없이도.

세트마다 다음 무게·휴식을 정해주는 AI 헬스 트레이너. 실시간 처방은 순수 로컬 엔진으로 오프라인 100%, AI는 그 위에 코칭을 얹습니다.

유형자체 프로덕트
역할기획 · 디자인 · 개발 · 인프라 · AI
사이트gymkc.org →
스택
React 19순수 TS 엔진CapacitorCF WorkersCF D1AI
GYMKC AI 트레이너 화면. 제안마다 근거 배지가 붙어 있고, 루틴 변경 제안을 검토하는 버튼이 아래에 있다

AI 트레이너 · 제안엔 근거를 붙이고, 적용은 사용자가 정합니다

// the brief

기록하는 앱은 많다. 정해주는 앱이 없을 뿐.

헬스 앱은 넘치지만 대부분 '기록'만 합니다. 정작 세트 중에 "다음은 몇 kg으로?"를 정해주는 앱은 드물죠. 게다가 AI 앱은 인터넷이 끊기면 무용지물인데, 헬스장은 지하·구석이라 신호가 약합니다.

운동하는 그 순간에 작동하지 않는 코치는 의미가 없습니다. 그래서 목표를 하나로 잡았어요. 인터넷이 없어도, 서버가 죽어도, 다음 세트를 정확히 정해주는 트레이너.

// approach

실시간 결정은 AI가 아니라, 엔진이 한다.

휴식 시간·다음 세트 무게·횟수 같은 실시간 처방을 LLM이 아니라 순수 TypeScript 로컬 엔진으로 만들었습니다. 인터넷도 서버도 없이 폰 안에서 100% 돕니다. AI는 그 위에 코칭·분석·대화를 얹는 보조 역할이고요.

엔진이 처방의 정본, AI는 코칭 보조. 이 원칙을 처음부터 끝까지 지켰습니다. 운동 중 결정에는 LLM이 한 번도 끼어들지 않습니다.

GYMKC 분석 화면. 스트렝스와 근비대 비율, 목표 일치도, 부족한 부위 보강 추천이 순서대로 보인다

분석 화면 · 엔진이 계산한 비율과 목표 일치도를, 판단 근거와 함께 보여줍니다

// key decisions

믿을 수 있는 코치를 만든 세 가지.

01

실시간 처방 = 로컬 엔진 (오프라인 100%)

다음 세트 무게·휴식·프로그레션을 순수 TS 엔진이 계산합니다. 인터넷이 끊겨도, 서버가 죽어도 운동은 안 멈춰요. 처방 로직은 1,100개가 넘는 엔진 유닛 테스트로 고정해, 앱이 바뀌어도 결정이 흔들리지 않게 했습니다.

02

AI는 코칭 보조 (비용까지 설계)

챗·세션 분석·주간 리포트는 AI가 맡습니다. 사용자가 늘어도 원가가 터지지 않게 비용 구조를 처음부터 설계했습니다. Pro 플랜은 한도에 닿아도 분석과 챗이 간단 모드로 계속 이어지고, 실시간 처방은 애초에 AI를 쓰지 않으니 그 축은 사용량과 무관합니다.

03

엔진 한 벌로 다섯 표면 (PWA · Android · iOS · 워치 둘)

폰·태블릿·웹 화면과 처방 엔진은 한 벌이고, 워치 둘은 그 엔진의 처방을 따르는 네이티브 앱입니다. 오프라인 우선 웹을 Capacitor 껍데기로 감싸고, 플랫폼마다 필요한 것만 직접 만든 네이티브 플러그인으로 붙였습니다. 건강 데이터 쓰기·삭제, GPS 트랙, 패스키, 위젯, 워치 연동은 생태계 플러그인이 없거나 부족해 자체 구현했습니다. 안드로이드는 Health Connect에 쓰고(삼성헬스가 이를 읽어 표시합니다), iOS는 HealthKit에 씁니다. 워치는 갤럭시 워치와 Apple Watch가 기능이 같고, Apple Watch는 사각 화면에 맞춰 다시 설계했습니다.

실시간 조절도 세밀합니다. 힘든 세트 뒤에 계획보다 훨씬 짧게 쉬었다면 다음 세트를 1회 줄이자고 제안하고, 컨디션이 나쁜 날은 수행이 떨어지는 신호를 더 일찍 잡아 볼륨을 낮춥니다. 드롭세트·디로드 같은 특수 세트는 강도 지표에서 정확히 걸러내 다음 처방이 오염되지 않게 했고요. 전부 엔진이, 오프라인에서.

// offline-first

'오프라인 우선'은 구호가 아니라 설계다.

운동 기록·타이머·세트 처방이 전부 폰 안(로컬 DB + 엔진)에서 돕니다. 서버(Cloudflare D1)는 여러 기기 동기화 대상일 뿐이에요. 그래서 동시 접속이 1만 명이어도 운동 중 처방에 드는 서버 부하는 0입니다. 부하가 걸리는 건 동기화와 AI 잡뿐이고, 그건 큐로 분리했습니다.

로컬 TS 엔진Dexie 오프라인outbox 동기화다기기 LWW패스키 로그인(지문·Face ID)

// exercise library

처방만 잘해선 부족했다.

다음 세트를 정확히 정해줘도, 그 운동이 뭔지 모르면 따라 할 수 없습니다. 그래서 종목 라이브러리를 2,795종까지 넓히고, 3D 자세 시연과 근육 도해를 붙였습니다.

라이브러리 밖 종목은 온디맨드로 가져오고, 그래도 없으면 직접 만들어 쓸 수 있습니다.

검색은 한글 오타를 잡습니다. 자모 축으로 편집거리를 재는 방식이고, 허용 오타 수는 감으로 정하지 않고 측정해서 정했습니다.

그리고 이 라이브러리를 로그인 없이 볼 수 있게 /exercise 허브로 열었습니다. "로그인 없이 볼 수 있습니다"를 문구가 아니라 사실로 만든 셈이고, 검색엔진이 종목 하나하나를 읽어갑니다.

종목 2,795종3D 자세 시연자모 편집거리 오타 보정공개 SEO 허브

// trust

처방이 제품이라면, 믿게 만드는 것도 제품이다.

처방하는 앱은 왜 그 무게인지 설명할 수 있어야 합니다. 설명하지 못하면 사용자는 다음 세트에서 앱을 닫습니다. 그래서 설계의 한 축을 처방을 믿게 만드는 일에 썼습니다.

휴식 처방 옆에 이유를 답니다. 이유 문장은 엔진이 그 휴식을 정한 원인을 그대로 옮깁니다. 엔진이 계산한 값 옆에 그 값이 나온 이유가 붙으니, 오프라인 로컬 엔진이라는 결정이 사용자 눈에도 보입니다.

운동 이름은 정식 명칭을 기본으로 보여 주고, 길게 누르면 초보자가 알아들을 우리말로 바뀝니다.

처방 풀에 넣을 종목은 근거로 가르고, 그 기준이 흔들리면 배포가 막힙니다. 루틴의 칸을 채울 때도 트레이닝 전문가 출처가 그 종목을 직접 다룬 수 같은 근거를 봅니다.

어젯밤 수면을 준비도 신호로 읽습니다. 남의 몸 데이터를 다루는 앱이라 권한도 코드로 지킵니다. 요청하는 권한과 실제 코드가 어긋나지 않도록 자동 검사가 매 배포 전에 대조하고, 쓰지 않는 건강 권한은 요청하지 않습니다.

좌우 차이는 관측해서 알려 줍니다. 굳어진 차이에는 무게를 깎는 대신, 설정에서 켠 상급자에게 약한 쪽부터 하는 순서를 제안합니다. 초보자에게는 좌우를 따로 재는 확인 세트를 내지 않습니다.

임신·산후 모드가 따로 있습니다. 해당될 수 있는 사용자에게만 묻고, 켜면 처방을 보수적으로 낮추고 종목별로 제외합니다.

아픈 곳은 위치와 그 운동이 거는 힘을 함께 보고 제외·교체·주의로 나눕니다. 무릎 앞이 아프면 점프는 빼도 스쿼트·런지는 남깁니다. 주의로 남긴 운동은 세트 뒤에 아팠는지 묻고, 조금 아팠으면 무게를 올리지 않고 많이 아팠으면 한 칸 가볍게 내립니다. 진료가 먼저인 증상이 있다고 답하면 남겨 둔 운동까지 모두 뺍니다.

처방 근거 표시휴식 이유 문장초보자용 우리말 이름근거 기준 종목 선정수면 준비도 신호권한 선언·코드 대조 게이트좌우 차이는 관측만통증 판정: 위치 × 걸리는 힘

// result

지금도 굴러가는, 상용 준비된 앱.

Google Play 와 App Store 에 둘 다 출시했습니다(iOS 는 2026-09-12 공개). 워치 앱은 각 스토어 제출본에 함께 들어 있습니다. 웹에서도 정기결제를 붙였고 체험은 양쪽 모두 14일입니다. 온보딩에서는 장비와 아픈 곳을 그림으로 고릅니다.

검증은 빌드가 통과했다고 끝나지 않습니다. 자동 게이트 146종과 자동 테스트 9,300여 개를 두고, 웹 7개 뷰포트와 Android·iOS·Wear·watchOS 각 표면에 검증 도장이 없으면 배포가 막힙니다. 갤럭시 S25와 아이폰·아이패드 실기기로도 반복 확인합니다.

100%
오프라인 처방 (인터넷 불필요)
1,100개 이상
엔진 처방 유닛 테스트
2층
로컬 엔진 + AI 코칭 구조
← 이전QUICKCUT

// 이런 걸 만들어 드립니다.