<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>동식이 블로그</title>
    <description>동식이 블로그 임니다</description>
    <link>https://dongsik93.github.io/</link>
    <atom:link href="https://dongsik93.github.io/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Tue, 12 May 2026 04:05:51 +0000</pubDate>
    <lastBuildDate>Tue, 12 May 2026 04:05:51 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>[AI Agent] Crosstalk 만들기 - 3편: 똑똑한 폴링보다 멍청한 콜백이 낫다</title>
        <description>&lt;h1 id=&quot;v010이-가장-좋았다&quot;&gt;v0.1.0이 가장 좋았다&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;/2026/05/08/crosstalk-2-plugin-packaging&quot;&gt;2편&lt;/a&gt;까지 패키징을 끝내고 v0.1.6까지 잘 굴러갔다. 그런데 어느 순간 깨달았다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;기능을 붙일수록 본질이 사라졌다.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;v0.1.0 PoC 시점이 가장 깔끔한 토론기였다. 화면에 답변이 나오면 사회자(Claude)가 직접 보고 다음 사람한테 넘기는 &lt;em&gt;진짜 핑-퐁&lt;/em&gt;. 그런데 v0.1.4에서 transport 도입, v0.1.5에서 활동 감지, v0.1.6에서 i18n까지 보강한 끝의 v0.1.6 토론은 &lt;em&gt;transport 프로토콜 따라가기 게임&lt;/em&gt;이 되어 있었다.&lt;/p&gt;

&lt;p&gt;3편은 그 &lt;em&gt;과한 보강의 함정&lt;/em&gt;을 어떻게 인식했고 어떻게 풀었는지 회고. 결과적으로는 v0.2.0에서 메이저 변경으로 가야 했다.&lt;/p&gt;

&lt;h2 id=&quot;첫-함정--긴-답변이-잘렸다&quot;&gt;첫 함정 — 긴 답변이 잘렸다&lt;/h2&gt;

&lt;p&gt;1편 한계점에서 적었던 그것. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux read-screen&lt;/code&gt;이 화면에 보이는 영역만 캡처하니까, 답변이 길면 위쪽이 스크롤로 사라진다.&lt;/p&gt;

&lt;p&gt;처음엔 &lt;em&gt;답변 길이를 한 단락으로 제한&lt;/em&gt;해서 회피했다. 근데 PR 리뷰처럼 본문이 결정적으로 중요한 토론에선 한 단락으로 못 줄인다. 그래서 v0.1.4에서 &lt;strong&gt;파일 기반 transport&lt;/strong&gt;를 도입했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;═══ Transport (변경 불가) ═══
이 턴의 답변은 화면이 아니라 파일에 기록한다.

1. 답변 본문 전체를 다음 파일에 그대로 써라:
     ${RUN_DIR}/responses/&amp;lt;agent&amp;gt;-r&amp;lt;NN&amp;gt;-a&amp;lt;N&amp;gt;.md
2. 파일 작성이 끝나면 화면(터미널)에 정확히 한 줄을 출력해라:
     DONE &amp;lt;MSG_ID&amp;gt;
3. 파일 외에 추가 가공/요약/박스 출력은 하지 마라.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;각 AI는 답변 본문을 파일에 쓰고, 화면에는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DONE &amp;lt;msg-id&amp;gt;&lt;/code&gt; 한 줄만. 사회자는 bridge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait-turn&lt;/code&gt; 으로 파일 안정성 + DONE 마커를 폴링하다가 둘 다 잡히면 &lt;em&gt;clean&lt;/em&gt; 상태로 종료. attempt id, run id, msg-id 같은 식별자도 도입했다.&lt;/p&gt;

&lt;p&gt;이게 잘 동작했다. &lt;em&gt;본문 잘림 문제는 해결&lt;/em&gt;. 그런데 새로운 문제들이 줄줄이 나왔다.&lt;/p&gt;

&lt;h2 id=&quot;두-번째-함정--gemini가-writefile-도구를-호출했다&quot;&gt;두 번째 함정 — Gemini가 WriteFile 도구를 호출했다&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:debate 비 오는 날 국밥 어때?&lt;/code&gt; 쳤더니 Gemini 화면이 이랬다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/crosstalk-gemini-writefile-fail.png&quot; alt=&quot;Gemini가 WriteFile 도구로 답변 파일을 4번 덮어쓰는 화면&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;WriteFile gemini-r01-a1.md → Accepted 가 줄줄이. 답변을 파일에 쓰라고 했더니 &lt;em&gt;진짜로&lt;/em&gt; WriteFile 도구를 호출하고, 답을 바꿀 때마다 같은 파일을 덮어씀.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;같은 파일을 4번 덮어썼다. 답변이 [AGREE] → [DISAGREE] → [DISAGREE] → 어쩌고 식으로 매번 바뀜.&lt;/p&gt;

&lt;p&gt;원인 두 개:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Gemini는 agentic CLI다.&lt;/strong&gt; “파일에 답변을 쓰라”고 하면 모델이 &lt;em&gt;진짜 WriteFile 도구를 호출&lt;/em&gt;한다. claude/codex는 메시지 텍스트로만 출력하는데 (도구 사용을 보수적으로), gemini는 자기가 사고 처리해서 도구 사용. 화면에 diff/Accepted 로그가 가득.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Enter 한 번이 두 번처럼 동작.&lt;/strong&gt; paste-bracket 우회용으로 Enter를 두 번 보내고 있었는데, gemini-cli는 두 번째 Enter를 &lt;em&gt;별도 submit&lt;/em&gt;으로 받아서 &lt;em&gt;같은 메시지를 두 번 처리&lt;/em&gt;. 그래서 답변 4번 출력.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;해결은 두 트랙.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# (1) gemini는 Enter 1번만&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$KIND&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in
  &lt;/span&gt;codex|claude&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;0.5
    cmux send-key &lt;span class=&quot;nt&quot;&gt;--surface&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; enter   &lt;span class=&quot;c&quot;&gt;# 두 번째 Enter&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;
  gemini&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    : &lt;span class=&quot;c&quot;&gt;# 1번이면 충분. 두 번째 Enter가 별도 submit으로 처리됨.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;esac&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# (2) gemini는 file 대신 screen 모드&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;file — claude/codex]
파일에 본문 + 화면에 DONE 마커.

&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;screen — gemini]
파일 X, WriteFile/Shell 도구 X.
화면에 정확히 한 번만:
  CROSSTALK_BEGIN &amp;lt;MSG_ID&amp;gt;
  &amp;lt;답변 본문&amp;gt;
  CROSSTALK_END &amp;lt;MSG_ID&amp;gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;bridge가 화면에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BEGIN/END&lt;/code&gt; 사이를 추출해 파일로 저장하는 구조. AI 입장에선 &lt;em&gt;그냥 텍스트 출력&lt;/em&gt;이라 도구 사고 안 남.&lt;/p&gt;

&lt;h2 id=&quot;세-번째-함정--gemini는-답변이-늦었다&quot;&gt;세 번째 함정 — Gemini는 답변이 늦었다&lt;/h2&gt;

&lt;p&gt;Gemini는 응답 시작이 &lt;em&gt;느린 케이스&lt;/em&gt;가 잦았다. 30초+ 걸리는 경우가 흔한데, MAX_WAIT 안에 답이 안 오면 사회자가 &lt;em&gt;timeout? 재시도?&lt;/em&gt; 를 띄웠다. 사용자가 “재시도”를 누르면 attempt 2 메시지를 보내는데, 그 사이 attempt 1 답변이 늦게 도착해서 두 답변이 동시에 떠다니는 사고.&lt;/p&gt;

&lt;p&gt;해결도 두 트랙.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;agent별 MAX_WAIT 차등&lt;/strong&gt; — gemini=360s, codex=240s, claude=180s. 처음부터 충분히 잡고 시작.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;활동 감지 자동 연장&lt;/strong&gt; — 화면/파일에 변화가 &lt;em&gt;최근 30초 안에&lt;/em&gt; 있으면 &lt;em&gt;살아있는 한&lt;/em&gt; 자동으로 60초씩 연장 (최대 3회). Gemini가 답변 시작이 늦어도 살아있으면 안 죽임.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;ACTIVITY_GRACE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;30 &lt;span class=&quot;nv&quot;&gt;ACTIVITY_EXTEND_BY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;60 &lt;span class=&quot;nv&quot;&gt;ACTIVITY_EXTEND_MAX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이걸로 timeout 사고는 거의 사라졌다.&lt;/p&gt;

&lt;h3 id=&quot;잠깐--gemini는-그냥-포기해야-하나&quot;&gt;잠깐 — Gemini는 그냥 포기해야 하나&lt;/h3&gt;

&lt;p&gt;이쯤 굴리다 보니 &lt;em&gt;Gemini는 아예 빼고 가는 게&lt;/em&gt; 합리적이지 않나 싶었다. WriteFile 사고, 답변 두 번 출력, 응답 시작 늦음, 다른 CLI랑 다른 paste-bracket 동작 — 사고 절반이 gemini-cli 관련이었다.&lt;/p&gt;

&lt;p&gt;다만 이게 &lt;em&gt;gemini-cli가 못 만들어졌다&lt;/em&gt;기보다는 &lt;strong&gt;목적이 다른 도구&lt;/strong&gt;라서 그런 측면이 크다. claude/codex는 &lt;em&gt;대화형으로 도구를 점진적으로&lt;/em&gt; 쓰는 결인데, gemini-cli는 처음부터 &lt;em&gt;agentic 행동을 능동적으로&lt;/em&gt; 하도록 만들어진 느낌. “파일에 답변 써라” 같은 요청에 &lt;em&gt;진짜로 WriteFile 도구 호출 + diff 표시&lt;/em&gt;하는 게 그 도구의 본 의도에 가깝다. 다만 &lt;em&gt;토론기처럼 결정적 동작이 필요한 시나리오&lt;/em&gt;에선 그 능동성이 거꾸로 작용했다.&lt;/p&gt;

&lt;p&gt;구글 내부에서도 코딩 워크플로우의 주력 채널은 &lt;strong&gt;Gemini Code Assist&lt;/strong&gt;(IDE), &lt;strong&gt;Jules&lt;/strong&gt;(자율 에이전트), &lt;strong&gt;Vertex AI&lt;/strong&gt; 같은 쪽이고 gemini-cli는 그 사이의 &lt;em&gt;경량 채널&lt;/em&gt;에 가까워 보인다. 그래서 &lt;em&gt;cmux 같은 터미널 자동화&lt;/em&gt; 환경에 가장 잘 맞는 도구는 아닐 수 있다 — &lt;em&gt;모델&lt;/em&gt;이 아니라 &lt;em&gt;CLI 레이어&lt;/em&gt;가 우리 시나리오랑 안 맞는 정도.&lt;/p&gt;

&lt;p&gt;지금은 일단 Gemini도 같이 살리는 방향으로 잡아뒀다. 의견의 &lt;em&gt;제3자 시각&lt;/em&gt;이 있는 게 토론 자체에 좋고, 사용자가 빼고 싶으면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:launch&lt;/code&gt;에서 codex만 띄우는 옵션도 있다. 다만 &lt;em&gt;기본 추천 구성에서 gemini를 제외&lt;/em&gt;하는 건 v0.3.x쯤 진지하게 검토해볼 듯.&lt;/p&gt;

&lt;h2 id=&quot;그런데-본문이-transport에-묻혔다&quot;&gt;그런데 본문이 transport에 묻혔다&lt;/h2&gt;

&lt;p&gt;세 함정을 다 잡고 나서 v0.1.6을 굴려봤다. 사용자 화면이 이렇게 떴다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&amp;gt; [Crosstalk safe mode]
  We will debate this topic: 비 오는 날 국밥
  
  Rules:
  1. Reply with one paragraph...
  2. Do not modify files... 
     One exception: each turn&apos;s Transport section may allow writing exactly one response file.
  ...
  
  ═══ 페르소나 (default) ═══
  ...
  
  ═══ 토론 규칙 (default) ═══
  ...
  
  ═══ 시스템 규칙 (변경 불가) ═══
  - 합의 시 [AGREE]
  - 이견 시 [DISAGREE: 사유]
  
  ═══ Transport (변경 불가) ═══
  파일에 쓰지 마라. WriteFile/Shell 같은 도구를 사용하지 마라.
  화면(터미널)에 아래 형식으로 정확히 한 번만 출력하라.
    CROSSTALK_BEGIN run-K4iN-r01-gemini-a1
    &amp;lt;답변 본문 한 단락&amp;gt;
    CROSSTALK_END run-K4iN-r01-gemini-a1
  그 외 텍스트(요약, 박스, 진행 상황)는 출력하지 마라.
  같은 답변을 여러 번 출력하지 마라.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;매 턴 메시지가 페르소나 + 룰 + 시스템 + Transport 섹션으로 가득. 답변 본문이 들어갈 자리는 마지막 한 줄.&lt;/p&gt;

&lt;p&gt;AI 입장에선 &lt;em&gt;프로토콜 따라가기&lt;/em&gt;에 인지 자원 다 쓰고 &lt;em&gt;내용&lt;/em&gt;에는 적게 쓴다. instruction following이 안 되거나, 답변이 &lt;em&gt;형식만 맞고 내용은 빈약&lt;/em&gt;해진다. 토론은 &lt;em&gt;진짜 핑-퐁&lt;/em&gt;이 아니라 &lt;em&gt;프로토콜 검증&lt;/em&gt; 같았다.&lt;/p&gt;

&lt;p&gt;사용자 화면에도 BEGIN/END/DONE 마커가 가득하고, gemini는 가끔 transport 무시하고 화면에 답변 그대로 적어버려서 사회자가 &lt;em&gt;protocol-error 분기 → 재시도 → 또 사고&lt;/em&gt; 의 굴레.&lt;/p&gt;

&lt;p&gt;이게 &lt;em&gt;내가 처음에 만들고 싶었던 토론&lt;/em&gt;인가? 1편의 v0.1.0이 더 깔끔했다. 화면에 답변이 나오고, 사회자가 보고, 다음 사람한테 넘기고. 단순.&lt;/p&gt;

&lt;h2 id=&quot;깨달음--한-마디&quot;&gt;깨달음 — 한 마디&lt;/h2&gt;

&lt;p&gt;v0.1.6까지 굴리다가 어느 순간 정리됐다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“내가 명령어 떄릴 거면 이거 안 만들지. 지네들끼리 핑퐁해야 하는데…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;v0.1.4 ~ v0.1.6의 토론 시스템은 &lt;em&gt;사회자(Claude)가 wait-ping/wait-turn으로 무한 폴링&lt;/em&gt;하는 구조였다. 사회자 bash 프로세스가 살아있어야 동작하고, 폴링하는 동안 사용자가 다른 거 시키면 &lt;em&gt;흐름이 깨짐&lt;/em&gt;. 한 번 깨지면 사회자가 &lt;em&gt;어디까지 했는지 모르고&lt;/em&gt; 멈춰버림.&lt;/p&gt;

&lt;p&gt;다른 말로: &lt;strong&gt;사회자를 &lt;em&gt;답을 기다리는 주체&lt;/em&gt;로 만든 게 잘못이었다.&lt;/strong&gt; 사회자는 &lt;em&gt;답이 도착했을 때 깨어나는 주체&lt;/em&gt;가 되어야 했다.&lt;/p&gt;

&lt;h2 id=&quot;v020--폴링-버리고-콜백으로&quot;&gt;v0.2.0 — 폴링 버리고 콜백으로&lt;/h2&gt;

&lt;p&gt;새 구조는 단순했다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;사회자(Claude): 한 라운드만 메시지 보내고 → 슬래시 커맨드 종료. idle.&lt;/li&gt;
  &lt;li&gt;AI(codex/gemini): 답변 출력 → bridge ping 한 줄 호출.&lt;/li&gt;
  &lt;li&gt;bridge ping: manifest에서 사회자 surface 찾아 cmux send로 사회자 pane 입력.&lt;/li&gt;
  &lt;li&gt;사회자(Claude): callback 메시지를 새 사용자 입력으로 받음 → 다음 라운드 자동 진행.&lt;/li&gt;
  &lt;li&gt;반복.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;핵심 변경 두 가지.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start-run&lt;/code&gt;이 manifest에 사회자 surface 저장&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;run_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;K4iN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;moderator_surface&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;surface:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bridge ping&lt;/code&gt;이 manifest 읽고 cmux send로 사회자 pane 깨움&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/scripts/crosstalk_bridge.sh ping &amp;lt;RUN_ID&amp;gt; &amp;lt;AGENT&amp;gt; &amp;lt;ROUND&amp;gt;
   ↓
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;crosstalk] codex R3 &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt; — &lt;span class=&quot;nv&quot;&gt;RUN_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;K4iN. 답변은 cmux pane 화면에서 캡처해서 정리하고 다음 라운드 진행해.
   ↓ &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;사회자 pane 입력창에 박힘&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;사회자 pane은 그 메시지를 &lt;em&gt;새 사용자 입력&lt;/em&gt;으로 받고, debate.md 본문대로 &lt;em&gt;callback 핸들링&lt;/em&gt;에 진입.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/crosstalk-callback-pane.png&quot; alt=&quot;callback 메시지가 claude pane 입력창에 박힌 직후 다음 단계 진행&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;codex 답변이 끝나자마자 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[r40 done] ...&lt;/code&gt; 한 줄이 사회자 pane에 도착 → Claude가 &lt;em&gt;받자마자&lt;/em&gt; 다음 단계 진입&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI 입장에서는 한 줄.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/scripts/crosstalk_bridge.sh ping K4iN codex 3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;경로/포맷 외울 필요 없고, AI가 답변 출력 직후 한 번 호출하면 끝.&lt;/p&gt;

&lt;h2 id=&quot;polling-vs-callback--비교&quot;&gt;Polling vs Callback — 비교&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Polling (v0.1.x)&lt;/th&gt;
      &lt;th&gt;Callback (v0.2.0)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;사회자 bash 프로세스&lt;/td&gt;
      &lt;td&gt;살아있어야 함&lt;/td&gt;
      &lt;td&gt;idle OK&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;사용자가 중간에 개입&lt;/td&gt;
      &lt;td&gt;흐름 깨짐, 복구 어려움&lt;/td&gt;
      &lt;td&gt;깨져도 ping 도착 시 자연 재진입&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;메시지 헤더 분량&lt;/td&gt;
      &lt;td&gt;매 턴 페르소나+룰+transport 풀 셋업&lt;/td&gt;
      &lt;td&gt;preamble 한 번 + 이후 짧은 follow-up&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;답변 캡처 방법&lt;/td&gt;
      &lt;td&gt;파일 transport + DONE 마커&lt;/td&gt;
      &lt;td&gt;화면 그대로 + 사회자가 후처리&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AI 부담&lt;/td&gt;
      &lt;td&gt;프로토콜 따라가기 (BEGIN/END/DONE)&lt;/td&gt;
      &lt;td&gt;답변 출력 + ping 한 줄&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;가장 흔한 실패&lt;/td&gt;
      &lt;td&gt;gemini WriteFile / 답변 두 번 / timeout&lt;/td&gt;
      &lt;td&gt;ping을 안 보내면 사회자 idle (수동 ping으로 unblock)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;callback 흐름은 &lt;em&gt;답을 잘 받기 위한 휴리스틱&lt;/em&gt;이 거의 필요 없다. 화면에 답이 와있고, ping이 &lt;em&gt;왔다&lt;/em&gt;는 신호가 오면 그게 진실. 폴링/안정성/timeout 추정 다 폐기.&lt;/p&gt;

