인간관계를 자동화 해보자

카톡 답장은 항상 어렵습니다. 사회성을 기르는 것과 외주를 맡기는 것 중 뭐가 빠를지 고민하다 후자로 기울었습니다. 그래서 에이전트 하나를 제 카톡 앞에 앉혀봤습니다.

2026년에 에이전트를 쓴다는 건 LLM을 API로 호출하는 것과는 다릅니다. Claude Code 같은 에이전트 런타임 위에 에이전트를 배치하는 일에 가깝습니다. 호출 타이밍과 컨텍스트 경계, 인간 개입 지점과 런타임 수명을 설계하는 일이 본체이고, 답장 문장을 생성하는 건 그 뒤에 자연스럽게 따라오는 결과입니다.

이 글은 auto-kakaotalk 이라는 오픈소스 소프트웨어(스킬)를 만들면서 고민한 결정들을 기록합니다. 제 카톡을 대신 해주는 에이전트를 만들었고, 그 에이전트가 카톡 앞에 앉아 말투를 익히고 대신 답장합니다.

런타임을 먼저 정한다

에이전트 시스템 설계에서 첫 질문은 "뭘 하게 할 것인가" 가 아니라 "어디서 돌릴 것인가" 입니다.

선택지는 세 가지였습니다. Python 데몬에 claude -p subprocess를 붙이는 방식, launchd plist로 OS 레벨 스케줄러를 쓰는 방식, Claude Code 세션 안에서 그대로 돌리는 방식. 직관적으로는 첫 번째나 두 번째가 맞아 보입니다. "데몬"이라는 단어가 익숙하기 때문입니다.

그런데 이 도구가 하려는 일은 "24/7 백그라운드 서비스"가 아닙니다. 사용자가 카톡을 안 보고 있는 시간에 대신 답장해주는 일입니다. 그러면 데몬을 띄우는 순간 문제가 하나 생깁니다. 사용자가 안 보는 시간에 엉뚱한 메시지가 나가는 사고를 막을 구조적 장치가 없다는 점입니다. 로그를 보거나 알림을 달아야 하고, 결국 안전을 운영 부담으로 치환하는 설계가 됩니다.

Claude Code 세션이 런타임이면 이 문제가 사라집니다. 창이 닫혀 있으면 아무 일도 일어나지 않고, 창이 열려 있을 때만 에이전트가 돕니다. "끄는 방법"이 곧 "창 닫기"라 따로 문서화할 필요도 없습니다. 안전이 기능이 아니라 런타임의 위상에서 나오는 구조인 셈이라, 이걸 위상적 안전이라고 부르기로 했습니다. 기능으로 막는 안전은 결국 운영 부담으로 돌아오는데, 위상으로 막는 안전은 시스템이 애초에 돌아가지 못하는 것 자체가 보장이 됩니다.

이 결정에 따라 이후의 설계도 함께 정해졌습니다.

CronCreate 로 에이전트를 깨운다

세션이 런타임이면 그 안에서 주기적으로 뭔가를 돌리는 방법이 필요합니다. Claude Code는 CronCreate라는 툴을 제공합니다. 세션이 살아있는 동안만 유효한 in-memory 스케줄러입니다. 표준 cron 표현을 받고, REPL이 idle일 때만 발화합니다. 사용자와 대화하는 중이면 조용히 기다리고, 세션을 닫으면 같이 사라집니다.

이 특성이 이 도메인에 정확히 맞았습니다.

CronCreate("*/3 * * * *", "auto-kakaotalk tick — cycle.sh poll → draft → send")

이 한 줄이 전통적인 설계라면 launchd plist, 데몬 프로세스, 헬스체크, 종료 핸들러 조합이 해야 할 일을 대신합니다. 별도 데몬 바이너리나 재시작 스크립트가 필요 없고, 규칙을 수정할 때도 마크다운 한 줄만 고치면 끝납니다.

써보고 나서야 알았습니다. 오늘날 에이전트 설계에서 중요한 건 코드를 호출하는 방식보다 런타임을 어떻게 활용하는가입니다. idle 발화와 세션 수명이 곧 스케줄 수명이 되는 특성을 런타임이 직접 제공하다 보니, 별도의 데몬 아키텍처가 필요 없어졌습니다.

Skill 을 얇게 쓴다