&lt;p&gt;다만 &lt;em&gt;하나는 남았다&lt;/em&gt;: AI가 ping을 안 보내면 사회자는 idle인 채로 영영 안 깨어남. 이건 자동 timeout으로 해결하면 &lt;em&gt;원래 polling 구조로 회귀&lt;/em&gt;하는 거니까 안 했다. 대신 사용자가 &lt;em&gt;수동 ping&lt;/em&gt;으로 unblock 가능.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# 수동 ping — codex가 답은 줬는데 ping 안 보낸 경우&lt;/span&gt;
~/.claude/scripts/crosstalk_bridge.sh ping K4iN codex 3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;교훈&quot;&gt;교훈&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. 신호 받는 방법이 본문을 잡아먹지 않게 할 것&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;v0.1.4 transport는 &lt;em&gt;답을 안전하게 받기 위한&lt;/em&gt; 메커니즘이었다. 그런데 그 메커니즘이 &lt;em&gt;매 턴 메시지의 절반 이상&lt;/em&gt;을 차지하면서 본문(토론 내용)을 압도했다. 신호는 &lt;em&gt;조용해야&lt;/em&gt; 한다 — 메시지 본문 옆이 아니라 &lt;em&gt;별도 채널&lt;/em&gt;로.&lt;/p&gt;

&lt;p&gt;callback의 ping은 메시지 본문에 안 들어간다. AI는 답변만 출력하고, 끝나면 한 줄 호출. 메시지 본문에는 &lt;em&gt;주제와 직전 답변 한 줄 인용&lt;/em&gt;만.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 휴리스틱 위에 더 정교한 휴리스틱 쌓지 말 것&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;화면 안정성으로 답변 완료를 추정 → 안정성 휴리스틱이 부정확 → 활동 감지 자동 연장 → 그것도 부정확 → 더 정교한 폴링… 이 사이클은 &lt;em&gt;근본 가정이 잘못된&lt;/em&gt; 신호.&lt;/p&gt;

&lt;p&gt;근본 가정은 &lt;em&gt;사회자가 답을 추측한다&lt;/em&gt;였다. 그걸 &lt;em&gt;AI가 신호를 명시적으로 준다&lt;/em&gt;로 바꾸자 휴리스틱이 다 사라졌다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. v0.1.0이 좋았던 이유: 기능이 적어서 단순했음&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;기능을 늘릴 때마다 &lt;em&gt;없앨 수 있는 가정&lt;/em&gt;도 같이 봐야 했다. v0.1.4의 transport는 &lt;em&gt;긴 답변 잘림&lt;/em&gt;이라는 가정 하나를 풀려고 &lt;em&gt;답변을 어디에 어떻게 쓸지&lt;/em&gt; 라는 새 가정 다섯을 도입했다. 가정이 늘면 실패 모드도 늘고, 그걸 막으려고 코드가 늘고…&lt;/p&gt;

&lt;p&gt;v0.2.0 callback은 &lt;em&gt;전부 화면에 답이 떠있다&lt;/em&gt;는 v0.1.0의 가정으로 회귀했다. 다만 &lt;em&gt;사회자를 깨우는 방법&lt;/em&gt;만 polling → callback으로 바꿨다. 작은 변경 + 큰 효과.&lt;/p&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;v0.2.0 푸시 후 첫 토론을 돌려봤다. 메시지 한 통 보내고 슬래시 커맨드 종료. 사회자 pane은 idle. 잠시 후 &lt;em&gt;codex pane에서 답변 출력 → ping 호출&lt;/em&gt; → claude pane에 callback 메시지 도착 → 자동으로 다음 라운드 시작.&lt;/p&gt;

&lt;p&gt;처음 본 그 흐름이 &lt;em&gt;정확히 1편의 v0.1.0에서 보던 그 흐름&lt;/em&gt;이었다. &lt;em&gt;진짜 핑-퐁&lt;/em&gt;. 화면이 깨끗하고, 메시지가 짧고, 사용자(나)도 중간에 끼어들어 &lt;em&gt;코덱스가 그건 좀 비약 같다, 다른 각도로 다시&lt;/em&gt; 같이 &lt;em&gt;진짜 사회자처럼&lt;/em&gt; 행동할 수 있다.&lt;/p&gt;

&lt;p&gt;transport 옵션은 남겨뒀다 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--transport file&lt;/code&gt; 또는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--transport screen&lt;/code&gt;으로 &lt;em&gt;명시적으로 켤 때만&lt;/em&gt; 사용. 긴 PR 리뷰 토론처럼 &lt;em&gt;답변 본문이 결정적&lt;/em&gt;인 케이스에서는 여전히 유용. 단 기본은 &lt;em&gt;off&lt;/em&gt;다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;v0.1.0 (PoC)        — 화면 캡처. 단순. 좋았음.
v0.1.4 ~ v0.1.6     — transport 보강. 본문 묻힘. 잘못된 길.
v0.2.0 (current)    — callback + 화면 캡처. v0.1.0의 가정 + ping callback. 회귀이자 진화.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;다음 단계로 검토 중인 것:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Codex CLI 사회자 모드 — 양방향 callback&lt;/li&gt;
  &lt;li&gt;tmux 어댑터 — cmux 의존 제거&lt;/li&gt;
  &lt;li&gt;라운드 로그 영구 저장 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Documents/crosstalk/&amp;lt;날짜&amp;gt;/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;특별히 새 함정이 또 나오기 전까지는 v0.2.x 안에서 정리할 예정.&lt;/p&gt;

&lt;p&gt;레포: &lt;a href=&quot;https://github.com/dongsik93/crosstalk&quot;&gt;github.com/dongsik93/crosstalk&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;3편 시리즈를 마치며. &lt;em&gt;기능을 늘릴 때 가정도 같이 보기&lt;/em&gt; — 이게 이번 회고의 한 줄이다. 다음에 또 비슷한 함정에 빠질 텐데, 그때는 조금 더 빨리 깨닫게 될 것 같다.&lt;/p&gt;

&lt;h2 id=&quot;참고-링크&quot;&gt;참고 링크&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dongsik93/crosstalk&quot;&gt;crosstalk 레포&lt;/a&gt; — v0.2.0 소스&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/05/07/crosstalk-1-multi-agent-debate&quot;&gt;1편: Claude + Codex + Gemini, 셋이서 토론하는 슬래시 커맨드&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/05/08/crosstalk-2-plugin-packaging&quot;&gt;2편: Claude Code 플러그인으로 패키징하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 08 May 2026 12:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/05/08/crosstalk-3-callback-pivot/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/05/08/crosstalk-3-callback-pivot/</guid>
        
        <category>claude-code</category>
        
        <category>cmux</category>
        
        <category>multi-agent</category>
        
        <category>crosstalk</category>
        
        <category>callback</category>
        
        <category>polling</category>
        
        <category>refactor</category>
        
        <category>postmortem</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[AI Agent] Crosstalk 만들기 - 2편: Claude Code 플러그인으로 패키징하기</title>
        <description>&lt;h1 id=&quot;1편-끝나고-남은-한-가지-찜찜함&quot;&gt;1편 끝나고 남은 한 가지 찜찜함&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;/2026/05/07/crosstalk-1-multi-agent-debate&quot;&gt;1편&lt;/a&gt;에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fight&lt;/code&gt; 슬래시 커맨드를 만들어서 잘 쓰고 있었다. 그런데 &lt;em&gt;내 손에서만&lt;/em&gt; 잘 굴러갔다.&lt;/p&gt;

&lt;p&gt;구성은 이랬다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/commands/fight.md           # 슬래시 커맨드 정의
~/.claude/scripts/fight_bridge.sh     # cmux 통신 헬퍼
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 두 파일이 &lt;em&gt;내 머신에&lt;/em&gt; 있어야 동작한다. 다른 노트북에서 다시 쓰려면 또 깔아야 하고, 친구한테 권하려고 해도 &lt;em&gt;brew로 cmux 깔고 / fight.md 복사하고 / bridge 권한 주고 / 푸터 시그니처는 네 CLI 버전에 따라 달라질 수 있고…&lt;/em&gt; 한 다음에야 동작한다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“내 손에서 도는 도구”는 내 손을 떠나는 순간 도구가 아니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그래서 이걸 &lt;strong&gt;Claude Code 플러그인&lt;/strong&gt;으로 패키징하기로 했다. 잘 모르는 친구도 한 줄로 깔 수 있게.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/plugin marketplace add dongsik93/crosstalk
/plugin install crosstalk@dongsik93/crosstalk
/crosstalk:install
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2편은 그 패키징 과정 — 의외로 &lt;em&gt;기술적&lt;/em&gt;으로는 어렵지 않은데 &lt;em&gt;판단할 게&lt;/em&gt; 많은 작업이었다.&lt;/p&gt;

&lt;h2 id=&quot;claude-code-플러그인-구조--marketplace-vs-plugin&quot;&gt;Claude Code 플러그인 구조 — marketplace vs plugin&lt;/h2&gt;

&lt;p&gt;처음 부딪힌 건 &lt;em&gt;메타데이터 파일이 두 개&lt;/em&gt;라는 점이었다. 둘 다 헷갈릴 수 있어서 정리부터.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;crosstalk/                                    # GitHub 레포
├── .claude-plugin/
│   └── marketplace.json                      # 마켓 메타 (이 레포가 어떤 플러그인 모음인가)
├── plugins/
│   └── crosstalk/
│       ├── .claude-plugin/
│       │   └── plugin.json                   # 플러그인 메타 (이 폴더가 어떤 플러그인인가)
│       └── commands/
│           ├── debate.md
│           ├── review.md
│           └── ...
└── assets/
    ├── scripts/crosstalk_bridge.sh
    ├── user-commands/crosstalk.md            # 단독 명령용
    ├── rules/{en,ko}/...                     # 빌트인 룰
    └── personas/{en,ko}/...                  # 빌트인 페르소나
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude-plugin/marketplace.json&lt;/code&gt;&lt;/strong&gt; — &lt;em&gt;레포 root에 두는&lt;/em&gt; 마켓플레이스 메타. “이 레포에 어떤 플러그인들이 들어있는지” 카탈로그.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;crosstalk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dongsik93&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Multi-agent AI debate via cmux split panes — run Claude, Codex, and Gemini in one command.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.1.6&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;plugins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;crosstalk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.1.6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./plugins/crosstalk&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins/&amp;lt;name&amp;gt;/.claude-plugin/plugin.json&lt;/code&gt;&lt;/strong&gt; — 각 플러그인 &lt;em&gt;자체&lt;/em&gt;의 메타.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;crosstalk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.1.6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Multi-agent AI debate slash commands powered by cmux split panes.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dongsik93&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;같은 정보(이름/버전/설명)가 &lt;em&gt;두 곳에 다&lt;/em&gt;. 처음엔 &lt;em&gt;왜 이렇게 중복?&lt;/em&gt; 했는데, 한 마켓플레이스 레포가 여러 플러그인을 가질 수 있는 구조라 그렇다 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins[]&lt;/code&gt; 배열). 마켓 레벨에서는 카탈로그 메타, 플러그인 레벨에서는 플러그인 자체 메타.&lt;/p&gt;

&lt;p&gt;설치 흐름은 두 단계다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/plugin marketplace add dongsik93/crosstalk
   ↓ ~/.claude/plugins/marketplaces/dongsik93-crosstalk/ 에 레포 클론
/plugin install crosstalk@dongsik93/crosstalk
   ↓ marketplace.json 읽고 → plugins[0].source 의 폴더를 활성화
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;마켓 메타가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version: 0.1.5&lt;/code&gt;라고 쓰여 있으면 마켓 캐시에 저장되고, 다음에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/plugin install&lt;/code&gt;을 해도 이미 0.1.5가 있으니 갱신을 안 한다. &lt;strong&gt;patch bump 없이는 마켓 캐시가 stale인 채로 굳어버린다&lt;/strong&gt;. (이거 때문에 나중에 patch release 6번을 돌리게 된다.)&lt;/p&gt;

&lt;h2 id=&quot;콜론-네임스페이스--crosstalkinstall의-의미&quot;&gt;콜론 네임스페이스 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt;의 의미&lt;/h2&gt;

&lt;p&gt;플러그인이 제공하는 명령은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&amp;lt;plugin&amp;gt;:&amp;lt;command&amp;gt;&lt;/code&gt; 형태다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:debate&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:review&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:setup&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:launch&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;콜론으로 네임스페이스가 분리되니까 다른 플러그인의 같은 이름 명령과 안 부딪힌다. 좋다.&lt;/p&gt;

&lt;p&gt;근데 사람이 매번 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:debate &amp;lt;주제&amp;gt;&lt;/code&gt; 라고 치는 건 길다. 단축으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk &amp;lt;주제&amp;gt;&lt;/code&gt; (콜론 없이) 가 되면 좋겠는데, 이건 플러그인 명령으로 만들 수 없다. &lt;strong&gt;사용자 레벨 슬래시 커맨드&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/commands/&amp;lt;name&amp;gt;.md&lt;/code&gt;) 만 콜론 없는 호출이 가능하기 때문이다.&lt;/p&gt;

&lt;p&gt;해결: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt; 이 사용자 홈에 단독 명령 파일을 &lt;em&gt;복사해 깔아주는&lt;/em&gt; 식.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# install이 하는 일 일부&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$MARKETPLACE_ROOT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/assets/user-commands/crosstalk.md&quot;&lt;/span&gt; ~/.claude/commands/
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이러면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk &amp;lt;주제&amp;gt;&lt;/code&gt; 도 동작 (debate 본문을 그대로 따라가도록 위임).&lt;/p&gt;

&lt;h2 id=&quot;crosstalkinstall--셋업-자동화의-핵심&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt; — 셋업 자동화의 핵심&lt;/h2&gt;

&lt;p&gt;플러그인을 깔면 끝나는 게 아니라 &lt;em&gt;사용자 환경&lt;/em&gt;도 같이 검증/셋업해야 한다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt; 한 번이 처리하는 것:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;환경 검증&lt;/strong&gt; — Node.js / npm / cmux / jq / gh&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AI CLI 자동 설치 안내&lt;/strong&gt; — claude / codex / gemini 중 누락된 것&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;언어 선택&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;English&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;한국어&lt;/code&gt; AskUserQuestion&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Crosstalk 컴포넌트 사용자 홈 복사&lt;/strong&gt; — bridge / 단독 명령 / 빌트인 룰·페르소나&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;완료 안내 + 다음 단계&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;사용자 화면은 이런 식으로 흘러간다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/crosstalk-install.png&quot; alt=&quot;/crosstalk:install 실행 결과&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;환경 검증 → AI CLI 자동 설치 → 언어 선택 → 컴포넌트 복사가 한 흐름으로&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;이게 &lt;em&gt;플러그인 자체 설치&lt;/em&gt; 다음에 한 번 더 도는 단계다. 처음엔 “왜 두 단계?” 싶었는데, 환경 검증 + AI CLI 설치 + 권한 체크는 플러그인 메타로 표현 못 한다. 결국 &lt;em&gt;플러그인 첫 명령이 세팅용&lt;/em&gt;인 형태가 자연스러웠다.&lt;/p&gt;

&lt;h3 id=&quot;--presets-only-빠른-경로&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--presets-only&lt;/code&gt; 빠른 경로&lt;/h3&gt;

&lt;p&gt;빌트인 룰/페르소나만 다시 깔고 싶을 때를 위한 옵션:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/crosstalk:install --presets-only --language ko
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;→ 환경 검증 / CLI 설치 / 언어 선택 모두 건너뛰고 룰·페르소나만 보충. 사용자가 새 언어로 toggle 했을 때 자가치유에도 쓰인다.&lt;/p&gt;

&lt;h2 id=&quot;crosstalklaunch--cmux-자동-셋업&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:launch&lt;/code&gt; — cmux 자동 셋업&lt;/h2&gt;

&lt;p&gt;cmux split을 사용자가 매번 직접 만들면 친절하지 못하다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:launch&lt;/code&gt; 한 번에:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;cmux 안에서 호출됐는지 확인 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux ping&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;cmux 외부면 → cmux 앱 띄우고 &lt;em&gt;cmux 안에서 다시 launch&lt;/em&gt; 안내&lt;/li&gt;
  &lt;li&gt;cmux 안이면 → 본인 surface 식별 → 우측에 split 추가 → 새 pane에서 codex/gemini 시작&lt;/li&gt;
  &lt;li&gt;각 pane이 &lt;em&gt;진짜 입력 가능 상태&lt;/em&gt;가 될 때까지 대기 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait-ready&lt;/code&gt; 폴링)&lt;/li&gt;
  &lt;li&gt;ready 통과한 pane만 cmux 탭에 라벨 박기 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-codex&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-gemini&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;sleep-15--wait-ready-폴링&quot;&gt;sleep 15 → wait-ready 폴링&lt;/h3&gt;

&lt;p&gt;처음엔 단순했다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;cmux send &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NEW_SURFACE_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;codex&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;15   &lt;span class=&quot;c&quot;&gt;# CLI 시작 대기&lt;/span&gt;
cmux rename-tab &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NEW_SURFACE_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ct-codex&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;문제는 &lt;em&gt;15초가 항상 맞진 않다&lt;/em&gt;는 것. 빠른 머신에서는 5초면 뜨는데 15초 강제 대기. 느린 환경에서는 15초도 부족할 수 있고. 그리고 라벨링이 &lt;em&gt;시작 전&lt;/em&gt;에 박히면, 라벨 우선 detect 로직이 &lt;em&gt;아직 안 떠있는 pane을 codex로 오판&lt;/em&gt; 할 수 있다.&lt;/p&gt;

&lt;p&gt;bridge에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait-ready&lt;/code&gt; 케이스를 추가했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/scripts/crosstalk_bridge.sh wait-ready &amp;lt;surface&amp;gt; &amp;lt;expected-kind&amp;gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;화면 푸터 패턴이 expected-kind와 일치하면 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE: ready&lt;/code&gt; (exit 0)&lt;/li&gt;
  &lt;li&gt;OAuth/로그인 화면 감지 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE: auth-needed&lt;/code&gt; (exit 2)&lt;/li&gt;
  &lt;li&gt;READY_MAX_WAIT(20초) 초과 → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STATE: timeout&lt;/code&gt; (exit 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;라벨은 ready 통과한 pane만 박는다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/scripts/crosstalk_bridge.sh wait-ready &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NEW_SURFACE_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE_KIND_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RC1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$?&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$RC1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eq&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  ~/.claude/scripts/crosstalk_bridge.sh label &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NEW_SURFACE_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE_KIND_1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;빠른 머신에선 4~6초에 끝나고, 느린 환경에선 20초까지 기다린다. auth 화면이면 즉시 &lt;em&gt;수동 인증 필요&lt;/em&gt; 안내 — sleep 15에서는 절대 못 잡던 분기.&lt;/p&gt;

&lt;h3 id=&quot;라벨링이-푸터-의존을-줄여준다&quot;&gt;라벨링이 푸터 의존을 줄여준다&lt;/h3&gt;

&lt;p&gt;cmux 탭에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-codex&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-gemini&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-claude&lt;/code&gt; 라벨이 박혀 있으면 detect는 &lt;em&gt;라벨 우선&lt;/em&gt;, 푸터 매칭은 폴백.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/crosstalk-tab-label.png&quot; alt=&quot;ct-codex 라벨이 박힌 cmux 탭&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;LABEL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;get-label &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LABEL&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LABEL&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;0
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 라벨 없으면 푸터 패턴 폴백 (1편의 self-match 방어 로직)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CLI 버전이 올라가서 푸터 디자인이 바뀌어도 라벨이 박혀 있으면 안 깨진다.&lt;/p&gt;

&lt;h2 id=&quot;i18n----language-enko&quot;&gt;i18n — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--language en|ko&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;처음엔 한국어 안내만 있었다. 그러다 README를 영어로 갈아엎으면서 &lt;em&gt;UI 안내&lt;/em&gt;도 양쪽 다 줘야겠다 싶었다. 풀 번역은 부담스러우니 절충안.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/crosstalk/config.json
{
  &quot;active_rules&quot;: &quot;default&quot;,
  &quot;active_persona&quot;: &quot;default&quot;,
  &quot;language&quot;: &quot;en&quot;        ← 사용자가 install 때 선택
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/crosstalk/
├── rules/
│   ├── en/{default,brainstorm,debate}.md
│   └── ko/{default,brainstorm,debate}.md
└── personas/
    ├── en/{default,senior-junior,critic-builder,triple-perspective}.md
    └── ko/{default,senior-junior,critic-builder,triple-perspective}.md
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;룰/페르소나는 &lt;em&gt;언어별 본문&lt;/em&gt;으로 갖고, 메시지 템플릿의 &lt;em&gt;사용자 안내&lt;/em&gt;만 언어별 분기. &lt;em&gt;transport 핵심 지시&lt;/em&gt;는 영어 고정.&lt;/p&gt;

&lt;p&gt;전환은 install 다시 안 돌려도 가능.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/crosstalk:setup --language ko
   → config.json .language = &quot;ko&quot; 갱신
   → 그 다음 라벨링까지 한 번에
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;누락-시-자동-복원--ensure-presets&quot;&gt;누락 시 자동 복원 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ensure-presets&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;새 언어로 toggle 했을 때 &lt;em&gt;그 언어 디렉토리가 비어있으면&lt;/em&gt; 디렉토리 비었으니 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:install&lt;/code&gt; 다시 돌리세요라는 안내가 뜨는 게 부자연스럽다. bridge가 자동으로 채워주게 했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.claude/scripts/crosstalk_bridge.sh ensure-presets &amp;lt;lang&amp;gt;
   → ~/.claude/crosstalk/{rules,personas}/&amp;lt;lang&amp;gt;/ 가 비어있으면
   → 마켓 캐시에서 빌트인 자동 복사 (사용자 편집 보존)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이걸 debate / review / rules / persona / status 명령들이 &lt;em&gt;룰 본문 읽기 직전&lt;/em&gt;에 호출. 사용자는 toggle 한 번이면 자동으로 새 언어 룰까지 갖춰진다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# debate.md 안&lt;/span&gt;
~/.claude/scripts/crosstalk_bridge.sh ensure-presets &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LANGUAGE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RULES_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/.claude/crosstalk/rules/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LANGUAGE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RULES_NAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.md
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이 자동 복원 덕분에 사용자가 install을 다시 안 돌려도 된다. 안 보이는 곳에서 알아서 굴러가는 작은 친절.&lt;/p&gt;

&lt;h2 id=&quot;테스트-중-발견된-불편한-것들-v010--v016&quot;&gt;테스트 중 발견된 불편한 것들 (v0.1.0 → v0.1.6)&lt;/h2&gt;

&lt;p&gt;릴리즈 6번 돌면서 잡은 것들. 다 &lt;em&gt;작은데 짜증 났던&lt;/em&gt; 거.&lt;/p&gt;

&lt;h3 id=&quot;별칭crosstalki이-동작-안-함&quot;&gt;별칭(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:i&lt;/code&gt;)이 동작 안 함&lt;/h3&gt;

&lt;p&gt;처음엔 자주 치는 명령에 단축 별칭을 만들려고 했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/crosstalk:i   → install
/crosstalk:d   → debate
/crosstalk:l   → launch
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;근데 슬래시 커맨드는 다른 슬래시 커맨드를 &lt;em&gt;호출&lt;/em&gt;할 수 없다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:i&lt;/code&gt;를 만들어도 그건 &lt;em&gt;그냥 마크다운 문서&lt;/em&gt;고, 본문에 “이 명령은 /crosstalk:install 과 동일하게 동작한다”라고 적어도 모델이 그걸 &lt;em&gt;직접 실행&lt;/em&gt; 못 한다. 그냥 텍스트로 안내만 떠버림.&lt;/p&gt;

&lt;p&gt;별칭 모두 제거. &lt;em&gt;콜론 없는 단독 명령&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk&lt;/code&gt;) 만이 유일한 단축.&lt;/p&gt;

&lt;h3 id=&quot;마켓-캐시-stale--patch-bump-강제&quot;&gt;마켓 캐시 stale → patch bump 강제&lt;/h3&gt;

&lt;p&gt;같은 버전 그대로 푸시하면 마켓이 &lt;em&gt;이미 최신&lt;/em&gt;이라며 갱신을 안 한다. 사용자 입장에서는 새 코드 받으려면 &lt;em&gt;수동으로 캐시 삭제&lt;/em&gt;해야 하는데 너무 번거롭다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/plugin marketplace remove dongsik93/crosstalk
/plugin marketplace add dongsik93/crosstalk
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;해결은 별 게 없고 — &lt;em&gt;고치면 패치 버전 올린다&lt;/em&gt;. v0.1.0 → v0.1.6까지 6번 올라간 것도 절반은 &lt;em&gt;마켓 캐시 갱신용&lt;/em&gt;이었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;작은 fix라도 &lt;em&gt;사용자 머신까지 도달하려면&lt;/em&gt; 버전이 올라야 한다. 마켓을 끼고 배포하는 도구의 숙명.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;self-pane-라벨링-누락&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self pane&lt;/code&gt; 라벨링 누락&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/crosstalk:setup&lt;/code&gt; 라벨링 단계에서 &lt;em&gt;본인이 떠있는 pane(claude)&lt;/em&gt; 만 빼먹고 옆 pane만 라벨 박는 케이스가 있었다. detect가 라벨을 우선 보니까, &lt;em&gt;본인은 푸터 매칭 폴백&lt;/em&gt;으로 잡혀서 가끔 unknown 처리되는 사고.&lt;/p&gt;

&lt;p&gt;setup이 self pane도 자동으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ct-claude&lt;/code&gt; 라벨 박게 수정.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;SELF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;~/.claude/scripts/crosstalk_bridge.sh list-all | &lt;span class=&quot;nb&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;\t&apos;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$3==&quot;self&quot; {print $1}&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
~/.claude/scripts/crosstalk_bridge.sh label &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SELF&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; claude
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;gemini-응답이-늦으면-사용자가-timeout-인식--재시도--답변-두-개&quot;&gt;Gemini 응답이 늦으면 사용자가 timeout 인식 → 재시도 → 답변 두 개&lt;/h3&gt;

&lt;p&gt;이건 좀 복잡한 사고였다. Gemini는 답변 시작이 느린 케이스가 잦은데, MAX_WAIT 안에 답이 안 오면 사용자한테 &lt;em&gt;timeout? 재시도?&lt;/em&gt; 가 떴다. 사용자가 재시도를 누르면 &lt;em&gt;attempt 2&lt;/em&gt; 메시지를 보내는데, 그 사이 &lt;em&gt;attempt 1 답변&lt;/em&gt;이 늦게 도착해서 두 답변이 동시에 떠다니는 사고.&lt;/p&gt;

&lt;p&gt;해결은 두 트랙으로 갔다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;agent별 MAX_WAIT 차등&lt;/strong&gt;: gemini=360s, codex=240s, claude=180s. 처음부터 충분히 잡고 시작.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;활동 감지 자동 연장&lt;/strong&gt;: 화면/응답 파일에 변화가 &lt;em&gt;최근 30초 안에&lt;/em&gt; 있으면 &lt;em&gt;살아있는 한&lt;/em&gt; 자동으로 60초씩 연장 (최대 3회). Gemini가 답변 &lt;em&gt;시작&lt;/em&gt;이 늦어도 &lt;em&gt;살아있으면&lt;/em&gt; 안 죽임.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;ACTIVITY_GRACE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;30 &lt;span class=&quot;nv&quot;&gt;ACTIVITY_EXTEND_BY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;60 &lt;span class=&quot;nv&quot;&gt;ACTIVITY_EXTEND_MAX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이걸로 timeout 사고는 거의 사라졌다. (그러나 &lt;em&gt;진짜 본질적 해결&lt;/em&gt;은 v0.2의 callback 구조였다 — 이건 3편에서.)&lt;/p&gt;

&lt;h2 id=&quot;codex-cli와의-차이--왜-양방향이-안-되는가&quot;&gt;Codex CLI와의 차이 — 왜 양방향이 안 되는가&lt;/h2&gt;

&lt;p&gt;Claude Code는 슬래시 커맨드 + 마켓플레이스. Codex CLI는 &lt;a href=&quot;https://platform.openai.com/docs/codex&quot;&gt;Skill 시스템&lt;/a&gt;이다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;~/.codex/skills/&amp;lt;skill-name&amp;gt;/
└── SKILL.md
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Codex는 SKILL.md 파일에 &lt;em&gt;언제 이 스킬을 발동할지&lt;/em&gt; 자연어로 적으면 모델이 알아서 트리거한다. 슬래시 커맨드가 아니고 &lt;em&gt;상황 기반 발동&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;이론상 양쪽에 다 깔면 &lt;em&gt;어느 CLI에서도 토론 사회자 역할&lt;/em&gt;이 가능해야 한다. 그런데 &lt;em&gt;cmux 안에서 Codex가 다른 pane을 깨우는 부분&lt;/em&gt;이 까다로워서 (각 CLI가 cmux 명령을 &lt;em&gt;진짜로 실행&lt;/em&gt;하는 거랑 &lt;em&gt;그냥 답변에 적기만 하는 거&lt;/em&gt;가 다름) v0.1에서는 Codex pane은 &lt;em&gt;참여자&lt;/em&gt;로만 동작하게 두고, &lt;em&gt;사회자=Claude 고정&lt;/em&gt;. v0.2 이후 Codex 사회자 모드는 따로 검토 예정.&lt;/p&gt;

&lt;p&gt;지금은 Claude Code → Codex pane / Gemini pane 단방향이다.&lt;/p&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;플러그인 패키징은 &lt;em&gt;기술적&lt;/em&gt;으로 어렵지 않았다. JSON 메타 두 개 + 컴포넌트 복사 스크립트 + 환경 검증.&lt;/p&gt;

&lt;p&gt;오히려 &lt;em&gt;판단할 게&lt;/em&gt; 많았다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;별칭은 어디까지? (콜론 없는 단독 명령 하나만 두는 게 깔끔)&lt;/li&gt;
  &lt;li&gt;자가치유는 어디까지 자동? (사용자 편집 보존 + 누락만 보충)&lt;/li&gt;
  &lt;li&gt;patch bump는 얼마나 자주? (수동 캐시 삭제보다는 patch가 낫다)&lt;/li&gt;
  &lt;li&gt;i18n은 풀 번역? (UI만 분기, 핵심 지시는 영어 고정 절충)&lt;/li&gt;
  &lt;li&gt;Codex/Gemini까지 양방향? (v0.1은 단방향, v0.2 이후 검토)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 결정이 &lt;em&gt;사용자가 한 번 깔고 잊어버려도 되는&lt;/em&gt; 도구가 되느냐 마느냐를 결정한다. &lt;em&gt;내 손에서 도는 도구&lt;/em&gt;에서 &lt;em&gt;남의 손에 깔리는 도구&lt;/em&gt;로 가는 거리는 생각보다 멀었다.&lt;/p&gt;

&lt;p&gt;레포: &lt;a href=&quot;https://github.com/dongsik93/crosstalk&quot;&gt;github.com/dongsik93/crosstalk&lt;/a&gt; — public 공개됨.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/plugin marketplace add dongsik93/crosstalk
/plugin install crosstalk@dongsik93/crosstalk
/crosstalk:install
/crosstalk:launch
/crosstalk 비 오는 날 국밥이 답인가?
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;다음-편-예고&quot;&gt;다음 편 예고&lt;/h2&gt;

&lt;p&gt;여기까지가 &lt;em&gt;패키징&lt;/em&gt; 이야기다. 사용자가 깔 수 있는 도구로 만들었고, v0.1.6까지 잘 돌아갔다.&lt;/p&gt;

&lt;p&gt;그런데 &lt;em&gt;진짜 본질적인 문제&lt;/em&gt; 하나가 남아 있었다.&lt;/p&gt;

&lt;p&gt;v0.1.6까지 굴려보다가 어느 순간 깨달았다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“내가 명령어 때릴 거면 이거 안 만들지. 지네들끼리 핑퐁해야 하는데…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1편/2편에서 만든 토론 시스템은 사회자(Claude)가 &lt;em&gt;답을 끝없이 폴링&lt;/em&gt;하는 구조였다. 폴링하는 동안 다른 거 시키면 흐름이 깨지고, 한 번 깨지면 복구가 안 됐다.&lt;/p&gt;

&lt;p&gt;3편은 &lt;em&gt;polling을 버리고 callback으로 갔다&lt;/em&gt;는 회고. v0.1.x → v0.2.0 메이저 변경 — 똑똑한 폴링보다 멍청한 콜백이 낫다는 깨달음의 기록.&lt;/p&gt;

&lt;h2 id=&quot;참고-링크&quot;&gt;참고 링크&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/plugins&quot;&gt;Claude Code Plugin 공식 문서&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://platform.openai.com/docs/codex&quot;&gt;Codex Skill 시스템&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dongsik93/crosstalk&quot;&gt;crosstalk 레포&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/05/07/crosstalk-1-multi-agent-debate&quot;&gt;1편: Claude + Codex + Gemini, 셋이서 토론하는 슬래시 커맨드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 08 May 2026 11:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/05/08/crosstalk-2-plugin-packaging/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/05/08/crosstalk-2-plugin-packaging/</guid>
        
        <category>claude-code</category>
        
        <category>plugin</category>
        
        <category>marketplace</category>
        
        <category>cmux</category>
        
        <category>multi-agent</category>
        
        <category>crosstalk</category>
        
        <category>i18n</category>
        
        <category>install</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[AI Agent] Crosstalk 만들기 - 1편: Claude + Codex + Gemini, 셋이서 토론하는 슬래시 커맨드</title>
        <description>&lt;h1 id=&quot;시작은-단순한-의문이었다&quot;&gt;시작은 단순한 의문이었다&lt;/h1&gt;

&lt;p&gt;요즘 코딩할 때 Claude Max, Codex Pro, Gemini Pro 세 개를 다 구독해서 쓰고 있다. 각 모델 강점이 달라서 작업 종류마다 골라 쓴다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Claude: 긴 컨텍스트, 코드 리팩토링&lt;/li&gt;
  &lt;li&gt;Codex (GPT-5.5): 추론, 알고리즘&lt;/li&gt;
  &lt;li&gt;Gemini 3 Pro: 멀티모달, 큰 코드베이스 분석&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;근데 어느 날 문득 이런 생각이 들었다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;얘네 셋이 같이 토론하면 더 좋은 결론이 나오지 않을까?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PR 리뷰처럼 한 사람 의견에 갇히고 싶지 않은 작업이 종종 있고, 안드로이드 신규 프로젝트 Compose 대 XML 같은 결정 문제도 셋의 의견을 다 듣고 싶었다. 그래서 만들어봤다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/ai-fight-debate.gif&quot; alt=&quot;AI 셋이서 토론하는 모습&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;Claude(사회자) + Codex + Gemini 다자 토론 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fight&lt;/code&gt; 한 번으로 자동 진행&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;첫-시도-claude-octopus-헤드리스-토론&quot;&gt;첫 시도, claude-octopus 헤드리스 토론&lt;/h2&gt;

&lt;p&gt;처음엔 &lt;a href=&quot;https://github.com/nyldn/claude-octopus&quot;&gt;claude-octopus&lt;/a&gt;를 썼다. Claude Code 안에서 여러 모델을 한 번에 호출해 답을 받아오는 도구. &lt;em&gt;주제 던지면 모델별 답변을 모아서 종합 의견까지&lt;/em&gt; 자동으로 만들어줘서, 처음엔 내가 원했던 게 이거 같았다.&lt;/p&gt;

&lt;p&gt;근데 며칠 쓰다 보니 점점 &lt;em&gt;답답함&lt;/em&gt;이 쌓였다.&lt;/p&gt;

&lt;p&gt;CLI 환경에서 AI를 직접 띄워 쓰는 진짜 가치는 &lt;strong&gt;추론 과정을 옆에서 보면서 개입할 수 있다는 점&lt;/strong&gt;이다. Claude가 어떤 파일을 먼저 읽는지, Codex가 어떤 가정을 깔고 결론을 내는지, Gemini가 무엇을 빠뜨렸는지 — 이게 다 화면에 뜨는 게 CLI다. 헤드리스 도구는 &lt;em&gt;최종 종합&lt;/em&gt;만 던져주고 그 뒤 사고 흐름은 블랙박스다. 결론이 미덥지 않아도 &lt;em&gt;왜&lt;/em&gt; 그렇게 나왔는지 모르니 후속 질문을 던질 곳이 없다.&lt;/p&gt;

&lt;p&gt;내가 원했던 건 한 줄로 정리됐다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;토론을 자동화한 헤드리스 도구&lt;/em&gt;가 아니라, &lt;strong&gt;각 AI의 사고 과정을 그대로 보면서 내가 사회자로 끼어들 수도 있는&lt;/strong&gt; 토론.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그러려면 셋이 동시에 &lt;em&gt;내 화면에 띄워져&lt;/em&gt; 있어야 한다. 헤드리스 호출이 아니라 진짜 인터랙티브 CLI 세 개가 옆에 나란히. 거기서부터 다시 도구를 찾기 시작했다.&lt;/p&gt;

&lt;h2 id=&quot;pal-mcp의-clink--visible-호출의-첫-단추&quot;&gt;PAL MCP의 clink — visible 호출의 첫 단추&lt;/h2&gt;

&lt;p&gt;처음 발견한 건 &lt;a href=&quot;https://github.com/BeehiveInnovations/pal-mcp-server&quot;&gt;PAL MCP 서버&lt;/a&gt;였다. Claude Code에서 다른 AI CLI를 호출하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clink&lt;/code&gt; 도구가 있는데, 코드 리뷰 같은 작업을 다른 모델에 맡기고 결과만 받아올 수 있었다.&lt;/p&gt;

&lt;p&gt;그런데 곧 한계가 보였다.&lt;/p&gt;

&lt;p&gt;PAL의 다른 도구들(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;consensus&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thinkdeep&lt;/code&gt;)은 API 키 기반이라 종량제 과금이다. 구독제 그대로 활용 가능한 건 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clink&lt;/code&gt; 하나뿐이었다. clink는 CLI를 직접 띄워서 결과를 받아오는 구조라 구독제로 동작한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;[Claude Code] → MCP 호출 → [PAL] → 자식 프로세스(codex CLI) → [Codex Pro 구독]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;clink 자체는 훌륭한 도구지만, 두 모델을 &lt;strong&gt;자동으로 토론시키는&lt;/strong&gt; 기능은 없었다. 1회성 호출만 가능. 자동 토론을 원하면 직접 만들어야 했다.&lt;/p&gt;

&lt;h2 id=&quot;멀티플렉서-선택-cmux&quot;&gt;멀티플렉서 선택, cmux&lt;/h2&gt;

&lt;p&gt;자동 토론을 만들려면 두 CLI를 나란히 띄워두고 외부에서 텍스트를 주입해야 한다. 처음엔 tmux를 떠올렸다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;tmux send-keys &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; fight:0.1 &lt;span class=&quot;s1&quot;&gt;&apos;안녕&apos;&lt;/span&gt; Enter
tmux capture-pane &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; fight:0.1 &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;tmux도 충분히 가능했지만, 검색하다가 &lt;a href=&quot;https://github.com/manaflow-ai/cmux&quot;&gt;cmux&lt;/a&gt;라는 macOS 전용 멀티플렉서를 발견했다. &lt;em&gt;AI 에이전트를 여러 pane에 띄워 운용하는 시나리오&lt;/em&gt;를 1순위로 두고 만들어진 도구다. Ghostty 설정을 그대로 활용하고, hook과 멀티 에이전트 명령(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude-teams&lt;/code&gt; 등)을 자체 제공한다.&lt;/p&gt;

&lt;p&gt;cmux의 장점은 분명했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;정식 CLI/Socket API (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux send&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux read-screen&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux send-key&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;AI 응답 완료 감지 훅 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux hooks setup&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux claude-teams&lt;/code&gt; 등 멀티 에이전트 명령 내장&lt;/li&gt;
  &lt;li&gt;ANSI escape 처리를 cmux가 알아서 해줘 화면 캡처가 깔끔&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;cmux &lt;span class=&quot;nt&quot;&gt;--help&lt;/span&gt;
Commands:
  list-pane-surfaces         &lt;span class=&quot;c&quot;&gt;# 모든 분할창 surface 목록&lt;/span&gt;
  send &amp;lt;surface&amp;gt; &amp;lt;text&amp;gt;      &lt;span class=&quot;c&quot;&gt;# 특정 surface에 텍스트 입력&lt;/span&gt;
  send-key &amp;lt;surface&amp;gt; &amp;lt;key&amp;gt;   &lt;span class=&quot;c&quot;&gt;# 특정 surface에 키 입력 (enter 등)&lt;/span&gt;
  read-screen &amp;lt;surface&amp;gt;      &lt;span class=&quot;c&quot;&gt;# 화면 텍스트 캡처&lt;/span&gt;
  ...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;fight-슬래시-커맨드-설계&quot;&gt;/fight 슬래시 커맨드 설계&lt;/h2&gt;

&lt;p&gt;원하는 흐름은 이렇다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;[사용자]
&amp;gt; /fight 안드로이드 신규 프로젝트, Compose vs XML?

[Claude Code]
🔍 cmux pane 스캔...
  ✓ surface:4 → codex 감지
  ✓ surface:5 → gemini 감지

? 누구와 토론할까요?
  ○ codex (1:1)
  ○ gemini (1:1)
  ● 모두 (다자, Claude 사회자)
  ○ 취소

&amp;gt; [선택] 모두

━━━ Round 1/10 ━━━
🟦 Claude: ...
🟧 Codex: ...
🟪 Gemini: ...

(라운드 반복)

━━━ 종합 ━━━
🎯 결론: ...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;구성 요소는 두 개다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/commands/fight.md&lt;/code&gt; — Claude Code 슬래시 커맨드 정의&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/scripts/fight_bridge.sh&lt;/code&gt; — cmux 통신 헬퍼&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;bridge-스크립트-구조&quot;&gt;bridge 스크립트 구조&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;~/.claude/scripts/fight_bridge.sh
Usage: fight_bridge.sh &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;peer|list-peers|detect|send|capture|lines|wait|stop&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;args...]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;peer&lt;/code&gt; — 본인 제외한 첫 번째 surface ID 반환&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list-peers&lt;/code&gt; — 본인 제외한 모든 surface와 각 CLI 종류&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;detect &amp;lt;surface&amp;gt;&lt;/code&gt; — 화면 보고 어느 CLI인지 감지 (claude/codex/gemini)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send &amp;lt;surface&amp;gt; &amp;lt;text&amp;gt;&lt;/code&gt; — 텍스트 와 Enter 전송&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wait &amp;lt;surface&amp;gt; &amp;lt;since-line&amp;gt;&lt;/code&gt; — 출력 안정될 때까지 대기, 새 텍스트 반환&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stop &amp;lt;surface&amp;gt;&lt;/code&gt; — Ctrl+C 전송 (긴급 정지)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;구현하면서-만난-함정-3개&quot;&gt;구현하면서 만난 함정 3개&lt;/h2&gt;

&lt;h3 id=&quot;함정-1-cli-자동-감지의-self-match-문제&quot;&gt;함정 1, CLI 자동 감지의 self-match 문제&lt;/h3&gt;

&lt;p&gt;각 surface가 어떤 CLI인지 알아야 사용자에게 codex와 토론, gemini와 토론 같은 옵션을 보여줄 수 있다. cmux는 surface ID와 작업 디렉토리만 줄 뿐, 거기 떠있는 CLI 종류는 모른다.&lt;/p&gt;

&lt;p&gt;1차 시도는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read-screen&lt;/code&gt;으로 화면 내용 받아서 시그니처 패턴 매칭이었다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CONTENT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;OpenAI Codex&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;codex&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; ...
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;테스트 결과는 이랬다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;surface:1 → gemini   ❌ (실제로는 Claude Code)
surface:4 → codex    ✅
surface:5 → gemini   ✅
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;원인을 추적해보니 surface:1(Claude pane)에서 직전에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux read-screen surface:5&lt;/code&gt;로 Gemini 화면을 출력해본 적이 있었고, 그 출력이 화면에 텍스트로 남아있어서 self-match가 일어난 것이었다.&lt;/p&gt;

&lt;p&gt;해결은 화면 본문을 무시하고 푸터(하단 8~20줄)만 스캔하는 것. CLI 푸터는 항상 화면 하단에 고정된다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;CLI&lt;/th&gt;
      &lt;th&gt;푸터 시그니처&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Claude&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;◐ medium · /effort&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;⏵⏵ accept edits on (shift+tab to cycle)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Codex&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-5.5 default ·&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Token usage: total=&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gemini&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMINI.md files&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemini-3-pro (default)&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;FOOTER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;cmux read-screen &lt;span class=&quot;nt&quot;&gt;--surface&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--lines&lt;/span&gt; 80 | &lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-20&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FOOTER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;GEMINI\.md files&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;gemini
&lt;span class=&quot;k&quot;&gt;elif &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FOOTER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;gpt-[0-9]+(\.[0-9]+)? default ·|Token usage:&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;codex
&lt;span class=&quot;k&quot;&gt;elif &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FOOTER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-qE&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;◐ [a-z]+ · /effort|⏵⏵ accept edits on&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;claude
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3개 모두 정확히 분류됐다.&lt;/p&gt;

&lt;h3 id=&quot;함정-2-gemini의-paste-bracket&quot;&gt;함정 2, Gemini의 paste-bracket&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send&lt;/code&gt; 명령으로 Codex와 Claude는 문제없이 메시지가 전송됐는데 Gemini만 Enter가 안 먹혔다. 사용자가 추가로 Enter를 한 번 더 눌러야만 메시지가 전송됐다.&lt;/p&gt;

&lt;p&gt;원인은 paste-bracket 모드였다. 텍스트가 한 번에 우다닥 들어오면 Gemini는 paste로 인식하고 Enter도 텍스트의 일부로 흡수한다. 두 번째 Enter는 새로 들어온 키 입력으로 인식해서 send 처리되는 식.&lt;/p&gt;

&lt;p&gt;해결은 send 명령에서 상대가 Gemini면 Enter를 두 번 보내는 것이다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;cmux send &lt;span class=&quot;nt&quot;&gt;--surface&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TEXT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
cmux send-key &lt;span class=&quot;nt&quot;&gt;--surface&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; enter
&lt;span class=&quot;nv&quot;&gt;KIND&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; detect &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$KIND&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; gemini &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;0.3
  cmux send-key &lt;span class=&quot;nt&quot;&gt;--surface&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SURFACE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; enter   &lt;span class=&quot;c&quot;&gt;# 한 번 더&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;함정-3-cmux-json의-키-순서가-매번-다름&quot;&gt;함정 3, cmux JSON의 키 순서가 매번 다름&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux identify&lt;/code&gt;로 본인 surface ID를 뽑는 부분에서 가끔 빈 값이 반환됐다. 처음엔 cmux 소켓 불안정으로 의심했는데, 알고 보니 JSON 키 순서가 호출마다 달랐던 게 원인이었다.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;어느&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;호출&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;caller&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;surface_ref&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;surface:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tab_ref&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tab:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;다른&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;호출&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;caller&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tab_ref&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tab:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;workspace_ref&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;workspace:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;is_browser_surface&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;surface_ref&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;surface:1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;   &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;←&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;번째&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;줄&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep -A1 &apos;&quot;caller&quot;&apos;&lt;/code&gt; 패턴은 caller 다음 1줄만 보니까 surface_ref가 첫 줄이 아닐 때 빈 값을 반환했다. awk 기반으로 caller 블록 안의 surface_ref만 정확히 매칭하도록 고쳤다.&lt;/p&gt;

&lt;h2 id=&quot;다자-토론-룰&quot;&gt;다자 토론 룰&lt;/h2&gt;

&lt;p&gt;다자 모드는 Claude를 사회자로 둔다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;매 라운드마다 Codex와 Gemini에게 같은 질문 동시 전송&lt;/li&gt;
  &lt;li&gt;둘의 답변과 Claude 본인 의견을 종합&lt;/li&gt;
  &lt;li&gt;셋 다 [AGREE]면 합의 도달, 종료&lt;/li&gt;
  &lt;li&gt;일부만 [AGREE]면 의견 차이 정리해서 다음 라운드 질문 생성&lt;/li&gt;
  &lt;li&gt;최대 10라운드 (1:1은 15턴)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;발언 길이는 한 단락(3-5문장)으로 제한. 토큰 절약과 핵심만 추리는 효과를 같이 본다.&lt;/p&gt;

&lt;h2 id=&quot;실제로-돌려본-결과&quot;&gt;실제로 돌려본 결과&lt;/h2&gt;

&lt;p&gt;주제는 안드로이드 신규 프로젝트 Compose 대 XML.&lt;/p&gt;

&lt;p&gt;3라운드 만에 합의 도달했다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;━━━ Round 1/10 ━━━
🟦 Claude: 신규 프로젝트라면 Compose가 디폴트. 선언형 UI, 코드량 감소, ...
🟧 Codex: [AGREE] Compose 우선이 합리적. 단 학습 곡선과 일부 ...
🟪 Gemini: 동의하지 않는다. XML이 여전히 더 안정적이고 팀 ...

━━━ Round 2/10 ━━━
🟦 Claude: Gemini 우려에 일부 동의. 하지만 ...
🟧 Codex: 균형을 잡자면 ...
🟪 Gemini: Compose 전환 비용을 무시할 수 없다 ...

━━━ Round 3/10 ━━━
🟦 Claude: 통합안 — Compose 디폴트, 단 (a) XML 전문성과 일정 타이트, 
            (b) WebView/고성능 커스텀 렌더링 핵심이면 예외 허용.
🟧 Codex: [AGREE] 가장 현실적인 기준.
🟪 Gemini: [AGREE] 우려했던 전환 비용과 리스크가 구체적 조건으로 제어 가능.

🎯 결론: 신규 안드로이드 프로젝트의 디폴트는 Compose.
   단 (a) 팀이 XML 전문성만 보유하고 출시 일정이 매우 타이트할 때,
   (b) 앱 핵심이 WebView, AppWidget, 레거시 SDK 뷰, 고성능 커스텀 렌더링일 때
   는 합리적 예외.
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;흥미로운 점이 몇 가지 있었다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Gemini가 처음엔 보수적(XML 우선 주장)이다가 라운드 3에서 양보&lt;/li&gt;
  &lt;li&gt;Codex는 빠르게 균형점 제시. 취향이 아니라 장기 유지비 대 단기 납기 리스크의 트레이드오프&lt;/li&gt;
  &lt;li&gt;Claude는 사회자 역할에 충실 — 양측 의견을 종합해서 통합안 제시&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 모델의 &lt;em&gt;성격&lt;/em&gt;이 보였다. 단순히 같은 답을 내는 게 아니라 의견 분포가 진짜 토론 같다.&lt;/p&gt;

&lt;h2 id=&quot;한계점&quot;&gt;한계점&lt;/h2&gt;

&lt;p&gt;만들고 나서 보이는 한계도 명확하다.&lt;/p&gt;

&lt;h3 id=&quot;macos와-cmux에-종속됨&quot;&gt;macOS와 cmux에 종속됨&lt;/h3&gt;

&lt;p&gt;cmux 자체가 macOS 전용이다. Linux/Windows에선 tmux로 직접 옮겨야 하는데, tmux는 ANSI escape 파싱과 출력 안정 감지를 직접 처리해야 해서 코드가 더 길어진다. 크로스 플랫폼 지원하려면 추상화 레이어 한 단계 더 필요하다.&lt;/p&gt;

&lt;h3 id=&quot;푸터-시그니처를-박아둔-점&quot;&gt;푸터 시그니처를 박아둔 점&lt;/h3&gt;

&lt;p&gt;CLI 감지 로직이 각 CLI의 푸터 텍스트(예: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMINI.md files&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-5.5 default ·&lt;/code&gt;)를 정규식으로 매칭한다. 문제는 이 푸터 텍스트가 CLI 버전 업그레이드로 바뀌면 그대로 깨진다는 것이다.&lt;/p&gt;

&lt;p&gt;지금은 Claude Code, Codex CLI, Gemini CLI 셋 다 활발히 개발 중이라 푸터 디자인이 자주 바뀐다. 이상적으론 cmux가 surface별로 실행 중인 프로세스 정보를 노출해주면 좋은데, 현재 cmux API에는 그런 게 없다. 차선책으로 사용자가 surface 이름을 직접 라벨링(예: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmux rename-tab&lt;/code&gt; 같은)하게 하는 방법도 있겠다.&lt;/p&gt;

&lt;h3 id=&quot;전체-토론-결과가-저장되지-않음&quot;&gt;전체 토론 결과가 저장되지 않음&lt;/h3&gt;

&lt;p&gt;토론 중 표시되는 라운드별 발언은 사용자에게 보여주기만 하고 디스크엔 안 남는다. 종합 의견까지 다 본 다음에 다시 보고 싶으면 cmux 스크롤백을 뒤져야 하는데, 화면 폭 제한 때문에 길면 잘려있다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--save debate.md&lt;/code&gt; 같은 옵션으로 라운드별 발언을 마크다운으로 누적 저장하는 기능이 필요하다. 블로그 글감으로 모으거나, 나중에 의사결정 근거로 다시 보려면 필수.&lt;/p&gt;

&lt;h3 id=&quot;답변이-매우-길면-화면-위쪽이-스크롤되어-잘림&quot;&gt;답변이 매우 길면 화면 위쪽이 스크롤되어 잘림&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read-screen&lt;/code&gt;이 현재 보이는 화면만 캡처해서, 한 발언이 길면 위쪽이 스크롤로 사라진다. 지금은 발언당 한 단락(3-5문장)으로 제한해서 회피하는데, 본문이 결정적으로 중요한 토론에선 스크롤백 옵션을 활용해야 한다.&lt;/p&gt;

&lt;h3 id=&quot;새-cli-추가할-때마다-케이스별-디버깅&quot;&gt;새 CLI 추가할 때마다 케이스별 디버깅&lt;/h3&gt;

&lt;p&gt;Gemini의 paste-bracket처럼 CLI마다 입력 처리 방식이 미묘하게 다르다. Qwen Code, Aider, OpenCode 같은 다른 CLI를 추가하면 또 새로운 우회 트릭이 필요할 가능성이 높다.&lt;/p&gt;

&lt;h3 id=&quot;각-cli-안에서-일어나는-동작을-제어할-수-없음&quot;&gt;각 CLI 안에서 일어나는 동작을 제어할 수 없음&lt;/h3&gt;

&lt;p&gt;이게 가장 큰 한계다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fight&lt;/code&gt;는 토론을 위해 메시지를 주고받는 정도까지만 다룬다. 그런데 Claude Code, Codex CLI, Gemini CLI는 단순 채팅 도구가 아니라 각자 도구를 호출하고, 파일을 읽고, 셸 명령을 실행하는 에이전트다. 토론 도중에 상대 AI가 갑자기 파일을 수정하거나 빌드를 돌리거나 외부 API를 호출해도 막을 방법이 없다.&lt;/p&gt;

&lt;p&gt;예를 들어 Codex에게 &lt;em&gt;이 PR의 보안 리스크를 토론해줘&lt;/em&gt;라고 보냈을 때, Codex가 토론 답변만 주는 게 아니라 진짜로 PR 파일을 열고 grep을 돌리고, 심지어 파일을 수정해버릴 수도 있다. 각 CLI의 권한 설정(approval mode, sandbox)이 토론 컨텍스트와 무관하게 그대로 적용되는 것이다.&lt;/p&gt;

&lt;p&gt;이상적으론 토론 모드에 들어갔을 때 모든 참여 CLI를 &lt;em&gt;읽기 전용 + 토론만&lt;/em&gt;으로 일시 전환할 수 있어야 한다. 하지만 cmux를 통한 키 입력만으로는 그런 모드 전환이 불가능하다. 각 CLI가 토론용 안전 모드를 노출해주거나, 토론 시작 시 권한을 명시적으로 잠그는 명령을 자동 주입하는 식의 접근이 필요해 보인다.&lt;/p&gt;

&lt;p&gt;지금은 운영 측면에서 &lt;em&gt;토론 주제는 코드 변경을 유발하지 않는 것으로만 한정한다&lt;/em&gt;는 사용자 측 약속에 의존하는 상태다.&lt;/p&gt;

&lt;h2 id=&quot;다음에-해보고-싶은-것&quot;&gt;다음에 해보고 싶은 것&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--vs gemini&lt;/code&gt; 같은 옵션으로 선택 단계 스킵&lt;/li&gt;
  &lt;li&gt;토론 내용을 마크다운으로 자동 저장 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--save debate.md&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;4자, 5자 토론 — Claude 두 개를 캐릭터 다르게(시니어/주니어)&lt;/li&gt;
  &lt;li&gt;토론 분위기 모드 — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--devils-advocate&lt;/code&gt;로 의도적 반대 입장 강제&lt;/li&gt;
  &lt;li&gt;cmux 푸터 의존 제거 — surface 라벨링 방식으로 전환&lt;/li&gt;
  &lt;li&gt;토론 안전 모드 — 토론 시작 전 각 CLI에 &lt;em&gt;파일 수정 금지, 셸 실행 금지&lt;/em&gt; 같은 가드레일을 미리 깔아두는 방식&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;PAL MCP에서 시작해서 cmux를 거쳐 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fight&lt;/code&gt;까지 한 흐름으로 만들면서, 결국 AI 도구는 잘 조립하는 게 핵심이라는 걸 다시 느꼈다. 각 도구의 한계를 이해하고 나면, 그것들을 엮어서 새로운 워크플로우를 만들 수 있다.&lt;/p&gt;

&lt;p&gt;요즘은 PR 리뷰할 때도 이걸 쓴다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;/fight 이 PR의 핵심 리스크는 뭐고, 머지해도 되는지?
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;세 모델이 각자 다른 각도에서 보고 합의하는 결론은, 한 모델 의견보다 훨씬 신뢰가 간다.&lt;/p&gt;

&lt;h2 id=&quot;다음-편-예고&quot;&gt;다음 편 예고&lt;/h2&gt;

&lt;p&gt;여기까지가 &lt;em&gt;내 손으로 만들어보기&lt;/em&gt;까지의 이야기다. 한 가지 마음에 걸리는 게 있었다 — 스크립트 두세 개 + 슬래시 커맨드 마크다운으로만 굴러가는 구조라, &lt;em&gt;다른 머신에서 다시 쓰려면 또 셋업해야 한다&lt;/em&gt;는 점. 그리고 비개발자 친구가 쓸 수 있을까? 라는 질문에 답이 안 나왔다.&lt;/p&gt;

&lt;p&gt;그래서 다음 편에서는 이걸 &lt;strong&gt;Claude Code 플러그인으로 패키징&lt;/strong&gt;한 과정을 다룬다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;마켓플레이스 메타데이터 vs 플러그인 메타데이터의 구분&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/plugin:command&lt;/code&gt; 콜론 네임스페이스의 의미&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:install&lt;/code&gt; 같은 셋업 명령으로 &lt;em&gt;비개발자도 한 번에&lt;/em&gt; 설치되도록 만들기&lt;/li&gt;
  &lt;li&gt;환경 검증 + npm 자동 설치 자동화 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;omx&lt;/code&gt;나 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mise&lt;/code&gt; 같은 도구가 왜 그렇게 동작하는지 직접 만들어봐야 안다)&lt;/li&gt;
  &lt;li&gt;Codex CLI와의 양방향 지원 (Codex Skill 시스템과의 차이)&lt;/li&gt;
  &lt;li&gt;v0.1 → v0.2 단계적 릴리즈 전략&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;레포: &lt;a href=&quot;https://github.com/dongsik93/crosstalk&quot;&gt;github.com/dongsik93/crosstalk&lt;/a&gt; — 패키징 완료 후 public 공개 (2편 참조).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;참고: 이 글에 등장하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fight&lt;/code&gt;는 &lt;em&gt;PoC 시점의 이름&lt;/em&gt;입니다. 이후 플러그인으로 패키징하면서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crosstalk&lt;/code&gt;으로 리네임했고, 본문의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/scripts/fight_bridge.sh&lt;/code&gt;도 현재는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/scripts/crosstalk_bridge.sh&lt;/code&gt;입니다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;참고-링크&quot;&gt;참고 링크&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/BeehiveInnovations/pal-mcp-server&quot;&gt;PAL MCP Server&lt;/a&gt; — 원래 출발점이었던 MCP 서버&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/manaflow-ai/cmux&quot;&gt;cmux&lt;/a&gt; — Ghostty 기반 멀티 에이전트 터미널&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.anthropic.com/en/docs/claude-code/slash-commands&quot;&gt;Claude Code Slash Commands 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 07 May 2026 03:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/05/07/crosstalk-1-multi-agent-debate/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/05/07/crosstalk-1-multi-agent-debate/</guid>
        
        <category>claude-code</category>
        
        <category>codex</category>
        
        <category>gemini</category>
        
        <category>cmux</category>
        
        <category>multi-agent</category>
        
        <category>slash-command</category>
        
        <category>tmux</category>
        
        <category>mcp</category>
        
        <category>crosstalk</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[AI] 2026 Stanford AI Index를 보며 미래에 대해 생각해보기</title>
        <description>&lt;h1 id=&quot;ai-2026-stanford-ai-index를-보며-미래에-대해-생각해보기&quot;&gt;[AI] 2026 Stanford AI Index를 보며 미래에 대해 생각해보기&lt;/h1&gt;