Claude Code의 스킬은 ~/.claude/skills/<name>/SKILL.md라는 문서 하나에서 시작합니다. 에이전트가 할 일을 자연어로 쓰면 됩니다. 순진하게 쓰면 긴 문서 하나에 모든 절차를 담게 되는데, 이건 에이전트 컨텍스트 관점에서 안티패턴입니다. 매 cycle에 전체가 컨텍스트에 올라오고, 문서가 길수록 에이전트가 어디에 주목해야 할지 흔들립니다.

대신 SKILL.md에는 라우팅 테이블과 최소 루프만 두고, 상세 절차는 references/*.md로 뺐습니다. 해당 상황이 실제 발생할 때만 로드됩니다.

| 하위 작업              | 참조 문서                |
| 초기 셋업 / check 실패 | references/setup.md     |
| 새 상대 등록           | references/register.md  |
| 루프 동작              | references/loop.md      |
| 페르소나 오버라이드    | references/persona.md   |
| 전송 2-phase           | references/send.md      |

매 cycle 첫 단계에서 이 표를 읽고, 무관한 reference는 끝까지 로드하지 않습니다. 무엇을 읽히는지 못지않게 무엇을 안 읽히는지도 중요합니다. 세션이라는 런타임은 컨텍스트 윈도우가 곧 메모리이고, 거기서는 메모리 할당이 곧 설계가 됩니다.

판단의 단일점

요즘 떠오르는 에이전트 기법 중 하나는 작은 classifier를 연쇄하는 패턴입니다. Route → classify → tool-select → generate. LangChain 스타일 체인이자 여러 agent framework의 기본 구조입니다. 어떤 도메인에선 맞는 접근이지만, 이 도메인에선 정반대였습니다.

시작은 감정 분류기였습니다. emotion_score.py가 느낌표, 이모지, 길이를 보고 suspicion을 뱉습니다. 점수가 임계치를 넘으면 "화난 상대"로 분류하고 답장 AI를 호출하지 않습니다. 이틀 만에 깨졌습니다.

친구가 *"됐어."* 하고 보냈습니다. 맥락은 "그래 네 말대로 해"였습니다. 느낌표 없음, 이모지 없음, 길이 2자, 점수 낮음. 에이전트가 엉뚱한 답장을 생성했습니다. 반대로 *"ㅋㅋㅋㅋ 미쳤네 진짜 ㅋㅋ"*는 반복 문자, 이모지, 높은 점수로 차단됐습니다. 맥락은 그냥 농담이었고, 대화는 거기서 끊겼습니다.

사람은 카톡을 볼 때 분류기를 먼저 돌리지 않습니다. 한 번 읽습니다. 그 한 번에 "어떤 기분인가"와 "뭐라고, 지금 답할까"가 동시에 정해집니다. 쪼개는 순간 맥락이 찢어집니다. 스크립트 점수는 표면의 신호일 뿐, 맥락은 표면에 있지 않습니다. 맥락은 에이전트에게만 있습니다.

분류기를 뺐습니다. 원칙 하나가 남았습니다.

어떤 스크립트도 단독으로 전송 게이트를 열거나 닫지 않습니다.

스크립트가 뽑은 모든 신호는 에이전트에게 참고 정보로만 넘어갑니다. 전송 여부는 에이전트가 결정하고, LLM 호출은 cycle당 한 번뿐입니다. 에이전트가 가진 통합 판단 능력을 쪼개는 순간 에이전트를 쓰는 이유 자체가 사라진다고 봤습니다.

원칙을 한 번 세우고 나면 구현 중 나오는 거의 모든 유혹이 이걸 흔드는 형태로 옵니다. 가장 강했던 건 register.py에서였습니다. 새 상대를 등록하면 과거 500개 대화를 수집하는데, 이왕 읽는 김에 페르소나 문서도 에이전트가 자동 생성하면 깔끔해 보였습니다. 합리적으로 들렸지만, 그렇게 하면 LLM 호출 지점이 세션 루프와 register 둘로 늘어납니다. 판단의 단일점이 깨지고, "register의 판단과 세션의 판단이 일치하는가"라는 영영 검증할 길 없는 골칫거리가 들어옵니다.

그래서 register.py는 LLM을 호출하지 않습니다. 과거 대화를 DB에 백필하고, 빈 persona.md를 만드는 선에서 끝납니다. 페르소나는 사용자와 에이전트가 세션 안에서 대화로 함께 씁니다. 스크립트는 데이터를 모으고 판단은 에이전트가 맡는 구조인데, 이 경계가 어디서부터 흐려지는지 감지하는 감각을 이 프로젝트에서 가장 많이 썼습니다.

승인을 N번에서 1번으로

에이전트와 사람이 함께 돌아가는 시스템에서 가장 어려운 설계 문제는 "언제 사람을 끼워넣을지"입니다. 매 행동마다 승인받으면 에이전트 자체가 무의미해지고, 한 번도 안 받으면 재앙이 쌓입니다.

초기엔 draft-and-ask 모드였습니다. Cron tick이 깨어나 새 메시지를 잡으면 에이전트가 드래프트를 만들고 세션 안에서 물어봅니다.

>  [맹구] "아 망했다" (05:51 KST)
>   ↳ 드래프트: "ㅋㅋ 왜"
>   보낼까? (예 / 아니 / 수정: ...)

안전해 보였습니다. 하루 써보니 문제가 뚜렷했습니다. 매번 "ㅇㅇ"을 치는 것 자체가 카톡 답장의 귀찮음과 다르지 않았습니다. 카톡을 피하던 이유가 원래 그 한 줄 쓰는 부담이었는데, 그걸 "ㅇㅇ" 치는 부담으로 바꿔놓은 꼴이었습니다. 원점이었습니다. 사람은 승인 루프가 있으면 그 루프 자체를 피하게 됩니다.

그래서 승인을 빼고 자동 전송으로 갔습니다. 대신 승인을 등록 시점 한 번으로 모았습니다.

등록할 때 에이전트가 과거 카톡 500개를 읽고 사회과학적 리포트를 씁니다. 관계의 성격과 말투, 답장하지 말아야 할 주제 같은 것들입니다. 대충 이런 식입니다.

[맹구 분석 리포트]

▸ 관계의 성격
  - 친밀도: 반말, 농담 섞음, 자조 공유 → 10년 이상 친구로 추정
  - 주도권: 메시지 시작 비율 58:42 (맹구:나). 맹구가 살짝 더 자주 건다
  - 응답 리듬: 평균 응답 간격 나 12분 / 맹구 4분. 내가 늦는 편

▸ 말투 패턴 (나의)
  - 평균 길이 1.4문장. 짧음
  - 말끝: ~임/~ㅇㅇ/~ㄱㄱ 축약형 우세

▸ 최근 변화
  - 최근 2주 응답 지연 평균 38분으로 증가. 뭐 있었나?

▸ 답장하지 말아야 할 패턴 (추정)
  - 계좌/송금 관련
  - "꿈" 에 대한 긴 서사 (과거 사용자가 안 반응한 이력)

이 분석 맞아? 내가 대신 답할 때 어떤 뉘앙스로 가면 좋을까?

사용자는 읽고 교정합니다. "최근 짧아진 건 시험기간이라 그런 거임", "형이라고 부르는 거 누락됨", "꿈 얘기 시작하면 길게 공감하지 말고 짧게만" 같은 식입니다. 교정이 반영된 합의가 state/personas/<chat_id>.md에 박힙니다.

이후 루프는 매 메시지마다 묻지 않습니다. persona.md를 근거로 판단하고 바로 보냅니다. 전송 후 한 줄 로그만 남깁니다.

>  [맹구] "아 망했다""ㅋㅋ 왜" 보냄

승인 요청이 아니라 사후 통지에 가깝습니다. 승인을 N번에서 1번으로 압축한 게 이 설계에서 가장 중요한 결정이었습니다. 자율성과 안전의 트레이드오프는 양자택일 문제라기보다, 승인의 타이밍과 횟수를 어떻게 설계하느냐의 문제에 가까웠습니다. 매 cycle에 한 번씩 승인받는 도구와 관계마다 한 번 승인받는 도구는 완전히 다른 물건입니다.

그리고 이게 제가 본 2026년 에이전트 UX의 공통 패턴입니다. 권한을 잘게 쪼개 매번 묻는 에이전트는 결국 안 쓰이고, 권한을 몇 개의 굵은 합의로 모아두고 그 안에서 자율성을 주는 에이전트가 실제로 살아남습니다. 굵은 합의는 사용자가 한 번만 버티면 이후 긴 자율이 따라오는 구조인 반면, 잔 합의는 사용자가 매번 버티다 결국 포기하게 되는 구조입니다. 총 자율성이 같아도 UX는 완전히 다릅니다.

계층은 만들지 않았다

이 프로젝트 설계에서 가장 오래 붙들고 있었던 건 4계층 Reflection Stack이었습니다. Reflexion, Voyager, Generative Agents, MemGPT를 인용했고, drift 방어를 세 층으로 짰습니다.

L1 Episode     raw 대화 로그
L2 Reflection  일일 요약
L3 Lesson      재사용 가능한 규칙
L4 Persona     실제 스타일 가이드 문서

설계 문서 자체는 근사하게 나왔습니다. 논문 네 개 인용, 4계층 표, drift 방어 세 층까지 갖췄습니다. 그런데 막상 구현에 들어가니 L1 외에는 전부 쓸 일이 생긴 적이 없는 레이어였습니다. L2의 "세션 종료 시 요약"은 뭘 요약해서 뭐에 쓸 건지 답이 안 나왔고, L3의 "여러 reflection 공통 패턴"은 reflection 자체가 없으니 입력 데이터가 존재하지 않았습니다. L4의 "persona 머지 승인"도 머지할 lesson이 없으면 의미가 없었습니다.

전부 미래에만 존재하는 기능이었습니다. 엄밀히 말하면 기능이 아니라 계획에 가까웠습니다. 계획을 기능으로 착각하는 건 에이전트 시스템 설계에서 흔히 벌어지는 오류이고, 논문 인용은 그 착각을 그럴듯하게 구조화해주는 도구이기도 합니다.

다 뺐습니다. 남은 건 DB에 쌓인 과거 대화와 persona.md 둘뿐입니다. 에이전트는 매 cycle에 최근 30개 컨텍스트를 당기고, persona.md로 오버라이드를 얹습니다. DB에는 사실이, 파일에는 의도가 담기고, 이 둘은 판단 시점에만 합쳐집니다. 레이어는 4개에서 2개로 줄었지만 기능은 하나도 잃지 않았습니다. L2부터 L4까지는 애초에 기능이 아니라 계획이었기 때문입니다.

지운 뒤에 한참을 쳐다봤습니다. 그림이 너무 깨끗해져서 오히려 의심이 들었습니다. 정말 이게 다인가 싶어 한참을 더 돌려봤는데, 추가로 필요해진 게 없었습니다.

피드백 루프는 DB 가 한다

계층을 지운 직후 떠오른 질문이 있었습니다. "학습은 어떻게 계속되지?" persona.md는 calibration 때 한 번 박아두고 끝나는, 정적인 문서입니다. 그렇다면 관계가 변하거나 말투가 바뀌면 에이전트는 뭘 보고 따라갈까요.

답은 DB입니다. 매 cycle에 에이전트가 db.py get-context --chat-id <id> --limit 30으로 최근 30개 메시지를 당깁니다. 이 30개는 고정 샘플이 아니라 sliding window입니다. 새 메시지가 들어오면 윈도우가 앞으로 전진합니다. 오늘 오간 농담이 다음 드래프트의 근거가 되고, 어제 바뀐 어투도 마찬가지입니다. 결국 실시간 피드백 루프 역할을 하는 건 페르소나 파일이 아니라 DB 자체입니다. 답장을 보내는 순간 그 답장도 DB에 적재되고, 다음 cycle의 context window에 포함됩니다. 에이전트는 자기가 방금 쓴 문장을 다음 판단의 입력으로 받는 셈입니다.

이 구조가 갖는 함의는 두 가지입니다. 첫째, 단기 변화는 별도의 학습 파이프라인 없이 자동으로 따라갑니다. 레이어를 덧붙이지 않아도 되고, Reflection Stack을 지울 수 있었던 진짜 이유도 여기에 있습니다. 둘째, persona.md의 역할이 명확해집니다. "관찰되는 것 중 무엇을 덮어쓸지"를 적는 오버라이드 문서라는 것입니다. 사실은 DB가, 의도는 파일이 담당하고 둘은 매 판단마다 합쳐지는데, 이 분리가 계층을 뺀 설계의 다른 한 면입니다.

장기 드리프트는 사용자가 주도합니다. 6개월 전 존댓말을 쓰던 관계가 반말 섞는 관계로 변했는데 persona.md가 여전히 존댓말 고정이면, 사용자가 "맹구 다시 분석해줘" 한 마디로 재-calibration을 트리거합니다. 에이전트가 다시 최근 대화를 분석해서 리포트를 내면 사용자가 교정하고, persona.md가 갱신됩니다. 자동으로 드리프트를 감지하게 만들 수도 있지만, 감지 로직이 틀리면 원치 않는 방향으로 persona가 움직입니다. 단기 변화는 자동으로 따라가게 하고 장기 변화는 수동으로 맡기는 것, 이 경계를 어디에 긋는지가 이 시스템의 자기개선 전략의 전부입니다.

어댑터는 쉘 verb 로

에이전트가 플랫폼과 대화하는 경계를 어떻게 그을지가 문제였습니다. 카톡 의존은 한 파일에 격리했습니다. scripts/adapters/kakao.sh입니다.

kakao.sh check                          → auth 살아있나
kakao.sh resolve                        → 채팅방 목록
kakao.sh history <id> <limit>           → 과거 메시지
kakao.sh poll <id> <since>              → 델타
kakao.sh send <display_name> <text>     → 전송

verb 다섯 개입니다. 처음엔 Python 추상 클래스를 고민했습니다. 타입 안전성이나 mypy 통과 같은 게 눈에 밟혔습니다. 그런데 실제로 호출하는 쪽은 에이전트이고, 에이전트는 bash kakao.sh poll ...을 호출해서 JSON을 받습니다. 공통 상위 클래스를 둬서 얻는 이득이 에이전트 관점에서는 거의 없었습니다.

쉘 verb는 계약이 stdout과 exit code, JSON으로 표현되므로 언어 중립적입니다. Slack 어댑터는 Ruby로 써도 되고 Discord는 Go로 써도 됩니다. 에이전트가 호출자인 시스템에서는 가장 싼 추상화가 가장 멀리까지 갑니다. 세션이라는 런타임과도 합이 맞습니다. 어댑터는 JSON을 내놓고, 에이전트가 그 위에서 판단한 뒤 다시 어댑터에 지시를 내립니다. 그 이상의 바인딩은 필요하지 않았습니다.

카톡은 입구가 없었다

어댑터 verb 다섯 개가 깔끔하게 떨어졌지만, 그 아래는 전혀 깔끔하지 않습니다.

먼저 이 도메인에는 공식 API가 없습니다. 정확히 말하면 개인 메시지를 외부에서 읽거나 자유롭게 보낼 수 있는 공식 경로가 없습니다. Kakao의 REST API는 같은 서비스 내 메시지 전달만 허용하고, AlimTalk 같은 채널은 비즈니스 브랜드 전용입니다. "내 AI가 내 카톡을 읽고 답해준다"는 시나리오는 문서화된 길이 아예 존재하지 않습니다.

그래서 두 개의 비공식 경로를 직접 뚫어야 했습니다. 읽기는 로컬 암호화 DB를 여는 것, 쓰기는 macOS 접근성 API로 앱 UI를 조작하는 것이었습니다.

읽기부터 보겠습니다. 카톡 Mac 앱은 메시지를 SQLCipher로 암호화해 저장합니다. 키는 user_id와 기기 UUID로부터 PBKDF2로 유도됩니다. 문제는 user_id가 plist에 날것으로 저장되지 않는다는 점입니다. 어떤 계정은 후보 목록에 섞여 있고, 어떤 계정은 해시만 남아 있습니다. 해시만 남은 경우엔 최대 10억 범위를 병렬로 뒤져서 user_id를 역산합니다. 맥북 8코어 기준 수십 초가 걸립니다. 이 전 과정이 _kakao_auth.py 한 파일, 500라인 안에 들어 있습니다.

쓰기는 또 다른 벽이었습니다. 공식 자동화 경로가 없으니 AppleScript로 UI를 직접 조작합니다. 그런데 한국어 IME가 켜진 상태에서 글자를 찍으면 V으로 날아갑니다. 그래서 텍스트를 직접 타이핑하지 않습니다. 클립보드에 복사한 뒤 키코드 9 + Command 조합으로 paste해서 IME를 통째로 우회합니다. 채팅방 row는 UI tree에서 이름으로 찾아 cliclick dc:x,y로 더블클릭하고, 입력창 좌표는 창 하단 70px에 고정합니다. 전부 실험으로 뽑아낸 숫자들입니다.

이 스택을 저 혼자 발명한 건 아닙니다. silver-flight-group의 kakaocli와 upstream auth 로직에서 필요한 부분만 떼왔고, IME 우회 AppleScript를 저장소에 인라인화했습니다. 원본은 searchschema 같은 추가 기능이 있지만 이 도구엔 chatsmessages 두 개만 필요했으므로 나머지는 잘랐습니다. 어댑터 아래에서 가장 얇은 충분조건이 무엇인지 정하는 일 자체가 하나의 설계였습니다.

에이전트는 이 위에 올라타서 사회성을 흉내낼 뿐입니다.

전송은 한쪽으로 몰았다

UI 자동화 전송의 본질적 문제는 되돌릴 수 없다는 것입니다. cliclick으로 글자를 찍고 Enter를 누르면 끝입니다. 한 번 Enter를 누르면 복구가 불가능하고, 최악의 시나리오는 중복 전송입니다.

"못 보냈다"와 "두 번 보냈다"는 가치가 완전히 다릅니다. 사과 메시지가 두 번 나가면 관계가 더 이상해지지만, 놓친 메시지는 사람이 재전송하면 그만입니다.

drafted  --(phase 1: DB commit)-->  sending
sending  --(phase 2: adapter)---->  sent    (정상)
sending  --(phase 2 fail)--------->  failed
sending  --(process crash)-------->  (stuck)
(stuck)  --(next session recover)->  failed

프로세스가 phase 1과 phase 2 사이에서 죽으면 행이 sending으로 남습니다. 다음 세션이 시작할 때 cycle.sh check가 무조건 failed로 마킹합니다. AppleScript가 Enter까지 성공했는지 알 길이 없으니, 진짜로 보냈다면 다음 poll에서 outbound로 되돌아오고, 안 보냈다면 그대로 실패로 남습니다.

굳이 이름을 붙이자면 safety-biased reconciliation입니다. 어느 실패가 덜 나쁜지를 명시적으로 정하고, 그 방향으로 편향되게 짜는 것입니다. 에이전트가 사람 대신 메시지를 보내는 시스템에서는 이 비대칭을 설계에 박아두는 게 꼭 필요하다고 생각했습니다.

아직 안 풀린 것들

동시에 Mac을 쓰는 상황이 제일 애매합니다. AppleScript가 입력창에 글자를 찍는 순간 사용자가 타자를 치고 있으면 경쟁이 발생합니다. 지금은 그냥 두고 있습니다. 단체방 발신자 구분도 걸립니다. sender 필드는 있지만, 에이전트가 "A가 나한테 한 말"과 "B가 C에게 한 말"을 신뢰성 있게 분리하는지는 실전 데이터가 더 쌓여봐야 알 수 있습니다.

조금 더 근본적인 문제도 있습니다. "답 안 하는 게 나았다"가 반복되면 에이전트가 침묵에 overfit할 가능성입니다. baseline 응답률이 시간에 따라 drift하면 이걸 식별하는 것 자체가 어려워집니다. 장기 드리프트 감지 자동화도 미해결입니다. 6개월 단위로 관계의 결이 바뀌는 걸 사용자가 알아차리지 못하면 re-calibration도 돌아가지 않습니다. DB의 단기 피드백 루프는 잡아내지만, "언제 다시 맞춰야 하나"를 시스템이 먼저 제안하는 구조는 아직 만들지 못했습니다.

마무리

몇 년 전 유행은 "LLM을 잘 프롬프트하는 법"이었습니다. 지금은 "에이전트 시스템을 잘 배치하는 법"에 가깝습니다. 프레임이 완전히 바뀐 셈입니다.

답장 문장을 잘 뽑는 건 LLM이 알아서 해줄 부분입니다. 이 프로젝트에서 제가 한 일은 그 문장이 실제로 나갈 때까지의 경로를 얇게 만드는 것이었습니다. 모델 능력 자체는 건드리지 않았습니다. 런타임, 수명, 컨텍스트 경계, 승인 지점, 어댑터 계약, 이 다섯 축을 어디에 고정할지만 결정했습니다.


이 글에서 다룬 프로젝트: auto-kakaotalk