&lt;p&gt;Stanford HAI에서 2026 AI Index Report가 나왔다.&lt;/p&gt;

&lt;p&gt;처음에는 그냥 AI 업계 동향을 보려고 열었는데, 읽다 보니 개발자로서 꽤 현실적으로 생각해볼 지점이 많았다.&lt;/p&gt;

&lt;p&gt;특히 이번 리포트에서 계속 보이는 흐름은 AI는 더 빨리 좋아지고 있고, 더 많은 사람이 쓰고 있고, 더 많은 돈이 들어가고 있다는 것이다. 그런데 성능을 측정하는 기준이나 안전하게 쓰는 방법, 교육과 제도는 그 속도를 따라가지 못하고 있는 것 같다.&lt;/p&gt;

&lt;p&gt;그래서 이번 글은 리포트를 단순 요약하기보다, AI에 관심 있는 개발자 입장에서 앞으로 무엇을 생각해야 할지 정리해보려고 한다.&lt;/p&gt;

&lt;h2 id=&quot;ai-성능은-아직-정체가-아니다&quot;&gt;AI 성능은 아직 정체가 아니다&lt;/h2&gt;

&lt;p&gt;AI 성능이 이제 plateau에 들어간 것 아니냐는 이야기도 있었지만, Stanford HAI의 2026 AI Index는 반대로 말한다. plateau는 성장이 멈추고 평평해지는 구간을 말하는데, 리포트에서는 AI capability가 정체가 아니라 계속 가속 중이라고 본다.&lt;/p&gt;

&lt;p&gt;리포트에 따르면 2025년 notable frontier model의 90% 이상은 산업계에서 만들어졌고, 일부 모델은 PhD 수준 과학 질문, 멀티모달 추론, 수학 대회 수준 문제에서 인간 기준선을 넘거나 근접했다. SWE-bench Verified 같은 코딩 벤치마크에서는 성능이 1년 사이 60% 수준에서 거의 100%에 가까워졌다고 정리한다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report&quot;&gt;Stanford HAI, 2026 AI Index Report&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;개발자 입장에서 이건 단순히 AI가 코딩을 잘한다는 뜻만은 아닌 것 같다.&lt;/p&gt;

&lt;p&gt;내가 보기에는 개발자의 기본 생산성 기준이 올라간다는 뜻에 가깝다. 예전에는 CRUD 구현, API 연결, UI 상태 처리, 테스트 코드 초안 같은 작업을 얼마나 빠르게 하느냐가 생산성의 큰 부분이었다. 그런데 이런 일들은 점점 AI가 보조하기 쉬운 영역이 된다.&lt;/p&gt;

&lt;p&gt;그렇다면 개발자의 가치는 조금씩 다른 곳으로 이동하게 될 것 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;요구사항을 실제 시스템 구조로 해석하는 능력&lt;/li&gt;
  &lt;li&gt;제품의 제약을 고려해 AI 기능을 자연스럽게 녹이는 능력&lt;/li&gt;
  &lt;li&gt;생성된 코드가 아키텍처, 보안, 성능, UX에 맞는지 판단하는 능력&lt;/li&gt;
  &lt;li&gt;서버, 모델, 클라이언트 사이의 책임 경계를 설계하는 능력&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉 코드를 작성하는 사람에서 AI를 포함한 시스템을 설계하고 검증하는 사람으로 조금씩 이동하는 느낌이다.&lt;/p&gt;

&lt;h2 id=&quot;벤치마크-점수만-믿기-어려워진다&quot;&gt;벤치마크 점수만 믿기 어려워진다&lt;/h2&gt;

&lt;p&gt;AI 성능이 좋아지는 만큼, 벤치마크의 수명은 짧아지고 있다.&lt;/p&gt;

&lt;p&gt;Technical Performance 챕터에서는 frontier model이 Humanity’s Last Exam에서 1년 사이 30 percentage point 상승했고, 몇 년은 버틸 것 같던 평가가 몇 달 만에 포화되는 상황을 지적한다. 또 MMLU Math에서는 invalid question rate가 2%, GSM8K에서는 42%까지 나왔다는 리뷰도 소개한다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/technical-performance&quot;&gt;Stanford HAI, Technical Performance&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;이건 개발할 때도 꽤 중요한 힌트다.&lt;/p&gt;

&lt;p&gt;앞으로 제품에 AI 기능을 넣을 때 이 모델이 벤치마크에서 1등이라는 것만으로는 충분하지 않다. 실제 제품에서는 이런 것들을 같이 봐야 한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;한국어 입력에서의 안정성&lt;/li&gt;
  &lt;li&gt;실제 유저 플로우에서 실패하는 케이스&lt;/li&gt;
  &lt;li&gt;hallucination이 생겼을 때의 복구 UX&lt;/li&gt;
  &lt;li&gt;네트워크 지연, 토큰 비용, 개인정보 처리&lt;/li&gt;
  &lt;li&gt;모델 업데이트 후 기존 기능에 생길 수 있는 영향&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실제 서비스에는 도메인, 데이터, 권한, 개인정보, 장애 상황 같은 현실적인 제약이 있다. 그래서 모델 자체의 평균 성능보다 내 제품에서 어떻게 실패하는지를 보는 게 더 중요해질 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;ai는-똑똑하지만-들쭉날쭉하다&quot;&gt;AI는 똑똑하지만 들쭉날쭉하다&lt;/h2&gt;

&lt;p&gt;이번 리포트에서 가장 인상적인 표현 중 하나는 jagged intelligence다.&lt;/p&gt;

&lt;p&gt;예를 들어 Gemini Deep Think는 2025년 International Mathematical Olympiad에서 gold 수준의 35점을 기록했지만, ClockBench에서 최고 모델의 아날로그 시계 판독 정확도는 50.6%였고 인간 기준선은 90.1%였다. OSWorld에서는 AI agent가 운영체제 위의 실제 컴퓨터 작업을 수행하는 정확도가 약 12%에서 66.3%까지 올랐지만, 여전히 구조화된 benchmark에서도 세 번 중 한 번은 실패한다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/technical-performance&quot;&gt;Stanford HAI, Technical Performance&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;이 부분을 보면 AI가 어떤 영역에서는 엄청 똑똑해 보이지만, 다른 영역에서는 이상하게 기본적인 실수를 하는 이유를 어느 정도 이해할 수 있다.&lt;/p&gt;

&lt;p&gt;제품에 AI 기능을 넣을 때도 마찬가지다. 어떤 기능에서는 사람보다 훨씬 좋은 결과를 보여주다가, 아주 기초적인 입력이나 edge case에서 이상한 실패를 할 수 있다. 그래서 AI 기능을 설계할 때는 다음 원칙이 중요해질 것 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AI 결과를 바로 실행하지 않고, 중요한 액션에는 confirmation을 둔다.&lt;/li&gt;
  &lt;li&gt;모델 출력은 domain rule로 한 번 더 검증한다.&lt;/li&gt;
  &lt;li&gt;실패했을 때 사용자가 직접 수정할 수 있는 UI를 둔다.&lt;/li&gt;
  &lt;li&gt;AI가 알아서 해주는 구조보다 AI가 초안을 만들고 사람이 결정하는 구조에 가깝게 설계한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이건 UX 설계이기도 하고, 안전장치이기도 하다.&lt;/p&gt;

&lt;h2 id=&quot;ai-투자는-아직-식지-않았다&quot;&gt;AI 투자는 아직 식지 않았다&lt;/h2&gt;

&lt;p&gt;Economy 챕터에 따르면 2025년 글로벌 corporate AI investment는 전년 대비 130% 증가한 581.7 billion dollar를 기록했다. private investment는 344.7 billion dollar로 127.5% 증가했고, generative AI는 200% 이상 성장하며 private AI funding의 거의 절반을 차지했다. 미국의 private AI investment는 285.9 billion dollar로 중국의 12.4 billion dollar보다 23배 이상 컸다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/economy&quot;&gt;Stanford HAI, Economy&lt;/a&gt;, &lt;a href=&quot;https://hai.stanford.edu/news/inside-the-ai-index-12-takeaways-from-the-2026-report&quot;&gt;Shana Lynch, 2026&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;투자 숫자를 보면 AI가 단기 유행으로 끝날 가능성은 낮아 보인다. 물론 버블이 섞여 있을 수는 있다. 하지만 회사들이 실제로 AI 제품, 인프라, 데이터, 인재에 돈을 쓰고 있다는 건 분명하다.&lt;/p&gt;

&lt;p&gt;개발자 입장에서는 이걸 이렇게 해석할 수 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AI 기능이 들어간 제품이 더 많아진다.&lt;/li&gt;
  &lt;li&gt;기존 서비스에도 요약, 검색, 추천, 자동화, 챗봇 기능이 붙는다.&lt;/li&gt;
  &lt;li&gt;프론트엔드와 백엔드는 모델과 사용자를 연결하는 인터페이스가 된다.&lt;/li&gt;
  &lt;li&gt;AI 기능을 잘 설계하고 운영하는 개발자의 수요가 생긴다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉 AI를 연구하는 사람이 아니더라도, AI를 제품에 연결하는 개발자는 점점 더 필요해질 수 있다.&lt;/p&gt;

&lt;h2 id=&quot;노동시장-변화는-특히-주니어에게-먼저-올-수-있다&quot;&gt;노동시장 변화는 특히 주니어에게 먼저 올 수 있다&lt;/h2&gt;

&lt;p&gt;가장 민감한 부분은 일자리다.&lt;/p&gt;

&lt;p&gt;2026 AI Index는 소프트웨어 개발자 22-25세 고용이 2024년 이후 거의 20% 감소했다고 정리한다. 또한 survey 응답 조직의 3분의 1은 향후 1년 안에 workforce reduction을 예상했다. 다만 리포트는 아직 overall employment data에서 대규모 실업이 나타난 것은 아니라고 선을 긋는다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/economy&quot;&gt;Stanford HAI, Economy&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;솔직히 이 부분은 좀 무섭게 느껴졌다. AI가 개발자를 바로 대체한다는 식으로 단순하게 볼 문제는 아니지만, 신입 개발자가 처음 맡던 일의 일부가 AI로 빠르게 대체될 수 있다는 점은 꽤 현실적으로 다가온다.&lt;/p&gt;

&lt;p&gt;다만 미국 쪽 데이터를 보면 또 다른 흐름도 있는 것 같다. 내가 미국주식 블로그에서 정리했던 시타델 리포트 쪽은 전체 고용이 정체된 와중에도 AI 개발자와 소프트웨어 엔지니어 채용이 늘어난다는 쪽에 가까웠다. (&lt;a href=&quot;https://miju30.com/ai-developer-hiring-citadel-report-202605/&quot;&gt;30초 미주, 시타델 리포트 정리&lt;/a&gt;) 결국 개발자 수요가 그냥 사라진다기보다는, 신입 개발자에게 기대하던 일과 AI를 다룰 수 있는 개발자에게 기대하는 일이 갈라지고 있는 것 같다.&lt;/p&gt;

&lt;p&gt;나는 이걸 개발자가 끝났다는 식으로 읽지는 않는다.&lt;/p&gt;

&lt;p&gt;다만 신입 개발자의 시장 진입 난이도는 올라갈 수 있다고 본다. AI가 boilerplate 코드, 간단한 버그 수정, 문서 작성, 테스트 초안, UI 초안 등을 잘 처리하면, 회사 입장에서는 주니어에게 기대하던 일부 업무가 자동화될 수 있다.&lt;/p&gt;

&lt;p&gt;그러면 개발자는 더 빨리 다음 단계 역량을 보여줘야 한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;주어진 ticket을 구현하는 수준을 넘어 문제를 정의하는 능력&lt;/li&gt;
  &lt;li&gt;AI output을 검토하고 production 수준으로 다듬는 능력&lt;/li&gt;
  &lt;li&gt;서비스 전체 구조와 데이터 흐름을 설명하는 능력&lt;/li&gt;
  &lt;li&gt;성능, 보안, 장애 상황까지 고려하는 능력&lt;/li&gt;
  &lt;li&gt;비개발자와 요구사항을 맞추고 제품 판단을 하는 능력&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;개발자에게는 단순히 구현만 하는 사람에서 제품 경험과 시스템 품질을 같이 보는 사람으로 포지션을 넓히는 게 중요해질 것 같다.&lt;/p&gt;

&lt;h2 id=&quot;ai-기능은-인프라와-비용-문제이기도-하다&quot;&gt;AI 기능은 인프라와 비용 문제이기도 하다&lt;/h2&gt;

&lt;p&gt;AI가 제품 안에 들어온다고 해서 모든 것이 클라이언트나 한 서버 안에서 해결되는 것은 아니다. 오히려 뒤쪽 인프라 의존성은 더 커진다.&lt;/p&gt;

&lt;p&gt;Research and Development 챕터에 따르면 2025년 글로벌 AI compute capacity는 2022년 이후 연 3.3배씩 성장해 17.1 million H100-equivalents에 도달했다. 미국은 5,427개의 data center를 보유해 다른 어떤 국가보다 많고, TSMC는 leading AI chip 대부분을 제조하는 핵심 병목으로 언급된다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/research-and-development&quot;&gt;Stanford HAI, Research and Development&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;일반 개발자 입장에서는 이 숫자가 멀게 느껴질 수 있다. 하지만 제품 관점에서는 꽤 직접적일 수 있다.&lt;/p&gt;

&lt;p&gt;AI 기능을 제품에 붙이면 결국 아래 내용을 같이 봐야만 한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;클라이언트에서 처리할지 서버 API를 사용할지&lt;/li&gt;
  &lt;li&gt;개인정보가 포함된 입력을 어디까지 외부로 보낼 수 있는지&lt;/li&gt;
  &lt;li&gt;사용자가 늘어났을 때 추론 비용을 감당할 수 있는지&lt;/li&gt;
  &lt;li&gt;응답 지연이 UX를 망치지 않는지&lt;/li&gt;
  &lt;li&gt;모델 제공사가 바뀌었을 때 구조를 갈아엎지 않아도 되는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예전에는 API만 잘 붙이면 되는 기능도 많았다. 이제는 AI 기능 하나를 넣어도 compute cost, latency, privacy, fallback, observability를 같이 봐야 한다. 특정 영역만 보는 개발자보다 제품과 인프라를 함께 이해하는 개발자가 더 강해질 수 있다는 뜻이기도 하다.&lt;/p&gt;

&lt;h2 id=&quot;책임-있는-ai는-성능보다-늦다&quot;&gt;책임 있는 AI는 성능보다 늦다&lt;/h2&gt;

&lt;p&gt;Responsible AI 챕터를 보면 조금 찝찝한 부분도 있다. capability benchmark 보고는 활발하지만, responsible AI benchmark 보고는 여전히 부족하다. AI Incident Database에 기록된 incident는 2024년 233건에서 2025년 362건으로 늘었다. Foundation Model Transparency Index의 평균 점수도 2024년 58에서 2025년 40으로 떨어졌다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/responsible-ai&quot;&gt;Stanford HAI, Responsible AI&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;성능은 올라가는데 투명성과 안전성은 같이 올라가지 않는다.&lt;/p&gt;

&lt;p&gt;이건 개발자에게도 남의 일이 아니다. AI 기능이 제품에 들어가면 사용자는 그것을 모델 회사의 기능이 아니라 해당 제품의 기능으로 경험한다. 요약이 틀리거나, 추천이 편향되거나, 민감한 정보를 잘못 처리하면 최종 책임은 제품 경험에 남는다.&lt;/p&gt;

&lt;p&gt;제품을 만들 때는 특히 이런 부분이 신경 쓰인다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;개인정보가 포함된 입력을 모델 API로 보낼 때의 동의와 고지&lt;/li&gt;
  &lt;li&gt;사용자가 AI 결과와 사람이 작성한 결과를 구분할 수 있는지&lt;/li&gt;
  &lt;li&gt;AI 추천이 중요한 결정을 유도하지 않는지&lt;/li&gt;
  &lt;li&gt;틀린 결과를 수정하거나 신고할 수 있는지&lt;/li&gt;
  &lt;li&gt;모델 출력이 로그에 남을 때 민감정보가 섞이지 않는지&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이제 AI 기능은 단순한 API 호출이 아니라 제품 리스크가 된다.&lt;/p&gt;

&lt;h2 id=&quot;사람들은-ai를-기대하면서도-불안해한다&quot;&gt;사람들은 AI를 기대하면서도 불안해한다&lt;/h2&gt;

&lt;p&gt;Public Opinion 챕터를 보면 전 세계적으로 AI 제품과 서비스가 단점보다 장점이 많다고 보는 비율은 2024년 55%에서 2025년 59%로 올랐다. 동시에 불안하다고 답한 비율도 52%로 증가했다. AI 전문가와 미국 대중의 시각 차이도 크다. 전문가의 73%는 AI가 사람들의 업무 방식에 긍정적 영향을 준다고 봤지만, 미국 대중은 23%만 그렇게 봤다. (&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/public-opinion&quot;&gt;Stanford HAI, Public Opinion&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;사용자는 AI를 기대하면서도 불안해한다.&lt;/p&gt;

&lt;p&gt;이걸 UX로 풀어야 한다. AI 기능을 크게 홍보하는 것보다, 사용자가 통제감을 느끼게 만드는 쪽이 더 중요해 보인다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AI가 무엇을 했는지 보여준다.&lt;/li&gt;
  &lt;li&gt;사용자가 적용 전 확인할 수 있게 한다.&lt;/li&gt;
  &lt;li&gt;되돌리기, 수정하기, 재생성하기를 쉽게 만든다.&lt;/li&gt;
  &lt;li&gt;민감한 영역에서는 자동 실행보다 제안 형태로 둔다.&lt;/li&gt;
  &lt;li&gt;AI가 판단했다는 느낌보다 AI가 도와줬다는 경험을 만든다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;제품에서는 이런 디테일이 신뢰를 만든다. AI가 끼어드는 순간을 조심스럽게 설계해야 한다.&lt;/p&gt;

&lt;h2 id=&quot;그래서-나는-뭘-준비해야-할까&quot;&gt;그래서 나는 뭘 준비해야 할까&lt;/h2&gt;

&lt;p&gt;2026 AI Index를 읽고, 개발자로서 준비해야겠다고 느낀 것은 대략 이 정도다.&lt;/p&gt;

&lt;p&gt;먼저 AI API를 실제 기능으로 연결하는 작은 프로젝트를 꾸준히 만들어봐야겠다. 단순 챗봇보다 실제 문제에 붙여보는 게 좋다. 예를 들면 개인 메모 요약, 소비 내역 분류, 운동 기록 피드백, 이미지 기반 입력 보조 같은 것들이다.&lt;/p&gt;

&lt;p&gt;그리고 evaluation을 공부하려고 한다. AI 기능은 동작한다는 것만으로 부족하다. 정확도, 실패율, 지연 시간, 비용, 사용자 수정률, 재시도율을 봐야 한다.&lt;/p&gt;

&lt;p&gt;개인정보와 보안 기준도 다시 봐야 한다. 권한, 저장소 암호화, network logging, analytics event 설계가 AI 기능과 만나면 더 민감해진다.&lt;/p&gt;

&lt;p&gt;UX 패턴도 따로 모아볼 생각이다. AI suggestion, streaming response, regenerate, citation, confidence, human confirmation 같은 패턴은 앞으로 자주 보게 될 것 같다.&lt;/p&gt;

&lt;p&gt;마지막으로 backend와 data 쪽 이해를 넓혀야겠다. 특정 클라이언트나 화면 구현만으로는 AI 제품을 끝까지 책임지기 어렵다. RAG, embedding, vector search, prompt versioning, model routing 같은 개념은 최소한 대화할 수 있을 정도로 알아야 한다.&lt;/p&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;

&lt;p&gt;2026 AI Index는 AI가 얼마나 대단해졌는지를 보여주는 리포트이기도 하지만, 동시에 아직 얼마나 불안정하고 불투명한지도 보여준다.&lt;/p&gt;

&lt;p&gt;나는 여기서 기회를 본다. AI 모델을 직접 만드는 사람이 아니어도(사실 그럴 능력은 안됨), AI를 사용자가 믿고 쓸 수 있는 제품 경험으로 바꾸는 일은 여전히 개발자의 영역이다.&lt;/p&gt;

&lt;p&gt;앞으로의 개발자는 UI를 만들고 API를 붙이는 것에서 더 나아가, AI의 실패와 불확실성을 제품 안에서 다루는 역할을 하게 될 것 같다. 그래서 지금 준비해야 할 것은 AI가 내 일을 뺏을까만 고민하는 것이 아니라, AI가 들어간 제품을 내가 얼마나 잘 만들 수 있을지 직접 실험해보는 일이라고 생각한다.&lt;/p&gt;

&lt;h2 id=&quot;참고&quot;&gt;참고&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report&quot;&gt;Stanford HAI - The 2026 AI Index Report&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/research-and-development&quot;&gt;Stanford HAI - Research and Development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/technical-performance&quot;&gt;Stanford HAI - Technical Performance&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/economy&quot;&gt;Stanford HAI - Economy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/responsible-ai&quot;&gt;Stanford HAI - Responsible AI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/ai-index/2026-ai-index-report/public-opinion&quot;&gt;Stanford HAI - Public Opinion&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hai.stanford.edu/news/inside-the-ai-index-12-takeaways-from-the-2026-report&quot;&gt;Stanford HAI - Inside the AI Index: 12 Takeaways from the 2026 Report&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/05/05/ai-index-2026-android-developer/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/05/05/ai-index-2026-android-developer/</guid>
        
        <category>ai</category>
        
        <category>developer</category>
        
        <category>career</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] Google AI Edge Portal 프라이빗 프리뷰 선정 및 소개</title>
        <description>&lt;h1 id=&quot;google-ai-edge-portal-프라이빗-프리뷰-합격&quot;&gt;Google AI Edge Portal 프라이빗 프리뷰 합격&lt;/h1&gt;

&lt;p&gt;요즘 사이드 프로젝트로 안드로이드 온디바이스 AI(On-device AI)를 붙여보려고 여러 모델과 프레임워크(MediaPipe, LiteRT 등)를 돌려보고 있었다.&lt;/p&gt;

&lt;p&gt;온디바이스 AI를 만지다 보면 꼭 부딪히는 벽이 하나 있다. &lt;em&gt;내 폰(최신 플래그십)에서는 잘 도는데, 다른 기기에서는 어떨까?&lt;/em&gt; 하는 하드웨어 파편화 문제다. NPU, GPU 가속은 기기마다 지원 여부와 칩셋(Snapdragon, Exynos 등)이 달라서, 개인이 수많은 기기를 사서 테스트하기란 불가능에 가깝다.&lt;/p&gt;

&lt;p&gt;그러던 중 구글에서 이 문제를 풀어줄 AI Edge Portal의 Private Preview(비공개 테스트)를 진행한다는 소식을 듣고 지원했는데, 오늘 승인 메일을 받았다.&lt;/p&gt;

&lt;h2 id=&quot;ai-edge-portal이란&quot;&gt;AI Edge Portal이란?&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;온디바이스 ML 모델을 위한 클라우드 기반 실제 기기 테스트 팜(Device Farm)&lt;/em&gt;이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/example-benchmark.png&quot; alt=&quot;Example Benchmark&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;이미지 출처: &lt;a href=&quot;https://ai.google.dev/edge&quot;&gt;Google AI Edge Portal 공식 홈페이지&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;구글 클라우드 콘솔(GCP) 안에서 작동한다. 개발자가 .tflite (또는 LiteRT) 모델을 업로드하면, 구글이 보유한 수백 대의 실제 안드로이드 기기 풀(Pool)에서 모델을 돌리고 벤치마크 결과를 돌려준다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/benchmark-creation-v2.gif&quot; alt=&quot;Benchmark Creation&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;벤치마크 생성 과정 예시 (출처: &lt;a href=&quot;https://ai.google.dev/edge&quot;&gt;Google AI Edge Portal 공식 홈페이지&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;공개된 소개 기준으로 가능한 테스트는 이렇다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;다양한 가속기 지원: CPU뿐 아니라 실제 기기의 GPU, NPU 가속기로 테스트&lt;/li&gt;
  &lt;li&gt;다양한 기기 풀: 수백 대의 실제 안드로이드 기기에서 모델 성능(Latency, Memory 등) 측정&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/img/in-post/benchmark-report-v2.gif&quot; alt=&quot;Benchmark Report&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;벤치마크 분석 리포트 예시 (출처: &lt;a href=&quot;https://ai.google.dev/edge&quot;&gt;Google AI Edge Portal 공식 홈페이지&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;이 모델이 갤럭시 S24 NPU에서는 얼마나 빠를까? 구형 기기에서는 메모리가 터지지 않을까?&lt;/em&gt; 하는 고민을 실제 기기 없이 클라우드에서 바로 검증할 수 있게 됐다.&lt;/p&gt;

&lt;h2 id=&quot;어떻게-신청하나&quot;&gt;어떻게 신청하나?&lt;/h2&gt;

&lt;p&gt;지금은 Private Preview 상태라 신청서를 낸 개발자/기업 중 심사를 거쳐 Allowlist에 추가해주는 방식이다.&lt;/p&gt;

&lt;p&gt;온디바이스 AI 앱을 개발 중이거나 TFLite/LiteRT 모델의 하드웨어 가속 성능을 재보고 싶다면 신청해볼 만하다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;신청 링크: 구글이 제공한 공식 Sign-up Form 링크를 확인하세요.&lt;/li&gt;
  &lt;li&gt;관련 정보: 구글 공식 블로그의 AI Edge 관련 포스트를 참고하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;마무리&quot;&gt;마무리&lt;/h2&gt;

&lt;p&gt;지금은 프라이빗 프리뷰 엠바고(NDA) 정책상 포털 내부 대시보드 화면이나 직접 돌려본 벤치마크 수치, 도구 사용법 등은 공개할 수 없다.&lt;/p&gt;

&lt;p&gt;다만 온디바이스 AI 개발자한테는 가뭄에 단비 같은 서비스다. 퍼블릭 프리뷰나 GA로 풀려서 엠바고가 해제되면, &lt;em&gt;실제 1B 모델 파인튜닝부터 AI Edge Portal을 활용한 NPU 최적화까지&lt;/em&gt; 이어지는 삽질기를 써볼 생각이다.&lt;/p&gt;

&lt;p&gt;안드로이드 온디바이스 AI 생태계가 생각보다 훨씬 빠르게 굴러가고 있다.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/27/android-ai-edge-portal/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/27/android-ai-edge-portal/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>on-device</category>
        
        <category>litert</category>
        
        <category>google-ai-edge</category>
        
        <category>private-preview</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] Gemma3/4 vs ML Kit GenAI</title>
        <description>&lt;h1 id=&quot;android-gemma34-vs-ml-kit-genai&quot;&gt;[Android] Gemma3/4 vs ML Kit GenAI&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;LiteRT로 직접 모델을 돌리다가 ML Kit GenAI로 갈아탄 이유&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;시작은-litert였다&quot;&gt;시작은 LiteRT였다&lt;/h2&gt;

&lt;p&gt;온디바이스 AI 텍스트/문서 요약을 구현할 때 처음 선택한 스택은 &lt;strong&gt;Google LiteRT-LM + Gemma3-1B&lt;/strong&gt;였다.&lt;/p&gt;

&lt;p&gt;선택 이유는 명확했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Google이 공식 지원하는 온디바이스 추론 런타임&lt;/li&gt;
  &lt;li&gt;int4 양자화로 584MB — 모바일 탑재 가능한 수준&lt;/li&gt;
  &lt;li&gt;Galaxy S26 Ultra GPU 백엔드 기준 &lt;strong&gt;2.8초 TTFT&lt;/strong&gt; 달성&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;실제로 성능은 괜찮았다. GPU 백엔드에서 2~3초면 문서 요약이 나왔고, Map-Reduce 패턴으로 긴 텍스트도 처리할 수 있었다.&lt;/p&gt;

&lt;p&gt;문제는 &lt;strong&gt;모델 배포&lt;/strong&gt;였다.&lt;/p&gt;

&lt;h2 id=&quot;litert--gemma-방식의-현실적인-문제&quot;&gt;LiteRT + Gemma 방식의 현실적인 문제&lt;/h2&gt;

&lt;h3 id=&quot;모델-파일-크기&quot;&gt;모델 파일 크기&lt;/h3&gt;

&lt;p&gt;Gemma3-1B int4: &lt;strong&gt;584MB&lt;/strong&gt;
Gemma4-E2B int4: &lt;strong&gt;약 2.4GB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;앱 본체를 30MB로 유지하면서 2.4GB 모델을 어떻게 배포할까? Play Asset Delivery를 검토했지만 2GB 넘는 파일 처리가 불안정하고, CDN 직접 배포로 가면 서버 비용과 운영 부담이 생긴다.&lt;/p&gt;

&lt;p&gt;사용자 입장에서도 부담이 크다. Wi-Fi 환경에서도 500MB~2.4GB를 내려받아야 기능을 쓸 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;프롬프트-커스터마이징의-함정&quot;&gt;프롬프트 커스터마이징의 함정&lt;/h3&gt;

&lt;p&gt;LiteRT를 쓰면 프롬프트를 직접 설계할 수 있다. 처음엔 장점이라 생각했는데, 막상 해보니 생각보다 손이 많이 갔다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;한국어 문서 특화 요약을 위한 프롬프트 튜닝&lt;/li&gt;
  &lt;li&gt;챗봇 말투 제거 (“알겠습니다! 요약해드리겠습니다” 같은 표현)&lt;/li&gt;
  &lt;li&gt;불릿 포인트 형식 강제&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;파인튜닝 없이 베이스 모델만으로는 품질이 들쭉날쭉했다. 파인튜닝 파이프라인을 구축하고 실제로 돌려보니 개선은 됐지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.litertlm&lt;/code&gt; 변환 툴이 아직 Early Access 상태라 변환 과정이 순탄하지 않았다.&lt;/p&gt;

&lt;h2 id=&quot;ml-kit-genai를-발견하다&quot;&gt;ML Kit GenAI를 발견하다&lt;/h2&gt;

&lt;p&gt;그러던 중 ML Kit의 &lt;strong&gt;GenAI Summarization API&lt;/strong&gt;를 접했다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.mlkit:genai-summarization:1.0.0-beta1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;핵심 차이는 단 하나다 — &lt;strong&gt;모델을 앱이 관리하지 않는다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ML Kit GenAI는 Android OS에 내장된 AICore가 모델을 관리한다. 앱은 API만 호출하면 된다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SummarizerOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setInputType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;InputType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ARTICLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setOutputType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OutputType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;THREE_BULLETS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setLanguage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;KOREAN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;summarizer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Summarization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;두-방식-직접-비교&quot;&gt;두 방식 직접 비교&lt;/h2&gt;

&lt;p&gt;같은 텍스트/문서로 두 방식을 테스트했다.&lt;/p&gt;

&lt;h3 id=&quot;테스트-환경&quot;&gt;테스트 환경&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;기기: Galaxy S26 Ultra&lt;/li&gt;
  &lt;li&gt;텍스트 길이: 약 2,000자 (실제 사용되는 긴 텍스트 문서)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;ttftfirst-token-time-비교&quot;&gt;TTFT(First Token Time) 비교&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;방식&lt;/th&gt;
      &lt;th&gt;TTFT&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;LiteRT + Gemma3-1B (GPU)&lt;/td&gt;
      &lt;td&gt;약 490ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ML Kit GenAI&lt;/td&gt;
      &lt;td&gt;약 4,400ms&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;속도는 LiteRT가 압도적이다.&lt;/p&gt;

&lt;h3 id=&quot;요약-품질-비교&quot;&gt;요약 품질 비교&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LiteRT + Gemma3-1B (프롬프트 튜닝)&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;• 3월 24일 오후 2시 전략기획 회의 예정
• 참석자: 개발팀, 마케팅팀 필수 참석
• 사전 자료 22일까지 공유 요청
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;ML Kit GenAI&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;• 3월 24일 오후 2시, 6층 대회의실에서 전략기획 회의 진행 예정
• 개발팀, 마케팅팀 전원 필수 참석 (재택 근무자 화상 연결)
• 발표 자료 및 분기 실적 데이터 3월 22일까지 공유 필요
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;내용의 밀도가 다르다. GenAI 결과가 더 구체적이고 누락이 적었다.&lt;/p&gt;

&lt;p&gt;개인 정보나 민감한 내용이 담긴 경우 GenAI 내부 정책 필터로 요약이 거부되는 경우도 있었는데, 이건 어쩔 수 없는 한계다.&lt;/p&gt;

&lt;h2 id=&quot;왜-genai를-선택했나&quot;&gt;왜 GenAI를 선택했나&lt;/h2&gt;

&lt;p&gt;결국 가장 큰 이유는 &lt;strong&gt;배포 복잡도&lt;/strong&gt;다.&lt;/p&gt;

&lt;p&gt;LiteRT 방식은 모델 파일을 직접 배포, 관리, 업데이트해야 한다. Gemma3(584MB)와 Gemma4(2.4GB)를 안정적으로 배포하는 인프라를 만드는 것 자체가 상당한 엔지니어링 비용이다.&lt;/p&gt;

&lt;p&gt;반면 GenAI는 OS가 모델을 관리한다. 앱 입장에서는 API 호출 한 줄로 끝난다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;항목&lt;/th&gt;
      &lt;th&gt;LiteRT + Gemma&lt;/th&gt;
      &lt;th&gt;ML Kit GenAI&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;TTFT&lt;/td&gt;
      &lt;td&gt;490ms&lt;/td&gt;
      &lt;td&gt;4,400ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;요약 품질&lt;/td&gt;
      &lt;td&gt;프롬프트 튜닝에 따라 가변&lt;/td&gt;
      &lt;td&gt;안정적으로 높음&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;모델 용량&lt;/td&gt;
      &lt;td&gt;앱/서버에서 직접 배포&lt;/td&gt;
      &lt;td&gt;OS 관리 (앱 부담 없음)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;기기 지원&lt;/td&gt;
      &lt;td&gt;GPU 지원 기기 (S22 이상)&lt;/td&gt;
      &lt;td&gt;Android 10+, Pixel 9 이상 우선&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;프롬프트 제어&lt;/td&gt;
      &lt;td&gt;완전 자유&lt;/td&gt;
      &lt;td&gt;OutputType/Language 수준&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;오프라인&lt;/td&gt;
      &lt;td&gt;가능&lt;/td&gt;
      &lt;td&gt;AICore 최초 초기화 필요&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;정책 필터&lt;/td&gt;
      &lt;td&gt;없음&lt;/td&gt;
      &lt;td&gt;민감 콘텐츠 거부 가능&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;속도 면에서는 LiteRT가 훨씬 낫다. 하지만 현시점에서 GenAI가 주는 &lt;strong&gt;배포 단순성과 품질 안정성&lt;/strong&gt;이 더 매력적이었다.&lt;/p&gt;

&lt;h2 id=&quot;현재-구조&quot;&gt;현재 구조&lt;/h2&gt;

&lt;p&gt;두 방식을 공존시키는 쪽으로 방향을 잡았다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;GenAI (내장 모델)&lt;/strong&gt;: 기기 지원 여부 체크 후 노출. 설치 부담 없음.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gemma3-1B (500MB 다운로드)&lt;/strong&gt;: 더 넓은 기기 지원, 빠른 응답 속도.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gemma4-E2B (2.4GB 다운로드)&lt;/strong&gt;: 최고 품질, 고사양 기기 타겟.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;사용자가 설정에서 모델을 직접 선택할 수 있게 했다. GenAI를 지원하지 않는 기기는 해당 옵션 자체가 보이지 않는다.&lt;/p&gt;

&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;

&lt;p&gt;TTFT 490ms vs 4,400ms라는 수치만 보면 LiteRT가 압승이다. 하지만 온디바이스 AI를 서비스로 제공할 때 속도가 전부는 아니다.&lt;/p&gt;

&lt;p&gt;모델 배포, 업데이트, 기기 호환성, 품질 안정성 — 이 모든 걸 고려하면 GenAI 같은 OS 레벨 지원이 장기적으로 훨씬 지속 가능한 선택이다.&lt;/p&gt;

&lt;p&gt;다만 GenAI는 아직 beta이고, 지원 기기 범위가 제한적이다. 당분간은 두 방식을 병행하다가 GenAI 커버리지가 넓어지면 자연스럽게 단일화할 계획이다.&lt;/p&gt;
</description>
        <pubDate>Sat, 18 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/18/android-on-device-ai-model-comparison/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/18/android-on-device-ai-model-comparison/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>on-device</category>
        
        <category>gemma</category>
        
        <category>mlkit</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] 온디바이스 LLM으로 긴 문서 요약하기</title>
        <description>&lt;h1 id=&quot;android-온디바이스-llm으로-긴-문서-요약하기&quot;&gt;[Android] 온디바이스 LLM으로 긴 문서 요약하기&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;온디바이스 LLM은 컨텍스트 윈도우가 좁다. 긴 문서를 어떻게 요약할까?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;배경&quot;&gt;배경&lt;/h2&gt;

&lt;p&gt;온디바이스 sLLM(소형 언어 모델)을 활용해 텍스트/문서 요약 기능을 개발하던 중 현실적인 한계에 부딪혔다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;모델 컨텍스트 윈도우 제한.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gemma3-1B 기준으로 안전하게 처리할 수 있는 입력 토큰은 약 1024~2048 수준이다. 그런데 실제 다루는 문서는 길이가 길어지면 10,000자를 훌쩍 넘기기도 한다. 긴 문서를 그대로 넣으면 모델이 중간을 잘라버리거나, 최악의 경우 추론 자체가 실패한다.&lt;/p&gt;

&lt;p&gt;해결 방법을 찾다가 &lt;strong&gt;Map-Reduce&lt;/strong&gt; 패턴을 적용했다.&lt;/p&gt;

&lt;h2 id=&quot;map-reduce-요약이란&quot;&gt;Map-Reduce 요약이란&lt;/h2&gt;

&lt;p&gt;원래 분산 처리 개념이지만, LLM 요약에 그대로 적용된다.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;긴 텍스트
    ↓ [Map 단계]
청크1 요약 / 청크2 요약 / 청크3 요약
    ↓ [Reduce 단계]
부분 요약들을 합쳐서 최종 요약
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;각 청크는 모델이 처리할 수 있는 크기로 나누고, 청크별로 먼저 요약한 뒤, 그 결과를 다시 합쳐 최종 요약을 생성한다.&lt;/p&gt;

&lt;h2 id=&quot;구현-방식&quot;&gt;구현 방식&lt;/h2&gt;

&lt;h3 id=&quot;1-html-전처리--textextractor&quot;&gt;1. HTML 전처리 — TextExtractor&lt;/h3&gt;

&lt;p&gt;문서 본문이 HTML인 경우 태그 노이즈가 심해서 별도 모듈로 추출 처리를 했다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, 인라인 CSS 제거&lt;/li&gt;
  &lt;li&gt;시그니처 패턴 감지 후 제거 (인용 블록 포함)&lt;/li&gt;
  &lt;li&gt;링크 URL은 제거하고 텍스트만 유지&lt;/li&gt;
  &lt;li&gt;연속 공백/개행 정리&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;최종적으로 &lt;strong&gt;순수 텍스트&lt;/strong&gt;만 남기는 파이프라인이다.&lt;/p&gt;

&lt;h3 id=&quot;2-균등-분할&quot;&gt;2. 균등 분할&lt;/h3&gt;

&lt;p&gt;추출된 텍스트를 &lt;strong&gt;4000자&lt;/strong&gt; 단위로 균등 분할한다.&lt;/p&gt;

&lt;p&gt;단순히 4000자마다 자르면 문장 중간이 잘릴 수 있어서, 마지막 문장 경계(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;)를 찾아서 자른다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;splitIntoChunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunkSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunkSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;chunks&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mutableListOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;minOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunkSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;boundary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;takeIf&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;takeIf&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boundary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boundary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunks&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-map-단계--청크별-요약&quot;&gt;3. Map 단계 — 청크별 요약&lt;/h3&gt;

&lt;p&gt;각 청크에 요약 프롬프트를 붙여 순서대로 추론한다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;chunkSummaries&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapIndexed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;prompt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildChunkPrompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;llmEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;summarize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;프롬프트는 청크가 1개면 단순 요약 지시, 여러 개면 “이 문서의 {index+1}/{total} 부분입니다. 핵심 내용을 요약하세요.” 식으로 문맥을 제공한다.&lt;/p&gt;

&lt;h3 id=&quot;4-reduce-단계--최종-통합-요약&quot;&gt;4. Reduce 단계 — 최종 통합 요약&lt;/h3&gt;

&lt;p&gt;청크 요약들을 합쳐 다시 한 번 요약 요청을 보낸다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;combined&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunkSummaries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joinToString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;finalSummary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;llmEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;summarize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;buildReducePrompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;combined&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;청크가 1개뿐이면 Reduce 단계를 건너뛰고 Map 결과를 그대로 반환한다.&lt;/p&gt;

&lt;h2 id=&quot;실측-결과&quot;&gt;실측 결과&lt;/h2&gt;

&lt;p&gt;Galaxy S26 Ultra (Snapdragon 8 Elite, GPU 백엔드) 기준:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;문서 길이&lt;/th&gt;
      &lt;th&gt;청크 수&lt;/th&gt;
      &lt;th&gt;총 처리 시간&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;3,000자 이하&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;약 2.8초&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5,000자&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;약 5.5초&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;12,000자&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;약 12초&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;12,000자짜리 긴 문서도 12초 안에 처리됐다. 문서 본문이 로딩되는 1~2초와 AI 추론이 병렬로 진행되기 때문에 사용자 체감 대기시간은 이보다 짧다.&lt;/p&gt;

&lt;h2 id=&quot;트레이드오프&quot;&gt;트레이드오프&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;길이 제한 없이 긴 문서 처리 가능&lt;/li&gt;
  &lt;li&gt;각 청크를 독립적으로 요약하므로 앞부분 정보 소실 최소화&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;청크 경계에서 문맥이 끊길 수 있다&lt;/li&gt;
  &lt;li&gt;처리 시간이 청크 수에 비례해서 증가한다&lt;/li&gt;
  &lt;li&gt;Reduce 단계에서 부분 요약들을 다시 합치면 중복 표현이 생기기도 한다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;중복 표현 문제는 Reduce 프롬프트에 “중복 내용은 제거하고 핵심만 남겨라”는 지시를 추가해서 어느 정도 완화했다.&lt;/p&gt;

&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;

&lt;p&gt;온디바이스 LLM의 컨텍스트 한계를 우회하는 Map-Reduce 패턴은 생각보다 단순하게 구현 가능하다. 핵심은 두 가지다:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;HTML 전처리를 제대로 해야 한다&lt;/strong&gt; — 태그 노이즈가 많으면 모델이 엉뚱한 걸 요약한다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;청크 경계를 문장 단위로 잘라야 한다&lt;/strong&gt; — 단어 중간을 자르면 품질이 급격히 떨어진다&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;4000자 단위 분할은 Gemma3-1B 기준으로 튜닝된 값이다. 모델마다 컨텍스트 윈도우가 다르니 직접 실험해보는 것을 권장한다.&lt;/p&gt;
</description>
        <pubDate>Fri, 17 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/17/android-on-device-ai-map-reduce/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/17/android-on-device-ai-map-reduce/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>on-device</category>
        
        <category>map-reduce</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] 온디바이스 AI 개발기 - 6편: Play Asset Delivery로 모델 배포하기</title>
        <description>&lt;h1 id=&quot;android-온디바이스-ai-개발기---6편-play-asset-delivery로-모델-배포하기&quot;&gt;[Android] 온디바이스 AI 개발기 - 6편: Play Asset Delivery로 모델 배포하기&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;1GB짜리 모델 파일, 앱에 번들링할 수는 없다. 그럼 어떻게 배포하나.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;이-글은&quot;&gt;이 글은&lt;/h2&gt;

&lt;p&gt;5편에서 1B 모델의 실제 한계를 정리했다. 이번엔 모델 파일 자체를 어떻게 앱에 제공하는지를 다룬다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemma3-1b-it-int4.litertlm&lt;/code&gt;은 약 1GB다. APK나 AAB에 번들링하는 건 처음부터 불가능하다. AAB의 base module 압축 다운로드 크기 제한이 200MB이기 때문이다.&lt;/p&gt;

&lt;p&gt;방법은 크게 두 가지다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;직접 다운로드&lt;/strong&gt; — 앱 서버나 CDN에서 직접 받아오기&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Play Asset Delivery&lt;/strong&gt; — Google Play 인프라를 활용해 배포&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;google-ai-gallery를-보면-답이-보인다&quot;&gt;Google AI Gallery를 보면 답이 보인다&lt;/h2&gt;

&lt;p&gt;얘기를 시작하기 전에 Google AI Gallery 얘기를 잠깐 하고 싶다.&lt;/p&gt;

&lt;p&gt;Google AI Gallery는 LiteRT-LM 모델들을 직접 체험해볼 수 있는 앱인데, 흥미로운 UX가 있다. &lt;strong&gt;사용자가 모델을 직접 선택하고 다운로드&lt;/strong&gt;할 수 있다.&lt;/p&gt;

&lt;p&gt;Gemma 1B, Gemma 3B, Gemma 4B… 모델마다 용량이 다르고, 사용자가 원하는 걸 골라서 받는 구조다.&lt;/p&gt;

&lt;p&gt;이게 그냥 UX 선택이 아닌 것 같다. 구글이 직접 만든 레퍼런스 앱이 이 방식을 채택했다는 건, &lt;strong&gt;온디바이스 AI 모델은 이렇게 배포해라&lt;/strong&gt; 는 가이드를 행동으로 보여주는 거라고 생각한다. 공식 문서보다 직관적인 레퍼런스다.&lt;/p&gt;

&lt;p&gt;그리고 이 구조를 구현하기에 딱 맞는 게 Play Asset Delivery다.&lt;/p&gt;

&lt;h2 id=&quot;play-asset-delivery란&quot;&gt;Play Asset Delivery란&lt;/h2&gt;

&lt;p&gt;Play Asset Delivery(PAD)는 Google Play 인프라를 통해 대용량 에셋을 런타임에 배포할 수 있는 기능이다. 게임에서 대용량 리소스를 나눠서 제공하던 방식인데, LLM 모델 배포에도 그대로 쓸 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;직접 다운로드 vs Play Asset Delivery 비교&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;직접 다운로드&lt;/th&gt;
      &lt;th&gt;Play Asset Delivery&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;인프라&lt;/td&gt;
      &lt;td&gt;직접 구축 (S3, CDN 등)&lt;/td&gt;
      &lt;td&gt;Google Play 인프라&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;비용&lt;/td&gt;
      &lt;td&gt;트래픽 비용 발생&lt;/td&gt;
      &lt;td&gt;Play 정책 내 무료&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;무결성 검증&lt;/td&gt;
      &lt;td&gt;직접 구현&lt;/td&gt;
      &lt;td&gt;자동 처리&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;델타 업데이트&lt;/td&gt;
      &lt;td&gt;직접 구현&lt;/td&gt;
      &lt;td&gt;지원&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;구현 복잡도&lt;/td&gt;
      &lt;td&gt;낮음&lt;/td&gt;
      &lt;td&gt;중간&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;오프라인 설치&lt;/td&gt;
      &lt;td&gt;불가&lt;/td&gt;
      &lt;td&gt;설치 시 포함 가능&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;pack당 최대 크기&lt;/td&gt;
      &lt;td&gt;제한 없음 (서버 설정에 따라)&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;1.5GB&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;모델 업데이트가 잦거나 여러 모델을 제공할 계획이라면 PAD가 유리하다.&lt;/p&gt;

&lt;h2 id=&quot;pad-구성-방식&quot;&gt;PAD 구성 방식&lt;/h2&gt;

&lt;p&gt;PAD는 에셋 전달 방식에 따라 세 가지로 나뉜다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;install-time  → 앱 설치 시 함께 설치 (용량 제한 있음)
fast-follow   → 설치 직후 자동 다운로드
on-demand     → 앱 실행 중 필요할 때 다운로드
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;모델 파일은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on-demand&lt;/code&gt;가 적합하다. 사용자가 AI 기능을 사용하려 할 때 그때 받는 구조다. AI Gallery처럼 여러 모델을 제공한다면 사용자가 선택한 모델만 받으면 된다.&lt;/p&gt;

&lt;h2 id=&quot;구현&quot;&gt;구현&lt;/h2&gt;

&lt;h3 id=&quot;1-asset-pack-모듈-생성&quot;&gt;1. asset pack 모듈 생성&lt;/h3&gt;

&lt;p&gt;프로젝트에 asset pack 전용 모듈을 추가한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;app/
ai-model-pack/          ← 새 모듈
  build.gradle.kts
  src/
    main/
      assets/
        gemma3-1b-it-int4.litertlm
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// ai-model-pack/build.gradle.kts&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;plugins&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.android.asset-pack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;assetPack&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;packName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ai_model_pack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;dynamicDelivery&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;deliveryType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;on-demand&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// app/build.gradle.kts&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;android&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assetPacks&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:ai-model-pack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-의존성-추가&quot;&gt;2. 의존성 추가&lt;/h3&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// app/build.gradle.kts&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.google.android.play:asset-delivery-ktx:2.2.2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-다운로드-요청&quot;&gt;3. 다운로드 요청&lt;/h3&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelDownloader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;assetPackManager&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AssetPackManagerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;downloadModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Flow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;callbackFlow&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;packName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ai_model_pack&quot;&lt;/span&gt;

        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AssetPackStateUpdateListener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;AssetPackStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DOWNLOADING&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;progress&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytesDownloaded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;totalBytesToDownload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                    &lt;span class=&quot;nf&quot;&gt;trySend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Downloading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;AssetPackStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;COMPLETED&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nf&quot;&gt;trySend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Completed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getModelPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;packName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
                    &lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;AssetPackStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FAILED&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nf&quot;&gt;trySend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
                    &lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;AssetPackStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;REQUIRES_USER_CONFIRMATION&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// 대용량 다운로드 시 사용자 확인 필요&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;showConfirmationDialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Unit&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;listOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;packName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;awaitClose&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unregisterListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getModelPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;packName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getPackLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;packName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;${location?.assetsPath()}/gemma3-1b-it-int4.litertlm&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;sealed&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;data class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Downloading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;data class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Completed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;modelPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;data class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Failed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;errorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModelDownloadState&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-이미-다운로드됐는지-확인&quot;&gt;4. 이미 다운로드됐는지 확인&lt;/h3&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isModelReady&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assetPackManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getPackLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ai_model_pack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;앱 시작 시점에 이미 다운로드된 모델이 있으면 바로 초기화하고, 없으면 다운로드 화면을 보여주면 된다.&lt;/p&gt;

&lt;h2 id=&quot;여러-모델을-선택할-수-있게-하려면&quot;&gt;여러 모델을 선택할 수 있게 하려면&lt;/h2&gt;

&lt;p&gt;AI Gallery처럼 사용자가 모델을 선택하는 구조라면, 모델마다 asset pack을 분리한다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;ai-model-gemma-1b/     → &quot;ai_model_gemma_1b&quot;
ai-model-gemma-3b/     → &quot;ai_model_gemma_3b&quot;
ai-model-gemma-4b/     → &quot;ai_model_gemma_4b&quot;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;사용자가 선택한 모델의 pack만 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt;하면 된다. 나머지는 다운로드하지 않는다.&lt;/p&gt;

&lt;p&gt;이 구조가 AI Gallery가 보여주는 방식이고, 실제로 LLM을 앱에 배포할 때 가장 현실적인 방법이기도 하다. 구글이 직접 만든 앱이 이렇게 한다는 건, 이 패턴을 권장한다는 신호로 읽힌다.&lt;/p&gt;

&lt;h2 id=&quot;주의할-점&quot;&gt;주의할 점&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;테스트 환경&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PAD는 Google Play를 통해 배포되는 구조라 로컬 개발 환경에서 테스트하기 까다롭다. 개발 중에는 adb로 모델을 직접 밀어 넣고, PAD 연동은 내부 테스트 트랙에서 별도로 검증하는 게 현실적이다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;adb push gemma3-1b-it-int4.litertlm /data/local/tmp/
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;용량 안내&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1GB 다운로드를 사용자에게 예고 없이 시작하면 안 된다. Wi-Fi 환경 확인, 용량 안내, 사용자 동의 흐름이 필요하다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;저장 공간 확인&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;다운로드 전 기기 여유 공간을 확인하고 부족하면 안내해줘야 한다.&lt;/p&gt;

&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;1GB 모델은 번들링 불가 → 런타임 배포 필요&lt;/li&gt;
  &lt;li&gt;Play Asset Delivery: Google Play 인프라 활용, 무결성 검증 자동&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on-demand&lt;/code&gt; 방식으로 사용자가 필요할 때 다운로드&lt;/li&gt;
  &lt;li&gt;여러 모델 제공 시 pack 분리 → 사용자가 선택한 것만 받는 구조&lt;/li&gt;
  &lt;li&gt;AI Gallery가 이 패턴의 레퍼런스 — 구글이 직접 보여주는 방법&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;이 시리즈는 여기서 마무리. 1편부터 읽어주신 분들 감사합니다.&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;참고사이트&quot;&gt;참고사이트&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.android.com/guide/playcore/asset-delivery&quot; class=&quot;underlineFill&quot;&gt;Play Asset Delivery - Android Developers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://support.google.com/googleplay/android-developer/answer/9859372&quot; class=&quot;underlineFill&quot;&gt;Google Play 앱 크기 제한&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://aistudio.google.com/gallery&quot; class=&quot;underlineFill&quot;&gt;Google AI Gallery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 08 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/08/android-on-device-ai-6/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/08/android-on-device-ai-6/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>on-device</category>
        
        <category>litert</category>
        
        <category>gemma</category>
        
        <category>play-asset-delivery</category>
        
        <category>dynamic-delivery</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] 온디바이스 AI 개발기 - 5편: 한계 실험기 (요약 / 분류 / 번역 직접 비교)</title>
        <description>&lt;h1 id=&quot;android-온디바이스-ai-개발기---5편-한계-실험기-요약--분류--번역-직접-비교&quot;&gt;[Android] 온디바이스 AI 개발기 - 5편: 한계 실험기 (요약 / 분류 / 번역 직접 비교)&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;설치하고 돌려봤다. 잘 되는 것도 있고, 쓰기 어려운 것도 있다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;이-글은&quot;&gt;이 글은&lt;/h2&gt;

&lt;p&gt;1~4편에서 왜 온디바이스인지, 어떤 엔진을 쓰는지, 세팅은 어떻게 하는지를 정리했다. 이번엔 실제로 여러 태스크를 돌려보면서 1B 모델의 현실적인 한계를 정리한다.&lt;/p&gt;

&lt;p&gt;모델: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemma3-1b-it-int4&lt;/code&gt; (int4 양자화)
기기: Snapdragon 8 Elite, GPU 백엔드&lt;/p&gt;

&lt;h2 id=&quot;실험-방식&quot;&gt;실험 방식&lt;/h2&gt;

&lt;p&gt;각 태스크에 동일한 프롬프트를 5회 반복 실행해서 결과를 비교했다. 평가 기준은 단순하다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;쓸 만함&lt;/strong&gt;: 결과를 그대로 혹은 약간 다듬어서 쓸 수 있는 수준&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;아쉬움&lt;/strong&gt;: 방향은 맞지만 품질이 들쑥날쑥하거나 손봐야 할 게 많음&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;어려움&lt;/strong&gt;: 결과를 신뢰하기 어렵거나 의도와 다른 결과가 자주 나옴&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-짧은-텍스트-요약--쓸-만함&quot;&gt;1. 짧은 텍스트 요약 ✅ 쓸 만함&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 내용을 2~3문장으로 요약해줘.

[200자 내외 텍스트]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;핵심을 잘 잡는다. 200자 이내 텍스트라면 5번 중 4~5번은 쓸 만한 결과가 나왔다. 문장 자체도 자연스러운 편이다.&lt;/p&gt;

&lt;p&gt;특히 &lt;strong&gt;중요한 것만 추려줘&lt;/strong&gt; 류의 단순 필터링 작업은 1B 모델도 잘 처리한다. 정보를 새로 만드는 게 아니라 줄이는 작업이기 때문인 것 같다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;한계&lt;/strong&gt;: 300자가 넘어가면서 품질이 흔들리기 시작한다. 길수록 중요한 부분을 빠뜨리거나, 요약인데 원문을 그대로 반복하는 경우가 생긴다.&lt;/p&gt;

&lt;h2 id=&quot;2-이진-분류--쓸-만함&quot;&gt;2. 이진 분류 ✅ 쓸 만함&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 텍스트가 업무 관련 내용인지 아닌지 판단해줘.
&quot;업무&quot;또는 &quot;비업무&quot; 중 하나만 답해줘.

[텍스트]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;단순 분류는 꽤 잘 된다. 특히 답변 형식을 엄격하게 지정하면(하나만 답해줘, 예/아니오로만 답해줘) 지시를 잘 따른다.&lt;/p&gt;

&lt;p&gt;중요/스팸, 긍정/부정 같은 이진 분류 태스크에서는 꽤 신뢰할 만한 결과가 나왔다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;한계&lt;/strong&gt;: 경계가 모호한 케이스에서는 판단이 들쑥날쑥하다. 반쯤 업무 관련인 내용은 실행마다 다른 결과가 나오기도 했다. 이런 케이스는 규칙 기반으로 보완하는 게 낫다.&lt;/p&gt;

&lt;h2 id=&quot;3-카테고리-분류-다중--아쉬움&quot;&gt;3. 카테고리 분류 (다중) 🤔 아쉬움&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 텍스트를 아래 카테고리 중 하나로 분류해줘.
카테고리: 공지, 업무요청, 미팅, 청구서, 뉴스레터, 기타

[텍스트]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;카테고리가 3~4개일 때는 나쁘지 않다. 그런데 5개 이상이 되면 덜 명확한 카테고리끼리 혼동이 생긴다.&lt;/p&gt;

&lt;p&gt;업무요청과 미팅을 섞거나, 명백한 뉴스레터를 공지로 분류하는 경우가 있었다. 정확도가 70~80% 수준이라 프로덕션에 그대로 쓰기엔 부족하다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;대안&lt;/strong&gt;: 1B 모델로 1차 분류 후 신뢰도가 낮은 케이스는 규칙 기반으로 재처리하는 하이브리드 방식이 현실적이다.&lt;/p&gt;

&lt;h2 id=&quot;4-짧은-답장-초안--아쉬움&quot;&gt;4. 짧은 답장 초안 🤔 아쉬움&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 메시지에 대한 짧은 답장 초안을 작성해줘. 2~3문장 이내로.

[수신 메시지]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;형식은 맞는데 내용이 밋밋하다. 확인했습니다. 검토 후 답변 드리겠습니다. 수준의 무난한 답변이 주로 나온다.&lt;/p&gt;

&lt;p&gt;발신자 톤에 맞추거나 맥락을 파악한 답변은 기대하기 어렵다. 1B 모델의 맥락 이해 능력에 한계가 있다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;그래도 쓸 수 있는 경우&lt;/strong&gt;: 수신 확인 또는 일정 조율 같이 정형화된 답변이 필요한 경우는 쓸 만하다. 창의적인 답변이 필요한 상황은 어렵다.&lt;/p&gt;

&lt;h2 id=&quot;5-한국어-번역--어려움&quot;&gt;5. 한국어 번역 ❌ 어려움&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 영어 문장을 한국어로 번역해줘.

[영어 텍스트]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;단어 수준 번역은 되지만 문장 자연스러움이 많이 부족하다. 직역 느낌이 강하고, 한국어 어순이나 어미 처리가 어색한 경우가 많다.&lt;/p&gt;

&lt;p&gt;비즈니스 문서나 공식적인 텍스트는 특히 품질이 낮다. DeepL, Papago 수준은 물론이고 일반적인 서버 AI 대비도 품질 차이가 크다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;: 번역은 온디바이스 1B 모델로는 무리다. 번역이 핵심 기능이라면 서버 API를 쓰는 게 맞다.&lt;/p&gt;

&lt;h2 id=&quot;6-긴-텍스트-분석--어려움&quot;&gt;6. 긴 텍스트 분석 ❌ 어려움&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;프롬프트 예시&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;다음 문서에서 핵심 액션 아이템을 추출해줘.

[1000자 이상 텍스트]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;컨텍스트가 길어질수록 품질이 급격히 떨어진다. 앞부분 내용에 집중하고 뒷부분을 놓치거나, 없는 내용을 만들어내는(환각) 경우도 나왔다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemma3-1b-it-int4&lt;/code&gt;의 최대 토큰이 제한적이기도 하고, 1B 모델 자체의 긴 컨텍스트 처리 능력에 한계가 있다.&lt;/p&gt;

&lt;h2 id=&quot;태스크별-정리&quot;&gt;태스크별 정리&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;태스크&lt;/th&gt;
      &lt;th&gt;평가&lt;/th&gt;
      &lt;th&gt;비고&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;짧은 텍스트 요약 (200자 이내)&lt;/td&gt;
      &lt;td&gt;✅ 쓸 만함&lt;/td&gt;
      &lt;td&gt;핵심 필터링에 적합&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;이진 분류&lt;/td&gt;
      &lt;td&gt;✅ 쓸 만함&lt;/td&gt;
      &lt;td&gt;형식 지정 엄격하게&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;다중 카테고리 분류&lt;/td&gt;
      &lt;td&gt;🤔 아쉬움&lt;/td&gt;
      &lt;td&gt;규칙 기반 보완 필요&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;짧은 답장 초안&lt;/td&gt;
      &lt;td&gt;🤔 아쉬움&lt;/td&gt;
      &lt;td&gt;정형화된 답변만&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;번역&lt;/td&gt;
      &lt;td&gt;❌ 어려움&lt;/td&gt;
      &lt;td&gt;서버 API 권장&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;긴 텍스트 분석&lt;/td&gt;
      &lt;td&gt;❌ 어려움&lt;/td&gt;
      &lt;td&gt;컨텍스트 한계 명확&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;결론&quot;&gt;결론&lt;/h2&gt;

&lt;p&gt;1B 모델은 &lt;strong&gt;짧고 단순한 작업&lt;/strong&gt;에서 빛을 발한다. 요약, 이진 분류처럼 있는 정보를 걸러내는 작업은 꽤 쓸 만하다.&lt;/p&gt;

&lt;p&gt;반면 &lt;strong&gt;생성, 번역, 긴 컨텍스트 처리&lt;/strong&gt;는 한계가 명확하다. 이런 작업을 온디바이스로 하려면 모델 크기를 키워야 하는데, 그러면 기기 제약이 다시 발목을 잡는다.&lt;/p&gt;

&lt;p&gt;온디바이스 1B 모델을 쓸 때 현실적인 전략은:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;단순 작업은 온디바이스로 처리&lt;/strong&gt; (속도, 프라이버시 이점)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;복잡한 작업은 서버 AI로&lt;/strong&gt; (품질이 중요할 때)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;규칙 기반과 혼합&lt;/strong&gt; (AI 결과의 신뢰도가 낮은 케이스를 보완)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;다음 편에서는 모델을 앱에 어떻게 배포하는지를 다룬다. Play Asset Delivery로 1GB 모델을 런타임에 제공하는 방법이다.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;#&quot;&gt;6편 - Play Asset Delivery로 모델 배포하기&lt;/a&gt; 에서 계속&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;참고사이트&quot;&gt;참고사이트&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ai.google.dev/edge/litert-lm/overview&quot; class=&quot;underlineFill&quot;&gt;Google AI Edge - LiteRT-LM Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ai.google.dev/gemma&quot; class=&quot;underlineFill&quot;&gt;Gemma model card - Google AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 07 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/07/android-on-device-ai-5/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/07/android-on-device-ai-5/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>on-device</category>
        
        <category>litert</category>
        
        <category>gemma</category>
        
        <category>benchmark</category>
        
        
        <category>til</category>
        
      </item>
    
      <item>
        <title>[Android] 온디바이스 AI 개발기 - 4편: GPU 가속, 어떤 기기에서 되나</title>
        <description>&lt;h1 id=&quot;android-온디바이스-ai-개발기---4편-gpu-가속-어떤-기기에서-되나&quot;&gt;[Android] 온디바이스 AI 개발기 - 4편: GPU 가속, 어떤 기기에서 되나&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Backend.GPU&lt;/code&gt;를 지정하면 끝인 줄 알았다. 그런데 기기마다 다르다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;이-글은&quot;&gt;이 글은&lt;/h2&gt;

&lt;p&gt;3편에서 LiteRT-LM 세팅 코드를 정리했다. GPU 백엔드 초기화 부분에서 fallback 처리가 필요하다고 했는데, 이번엔 왜 그런지, 어떤 기기에서 GPU가 되고 안 되는지를 정리한다.&lt;/p&gt;

&lt;h2 id=&quot;gpu-가속의-전제-조건&quot;&gt;GPU 가속의 전제 조건&lt;/h2&gt;

&lt;p&gt;LiteRT-LM의 GPU 가속은 &lt;strong&gt;OpenCL&lt;/strong&gt; 기반이다. OpenCL 연산을 위해 기기에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libOpenCL.so&lt;/code&gt; 라이브러리가 있어야 한다.&lt;/p&gt;

&lt;p&gt;문제는 Android가 OpenCL을 표준 스펙에 포함시키지 않는다는 것이다. iOS가 Metal을 OS 레벨에서 제공하는 것과 달리, Android에서 OpenCL은 &lt;strong&gt;OEM이 알아서 드라이버를 탑재해야 한다&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;즉, GPU 가속 가능 여부는 SoC가 뭐냐보다 OEM이 OpenCL 드라이버를 심었냐에 달려 있다.&lt;/p&gt;

&lt;h2 id=&quot;어떤-기기에서-되나&quot;&gt;어떤 기기에서 되나&lt;/h2&gt;

&lt;p&gt;화이트리스트 방식이 아니라서 공식 목록이 있는 건 아니다. 커뮤니티와 GitHub 이슈에서 확인된 내용을 정리하면 이렇다.&lt;/p&gt;

&lt;h3 id=&quot;잘-되는-기기&quot;&gt;잘 되는 기기&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;기기&lt;/th&gt;
      &lt;th&gt;칩셋&lt;/th&gt;
      &lt;th&gt;GPU&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Samsung Galaxy S24 Ultra 이상&lt;/td&gt;
      &lt;td&gt;Snapdragon 8 Gen 3+&lt;/td&gt;
      &lt;td&gt;Adreno&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Samsung Galaxy S25 / S26 시리즈&lt;/td&gt;
      &lt;td&gt;Snapdragon 8 Elite&lt;/td&gt;
      &lt;td&gt;Adreno&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Xiaomi 플래그십 (2023년 이후)&lt;/td&gt;
      &lt;td&gt;Snapdragon 8 Gen 2+&lt;/td&gt;
      &lt;td&gt;Adreno&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Google Pixel 8 이상&lt;/td&gt;
      &lt;td&gt;Google Tensor G3+&lt;/td&gt;
      &lt;td&gt;Immortalis-G715&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MediaTek Dimensity 9200+ 탑재 기기&lt;/td&gt;
      &lt;td&gt;Dimensity 9200+&lt;/td&gt;
      &lt;td&gt;Mali&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Snapdragon Adreno 계열이 가장 안정적이다.&lt;/strong&gt; 구글 공식 벤치마크 기기도 Snapdragon 8 Gen 3 탑재 기기 기준으로 나온다.&lt;/p&gt;

&lt;h3 id=&quot;안-되거나-불안정한-기기&quot;&gt;안 되거나 불안정한 기기&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;기기&lt;/th&gt;
      &lt;th&gt;이유&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;보급형 기기 (A 시리즈 등)&lt;/td&gt;
      &lt;td&gt;OEM이 OpenCL 드라이버 미탑재&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Samsung Galaxy A34 (Exynos 1280)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clGetCommandBufferInfoKHR&lt;/code&gt; 심볼 오류&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Android 에뮬레이터&lt;/td&gt;
      &lt;td&gt;OpenCL 미지원&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;구형 기기 (2021년 이전)&lt;/td&gt;
      &lt;td&gt;OpenCL 버전 낮거나 미탑재&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Samsung Exynos 계열은 NPU 지원이 로드맵에 있긴 한데 아직 정식 지원 전이다.&lt;/p&gt;

&lt;h2 id=&quot;libopenclso를-선택적으로-로드하려면&quot;&gt;libOpenCL.so를 선택적으로 로드하려면&lt;/h2&gt;

&lt;p&gt;GPU를 쓰려면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AndroidManifest.xml&lt;/code&gt;에 OpenCL 라이브러리를 선택적으로 로드하도록 설정해야 한다.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- AndroidManifest.xml --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;application&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-native-library&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;libOpenCL.so&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;android:required=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;uses-native-library&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;android:name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;libvndksupport.so&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;android:required=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android:required=&quot;false&quot;&lt;/code&gt;가 핵심이다. 이걸 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;로 하면 OpenCL이 없는 기기에서 앱 자체가 설치되지 않는다.&lt;/p&gt;

&lt;h2 id=&quot;런타임에-gpu-지원-여부-확인&quot;&gt;런타임에 GPU 지원 여부 확인&lt;/h2&gt;

&lt;p&gt;앱 실행 시점에 GPU를 지원하는지 확인하는 방법은 두 가지다.&lt;/p&gt;

&lt;h3 id=&quot;방법-1-try-catch로-gpu-초기화-시도&quot;&gt;방법 1: try-catch로 GPU 초기화 시도&lt;/h3&gt;

&lt;p&gt;가장 단순한 방법이다. GPU 초기화를 시도하고, 실패하면 CPU로 떨어진다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;resolveBackend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;testOptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LlmInference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LlmInferenceOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setModelPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modelPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setMaxTokens&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setPreferredBackend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GPU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LlmInference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createFromOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;also&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GPU&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CPU&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;초기화 시도 자체가 무거운 작업이라 실용적이지 않을 수 있다.&lt;/p&gt;

&lt;h3 id=&quot;방법-2-tflitegpu-api로-사전-확인&quot;&gt;방법 2: TfLiteGpu API로 사전 확인&lt;/h3&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// 의존성 추가 필요&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// implementation(&quot;com.google.android.gms:play-services-tflite-gpu:16.2.0&quot;)&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;TfLiteGpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isGpuDelegateAvailable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addOnSuccessListener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gpuAvailable&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gpuAvailable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GPU&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CPU&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;initializeEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addOnFailureListener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;initializeEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CPU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;LiteRT-LM과 별개의 라이브러리지만, GPU 지원 여부를 가볍게 사전 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;실용적인 접근은 &lt;strong&gt;앱 첫 실행 시 GPU 가능 여부를 확인하고 결과를 로컬에 저장&lt;/strong&gt;, 이후 실행부터는 저장된 값을 사용하는 것이다.&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;// DataStore나 SharedPreferences에 캐싱&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;cachedBackend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getBoolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gpu_available&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cachedBackend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GPU&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CPU&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;실제로-어떤-차이가-나나&quot;&gt;실제로 어떤 차이가 나나&lt;/h2&gt;

&lt;p&gt;내가 직접 측정한 수치다. 모델은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemma3-1b-it-int4.litertlm&lt;/code&gt; 기준.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;백엔드&lt;/th&gt;
      &lt;th&gt;TTFT&lt;/th&gt;
      &lt;th&gt;전체 응답&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;GPU (Adreno 830)&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;492ms&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;2.7초&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU&lt;/td&gt;
      &lt;td&gt;수 초 이상&lt;/td&gt;
      &lt;td&gt;10~20초 이상&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;GPU와 CPU 차이가 5~10배 이상 난다. CPU로 돌리면 UX가 사실상 불가능한 수준이다.&lt;/p&gt;

&lt;p&gt;그래서 &lt;strong&gt;GPU를 지원하지 않는 기기에서는 기능 자체를 비활성화하는 게 현실적인 선택&lt;/strong&gt;이다. CPU로 억지로 돌리는 것보다, 지원 기기에서만 기능을 노출하는 게 낫다.&lt;/p&gt;

&lt;h2 id=&quot;정리&quot;&gt;정리&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;LiteRT-LM GPU 가속은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libOpenCL.so&lt;/code&gt; 존재 여부에 달려 있다&lt;/li&gt;
  &lt;li&gt;Android는 OpenCL을 표준 제공하지 않아서 기기마다 다르다&lt;/li&gt;
  &lt;li&gt;Snapdragon Adreno 계열 플래그십(2022년 이후)은 대체로 잘 된다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AndroidManifest.xml&lt;/code&gt;에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android:required=&quot;false&quot;&lt;/code&gt; 설정 필수&lt;/li&gt;
  &lt;li&gt;GPU 미지원 기기에서는 기능을 비활성화하는 게 현실적&lt;/li&gt;
  &lt;li&gt;GPU vs CPU 성능 차이는 TTFT 기준 5~10배 이상&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;다음 편에서는 실제로 여러 작업을 돌려보면서 1B 모델이 잘 되는 것과 안 되는 것을 직접 비교해본다. 요약, 분류, 번역 등 태스크별로 결과가 꽤 다르다.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;#&quot;&gt;5편 - 온디바이스 AI 한계 실험기 (요약 / 분류 / 번역 직접 비교)&lt;/a&gt; 에서 계속&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;참고사이트&quot;&gt;참고사이트&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ai.google.dev/edge/litert/next/gpu&quot; class=&quot;underlineFill&quot;&gt;GPU acceleration with LiteRT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/google-ai-edge/LiteRT/issues/886&quot; class=&quot;underlineFill&quot;&gt;LiteRT-LM GitHub - Issue #886 (OpenCL undefined symbol)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://huggingface.co/collections/litert-community/android-models&quot; class=&quot;underlineFill&quot;&gt;litert-community Android 모델 컬렉션&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/android/R.attr#required&quot; class=&quot;underlineFill&quot;&gt;AndroidManifest uses-native-library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 06 Apr 2026 09:00:00 +0000</pubDate>
        <link>https://dongsik93.github.io/til/2026/04/06/android-on-device-ai-4/</link>
        <guid isPermaLink="true">https://dongsik93.github.io/til/2026/04/06/android-on-device-ai-4/</guid>
        
        <category>android</category>
        
        <category>ai</category>
        
        <category>llm</category>
        
        <category>on-device</category>
        
        <category>litert</category>
        
        <category>gpu</category>
        
        <category>opencl</category>
        
        <category>adreno</category>
        
        
        <category>til</category>
        
      </item>
    
  </channel>
</rss>
