<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>오늘도 공부</title>
    <link>https://javaexpert.tistory.com/</link>
    <description>AI,Flutter,Node등 다양한 분야를 연구하는 블로그입니다</description>
    <language>ko</language>
    <pubDate>Sat, 15 Aug 2026 04:54:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>행복한 수지아빠</managingEditor>
    <image>
      <title>오늘도 공부</title>
      <url>https://tistory1.daumcdn.net/tistory/649446/attach/3ffb00b63198428d8082dda2b9aef56d</url>
      <link>https://javaexpert.tistory.com</link>
    </image>
    <item>
      <title>현재 한국의 외환&amp;middot;신용&amp;middot;채권&amp;middot;자금흐름&amp;middot;부동산&amp;middot;부채 현황에 대해 확인</title>
      <link>https://javaexpert.tistory.com/1808</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;2026년 8월 14일 18:11 KST 기준&lt;/b&gt;으로 다시 확인했습니다. 환율&amp;middot;채권&amp;middot;주식은 오늘 시장 데이터, CDS는 8월 13일 최신치, 부동산은 8월 10일 기준 주간통계, 가계부채는 최신 공식 총액과 7월 대출 흐름을 사용했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  현재 한국 경제 위기 신호등&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표판정현재 확인값핵심 해석&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;원/달러 환율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;주의 &amp;darr; 개선&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;1,418.3원&lt;/b&gt; 8/14 종가&lt;/td&gt;
&lt;td&gt;6월 1,561.5원까지 갔던 극단적 원화 약세에서는 크게 회복&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; ️ &lt;b&gt;한국 CDS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;정상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5년물 &lt;b&gt;22.63bp&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;현재 가장 중요한 긍정 신호. 국가 신용위험 급등은 관찰되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;국고채 금리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;위험&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;10년 &lt;b&gt;4.31%&lt;/b&gt;, 30년 &lt;b&gt;4.66%&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;현재 6개 중 가장 주의해야 할 지표 중 하나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;외국인 자금&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;주의 &amp;uarr; 회복&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;7월 주식 &lt;b&gt;-62.6억 달러&lt;/b&gt;, 8/14 하루 &lt;b&gt;+3.04조원 순매수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;대규모 이탈 뒤 최근 강하게 복귀 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;부동산&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;위험&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;전국 주간 &lt;b&gt;+0.08%&lt;/b&gt;, 서울 &lt;b&gt;+0.21%&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;붕괴 위험이 아니라 &lt;b&gt;과열&amp;middot;부채 연계 위험&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;가계부채&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;  &lt;b&gt;위험&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1분기 &lt;b&gt;1,993.1조원&lt;/b&gt;, 7월 대출 &lt;b&gt;+6.2조원&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;이미 높은 부채에 올해 대출 증가가 다시 빨라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;종합 판정:   &lt;b&gt;주의 &amp;mdash; 다만 빨간불에 가까운 주의&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 판단으로는 &lt;b&gt;현재 한국이 외환위기&amp;middot;국가부도형 위기에 들어간 상태라고 볼 데이터는 아닙니다.&lt;/b&gt; 가장 중요한 이유가 CDS입니다. 한국 5년 CDS가 8월 13일 기준 &lt;b&gt;22.63bp&lt;/b&gt;입니다. 참고로 2022년 시장 불안기에는 70bp까지 올라간 적이 있었습니다. 현재 국제시장이 한국 정부의 채무불이행 위험을 급격히 가격에 반영하고 있는 상황과는 상당한 차이가 있습니다. (&lt;a href=&quot;https://www.worldgovernmentbonds.com/cds-historical-data/south-korea/5-years/?utm_source=chatgpt.com&quot;&gt;World Government Bonds&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;b&gt;국내의 금리&amp;middot;부동산&amp;middot;가계부채라는 세 축은 분명히 위험 신호&lt;/b&gt;가 켜져 있습니다. 그래서 저는 전체를   정상으로 보기는 어렵습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;①   환율 &amp;mdash;   주의, 하지만 빠르게 개선 중&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8월 14일 서울 외환시장 원/달러 종가는 &lt;b&gt;1,418.3원&lt;/b&gt;입니다. (&lt;a href=&quot;https://english.mofe.go.kr/&quot;&gt;재정경제부&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 방향입니다. 원화는 6월 한때 &lt;b&gt;1달러=1,561.5원&lt;/b&gt;까지 떨어져 17년 만의 약세 수준을 기록했는데, 7월에는 8% 이상 반등했습니다. 정부의 달러 매도 개입도 있었고 7월 말 1,418원까지 회복했습니다. (&lt;a href=&quot;https://www.reuters.com/world/asia-pacific/south-korea-conducts-rare-dollar-selling-intervention-source-says-2026-07-30/?utm_source=chatgpt.com&quot;&gt;Reuters&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 현재 환율은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1,561 &amp;rarr; 1,418&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 상당히 정상화됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] &lt;b&gt;환율만 보면 위기 방향이 아니라 위기에서 멀어지는 방향&lt;/b&gt;입니다. 다만 1,400원대 자체가 낮은 환율은 아니며, 한국은행도 최근 원화 수준이 여전히 인플레이션 압력 요인이 될 수 있다고 평가했습니다. (&lt;a href=&quot;https://www.reuters.com/world/asia-pacific/bank-korea-likely-raise-interest-rates-further-outgoing-deputy-chief-says-2026-08-11/?utm_source=chatgpt.com&quot;&gt;Reuters&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   주의 &amp;rarr;   방향으로 개선 중&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;②  ️ CDS &amp;mdash;   정상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 6개 가운데 &lt;b&gt;가장 중요하게 보는 지표&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 5년물 CDS 최신치는 8월 13일 기준 &lt;b&gt;22.63bp&lt;/b&gt;입니다. (&lt;a href=&quot;https://www.worldgovernmentbonds.com/cds-historical-data/south-korea/5-years/?utm_source=chatgpt.com&quot;&gt;World Government Bonds&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CDS는 쉽게 말하면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;한국 정부가 빚을 못 갚을 위험에 대해 보험료를 얼마나 내야 하느냐&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 국제시장이 가격으로 표현한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 30년 국채금리가 사상 최고인데 &lt;b&gt;CDS가 20bp 초반이라는 조합은 상당히 중요합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 현재 30년 금리 상승을 시장이 주로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국 국가부도 위험 &amp;uarr;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 보고 있다기보다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인플레이션 + 장기금리 + 국채 공급 + 장기채 수급 + 기간 프리미엄&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제로 가격에 반영하고 있을 가능성이 더 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   정상&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③   국고채 &amp;mdash;   위험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 확실히 안 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8월 14일 한국 국고채 10년물은 약 **4.31%**입니다. 1년 전보다 약 &lt;b&gt;1.48%p 높습니다.&lt;/b&gt; (&lt;a href=&quot;https://tradingeconomics.com/south-korea/government-bond-yield?utm_source=chatgpt.com&quot;&gt;Trading Economics&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30년물은 &lt;b&gt;4.66%&lt;/b&gt; 수준이고, 8월 12일에는 &lt;b&gt;4.67%&lt;/b&gt;까지 올라 &lt;b&gt;30년물이 도입된 2012년 이후 최고 수준&lt;/b&gt;을 기록했습니다. (&lt;a href=&quot;https://tradingeconomics.com/south-korea/30-year-bond-yield?utm_source=chatgpt.com&quot;&gt;Trading Economics&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재 기준금리는 **2.75%**입니다. (&lt;a href=&quot;https://www.bok.or.kr/eng/main/main.do?utm_source=chatgpt.com&quot;&gt;한국은행&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 대략&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기준금리 2.75%&lt;br /&gt;&amp;rarr; 3년 3.80%&lt;br /&gt;&amp;rarr; 5년 4.05%&lt;br /&gt;&amp;rarr; 10년 4.31%&lt;br /&gt;&amp;rarr; 30년 4.66%&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태입니다. 3년&amp;middot;5년물 최신치는 정부 경제지표에서도 확인됩니다. (&lt;a href=&quot;https://english.mofe.go.kr/&quot;&gt;재정경제부&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 이것은 한국의 &lt;b&gt;장기 자금조달 여건이 상당히 빡빡해지고 있다는 경고&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정부뿐 아니라 회사채 &amp;rarr; 기업대출 &amp;rarr; PF &amp;rarr; 주택담보대출 금리에도 간접적으로 영향을 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   위험&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 중요한 구분은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  국채금리 위험 &amp;ne;   국가부도 위험&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다. CDS가 이를 구분해 주고 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④   외국인 자금 &amp;mdash;   주의, 최근 급격히 회복&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 재미있는 변화가 나타나고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7월에는 외국인이 한국 주식을 &lt;b&gt;62.6억 달러 순매도&lt;/b&gt;했습니다. 그것도 &lt;b&gt;3개월 연속 순유출&lt;/b&gt;이었습니다. (&lt;a href=&quot;https://www.reuters.com/world/china/taiwan-south-korea-drive-asian-equity-outflows-july-ai-worries-bite-2026-08-11/?utm_source=chatgpt.com&quot;&gt;Reuters&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 최근 방향이 반대로 바뀌었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8월 14일 하루만 유가증권시장에서 외국인이 3.04조원을 순매수&lt;/b&gt;했고, 코스피는 2.42% 상승한 6,977.94로 마감했습니다. (&lt;a href=&quot;https://en.yna.co.kr/view/AEN20260814008051320&quot;&gt;연합뉴스&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5~7월: 외국인 이탈&lt;br /&gt;&amp;rarr; 8월: 외국인 다시 매수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 전환이 나타나고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 따라서 현재 상황을 &lt;b&gt;&amp;ldquo;외국인이 한국을 탈출하고 있다&amp;rdquo;&lt;/b&gt;고 표현하는 것은 최신 데이터와 맞지 않습니다. 그렇다고 몇 거래일의 매수만으로 3개월간의 자금유출 추세가 완전히 끝났다고 확인할 수도 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   주의 &amp;rarr; 개선 중&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑤   부동산 &amp;mdash;   위험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 &lt;b&gt;폭락 위험이 아니라 과열 위험&lt;/b&gt;이라는 점이 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국부동산원 8월 둘째 주(8월 10일 기준) 자료를 보면 전국 아파트 가격은 한 주 동안 &lt;b&gt;+0.08%&lt;/b&gt;, 서울은 &lt;b&gt;+0.21%&lt;/b&gt; 상승했습니다. 서울 상승률은 직전 주 +0.26%보다는 낮아졌습니다. (&lt;a href=&quot;https://www.yna.co.kr/amp/view/AKR20260813088751003&quot;&gt;연합뉴스&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 내부적으로 변화가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강남구는 &lt;b&gt;-0.02%&lt;/b&gt;, 서초구는 &lt;b&gt;-0.04%&lt;/b&gt;로 하락 전환했지만 중랑 +0.46%, 성북 +0.43%, 서대문 +0.41%, 중구&amp;middot;강북 +0.40% 등 중저가 지역은 상당한 상승세를 이어갔습니다. (&lt;a href=&quot;https://www.yna.co.kr/amp/view/AKR20260813088751003&quot;&gt;연합뉴스&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정부도 현재 주택시장을 심각한 과열 문제로 보고 주택공급&amp;middot;금융대책을 발표한 상태입니다. (&lt;a href=&quot;https://www.reuters.com/world/asia-pacific/south-korea-unveils-package-boost-housing-supply-support-young-buyers-2026-08-13/?utm_source=chatgpt.com&quot;&gt;Reuters&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 따라서 현재 부동산의 위험은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;집값이 무너진다&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;높은 금리에도 집값 상승 &amp;rarr; 다시 대출 증가 &amp;rarr; 가계부채 증가&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쪽입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   위험&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑥   가계부채 &amp;mdash;   위험&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국은행이 현재 공식적으로 발표한 최신 분기 총액은 2026년 1분기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가계신용은 &lt;b&gt;1,993.1조원&lt;/b&gt;, 전분기보다 &lt;b&gt;14.0조원 증가&lt;/b&gt;했습니다. 이 가운데 가계대출이 1,865.8조원입니다. (&lt;a href=&quot;https://www.bok.or.kr/portal/bbs/B0000501/view.do?depth=201264&amp;amp;menuNo=201264&amp;amp;nttId=10098042&amp;amp;programType=newsData&amp;amp;relate=Y&quot;&gt;한국은행&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2분기 공식 가계신용은 아직 발표 전이며 &lt;b&gt;8월 19일 발표 예정&lt;/b&gt;입니다. 따라서 지금 2분기 총액을 확정적으로 말할 수는 없습니다. (&lt;a href=&quot;https://www.bok.or.kr/portal/stats/statsPublictSchdul/listCldr.do?date=2020-06&amp;amp;menuNo=200775&amp;amp;utm_source=chatgpt.com&quot;&gt;한국은행&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 더 빠르게 나오는 월별 금융권 대출을 보면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4월 +3.5조&lt;br /&gt;&amp;rarr; 5월 +9.3조&lt;br /&gt;&amp;rarr; 6월 +8.3조&lt;br /&gt;&amp;rarr; 7월 +6.2조원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다. 올해 &lt;b&gt;1~7월 누적으로 +35.3조원&lt;/b&gt; 늘었습니다. (&lt;a href=&quot;https://www.asiae.co.kr/en/article/2026081410240887044?utm_source=chatgpt.com&quot;&gt;아시아경제&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 두 달 증가 속도가 떨어진 것은 긍정적이지만, 금융당국도 7월 증가액이 과거 평균보다 여전히 높은 수준이라고 보고 있습니다. (&lt;a href=&quot;https://v.daum.net/v/xYHGF4sAv7?utm_source=chatgpt.com&quot;&gt;다음 뉴스&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;판정:   위험&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 제가 현재 가장 걱정하는 것은 이 연결입니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부동산 가격 상승&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;주담대&amp;middot;가계대출 증가&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;가계부채 2,000조원 부근&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;장기 국채금리 4%대 중후반&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;가계&amp;middot;기업 이자부담 증가&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;소비&amp;middot;투자 위축&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시나리오입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 지금 한국의 핵심 위험은 &lt;b&gt;&amp;ldquo;외환위기&amp;rdquo;보다는 부동산 + 부채 + 고금리의 조합&lt;/b&gt;에 더 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 실제 외환&amp;middot;국가신용 위기로 발전하고 있다면 &lt;b&gt;원화 급락 + CDS 급등 + 외국인 지속 이탈 + 국채금리 급등&lt;/b&gt;이 동시에 나타나는지를 봐야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환율   &amp;rarr; 개선&lt;br /&gt;CDS  &lt;br /&gt;외국인   &amp;rarr; 개선&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;국채  &lt;br /&gt;부동산  &lt;br /&gt;가계부채  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;&amp;ldquo;한국이 당장 위험하다&amp;rdquo;보다는 &amp;ldquo;대외위기는 아직 아니지만 내부 금융불균형의 압력이 상당히 높다&amp;rdquo;&lt;/b&gt;가 현재 데이터에 더 맞는 표현입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 처음 보여주신 &lt;b&gt;30년 국채 4.6%대&lt;/b&gt;는 무시할 숫자는 아닙니다. 앞으로는 &lt;b&gt;CDS가 같이 올라가기 시작하는지&lt;/b&gt;를 보는 게 핵심입니다. 30년물은 계속 높은데 CDS가 20bp대에 머물면 장기금리 문제에 가깝고, &lt;b&gt;CDS까지 빠르게 튀기 시작하면 상황의 성격이 달라집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지표들은 변동이 빨라서 매일 같은 기준으로 점검하면 변화가 훨씬 잘 보입니다.&lt;/p&gt;</description>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1808</guid>
      <comments>https://javaexpert.tistory.com/1808#entry1808comment</comments>
      <pubDate>Fri, 14 Aug 2026 18:16:53 +0900</pubDate>
    </item>
    <item>
      <title>AI가 코드를 쓰는 시대, 이제 필요한 것은 &amp;lsquo;소프트웨어 공장&amp;rsquo;이다 (하네스)</title>
      <link>https://javaexpert.tistory.com/1807</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구가 빠르게 발전하면서 소프트웨어를 만드는 속도는 이전과 비교하기 어려울 정도로 빨라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor, Claude Code, Codex와 같은 도구를 사용하면 개발자는 몇 시간 만에 기능을 구현하고, 화면을 만들고, 테스트 코드까지 작성할 수 있다. 예전에는 며칠 걸리던 작업이 몇 시간 안에 끝나는 경우도 이제 낯설지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 새로운 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드를 만드는 속도는 빨라졌지만, 좋은 소프트웨어를 지속적으로 배포하는 속도까지 같은 비율로 빨라진 것은 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만들어내는 코드가 많아질수록 리뷰해야 할 코드도 늘어난다. 품질 편차도 커지고, 어느 순간부터는 사람이 전체 시스템을 제대로 이해하지 못하는 상황까지 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 AI 개발의 다음 경쟁력은 &amp;lsquo;코드를 얼마나 빨리 생성할 수 있는가&amp;rsquo;가 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI가 만든 결과물을 어떻게 안정적으로 검증하고 지속적으로 제품으로 내보낼 것인가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 이동하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지점에서 다시 주목받는 개념이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;소프트웨어 팩토리(Software Factory)&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소프트웨어 팩토리란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리를 단순하게 표현하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 개발 공정을 설계하고, 여러 AI 에이전트가 그 공정 안에서 반복적으로 소프트웨어를 생산하는 개발 체계&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 중요한 단어는 &amp;lsquo;AI&amp;rsquo;보다 오히려 &lt;b&gt;공정(Process)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 AI 코딩은 개발자가 AI에게 이렇게 요청하는 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;로그인 기능 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 코드를 작성하고, 개발자는 결과를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 소프트웨어 팩토리에서는 작업이 조금 다르게 흘러간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 요구사항이 표준화된 형태로 입력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 만들어야 하는지, 어디까지 구현해야 하는지, 어떤 조건을 만족해야 하는지, 어떤 테스트를 통과해야 하는지가 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 여러 에이전트가 역할을 나눠 작업한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 에이전트는 구현하고, 다른 에이전트는 테스트한다. 또 다른 에이전트는 보안이나 코드 품질을 검사할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로 모든 결과물이 동일한 품질 검사를 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건을 만족한 변경만 실제 서비스에 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 단순화하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요구사항 &amp;rarr; 표준화된 Spec &amp;rarr; 작업 Queue &amp;rarr; AI Agent &amp;rarr; 검증 &amp;rarr; 리뷰 &amp;rarr; 배포 &amp;rarr; 사용자 피드백 &amp;rarr; 다시 작업 Queue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 루프가 하나가 아니라 여러 개 동시에 돌아가는 것이 소프트웨어 팩토리의 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 코딩과 소프트웨어 팩토리는 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 조직이 개발자들에게 AI 코딩 도구를 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI 도구를 많이 사용하는 조직이 반드시 AI 개발을 잘하는 조직은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 10명이 각자 AI를 사용하면 코드 생산량은 크게 증가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 그다음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰해야 할 Pull Request가 급증하고, 테스트해야 할 기능도 늘어난다. 각 개발자가 서로 다른 방식으로 AI를 사용하면서 코드 스타일이나 설계 방식도 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이런 상황이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 생산량은 10배 늘었는데 검증 능력은 그대로다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 AI는 오히려 새로운 병목을 만들어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 소프트웨어 팩토리는 단순히 AI에게 일을 많이 시키는 개념이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 결과물이 반드시 일정한 공정을 통과하도록 만드는 것이 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공방의 품질과 공장의 품질&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리를 이해하는 데 좋은 비유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;공방&amp;rsquo;과 &amp;lsquo;공장&amp;rsquo;의 차이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공방에서는 장인의 실력이 제품의 품질을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 개발자가 있으면 좋은 코드가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 공장에서는 조금 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품의 품질이 특정 작업자의 능력에만 의존하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 자재를 사용했는지, 어떤 공정을 통과했는지, 어떤 검사를 통과했는지가 품질의 근거가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 역시 같은 방향으로 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 이런 질문이 중요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 코드는 누가 만들었나?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 다음 질문이 더 중요해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 코드는 어떤 기준을 만족했고, 어떤 검사를 통과했나?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 작성했든 AI가 작성했든 같은 품질 기준을 통과하도록 만드는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소프트웨어 팩토리의 네 가지 조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리를 운영하려면 최소한 네 가지 요소가 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 입력의 표준화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 자유로운 자연어만 던지는 방식으로는 안정적인 결과를 얻기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 요청 자체가 일정한 구조를 가져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 단순히&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;구글 로그인 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 요청하는 것이 아니라,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기능 목표&lt;/li&gt;
&lt;li&gt;구현 범위&lt;/li&gt;
&lt;li&gt;기술적 제약&lt;/li&gt;
&lt;li&gt;Acceptance Criteria&lt;/li&gt;
&lt;li&gt;테스트 요구사항&lt;/li&gt;
&lt;li&gt;보안 요구사항&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 명확하게 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 좋은 코드를 요구하기 전에 &lt;b&gt;좋은 작업 정의를 제공하는 것&lt;/b&gt;이 먼저다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 공통 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 작성한 코드와 AI가 작성한 코드가 다른 검사를 받아서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 변경은 동일한 품질 기준을 통과해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 항목들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unit Test&lt;/li&gt;
&lt;li&gt;Integration Test&lt;/li&gt;
&lt;li&gt;E2E Test&lt;/li&gt;
&lt;li&gt;Security Scan&lt;/li&gt;
&lt;li&gt;Performance Test&lt;/li&gt;
&lt;li&gt;Accessibility&lt;/li&gt;
&lt;li&gt;디자인 시스템 준수&lt;/li&gt;
&lt;li&gt;API 호환성&lt;/li&gt;
&lt;li&gt;코드 품질 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만들었다는 이유만으로 더 느슨한 기준을 적용해서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 코드 생성량이 커질수록 자동 검증의 비중은 더 중요해진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결과의 측정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공장은 측정할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리 역시 마찬가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 지표를 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 사이클 타임&lt;/li&gt;
&lt;li&gt;PR Merge 시간&lt;/li&gt;
&lt;li&gt;테스트 실패율&lt;/li&gt;
&lt;li&gt;배포 후 버그 발생률&lt;/li&gt;
&lt;li&gt;Rollback 비율&lt;/li&gt;
&lt;li&gt;보안 문제 발생률&lt;/li&gt;
&lt;li&gt;Agent 성공률&lt;/li&gt;
&lt;li&gt;Human Intervention 비율&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 AI 모델이 좋은지보다 더 중요한 질문은 이것일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 개발 루프가 가장 안정적으로 좋은 결과를 만들어내는가.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 추적 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 AI는 같은 입력을 주더라도 항상 같은 결과를 만들지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 완벽한 재현성보다는 &lt;b&gt;어떻게 만들어졌는지를 추적할 수 있는 구조&lt;/b&gt;가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 변경 사항에 다음 정보가 남을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 Task에서 시작됐는지&lt;/li&gt;
&lt;li&gt;어떤 Prompt를 사용했는지&lt;/li&gt;
&lt;li&gt;어떤 모델을 사용했는지&lt;/li&gt;
&lt;li&gt;어떤 Agent가 실행했는지&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 data-ke-size=&quot;size16&quot;&gt;이 기록은 문제가 발생했을 때 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 개발에 더 깊게 들어올수록 &lt;b&gt;코드 자체뿐 아니라 생성 과정 역시 개발 자산&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결국 핵심은 Harness다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 AI Agent 개발에서 자주 등장하는 개념이 &lt;b&gt;Harness&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Harness는 AI Agent가 일하는 환경과 규칙 전체를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 AI에게 제공하는 &amp;lsquo;작업장&amp;rsquo;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 다음과 같은 요소가 포함될 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context&lt;/li&gt;
&lt;li&gt;Rules&lt;/li&gt;
&lt;li&gt;Tools&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Quality Gate&lt;/li&gt;
&lt;li&gt;Definition of Done&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 모델이 아무리 좋아도 이런 구조가 없다면 결과 품질은 흔들릴 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 모델이 조금 부족하더라도 좋은 Harness가 있다면 일정 수준 이상의 결과를 반복적으로 만들어낼 가능성이 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 앞으로 AI 개발에서 중요한 경쟁력은 모델을 선택하는 능력보다 &lt;b&gt;Agent가 안정적으로 일할 수 있는 작업 환경을 설계하는 능력&lt;/b&gt;이 될 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;거대한 AI 하나보다 작은 루프 여러 개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI Agent를 활용하는 방식에서도 변화가 나타나고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 하나의 강력한 Agent에게 긴 작업을 맡기는 방식이 주목받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 서비스를 처음부터 끝까지 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 작업이 길어질수록 Context가 복잡해지고 오류가 누적될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최근에는 작업을 작게 나누고 짧은 Agent 실행을 반복하는 방식도 많이 논의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 1을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 Git이나 파일에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 Task에서는 새로운 Context로 Agent를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 결과를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작은 루프가 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 소프트웨어 팩토리는 하나의 거대한 AI 개발자가 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수많은 작은 AI 작업 루프가 연결된 개발 조직&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인 역시 공장의 품질 기준이 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리가 생산하는 것은 코드가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 실제로 사용하는 제품이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 디자인 역시 검증 대상이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 규칙을 자동으로 검사할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지정된 Color Token을 사용했는가&lt;/li&gt;
&lt;li&gt;Spacing 규칙을 지켰는가&lt;/li&gt;
&lt;li&gt;Typography가 Design System과 일치하는가&lt;/li&gt;
&lt;li&gt;Loading 상태가 존재하는가&lt;/li&gt;
&lt;li&gt;Empty State가 정의되어 있는가&lt;/li&gt;
&lt;li&gt;Error State가 존재하는가&lt;/li&gt;
&lt;li&gt;Keyboard Navigation이 가능한가&lt;/li&gt;
&lt;li&gt;문구 Tone &amp;amp; Manner가 규칙을 따르는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 디자인 시스템은 단순한 Figma 파일이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI와 사람이 모두 읽을 수 있는 제품 품질 규칙&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인도 개발 이전 단계의 작업물이 아니라 배포를 결정하는 Quality Gate의 일부가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가장 위험한 문제, &amp;lsquo;이해 부채&amp;rsquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발이 빠르게 확산되면서 새로운 형태의 기술 부채도 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;이해 부채(Understanding Debt)&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 작성하는 속도가 사람이 코드를 이해하는 속도보다 빨라지면 이런 상황이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 계속 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트도 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스도 정상적으로 돌아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 어느 순간 문제가 발생했을 때 아무도 해당 코드가 왜 그렇게 만들어졌는지 설명하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드량과 사람이 실제로 이해하고 있는 코드 사이의 간격이 계속 커지는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 많은 코드를 만들어낼수록 이 문제는 더 빠르게 커질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 모든 개발을 무조건 자동화하는 것이 좋은 전략이라고 보기는 어렵다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;같은 AI가 코드와 테스트를 만들 때 생기는 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발에서 특히 조심해야 할 부분이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 Agent가 기능을 구현하고 그 기능의 테스트까지 작성하는 경우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 요구사항을 잘못 이해했다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 잘못 이해한 요구사항을 기준으로 코드를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 같은 이해를 바탕으로 테스트도 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이런 상황이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드는 잘못됐지만 테스트는 통과한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초록색 체크가 반드시 올바른 소프트웨어를 의미하지 않는 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 중요한 기능에서는 구현과 검증을 분리하거나, 원래 Specification을 기준으로 독립적인 검증을 수행하는 구조가 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모든 것을 자동화할 필요는 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리에서 중요한 것은 &amp;lsquo;사람을 없애는 것&amp;rsquo;이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 작업에 사람이 필요한지 결정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화하기 쉬운 작업이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 영향 범위가 작고 실패 여부를 기계적으로 판단할 수 있는 변경이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 사람이 반드시 판단해야 할 작업도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증&lt;/li&gt;
&lt;li&gt;결제&lt;/li&gt;
&lt;li&gt;개인정보&lt;/li&gt;
&lt;li&gt;권한&lt;/li&gt;
&lt;li&gt;데이터베이스 Migration&lt;/li&gt;
&lt;li&gt;Public API&lt;/li&gt;
&lt;li&gt;핵심 Architecture&lt;/li&gt;
&lt;li&gt;중요한 보안 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 영역은 잘못됐을 때 영향이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 AI가 구현하더라도 사람이 최종 판단을 하는 것이 합리적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 &amp;lsquo;Human 0%&amp;rsquo;가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기계가 검증할 수 있는 것은 자동화하고, 판단이 필요한 곳에는 인간을 남기는 것.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경계를 설계하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 시대의 진짜 병목은 코드 생성이 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 모델과 컴퓨팅 자원을 늘리면 코드 생성량은 계속 늘릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사람의 Attention은 무한하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QA 인력도 무한하지 않고, Security Review나 Architecture Review에 사용할 수 있는 시간 역시 제한되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 AI 개발에서 가장 희소한 자원은 코드가 아니라 &lt;b&gt;검증 능력&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 앞으로 개발 조직이 고민해야 할 질문도 바뀔 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;어떤 AI가 코드를 가장 잘 쓰는가?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;우리는 AI가 만든 결과를 얼마나 빠르고 정확하게 검증할 수 있는가?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 더 중요한 질문이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발자의 역할도 달라질 수 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 더 많이 작성하게 된다고 해서 개발자가 필요 없어지는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 역할의 중심이 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 개발자의 핵심 업무가 직접 코드를 작성하는 것이었다면 앞으로는 다음과 같은 일이 더 중요해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 만들어야 하는지 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Acceptance Criteria를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent가 사용할 Context와 Tool을 설계한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화할 수 있는 검증 기준을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 Architecture를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 최종 품질을 책임진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 개발자의 역할이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Coder&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Software Factory Designer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 확장되는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI를 잘 쓰는 회사보다 &amp;lsquo;AI가 일하는 시스템&amp;rsquo;을 잘 만드는 회사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구는 빠르게 평준화될 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 모델은 누구나 사용할 수 있고, 좋은 코딩 Agent 역시 많은 조직이 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 차이는 어디에서 만들어질까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 조직 내부에 쌓인 다음과 같은 것들이 경쟁력이 될 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 Specification을 만드는 방법&lt;/li&gt;
&lt;li&gt;좋은 Prompt와 Context&lt;/li&gt;
&lt;li&gt;개발 규칙&lt;/li&gt;
&lt;li&gt;Design System&lt;/li&gt;
&lt;li&gt;테스트 체계&lt;/li&gt;
&lt;li&gt;Quality Gate&lt;/li&gt;
&lt;li&gt;Security Policy&lt;/li&gt;
&lt;li&gt;Agent Workflow&lt;/li&gt;
&lt;li&gt;실패 사례와 Feedback Loop&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들은 단순히 AI 모델을 바꾸는 것으로 얻을 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직이 시행착오를 반복하면서 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이런 구조가 갖춰진 조직에서는 AI Agent가 하나 늘어날 때 단순히 개발자 한 명이 늘어나는 것이 아니라 &lt;b&gt;생산라인 하나가 추가되는 효과&lt;/b&gt;가 나타날 수도 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앞으로 중요한 질문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발의 초반 경쟁은 &amp;lsquo;누가 AI로 더 빨리 코드를 쓰는가&amp;rsquo;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 단계는 빠르게 지나가고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 더 중요한 질문은 이것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 하루에 수백 개의 변경을 만들어낸다면 우리는 그 결과를 어떻게 믿을 것인가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 팩토리는 그 질문에 대한 하나의 답이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 더 많은 코드를 작성시키는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 코드만 살아남는 개발 공정을 만드는 것.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 시대의 소프트웨어 경쟁력은 결국 코드 생성량이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;얼마나 안정적으로 검증하고, 얼마나 반복적으로 좋은 제품을 출하할 수 있는 시스템을 가지고 있는가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 결정될 가능성이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌면 앞으로 가장 중요한 개발 능력은 코드를 잘 작성하는 능력만이 아닐지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI가 좋은 소프트웨어를 계속 만들어낼 수 있도록 &amp;lsquo;공장&amp;rsquo;을 설계하는 능력.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 소프트웨어 팩토리가 다시 주목받는 이유다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1807</guid>
      <comments>https://javaexpert.tistory.com/1807#entry1807comment</comments>
      <pubDate>Fri, 14 Aug 2026 17:59:46 +0900</pubDate>
    </item>
    <item>
      <title>Appwrite 뜯어보기: 오픈소스 BaaS는 내부에서 어떻게 동작할까?</title>
      <link>https://javaexpert.tistory.com/1806</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;웹이나 모바일 앱 하나를 만들 때 실제로 필요한 백엔드 기능은 생각보다 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입과 로그인, 데이터베이스, 파일 업로드, 권한 관리, 서버 함수, 실시간 통신, 이메일과 푸시 알림, 웹훅, 배포 환경까지 준비해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 서비스를 조합해 직접 만들 수도 있지만, Appwrite는 이 영역을 하나의 개발 플랫폼으로 묶으려는 오픈소스 프로젝트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 공식 README에서 자신을 &lt;b&gt;웹&amp;middot;모바일&amp;middot;AI 애플리케이션을 위한 오픈소스 개발 플랫폼&lt;/b&gt;으로 설명한다. Auth, Databases, Storage, Functions, Messaging, Sites, Realtime 등을 하나의 플랫폼에서 제공하며, Appwrite Cloud를 이용하거나 직접 서버에 Self-hosting할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Appwrite에서 더 흥미로운 부분은 기능 목록이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 많은 기능을 하나의 백엔드 플랫폼 안에서 어떻게 구조화했는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 코드를 살펴보면 Appwrite는 꽤 흥미로운 답을 보여준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Appwrite는 단순한 API 서버가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite를 가장 단순하게 보면 다음과 같은 구조다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Web / Mobile / Server Application
                │
                ▼
             Appwrite
                │
        ┌───────┼────────┐
        │       │        │
       Auth  Database  Storage
        │       │        │
        ├──── Functions ─┤
        │                │
     Realtime         Messaging
        │                │
        └───── Sites ────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 이런 기능을 REST API나 SDK를 통해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 README 기준 주요 제품은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Auth &amp;mdash; 로그인, 세션, OAuth, MFA, 사용자 인증&lt;/li&gt;
&lt;li&gt;Databases &amp;mdash; 데이터 저장, Query, Index, Relationship&lt;/li&gt;
&lt;li&gt;Storage &amp;mdash; 파일 업로드&amp;middot;다운로드와 이미지 처리&lt;/li&gt;
&lt;li&gt;Functions &amp;mdash; 이벤트 또는 스케줄 기반 서버 코드 실행&lt;/li&gt;
&lt;li&gt;Messaging &amp;mdash; Email, SMS, Push&lt;/li&gt;
&lt;li&gt;Sites &amp;mdash; 웹 애플리케이션 호스팅&lt;/li&gt;
&lt;li&gt;Realtime &amp;mdash; 실시간 데이터 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 REST뿐 아니라 WebSocket과 GraphQL도 지원한다고 아키텍처 문서에서 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Appwrite를 단순히&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Firebase를 오픈소스로 다시 만든 프로젝트&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정도로 이해하면 내부 구조에서 배울 수 있는 많은 부분을 놓치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 오히려 &lt;b&gt;여러 백엔드 기능을 하나의 플랫폼으로 구성하는 방법을 보여주는 대규모 백엔드 시스템 사례&lt;/b&gt;로 보는 편이 더 흥미롭다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 핵심은 '모놀리스 + 마이크로서비스' 혼합 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite의 아키텍처에서 가장 먼저 눈에 들어오는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 CONTRIBUTING 문서에는 현재 Appwrite 구조가 &lt;b&gt;Monolithic Architecture와 Microservice Architecture의 조합&lt;/b&gt;이라고 명시되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 API는 하나의 애플리케이션으로 구성한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;              ┌─────────────────────┐
Client ─────▶ │    Appwrite API     │
              │                     │
              │ Auth                │
              │ Database            │
              │ Storage             │
              │ Functions           │
              │ Teams               │
              │ Users               │
              │ Sites               │
              └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 시간이 많이 걸리거나 독립적으로 처리할 수 있는 작업은 Worker로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Docker Compose에는 다음과 같은 Worker들이 별도 컨테이너로 존재한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;appwrite-worker-webhooks
appwrite-worker-deletes
appwrite-worker-databases
appwrite-worker-builds
appwrite-worker-jobs
appwrite-worker-screenshots
appwrite-worker-certificates
appwrite-worker-functions
appwrite-worker-mails
appwrite-worker-notifications
appwrite-worker-messaging
appwrite-worker-migrations
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전체 구조를 단순화하면 다음에 가깝다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                         ┌──────────────┐
Internet ───────────────▶│   Traefik    │
                         └──────┬───────┘
                                │
                    ┌───────────┴──────────┐
                    ▼                      ▼
             Appwrite API           Appwrite Realtime
                    │
                    ▼
             Application Logic
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
      Database    Redis      Storage
                    │
                    ▼
                 Queue
                    │
       ┌────────────┼──────────────┐
       ▼            ▼              ▼
   Functions     Webhooks       Messaging
     Worker        Worker          Worker
       │
       ▼
 OpenRuntimes
 Executor
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README도 API Layer에서는 캐시를 활용하고, 무거운 작업은 Background Worker로 넘긴다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Appwrite의 설계 방향을 명확하게 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 기능을 처음부터 마이크로서비스로 분리하지 않았다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CONTRIBUTING 문서에 따르면 핵심 API를 모놀리스로 유지한 이유 중 하나는 작은 팀이 더 빠르게 개발하기 위해서였다. 대신 Worker와 내부 서비스는 별도 컨테이너로 나누어 독립적으로 확장할 수 있도록 구성했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. PHP인데 일반적인 PHP 서버와 조금 다르다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite 서버의 중심 언어는 PHP다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 일반적인&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Nginx
  &amp;darr;
PHP-FPM
  &amp;darr;
Laravel
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태와는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 main의 composer.json은 PHP &amp;gt;= 8.5.0과 Swoole 6 확장을 요구하고 있다. 또한 상당수의 핵심 구성요소가 utopia-php 패키지로 나뉘어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 의존성을 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;PHP &amp;gt;= 8.5
Swoole 6
Utopia Platform
Utopia Database
Utopia Queue
Utopia Cache
Utopia Storage
Utopia Messaging
Utopia WebSocket
GraphQL PHP
Redis Extension
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite 내부 개발 가이드 역시 Swoole 기반 비동기 런타임과 Utopia PHP Framework를 핵심 Stack으로 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너의 기본 실행 명령도 흥미롭다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;CMD [ &quot;php&quot;, &quot;app/http.php&quot; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도 PHP-FPM이 아니라 Appwrite 애플리케이션 자체가 HTTP 서버 프로세스로 동작하는 구조다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 거대한 Controller 대신 'Module + Action'&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 백엔드에서는 기능이 증가하면서 Controller가 비대해지는 문제가 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite 역시 과거에는&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app/controllers/api/[service].php
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태의 큰 Controller 파일을 사용했지만, 유지보수 문제가 생기면서 신규 개발을 HTTP Module 구조로 이전하고 있다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;src/Appwrite/Platform/Modules/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 아래에 기능별 Module이 존재한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Account
Avatars
Databases
Functions
Health
Notifications
Projects
Sites
Storage
Teams
Tokens
Users
VCS
Webhooks
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Appwrite Platform 클래스가 이 모듈들을 하나씩 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Module 내부는 대략 다음 패턴을 따른다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Modules/
  Teams/
    Module.php

    Services/
      Http.php
      Workers.php
      Tasks.php

    Http/
      Teams/
        Create.php
        Get.php
        Update.php
        Delete.php
        XList.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 개발 가이드에서는 HTTP Endpoint의 경로까지 파일 구조에 반영하도록 규칙을 정의하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;POST /v1/teams
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;Modules/Teams/Http/Teams/Create.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 코드를 보면 하나의 Action에서 HTTP Method, Path, Scope, Event, Audit, SDK Metadata, Parameter, Dependency Injection까지 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 단순화하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;class Create extends Action
{
    public function __construct()
    {
        $this
            -&amp;gt;setHttpMethod('POST')
            -&amp;gt;setHttpPath('/v1/teams')
            -&amp;gt;label('event', 'teams.[teamId].create')
            -&amp;gt;label('scope', 'teams.write')
            -&amp;gt;inject('dbForProject')
            -&amp;gt;inject('queueForEvents')
            -&amp;gt;callback($this-&amp;gt;action(...));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 눈여겨볼 점은 하나의 Endpoint 정의가 단순 Controller 함수가 아니라는 것이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HTTP Route
+
Validation
+
Authorization Scope
+
Audit
+
Event
+
SDK Metadata
+
Dependency Injection
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모두가 하나의 Action Contract에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 구현과 SDK 생성 규칙을 최대한 동일한 구조에서 관리하려는 방식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 무거운 작업은 Queue 뒤로 넘긴다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite 내부에서 중요한 구성요소 중 하나가 Event Queue다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Event.php에는 다양한 Queue 이름이 정의되어 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;v1-database
v1-deletes
v1-audits
v1-mails
v1-notifications
v1-functions
v1-webhooks
v1-certificates
v1-builds
v1-jobs
v1-screenshots
v1-messaging
v1-executions
v1-migrations
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event가 발생하면 Publisher를 이용해 Queue에 Payload를 넣는다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;return $this-&amp;gt;publisher-&amp;gt;enqueue(
    $queue,
    $payload
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 Event 구현에서도 Queue 생성 &amp;rarr; Payload 구성 &amp;rarr; Publisher enqueue 과정이 확인된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구조적으로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;API Request
    │
    ▼
Business Logic
    │
    ├────────────▶ Database
    │
    ▼
Generate Event
    │
    ▼
Queue
    │
    ├────▶ Webhook Worker
    ├────▶ Function Worker
    ├────▶ Messaging Worker
    ├────▶ Build Worker
    └────▶ Delete Worker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Team 생성 API에서도 데이터베이스 작업 후 queueForEvents에 이벤트 관련 Parameter를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 대규모 백엔드에서 매우 자주 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 기다릴 필요가 없는 작업을 동기 HTTP Request에서 분리하면 API와 Background Processing을 별도로 운영할 수 있기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Redis는 단순 캐시 이상으로 중요하다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite Compose에는 Redis가 별도 서비스로 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 설정에서는 Redis 7.4 Alpine 이미지를 사용하며 최대 메모리와 LRU 정책도 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite 개발 가이드에서는 Redis의 역할을 다음처럼 정리한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Cache
Queue
Pub/Sub
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전체 Appwrite 구조를 이해하려면 Redis를 단순한 캐시 서버라고 생각하기보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스와 Worker 사이를 연결하는 인메모리 인프라&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 관점에서 보는 것이 더 적절하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. 데이터베이스도 하나만 사용하는 구조가 아니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 8월 현재 main의 개발 환경 설정은 특히 흥미롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env에는 기본 Platform Database와 DocumentsDB, VectorsDB가 서로 다른 Adapter를 사용할 수 있도록 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 기본 설정은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Main DB
PostgreSQL

DocumentsDB
MongoDB

VectorsDB
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Redis는 별도로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Compose에도 MariaDB, MongoDB, PostgreSQL이 각각 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 현재 main에는&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;DocumentsDB
TablesDB
VectorsDB
Embeddings
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 데이터베이스 기능이 세분화되고 있다. VectorsDB에는 Collection과 Transaction을 포함한 별도 HTTP API 구조도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에는 별도의 Embedding 서비스도 포함되어 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;appwrite-embedding:
  image: appwrite/embedding:0.1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Embedding Model 설정에는 nomic-embed-text가 지정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 현재 개발 브랜치에서는 기존 BaaS 기능뿐 아니라 &lt;b&gt;Vector Database와 Embedding을 위한 AI 애플리케이션 인프라까지 코드베이스에 포함되고 있는 것&lt;/b&gt;을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, main 브랜치의 기능은 최신 정식 릴리스와 반드시 동일하다고 볼 수 없으므로 버전별 기능 확인은 별도로 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. Serverless Function은 OpenRuntimes가 담당한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite Functions도 Appwrite API 프로세스 안에서 직접 실행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Compose에는 별도의&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;openruntimes-executor
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 main에서는 openruntimes/executor:0.25.4 이미지를 사용하며 Functions와 Sites의 Runtime을 실행할 수 있도록 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Build&amp;middot;Job 처리를 위한&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;orchestrator-jobs
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조를 단순화하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Developer
    │
    ▼
Upload Function
    │
    ▼
Appwrite API
    │
    ▼
Build Worker
    │
    ▼
Jobs / Build Infrastructure
    │
    ▼
OpenRuntimes Executor
    │
    ▼
Isolated Runtime
    │
    ├─ Node
    ├─ PHP
    ├─ Python
    └─ ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README에서는 현재 Functions 제품이 여러 Runtime을 지원한다고 안내하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite가 단순 API 서버를 넘어 플랫폼으로 복잡해지는 이유 중 하나가 바로 이 Compute Layer다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Realtime도 API 서버와 분리되어 있다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realtime 역시 별도의 컨테이너다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;appwrite-realtime:
  entrypoint: realtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/v1/realtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 Appwrite Realtime 서비스로 라우팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 HTTP API와 WebSocket 연결을 같은 프로세스에 모두 몰아넣는 구조가 아니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;               Traefik
                  │
         ┌────────┴─────────┐
         │                  │
     REST / GraphQL      WebSocket
         │                  │
   Appwrite API      Appwrite Realtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realtime 연결과 일반 API 요청의 부하 특성이 다르다는 점을 고려한 서비스 분리가 소스 구조에서도 확인된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 앞단에는 Traefik이 있다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self-hosted Compose 환경에서 외부 트래픽의 진입점은 Traefik이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Compose에서는 Traefik 3.6이 사용되고 있으며 HTTP 80과 HTTPS 443 Entry Point를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik이 요청을 구분해&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;API
Console
Realtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등으로 라우팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조를 한 번 더 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                    Internet
                       │
                       ▼
                   Traefik
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
      Appwrite      Realtime      Console
        API
          │
          ▼
       Modules
          │
     ┌────┴────┐
     ▼         ▼
 Database     Redis
               │
               ▼
             Queue
               │
      ┌────────┼─────────┐
      ▼        ▼         ▼
 Functions  Webhooks  Messaging
 Worker      Worker     Worker
      │
      ▼
 OpenRuntimes
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite를 이해하는 데 가장 중요한 그림도 사실 이것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. Self-hosting이 가능한 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 Docker 기반 배포를 핵심 설치 방식으로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README에서는 Docker Compose뿐 아니라 Kubernetes, Docker Swarm, Rancher 같은 Container Orchestration 환경에서도 운영할 수 있다고 안내한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile에는 Storage 관련 디렉터리도 명시적으로 구성되어 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/storage/uploads
/storage/imports
/storage/cache
/storage/config
/storage/certificates
/storage/functions
/storage/debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서는 추가로 Functions, Sites, Builds 등을 Volume으로 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Self-hosted Appwrite를 운영한다면 Appwrite API 컨테이너 하나만 관리한다고 생각하기보다는&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Reverse Proxy
Database
Redis
Realtime
Workers
Executor
Build infrastructure
Persistent Storage
Certificate
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 포함한 &lt;b&gt;하나의 백엔드 플랫폼 Stack&lt;/b&gt;을 운영한다고 이해하는 편이 정확하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 오픈소스 라이선스는 BSD 3-Clause&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite Server Repository는 BSD 3-Clause License를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이선스 전문에는 소스 및 바이너리 형태의 재배포와 수정 사용을 허용하면서 Copyright Notice와 Disclaimer 유지 등의 조건을 명시하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업에서 직접 Self-hosting하거나 소스를 분석하려는 경우에도 비교적 접근하기 쉬운 오픈소스 라이선스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 상용 제품에 수정&amp;middot;재배포하려는 경우에는 Appwrite Server뿐 아니라 함께 사용하는 별도 이미지와 외부 프로젝트들의 라이선스도 각각 확인할 필요가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. Appwrite 코드에서 배울 수 있는 다섯 가지&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite를 직접 사용할 계획이 없더라도 백엔드 개발자 입장에서 참고할 만한 설계 요소가 많다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;① 처음부터 모든 것을 Microservice로 만들 필요는 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 핵심 API를 Monolith로 유지하면서 Worker를 독립 서비스로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Core Business Logic
        │
        ▼
     Monolith

Heavy / Async Job
        │
        ▼
   Independent Worker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 현실적인 분리 방법을 보여준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;② Side Effect는 Queue 뒤로 분리할 수 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일, Function 실행, Webhook, Messaging, Delete 같은 작업은 별도 Queue와 Worker로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 서버와 Background Processing의 책임을 분리하는 실제 대규모 사례로 살펴볼 만하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;③ 기능 단위 Module 구조가 중요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite는 Account, Databases, Functions, Storage, Sites, Teams처럼 도메인별 Module을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 커질수록 단순한&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;controllers/
services/
repositories/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조보다&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Modules/
  Teams/
  Storage/
  Functions/
  Databases/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 기능 자체를 상위 경계로 두는 방식이 코드 탐색에 유리한 경우가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;④ API 규칙을 코드 구조까지 연결한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite의 신규 HTTP Action 파일은 기본적으로&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Create
Get
Update
Delete
XList
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태로 제한하는 규칙을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL, Directory, Action Class, SDK Operation이 최대한 동일한 Resource Model을 표현하도록 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 API를 오랫동안 유지할 때 Naming Convention과 Directory Convention 자체가 Architecture의 일부가 될 수 있다는 좋은 사례다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⑤ BaaS도 결국 분산 시스템이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 Appwrite에서 간단하게&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;account.create(...)
databases.create(...)
storage.createFile(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 API를 호출하지만 그 뒤에는&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Reverse Proxy
API
Authorization
Database
Cache
Queue
Worker
Runtime
Storage
Realtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 연결되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BaaS 제품이 사용자에게 복잡성을 감춰준다고 해서 시스템 자체가 단순한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite Repository는 그 복잡성을 실제 코드 수준에서 관찰할 수 있다는 점에서 공부할 가치가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. 그렇다면 Appwrite는 어떤 프로젝트에 어울릴까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구성상 Appwrite는 다음과 같은 요구사항을 가진 애플리케이션에서 검토할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;사용자 인증이 필요하다
+
Database가 필요하다
+
파일 Storage가 필요하다
+
Realtime 기능이 필요하다
+
Backend Function이 필요하다
+
Messaging이 필요하다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Cloud 서비스뿐 아니라 Self-hosted 선택지도 필요하다면 Appwrite의 구조를 살펴볼 이유가 충분하다. Cloud와 Self-hosted 두 방식을 모두 제공한다는 점은 공식 README에서도 명시되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Self-hosted를 선택한다면 Docker Compose에서 확인할 수 있듯 Appwrite API뿐 아니라 Redis, 데이터베이스, Worker, Executor 등 여러 인프라 컴포넌트를 함께 운영하게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. Appwrite Repository를 공부한다면 이 순서가 좋다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 수많은 소스 파일을 읽는 것은 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 순서가 구조를 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. README.md
        &amp;darr;
2. docker-compose.yml
        &amp;darr;
3. composer.json
        &amp;darr;
4. src/Appwrite/Platform/Appwrite.php
        &amp;darr;
5. src/Appwrite/Platform/Modules
        &amp;darr;
6. 특정 HTTP Action 하나
        &amp;darr;
7. Event / Queue
        &amp;darr;
8. Worker
        &amp;darr;
9. OpenRuntimes Executor
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Team 생성 하나만 따라가도 많은 구조를 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;POST /v1/teams
       │
       ▼
Teams/Create.php
       │
       ├── Validation
       ├── Authorization
       ├── Database
       ├── Audit Metadata
       └── Event
                │
                ▼
              Queue
                │
                ▼
              Worker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 하나의 요청을 끝까지 추적하면 거대한 Repository를 훨씬 빠르게 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite를 겉에서 보면 Auth, Database, Storage를 제공하는 편리한 Backend-as-a-Service다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 내부를 살펴보면 훨씬 더 흥미롭다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;PHP + Swoole
        │
        ▼
Modular Monolith API
        │
        ▼
Redis / Queue
        │
        ▼
Independent Workers
        │
        ├── Messaging
        ├── Webhook
        ├── Functions
        ├── Build
        └── Migration
        │
        ▼
OpenRuntimes
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 모든 구성요소를 Docker 환경에서 하나의 개발 플랫폼으로 묶는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appwrite에서 가장 인상적인 부분은 &quot;기능이 많다&quot;는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복잡한 백엔드 플랫폼을 하나의 거대한 마이크로서비스 집합으로 시작하지 않고, 핵심 API의 응집도는 유지하면서 비동기 작업과 실행 인프라를 점진적으로 분리했다는 점&lt;/b&gt;이다. 이 설계 배경은 Appwrite의 공식 아키텍처 설명과 현재 컨테이너 구조에서 직접 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 백엔드, BaaS, Event-driven Architecture, Worker Architecture, Serverless Runtime에 관심이 있다면 Appwrite는 단순히 사용해 볼 오픈소스를 넘어 &lt;b&gt;실제 프로덕션급 백엔드 플랫폼의 구조를 공부할 수 있는 Repository&lt;/b&gt;로 살펴볼 만하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석 정보&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Repository: appwrite/appwrite&lt;/li&gt;
&lt;li&gt;분석 기준: 2026-08-12&lt;/li&gt;
&lt;li&gt;분석 브랜치: main&lt;/li&gt;
&lt;li&gt;분석 시점 최신 main Commit: ac639005607c71d8a05253b38f3d7398bd298075&lt;/li&gt;
&lt;li&gt;확인 시점 최신 정식 Release: 1.9.6&lt;/li&gt;
&lt;li&gt;License: BSD 3-Clause&lt;/li&gt;
&lt;li&gt;Repository: &lt;a href=&quot;https://github.com/appwrite/appwrite&quot;&gt;https://github.com/appwrite/appwrite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 분석 파일&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;README.md
CONTRIBUTING.md
AGENTS.md
composer.json
Dockerfile
docker-compose.yml
.env

src/Appwrite/Platform/Appwrite.php
src/Appwrite/Platform/Modules/
src/Appwrite/Event/Event.php
src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ main 브랜치는 개발이 계속 진행되는 브랜치이므로 향후 디렉터리 구조, Runtime, Database 구성 및 서비스 버전은 변경될 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1786505093298&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - appwrite/appwrite: Appwrite&amp;reg; - complete cloud infrastructure for your web, mobile and AI apps. Including Auth, Databas&quot; data-og-description=&quot;Appwrite&amp;reg; - complete cloud infrastructure for your web, mobile and AI apps. Including Auth, Databases, Storage, Functions, Messaging, Hosting, Realtime and more - appwrite/appwrite&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/appwrite/appwrite&quot; data-og-url=&quot;https://github.com/appwrite/appwrite&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yVo1l/dJMb8U89IPH/ZZvtjK9aKjO9pAnyorDeC0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/EQrXe/dJMb9eftRtM/M2pRqDKBFejbP6Vktvmlo0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/1UamP/dJMb9dHD4Wd/sn4yBZwSZha63A4SHEeLck/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://github.com/appwrite/appwrite&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/appwrite/appwrite&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yVo1l/dJMb8U89IPH/ZZvtjK9aKjO9pAnyorDeC0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/EQrXe/dJMb9eftRtM/M2pRqDKBFejbP6Vktvmlo0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/1UamP/dJMb9dHD4Wd/sn4yBZwSZha63A4SHEeLck/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - appwrite/appwrite: Appwrite&amp;reg; - complete cloud infrastructure for your web, mobile and AI apps. Including Auth, Databas&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Appwrite&amp;reg; - complete cloud infrastructure for your web, mobile and AI apps. Including Auth, Databases, Storage, Functions, Messaging, Hosting, Realtime and more - appwrite/appwrite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1806</guid>
      <comments>https://javaexpert.tistory.com/1806#entry1806comment</comments>
      <pubDate>Wed, 12 Aug 2026 12:25:00 +0900</pubDate>
    </item>
    <item>
      <title>안드레 카파시가 말하는 &amp;lsquo;어떤 분야에서 전문가가 되는 법&amp;rsquo;</title>
      <link>https://javaexpert.tistory.com/1804</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uMgp9/dJMcagT4RlX/KtKFSUsoDSrmX8Yj3EGUsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uMgp9/dJMcagT4RlX/KtKFSUsoDSrmX8Yj3EGUsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uMgp9/dJMcagT4RlX/KtKFSUsoDSrmX8Yj3EGUsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuMgp9%2FdJMcagT4RlX%2FKtKFSUsoDSrmX8Yj3EGUsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;566&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 공부부터 하지 말고, &lt;b&gt;프로젝트부터 시작한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무언가를 처음부터 끝까지 체계적으로 전부 공부한 뒤 시작하려 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 &lt;b&gt;구체적인 프로젝트 하나를 정하고 직접 완성해본다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행하다가 모르는 것이 나오면 그때 필요한 내용을 배운다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Project &amp;rarr; 문제 발생 &amp;rarr; 필요한 공부 &amp;rarr; 해결 &amp;rarr; 다음 문제&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;너비 우선 학습(Breadth-first)&lt;/b&gt;보다 &lt;b&gt;깊이 우선 학습(Depth-first)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 웹 개발을 배운다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML &amp;rarr; CSS &amp;rarr; JS &amp;rarr; React &amp;rarr; DB &amp;rarr; 서버...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 모두 공부하고 프로젝트를 시작하는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만들고 싶은 서비스 시작 &amp;rarr; 로그인 필요 &amp;rarr; 인증 공부 &amp;rarr; DB 필요 &amp;rarr; DB 공부 &amp;rarr; 배포 필요 &amp;rarr; 배포 공부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 &lt;b&gt;필요할 때 배우는 On-demand Learning&lt;/b&gt;을 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 배운 것을 반드시 &lt;b&gt;자기 언어로 다시 설명한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 보고 이해했다고 끝내지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배운 것을&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로 정리하고&lt;/li&gt;
&lt;li&gt;다른 사람에게 설명하고&lt;/li&gt;
&lt;li&gt;블로그에 쓰거나&lt;/li&gt;
&lt;li&gt;개발 노트로 남기고&lt;/li&gt;
&lt;li&gt;&amp;ldquo;왜 이렇게 동작하는가?&amp;rdquo;를 자기 말로 설명한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명하지 못한다면 아직 완전히 이해하지 못한 부분이 있다는 신호로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 흐름은:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배운다 &amp;rarr; 사용한다 &amp;rarr; 설명한다 &amp;rarr; 다시 이해한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면서 지식이 단순한 정보가 아니라 &lt;b&gt;자기 지식&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 다른 사람과 비교하지 말고 &lt;b&gt;과거의 나와 비교한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문가와 자신의 현재 실력을 비교하면 끝이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 기준을&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;저 사람보다 잘하나?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 아니라&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;3개월 전의 나보다 무엇을 더 할 수 있게 되었나?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 잡는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 하나씩 쌓이면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트 1 &amp;rarr; 프로젝트 2 &amp;rarr; 프로젝트 3 &amp;rarr; &amp;hellip;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로젝트가 이전보다 조금 더 어려워지고, 해결할 수 있는 문제의 범위가 넓어진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결국 하나의 루프로 정리하면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① 만들고 싶은 프로젝트를 정한다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;② 일단 만든다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;③ 막히는 부분을 공부한다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;④ 직접 해결한다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;⑤ 배운 것을 자기 언어로 정리한다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;⑥ 다음에는 조금 더 어려운 프로젝트를 만든다&lt;/b&gt;&lt;br /&gt;&amp;rarr; &lt;b&gt;⑦ 이전의 나와 비교한다&lt;/b&gt;&lt;br /&gt;&amp;rarr; 반복&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 문장으로 요약&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전문가가 되려면 모든 것을 공부하려 하지 말고, 실제 프로젝트를 깊게 파고들며 필요한 것을 그때그때 배우고, 배운 것을 자신의 언어로 설명하면서 이전의 나보다 계속 발전하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 개발 공부에 적용하면 &lt;b&gt;&amp;ldquo;공부 &amp;rarr; 프로젝트&amp;rdquo;가 아니라 &amp;ldquo;프로젝트 &amp;rarr; 공부 &amp;rarr; 해결 &amp;rarr; 기록 &amp;rarr; 더 어려운 프로젝트&amp;rdquo;&lt;/b&gt;의 순서라는 것이 핵심입니다.&lt;/p&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1804</guid>
      <comments>https://javaexpert.tistory.com/1804#entry1804comment</comments>
      <pubDate>Mon, 10 Aug 2026 14:02:50 +0900</pubDate>
    </item>
    <item>
      <title>핫한 prime-agent 훝어보기</title>
      <link>https://javaexpert.tistory.com/1803</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심을 한 문장으로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 코딩 에이전트가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM &amp;rarr; tool call &amp;rarr; 결과 &amp;rarr; LLM &amp;rarr; tool call&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식이라면 Prime Agent는 오히려&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM &amp;rarr; 지속되는 Python 런타임 &amp;rarr; 코드로 도구/데이터/서브에이전트 제어 &amp;rarr; 결과 축적 &amp;rarr; 다음 행동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 구조를 중심으로 설계했습니다. 모델에 기본적으로 노출되는 핵심 실행 환경도 &lt;b&gt;persistent IPython kernel&lt;/b&gt;이며, 파일 읽기&amp;middot;수정, shell 실행, skill 호출, subagent 생성까지 Python에서 프로그래밍하듯 조합하게 합니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/rlm.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처를 단순화하면&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;                  ┌─────────────────────┐
                  │      User / TUI     │
                  └──────────┬──────────┘
                             │
                     Prime Agent Daemon
                             │
              ┌──────────────▼─────────────┐
              │       Root Agent           │
              │                            │
              │ Persistent IPython Kernel  │
              │ variables / functions      │
              │ files / shell / skills     │
              └──────┬─────────┬───────────┘
                     │         │
                rlm(...)       │ skills
                     │         │
             ┌───────▼───┐ ┌──▼──────────┐
             │ Subagent A│ │ Python Skill│
             └───────────┘ └─────────────┘
                     │
               ┌─────▼─────┐
               │Subagent A-1│
               └────────────┘

         &amp;darr; persistent state &amp;darr;

 JSONL history
 Python state
 goals
 schedules
 heartbeat
 memories
 skills
 subagent definitions
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 구현에서도 TUI와 실행 프로세스를 분리하고, daemon supervisor가 세션&amp;middot;worker&amp;middot;routing&amp;middot;복구를 관리하며, 각 worker가 root agent와 scheduler, IPython kernel, RLM children을 보유합니다. 그래서 터미널 UI를 닫아도 worker가 살아 있으면 작업이 계속될 수 있습니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/architecture.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. RLM이 가장 중요한 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 RLM은 단순히 &amp;ldquo;에이전트를 여러 개 띄운다&amp;rdquo;는 의미가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Agent에서 rlm(...)은 Python 함수처럼 취급됩니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;api = await rlm(
    &quot;API 코드를 분석하고 보안 문제를 찾아라&quot;,
    name=&quot;api-reviewer&quot;
)

tests = await rlm(
    &quot;테스트 커버리지를 분석해라&quot;,
    name=&quot;test-reviewer&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 호출은 단순 LLM API 호출이 아니라 &lt;b&gt;독립적인 Prime Agent 세션&lt;/b&gt;을 하나 생성합니다. child에게도 자체 context, session directory, 모델, 필요 시 IPython kernel이 있으며 결과는 단순 함수 return으로 돌려주는 게 아니라 agent-to-agent messaging을 통해 전달할 수 있습니다. (&lt;a href=&quot;https://www.primeintellect.ai/blog/prime-agent&quot;&gt;Prime Intellect&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 중요한 것은 child가 일회용이 아니라는 점입니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;children = await rlm.list_subagents()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 기존 child를 다시 찾은 뒤,&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;await agent_message.send(
    &quot;방금 수정한 코드까지 다시 검사해&quot;,
    receiver_role=&quot;child&quot;,
    receiver_name=&quot;api-reviewer&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 이전에 일했던 에이전트에게 추가 작업을 줄 수 있습니다. child registry는 compaction이나 kernel restart, parent restoration 이후에도 복구하도록 설계돼 있습니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/rlm.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 꽤 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 패턴이&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;manager
 ├─ agent A &amp;rarr; 종료
 ├─ agent B &amp;rarr; 종료
 └─ agent C &amp;rarr; 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라면 Prime Agent가 지향하는 것은&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;manager
 ├─ frontend specialist ──────┐
 ├─ backend specialist ───────┤ 장기간 유지
 ├─ test specialist ──────────┤
 └─ security specialist ──────┘
             &amp;uarr;
       필요할 때 재호출
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 더 가깝습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Persistent Python이 상당히 좋은 아이디어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 tool-calling에서는 이런 데이터를 매번 모델 context에 다시 넣는 경우가 많습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;50개 파일 검색
&amp;rarr; 결과 30KB
&amp;rarr; LLM에게 전달
&amp;rarr; 다시 grep
&amp;rarr; 또 LLM에게 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Agent에서는 Python에 그대로 남겨둘 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;files = ...
results = ...
failed_tests = ...
dependency_graph = ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 turn에서도 해당 변수와 함수들을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서상 Python state는 tool call과 context compaction을 넘어 유지됩니다. 즉 LLM의 context window를 &lt;b&gt;작업 메모리 전체로 사용하지 않고&lt;/b&gt;, 상당 부분을 외부 Python environment에 밀어놓는 것입니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/rlm.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 RLM의 핵심 철학입니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;LLM context
=
모든 정보를 집어넣는 창고
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 아니라&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;LLM context
=
현재 판단에 필요한 정보

Python environment
=
거대한 외부 작업 메모리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 역할을 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장시간 코딩 작업에서는 상당히 합리적인 설계입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. &amp;ldquo;Self-improving&amp;rdquo;의 정확한 의미&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기가 가장 오해하기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prime Agent가 작업하면서 GPT나 Claude의 neural weights를 재학습시키는 것은 아닙니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 Continual Harness라는 별도의 상태를 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 설명에서는 이를&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;H = (&amp;rho;, G, K, M)

&amp;rho; = prompts
G = subagents
K = skills
M = memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태로 정의하고 있습니다. 이 상태에 대해 create/read/update/delete가 가능합니다. (&lt;a href=&quot;https://www.primeintellect.ai/blog/prime-agent&quot;&gt;Prime Intellect&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 에이전트가 여러 번 작업해보니&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;우리 프로젝트에서는 migration 수정 후
반드시 pnpm prisma generate를 실행해야 한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 패턴을 발견했다고 합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그냥 대화 중 발견한 사실입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 /refine이 trajectory를 분석해 이것을&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Memory
또는
Prompt Note
또는
Skill
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 승격시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 반복 작업이라면:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;migration-check/
 ├── SKILL.md
 ├── pyproject.toml
 └── src/
     └── migration_check/
         └── __init__.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 Python-backed skill까지 만들 수 있습니다. Prime Agent는 instruction-only SKILL.md뿐 아니라 실제 Python package를 persistent IPython에 설치하는 형태도 공식 지원합니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/skills.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정확하게 표현하면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델이 개선되는 것이 아니라 에이전트의 작업 환경과 operating procedure가 경험을 통해 개선됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 보는 게 맞습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 오히려 이 방식이 실용적으로 중요하다고 봅니다. 모델 fine-tuning 없이도 실제 프로젝트에서 반복적으로 발생하는 노하우를 축적할 수 있기 때문입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Continual Harness가 재미있는 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 AGENTS.md, CLAUDE.md, SKILL.md 방식은 사람이 관리합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;사람
&amp;darr;
실수 발견
&amp;darr;
AGENTS.md 수정
&amp;darr;
다음 작업부터 개선
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Agent는 이 루프의 일부를 agent에게 넘기려 합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Agent 작업

       &amp;darr;

성공 / 실패 trajectory

       &amp;darr;

/refine

       &amp;darr;

┌──────────────┐
│ Prompt Note  │
│ Memory       │
│ Skill        │
│ Subagent Spec│
└──────────────┘

       &amp;darr;

다음 작업

       &amp;darr;

더 나은 Harness
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/refine은 현재 trajectory를 보고 가능한 한 작은 CRUD 변경을 적용하도록 설계되어 있으며, base system prompt 자체는 수정하지 않습니다. refinement history를 남기고 이전 상태로 rollback하는 구조도 있습니다. (&lt;a href=&quot;https://www.primeintellect.ai/blog/prime-agent&quot;&gt;Prime Intellect&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이것은 사실상 &lt;b&gt;&amp;ldquo;agent-level continual learning&amp;rdquo;을 weights 밖에서 구현하려는 접근법&lt;/b&gt;이라고 이해하면 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 장시간 자율 작업은 꽤 제대로 설계되어 있습니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Agent에는 서로 다른 역할의 기능이 따로 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Persistent Goal&lt;/td&gt;
&lt;td&gt;끝날 때까지 목표 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autonomous Mode&lt;/td&gt;
&lt;td&gt;다음 작업을 계속 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heartbeat&lt;/td&gt;
&lt;td&gt;일정 간격으로 agent 깨우기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schedule&lt;/td&gt;
&lt;td&gt;특정 시점/cron 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subagent&lt;/td&gt;
&lt;td&gt;병렬 전문 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compaction&lt;/td&gt;
&lt;td&gt;오래된 context 압축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSONL persistence&lt;/td&gt;
&lt;td&gt;세션 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daemon&lt;/td&gt;
&lt;td&gt;UI와 실행 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 heartbeat는 사용자용 /heartbeat와 agent가 직접 만드는 rlm_heartbeat가 분리되어 있습니다. prime-agent schedule은 one-time 또는 cron prompt를 별도로 예약할 수 있습니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/long-running-agents.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이런 구조가 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;Goal:
&quot;서비스 전체 성능을 개선하고 테스트까지 통과시켜라.&quot;

09:00
main agent 시작

09:05
├── backend-agent
├── frontend-agent
├── db-agent
└── benchmark-agent

09:30 heartbeat
&quot;benchmark 결과 확인&quot;

10:00
benchmark-agent &amp;rarr; main
&quot;API P95 820ms&quot;

main
&amp;rarr; backend-agent
&quot;DB query 최적화&quot;

11:00
test gate 실행

FAILED

main
&amp;rarr; 수정

12:00
test gate

PASSED

goal.complete()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Autonomous mode에는 max turns, token budget, wall-clock timeout과 quality gate를 설정할 수 있고, gate가 실패하면 그 출력을 agent에게 다시 넘겨 후속 작업을 수행하게 할 수 있습니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/long-running-agents.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 단순 cron 기반 AI script와 큰 차이입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Codex / Claude Code와 가장 다른 부분&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 보기에는 차이를 이렇게 잡는 것이 가장 정확합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex / Claude Code 계열Prime Agent&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;중심 abstraction&lt;/td&gt;
&lt;td&gt;Agent + tools&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Agent + persistent REPL&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool 사용&lt;/td&gt;
&lt;td&gt;개별 tool calling&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Python program&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;subagent&lt;/td&gt;
&lt;td&gt;task delegation&lt;/td&gt;
&lt;td&gt;&lt;b&gt;persistent agent process&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;context&lt;/td&gt;
&lt;td&gt;모델 context 중심&lt;/td&gt;
&lt;td&gt;&lt;b&gt;context + Python state&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기 작업&lt;/td&gt;
&lt;td&gt;continuation 중심&lt;/td&gt;
&lt;td&gt;&lt;b&gt;daemon + goal + schedule + heartbeat&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습&lt;/td&gt;
&lt;td&gt;rules/memory&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Continual Harness CRUD&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;skill&lt;/td&gt;
&lt;td&gt;instructions/scripts&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Python package 가능&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A2A&lt;/td&gt;
&lt;td&gt;제한적&lt;/td&gt;
&lt;td&gt;&lt;b&gt;native messaging&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 그래서 Prime Agent의 진짜 경쟁 포인트는 모델 자체의 코딩 능력보다 &lt;b&gt;&amp;ldquo;에이전트를 운영하는 OS/runtime&amp;rdquo;&lt;/b&gt;에 있습니다. 이 해석은 위 아키텍처와 공식 문서에 나타난 설계 패턴을 기반으로 한 것입니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/architecture.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. 의외로 모델 선택도 자유롭습니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 공식 provider 문서에는 OpenAI, Anthropic, Gemini, DeepSeek, Groq, Cerebras, OpenRouter, Fireworks, MiniMax, Kimi, Xiaomi MiMo 등 상당히 많은 provider가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 OAuth 방식으로 &lt;b&gt;ChatGPT Plus/Pro(Codex), Claude Pro/Max, GitHub Copilot&lt;/b&gt;도 지원한다고 문서화돼 있습니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/providers.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Prime Agent
       │
       ├─ GPT
       ├─ Claude
       ├─ Gemini
       ├─ GLM
       ├─ MiniMax
       ├─ Kimi
       └─ OpenRouter models
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 harness 실험이 가능합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 주의할 부분도 큽니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 &lt;b&gt;보안&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Agent 공식 문서도 IPython kernel과 worker가 security sandbox가 아니라고 명시하고 있습니다. 모델이 생성한 Python과 shell command가 기본적으로 사용자 OS 권한으로 실행됩니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;Prime Agent
&amp;darr;
persistent Python
&amp;darr;
shell
&amp;darr;
filesystem
&amp;darr;
skills
&amp;darr;
subagents
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 agent의 힘이 커질수록 prompt injection이나 악성 repository, 악성 skill의 피해 범위도 커질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 장시간 autonomous mode를 쓸 거라면 &lt;b&gt;Docker/VM/devcontainer 같은 별도 sandbox 안에서 돌리는 방식&lt;/b&gt;이 더 적합하다고 판단합니다. [Inference] 이는 공식 문서가 untrusted repository/instruction에 external sandbox 또는 restricted environment 사용을 권고한다는 점을 바탕으로 한 운영상 권고입니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. 벤치마크는 흥미롭지만 아직 조심해서 봐야 합니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prime Intellect는 자체 발표에서 Prime Agent를 Opus 5, GPT-5.6 Sol, GLM-5.2 등과 조합해 장문맥 benchmark를 테스트했고, 여러 항목에서 native harness와 경쟁하거나 앞서는 결과를 보고했습니다. ARC-AGI-3에서도 높은 결과를 발표했습니다. (&lt;a href=&quot;https://www.primeintellect.ai/blog/prime-agent&quot;&gt;Prime Intellect&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이것은 &lt;b&gt;Prime Intellect 자체 평가 결과&lt;/b&gt;입니다. 독립적인 대규모 재현 결과와 동일하게 취급해서는 안 됩니다. 또한 회사도 현재 모델들이 Prime Agent harness 자체를 대상으로 훈련된 것은 아니라고 밝히고 있습니다. (&lt;a href=&quot;https://www.primeintellect.ai/blog/prime-agent&quot;&gt;Prime Intellect&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 이 프로젝트에서 제가 가장 높게 보는 부분&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 개인적으로 기술적으로 가장 중요한 순서를 꼽으면 &lt;b&gt;Persistent REPL &amp;rarr; persistent subagent &amp;rarr; Continual Harness &amp;rarr; daemon/scheduler&lt;/b&gt; 순입니다. 이는 기능의 기반 의존관계를 기준으로 한 판단입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 이 조합:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Persistent Python
       +
Persistent Subagents
       +
Persistent Memory
       +
Executable Skills
       +
Heartbeat
       +
Goal
       +
Scheduler
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 만들어내는 결과는 단순히&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;AI가 코드를 잘 짠다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 아니라&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;AI 개발팀이 프로젝트 안에 계속 상주한다.&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 가까워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 프로젝트에:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;main-agent
│
├── architect
├── backend
├── flutter
├── security
├── test
└── devops
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 장기간 유지하면서 각 agent에게 프로젝트별 memory와 skill을 축적시키는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 잘 발전하면 &lt;b&gt;Claude Code/Codex의 경쟁 제품&lt;/b&gt;보다는 오히려 &lt;b&gt;AI Agent Runtime / Agent OS / autonomous engineering runtime&lt;/b&gt;이라는 분류가 더 적합해질 가능성이 있습니다. [Inference] 이는 현재 daemon, persistent session, RLM child lifecycle, scheduling, Continual Harness 구조를 종합한 판단입니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/architecture.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주목할 가치가 상당히 큰 레포입니다.&lt;/b&gt; 특히 RLM = recursive call 정도로 보면 핵심을 놓칩니다. 진짜 포인트는 &lt;b&gt;LLM이 Python을 자기 작업용 컴퓨터처럼 사용하고, 그 안에서 다른 LLM 세션까지 함수처럼 조직하며, 수행 과정에서 얻은 노하우를 memory/skill/subagent spec으로 축적하는 것&lt;/b&gt;입니다. (&lt;a href=&quot;https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/docs/rlm.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Prime Agent가 말하는 &amp;ldquo;self-improving&amp;rdquo;도 &lt;b&gt;모델 자체 self-training이 아니라 harness의 self-improvement&lt;/b&gt;라고 이해하는 게 정확합니다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1803</guid>
      <comments>https://javaexpert.tistory.com/1803#entry1803comment</comments>
      <pubDate>Mon, 10 Aug 2026 11:02:13 +0900</pubDate>
    </item>
    <item>
      <title>OpenAI Agents SDK</title>
      <link>https://javaexpert.tistory.com/1802</link>
      <description>&lt;h1&gt;챗봇 하나를 만드는 일과 인공지능(AI) 에이전트 팀을 만드는 일은 다르다.&lt;/h1&gt;
&lt;p&gt;챗봇은 질문을 받고 답을 돌려주면 끝난다. 에이전트는 다르다. 필요한 도구를 고르고, 작업 결과를 다시 읽고, 다른 전문가에게 일을 넘긴 뒤 조건을 만족할 때까지 다음 행동을 결정해야 한다. 정작 어려운 부분은 모델 호출보다 그 주변의 실행 흐름에 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openai/openai-agents-python&quot;&gt;OpenAI Agents SDK&lt;/a&gt;는 바로 이 흐름을 파이썬 코드로 다루기 위한 오픈소스 소프트웨어 개발 키트(Software Development Kit, SDK)다. 거대한 워크플로 엔진을 먼저 배우지 않아도 &lt;code&gt;Agent&lt;/code&gt;, &lt;code&gt;Runner&lt;/code&gt;, 도구, handoff, guardrail 같은 몇 가지 개념만으로 에이전트 애플리케이션을 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;이 프로젝트를 한 문장으로 정리하면 이렇다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;OpenAI Agents SDK는 AI가 답변만 생성하는 단계를 넘어, 도구를 사용하고 다른 에이전트와 협업하며 작업을 끝낼 때까지 실행하는 런타임이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;모델보다 중요한 것은 실행 흐름이다&lt;/h2&gt;
&lt;p&gt;일반적인 대규모 언어 모델(Large Language Model, LLM) 애플리케이션은 입력을 모델에 보내고 결과를 받는다.&lt;/p&gt;
&lt;p&gt;에이전트 애플리케이션이라면 그 사이에 여러 단계가 들어간다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;사용자 입력
  ↓
에이전트가 다음 행동 판단
  ↓
도구 실행 또는 다른 에이전트로 handoff
  ↓
실행 결과를 다시 모델에 전달
  ↓
가드레일과 출력 형식 검증
  ↓
최종 결과 또는 다음 반복&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 반복 구조를 직접 구현하려면 도구 호출, 오류 처리, 대화 이력, 상태 저장, 스트리밍, 실행 중단과 재개까지 관리해야 한다. Agents SDK의 &lt;code&gt;Runner&lt;/code&gt;가 이 반복을 맡는다. 개발자에게 남는 일은 각 에이전트의 역할과 사용할 수 있는 도구를 선언하는 것이다.&lt;/p&gt;
&lt;p&gt;핵심은 모델에게 더 긴 프롬프트를 주는 데 있지 않다. &lt;strong&gt;모델이 어떤 범위 안에서 무엇을 할 수 있는지 코드로 정의하는 것&lt;/strong&gt;, 그것이 출발점이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;네 가지 개념으로 시작한다&lt;/h2&gt;
&lt;h3&gt;1. Agent: 역할과 권한을 가진 작업자&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Agent&lt;/code&gt;는 단순한 모델 별칭이 아니다. 역할과 실행 조건을 다음과 같이 한데 묶는다.&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;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;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from agents import Agent, Runner

agent = Agent(
    name=&amp;quot;Assistant&amp;quot;,
    instructions=&amp;quot;질문에 짧고 정확한 한국어로 답하세요.&amp;quot;,
)

result = Runner.run_sync(
    agent,
    &amp;quot;멀티 에이전트 시스템이 필요한 이유를 한 문장으로 설명해 줘.&amp;quot;,
)

print(result.final_output)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;동기 실행이 필요하지 않다면 비동기 &lt;code&gt;Runner.run()&lt;/code&gt;이나 스트리밍 실행을 선택할 수 있다. 네트워크 요청과 도구 실행이 많은 실제 서비스에서는 비동기 방식이 자연스럽다.&lt;/p&gt;
&lt;h3&gt;2. Tool: 답변을 행동으로 바꾸는 연결점&lt;/h3&gt;
&lt;p&gt;에이전트가 사내 데이터 조회, 계산, 검색, 파일 처리 같은 일을 하려면 도구가 필요하다. Agents SDK는 파이썬 함수의 타입 정보를 읽어 도구 스키마를 만들고, Pydantic 기반 검증을 적용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from agents import Agent, Runner
from agents.decorators import tool


@tool
def get_order_status(order_id: str) -&amp;gt; str:
    &amp;quot;&amp;quot;&amp;quot;주문 번호로 현재 배송 상태를 조회한다.&amp;quot;&amp;quot;&amp;quot;
    return f&amp;quot;{order_id}: 배송 준비 중&amp;quot;


agent = Agent(
    name=&amp;quot;Order Support&amp;quot;,
    instructions=&amp;quot;주문 문의를 처리하세요. 필요한 경우 주문 조회 도구를 사용하세요.&amp;quot;,
    tools=[get_order_status],
)

result = Runner.run_sync(agent, &amp;quot;ORDER-1024의 상태를 확인해 줘.&amp;quot;)
print(result.final_output)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;도구는 일반 함수에만 머물지 않는다. AI 애플리케이션과 외부 도구를 표준 방식으로 연결하는 Model Context Protocol(MCP) 서버, OpenAI 호스팅 도구, 다른 에이전트, 그리고 실험적인 로컬 Codex CLI까지 연결할 수 있다.&lt;/p&gt;
&lt;h3&gt;3. Handoff: 적합한 전문가에게 실행권 넘기기&lt;/h3&gt;
&lt;p&gt;멀티 에이전트 구성에서 중요한 것은 에이전트의 수가 아니라 &lt;strong&gt;책임의 경계&lt;/strong&gt;다.&lt;/p&gt;
&lt;p&gt;결제 문의와 기술 장애를 하나의 프롬프트에 모두 넣을 수도 있다. 하지만 업무별 정책, 도구, 출력 형식이 달라진다면 전문 에이전트를 나누는 편이 관리하기 쉽다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from agents import Agent, Runner

billing_agent = Agent(
    name=&amp;quot;Billing Specialist&amp;quot;,
    handoff_description=&amp;quot;결제, 청구서, 환불 문의를 담당합니다.&amp;quot;,
    instructions=&amp;quot;결제 정책에 따라 문의를 처리하세요.&amp;quot;,
)

technical_agent = Agent(
    name=&amp;quot;Technical Specialist&amp;quot;,
    handoff_description=&amp;quot;로그인, 오류, 성능 문제를 담당합니다.&amp;quot;,
    instructions=&amp;quot;증상을 확인하고 재현 가능한 진단 절차를 안내하세요.&amp;quot;,
)

triage_agent = Agent(
    name=&amp;quot;Triage&amp;quot;,
    instructions=&amp;quot;문의 내용을 분류해 적합한 전문가에게 넘기세요.&amp;quot;,
    handoffs=[billing_agent, technical_agent],
)

result = Runner.run_sync(
    triage_agent,
    &amp;quot;결제는 됐는데 구독 기능이 활성화되지 않았어요.&amp;quot;,
)

print(result.final_output)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Handoff가 일어나면 다음 에이전트가 실행 흐름을 이어받는다. 반대로 총괄 에이전트가 하위 에이전트의 결과를 받아 직접 최종 답변을 작성해야 한다면, 전문 에이전트를 ‘agent as tool’ 형태로 등록할 수 있다.&lt;/p&gt;
&lt;h3&gt;4. Guardrail: 모델 앞뒤에 세우는 검증선&lt;/h3&gt;
&lt;p&gt;가드레일은 모델이 스스로 지켜 주기를 기대하는 프롬프트 문장이 아니다. 애플리케이션 실행 경로에 포함되는 검사다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음 조건을 코드로 분리할 수 있다.&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;li&gt;도구에 전달할 인자가 정책을 위반하지 않는가&lt;/li&gt;
&lt;li&gt;특정 조건에서는 사람의 승인이 필요한가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;검증에 실패하면 실행을 중단하거나 별도 처리 경로로 보낼 수 있다. 생성형 모델의 불확실성을 없애지는 못하지만, 실패를 발견하고 통제할 위치를 만들어 준다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;실제 서비스에 필요한 주변 기능&lt;/h2&gt;
&lt;p&gt;Agents SDK의 강점은 멀티 에이전트 데모를 만드는 데서 끝나지 않는다. 운영 환경에 필요한 기능도 실행 런타임 주변에 모여 있다.&lt;/p&gt;
&lt;h3&gt;세션과 대화 상태&lt;/h3&gt;
&lt;p&gt;여러 실행 사이에서 대화 이력을 유지할 수 있다. 저장 방식도 하나로 고정되지 않는다. 기본 SQLite 구성뿐 아니라 SQLAlchemy, Redis, 암호화 세션과 사용자 정의 저장소를 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;세션은 ‘AI의 기억’이라는 추상적인 표현보다 &lt;strong&gt;다음 실행에 어떤 대화 상태를 다시 공급할 것인가&lt;/strong&gt;에 가깝다. 서비스에서는 사용자별 격리, 보존 기간, 암호화와 삭제 정책을 함께 설계해야 한다.&lt;/p&gt;
&lt;h3&gt;Human-in-the-loop&lt;/h3&gt;
&lt;p&gt;환불, 배포, 파일 삭제, 외부 메시지 전송처럼 되돌리기 어려운 행동은 사람의 승인을 기다리게 만들 수 있다. 실행을 중단하고 상태를 저장한 뒤, 승인 또는 거절 결과를 받아 이어서 실행하는 방식이다.&lt;/p&gt;
&lt;p&gt;이 기능의 목적은 AI가 모든 것을 자동 처리하게 만드는 데 있지 않다. &lt;strong&gt;자동화할 부분과 사람이 책임질 부분을 분리하는 장치&lt;/strong&gt;에 가깝다.&lt;/p&gt;
&lt;h3&gt;Tracing&lt;/h3&gt;
&lt;p&gt;에이전트가 어떤 판단을 거쳐 어느 도구를 호출했고, 언제 다른 에이전트로 넘어갔는지 추적할 수 있다. 모델 호출 시간, 도구 실행, 오류와 토큰 사용량을 살펴보며 병목과 실패 지점을 찾는 데 유용하다.&lt;/p&gt;
&lt;p&gt;멀티 에이전트 시스템은 최종 답변만 봐서는 문제의 원인을 찾기 어렵다. 추적 정보가 있다면 잘못된 분류인지, 도구 오류인지, handoff 이후의 문맥 손실인지 구분할 수 있다.&lt;/p&gt;
&lt;h3&gt;Sandbox Agent&lt;/h3&gt;
&lt;p&gt;코드 저장소를 읽고 명령을 실행하거나 파일을 수정하는 작업에는 실제 작업공간이 필요하다. &lt;code&gt;SandboxAgent&lt;/code&gt;는 로컬 Unix 환경, Docker 또는 호스팅 샌드박스와 연결해 격리된 작업을 수행한다.&lt;/p&gt;
&lt;p&gt;파일 접근 범위, 네트워크 사용, 명령 실행 권한은 명확히 제한해야 한다. 일반 텍스트 에이전트에 셸 권한을 무심코 붙이지 않고 작업공간과 권한을 별도 경계로 다루는 이유가 여기에 있다.&lt;/p&gt;
&lt;h3&gt;Realtime Agent와 Voice Pipeline&lt;/h3&gt;
&lt;p&gt;저지연 음성 대화에는 WebSocket 기반 &lt;code&gt;RealtimeAgent&lt;/code&gt;를 사용할 수 있다. 음성 인식, 일반 에이전트 실행, 음성 합성을 단계별로 조합하고 싶다면 &lt;code&gt;VoicePipeline&lt;/code&gt;을 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;두 기능은 비슷해 보이지만 구조가 다르다. Realtime Agent는 지속적인 실시간 세션에 가깝다. Voice Pipeline은 음성 인식(Speech-to-Text, STT) → 에이전트 → 음성 합성(Text-to-Speech, TTS) 처리 흐름에 가깝다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;OpenAI 모델만 사용할 수 있는 것은 아니다&lt;/h2&gt;
&lt;p&gt;프로젝트 README는 이 SDK를 provider-agnostic, 즉 특정 모델 제공자에만 묶이지 않는 구조로 설명한다. OpenAI Responses API(Application Programming Interface)와 Chat Completions API뿐 아니라 OpenAI 호환 엔드포인트와 외부 어댑터도 연결할 수 있다는 뜻이다.&lt;/p&gt;
&lt;p&gt;예를 들어 DeepSeek API를 기본 모델 클라이언트로 지정할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os

from openai import AsyncOpenAI
from agents import (
    Agent,
    Runner,
    set_default_openai_client,
    set_tracing_disabled,
)

client = AsyncOpenAI(
    api_key=os.environ[&amp;quot;DEEPSEEK_API_KEY&amp;quot;],
    base_url=&amp;quot;https://api.deepseek.com&amp;quot;,
)

set_default_openai_client(
    client,
    use_for_tracing=False,
)
set_tracing_disabled(True)

agent = Agent(
    name=&amp;quot;DeepSeek Assistant&amp;quot;,
    instructions=&amp;quot;정확하고 간결한 한국어로 답하세요.&amp;quot;,
    model=&amp;quot;deepseek-v4-flash&amp;quot;,
)

result = Runner.run_sync(agent, &amp;quot;에이전트와 챗봇의 차이를 설명해 줘.&amp;quot;)
print(result.final_output)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 연결 형태를 보여 주는 예제이며, 이 글을 작성한 환경에서는 실제 DeepSeek API 키를 사용한 호출까지 검증하지 않았다.&lt;/p&gt;
&lt;p&gt;2026년 8월 10일 기준으로 DeepSeek의 Responses API는 &lt;code&gt;deepseek-v4-flash&lt;/code&gt;를 지원하지만 일부 OpenAI 기능과 완전히 같지는 않다. &lt;code&gt;previous_response_id&lt;/code&gt;, 서버 측 conversation 저장, 파일·이미지 입력, MCP와 일부 호스팅 도구에는 제약이 있다. 모델 제공자를 바꿀 때는 ‘요청 형식이 비슷하다’는 사실과 ‘기능이 동일하다’는 판단을 구분해야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;설치하고 첫 실행까지&lt;/h2&gt;
&lt;p&gt;이 저장소의 현재 패키지는 Python 3.10 이상을 요구한다. 아래 절차는 macOS 또는 Linux의 표준 셸 환경을 기준으로 한다.&lt;/p&gt;
&lt;h3&gt;1. 가상환경과 패키지 준비&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install openai-agents&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;확인:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -c &amp;quot;import agents; print(agents.__version__)&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;버전 번호가 출력되면 패키지를 불러올 수 있는 상태다.&lt;/p&gt;
&lt;p&gt;실패한다면 현재 셸에서 &lt;code&gt;.venv&lt;/code&gt;가 활성화됐는지, &lt;code&gt;python --version&lt;/code&gt;이 3.10 이상인지 먼저 확인한다.&lt;/p&gt;
&lt;h3&gt;2. API 키 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OPENAI_API_KEY=&amp;quot;발급받은_API_키&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실제 키를 소스 코드나 Git 저장소에 저장하지 않는다. 서비스 환경에서는 운영체제의 비밀 저장소나 배포 플랫폼의 secret 기능을 사용한다.&lt;/p&gt;
&lt;h3&gt;3. 예제 실행&lt;/h3&gt;
&lt;p&gt;다음 내용을 &lt;code&gt;hello_agent.py&lt;/code&gt;로 저장한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from agents import Agent, Runner

agent = Agent(
    name=&amp;quot;Assistant&amp;quot;,
    instructions=&amp;quot;친절하고 정확한 한국어로 답하세요.&amp;quot;,
)

result = Runner.run_sync(agent, &amp;quot;오늘 해야 할 일을 세 단계로 정리해 줘.&amp;quot;)
print(result.final_output)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python hello_agent.py&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상이라면 모델이 생성한 답변이 터미널에 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Missing credentials&lt;/code&gt; 오류가 나오면 &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;가 현재 셸에 설정됐는지 확인한다. &lt;code&gt;401&lt;/code&gt; 오류가 나오면 키의 유효성과 API 프로젝트 권한을 확인한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 SDK가 잘 맞는 경우&lt;/h2&gt;
&lt;p&gt;다음 조건이라면 Agents SDK의 장점이 분명하다.&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;li&gt;대화 상태와 장기 실행을 관리해야 한다&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;반대로 한 번의 요청으로 짧은 텍스트를 생성하고 끝나는 기능이라면 Responses API나 Chat Completions API를 직접 호출하는 편이 단순할 수 있다. 에이전트 수가 많다고 좋은 구조가 되는 것도 아니다. 책임과 권한이 분리되지 않은 에이전트는 이름만 여러 개인 하나의 복잡한 프롬프트가 되기 쉽다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;AI 팀을 만든다는 것&lt;/h2&gt;
&lt;p&gt;멀티 에이전트 시스템을 소개할 때는 종종 조직도 같은 그림이 먼저 등장한다. 총괄 에이전트 아래에 조사, 개발, 검토 에이전트를 배치한 모습은 제법 그럴듯하다.&lt;/p&gt;
&lt;p&gt;그렇다면 에이전트를 많이 배치할수록 결과도 좋아질까? 실제 품질을 결정하는 것은 숫자가 아니다.&lt;/p&gt;
&lt;p&gt;누가 어떤 정보를 볼 수 있는지, 어떤 도구를 실행할 수 있는지부터 정해야 한다. 언제 다른 에이전트에게 넘길지, 실패하면 어디에서 중단할지, 마지막 결과를 누가 책임질지도 빠질 수 없다. OpenAI Agents SDK는 이런 결정을 파이썬 코드로 옮길 수 있게 해 준다.&lt;/p&gt;
&lt;p&gt;복잡한 프레임워크 없이 시작할 수 있다는 말이 설계까지 필요 없다는 뜻은 아니다. 오히려 작은 문법으로 책임과 경계를 또렷하게 드러낼 수 있다는 뜻에 가깝다.&lt;/p&gt;
&lt;p&gt;AI에게 일을 시키는 시대를 지나 AI 팀을 운영하려 한다면, 이 프로젝트는 꽤 현실적인 출발점이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/openai-agents-python&quot;&gt;OpenAI Agents SDK Python GitHub 저장소&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openai.github.io/openai-agents-python/&quot;&gt;OpenAI Agents SDK 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.org/project/openai-agents/&quot;&gt;OpenAI Agents SDK PyPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api-docs.deepseek.com/guides/responses_api/&quot;&gt;DeepSeek Responses API 호환성 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;검증 기준&lt;/h2&gt;
&lt;p&gt;이 글의 프로젝트 기능과 설치 조건은 2026년 8월 10일에 다음 로컬 소스를 기준으로 확인했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;저장소 브랜치: &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;확인 커밋: &lt;code&gt;54cc7d93&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;패키지 버전: &lt;code&gt;openai-agents 0.19.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;패키지 요구 조건: Python 3.10 이상&lt;/li&gt;
&lt;li&gt;로컬 확인 환경: macOS, Python 3.13.7, &lt;code&gt;uv&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저장소 설치와 Python import는 로컬에서 검증했다. 글에 포함한 OpenAI 및 DeepSeek API 호출 예제는 실제 자격 증명을 사용해 실행하지 않았으므로 API 응답 결과는 미검증이다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1802</guid>
      <comments>https://javaexpert.tistory.com/1802#entry1802comment</comments>
      <pubDate>Mon, 10 Aug 2026 10:30:17 +0900</pubDate>
    </item>
    <item>
      <title>메인은 GPT-5.6, 서브에이전트는 DeepSeek V4 Flash로 설정하기</title>
      <link>https://javaexpert.tistory.com/1801</link>
      <description>&lt;h1&gt;GPT-5.6은 감독하고, DeepSeek V4 Flash 서브에이전트가 작업하는 Codex 구성법&lt;/h1&gt;
&lt;p&gt;이 구성에서 실제 일은 &lt;code&gt;deepseek_v4_flash&lt;/code&gt; 자식이 합니다. 자식은 요청을 받아 파일을 조사하고, 필요한 코드를 바꾸고, 관련 검증까지 수행합니다. 메인 GPT-5.6은 같은 결과물을 다시 작성하지 않고 작업 범위·변경 내역·검증 근거를 확인한 뒤 승인하거나 보완 작업만 지시합니다.&lt;/p&gt;
&lt;p&gt;이 글은 Codex의 메인을 GPT-5.6으로 유지하면서, &lt;code&gt;deepseek_v4_flash&lt;/code&gt;라는 전용 자식 worker가 DeepSeek V4 Flash로 작업을 소유하게 만드는 방법을 다룹니다. 부모는 조율자와 최종 검토자이고, 자식은 실행자입니다.&lt;/p&gt;
&lt;p&gt;핵심은 API 키를 에이전트 파일마다 복사하지 않는 것입니다. 키는 provider에 한 번만 연결하고, 서브에이전트는 그 provider를 명시적으로 선택합니다. Codex는 커스텀 provider와 커스텀 에이전트 파일을 지원합니다. &lt;a href=&quot;https://developers.openai.com/codex/config-reference&quot;&gt;Codex 설정 레퍼런스&lt;/a&gt;, &lt;a href=&quot;https://developers.openai.com/codex/subagents&quot;&gt;서브에이전트 문서&lt;/a&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;이 글의 예시는 macOS/Linux 기준입니다. API 키를 문서, Git 저장소, 스크린샷, 채팅 로그에 남기지 마세요. 이미 노출된 키라면 먼저 폐기하고 새 키를 발급받는 편이 안전합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;먼저 확인할 것&lt;/h2&gt;
&lt;p&gt;이 구성에는 다음이 필요합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;최신 Codex&lt;/li&gt;
&lt;li&gt;ChatGPT 또는 OpenAI 인증으로 동작하는 GPT-5.6 메인 모델&lt;/li&gt;
&lt;li&gt;DeepSeek API 키와 사용 가능한 잔액/권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deepseek-v4-flash&lt;/code&gt; 모델 접근 권한&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DeepSeek의 현재 모델 ID와 기본 API URL은 공식 모델 문서에서 확인하는 편이 좋습니다. 모델 이름이나 API 형식은 바뀔 수 있으므로, 다른 블로그의 값을 그대로 복사하기보다 공식 문서를 우선합니다. &lt;a href=&quot;https://api-docs.deepseek.com/quick_start/pricing/&quot;&gt;DeepSeek Models &amp;amp; Pricing&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. 설정부터 백업한다&lt;/h2&gt;
&lt;p&gt;provider 설정은 사용자 전역 설정에 들어가므로, 먼저 원본을 따로 보관합니다. 아래 명령은 삭제하거나 덮어쓰지 않고 복사본만 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p ~/.codex/backups
backup_stamp=&amp;quot;$(date +%Y%m%d-%H%M%S)&amp;quot;
backup_dir=&amp;quot;$HOME/.codex/backups/before-deepseek-$backup_stamp&amp;quot;

mkdir -p &amp;quot;$backup_dir&amp;quot;
cp -p ~/.codex/config.toml &amp;quot;$backup_dir/config.toml&amp;quot;

if [ -d ~/.codex/agents ]; then
  ditto ~/.codex/agents &amp;quot;$backup_dir/agents&amp;quot;
fi

chmod -R go-rwx &amp;quot;$backup_dir&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;config.toml&lt;/code&gt;에 토큰이 직접 들어 있을 가능성이 있다면 백업도 민감 정보입니다. 클라우드 드라이브나 Git에 올리지 말고, 소유자만 읽을 수 있도록 둡니다.&lt;/p&gt;
&lt;h2&gt;2. API 키는 provider에 한 번만 연결한다&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;의 기존 메인 모델 설정은 유지합니다. 그 아래에 DeepSeek provider만 추가합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# 메인 모델은 기존 GPT-5.6/OpenAI 설정을 그대로 둡니다.
model = &amp;quot;gpt-5.6-sol&amp;quot;
model_provider = &amp;quot;openai&amp;quot;

[model_providers.deepseek]
name = &amp;quot;DeepSeek&amp;quot;
base_url = &amp;quot;https://api.deepseek.com&amp;quot;
wire_api = &amp;quot;responses&amp;quot;
env_key = &amp;quot;DEEPSEEK_API_KEY&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;env_key&lt;/code&gt;는 키 자체가 아니라 환경 변수의 이름입니다. Codex의 provider 설정은 환경 변수로 API 키를 받도록 지원하며, 직접 토큰을 TOML에 쓰는 방식보다 안전합니다. &lt;a href=&quot;https://developers.openai.com/codex/config-reference&quot;&gt;Codex provider 설정&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;macOS 데스크톱 앱이 키를 읽도록 하려면 현재 로그인 세션에 환경 변수를 설정한 뒤 Codex를 완전히 재시작합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;launchctl setenv DEEPSEEK_API_KEY &amp;quot;발급받은_키&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령은 현재 로그인 세션을 위한 설정입니다. 영구 보관이 필요하다면 운영체제 키체인이나 조직의 비밀 관리 방식을 사용하면 됩니다. 셸 시작 파일에 실제 키를 평문으로 적는 방법은 피하는 것이 좋습니다.&lt;/p&gt;
&lt;h2&gt;3. 자식에게 실행권한을 주고, 부모는 감독만 하게 한다&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;agents.default_subagent_model&lt;/code&gt;만 바꾸면 모델 이름만 기본값으로 바뀌고 provider까지 안전하게 고정되지 않습니다. 메인 OpenAI provider로 DeepSeek 모델을 호출하는 실수를 피하려면, provider를 함께 지정한 커스텀 에이전트를 만듭니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.codex/agents/deepseek-v4-flash.toml&lt;/code&gt; 파일을 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;name = &amp;quot;deepseek_v4_flash&amp;quot;
description = &amp;quot;Fast DeepSeek V4 Flash worker for bounded implementation, documentation, and verification tasks.&amp;quot;
model = &amp;quot;deepseek-v4-flash&amp;quot;
model_provider = &amp;quot;deepseek&amp;quot;
sandbox_mode = &amp;quot;workspace-write&amp;quot;

developer_instructions = &amp;quot;&amp;quot;&amp;quot;
Own the bounded task delegated by the parent from inspection through
implementation and focused verification. Modify only files needed for the task,
preserve unrelated user changes, and never access or change credentials. Return
a concise summary of changed files, verification evidence, and any limitation.
&amp;quot;&amp;quot;&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;workspace-write&lt;/code&gt;는 자식에게 현재 작업공간 안에서 구현할 권한을 줍니다. 자식은 요청을 받아 구현과 검증을 마칠 때까지 작업을 소유하고, 부모는 결과가 요구사항과 맞는지 확인합니다. 자격 증명·전역 설정·배포 권한은 자식의 범위에서 제외하는 편이 좋습니다.&lt;/p&gt;
&lt;p&gt;Codex는 &lt;code&gt;~/.codex/agents/&lt;/code&gt; 아래의 각 TOML 파일을 커스텀 에이전트로 로드하며, 그 파일의 &lt;code&gt;model&lt;/code&gt; 설정은 부모 설정보다 우선합니다. &lt;a href=&quot;https://developers.openai.com/codex/subagents&quot;&gt;커스텀 에이전트 설정 방식&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;4. 부모는 작업을 넘기고, 결과만 검토한다&lt;/h2&gt;
&lt;p&gt;Codex를 완전히 종료한 뒤 다시 엽니다. 부모에게는 다음처럼 역할을 분명히 요청합니다. 부모는 직접 파일을 고치지 않고, 자식이 작업한 결과와 검증 기록을 검토합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;deepseek_v4_flash 자식에게 이 작업을 전부 맡겨 주세요.
자식이 관련 파일을 조사하고 구현한 뒤, 필요한 테스트를 실행해 결과를 보고하게 하세요.
부모는 파일을 직접 수정하지 말고 변경 파일, 테스트 결과, 요구사항 충족 여부만 검토해 승인 또는 보완 사항을 알려 주세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자식에게 원래 사용자 요청 또는 그 요청을 정확히 요약한 작업 지시가 전달돼야 합니다. 부모가 결과물을 대신 쓸 필요는 없지만, 자식이 무엇을 완성해야 하는지 알 수 있어야 하기 때문입니다.&lt;/p&gt;
&lt;p&gt;부모가 확인할 항목은 세 가지면 충분합니다.&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;h2&gt;5. provider와 worker를 각각 검증한다&lt;/h2&gt;
&lt;p&gt;먼저 DeepSeek provider 자체를 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;codex exec --strict-config --sandbox workspace-write \
  --model deepseek-v4-flash \
  --config &amp;#39;model_provider=&amp;quot;deepseek&amp;quot;&amp;#39; \
  &amp;#39;Create a small Markdown file, read it back, and report the title and line count.&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;성공 기준은 자식이 파일을 만들고, 스스로 읽어 확인한 결과를 반환하는 것입니다. 이후 부모가 같은 파일을 읽어 제목·줄 수·변경 범위를 독립적으로 확인하면 작성과 감독이 분리됩니다.&lt;/p&gt;
&lt;p&gt;커스텀 에이전트를 통한 자동 위임은 Codex 버전과 실행 환경에 따라 작업 본문 전달이 실패할 수 있습니다. 실제 업무에 쓰기 전에 반드시 자식이 전달받은 작업을 자신의 말로 요약하고, 해당 파일을 실제로 수정·검증했는지 확인하세요. 작업 지시가 비어 있다면 provider 문제가 아니라 handoff 문제이므로, 성공한 것처럼 다음 단계로 넘어가면 안 됩니다.&lt;/p&gt;
&lt;h2&gt;6. 문제가 생기면 원본으로 되돌린다&lt;/h2&gt;
&lt;p&gt;테스트가 실패했거나 메인 모델 선택이 달라졌다면, Codex를 종료한 상태에서 백업본의 &lt;code&gt;config.toml&lt;/code&gt;과 &lt;code&gt;agents&lt;/code&gt; 폴더를 복원합니다. 복원 전 현재 파일도 별도로 복사해 두면 원인을 비교할 수 있습니다.&lt;/p&gt;
&lt;p&gt;복원 후에는 &lt;code&gt;codex doctor&lt;/code&gt;로 다음 두 가지를 확인합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config.toml parse ok&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;메인 모델이 의도한 GPT-5.6과 &lt;code&gt;openai&lt;/code&gt; provider로 표시되는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DeepSeek 모델을 못 찾는다는 메타데이터 경고가 나올 수 있습니다. 호출이 성공하더라도 복잡한 도구 사용이나 장기 작업에서 동작이 달라질 수 있으므로, 처음에는 작은 기능 하나처럼 검증 범위가 분명한 작업부터 맡기는 편이 안전합니다.&lt;/p&gt;
&lt;h2&gt;어떤 작업을 DeepSeek 서브에이전트에 맡길까&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DeepSeek 자식이 소유할 작업&lt;/th&gt;
&lt;th&gt;GPT-5.6 부모가 관리할 작업&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;부모가 자식의 결과를 다시 구현하면 분업의 이점이 사라집니다. 부모는 결과를 믿는 것이 아니라, 변경 파일과 검증 근거를 살펴 결과를 판정합니다. 이 역할 분리가 비용을 줄이면서도 품질을 통제하는 핵심입니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이 구성의 목표는 GPT-5.6을 구현자로 쓰는 것이 아닙니다. GPT-5.6은 맥락·우선순위·최종 책임을 유지하고, DeepSeek V4 Flash 자식은 실제 작업과 검증을 끝까지 수행합니다. provider에는 키를 한 번만 연결하고, 에이전트 파일에는 worker의 역할·모델·권한만 남기면 관리와 복구가 단순해집니다.&lt;/p&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1801</guid>
      <comments>https://javaexpert.tistory.com/1801#entry1801comment</comments>
      <pubDate>Thu, 6 Aug 2026 13:41:49 +0900</pubDate>
    </item>
    <item>
      <title>Cerebras는 어떻게 사내 지식 검색 시스템을 구축했을까?</title>
      <link>https://javaexpert.tistory.com/1800</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;PostgreSQL 하나로 구현한 엔터프라이즈 RAG 아키텍처 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 생성형 AI가 기업 업무에 빠르게 도입되면서, 사내 문서와 대화 기록을 AI가 검색하고 활용할 수 있도록 만드는 &lt;b&gt;엔터프라이즈 지식 검색 시스템&lt;/b&gt;이 주목받고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 실제 기업 환경에서 RAG 시스템을 운영하는 일은 생각보다 단순하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업의 지식은 한곳에 정리되어 있지 않기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업무 대화는 Slack에서 이루어진다.&lt;/li&gt;
&lt;li&gt;소스 코드는 GitHub에 저장된다.&lt;/li&gt;
&lt;li&gt;프로젝트 일정과 이슈는 Jira에서 관리된다.&lt;/li&gt;
&lt;li&gt;공식 문서는 Google Drive나 Notion에 존재한다.&lt;/li&gt;
&lt;li&gt;중요한 결정은 회의록이나 이메일에 남는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 데이터가 여러 서비스에 분산된 상황에서, AI가 사용자의 질문에 정확한 답을 제공하려면 단순한 벡터 검색 이상의 설계가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 AI 반도체 기업 &lt;b&gt;Cerebras&lt;/b&gt;가 공개한 사내 지식 검색 시스템인 &lt;b&gt;Cerebras Knowledge&lt;/b&gt;의 구조를 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 네 가지를 중심으로 분석한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전용 벡터 데이터베이스 없이 PostgreSQL을 선택한 이유&lt;/li&gt;
&lt;li&gt;Slack 데이터를 검색 가능한 지식으로 변환하는 방법&lt;/li&gt;
&lt;li&gt;키워드&amp;middot;벡터&amp;middot;희소성&amp;middot;신선도를 결합한 하이브리드 검색&lt;/li&gt;
&lt;li&gt;인간용 UI와 AI 에이전트용 MCP 도구를 분리한 설계&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1. Cerebras Knowledge란 무엇인가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras Knowledge는 Cerebras 내부의 여러 데이터 소스를 통합 검색하기 위해 구축된 사내 지식 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공된 기사에 따르면 이 시스템은 출시 후 약 3개월 만에 하루 15,000건 이상의 질문을 처리하는 수준으로 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문을 보내는 주체도 사람만이 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 직원&lt;/li&gt;
&lt;li&gt;개발자&lt;/li&gt;
&lt;li&gt;사내 자동화 시스템&lt;/li&gt;
&lt;li&gt;AI 에이전트&lt;/li&gt;
&lt;li&gt;코딩 에이전트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Cerebras Knowledge는 단순한 사내 챗봇이 아니라 인간과 AI가 함께 사용하는 &lt;b&gt;조직 지식 인프라&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 사내 RAG 시스템은 다음과 같은 흐름을 따른다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Slack &amp;middot; GitHub &amp;middot; Jira &amp;middot; 문서
        &amp;darr;
데이터 수집 및 청크 분할
        &amp;darr;
임베딩 생성
        &amp;darr;
벡터 데이터베이스 저장
        &amp;darr;
유사도 검색
        &amp;darr;
LLM 답변 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras의 구조도 큰 흐름은 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 다음 두 가지에서 차별점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, Pinecone이나 전용 검색 클러스터 대신 PostgreSQL을 중심으로 시스템을 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, Slack 메시지를 그대로 임베딩하지 않고, 검색에 적합한 형태로 정규화한 후 저장했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 왜 사내 지식 검색은 어려운가?&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1 지식은 작성하기 편한 장소에서 만들어진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 기업이 지식 관리 문제를 해결하기 위해 다음과 같은 목표를 세운다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 정보를 하나의 플랫폼에 모으자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 흔히 &lt;b&gt;Single Source of Truth&lt;/b&gt;, 즉 단일 진실 공급원이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론적으로는 합리적인 접근이다. 하지만 실제 조직에서는 제대로 실현되기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 정보를 관리하기 좋은 장소보다, 업무를 처리하기 편한 장소에 기록하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 개발팀은 Slack에서 기술적인 결정을 내리고, GitHub Pull Request에서 구현 방향을 토론하며, Jira 이슈에 진행 상태를 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 모두 특정 지식 관리 플랫폼으로 다시 옮기는 작업은 지속되기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 현실적인 사내 지식 검색 시스템은 모든 사용자의 작성 습관을 바꾸기보다, 기존 도구에 흩어진 정보를 수집하고 검색할 수 있어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2 Slack 메시지는 일반 문서와 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 데이터는 RAG에서 다루기 까다로운 대표적인 비정형 데이터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 문서는 하나의 문단이나 페이지에 비교적 완전한 의미가 담겨 있다. 반면 Slack 메시지는 대화의 전후 맥락에 의존한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 메시지가 있다고 가정해 보자.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;A: 이번 장애 원인이 뭐였나요?

B: 캐시 만료 정책 때문입니다.

C: 확인했습니다.

B: 정확히는 Redis TTL이 배포 과정에서 기본값으로 변경됐습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 메시지를 개별 청크로 저장하면 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 만료 정책 때문입니다라는 메시지는 질문과 분리될 경우 무엇을 설명하는지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인했습니다와 같은 짧은 메시지는 검색 가치가 거의 없지만, 임베딩 유사도에 따라 검색 결과 상단에 나타날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 가장 중요한 기술적 설명이 스레드 중간이나 마지막에 위치할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Slack 데이터를 메시지 단위로 단순 벡터화하면 다음과 같은 문제가 발생한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전후 맥락 손실&lt;/li&gt;
&lt;li&gt;짧은 메시지의 과도한 노출&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 data-ke-size=&quot;size16&quot;&gt;Cerebras는 이를 해결하기 위해 Slack 데이터를 검색 전에 가공하는 파이프라인을 구축했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. PostgreSQL 하나로 검색 시스템 구성하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras Knowledge에서 가장 눈에 띄는 선택은 전용 벡터 데이터베이스를 사용하지 않았다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공된 기사에 따르면 검색 인프라는 PostgreSQL을 중심으로 구성되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키워드 검색: PostgreSQL Full Text Search와 GIN 인덱스&lt;/li&gt;
&lt;li&gt;벡터 검색: pgvector&lt;/li&gt;
&lt;li&gt;벡터 인덱스: HNSW&lt;/li&gt;
&lt;li&gt;구조화 데이터 및 메타데이터: 일반 PostgreSQL 컬럼&lt;/li&gt;
&lt;li&gt;원문 데이터: 동일 데이터베이스에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 RAG 서비스에서는 다음과 같이 여러 저장소를 분리하는 경우가 많다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;PostgreSQL
- 사용자
- 권한
- 문서 메타데이터

Vector DB
- 임베딩
- 벡터 검색

Elasticsearch
- 키워드 검색
- 로그 검색
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Cerebras 방식은 이를 하나의 PostgreSQL 시스템으로 단순화한다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;PostgreSQL
- 원문
- 정규화된 문서
- 임베딩
- 키워드 인덱스
- 메타데이터
- 시간 정보
- 데이터 출처
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.1 PostgreSQL 중심 설계의 장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운영 복잡도 감소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 종류가 늘어나면 배포, 백업, 모니터링, 장애 대응 체계도 복잡해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL 하나를 사용하면 다음 항목을 일관되게 관리할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백업&lt;/li&gt;
&lt;li&gt;복구&lt;/li&gt;
&lt;li&gt;마이그레이션&lt;/li&gt;
&lt;li&gt;트랜잭션&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;h3 data-ke-size=&quot;size23&quot;&gt;데이터 동기화가 단순하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 원문은 PostgreSQL에 있고 임베딩은 별도 벡터 DB에 저장되어 있다면, 문서 수정 시 두 시스템을 모두 갱신해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 다음과 같은 오류가 발생할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원문은 최신인데 벡터는 이전 버전인 상태&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 data-ke-size=&quot;size16&quot;&gt;동일 데이터베이스에 원문과 임베딩을 저장하면 이러한 동기화 문제를 줄일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자의 학습 비용이 낮다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 대부분의 백엔드 엔지니어가 사용 경험을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 벡터 데이터베이스의 쿼리 문법, 운영 방식, 백업 정책, 클러스터 구조를 추가로 학습하지 않아도 된다는 장점이 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.2 PostgreSQL만으로 충분한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 RAG 시스템에 PostgreSQL이 적합한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황에서는 전용 검색 시스템이 더 적절할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수억 개 이상의 임베딩을 검색해야 하는 경우&lt;/li&gt;
&lt;li&gt;초당 검색 요청이 매우 높은 경우&lt;/li&gt;
&lt;li&gt;복잡한 언어별 형태소 분석이 필요한 경우&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 data-ke-size=&quot;size16&quot;&gt;그러나 사내 지식 검색처럼 데이터 규모가 통제 가능하고, 메타데이터 필터링과 정합성이 중요한 환경에서는 PostgreSQL이 실용적인 선택이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &amp;ldquo;전용 벡터 DB가 항상 필요하다&amp;rdquo;가 아니라, 시스템 규모와 운영 조건에 따라 저장소를 선택해야 한다는 점이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Slack 스레드를 검색 가능한 문서로 변환하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras는 Slack 메시지를 그대로 임베딩하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 스레드 전체를 LLM에 입력하고, 검색에 적합한 구조화 문서로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같은 처리 과정이다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Slack 원본 스레드
        &amp;darr;
대화 분석
        &amp;darr;
핵심 주제 추출
        &amp;darr;
질문과 답변 관계 복원
        &amp;darr;
기술적 결론 요약
        &amp;darr;
예상 검색 질문 생성
        &amp;darr;
정규화 문서 저장
        &amp;darr;
임베딩 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 원본 Slack 스레드가 다음과 같다고 하자.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;A: 배포 이후 API 응답이 느려진 이유가 뭔가요?

B: DB 문제는 아닌 것 같습니다.

C: 로그를 보니 캐시 적중률이 20%대로 내려갔습니다.

B: 배포 스크립트에서 Redis TTL 환경 변수가 누락됐네요.

A: 기본값이 적용된 건가요?

B: 네. 수정 후 캐시 적중률이 정상화됐습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 검색용 문서로 변환하면 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;제목:
배포 이후 API 응답 지연과 Redis TTL 환경 변수 누락

요약:
배포 스크립트에서 Redis TTL 환경 변수가 누락되면서
기본 만료 시간이 적용되었고, 캐시 적중률이 약 20%대로 감소했다.
환경 변수를 복원한 후 캐시 적중률과 API 응답 속도가 정상화되었다.

예상 검색 질문:
- 배포 이후 API 응답이 느려진 이유는 무엇인가?
- Redis 캐시 적중률이 감소한 원인은 무엇인가?
- TTL 환경 변수 누락으로 발생한 장애 사례가 있는가?

관련 키워드:
Redis, TTL, 캐시 적중률, 배포 스크립트, API 지연
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 변환하면 사용자가 원문의 정확한 표현을 기억하지 못하더라도 관련 내용을 찾기 쉬워진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.1 예상 질문을 생성하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩 검색은 질문과 문서가 의미적으로 유사할수록 잘 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 원본 문서는 사실을 설명하는 서술형 문장이고, 사용자는 의문형으로 검색하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문서에는 다음과 같이 기록되어 있을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Redis TTL 환경 변수가 누락되어 기본값이 적용됐다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 다음과 같이 질문할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;지난번 배포 후 API가 느려진 이유가 뭐였지?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문장은 동일한 사건을 가리키지만 사용하는 단어와 문장 구조가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색용 예상 질문을 미리 생성하면 사용자 질문과 임베딩 공간에서 가까운 표현을 확보할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 흔히 다음과 같은 이름으로 불린다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Question Generation&lt;/li&gt;
&lt;li&gt;Hypothetical Question Indexing&lt;/li&gt;
&lt;li&gt;Query-oriented Chunk Enrichment&lt;/li&gt;
&lt;li&gt;Document Expansion&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.2 원문도 별도로 보존한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화된 요약만 저장하면 LLM이 생략한 세부 정보가 손실될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 원본 Slack 텍스트도 함께 저장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권장 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;knowledge_chunks
├── source_type
├── source_id
├── thread_id
├── raw_content
├── normalized_content
├── summary
├── hypothetical_questions
├── keywords
├── embedding
├── author_ids
├── channel_id
├── created_at
├── updated_at
└── metadata
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 단계에서는 정규화된 문서와 예상 질문을 벡터 검색에 활용하고, 원문은 키워드 검색과 최종 근거 제공에 활용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 긴 Slack 스레드를 처리하는 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 전체를 하나의 청크로 만드는 방법도 완전하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 지나치게 길면 중간에 있는 중요한 정보가 임베딩에서 희석될 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 스레드에 다음 내용이 모두 포함되어 있을 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장애 발생 보고&lt;/li&gt;
&lt;li&gt;원인 분석&lt;/li&gt;
&lt;li&gt;임시 대응&lt;/li&gt;
&lt;li&gt;코드 수정&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 data-ke-size=&quot;size16&quot;&gt;이 스레드를 하나의 벡터로 표현하면 개별 주제의 의미가 약해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras는 긴 스레드에서 특정 조건을 충족하는 연속 발언을 별도 청크로 분리하는 방식을 사용한 것으로 소개된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 한 사용자가 연속해서 작성한 장문의 기술 설명은 독립적인 지식 단위일 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 분할 기준은 다음과 같이 설계할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일 작성자의 연속 메시지&lt;/li&gt;
&lt;li&gt;일정 글자 수 이상의 메시지 묶음&lt;/li&gt;
&lt;li&gt;코드 블록을 포함한 설명&lt;/li&gt;
&lt;li&gt;URL이나 문서 링크를 포함한 설명&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 data-ke-size=&quot;size16&quot;&gt;이를 기반으로 스레드에서 여러 종류의 청크를 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;Slack Thread
├── 스레드 전체 요약 청크
├── 원인 분석 청크
├── 기술 설명 청크
├── 해결 방법 청크
└── 최종 결정 청크
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 한 가지 청킹 방식만 사용하는 것보다 검색 recall을 높이는 데 유리하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. 검색할 때 네 가지 신호를 함께 사용한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras Knowledge는 사용자의 질문이 들어왔을 때 하나의 검색 방식만 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 검색 신호를 병렬로 계산한 후 결과를 결합한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기사에서 소개된 주요 신호는 다음 네 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;키워드 검색&lt;/li&gt;
&lt;li&gt;벡터 검색&lt;/li&gt;
&lt;li&gt;IDF 기반 희귀 단어 가중치&lt;/li&gt;
&lt;li&gt;정보 신선도&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.1 키워드 검색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키워드 검색은 사용자가 입력한 단어가 문서에 직접 포함되어 있는지를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 검색에 강하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정확한 클래스 이름&lt;/li&gt;
&lt;li&gt;오류 코드&lt;/li&gt;
&lt;li&gt;티켓 번호&lt;/li&gt;
&lt;li&gt;프로젝트 이름&lt;/li&gt;
&lt;li&gt;사람 이름&lt;/li&gt;
&lt;li&gt;API 엔드포인트&lt;/li&gt;
&lt;li&gt;라이브러리 이름&lt;/li&gt;
&lt;li&gt;커밋 해시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ERR_CONNECTION_RESET
JIRA-1842
payment-webhook
RedisClusterManager
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 값은 의미 기반 임베딩보다 정확한 문자열 매칭이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL에서는 tsvector, tsquery, GIN 인덱스를 사용해 전체 텍스트 검색을 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 SQL은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT
    id,
    normalized_content,
    ts_rank(search_vector, query) AS keyword_score
FROM knowledge_chunks,
     plainto_tsquery('simple', :query) AS query
WHERE search_vector @@ query
ORDER BY keyword_score DESC
LIMIT 50;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.2 벡터 검색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색은 표현이 다르더라도 의미가 비슷한 문서를 찾는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 다음과 같이 질문한다고 하자.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;서비스 배포 후 갑자기 느려졌던 사건
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에는 다음 표현만 존재할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;릴리스 이후 캐시 적중률 저하로 API latency가 증가했다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 일치하는 단어는 적지만 의미는 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pgvector를 사용하면 다음과 같은 형태로 검색할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT
    id,
    normalized_content,
    1 - (embedding &amp;lt;=&amp;gt; :query_embedding) AS vector_score
FROM knowledge_chunks
ORDER BY embedding &amp;lt;=&amp;gt; :query_embedding
LIMIT 50;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.3 IDF 기반 희귀 단어 가중치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDF는 &lt;b&gt;Inverse Document Frequency&lt;/b&gt;, 즉 역문서 빈도다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 문서에 반복해서 등장하는 단어는 검색 가치가 낮고, 일부 문서에만 등장하는 희귀 단어는 검색 가치가 높다고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Slack에는 다음과 같은 표현이 매우 자주 나타난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확인했습니다&lt;/li&gt;
&lt;li&gt;감사합니다&lt;/li&gt;
&lt;li&gt;알겠습니다&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 data-ke-size=&quot;size16&quot;&gt;이런 단어가 포함된 메시지는 검색 결과에서 높은 우선순위를 받을 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 다음 표현은 특정 문제와 직접 연결될 가능성이 높다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NCCL timeout&lt;/li&gt;
&lt;li&gt;HNSW index&lt;/li&gt;
&lt;li&gt;memory fragmentation&lt;/li&gt;
&lt;li&gt;TTL override&lt;/li&gt;
&lt;li&gt;compiler regression&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDF 신호를 사용하면 검색 가치가 낮은 상투적 표현을 억제하고, 기술적으로 구체적인 문장을 우대할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.4 정보 신선도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 지식은 시간이 지나면서 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월 전의 배포 절차보다 지난주에 개정된 배포 절차가 더 정확할 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 검색 결과에 시간 가중치를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 감쇠 함수를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;freshness_score = exp(-&amp;lambda; &amp;times; document_age)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 document_age는 문서 생성 또는 갱신 후 경과 시간이며, &amp;lambda;는 오래된 정보의 점수를 얼마나 빠르게 낮출지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 최신 문서가 항상 정답인 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래된 장애 회고, 기술적 결정 기록, 설계 배경은 시간이 지나도 가치가 유지될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 데이터 유형에 따라 신선도 가중치를 다르게 적용하는 것이 안전하다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;운영 절차       &amp;rarr; 신선도 가중치 높음
현재 담당자 정보 &amp;rarr; 신선도 가중치 높음
장애 회고       &amp;rarr; 신선도 가중치 중간
아키텍처 결정   &amp;rarr; 신선도 가중치 중간
역사적 기록     &amp;rarr; 신선도 가중치 낮음
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. RRF로 검색 결과를 통합한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키워드 검색과 벡터 검색은 각각 다른 점수 체계를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 키워드 검색 점수는 0.72, 벡터 유사도는 0.84, 신선도 점수는 0.56처럼 계산될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점수들을 단순히 더하면 각 점수의 범위와 분포가 달라 결과가 왜곡될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras는 여러 랭킹을 결합하기 위해 &lt;b&gt;RRF, Reciprocal Rank Fusion&lt;/b&gt;을 사용하는 것으로 소개된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RRF는 검색 결과의 절대 점수보다 각 검색 방식에서 몇 위에 올랐는지를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 식은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;RRF Score(d) = &amp;Sigma; 1 / (k + rankᵢ(d))
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;d: 문서&lt;/li&gt;
&lt;li&gt;rankᵢ(d): i번째 검색 방식에서 해당 문서의 순위&lt;/li&gt;
&lt;li&gt;k: 상위 결과의 점수 차이를 완화하는 상수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 문서가 다음 순위를 기록했다고 하자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;키워드 검색: 2위
벡터 검색: 5위
신선도 랭킹: 3위
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k = 60이면 다음과 같이 계산할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;RRF = 1 / 62 + 1 / 65 + 1 / 63
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RRF의 장점은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 점수 정규화가 필요하지 않다.&lt;/li&gt;
&lt;li&gt;서로 다른 검색 엔진의 결과를 쉽게 결합할 수 있다.&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 소형 리랭커로 최종 문서를 선별한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RRF로 검색 결과를 합친 뒤에도 상위 문서가 모두 질문에 적합한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 상위 후보에 대해 리랭커를 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 검색 파이프라인은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;사용자 질문
    &amp;darr;
키워드 검색 Top 50
벡터 검색 Top 50
IDF 랭킹 Top 50
신선도 랭킹 Top 50
    &amp;darr;
RRF 통합
    &amp;darr;
상위 30~50개 후보
    &amp;darr;
소형 리랭커
    &amp;darr;
최종 Top 10
    &amp;darr;
LLM 컨텍스트 구성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리랭커는 질문과 각 문서를 함께 입력받아 관련성을 직접 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색이 질문과 문서를 각각 독립적으로 임베딩하는 bi-encoder 방식이라면, 리랭커는 질문과 문서를 동시에 비교하는 cross-encoder 방식으로 구현되는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리랭커는 일반적으로 벡터 검색보다 정확하지만 계산 비용이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 전체 문서를 리랭킹하지 않고, 1차 검색으로 좁힌 후보에만 적용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. 검색된 청크에 주변 문맥을 다시 붙인다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색용 청크는 정확한 검색을 위해 작게 분할해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 LLM에 제공하는 컨텍스트는 충분한 문맥을 포함해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 검색에 사용한 청크와 생성 모델에 제공하는 청크를 동일하게 구성할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 검색 결과가 Slack 스레드 중간의 기술 설명이라면, 최종적으로 LLM에 전달할 때는 다음 내용을 추가할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 질문&lt;/li&gt;
&lt;li&gt;검색된 핵심 메시지&lt;/li&gt;
&lt;li&gt;바로 앞 메시지&lt;/li&gt;
&lt;li&gt;바로 뒤 메시지&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 data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;context expansion&lt;/b&gt;, &lt;b&gt;neighbor expansion&lt;/b&gt;, &lt;b&gt;parent-child retrieval&lt;/b&gt; 등의 방식으로 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;작은 청크로 검색
        &amp;darr;
관련 청크 선택
        &amp;darr;
부모 문서 또는 주변 청크 로드
        &amp;darr;
확장된 컨텍스트를 LLM에 전달
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 검색 정확도와 답변 완성도를 동시에 확보하는 데 유리하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. MCP에는 완성형 답변이 아니라 검색 도구를 공개한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras Knowledge는 인간이 사용하는 웹 인터페이스뿐 아니라 AI 에이전트가 호출할 수 있는 MCP 인터페이스도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 설계 원칙은 AI 에이전트에게 다음과 같은 단일 도구를 제공하지 않는다는 점이다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;answer_company_question(question)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 보다 작은 검색 도구를 제공한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;search_slack(query)
search_code(query)
search_documents(query)
search_jira(query)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 도구를 흔히 &lt;b&gt;primitive tool&lt;/b&gt;, 즉 기본 단위 도구라고 부를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 사용자의 요청을 분석하고 필요한 도구를 스스로 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 질문이 들어왔다고 하자.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;결제 웹훅 중복 처리 문제가 왜 발생했고 현재 코드는 어떻게 수정됐나요?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트는 다음과 같은 순서로 동작할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;1. search_slack
   - 장애 당시 논의와 원인 검색

2. search_jira
   - 관련 이슈와 후속 작업 검색

3. search_code
   - 현재 구현 코드와 커밋 검색

4. 결과 비교
   - 과거 원인과 현재 수정 상태를 연결

5. 최종 답변 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 시스템이 모든 질문을 한 번에 해결하려 하기보다, 에이전트가 조합할 수 있는 작은 기능을 제공하는 구조다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10.1 인간용 인터페이스와 AI용 인터페이스의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인간 사용자는 일반적으로 다음과 같은 경험을 원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문 입력&lt;/li&gt;
&lt;li&gt;답변 확인&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 data-ke-size=&quot;size16&quot;&gt;반면 AI 에이전트는 다음을 필요로 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조화된 입력과 출력&lt;/li&gt;
&lt;li&gt;데이터 출처 구분&lt;/li&gt;
&lt;li&gt;검색 범위 지정&lt;/li&gt;
&lt;li&gt;페이지네이션&lt;/li&gt;
&lt;li&gt;메타데이터 필터&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 data-ke-size=&quot;size16&quot;&gt;따라서 인간용 챗봇 API와 AI 에이전트용 검색 API를 분리하는 것은 합리적인 접근이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. 전체 아키텍처 예시&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras 사례를 참고해 사내 지식 검색 시스템을 구성하면 다음과 같은 구조를 설계할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;┌─────────────────────────────────────┐
│            데이터 소스              │
│ Slack &amp;middot; GitHub &amp;middot; Jira &amp;middot; Drive       │
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│          수집 및 동기화 계층         │
│ Webhook &amp;middot; Polling &amp;middot; Incremental Sync│
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│          전처리 파이프라인           │
│ 정규화 &amp;middot; 중복 제거 &amp;middot; 권한 추출      │
│ 스레드 요약 &amp;middot; 질문 생성 &amp;middot; 청크 분할 │
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│             PostgreSQL              │
│ 원문 &amp;middot; 요약 &amp;middot; 메타데이터 &amp;middot; 임베딩   │
│ Full Text Search &amp;middot; pgvector &amp;middot; HNSW │
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│             검색 계층               │
│ Keyword &amp;middot; Vector &amp;middot; IDF &amp;middot; Freshness │
│ RRF &amp;middot; Reranker &amp;middot; Context Expansion │
└──────────────────┬──────────────────┘
                   │
          ┌────────┴────────┐
          ▼                 ▼
┌─────────────────┐ ┌─────────────────┐
│   인간용 Web UI  │ │ AI Agent &amp;middot; MCP  │
│ 검색 &amp;middot; 채팅      │ │ search_slack    │
│ 출처 &amp;middot; 피드백    │ │ search_code     │
└─────────────────┘ └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 실제 구현 시 가장 어려운 문제: 권한 관리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기사에서도 한계로 지적된 부분이 권한 관리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 데이터는 모든 직원에게 동일하게 공개되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 접근 제한이 존재할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비공개 Slack 채널&lt;/li&gt;
&lt;li&gt;경영진 전용 문서&lt;/li&gt;
&lt;li&gt;인사 평가 자료&lt;/li&gt;
&lt;li&gt;법무 관련 문서&lt;/li&gt;
&lt;li&gt;특정 프로젝트 참여자 전용 GitHub 저장소&lt;/li&gt;
&lt;li&gt;고객 계약 자료&lt;/li&gt;
&lt;li&gt;보안 사고 보고서&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 서비스의 검색 API를 직접 사용하는 경우에는 해당 서비스의 권한 체계를 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자의 Slack 토큰으로 Slack 검색 API를 호출하면, 사용자가 접근 가능한 채널만 검색된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터를 별도 PostgreSQL에 복제하면 권한 모델도 함께 복제해야 한다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;원본 데이터 복제
      +
권한 정보 복제
      +
사용자 그룹 동기화
      +
검색 시 권한 필터링
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 하나라도 누락되면 민감한 정보가 노출될 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.1 권한 필터링 기본 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 지식 청크에 접근 제어 정보를 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;knowledge_chunks
├── allowed_user_ids
├── allowed_group_ids
├── source_channel_id
├── source_repository_id
├── visibility
└── classification_level
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색할 때는 벡터 유사도 계산만 수행하는 것이 아니라, 현재 사용자의 권한 조건을 함께 적용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT
    id,
    normalized_content,
    1 - (embedding &amp;lt;=&amp;gt; :query_embedding) AS score
FROM knowledge_chunks
WHERE
    visibility = 'public'
    OR :user_id = ANY(allowed_user_ids)
    OR allowed_group_ids &amp;amp;&amp;amp; :user_group_ids
ORDER BY embedding &amp;lt;=&amp;gt; :query_embedding
LIMIT 20;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 검색 후 결과를 제거하는 것이 아니라, 검색 단계부터 권한 조건을 적용해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 후 필터링 방식은 다음과 같은 문제를 일으킬 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권한 없는 문서가 상위 결과를 차지함&lt;/li&gt;
&lt;li&gt;권한 필터링 후 결과가 부족함&lt;/li&gt;
&lt;li&gt;검색 점수나 로그를 통해 정보가 간접 노출됨&lt;/li&gt;
&lt;li&gt;리랭커나 LLM에 민감한 문서가 전달될 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12.2 삭제와 권한 변경도 동기화해야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 수집보다 더 어려운 것은 삭제와 권한 변경이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 상황을 처리해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개 채널이 비공개 채널로 변경됨&lt;/li&gt;
&lt;li&gt;사용자가 프로젝트에서 제외됨&lt;/li&gt;
&lt;li&gt;GitHub 저장소 접근 권한이 제거됨&lt;/li&gt;
&lt;li&gt;문서가 삭제됨&lt;/li&gt;
&lt;li&gt;Slack 메시지가 수정됨&lt;/li&gt;
&lt;li&gt;민감 정보가 포함되어 원문이 삭제됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 시스템에 이전 데이터가 남아 있다면 원본에서 권한이 제거되더라도 계속 검색될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동기화 시스템은 생성 이벤트뿐 아니라 다음 이벤트를 추적해야 한다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Create
Update
Delete
Permission Change
Membership Change
Visibility Change
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. 하나의 거대한 지식 기반은 계속 유지될 수 있을까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 모든 데이터를 하나의 지식 기반에 저장하는 방식이 단순하고 효율적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 데이터와 사용 사례가 증가하면 하나의 검색 랭킹으로 모든 질문을 처리하기 어려워질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 질문은 서로 다른 검색 전략이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;코드 오류 원인을 찾아줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;지난 분기 영업 전략을 알려줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;휴가 신청 절차가 어떻게 되나요?
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;고객 A와 체결한 계약 조건을 찾아줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 검색은 함수명, 경로, 심볼, 커밋 관계가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인사 규정 검색은 최신 공식 문서와 정확한 조항이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영업 자료 검색은 고객, 산업, 시점, 담당 조직이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 시스템이 성장하면 도메인별 검색 구성이 필요할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Engineering Knowledge
- GitHub
- Jira
- 기술 Slack
- 설계 문서

Sales Knowledge
- CRM
- 제안서
- 고객 미팅 기록
- 영업 Slack

HR Knowledge
- 사내 규정
- 복지 문서
- 인사 공지

Legal Knowledge
- 계약서
- 정책
- 컴플라이언스 문서
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 상위 에이전트가 질문을 분류한 후 적절한 검색 도구를 선택하도록 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;사용자 질문
      &amp;darr;
도메인 라우터
      &amp;darr;
Engineering / Sales / HR / Legal
      &amp;darr;
도메인별 검색
      &amp;darr;
통합 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 도메인을 분리할수록 권한 관리와 운영 복잡도는 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 처음부터 과도하게 분리하기보다, 검색 품질과 데이터 규모를 관찰하면서 단계적으로 분리하는 것이 현실적이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. 실무에 적용할 수 있는 핵심 교훈&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.1 벡터 데이터베이스부터 선택하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 시스템을 구축할 때 기술 스택부터 결정하는 경우가 많다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;Pinecone을 사용할까?
Weaviate를 사용할까?
Milvus를 사용할까?
Elasticsearch를 사용할까?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 검색 품질을 결정하는 핵심은 데이터베이스 제품보다 다음 요소일 가능성이 높다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터의 품질&lt;/li&gt;
&lt;li&gt;청크 분할 방식&lt;/li&gt;
&lt;li&gt;메타데이터 설계&lt;/li&gt;
&lt;li&gt;질문과 문서의 표현 차이&lt;/li&gt;
&lt;li&gt;권한 필터링&lt;/li&gt;
&lt;li&gt;최신 정보 반영&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 data-ke-size=&quot;size16&quot;&gt;소규모 또는 중간 규모의 사내 지식 시스템이라면 PostgreSQL과 pgvector로 먼저 검증하는 접근도 가능하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.2 채팅 데이터는 반드시 대화 단위로 처리한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack이나 Teams 메시지를 개별 메시지 단위로 임베딩하는 방식은 피하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소한 다음 처리가 필요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 단위 병합&lt;/li&gt;
&lt;li&gt;질문과 답변 관계 복원&lt;/li&gt;
&lt;li&gt;짧은 응답 제거&lt;/li&gt;
&lt;li&gt;장문 설명 별도 추출&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.3 검색은 하나의 방식으로 해결하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색만으로는 다음 항목을 정확히 찾기 어렵다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오류 코드&lt;/li&gt;
&lt;li&gt;프로젝트명&lt;/li&gt;
&lt;li&gt;함수명&lt;/li&gt;
&lt;li&gt;티켓 번호&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 data-ke-size=&quot;size16&quot;&gt;반대로 키워드 검색만으로는 표현이 다른 질문을 찾기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실무에서는 하이브리드 검색이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;Keyword
+ Vector
+ Metadata
+ Freshness
+ RRF
+ Reranker
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.4 검색용 청크와 답변용 컨텍스트를 분리한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색에는 작은 청크가 유리하지만, 답변에는 충분한 문맥이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 구조가 효과적이다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;작은 청크로 검색
&amp;rarr; 부모 문서 확인
&amp;rarr; 주변 문맥 확장
&amp;rarr; 최종 컨텍스트 구성
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.5 AI 에이전트에는 작고 명확한 도구를 제공한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 도구는 하나의 거대한 답변 API보다 기능별 검색 도구로 제공하는 것이 활용성이 높다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;search_slack
search_code
search_jira
fetch_document
get_thread
get_commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 목적에 맞게 도구를 조합할 수 있기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14.6 권한 관리는 나중에 추가할 기능이 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업용 RAG에서 권한 관리는 부가 기능이 아니라 핵심 아키텍처다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 모델을 설계할 때부터 다음 항목이 포함되어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 ID&lt;/li&gt;
&lt;li&gt;그룹 ID&lt;/li&gt;
&lt;li&gt;조직 ID&lt;/li&gt;
&lt;li&gt;원본 서비스 권한&lt;/li&gt;
&lt;li&gt;문서 공개 범위&lt;/li&gt;
&lt;li&gt;검색 시 ACL 필터&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. 단계별 구축 전략&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras 사례를 참고하되 처음부터 모든 기능을 구현할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 단계적으로 구축할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계: 기본 검색 MVP&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;pgvector&lt;/li&gt;
&lt;li&gt;문서 수집&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;h2 data-ke-size=&quot;size26&quot;&gt;2단계: 하이브리드 검색&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PostgreSQL Full Text Search&lt;/li&gt;
&lt;li&gt;키워드와 벡터 검색 병렬 실행&lt;/li&gt;
&lt;li&gt;RRF 통합&lt;/li&gt;
&lt;li&gt;메타데이터 필터&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계: Slack 최적화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 단위 수집&lt;/li&gt;
&lt;li&gt;LLM 요약&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;h2 data-ke-size=&quot;size26&quot;&gt;4단계: 검색 품질 향상&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리랭커&lt;/li&gt;
&lt;li&gt;신선도 점수&lt;/li&gt;
&lt;li&gt;IDF 기반 노이즈 억제&lt;/li&gt;
&lt;li&gt;컨텍스트 확장&lt;/li&gt;
&lt;li&gt;사용자 피드백 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계: 엔터프라이즈 권한&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 및 그룹 동기화&lt;/li&gt;
&lt;li&gt;문서 ACL 저장&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;h2 data-ke-size=&quot;size26&quot;&gt;6단계: AI 에이전트 연동&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MCP 서버&lt;/li&gt;
&lt;li&gt;search_slack&lt;/li&gt;
&lt;li&gt;search_code&lt;/li&gt;
&lt;li&gt;search_documents&lt;/li&gt;
&lt;li&gt;원문 조회 도구&lt;/li&gt;
&lt;li&gt;도메인별 검색 라우팅&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. 결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cerebras의 사내 지식 검색 사례에서 가장 중요한 점은 특정한 벡터 데이터베이스나 최신 AI 모델을 사용했다는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 다음과 같은 기본 원칙을 충실히 적용했다는 점이 중요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 작성되는 장소에서 수집한다.&lt;/li&gt;
&lt;li&gt;Slack 대화를 검색 가능한 문서로 정규화한다.&lt;/li&gt;
&lt;li&gt;키워드 검색과 벡터 검색을 함께 사용한다.&lt;/li&gt;
&lt;li&gt;희귀 단어와 최신 정보를 랭킹에 반영한다.&lt;/li&gt;
&lt;li&gt;RRF로 여러 검색 결과를 결합한다.&lt;/li&gt;
&lt;li&gt;소형 리랭커로 최종 관련성을 평가한다.&lt;/li&gt;
&lt;li&gt;검색 후 주변 문맥을 다시 확장한다.&lt;/li&gt;
&lt;li&gt;인간용 인터페이스와 AI 에이전트용 도구를 구분한다.&lt;/li&gt;
&lt;li&gt;운영 복잡도를 낮추기 위해 PostgreSQL을 중심으로 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사례는 RAG 시스템의 성능이 단순히 임베딩 모델이나 벡터 데이터베이스 성능으로 결정되지 않는다는 사실을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 사내 지식 시스템을 만들기 위해서는 검색 기술뿐 아니라 데이터 전처리, 동기화, 권한 관리, 사용자 경험, 에이전트 인터페이스를 하나의 시스템으로 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 초기 단계에서는 복잡한 분산 아키텍처보다 다음과 같은 단순한 구성으로 시작하는 것이 현실적일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;PostgreSQL
+ pgvector
+ Full Text Search
+ RRF
+ Reranker
+ 권한 필터
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 위에 Slack 정규화, 코드 검색, MCP, 도메인 라우팅을 단계적으로 추가하면 운영 가능한 엔터프라이즈 지식 기반으로 발전시킬 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Retrieval-Augmented Generation, RAG&lt;/li&gt;
&lt;li&gt;PostgreSQL Full Text Search&lt;/li&gt;
&lt;li&gt;GIN Index&lt;/li&gt;
&lt;li&gt;pgvector&lt;/li&gt;
&lt;li&gt;HNSW&lt;/li&gt;
&lt;li&gt;Inverse Document Frequency, IDF&lt;/li&gt;
&lt;li&gt;Reciprocal Rank Fusion, RRF&lt;/li&gt;
&lt;li&gt;Cross-Encoder Reranker&lt;/li&gt;
&lt;li&gt;Hypothetical Question Generation&lt;/li&gt;
&lt;li&gt;Context Expansion&lt;/li&gt;
&lt;li&gt;Model Context Protocol, MCP&lt;/li&gt;
&lt;li&gt;Access Control List, ACL&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1800</guid>
      <comments>https://javaexpert.tistory.com/1800#entry1800comment</comments>
      <pubDate>Wed, 5 Aug 2026 16:29:57 +0900</pubDate>
    </item>
    <item>
      <title>#   한눈에 정리하는 23가지 핵심 디자인 패턴 (Design Patterns for Humans)</title>
      <link>https://javaexpert.tistory.com/1799</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;&amp;quot;디자인 패턴을 머릿속에 쏙쏙 들어오게!&amp;quot;&lt;/strong&gt;&lt;br&gt;누구나 한번쯤 머리가 아팠을 디자인 패턴, 직관적인 &lt;strong&gt;현실 세계 비유&lt;/strong&gt;와 &lt;strong&gt;JavaScript(Node.js) &amp;amp; Python&lt;/strong&gt; 예제 코드로 가장 쉽고 명쾌하게 정리했습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;  목차 (Table of Contents)&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt; ️ 생성 패턴 (Creational)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;  구조 패턴 (Structural)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;  행동 패턴 (Behavioral)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-1-simple-factory-%EC%89%AC%EC%9A%B4-%EA%B3%B5%EC%9E%A5&quot;&gt;  Simple Factory&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-1-adapter-%EC%96%B4%EB%8C%91%ED%84%B0&quot;&gt;  Adapter&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-1-chain-of-responsibility-%EC%B1%85%EC%9E%84-%EC%82%AC%EC%8A%AC&quot;&gt;  Chain of Responsibility&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-2-factory-method-%EA%B3%B5%EC%9E%A5-%EB%A9%94%EC%84%9C%EB%93%9C&quot;&gt;  Factory Method&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-2-bridge-%EB%B8%8C%EB%A6%AC%EC%A7%80&quot;&gt;  Bridge&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-2-command-%EC%BB%A4%EB%A7%A8%EB%93%9C&quot;&gt;  Command&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-3-abstract-factory-%EC%B6%94%EC%83%81-%EA%B3%B5%EC%9E%A5&quot;&gt;  Abstract Factory&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-3-composite-%EC%BB%B4%ED%8F%AC%EC%A7%80%ED%8A%B8&quot;&gt;  Composite&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-3-iterator-%EB%B0%98%EB%B3%B5%EC%9E%90&quot;&gt;➿ Iterator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-4-builder-%EB%B9%8C%EB%8D%94&quot;&gt;  Builder&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-4-decorator-%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0&quot;&gt;☕ Decorator&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-4-mediator-%EC%A4%91%EC%9E%AC%EC%9E%90&quot;&gt;  Mediator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-5-prototype-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85&quot;&gt;  Prototype&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-5-facade-%ED%8D%BC%EC%82%AC%EB%93%9C&quot;&gt;  Facade&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-5-memento-%EB%A9%94%EB%A9%98%ED%86%A0&quot;&gt;  Memento&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-6-singleton-%EC%8B%B1%EA%B8%80%ED%86%A4&quot;&gt;  Singleton&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-6-flyweight-%ED%94%8C%EB%9D%BC%EC%9D%B4%EC%9B%A8%EC%9D%B4%ED%8A%B8&quot;&gt;  Flyweight&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-6-observer-%EC%98%B5%EC%A0%80%EB%B2%84&quot;&gt;  Observer&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-7-proxy-%ED%94%84%EB%A1%9D%EC%8B%9C&quot;&gt;  Proxy&lt;/a&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-7-visitor-%EB%B0%A9%EB%AC%B8%EC%9E%90&quot;&gt;  Visitor&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-8-strategy-%EC%A0%84%EB%9E%B5&quot;&gt;  Strategy&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-9-state-%EC%83%81%ED%83%9C&quot;&gt;  State&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;#-10-template-method-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%84%9C%EB%93%9C&quot;&gt;  Template Method&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;  디자인 패턴 들어가기 (Introduction)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;[!IMPORTANT]&lt;br&gt;&lt;strong&gt;디자인 패턴이란?&lt;/strong&gt;&lt;br&gt;자주 발생하는 문제들에 대한 &lt;strong&gt;일반적이고 재사용 가능한 해결책(가이드라인)&lt;/strong&gt;입니다. 가져다 바로 꽂아 넣는 플러그인이나 라이브러리가 아니라, &lt;strong&gt;상황을 풀어나가기 위한 디자인 템플릿&lt;/strong&gt;에 가깝습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;⚠️ 사용 시 주의사항 (Be Careful)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;만능 해결책(Silver Bullet)이 아닙니다.&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;hr&gt;
&lt;h1&gt; ️ 생성 디자인 패턴 (Creational Design Patterns)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 객체 또는 연관된 객체 그룹을 &lt;strong&gt;어떻게 인스턴스화할 것인가&lt;/strong&gt;에 집중하는 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  1. Simple Factory (쉬운 공장)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;집을 짓다가 문이 필요할 때 직접 목수 옷을 입고 나무와 톱을 들고 문을 짤 수도 있지만, &lt;strong&gt;공장에 주문해서 배송받는 편이 훨씬 깔끔&lt;/strong&gt;합니다. 문을 만드는 복잡한 과정이나 먼지를 뒤집어쓸 필요가 없기 때문입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;클라이언트에게 인스턴스화 로직을 노출하지 않고 인스턴스를 만들어 제공합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class WoodenDoor {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  getWidth() {
    return this.width;
  }

  getHeight() {
    return this.height;
  }
}

class DoorFactory {
  static makeDoor(width, height) {
    return new WoodenDoor(width, height);
  }
}

// 사용 예시
const door = DoorFactory.makeDoor(100, 200);
console.log(`Width: ${door.getWidth()}, Height: ${door.getHeight()}`);&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Door(ABC):
    @abstractmethod
    def get_width(self) -&amp;gt; float: pass

    @abstractmethod
    def get_height(self) -&amp;gt; float: pass

class WoodenDoor(Door):
    def __init__(self, width: float, height: float):
        self._width = width
        self._height = height

    def get_width(self) -&amp;gt; float: return self._width
    def get_height(self) -&amp;gt; float: return self._height

class DoorFactory:
    @staticmethod
    def make_door(width: float, height: float) -&amp;gt; Door:
        return WoodenDoor(width, height)

# 사용 예시
door1 = DoorFactory.make_door(100, 200)
print(f&amp;quot;Width: {door1.get_width()}, Height: {door1.get_height()}&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;단순 생성을 넘어 약간의 초기화 로직이나 설정 작업이 필요할 때, 해당 생성을 전용 팩토리 클래스로 묶어 코드 중복을 방지할 때 사용합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  2. Factory Method (공장 메서드)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;총괄 채용 매니저 혼자 모든 직군의 면접을 진행하는 것은 불가능합니다. 면접 진행이라는 전체 큰 틀은 갖고 있되, &lt;strong&gt;구체적으로 어떤 면접관을 세울지는 각 분야(개발, 마케팅) 팀장에게 위임&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체 생성 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지는 &lt;strong&gt;서브클래스에 위임&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Developer {
  askQuestions() {
    console.log(&amp;#39;디자인 패턴에 대해 질문합니다!&amp;#39;);
  }
}

class CommunityExecutive {
  askQuestions() {
    console.log(&amp;#39;커뮤니티 빌딩에 대해 질문합니다!&amp;#39;);
  }
}

class HiringManager {
  // Factory Method (하위 클래스에서 구현)
  makeInterviewer() {
    throw new Error(&amp;#39;makeInterviewer() 메서드를 구현해야 합니다.&amp;#39;);
  }

  takeInterview() {
    const interviewer = this.makeInterviewer();
    interviewer.askQuestions();
  }
}

class DevelopmentManager extends HiringManager {
  makeInterviewer() {
    return new Developer();
  }
}

class MarketingManager extends HiringManager {
  makeInterviewer() {
    return new CommunityExecutive();
  }
}

// 사용 예시
const devManager = new DevelopmentManager();
devManager.takeInterview(); // 디자인 패턴에 대해 질문합니다!

const marketingManager = new MarketingManager();
marketingManager.takeInterview(); // 커뮤니티 빌딩에 대해 질문합니다!&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Interviewer(ABC):
    @abstractmethod
    def ask_questions(self): pass

class Developer(Interviewer):
    def ask_questions(self):
        print(&amp;quot;디자인 패턴에 대해 질문합니다!&amp;quot;)

class CommunityExecutive(Interviewer):
    def ask_questions(self):
        print(&amp;quot;커뮤니티 빌딩에 대해 질문합니다!&amp;quot;)

class HiringManager(ABC):
    @abstractmethod
    def make_interviewer(self) -&amp;gt; Interviewer: pass

    def take_interview(self):
        interviewer = self.make_interviewer()
        interviewer.ask_questions()

class DevelopmentManager(HiringManager):
    def make_interviewer(self) -&amp;gt; Interviewer:
        return Developer()

# 사용 예시
dev_manager = DevelopmentManager()
dev_manager.take_interview()&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;상위 클래스가 자신이 생성해야 하는 객체의 구체적인 클래스를 미리 알 수 없을 때 유용합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  3. Abstract Factory (추상 공장)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;목재 문에는 목수가 필요하고, 철제 문에는 용접공이 필요합니다. 문이라는 제품과 설치 기사라는 전문가 사이에 끈끈한 연관성/의존성이 존재할 때 이를 &lt;strong&gt;그룹 단위로 세트로 제공&lt;/strong&gt;하는 매장 개념입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;연관되거나 의존적인 객체들의 패밀리를 구체적인 클래스 지정 없이 생성할 수 있는 &lt;strong&gt;&amp;quot;공장들의 공장&amp;quot;&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class WoodenDoor {
  getDescription() { console.log(&amp;#39;목재 문입니다.&amp;#39;); }
}
class IronDoor {
  getDescription() { console.log(&amp;#39;철제 문입니다.&amp;#39;); }
}

class Carpenter {
  getDescription() { console.log(&amp;#39;목재 문 전문가입니다.&amp;#39;); }
}
class Welder {
  getDescription() { console.log(&amp;#39;철제 문 전문가입니다.&amp;#39;); }
}

// 추상 공장 역
class WoodenDoorFactory {
  makeDoor() { return new WoodenDoor(); }
  makeFittingExpert() { return new Carpenter(); }
}

class IronDoorFactory {
  makeDoor() { return new IronDoor(); }
  makeFittingExpert() { return new Welder(); }
}

// 사용 예시
const woodenFactory = new WoodenDoorFactory();
const door = woodenFactory.makeDoor();
const expert = woodenFactory.makeFittingExpert();

door.getDescription();   // 목재 문입니다.
expert.getDescription(); // 목재 문 전문가입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Door(ABC):
    @abstractmethod
    def get_description(self): pass

class WoodenDoor(Door):
    def get_description(self): print(&amp;quot;목재 문입니다.&amp;quot;)

class DoorFittingExpert(ABC):
    @abstractmethod
    def get_description(self): pass

class Carpenter(DoorFittingExpert):
    def get_description(self): print(&amp;quot;목재 문 전문가입니다.&amp;quot;)

class DoorFactory(ABC):
    @abstractmethod
    def make_door(self) -&amp;gt; Door: pass
    @abstractmethod
    def make_fitting_expert(self) -&amp;gt; DoorFittingExpert: pass

class WoodenDoorFactory(DoorFactory):
    def make_door(self) -&amp;gt; Door: return WoodenDoor()
    def make_fitting_expert(self) -&amp;gt; DoorFittingExpert: return Carpenter()

# 사용 예시
factory = WoodenDoorFactory()
door = factory.make_door()
expert = factory.make_fitting_expert()
door.get_description()
expert.get_description()&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;연관된 제품군을 묶어서 생성해야 하고, 서로 호환되지 않는 제품들이 섞이는 것을 방지하고 싶을 때 사용합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  4. Builder (빌더)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;패스트푸드점에서 &amp;quot;세트 하나 주세요&amp;quot;는 단순 생성(Simple Factory)이지만, 수제 샌드위치 매장에서 &lt;strong&gt;&amp;quot;빵은 아티잔, 치즈는 체다, 소스는 칠리 추가해주세요&amp;quot;&lt;/strong&gt;처럼 복잡한 옵션을 단계별로 조합하는 것이 빌더입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;복잡한 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 합니다 (망원경 생성자 안티패턴 방지).&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Burger {
  constructor(builder) {
    this.size = builder.size;
    this.cheese = builder.cheese || false;
    this.pepperoni = builder.pepperoni || false;
    this.lettuce = builder.lettuce || false;
    this.tomato = builder.tomato || false;
  }
}

class BurgerBuilder {
  constructor(size) {
    this.size = size;
  }

  addCheese() {
    this.cheese = true;
    return this;
  }

  addPepperoni() {
    this.pepperoni = true;
    return this;
  }

  addTomato() {
    this.tomato = true;
    return this;
  }

  build() {
    return new Burger(this);
  }
}

// 사용 예시 (메서드 체이닝)
const burger = new BurgerBuilder(14)
  .addPepperoni()
  .addCheese()
  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Burger:
    def __init__(self, builder):
        self.size = builder.size
        self.cheese = builder.cheese
        self.pepperoni = builder.pepperoni

class BurgerBuilder:
    def __init__(self, size: int):
        self.size = size
        self.cheese = False
        self.pepperoni = False

    def add_cheese(self):
        self.cheese = True
        return self

    def add_pepperoni(self):
        self.pepperoni = True
        return self

    def build(self) -&amp;gt; Burger:
        return Burger(self)

# 사용 예시
burger = BurgerBuilder(14).add_cheese().add_pepperoni().build()&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;언제 사용하나요?&lt;/strong&gt;&lt;br&gt;인자(Parameter)가 엄청 많거나 생성이 여러 단계로 나누어져 선택적 옵션이 많을 때 쓰면 코드가 훨씬 깔끔해집니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  5. Prototype (프로토타입)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;복제 양 돌리를 생각해 보세요! 처음부터 하나하나 다시 키우고 설계하는 것보다 &lt;strong&gt;완성된 원본을 복제(Clone)&lt;/strong&gt;해서 살짝 수정하는 것이 훨씬 빠르고 비용이 적게 듭니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;기존 객체를 복제(Cloning)하여 새로운 객체를 생성하는 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Sheep {
  constructor(name, category = &amp;#39;Mountain Sheep&amp;#39;) {
    this.name = name;
    this.category = category;
  }

  setName(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }

  clone() {
    return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
  }
}

// 사용 예시
const original = new Sheep(&amp;#39;Jolly&amp;#39;);
const cloned = original.clone();
cloned.setName(&amp;#39;Dolly&amp;#39;);

console.log(original.getName()); // Jolly
console.log(cloned.getName());   // Dolly&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import copy

class Sheep:
    def __init__(self, name: str, category: str = &amp;quot;Mountain Sheep&amp;quot;):
        self.name = name
        self.category = category

    def clone(self):
        return copy.deepcopy(self)

# 사용 예시
original = Sheep(&amp;quot;Jolly&amp;quot;)
cloned = original.clone()
cloned.name = &amp;quot;Dolly&amp;quot;

print(original.name) # Jolly
print(cloned.name)   # Dolly&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  6. Singleton (싱글톤)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;한 나라에는 한 시점에 오직 &lt;strong&gt;단 한 명의 대통령&lt;/strong&gt;만 존재해야 합니다. 필요할 때마다 대통령을 새로 찍어낼 수는 없습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;클래스의 인스턴스가 애플리케이션 내에서 &lt;strong&gt;단 하나만 존재&lt;/strong&gt;하도록 보장하고, 어디서든 접근할 수 있는 전역 접점을 제공합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class President {
  constructor() {
    if (President.instance) {
      return President.instance;
    }
    this.name = &amp;#39;President&amp;#39;;
    President.instance = this;
  }
}

// 사용 예시
const p1 = new President();
const p2 = new President();

console.log(p1 === p2); // true&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class President:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

# 사용 예시
p1 = President()
p2 = President()
print(p1 is p2) # True&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h1&gt;  구조 디자인 패턴 (Structural Design Patterns)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 객체들을 큰 구조로 합성하거나, 서로 호환되지 않는 인터페이스를 연결하여 &lt;strong&gt;효율적인 관계를 형성&lt;/strong&gt;하는 것에 관한 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  1. Adapter (어댑터)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;SD 카드의 데이터를 노트북에 옮기고 싶은데 노트북에는 MicroSD 슬롯만 있다면? &lt;strong&gt;카드 리더기(어댑터)&lt;/strong&gt;를 끼워서 형태를 바꿔 통신합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;호환되지 않는 인터페이스를 가진 객체들이 함께 일할 수 있도록 &lt;strong&gt;중간에서 변환 역할을 감싸주는(Wrapper)&lt;/strong&gt; 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class AfricanLion {
  roar() {}
}

class Hunter {
  hunt(lion) {
    lion.roar();
  }
}

class WildDog {
  bark() {
    console.log(&amp;#39;Woof woof!&amp;#39;);
  }
}

// 어댑터 클래스
class WildDogAdapter {
  constructor(dog) {
    this.dog = dog;
  }

  roar() {
    this.dog.bark();
  }
}

// 사용 예시
const dog = new WildDog();
const adapter = new WildDogAdapter(dog);
const hunter = new Hunter();
hunter.hunt(adapter); // Woof woof!&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Lion:
    def roar(self): pass

class Hunter:
    def hunt(self, lion: Lion):
        lion.roar()

class WildDog:
    def bark(self):
        print(&amp;quot;Woof woof!&amp;quot;)

class WildDogAdapter(Lion):
    def __init__(self, dog: WildDog):
        self.dog = dog

    def roar(self):
        self.dog.bark()

# 사용 예시
dog = WildDog()
adapter = WildDogAdapter(dog)
hunter = Hunter()
hunter.hunt(adapter) # Woof woof!&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  2. Bridge (브리지)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;웹사이트에 &amp;#39;소개 페이지&amp;#39;, &amp;#39;홈 페이지&amp;#39;가 있고 각 페이지는 &amp;#39;다크 테마&amp;#39;, &amp;#39;라이트 테마&amp;#39;를 적용할 수 있습니다. 상속으로 만들면 2x2=4개의 클래스를 만들어야 하지만, &lt;strong&gt;페이지(추상화)와 테마(구현)를 브리지로 분리하여 합성&lt;/strong&gt;하면 클래스가 2+2=4개로 확장성이 훨씬 좋아집니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;추상화(Abstraction)와 구현(Implementation)을 분리&lt;/strong&gt;하여 두 계층이 독립적으로 변형될 수 있도록 만드는 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class DarkTheme {
  getColor() { return &amp;#39;Dark Black&amp;#39;; }
}

class LightTheme {
  getColor() { return &amp;#39;Off White&amp;#39;; }
}

class AboutPage {
  constructor(theme) {
    this.theme = theme;
  }

  getContent() {
    return `About page in ${this.theme.getColor()}`;
  }
}

// 사용 예시
const darkPage = new AboutPage(new DarkTheme());
console.log(darkPage.getContent()); // About page in Dark Black&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class DarkTheme:
    def get_color(self): return &amp;quot;Dark Black&amp;quot;

class AboutPage:
    def __init__(self, theme):
        self.theme = theme

    def get_content(self):
        return f&amp;quot;About page in {self.theme.get_color()}&amp;quot;

# 사용 예시
page = AboutPage(DarkTheme())
print(page.get_content())&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  3. Composite (컴포지트)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;회사는 개별 개발자(단일 객체)들로 구성되기도 하고, 개발팀(객체 그룹)으로 구성되기도 합니다. 대표님이 &amp;quot;이번 달 급여 총액 얼마야?&amp;quot;라고 물었을 때 &lt;strong&gt;단일 직원이나 팀 전체나 똑같이 &lt;code&gt;getSalary()&lt;/code&gt;로 일관되게 조회&lt;/strong&gt;할 수 있어야 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체들을 &lt;strong&gt;트리 구조&lt;/strong&gt;로 구성하여 개별 객체와 복합 객체를 클라이언트 입장에서 동일하게 다룰 수 있게 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Developer {
  constructor(name, salary) {
    this.name = name;
    this.salary = salary;
  }

  getSalary() {
    return this.salary;
  }
}

class Organization {
  constructor() {
    this.employees = [];
  }

  addEmployee(employee) {
    this.employees.push(employee);
  }

  getSalary() {
    return this.employees.reduce((total, e) =&amp;gt; total + e.getSalary(), 0);
  }
}

// 사용 예시
const org = new Organization();
org.addEmployee(new Developer(&amp;#39;John&amp;#39;, 1000));
org.addEmployee(new Developer(&amp;#39;Jane&amp;#39;, 1200));

console.log(&amp;#39;Total Salary:&amp;#39;, org.getSalary()); // 2200&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Employee(ABC):
    @abstractmethod
    def get_salary(self) -&amp;gt; float: pass

class Developer(Employee):
    def __init__(self, name: str, salary: float):
        self.name = name
        self.salary = salary

    def get_salary(self) -&amp;gt; float: return self.salary

class Organization(Employee):
    def __init__(self):
        self.employees = []

    def add_employee(self, employee: Employee):
        self.employees.append(employee)

    def get_salary(self) -&amp;gt; float:
        return sum(e.get_salary() for e in self.employees)

# 사용 예시
org = Organization()
org.add_employee(Developer(&amp;quot;John&amp;quot;, 1000))
org.add_employee(Developer(&amp;quot;Jane&amp;quot;, 1200))
print(&amp;quot;Total:&amp;quot;, org.get_salary())&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;☕ 4. Decorator (데코레이터)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;기본 에스프레소 커피에 우유 토핑을 얹으면 라떼가 되고, 휘핑크림을 또 얹으면 모카가 됩니다. 기본 제품의 틀을 변경하지 않고 &lt;strong&gt;필요한 옵션을 겉에 겹겹이 포장(감싸서) 추가&lt;/strong&gt;하는 느낌입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체에 동적으로 새로운 책임(기능)을 추가하는 패턴으로, &lt;strong&gt;서브클래싱(상속)의 유연한 대안&lt;/strong&gt;을 제공합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class SimpleCoffee {
  getCost() { return 10; }
  getDescription() { return &amp;#39;Simple coffee&amp;#39;; }
}

class MilkCoffee {
  constructor(coffee) {
    this.coffee = coffee;
  }

  getCost() {
    return this.coffee.getCost() + 2;
  }

  getDescription() {
    return `${this.coffee.getDescription()}, milk`;
  }
}

// 사용 예시
let coffee = new SimpleCoffee();
coffee = new MilkCoffee(coffee);
console.log(coffee.getCost()); // 12
console.log(coffee.getDescription()); // Simple coffee, milk&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Coffee:
    def get_cost(self) -&amp;gt; float: return 10.0
    def get_description(self) -&amp;gt; str: return &amp;quot;Simple coffee&amp;quot;

class MilkCoffee:
    def __init__(self, coffee: Coffee):
        self._coffee = coffee
    def get_cost(self) -&amp;gt; float: return self._coffee.get_cost() + 2.0
    def get_description(self) -&amp;gt; str: return self._coffee.get_description() + &amp;quot;, milk&amp;quot;

# 사용 예시
coffee = SimpleCoffee()
coffee_with_milk = MilkCoffee(coffee)
print(coffee_with_milk.get_cost()) # 12.0&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  5. Facade (퍼사드)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;컴퓨터를 켤 때 메인보드 전원 공급, CPU 클럭 가동, 램 전압 체크 등 복잡한 과정을 일일이 조작하지 않습니다. 그저 &lt;strong&gt;전원 버튼 하나(Facade)&lt;/strong&gt;만 누르면 내부 복잡한 서브시스템이 일괄 실행됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;서브시스템의 복잡한 인터페이스 집합에 대해 &lt;strong&gt;통합된 단순한 고수준 인터페이스를 제공&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Computer {
  getElectricShock() { console.log(&amp;#39;Ouch!&amp;#39;); }
  makeSound() { console.log(&amp;#39;Beep beep!&amp;#39;); }
  showLoadingScreen() { console.log(&amp;#39;Loading..&amp;#39;); }
}

class ComputerFacade {
  constructor(computer) {
    this.computer = computer;
  }

  turnOn() {
    this.computer.getElectricShock();
    this.computer.makeSound();
    this.computer.showLoadingScreen();
  }
}

// 사용 예시
const facade = new ComputerFacade(new Computer());
facade.turnOn();&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Computer:
    def get_electric_shock(self): print(&amp;quot;Ouch!&amp;quot;)
    def make_sound(self): print(&amp;quot;Beep beep!&amp;quot;)
    def show_loading_screen(self): print(&amp;quot;Loading..&amp;quot;)

class ComputerFacade:
    def __init__(self, computer: Computer):
        self.computer = computer

    def turn_on(self):
        self.computer.get_electric_shock()
        self.computer.make_sound()
        self.computer.show_loading_screen()

# 사용 예시
facade = ComputerFacade(Computer())
facade.turn_on()&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  6. Flyweight (플라이웨이트)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;찻집에서 손님이 100명 왔을 때 매번 완전히 새로운 찻잔 객체를 100개 찍어내기보다, &lt;strong&gt;동일한 종류의 차(상태가 같은 객체)는 재사용하여 메모리와 자원을 아끼는 방식&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;많은 수의 유사한 객체를 효율적으로 공유하여 &lt;strong&gt;메모리 사용량을 절감&lt;/strong&gt;시키는 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class KarakTea {}

class TeaMaker {
  constructor() {
    this.availableTea = {};
  }

  make(preference) {
    if (!this.availableTea[preference]) {
      this.availableTea[preference] = new KarakTea();
    }
    return this.availableTea[preference];
  }
}

// 사용 예시
const maker = new TeaMaker();
const tea1 = maker.make(&amp;#39;less sugar&amp;#39;);
const tea2 = maker.make(&amp;#39;less sugar&amp;#39;);

console.log(tea1 === tea2); // true (객체 공유)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class KarakTea:
    pass

class TeaMaker:
    def __init__(self):
        self._available_tea = {}

    def make(self, preference: str) -&amp;gt; KarakTea:
        if preference not in self._available_tea:
            self._available_tea[preference] = KarakTea()
        return self._available_tea[preference]

# 사용 예시
maker = TeaMaker()
tea1 = maker.make(&amp;quot;less sugar&amp;quot;)
tea2 = maker.make(&amp;quot;less sugar&amp;quot;)
print(tea1 is tea2) # True&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  7. Proxy (프록시)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;연구실 문을 그냥 열 수도 있지만, &lt;strong&gt;보안 출입 카드키(Proxy)&lt;/strong&gt;를 거쳐야만 문이 열리도록 출입 통제 기능을 대리하는 것입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;다른 객체에 대한 &lt;strong&gt;접근을 제어하거나 대리자 역할&lt;/strong&gt;을 수행하는 객체를 제공합니다 (보안, 캐싱, 지연 로딩 등).&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class LabDoor {
  open() { console.log(&amp;#39;Opening lab door&amp;#39;); }
  close() { console.log(&amp;#39;Closing lab door&amp;#39;); }
}

class SecuredDoor {
  constructor(door) {
    this.door = door;
  }

  open(password) {
    if (password === &amp;#39;secret&amp;#39;) {
      this.door.open();
    } else {
      console.log(&amp;#39;Access denied!&amp;#39;);
    }
  }

  close() {
    this.door.close();
  }
}

// 사용 예시
const door = new SecuredDoor(new LabDoor());
door.open(&amp;#39;wrong&amp;#39;);  // Access denied!
door.open(&amp;#39;secret&amp;#39;); // Opening lab door&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class LabDoor:
    def open(self): print(&amp;quot;Opening lab door&amp;quot;)

class SecuredDoor:
    def __init__(self, door: LabDoor):
        self.door = door

    def open(self, password: str):
        if password == &amp;quot;secret&amp;quot;:
            self.door.open()
        else:
            print(&amp;quot;Access denied!&amp;quot;)

# 사용 예시
door = SecuredDoor(LabDoor())
door.open(&amp;quot;wrong&amp;quot;)  # Access denied!
door.open(&amp;quot;secret&amp;quot;) # Opening lab door&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h1&gt;  행동 디자인 패턴 (Behavioral Design Patterns)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 요약:&lt;/strong&gt; 객체 간의 &lt;strong&gt;책임 분배, 알고리즘 제어 흐름, 그리고 통신 방식&lt;/strong&gt;을 다루는 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;  1. Chain of Responsibility (책임 사슬)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;결제 시스템에서 먼저 &lt;strong&gt;계좌 A(잔액 부족) ➔ 카드 B(잔액 부족) ➔ 카카오페이 C(성공)&lt;/strong&gt;로 순차적으로 처리 권한을 넘겨가며 결제를 완료하는 구조입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;요청을 처리할 수 있는 객체들을 사슬(Chain) 형태로 연결하여, &lt;strong&gt;요청이 처리될 때까지 사슬을 따라 전달&lt;/strong&gt;시킵니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Account {
  setNext(account) {
    this.successor = account;
  }

  pay(amountToPay) {
    if (this.canPay(amountToPay)) {
      console.log(`Paid ${amountToPay} using ${this.name}`);
    } else if (this.successor) {
      console.log(`Cannot pay using ${this.name}. Proceeding..`);
      this.successor.pay(amountToPay);
    } else {
      console.log(&amp;#39;None of the accounts have enough balance&amp;#39;);
    }
  }

  canPay(amount) {
    return this.balance &amp;gt;= amount;
  }
}

class Bank extends Account {
  constructor(balance) {
    super();
    this.name = &amp;#39;Bank&amp;#39;;
    this.balance = balance;
  }
}

class Paypal extends Account {
  constructor(balance) {
    super();
    this.name = &amp;#39;Paypal&amp;#39;;
    this.balance = balance;
  }
}

// 사용 예시
const bank = new Bank(100);
const paypal = new Paypal(200);

bank.setNext(paypal);
bank.pay(150); // Paid 150 using Paypal&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Account:
    def __init__(self, balance: float):
        self.balance = balance
        self.next_account = None

    def set_next(self, account):
        self.next_account = account

    def pay(self, amount: float):
        if self.balance &amp;gt;= amount:
            print(f&amp;quot;Paid {amount} using {self.__class__.__name__}&amp;quot;)
        elif self.next_account:
            self.next_account.pay(amount)
        else:
            print(&amp;quot;Cannot pay!&amp;quot;)

# 사용 예시
bank = Account(100)
paypal = Account(200)
bank.set_next(paypal)
bank.pay(150) # Paid 150 using Paypal&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  2. Command (커맨드)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;손님이 주문서(Command)를 작성해서 웨이터(Invoker)에게 주면, 웨이터는 주문 내용을 신경 쓰지 않고 요리사(Receiver)에게 그대로 전달만 합니다. &lt;strong&gt;요청 자체가 독립된 객체&lt;/strong&gt;가 됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;요청 자체를 &lt;strong&gt;독립적인 객체로 캡슐화&lt;/strong&gt;하여 요청 매개변수화, 작업 취소(Undo), 큐 저장 등을 가능하게 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Bulb {
  turnOn() { console.log(&amp;#39;Light on&amp;#39;); }
  turnOff() { console.log(&amp;#39;Light off&amp;#39;); }
}

class TurnOnCommand {
  constructor(bulb) {
    this.bulb = bulb;
  }

  execute() {
    this.bulb.turnOn();
  }
}

class RemoteControl {
  submit(command) {
    command.execute();
  }
}

// 사용 예시
const bulb = new Bulb();
const cmd = new TurnOnCommand(bulb);
const remote = new RemoteControl();
remote.submit(cmd); // Light on&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Bulb:
    def turn_on(self): print(&amp;quot;Light on&amp;quot;)
    def turn_off(self): print(&amp;quot;Light off&amp;quot;)

class TurnOnCommand:
    def __init__(self, bulb: Bulb): self.bulb = bulb
    def execute(self): self.bulb.turn_on()

class RemoteControl:
    def submit(self, command): command.execute()

# 사용 예시
bulb = Bulb()
cmd = TurnOnCommand(bulb)
remote = RemoteControl()
remote.submit(cmd) # Light on&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;➿ 3. Iterator (반복자)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;라디오의 &amp;#39;다음 채널&amp;#39; 버튼을 누르면 내부 회로 구조가 복잡하게 어찌 되어 있든 &lt;strong&gt;단순히 순서대로 다음 채널로 넘어가서 청취&lt;/strong&gt;할 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;컬렉션의 내부 표현 방식을 노출하지 않고 &lt;strong&gt;그 안의 요소들에 순차적으로 접근&lt;/strong&gt;할 수 있는 방법을 제공합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class StationList {
  constructor() {
    this.stations = [];
  }

  addStation(freq) {
    this.stations.push(freq);
  }

  // Symbol.iterator 구현
  *[Symbol.iterator]() {
    for (const station of this.stations) {
      yield station;
    }
  }
}

// 사용 예시
const stations = new StationList();
stations.addStation(89.0);
stations.addStation(101.5);

for (const station of stations) {
  console.log(station);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class StationList:
    def __init__(self):
        self.stations = []

    def add_station(self, freq: float):
        self.stations.append(freq)

    def __iter__(self):
        return iter(self.stations)

# 사용 예시
stations = StationList()
stations.add_station(89.0)
stations.add_station(101.5)
for s in stations:
    print(s)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  4. Mediator (중재자)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;채팅방(Mediator)이 존재하여 모든 유저가 서로 1:1로 직접 연결될 필요 없이 &lt;strong&gt;채팅방 서버 하나만을 통해 메시지를 송수신&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체 간의 복잡한 상호작용을 &lt;strong&gt;중재자 객체에 캡슐화&lt;/strong&gt;하여 객체 간의 결합도(Coupling)를 획기적으로 낮춥니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class ChatRoom {
  showMessage(user, message) {
    console.log(`[${user.getName()}]: ${message}`);
  }
}

class User {
  constructor(name, chatMediator) {
    this.name = name;
    this.chatMediator = chatMediator;
  }

  getName() {
    return this.name;
  }

  send(message) {
    this.chatMediator.showMessage(this, message);
  }
}

// 사용 예시
const mediator = new ChatRoom();
const john = new User(&amp;#39;John&amp;#39;, mediator);
john.send(&amp;#39;Hello!&amp;#39;); // [John]: Hello!&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ChatRoom:
    def show_message(self, user, message: str):
        print(f&amp;quot;[{user.name}]: {message}&amp;quot;)

class User:
    def __init__(self, name: str, mediator: ChatRoom):
        self.name = name
        self.mediator = mediator

    def send(self, message: str):
        self.mediator.show_message(self, message)

# 사용 예시
chat = ChatRoom()
john = User(&amp;quot;John&amp;quot;, chat)
john.send(&amp;quot;Hello!&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  5. Memento (메멘토)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;텍스트 에디터에서 글을 쓰다가 &lt;strong&gt;&lt;code&gt;Ctrl + Z&lt;/code&gt; (Undo)&lt;/strong&gt;를 누르면 이전 저장 시점(스냅샷)으로 글의 상태가 롤백됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;캡슐화를 저해하지 않으면서 &lt;strong&gt;객체의 내부 상태를 스냅샷으로 저장하여 나중에 복원&lt;/strong&gt;할 수 있게 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Memento {
  constructor(content) {
    this.content = content;
  }

  getContent() {
    return this.content;
  }
}

class Editor {
  constructor() {
    this.content = &amp;#39;&amp;#39;;
  }

  type(text) {
    this.content += text;
  }

  save() {
    return new Memento(this.content);
  }

  restore(memento) {
    this.content = memento.getContent();
  }
}

// 사용 예시
const editor = new Editor();
editor.type(&amp;#39;Hello World&amp;#39;);
const saved = editor.save();

editor.type(&amp;#39; Extra text&amp;#39;);
editor.restore(saved);
console.log(editor.content); // Hello World&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Memento:
    def __init__(self, state: str): self._state = state
    def get_state(self): return self._state

class Editor:
    def __init__(self): self.content = &amp;quot;&amp;quot;
    def type(self, text: str): self.content += text
    def save(self): return Memento(self.content)
    def restore(self, memento: Memento): self.content = memento.get_state()

# 사용 예시
editor = Editor()
editor.type(&amp;quot;Hello World&amp;quot;)
saved = editor.save()
editor.type(&amp;quot; Extra text&amp;quot;)
editor.restore(saved)
print(editor.content) # Hello World&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  6. Observer (옵저버)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;YouTube 채널 구독과 같습니다. 채널에 새 영상이 업로드(상태 변경)되면 &lt;strong&gt;구독(Subscriber/Observer) 중인 시청자들에게 알림&lt;/strong&gt;이 자동으로 보내집니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체의 상태 변화가 있을 때 이를 구독하고 있는 &lt;strong&gt;관찰자들에게 자동으로 알림을 전달&lt;/strong&gt;하는 일대다 의존 관계 패턴입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class JobSeeker {
  constructor(name) {
    this.name = name;
  }

  notify(jobTitle) {
    console.log(`Hi ${this.name}, new job posted: ${jobTitle}`);
  }
}

class EmploymentAgency {
  constructor() {
    this.observers = [];
  }

  attach(observer) {
    this.observers.push(observer);
  }

  addJob(jobTitle) {
    this.observers.forEach(obs =&amp;gt; obs.notify(jobTitle));
  }
}

// 사용 예시
const agency = new EmploymentAgency();
agency.attach(new JobSeeker(&amp;#39;Alice&amp;#39;));
agency.addJob(&amp;#39;Backend Developer&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class JobSeeker:
    def __init__(self, name: str): self.name = name
    def notify(self, job_title: str): print(f&amp;quot;Hi {self.name}, new job: {job_title}&amp;quot;)

class EmploymentAgency:
    def __init__(self): self.observers = []
    def attach(self, observer: JobSeeker): self.observers.append(observer)
    def add_job(self, title: str):
        for obs in self.observers: obs.notify(title)

# 사용 예시
agency = EmploymentAgency()
agency.attach(JobSeeker(&amp;quot;Alice&amp;quot;))
agency.add_job(&amp;quot;Backend Developer&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  7. Visitor (방문자)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;관광객(Visitor)이 해외 국가를 방문할 때 현지 법이나 규칙을 바꾸지 않고도 &lt;strong&gt;관광객 자신의 고유한 동작(사진 찍기, 쇼핑하기 등)을 수행&lt;/strong&gt;합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체 구조의 클래스 코드를 변경하지 않고 &lt;strong&gt;새로운 연산/기능을 외부 방문자 클래스에 추가&lt;/strong&gt;할 수 있게 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Monkey {
  shout() {
    console.log(&amp;#39;Ooh oo aa aa!&amp;#39;);
  }

  accept(operation) {
    operation.visitMonkey(this);
  }
}

class SpeakOperation {
  visitMonkey(monkey) {
    monkey.shout();
  }
}

// 사용 예시
const monkey = new Monkey();
const speak = new SpeakOperation();
monkey.accept(speak); // Ooh oo aa aa!&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Monkey:
    def shout(self): print(&amp;quot;Ooh oo aa aa!&amp;quot;)
    def accept(self, operation): operation.visit_monkey(self)

class SpeakOperation:
    def visit_monkey(self, monkey: Monkey): monkey.shout()

# 사용 예시
monkey = Monkey()
speak = SpeakOperation()
monkey.accept(speak)&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  8. Strategy (전략)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;정렬할 데이터 양에 따라 &lt;strong&gt;작은 데이터는 버블 정렬, 큰 데이터는 퀵 정렬&lt;/strong&gt;로 알고리즘 전략을 런타임에 유연하게 변경합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;동일 계열의 &lt;strong&gt;알고리즘들을 개별 클래스로 캡슐화&lt;/strong&gt;하여 런타임에 동적으로 교체해서 사용할 수 있게 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class BubbleSort {
  sort(dataset) {
    console.log(&amp;#39;Bubble sort applied&amp;#39;);
    return dataset.sort();
  }
}

class QuickSort {
  sort(dataset) {
    console.log(&amp;#39;Quick sort applied&amp;#39;);
    return dataset.sort();
  }
}

class Sorter {
  constructor(strategy) {
    this.strategy = strategy;
  }

  sort(dataset) {
    return this.strategy.sort(dataset);
  }
}

// 사용 예시
const sorter = new Sorter(new BubbleSort());
sorter.sort([3, 1, 2]);&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class BubbleSort:
    def sort(self, data):
        print(&amp;quot;Bubble sort applied&amp;quot;)
        return sorted(data)

class QuickSort:
    def sort(self, data):
        print(&amp;quot;Quick sort applied&amp;quot;)
        return sorted(data)

class Sorter:
    def __init__(self, strategy): self.strategy = strategy
    def sort(self, data): return self.strategy.sort(data)

# 사용 예시
sorter = Sorter(BubbleSort())
sorter.sort([3, 1, 2])&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  9. State (상태)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;스마트폰에서 똑같이 화면을 누르더라도 &lt;strong&gt;잠금 상태, 통화 상태, 카메라 작동 상태&lt;/strong&gt;에 따라 각각 완전히 다른 동작을 수행하는 것과 같습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;객체의 &lt;strong&gt;내부 상태 변경에 따라 객체의 행동을 변경&lt;/strong&gt;하여, 마치 객체의 클래스가 바뀐 것처럼 동작하게 만듭니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class StateIdle {
  pickUp() {
    console.log(&amp;#39;Picked up&amp;#39;);
    return new StatePickedUp();
  }
}

class StatePickedUp {
  pickUp() {
    console.log(&amp;#39;Already picked up&amp;#39;);
    return this;
  }
}

class Phone {
  constructor() {
    this.state = new StateIdle();
  }

  pickUp() {
    this.state = this.state.pickUp();
  }
}

// 사용 예시
const phone = new Phone();
phone.pickUp(); // Picked up
phone.pickUp(); // Already picked up&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class StateIdle:
    def pick_up(self):
        print(&amp;quot;Picked up&amp;quot;)
        return StatePickedUp()

class StatePickedUp:
    def pick_up(self):
        print(&amp;quot;Already picked up&amp;quot;)
        return self

class Phone:
    def __init__(self): self.state = StateIdle()
    def pick_up(self): self.state = self.state.pick_up()

# 사용 예시
phone = Phone()
phone.pick_up() # Picked up&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h3&gt;  10. Template Method (템플릿 메서드)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;현실 예시:&lt;/strong&gt;&lt;br&gt;건축 공정에서 &lt;strong&gt;&lt;code&gt;기초 공사 ➔ 벽 세우기 ➔ 지붕 얹기&lt;/code&gt;의 큰 틀(스켈레톤)&lt;/strong&gt;은 일정하지만, 벽을 콘크리트로 할지 목재로 할지의 개별 세부 단계는 상황에 맞춰 다르게 구현합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;한 줄 정의:&lt;/strong&gt;&lt;br&gt;알고리즘의 &lt;strong&gt;골격(뼈대)을 상위 클래스의 메서드에 정의&lt;/strong&gt;하고, 일부 세부 단계는 하위 클래스에서 오버라이딩하도록 미룹니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;  프로그래밍 예제&lt;/h4&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;JavaScript (Node.js) 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;class Builder {
  // 템플릿 메서드
  build() {
    this.test();
    this.assemble();
  }

  test() {
    throw new Error(&amp;#39;test() 메서드를 구현해야 합니다.&amp;#39;);
  }

  assemble() {
    throw new Error(&amp;#39;assemble() 메서드를 구현해야 합니다.&amp;#39;);
  }
}

class AndroidBuilder extends Builder {
  test() {
    console.log(&amp;#39;Testing Android code&amp;#39;);
  }

  assemble() {
    console.log(&amp;#39;Assembling APK&amp;#39;);
  }
}

// 사용 예시
const builder = new AndroidBuilder();
builder.build();&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Python 예제 보기&lt;/b&gt;&lt;/summary&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Builder(ABC):
    # Template method
    def build(self):
        self.test()
        self.assemble()

    @abstractmethod
    def test(self): pass
    @abstractmethod
    def assemble(self): pass

class AndroidBuilder(Builder):
    def test(self): print(&amp;quot;Testing Android code&amp;quot;)
    def assemble(self): print(&amp;quot;Assembling APK&amp;quot;)

# 사용 예시
builder = AndroidBuilder()
builder.build()&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;

&lt;hr&gt;
&lt;h2&gt;  마무리하며 (Wrap-Up)&lt;/h2&gt;
&lt;p&gt;이것으로 핵심 23가지 디자인 패턴 정리를 마칩니다!&lt;br&gt;디자인 패턴은 코드를 무조건 복잡하게 만드는 도구가 아니라, &lt;strong&gt;팀원 간의 공통 언어를 형성하고 유지보수성을 극대화&lt;/strong&gt;하기 위한 약속입니다. 적재적소에 유연하게 활용해 보세요!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;  기여 및 피드백 (Contribution)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;  이슈 제보 (Report issues)&lt;/li&gt;
&lt;li&gt;  개선 푸시 리퀘스트 (Open Pull Request)&lt;/li&gt;
&lt;li&gt;  주변 공유 및 전달 (Spread the word)&lt;/li&gt;
&lt;li&gt;  작성자 인사하기: &lt;a href=&quot;https://twitter.com/kamrify&quot;&gt;&lt;img src=&quot;https://img.shields.io/twitter/url/https/twitter.com/kamrify.svg?style=social&amp;label=Follow%20%40kamrify&quot; alt=&quot;Twitter URL&quot;&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;  라이선스 (License)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://creativecommons.org/licenses/by/4.0/&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg&quot; alt=&quot;License: CC BY 4.0&quot;&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1799</guid>
      <comments>https://javaexpert.tistory.com/1799#entry1799comment</comments>
      <pubDate>Wed, 5 Aug 2026 14:41:11 +0900</pubDate>
    </item>
    <item>
      <title>AI 네이티브 앱 제작 프로세스</title>
      <link>https://javaexpert.tistory.com/1798</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ1CpM/dJMcadXfVET/ISFk7mTHFesmMY83yT89W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ1CpM/dJMcadXfVET/ISFk7mTHFesmMY83yT89W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ1CpM/dJMcadXfVET/ISFk7mTHFesmMY83yT89W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ1CpM%2FdJMcadXfVET%2FISFk7mTHFesmMY83yT89W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;864&quot; height=&quot;1821&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;1821&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사용자 문제 정의부터 디자인, 통합 명세, 구현, 검증까지 연결하는 실무 가이드&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. 문서 개요&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;목적&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 문서는 AI를 활용해 앱을 만들 때 곧바로 코드를 작성하지 않고, 사용자 문제와 제품 기준을 먼저 구체화하는 작업 절차를 설명한다. 독자는 이 절차를 따라 다음 산출물을 순서대로 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;사용자 문제 정의&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;핵심 화면 시안&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;통합 기획서 &lt;/span&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전체 화면 목업 &lt;/span&gt;&lt;span&gt;design.html&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;구현된 애플리케이션&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;완료 상태는 제품 요구사항, 디자인, 데이터 구조, 구현 코드가 서로 일치하고 핵심 사용자 흐름을 실행할 수 있는 상태다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;대상 독자&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;AI 코딩 도구로 웹 또는 모바일 앱을 만들려는 기획자와 개발자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;제품 아이디어를 실제 서비스 명세로 구체화하려는 1인 개발자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AI가 생성한 화면과 코드를 검수해야 하는 제품 책임자&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;범위&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 문서는 제품 기획과 제작 순서를 다룬다. 특정 AI 도구의 설치 방법, 명령어, 요금, 배포 환경 설정은 다루지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;미검증 범위:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 이 문서는 사용자가 제공한 영상 요약을 기술 문서 형식으로 재구성한 것이다. 영상 원본, 도구의 현재 기능, 실제 구현 결과는 별도로 검증하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. 핵심 원칙&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AI 네이티브 앱 제작의 핵심은 코딩 속도가 아니라 &lt;/span&gt;&lt;b&gt;&lt;span&gt;AI가 판단할 수 있는 기준을 먼저 제공하는 것&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이다. 전체 흐름은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;아이디어
&amp;rarr; 사용자 문제 정의
&amp;rarr; 디자인 원칙 수립
&amp;rarr; 핵심 화면 탐색
&amp;rarr; 제품&amp;middot;디자인&amp;middot;기술 명세 통합
&amp;rarr; 전체 화면과 상태 설계
&amp;rarr; 구현 전 질의응답
&amp;rarr; 실제 개발
&amp;rarr; 검증과 반복 수정
&amp;rarr; 명세&amp;middot;디자인&amp;middot;코드 동기화
&amp;rarr; 배포&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 과정에서 AI는 여러 대안을 빠르게 생성한다. 사람은 해결할 문제, 디자인의 취향, 기능의 우선순위, 데이터 구조와 최종 품질 기준을 결정한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. 예제 서비스: TasteMaker&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TasteMaker는 영화, TV 프로그램, 게임처럼 여러 서비스에 흩어진 개인 취향을 한 페이지에 정리하고 공유하는 프로필 서비스다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;사용자 문제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;영화, TV 프로그램, 게임 기록이 서로 다른 서비스에 나뉘어 있어 사용자가 자신의 취향 전체를 한 페이지로 보여주기 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 기능&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;좋아하는 영화&amp;middot;TV 프로그램&amp;middot;게임 표시&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;최근 리뷰와 별점 표시&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자가 만든 콘텐츠 목록 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공개 프로필 공유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회원가입과 온보딩&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;콘텐츠 추가&amp;middot;수정&amp;middot;정렬&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 예제는 이후 단계에서 어떤 산출물을 만들어야 하는지 설명하기 위한 기준으로 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. 단계별 제작 절차&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.1 사용자 문제 정의&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기능 목록을 작성하기 전에 제품이 해결할 사용자 문제를 한 문장으로 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;작성 항목&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;이 제품을 사용할 사람은 누구인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자는 현재 어떤 상황에서 불편을 겪는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;기존 해결 방법은 왜 충분하지 않은가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;제품을 사용한 뒤 무엇이 달라져야 하는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사업화를 전제로 한다면 다음 항목도 조사한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;실제로 같은 문제를 겪는 사용자가 있는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;직접 또는 간접 경쟁 서비스는 무엇인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자가 비용을 지불할 가능성이 있는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;지속 가능한 수익 모델을 구성할 수 있는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개인용 앱이나 내부 도구는 시장성 조사를 줄일 수 있지만, 사용자와 완료 상태는 명확히 정의해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;산출물 예시&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;[대상 사용자]는 [현재 상황]에서 [문제]를 겪는다.
기존에는 [대체 방법]을 사용하지만 [한계]가 있다.
이 제품은 [핵심 가치]를 제공해 [기대 결과]를 만든다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;특정 기능명을 사용하지 않고도 문제를 설명할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;대상 사용자와 사용 상황이 드러난다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;제품이 제공할 변화가 한 문장으로 정리된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사업용 제품이라면 경쟁 서비스와 지불 가능성을 조사할 계획이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.2 디자인 레퍼런스 수집과 &lt;/span&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;span&gt; 작성&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;ldquo;예쁘게 만들어 달라&amp;rdquo;는 추상적인 요청을 시각적 기준과 구현 가능한 디자인 규칙으로 바꾼다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;절차&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;Mobbin, Dribbble, 실제 앱 화면 등에서 레퍼런스를 수집한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;각 레퍼런스에서 채택할 요소와 제외할 요소를 구분한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;제품의 콘텐츠와 사용 맥락에 맞는 디자인 원칙을 정한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;결정한 내용을 &lt;/span&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;span&gt;에 기록한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;레퍼런스는 방향을 찾는 자료로만 사용하고 화면을 그대로 복제하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;span&gt; 권장 구조&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;# Design Direction

## Design Principles
- 콘텐츠가 인터페이스보다 먼저 보이게 한다.
- 장식 요소는 사용자의 판단을 방해하지 않는 범위에서 사용한다.

## Color
- 배경색:
- 기본 텍스트:
- 보조 텍스트:
- 강조색:
- 상태 색상:

## Typography
- 제목:
- 본문:
- 보조 정보:

## Spacing and Layout
- 기본 간격 단위:
- 최대 콘텐츠 너비:
- 카드 간격:

## Components
- 버튼:
- 카드:
- 입력창:
- 모달 및 시트:

## Content Density
- 한 화면에 노출할 정보량:
- 모바일 축약 규칙:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TasteMaker의 디자인 방향은 인터페이스를 조용하고 단순하게 유지하고, 영화와 게임의 커버 이미지가 화면의 주요 색상을 담당하게 하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;색상, 타이포그래피, 간격, 카드와 버튼 규칙이 정의되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;레퍼런스에서 가져올 원칙과 복제하지 않을 요소가 구분되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;AI가 서로 다른 화면에서도 같은 시각 언어를 적용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.3 핵심 화면 탐색&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;전체 화면을 만들기 전에 제품의 가치와 디자인 방향을 가장 잘 보여주는 화면 두 개를 설계한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TasteMaker에서는 다음 화면을 먼저 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;비로그인 사용자가 보는 랜딩 페이지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;영화&amp;middot;TV 프로그램&amp;middot;게임 취향을 보여주는 공개 프로필&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;절차&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;span&gt;와 사용자 문제를 AI 디자인 도구에 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;서로 다른 레이아웃과 테마의 시안을 요청한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;각 시안을 콘텐츠 우선순위와 사용성 기준으로 비교한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;적합한 방향을 하나 선택한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;불필요한 요소를 제거하고 배치, 밀도, 문구를 반복 수정한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;시안 변형 예시는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;행 중심 레이아웃&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그리드 중심 에디토리얼 레이아웃&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;라이트 모드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;다크 모드&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;피드백 작성 원칙&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;ldquo;더 예쁘게&amp;rdquo;처럼 평가 기준이 없는 표현보다 대상과 변경 내용을 함께 적는다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;[대상 화면 또는 컴포넌트]에서
[현재 문제]가 있으므로
[구체적인 변경]을 적용한다.
[변경 후 확인할 기준]은 다음과 같다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;사용하지 않는 팔로우 버튼을 삭제한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;즐겨찾기 콘텐츠를 한 행에 6개 배치한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;콘텐츠 행에 좌우 이동 버튼을 추가한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰는 작은 카드 그리드 대신 전체 너비의 한 줄 구조로 표시한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공개 프로필 왼쪽에 섹션 빠른 이동 메뉴를 배치한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;랜딩 페이지에서 실제 공개 프로필 예시를 크게 보여준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;선택한 화면이 제품의 핵심 가치를 설명한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;콘텐츠 우선순위와 화면 밀도가 정리되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;두 핵심 화면이 같은 컴포넌트와 시각 규칙을 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;선택하지 않은 시안과 선택 이유를 기록했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.4 통합 기획서 &lt;/span&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt; 작성&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제품 요구사항, 디자인 시스템, 기술 설계를 하나의 기준 문서로 통합한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt;은 최소한 Product, Design, Tech의 세 영역으로 구성한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Product 영역&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;사용자 문제와 대상 사용자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;제품 목표와 성공 기준&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;핵심 기능과 우선순위&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;화면별 요구사항&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;기본 상태와 예외 상태&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자 권한과 접근 범위&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Design 영역&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;디자인 원칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;색상과 타이포그래피&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;간격과 레이아웃 규칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공통 UI 컴포넌트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;컴포넌트별 상태와 사용 규칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;반응형 동작&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Tech 영역&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;프런트엔드와 백엔드 기술&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인증 방식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;데이터베이스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;데이터 스키마와 관계&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;외부 API와 의존성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;오류 처리와 로딩 전략&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;구현 제약과 미결정 사항&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;공통 컴포넌트 정의&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;컴포넌트마다 다음 항목을 기록한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;이름&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;코드와 디자인에서 공통으로 사용할 이름&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;목적&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;사용자가 이 컴포넌트로 수행하는 작업&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;변형&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;크기, 강조 수준, 용도별 형태&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;상태&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;기본, 호버, 포커스, 비활성, 로딩, 오류&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;콘텐츠 규칙&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;허용 길이, 줄바꿈, 생략 방식&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;접근성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;키보드 조작, 레이블, 대비 등&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;데이터 스키마 정의&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;데이터 모델마다 다음 항목을 명시한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;엔터티 이름과 역할&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;필드명, 데이터 형식, 필수 여부&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;고유값과 기본값&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;엔터티 간 관계&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;생성&amp;middot;수정&amp;middot;삭제 규칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공개 정보와 비공개 정보&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;정렬과 검색에 필요한 인덱스&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;스키마가 확정되지 않은 항목은 추정하여 채우지 않고 &lt;/span&gt;&lt;span&gt;미결정&lt;/span&gt;&lt;span&gt;으로 표시한 뒤 구현 전에 질문한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;모든 핵심 기능이 특정 화면 또는 사용자 흐름에 연결된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공통 컴포넌트의 이름과 상태가 정의되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;데이터 스키마가 화면의 입력&amp;middot;출력과 연결되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;미결정 사항과 결정 책임자가 구분되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.5 전체 화면과 상태 설계&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정상 상태뿐 아니라 실제 사용 중 나타날 수 있는 빈 화면, 오류, 권한, 긴 콘텐츠와 작은 화면까지 설계한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;TasteMaker의 화면 목록&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;랜딩 페이지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;공개 프로필&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;본인 프로필 편집&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;콘텐츠 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리뷰 작성&amp;middot;수정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전체 영화&amp;middot;게임 목록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;콘텐츠 상세 시트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회원가입과 온보딩&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자 핸들 선택&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;좋아하는 콘텐츠 6개 선택&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로필 공유&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;화면별 필수 상태&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;상태&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;확인할 내용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;기본&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;대표 데이터가 있을 때의 정상 화면&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;빈 화면&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;데이터가 없을 때의 안내와 다음 행동&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;최초 가입&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;사용자가 처음 진입했을 때의 안내&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;로딩&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;대기 중인 영역과 사용자 조작 가능 범위&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;오류&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;오류 설명, 재시도, 안전한 이탈 방법&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;검색 결과 없음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;검색어 수정 또는 초기화 방법&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;저장 전&amp;middot;후&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;미저장 변경과 저장 성공 여부&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;권한 없음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;접근 제한 이유와 가능한 다음 행동&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;긴 콘텐츠&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;긴 제목, 리뷰, 사용자 이름 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;작은 화면&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;모바일 배치, 축약, 탐색 방식&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;화면 명세 템플릿&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;화면 이름:
사용자 목표:
진입 조건:
주요 데이터:
주요 행동:
공통 컴포넌트:
정상 상태:
빈 상태:
로딩 상태:
오류 상태:
권한 조건:
모바일 규칙:
완료 조건:&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;모든 핵심 사용자 흐름에 시작점과 종료점이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;각 화면에 필요한 데이터와 사용자 행동이 정의되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;빈 상태, 로딩, 오류, 권한 없음 상태를 확인했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;긴 콘텐츠와 모바일 화면에서 레이아웃이 깨지지 않는 기준이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.6 구현 전 검토와 실제 개발&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;목표&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코딩 에이전트가 임의로 결정해야 하는 부분을 줄이고, 합의된 명세와 디자인을 코드로 옮긴다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;입력 파일&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt;: 제품, 디자인, 기술 요구사항&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;design.html&lt;/span&gt;&lt;span&gt;: 전체 화면과 상태의 목업&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;구현 전 요청 예시&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;spec.html과 design.html을 검토하세요.
아직 구현하지 말고, 두 문서가 충돌하거나 구현에 필요한 정보가
빠진 부분을 질문 목록으로 정리하세요.

각 질문에는 다음 내용을 포함하세요.
1. 관련 화면 또는 요구사항
2. 현재 모호한 점
3. 결정하지 않았을 때 구현에 미치는 영향
4. 선택 가능한 대안&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;구현 순서&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;코딩 에이전트의 질문에 답하고 미결정 사항을 확정한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;핵심 사용자 흐름부터 수직으로 구현한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;로컬 환경에서 화면과 동작을 확인한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;디자인과 다른 부분, 누락 기능, 상태 처리를 수정한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인증과 데이터베이스를 연결한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;실제 데이터의 생성, 조회, 수정, 삭제를 확인한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;변경된 요구사항을 &lt;/span&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt;과 &lt;/span&gt;&lt;span&gt;design.html&lt;/span&gt;&lt;span&gt;에 반영한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;검수 항목&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;리뷰 영역 등 필수 콘텐츠가 빠지지 않았는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;카드의 호버 상태처럼 상호작용 조건이 디자인과 일치하는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;좌우 이동, 정렬, 필터 등 탐색 동작이 요구사항과 일치하는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;여백, 글꼴, 정렬, 반응형 동작이 디자인과 일치하는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인증 상태에 따라 접근 권한이 올바르게 달라지는가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;입력한 데이터가 실제 데이터베이스에 저장되고 다시 조회되는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;완료 기준&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;구현 전 질문에 답했으며 남은 미결정 사항이 표시되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;핵심 사용자 흐름을 로컬 환경에서 처음부터 끝까지 실행할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;요구사항, 디자인, 구현의 차이를 기록하고 해소했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;코드 변경으로 요구사항이 달라졌다면 문서도 함께 갱신했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5. 산출물 간 추적 관계&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;각 산출물은 독립 문서가 아니라 다음 단계의 입력이다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;산출물&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;포함할 내용&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;다음 단계에서의 용도&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;문제 정의&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;사용자, 상황, 문제, 기대 변화&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;기능과 우선순위 판단&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;디자인 원칙과 시각 규칙&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;핵심 화면 시안 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;핵심 화면&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;선택한 레이아웃과 콘텐츠 구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;상세 요구사항 발견&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;제품&amp;middot;디자인&amp;middot;기술 명세&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;전체 화면 설계와 구현 기준&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;design.html&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;전체 화면과 상태&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;구현 결과의 시각적 기준&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;애플리케이션 코드&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;실제 동작&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;테스트와 배포 대상&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;요구사항이 바뀌면 영향을 받는 산출물을 함께 수정한다. 예를 들어 프로필에서 즐겨찾기 개수가 6개에서 8개로 바뀌었다면 Product 요구사항, 컴포넌트 규칙, 화면 목업, 데이터 검증 규칙과 구현 코드를 모두 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6. 단계별 품질 게이트&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 단계로 넘어가기 전에 각 질문에 답한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;문제 정의 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;대상 사용자가 구체적인가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용 상황과 문제가 한 문장으로 설명되는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기능이 아니라 사용자의 변화를 중심으로 작성했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;디자인 방향 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;레퍼런스의 채택&amp;middot;제외 기준이 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;색상, 글꼴, 간격, 컴포넌트 원칙이 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제품 콘텐츠가 시각적 장식보다 우선하는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;핵심 화면 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;최소 두 가지 이상의 대안을 비교했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;선택 이유를 설명할 수 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;불필요한 요소를 제거했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;통합 명세 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기능이 화면과 사용자 흐름에 연결되는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공통 컴포넌트와 상태가 정의되어 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;데이터 스키마와 권한 규칙이 정의되어 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;미결정 사항을 질문 목록으로 분리했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;전체 디자인 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정상&amp;middot;빈&amp;middot;로딩&amp;middot;오류 상태가 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;권한 없음과 검색 결과 없음 상태가 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;긴 콘텐츠와 모바일 화면을 확인했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;구현 완료&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;핵심 흐름을 실제로 실행했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;저장한 데이터를 다시 조회할 수 있는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;명세와 디자인의 차이를 수정했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-checked=&quot;false&quot; data-task-list-item=&quot;true&quot;&gt;&lt;span&gt;&lt;/span&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;변경된 코드와 문서를 동기화했는가?&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;7. 자주 발생하는 실패와 대응&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;추상적인 디자인 요청&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;증상:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 화면마다 전형적인 색상과 구성요소가 반복되고 제품의 개성이 드러나지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;확인:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;design.md&lt;/span&gt;&lt;span&gt;에 콘텐츠 우선순위, 제외할 스타일, 컴포넌트 규칙이 있는지 점검한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;대응:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 실제 레퍼런스에서 채택할 원칙을 뽑고 색상, 간격, 밀도와 컴포넌트 상태를 명시한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;첫 번째 시안을 완성품으로 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;증상:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 불필요한 버튼, 배지, 문구가 남고 콘텐츠 구조가 사용자 목표와 맞지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;확인:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 서로 다른 구조의 대안을 비교했는지, 삭제 중심의 피드백을 진행했는지 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;대응:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 레이아웃 대안을 만든 뒤 콘텐츠 우선순위, 정보 밀도와 사용자 행동을 기준으로 하나를 선택한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;정상 화면만 설계&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;증상:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 실제 데이터가 없거나 오류가 발생했을 때 화면과 다음 행동이 정의되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;확인:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 화면 명세에 빈 상태, 로딩, 오류, 권한과 모바일 항목이 있는지 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;대응:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 상태 매트릭스를 만들고 각 상태의 메시지, 행동과 복구 경로를 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;화면마다 다른 컴포넌트 생성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;증상:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 같은 역할의 버튼과 카드가 화면마다 다른 크기, 색상, 동작을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;확인:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 컴포넌트 이름, 변형과 상태가 &lt;/span&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt;에 정의되어 있는지 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;대응:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 공통 컴포넌트 목록을 만들고 디자인과 코드에서 같은 이름을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;문서와 코드 불일치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;증상:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 구현은 변경됐지만 명세나 목업에는 이전 동작이 남아 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;확인:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 변경 사항이 제품 요구사항, 디자인, 데이터와 코드 중 어디에 영향을 주는지 추적한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;대응:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 구현 작업의 완료 조건에 관련 문서 갱신을 포함한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;8. 운영 원칙&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;기획에 충분한 시간 배정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제공된 영상은 전체 제작 시간의 최소 50%를 사전 기획에 투자하라고 제안한다. 이 비율은 이 문서에서 독립적으로 검증하지 않았다. 실무에서는 데이터베이스 구조, 인증 방식, 화면 구조, 핵심 사용자 흐름과 공통 컴포넌트처럼 변경 영향이 큰 결정을 구현 전에 우선 검토한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;구체적인 반복 피드백&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AI의 첫 결과는 검토할 초안으로 취급한다. 피드백에는 대상, 문제, 변경 내용과 확인 기준을 포함한다. 시각적 취향만 말하지 않고 사용자 목표와 제품 원칙을 근거로 판단한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;단일 기준 문서 유지&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;spec.html&lt;/span&gt;&lt;span&gt;과 &lt;/span&gt;&lt;span&gt;design.html&lt;/span&gt;&lt;span&gt;을 구현의 기준으로 사용한다. 코드에서 요구사항이 바뀌면 관련 문서도 같은 작업 범위에서 수정한다. 문서와 코드가 충돌하면 어느 쪽이 최신 결정인지 확인한 뒤 한쪽을 임의로 덮어쓰지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;9. 최종 완료 정의&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 조건을 모두 충족하면 제작 프로세스가 완료된 것으로 판단한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;해결할 사용자 문제와 대상 사용자가 명확하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;선택한 디자인 방향과 선택 이유가 문서화되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전체 기능, 화면, 상태, 컴포넌트와 데이터 스키마가 정의되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;구현 전에 모호한 요구사항을 질문하고 결정했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;핵심 사용자 흐름이 실제 환경에서 동작한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;빈 상태, 로딩, 오류, 권한과 모바일 화면을 검증했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;명세, 디자인과 코드가 같은 요구사항을 반영한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;배포 전 테스트 결과와 남은 미검증 항목이 기록되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;10. 결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AI 네이티브 앱 제작은 AI에게 한 번의 명령으로 완성품을 요청하는 과정이 아니다. 사용자 문제를 정의하고, 디자인의 판단 기준을 만들고, 핵심 화면으로 제품 구조를 탐색한 뒤, 제품&amp;middot;디자인&amp;middot;기술 명세를 구현 가능한 수준으로 연결하는 과정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AI는 대안 생성과 구현 속도를 높이는 역할을 맡는다. 사람은 어떤 문제를 해결할지, 어떤 화면이 더 적합한지, 무엇을 삭제할지, 어떤 데이터 구조와 품질 기준을 채택할지 결정한다. 따라서 최종 품질은 코드 생성 속도뿐 아니라 기획, 디자인 판단, 명세 작성과 검수 능력에 달려 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1798</guid>
      <comments>https://javaexpert.tistory.com/1798#entry1798comment</comments>
      <pubDate>Mon, 3 Aug 2026 10:14:31 +0900</pubDate>
    </item>
    <item>
      <title>Codex에서 GPT-5.6 Luna를 서브에이전트로 사용하는 방법</title>
      <link>https://javaexpert.tistory.com/1797</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서는 GPT-5.6 Sol을 메인 오케스트레이터로 사용하면서 GPT-5.6 Luna를 서브에이전트로 실행하는 로컬 설정을 기록한 운영 가이드다. Codex 업데이트나 설정 초기화 후 Luna 서브에이전트가 보이지 않을 때 이 문서를 따라 다시 설정하고 검증한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재 확인된 정상 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 8월 1일 기준으로 다음 구성이 실제 실행까지 확인됐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 모델: &lt;code&gt;gpt-5.6-sol&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;서브에이전트 모델: &lt;code&gt;gpt-5.6-luna&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Luna 다중 에이전트 프로토콜: &lt;code&gt;v2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Luna 추론 수준: &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;실제 검증 결과: Luna 자식 에이전트가 생성되고 &lt;code&gt;LUNA_SUBAGENT_OK&lt;/code&gt;를 반환함&lt;/li&gt;
&lt;li&gt;검증 당시 데스크톱 내장 Codex: &lt;code&gt;0.146.0-alpha.9.2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;검증 당시 터미널 Codex: &lt;code&gt;0.146.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하기 전에: Codex 경로 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서의 명령은 macOS와 Linux의 Bash/Zsh 셸을 기준으로 한다. 먼저 현재 터미널에서 Codex 데이터 디렉터리를 &lt;code&gt;CODEX_DIR&lt;/code&gt; 변수로 지정한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export CODEX_DIR=&quot;${CODEX_HOME:-$HOME/.codex}&quot;
printf 'Codex directory: %s\n' &quot;$CODEX_DIR&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;CODEX_HOME&lt;/code&gt;을 따로 설정했다면 그 경로를 사용한다.&lt;/li&gt;
&lt;li&gt;별도 설정이 없다면 현재 사용자의 기본 경로인 &lt;code&gt;$HOME/.codex&lt;/code&gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;이후 터미널을 새로 열었다면 위 명령을 다시 실행한다.&lt;/li&gt;
&lt;li&gt;Windows에서는 Codex 데이터 디렉터리를 확인한 뒤 PowerShell 환경 변수와 경로 표기법에 맞게 명령을 바꿔야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서에서 사용하는 파일은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;$CODEX_DIR/config.toml
$CODEX_DIR/models_cache.json
$CODEX_DIR/models-luna-v2.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;models_cache.json&lt;/code&gt;은 Codex가 내려받은 원본 모델 카탈로그다. 이 파일을 직접 수정하지 않고 복사본인 &lt;code&gt;models-luna-v2.json&lt;/code&gt;에서 Luna 항목만 &lt;code&gt;v2&lt;/code&gt;로 바꾼다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정은 로컬 모델 카탈로그 오버라이드다. Codex 업데이트로 공식 카탈로그나 서브에이전트 동작 방식이 바뀌면 더 이상 필요하지 않거나 작동하지 않을 수 있다. 업데이트 후에는 반드시 실제 자식 에이전트 실행까지 확인한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 현재 설정 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 다음 명령을 실행한다.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;grep -nE 'model_catalog_json|multi_agent' &quot;$CODEX_DIR/config.toml&quot;

jq '.models[]
  | select(.slug == &quot;gpt-5.6-luna&quot;)
  | {
      slug,
      multi_agent_version,
      supported_reasoning_levels
    }' &quot;$CODEX_DIR/models-luna-v2.json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상이라면 다음 두 조건을 만족해야 한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;model_catalog_json = &quot;/absolute/path/to/codex-home/models-luna-v2.json&quot;
multi_agent = true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luna 모델 정보에는 다음 값이 있어야 한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;slug&quot;: &quot;gpt-5.6-luna&quot;,
  &quot;multi_agent_version&quot;: &quot;v2&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일이 없거나 값이 다르면 다음 절차로 다시 만든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Luna V2 모델 카탈로그 다시 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 원본 모델 캐시와 현재 설정을 백업한다.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;mkdir -p &quot;$CODEX_DIR/backups/luna-v2-setup&quot;

cp &quot;$CODEX_DIR/models_cache.json&quot; \
   &quot;$CODEX_DIR/backups/luna-v2-setup/models_cache.json&quot;

cp &quot;$CODEX_DIR/config.toml&quot; \
   &quot;$CODEX_DIR/backups/luna-v2-setup/config.toml&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 카탈로그를 복사하고 Luna 항목만 &lt;code&gt;v2&lt;/code&gt;로 변경한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cp &quot;$CODEX_DIR/models_cache.json&quot; \
   &quot;$CODEX_DIR/models-luna-v2.json&quot;

python3 - &amp;lt;&amp;lt;'PY'
import json
import os
from pathlib import Path

codex_dir = Path(os.environ[&quot;CODEX_DIR&quot;]).expanduser().resolve()
catalog_path = codex_dir / &quot;models-luna-v2.json&quot;
data = json.loads(catalog_path.read_text(encoding=&quot;utf-8&quot;))
models = data.get(&quot;models&quot;, [])

matches = [
    model
    for model in models
    if model.get(&quot;slug&quot;) == &quot;gpt-5.6-luna&quot;
]

if len(matches) != 1:
    raise SystemExit(
        f&quot;gpt-5.6-luna 항목이 정확히 1개여야 합니다. 현재: {len(matches)}개&quot;
    )

matches[0][&quot;multi_agent_version&quot;] = &quot;v2&quot;

temporary_path = catalog_path.with_suffix(&quot;.json.tmp&quot;)
temporary_path.write_text(
    json.dumps(data, ensure_ascii=False, indent=2) + &quot;\n&quot;,
    encoding=&quot;utf-8&quot;,
)
temporary_path.replace(catalog_path)

print(&quot;gpt-5.6-luna multi_agent_version=v2 설정 완료&quot;)
PY&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령이 Luna 항목을 찾지 못하면 작업을 중단한다. 다른 모델을 Luna로 추정해 바꾸면 안 된다. 먼저 Codex를 업데이트하거나 &lt;code&gt;models_cache.json&lt;/code&gt;에 Luna가 제공되는지 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. &lt;code&gt;config.toml&lt;/code&gt; 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;$CODEX_DIR/config.toml&lt;/code&gt;의 최상위 영역에 다음 설정을 둔다. 첫 번째 &lt;code&gt;[섹션]&lt;/code&gt;이 시작되기 전에 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 현재 사용자에게 맞는 설정 한 줄을 출력한다.&lt;/p&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;printf 'model_catalog_json = &quot;%s/models-luna-v2.json&quot;\n' &quot;$CODEX_DIR&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력된 절대경로를 복사해 &lt;code&gt;config.toml&lt;/code&gt;에 넣는다. 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;model = &quot;gpt-5.6-sol&quot;
model_catalog_json = &quot;/absolute/path/to/codex-home/models-luna-v2.json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 &lt;code&gt;[features]&lt;/code&gt; 섹션에는 다음 값을 추가하거나 확인한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;[features]
multi_agent = true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의 사항:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;model_catalog_json&lt;/code&gt;을 &lt;code&gt;[features]&lt;/code&gt;나 &lt;code&gt;[projects]&lt;/code&gt; 아래에 넣으면 안 된다.&lt;/li&gt;
&lt;li&gt;TOML 문자열 안에서는 &lt;code&gt;$HOME&lt;/code&gt;이나 &lt;code&gt;$CODEX_DIR&lt;/code&gt; 같은 셸 변수가 자동으로 확장되지 않는다. 반드시 앞의 &lt;code&gt;printf&lt;/code&gt; 명령으로 확인한 절대경로를 넣는다.&lt;/li&gt;
&lt;li&gt;같은 키를 두 번 선언하면 TOML 로딩 오류가 날 수 있다.&lt;/li&gt;
&lt;li&gt;기존 &lt;code&gt;[features]&lt;/code&gt; 섹션이 있으면 새 섹션을 하나 더 만들지 말고 그 안에 추가한다.&lt;/li&gt;
&lt;li&gt;메인 모델을 Sol로 유지하려면 &lt;code&gt;model = &quot;gpt-5.6-sol&quot;&lt;/code&gt;을 그대로 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 저장한 뒤 파싱 상태를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;codex doctor --summary&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;config.load&lt;/code&gt;가 실패한다면 앱을 재시작하기 전에 &lt;code&gt;config.toml&lt;/code&gt;의 중복 키와 섹션 위치부터 고친다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Codex 완전 재시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 Codex는 시작 시 읽은 모델 카탈로그와 도구 스키마를 계속 사용할 수 있다. 설정 파일만 바꿔서는 이미 열린 작업에 Luna가 나타나지 않을 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;진행 중인 작업을 마친다.&lt;/li&gt;
&lt;li&gt;ChatGPT/Codex 데스크톱 앱을 &lt;code&gt;⌘Q&lt;/code&gt;로 완전히 종료한다.&lt;/li&gt;
&lt;li&gt;터미널에서 별도 &lt;code&gt;codex&lt;/code&gt; 프로세스가 실행 중이면 함께 종료한다.&lt;/li&gt;
&lt;li&gt;데스크톱 앱을 다시 실행한다.&lt;/li&gt;
&lt;li&gt;기존 작업이 아니라 새 작업에서 Luna 서브에이전트를 검증한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강제 종료나 macOS 재부팅은 필수 조건이 아니다. 앱을 정상적으로 완전히 종료했다가 다시 여는 것으로 충분하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Luna 서브에이전트 실제 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luna를 확실히 사용하려면 서브에이전트 생성 시 모델을 명시해야 한다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;model: gpt-5.6-luna
reasoning_effort: max
fork_turns: none&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fork_turns&lt;/code&gt;는 &lt;code&gt;none&lt;/code&gt; 또는 최근 턴 수처럼 제한된 값을 사용한다. 전체 대화 기록을 상속하는 생성 방식은 부모 모델과 추론 수준을 그대로 물려받으므로 Luna 모델 오버라이드를 적용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 Codex에 요청할 때는 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;메인 Sol이 작업을 조율하고, 실제 하위 작업은
gpt-5.6-luna / reasoning max 서브에이전트로 실행해 줘.
Luna 생성 시 모델을 명시하고 fork_turns는 none 또는 제한된 턴 수를 사용해 줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luna는 &lt;code&gt;max&lt;/code&gt;까지 지원한다. &lt;code&gt;ultra&lt;/code&gt;를 지정하면 모델 검증 단계에서 거부될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 빠른 스모크 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 작업에서 다음과 같이 요청한다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;gpt-5.6-luna, reasoning max로 읽기 전용 서브에이전트 한 개를 생성해 줘.
그 에이전트는 다른 도구를 사용하지 말고 LUNA_SUBAGENT_OK만 반환하게 해 줘.
실행 후 자식 세션의 실제 model과 reasoning_effort도 확인해 줘.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공 판정은 다음 세 조건을 모두 만족해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 생성 요청이 모델 검증 단계에서 거부되지 않는다.&lt;/li&gt;
&lt;li&gt;자식이 &lt;code&gt;LUNA_SUBAGENT_OK&lt;/code&gt;를 반환한다.&lt;/li&gt;
&lt;li&gt;자식 rollout의 &lt;code&gt;turn_context&lt;/code&gt;에 &lt;code&gt;model=&quot;gpt-5.6-luna&quot;&lt;/code&gt;와 &lt;code&gt;reasoning_effort=&quot;max&quot;&lt;/code&gt;가 기록된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 자식 세션에서 모델 기록을 찾으려면 다음 명령을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;grep -R -l '&quot;model&quot;:&quot;gpt-5.6-luna&quot;' \
  &quot;$CODEX_DIR/sessions&quot; |
  tail -n 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾은 rollout 파일에서 &lt;code&gt;turn_context&lt;/code&gt;를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;jq -c '
  select(.type == &quot;turn_context&quot;)
  | {
      model: .payload.model,
      reasoning_effort:
        .payload.collaboration_mode.settings.reasoning_effort
    }
' /절대/경로/rollout-파일.jsonl&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 결과 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;model&quot;: &quot;gpt-5.6-luna&quot;,
  &quot;reasoning_effort&quot;: &quot;max&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트가 자기 모델을 말로 주장하는 것만으로는 충분하지 않다. 반드시 자식 세션의 &lt;code&gt;turn_context&lt;/code&gt;를 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 문제가 생겼을 때&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;gpt-5.6-luna&lt;/code&gt;가 허용 모델 목록에 없음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 항목을 순서대로 확인한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;model_catalog_json&lt;/code&gt; 경로가 정확한가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;models-luna-v2.json&lt;/code&gt;에 Luna 항목이 있는가&lt;/li&gt;
&lt;li&gt;Luna의 &lt;code&gt;multi_agent_version&lt;/code&gt;이 &lt;code&gt;v2&lt;/code&gt;인가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[features]&lt;/code&gt;의 &lt;code&gt;multi_agent&lt;/code&gt;가 &lt;code&gt;true&lt;/code&gt;인가&lt;/li&gt;
&lt;li&gt;설정 변경 후 앱을 완전히 재시작했는가&lt;/li&gt;
&lt;li&gt;재시작 후 만든 새 작업에서 테스트했는가&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Luna를 지정했는데 Sol 자식이 실행됨&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 원인이 가장 흔하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 요청에서 &lt;code&gt;model&lt;/code&gt;을 생략함&lt;/li&gt;
&lt;li&gt;부모의 전체 대화 기록을 그대로 상속함&lt;/li&gt;
&lt;li&gt;자동 오케스트레이션에 맡기고 Luna 사용을 명시하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luna를 강제하려면 &lt;code&gt;model=gpt-5.6-luna&lt;/code&gt;와 제한된 &lt;code&gt;fork_turns&lt;/code&gt;를 함께 지정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;reasoning_effort&lt;/code&gt;가 거부됨&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Luna에는 &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt; 중 하나를 사용한다. &lt;code&gt;ultra&lt;/code&gt;는 사용하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;업데이트 후 다시 작동하지 않음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트된 &lt;code&gt;$CODEX_DIR/models_cache.json&lt;/code&gt;에서 Luna의 공식 &lt;code&gt;multi_agent_version&lt;/code&gt;을 먼저 확인한다.&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;jq '.models[]
  | select(.slug == &quot;gpt-5.6-luna&quot;)
  | {
      slug,
      multi_agent_version,
      supported_reasoning_levels
    }' &quot;$CODEX_DIR/models_cache.json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공식 값이 이미 &lt;code&gt;v2&lt;/code&gt;라면 별도 오버라이드가 필요하지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;공식 값이 &lt;code&gt;v1&lt;/code&gt;이면 이 문서의 절차로 별도 카탈로그를 다시 만든다.&lt;/li&gt;
&lt;li&gt;Luna 항목 자체가 없으면 임의로 모델 항목을 만들지 않는다.&lt;/li&gt;
&lt;li&gt;모델은 보이지만 생성 도구가 거부한다면 현재 Codex 버전의 서브에이전트 도구 스키마가 Luna를 허용하는지 확인한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 원상 복구&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 오버라이드를 제거하려면 앱을 완전히 종료한 뒤 &lt;code&gt;config.toml&lt;/code&gt;에서 다음 줄을 삭제한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;model_catalog_json = &quot;/absolute/path/to/codex-home/models-luna-v2.json&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;models-luna-v2.json&lt;/code&gt;은 즉시 삭제하지 말고 보관해도 된다. 그다음 앱을 다시 실행하면 Codex가 기본 모델 카탈로그를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업한 설정으로 되돌려야 할 때는 앱을 종료한 상태에서 백업 내용을 확인한 뒤 복원한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;cp &quot;$CODEX_DIR/backups/luna-v2-setup/config.toml&quot; \
   &quot;$CODEX_DIR/config.toml&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복원 후에는 &lt;code&gt;codex doctor --summary&lt;/code&gt;로 설정 파싱 여부를 확인하고 앱을 다시 실행한다.&lt;/p&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1797</guid>
      <comments>https://javaexpert.tistory.com/1797#entry1797comment</comments>
      <pubDate>Sat, 1 Aug 2026 18:40:46 +0900</pubDate>
    </item>
    <item>
      <title>PixelRAG: PDF와 이미지를 화면 그대로 검색하는 시각 자료용 RAG</title>
      <link>https://javaexpert.tistory.com/1796</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 PDF, 이미지, 웹페이지를 텍스트로 변환하지 않고 &lt;b&gt;화면 이미지 자체를 검색 대상으로 사용하는 시각 자료용 RAG(Retrieval-Augmented Generation) 프로젝트&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 문서 RAG는 PDF에서 텍스트를 추출한 뒤 문단 단위로 나누어 검색합니다. 반면 PixelRAG는 문서 페이지를 이미지로 저장하고, 비전&amp;middot;언어 임베딩 모델을 이용해 질문과 관련된 페이지를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 다음과 같이 시각적 구조가 중요한 문서에 적합합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표와 차트가 많은 보고서&lt;/li&gt;
&lt;li&gt;그림과 설명이 결합된 교재&lt;/li&gt;
&lt;li&gt;PPT를 변환한 PDF&lt;/li&gt;
&lt;li&gt;인포그래픽&lt;/li&gt;
&lt;li&gt;제품 매뉴얼&lt;/li&gt;
&lt;li&gt;스캔 문서&lt;/li&gt;
&lt;li&gt;시스템 구성도와 설계 도면&lt;/li&gt;
&lt;li&gt;웹페이지 UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 완성된 문서 채팅 서비스라기보다, 이러한 자료를 검색하기 위한 &lt;b&gt;픽셀 기반 RAG 파이프라인&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 핵심 처리 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG의 기본 처리 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;PDF&amp;middot;이미지&amp;middot;웹페이지 입력
          &amp;darr;
페이지를 스크린샷 또는 이미지 타일로 변환
          &amp;darr;
Qwen3-VL-Embedding-2B로 이미지 임베딩 생성
          &amp;darr;
FAISS 또는 Qdrant에 벡터 저장
          &amp;darr;
사용자 질문을 벡터로 변환
          &amp;darr;
질문과 관련된 페이지 이미지 검색
          &amp;darr;
검색된 이미지를 비전 언어 모델이 분석
          &amp;darr;
답변과 출처 페이지 제공
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 문서를 OCR 텍스트로 변환한 뒤 검색하는 것이 아니라, &lt;b&gt;문서의 시각적 표현을 유지한 상태에서 검색한다는 점&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 일반 문서 RAG와의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분일반 문서 RAGPixelRAG&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문서 처리&lt;/td&gt;
&lt;td&gt;PDF에서 텍스트 추출&lt;/td&gt;
&lt;td&gt;PDF를 페이지 이미지로 변환&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;표&amp;middot;차트 처리&lt;/td&gt;
&lt;td&gt;추출 과정에서 구조가 깨질 수 있음&lt;/td&gt;
&lt;td&gt;원래 배치와 형태 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR 의존성&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;교재&amp;middot;보고서&amp;middot;도면&amp;middot;인포그래픽&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 data-ke-size=&quot;size16&quot;&gt;일반 RAG에서는 표의 행과 열, 이미지와 설명의 위치 관계, 페이지 레이아웃 등이 텍스트 추출 과정에서 사라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 페이지를 이미지로 다루기 때문에 이러한 시각적 관계를 유지할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 검색 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 PDF가 있다고 가정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지내용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1페이지&lt;/td&gt;
&lt;td&gt;회사 소개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2페이지&lt;/td&gt;
&lt;td&gt;PixelRAG 시스템 구성도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3페이지&lt;/td&gt;
&lt;td&gt;매출 현황 표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4페이지&lt;/td&gt;
&lt;td&gt;제품 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 다음과 같이 질문합니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;PixelRAG 시스템 구조도를 보여줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 OCR로 추출한 문장을 찾는 대신, 질문과 시각적으로 관련된 &lt;b&gt;2페이지 이미지를 검색 결과로 반환&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 예제에서도 PDF를 인덱싱한 후 다음과 같은 질문으로 구성도가 포함된 페이지를 검색합니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Overview of PixelRAG and the diagram
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 PixelRAG가 담당하는 핵심 역할은 다음과 같이 정리할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 질문과 가장 관련된 PDF 페이지 또는 이미지 영역을 시각적으로 검색한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. PDF를 업로드하면 바로 질문할 수 있는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 구현할 수 있지만, 질문하기 전에 &lt;b&gt;문서 인덱싱 과정&lt;/b&gt;이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 사용자가 질문할 때마다 PDF 전체를 처음부터 분석하지 않습니다. 문서를 최초로 등록할 때 페이지별 임베딩을 생성하고, 이후에는 만들어진 벡터 인덱스를 이용해 관련 페이지를 검색합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 작업 순서는 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 입력 문서 지정&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;source:
  type: local
  path: ./paper.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 이미지 임베딩 모델 지정&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;embed:
  model: Qwen/Qwen3-VL-Embedding-2B
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.3 인덱스 생성&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;pixelrag index build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4 검색 API 실행&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;pixelrag serve \
  --index-dir ./paper_index \
  --port 30001
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 생성이 완료되면 애플리케이션은 사용자의 질문을 검색 API로 전달하고, 관련성이 높은 페이지 이미지를 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 README에 기재된 예시 기준으로 샘플 PDF 인덱싱 시간은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Apple M 시리즈: 약 3분&lt;/li&gt;
&lt;li&gt;GPU 환경: 약 1분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 크기, 페이지 수, 이미지 해상도, 하드웨어 사양에 따라 실제 처리 시간은 달라질 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. PixelRAG가 담당하는 범위&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG의 주요 역할은 &lt;b&gt;관련 이미지 검색&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색된 페이지를 읽고 자연어 답변을 생성하려면 별도의 비전 언어 모델(VLM)을 연결해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 모델을 답변 생성 단계에 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Qwen3-VL&lt;/li&gt;
&lt;li&gt;GPT 비전 지원 모델&lt;/li&gt;
&lt;li&gt;Claude 비전 지원 모델&lt;/li&gt;
&lt;li&gt;기타 이미지 입력을 지원하는 멀티모달 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 문서 채팅 시스템에서는 PixelRAG와 답변 생성 모델의 역할이 구분됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 요소담당 역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PixelRAG&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비전 언어 모델&lt;/td&gt;
&lt;td&gt;검색된 이미지의 내용 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 문서 채팅 서비스 구성 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatPDF와 같은 서비스를 만들려면 PixelRAG 외에 업로드, 작업 관리, 답변 생성, 출처 표시 기능을 추가해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Next.js 파일 업로드 화면
          &amp;darr;
PDF 원본 저장
          &amp;darr;
인덱싱 작업 등록
          &amp;darr;
페이지 이미지 생성
          &amp;darr;
PixelRAG 임베딩 및 인덱스 생성
          &amp;darr;
사용자 질문 입력
          &amp;darr;
관련 페이지 이미지 검색
          &amp;darr;
비전 언어 모델이 페이지 분석
          &amp;darr;
답변과 PDF 페이지 출처 표시
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필요한 주요 구성 요소&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 업로드 기능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;사용자가 PDF나 이미지를 업로드할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원본 파일 저장소&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;업로드된 문서를 로컬 디스크나 객체 스토리지에 보관합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 인덱싱 작업&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;파일 업로드 후 백그라운드 작업으로 페이지 변환과 임베딩 생성을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;벡터 데이터베이스&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;소규모 서비스는 FAISS, 다중 사용자 서비스는 Qdrant와 같은 벡터 데이터베이스를 검토할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검색 API&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;사용자 질문을 받아 관련 페이지와 이미지 영역을 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;답변 생성 모델&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;검색된 페이지 이미지를 읽고 질문에 맞는 답변을 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출처 표시 기능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;답변과 함께 문서명, 페이지 번호, 원본 페이지 이미지를 표시합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 적합한 활용 사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 다음과 같은 서비스에 활용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교육 자료 검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교재의 그림, 문법표, 수학 도형, 과학 실험 이미지가 포함된 페이지를 검색할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보고서 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매출 그래프, 통계표, 조직도, 비교 차트가 포함된 페이지를 찾아 분석할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 문서 검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 구성도, 네트워크 다이어그램, UI 화면, 설치 절차가 포함된 페이지를 검색할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스캔 문서 검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 추출 품질이 낮은 스캔 문서를 페이지 이미지 상태로 검색할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹페이지 UI 검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지를 화면 이미지로 수집한 뒤 특정 메뉴, 버튼, 카드 또는 화면 구성을 검색하는 데 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 고려해야 할 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG를 실제 서비스에 적용할 때는 다음 항목을 함께 검토해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서별 인덱싱 시간&lt;/li&gt;
&lt;li&gt;페이지 이미지 저장 공간&lt;/li&gt;
&lt;li&gt;GPU 또는 Apple Silicon 사용 여부&lt;/li&gt;
&lt;li&gt;임베딩 모델의 메모리 요구량&lt;/li&gt;
&lt;li&gt;여러 사용자의 인덱스 분리 방식&lt;/li&gt;
&lt;li&gt;질문별 검색 결과 수&lt;/li&gt;
&lt;li&gt;비전 언어 모델 호출 비용&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 data-ke-size=&quot;size16&quot;&gt;특히 이미지 기반 답변은 검색된 페이지의 내용을 비전 모델이 다시 해석하는 구조이므로, 중요한 수치나 표 데이터는 원본 페이지와 함께 제공하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 PDF나 이미지를 OCR 텍스트로 변환해 검색하는 프로젝트가 아닙니다. 문서의 페이지와 화면 영역을 이미지 상태로 임베딩하고, 사용자 질문과 관련된 시각 자료를 검색하는 프로젝트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 이해할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PixelRAG는 PDF&amp;middot;이미지&amp;middot;웹페이지의 시각적 구조를 유지하면서, 질문과 관련된 페이지나 이미지 영역을 찾아주는 픽셀 기반 검색 시스템이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표, 차트, 교재, 인포그래픽, 시스템 구성도처럼 &lt;b&gt;텍스트만 추출하면 정보 구조가 손실되는 문서&lt;/b&gt;에서 특히 활용 가치가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 완성형 문서 채팅 서비스를 구축하려면 PixelRAG에 파일 업로드 UI, 문서 저장소, 인덱싱 작업 관리, 비전 언어 모델, 답변 생성 및 출처 표시 기능을 추가해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/StarTrail-org/PixelRAG&quot;&gt;PixelRAG GitHub 저장소&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/StarTrail-org/PixelRAG/blob/main/README.md&quot;&gt;PixelRAG README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/StarTrail-org/PixelRAG/blob/main/CLAUDE.md&quot;&gt;PixelRAG CLAUDE.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1796</guid>
      <comments>https://javaexpert.tistory.com/1796#entry1796comment</comments>
      <pubDate>Thu, 30 Jul 2026 11:33:10 +0900</pubDate>
    </item>
    <item>
      <title>LLM 추론 엔지니어링 튜토리얼</title>
      <link>https://javaexpert.tistory.com/1795</link>
      <description>&lt;h1&gt;LLM 추론 엔지니어링 튜토리얼&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미 만들어진 AI 모델을 GPU 서버에 올리고, 수많은 사용자에게 빠르고 안정적으로 제공하면서 비용까지 최적화하는 기술을 배우는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정을 마치면 다음 결과물을 갖게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vLLM과 SGLang으로 구축한 OpenAI 호환 추론 서버&lt;/li&gt;
&lt;li&gt;처리량 중심, 지연시간 중심, 긴 문맥 중심의 서빙 파이프라인 3종&lt;/li&gt;
&lt;li&gt;비용&amp;middot;지연시간&amp;middot;품질을 함께 고려하는 모델 라우터&lt;/li&gt;
&lt;li&gt;사용자별 토큰 예산 및 요청 제어 API&lt;/li&gt;
&lt;li&gt;Prometheus&amp;middot;Grafana 기반 운영 대시보드&lt;/li&gt;
&lt;li&gt;Kubernetes 자동 확장 환경&lt;/li&gt;
&lt;li&gt;1,000개 이상의 동시 요청을 재현하는 부하 시험&lt;/li&gt;
&lt;li&gt;재현 가능한 공개 벤치마크 보고서&lt;/li&gt;
&lt;li&gt;요청당 원가와 손익분기점을 계산하는 비용 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 독자&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python과 REST API의 기본 개념을 아는 개발자&lt;/li&gt;
&lt;li&gt;로컬 LLM 실행에서 실제 서비스 운영으로 넘어가려는 사람&lt;/li&gt;
&lt;li&gt;모델 연구보다 서빙&amp;middot;성능&amp;middot;비용 최적화에 관심이 있는 사람&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권장 학습 기간&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주 5일, 하루 1.5&lt;/p&gt;
&lt;p&gt;&lt;del&gt;2시간을 기준으로 &lt;b&gt;12주&lt;/b&gt;를 권장한다. GPU가 한 대여도 1&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8주차 대부분을 학습할 수 있다. 1,000 동시 요청과 Kubernetes 다중 GPU 실습은 클라우드 GPU를 짧게 빌려 수행해도 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;전체 학습 지도&lt;/h1&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th align=&quot;right&quot;&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;0&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2일&lt;/td&gt;
&lt;td&gt;환경과 측정 기준&lt;/td&gt;
&lt;td&gt;기준 성능표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;Ollama&amp;middot;LM Studio&amp;middot;LiteLLM&lt;/td&gt;
&lt;td&gt;로컬 API 게이트웨이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2주&lt;/td&gt;
&lt;td&gt;vLLM&amp;middot;SGLang&lt;/td&gt;
&lt;td&gt;서빙 파이프라인 2종&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;Paged Attention&amp;middot;KV 캐시&lt;/td&gt;
&lt;td&gt;긴 문맥 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;양자화&lt;/td&gt;
&lt;td&gt;정밀도별 비교표&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;추측 디코딩&lt;/td&gt;
&lt;td&gt;draft/target 비교 실험&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;라우터&amp;middot;토큰 예산&lt;/td&gt;
&lt;td&gt;정책 기반 라우터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;관측 가능성&lt;/td&gt;
&lt;td&gt;Grafana 대시보드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;부하 시험&lt;/td&gt;
&lt;td&gt;1,000 동시 요청 보고서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;Kubernetes&amp;middot;HPA&lt;/td&gt;
&lt;td&gt;자동 확장 배포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;ONNX&amp;middot;TensorRT&amp;middot;WebLLM&lt;/td&gt;
&lt;td&gt;엣지 데모&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1주&lt;/td&gt;
&lt;td&gt;비용 경제성&amp;middot;공개 벤치마크&lt;/td&gt;
&lt;td&gt;최종 포트폴리오&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;0. 준비와 기준선&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.1 권장 환경&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 학습용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ubuntu 22.04/24.04 또는 WSL2&lt;/li&gt;
&lt;li&gt;Python 3.11 또는 프로젝트가 지원하는 버전&lt;/li&gt;
&lt;li&gt;Docker와 Docker Compose&lt;/li&gt;
&lt;li&gt;RAM 64GB 권장, 최소 32GB&lt;/li&gt;
&lt;li&gt;SSD 여유 공간 200GB 이상 권장&lt;/li&gt;
&lt;li&gt;NVIDIA GPU 24GB 이상 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMD 32GB GPU도 개념 학습, Ollama, llama.cpp 계열, 일부 SGLang&amp;middot;ROCm 실험에 사용할 수 있다. 다만 CUDA 전용 커널, TensorRT-LLM, FP8 및 일부 양자화 경로는 동일하게 재현되지 않을 수 있다. 따라서 이 과정에서는 &lt;b&gt;AMD로 가능한 실습&lt;/b&gt;과 &lt;b&gt;CUDA 환경에서 별도로 확인할 실습&lt;/b&gt;을 구분한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라우드 실습용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 GPU: L4, L40S, A100, H100급 중 예산에 맞는 인스턴스&lt;/li&gt;
&lt;li&gt;다중 GPU: 마지막 2주에만 시간 단위로 대여&lt;/li&gt;
&lt;li&gt;Kubernetes: 로컬 &lt;code&gt;kind&lt;/code&gt;/&lt;code&gt;k3d&lt;/code&gt;로 구조를 익힌 뒤 GPU 노드에서 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.2 저장소 구조&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;llm-inference-lab/
├── README.md
├── models/
├── configs/
├── servers/
│   ├── vllm/
│   ├── sglang/
│   └── router/
├── loadtest/
├── observability/
│   ├── prometheus/
│   └── grafana/
├── kubernetes/
├── edge/
├── results/
└── reports/&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.3 가장 먼저 정의할 지표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;TTFT&lt;/td&gt;
&lt;td&gt;요청부터 첫 토큰까지 걸린 시간&lt;/td&gt;
&lt;td&gt;대화형 UX에 중요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ITL&lt;/td&gt;
&lt;td&gt;토큰 사이의 평균 지연시간&lt;/td&gt;
&lt;td&gt;스트리밍 체감 속도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT&lt;/td&gt;
&lt;td&gt;출력 토큰 1개당 처리시간&lt;/td&gt;
&lt;td&gt;ITL과 정의를 통일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2E latency&lt;/td&gt;
&lt;td&gt;요청 전체 완료 시간&lt;/td&gt;
&lt;td&gt;입력&amp;middot;출력 길이를 함께 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;초당 처리한 요청 또는 토큰&lt;/td&gt;
&lt;td&gt;요청/초와 토큰/초를 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goodput&lt;/td&gt;
&lt;td&gt;SLO를 만족한 유효 처리량&lt;/td&gt;
&lt;td&gt;단순 처리량보다 운영에 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue time&lt;/td&gt;
&lt;td&gt;대기열에서 기다린 시간&lt;/td&gt;
&lt;td&gt;GPU 계산 시간과 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache hit rate&lt;/td&gt;
&lt;td&gt;재사용된 prefix/KV 비율&lt;/td&gt;
&lt;td&gt;동일 프롬프트 비율과 함께 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;전체 요청 중 실패 비율&lt;/td&gt;
&lt;td&gt;타임아웃과 서버 오류를 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost/request&lt;/td&gt;
&lt;td&gt;요청 한 건의 추론 원가&lt;/td&gt;
&lt;td&gt;GPU 유휴시간도 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0.4 기준선 실험&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 모델, 동일한 데이터셋, 동일한 입력&amp;middot;출력 길이로 아래 표를 채운다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;| 엔진 | 모델 | 정밀도 | 동시성 | 입력/출력 토큰 | TTFT p50/p95 | ITL p50/p95 | tok/s | GPU 메모리 |
|---|---|---|---:|---:|---:|---:|---:|---:|&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;측정 스크립트를 두 번 실행해 결과 편차를 확인했다.&lt;/li&gt;
&lt;li&gt;모델명, revision, 엔진 버전, GPU, 드라이버를 기록했다.&lt;/li&gt;
&lt;li&gt;워밍업 요청을 측정값에서 분리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1. 로컬 도구로 추론 흐름 익히기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.1 Ollama&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 목표는 모델 다운로드, 메모리 적재, 프롬프트 요청, 스트리밍 응답의 흐름을 이해하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ollama pull qwen3:4b
ollama run qwen3:4b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 확인:&lt;/p&gt;
&lt;pre class=&quot;flix&quot;&gt;&lt;code&gt;curl http://localhost:11434/api/generate \
  -d '{&quot;model&quot;:&quot;qwen3:4b&quot;,&quot;prompt&quot;:&quot;KV cache를 한 문장으로 설명해 줘.&quot;,&quot;stream&quot;:false}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인할 항목:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 요청과 두 번째 요청의 시간 차이&lt;/li&gt;
&lt;li&gt;모델이 메모리에 남아 있을 때의 변화&lt;/li&gt;
&lt;li&gt;입력 길이가 늘어날 때 TTFT 변화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.2 LM Studio&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GUI에서 모델별 메모리 사용량과 오프로딩 설정을 비교한다. 로컬 서버 기능을 켠 뒤 OpenAI 호환 클라이언트로 호출한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from openai import OpenAI

client = OpenAI(base_url=&quot;http://localhost:1234/v1&quot;, api_key=&quot;local&quot;)
response = client.chat.completions.create(
    model=&quot;local-model&quot;,
    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;continuous batching을 설명해 줘.&quot;}],
)
print(response.choices[0].message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.3 LiteLLM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiteLLM은 여러 모델 제공자 또는 로컬 서버를 하나의 API 형태로 연결하는 게이트웨이 실습에 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# configs/litellm.yaml
model_list:
  - model_name: local-fast
    litellm_params:
      model: openai/local-model
      api_base: http://localhost:1234/v1
      api_key: local&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;litellm --config configs/litellm.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;미니 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ollama와 LM Studio 중 사용 가능한 서버를 선택하고, 실패하면 다른 서버로 전환하는 작은 게이트웨이를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;/v1/chat/completions&lt;/code&gt;로 두 개 이상의 백엔드에 요청했다.&lt;/li&gt;
&lt;li&gt;모델 로딩 시간과 순수 생성 시간을 분리했다.&lt;/li&gt;
&lt;li&gt;타임아웃과 연결 실패를 로그에서 구분했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. vLLM과 SGLang&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령의 옵션은 릴리스에 따라 달라질 수 있다. 실습 전에 각 프로젝트의 공식 최신 CLI 문서를 확인한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1 vLLM 서버&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 가상환경에서 설치한다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;python -m venv .venv-vllm
source .venv-vllm/bin/activate
python -m pip install --upgrade pip
pip install vllm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 시작:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;vllm serve Qwen/Qwen3-4B-Instruct-2507 \
  --host 0.0.0.0 \
  --port 8000 \
  --dtype auto \
  --api-key local-token&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청:&lt;/p&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl http://localhost:8000/v1/chat/completions \
  -H &quot;Authorization: Bearer local-token&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;model&quot;:&quot;Qwen/Qwen3-4B-Instruct-2507&quot;,
    &quot;messages&quot;:[{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;Paged Attention이 필요한 이유는?&quot;}],
    &quot;max_tokens&quot;:128
  }'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;curl http://localhost:8000/health
curl http://localhost:8000/metrics&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2 SGLang 서버&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;python -m venv .venv-sglang
source .venv-sglang/bin/activate
python -m pip install --upgrade pip
pip install &quot;sglang[all]&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;python -m sglang.launch_server \
  --model-path Qwen/Qwen3-4B-Instruct-2507 \
  --host 0.0.0.0 \
  --port 30000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 프롬프트 묶음을 vLLM과 SGLang에 보내 다음을 비교한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 요청 TTFT&lt;/li&gt;
&lt;li&gt;동시성 1, 8, 32, 128의 처리량&lt;/li&gt;
&lt;li&gt;반복 prefix가 있을 때의 처리량&lt;/li&gt;
&lt;li&gt;긴 입력과 짧은 출력&lt;/li&gt;
&lt;li&gt;짧은 입력과 긴 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.3 파이프라인 3종&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;A. 처리량 우선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연속 배칭 활성화&lt;/li&gt;
&lt;li&gt;GPU 메모리 사용률을 점진적으로 높여 탐색&lt;/li&gt;
&lt;li&gt;동시 요청 수를 늘리며 최대 goodput 측정&lt;/li&gt;
&lt;li&gt;p95 지연시간 SLO를 넘으면 직전 설정으로 복귀&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B. 대화형 지연시간 우선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청당 출력 상한 설정&lt;/li&gt;
&lt;li&gt;대형 배치보다 짧은 대기열 선호&lt;/li&gt;
&lt;li&gt;스트리밍 사용&lt;/li&gt;
&lt;li&gt;TTFT p95를 핵심 SLO로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C. 긴 문맥 우선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prefix caching 또는 Radix Cache 활용&lt;/li&gt;
&lt;li&gt;최대 문맥 길이를 실제 제품 요구에 맞춰 제한&lt;/li&gt;
&lt;li&gt;KV 캐시 부족 시 선점&amp;middot;재계산&amp;middot;퇴출 현상 관찰&lt;/li&gt;
&lt;li&gt;긴 요청이 짧은 요청을 막지 않도록 스케줄링 정책 실험&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 엔진의 결과를 같은 표 형식으로 기록했다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;어느 엔진이 항상 빠르다&amp;rdquo;가 아니라 워크로드별 승자를 설명했다.&lt;/li&gt;
&lt;li&gt;서버 시작 명령과 전체 환경 정보를 보고서에 남겼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Paged Attention과 KV 캐시&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.1 먼저 이해할 계산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transformer의 KV 캐시 메모리는 모델 구조와 데이터형에 따라 달라진다. 개념적으로는 다음 항목에 비례한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;KV cache bytes
&amp;asymp; batch &amp;times; sequence_length &amp;times; layers
  &amp;times; 2(K,V) &amp;times; KV_heads &amp;times; head_dim &amp;times; bytes_per_element&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grouped Query Attention을 사용하는 모델은 attention head 수 대신 KV head 수가 중요하다. 모델 설정 파일의 &lt;code&gt;num_key_value_heads&lt;/code&gt;, &lt;code&gt;num_hidden_layers&lt;/code&gt;, &lt;code&gt;head_dim&lt;/code&gt; 또는 이에 해당하는 값을 직접 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.2 Paged Attention&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Paged Attention은 요청별 KV 캐시를 고정된 큰 연속 공간으로 잡는 대신 블록 단위로 관리해 단편화와 낭비를 줄이는 접근이다. 운영 관점에서는 다음 질문으로 연결한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;블록 크기가 메모리 낭비와 관리 비용에 어떤 영향을 주는가?&lt;/li&gt;
&lt;li&gt;요청이 끝난 뒤 블록이 얼마나 빨리 재사용되는가?&lt;/li&gt;
&lt;li&gt;여러 요청이 동일 prefix를 공유할 때 어느 정도 절약되는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.3 캐시 퇴출 실험&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 트래픽을 만든다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;짧은 대화 100개&lt;/li&gt;
&lt;li&gt;동일한 시스템 프롬프트를 공유하는 요청 100개&lt;/li&gt;
&lt;li&gt;긴 문서 질의 20개와 짧은 대화 100개의 혼합&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 실험에서 다음을 기록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KV 캐시 사용률&lt;/li&gt;
&lt;li&gt;prefix cache hit rate&lt;/li&gt;
&lt;li&gt;preemption 또는 recomputation 횟수&lt;/li&gt;
&lt;li&gt;대기열 길이&lt;/li&gt;
&lt;li&gt;TTFT p50/p95/p99&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.4 퇴출 정책 설계 과제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 점수로 직접 정책을 설계한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;eviction_score =
  0.40 &amp;times; idle_time_normalized
  + 0.25 &amp;times; cache_size_normalized
  + 0.20 &amp;times; recompute_cost_inverse
  + 0.15 &amp;times; tenant_priority_inverse&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 공식은 학습용 예시다. 가중치를 바꾸며 다음 사례를 비교한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LRU&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시가 부족해지는 시점을 그래프로 찾았다.&lt;/li&gt;
&lt;li&gt;hit rate가 높아져도 p95가 나빠질 수 있는 조건을 설명했다.&lt;/li&gt;
&lt;li&gt;퇴출 정책의 장단점을 실제 측정값으로 비교했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 양자화: INT4, FP8, AWQ, GPTQ&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.1 구분&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;FP8&lt;/td&gt;
&lt;td&gt;낮은 정밀도의 부동소수점 연산&lt;/td&gt;
&lt;td&gt;하드웨어 지원, 처리량, 품질&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;4비트 정수 기반 가중치 표현&lt;/td&gt;
&lt;td&gt;메모리 절감, 커널 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWQ&lt;/td&gt;
&lt;td&gt;activation을 고려한 weight-only 양자화&lt;/td&gt;
&lt;td&gt;실제 프롬프트 품질&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPTQ&lt;/td&gt;
&lt;td&gt;2차 정보 근사를 이용한 사후 학습 양자화&lt;/td&gt;
&lt;td&gt;양자화 설정과 커널 호환성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWQ와 GPTQ는 &amp;ldquo;파일이 더 작다&amp;rdquo;만으로 평가하지 않는다. 엔진과 GPU에 맞는 커널이 없으면 메모리는 줄어도 속도가 느려질 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.2 비교 실험&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일 모델 계열에서 BF16/FP16, FP8, AWQ INT4, GPTQ INT4를 준비한다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;| 형식 | 모델 크기 | VRAM idle/peak | TTFT p95 | tok/s | 정확도 | 비고 |
|---|---:|---:|---:|---:|---:|---|&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;품질 평가는 최소 세 층으로 나눈다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;짧은 지식&amp;middot;상식 100문항&lt;/li&gt;
&lt;li&gt;한국어 지시 이행 50문항&lt;/li&gt;
&lt;li&gt;실제 서비스 프롬프트 50개&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4.3 선택 규칙&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;if GPU가 FP8을 효율적으로 지원하고 품질 저하가 허용 범위:
    FP8 후보
elif VRAM이 가장 큰 제약이고 지원 커널이 검증됨:
    AWQ 또는 GPTQ INT4 후보
else:
    BF16/FP16 기준선 유지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 절감률과 처리량 향상률을 따로 기록했다.&lt;/li&gt;
&lt;li&gt;정확도 평균뿐 아니라 실패 유형을 분류했다.&lt;/li&gt;
&lt;li&gt;현재 장비에서 실제로 지원하지 않는 경로를 &lt;code&gt;미검증&lt;/code&gt;으로 표시했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 추측 디코딩&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.1 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 draft 모델이 여러 후보 토큰을 먼저 제안하고 target 모델이 이를 검증한다. 목표는 출력 품질을 유지하면서 토큰 사이 지연시간을 낮추는 것이다. 성능 향상은 모델 조합, acceptance rate, 배치 크기, 메모리 대역폭과 워크로드에 따라 달라진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.2 실험 설계&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;target model&lt;/td&gt;
&lt;td&gt;7B~14B instruct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;draft model&lt;/td&gt;
&lt;td&gt;같은 tokenizer를 쓰는 소형 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;speculative tokens&lt;/td&gt;
&lt;td&gt;1, 3, 5, 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동시성&lt;/td&gt;
&lt;td&gt;1, 8, 32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;출력 길이&lt;/td&gt;
&lt;td&gt;32, 256, 1,024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집 항목:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;draft acceptance rate&lt;/li&gt;
&lt;li&gt;speculative efficiency&lt;/li&gt;
&lt;li&gt;ITL p50/p95&lt;/li&gt;
&lt;li&gt;전체 처리량&lt;/li&gt;
&lt;li&gt;추가 GPU 메모리&lt;/li&gt;
&lt;li&gt;비추측 기준선 대비 속도 변화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5.3 중단 조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 중 하나면 해당 설정을 채택하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;p95 ITL이 기준선보다 나빠짐&lt;/li&gt;
&lt;li&gt;draft 모델 메모리 때문에 동시성이 감소함&lt;/li&gt;
&lt;li&gt;acceptance rate가 낮아 추가 계산이 이익을 상쇄함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소 2개 draft/target 조합을 비교했다.&lt;/li&gt;
&lt;li&gt;단일 요청과 높은 동시성에서 결과가 다른 이유를 설명했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. 모델 라우터와 토큰 예산&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.1 라우팅 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 &amp;ldquo;가장 좋은 모델&amp;rdquo;을 고르는 장치가 아니라 요청의 품질 하한, 지연시간 SLO, 비용 상한을 동시에 만족하는 후보를 선택하는 정책 계층이다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;flowchart TD
    A[&quot;사용자 요청&quot;] --&amp;gt; B[&quot;정책&amp;middot;예산 검사&quot;]
    B --&amp;gt; C[&quot;난이도&amp;middot;길이 분류&quot;]
    C --&amp;gt; D{&quot;후보 모델&quot;}
    D --&amp;gt; E[&quot;저비용 모델&quot;]
    D --&amp;gt; F[&quot;저지연 모델&quot;]
    D --&amp;gt; G[&quot;고품질 모델&quot;]
    E --&amp;gt; H[&quot;응답&amp;middot;비용 기록&quot;]
    F --&amp;gt; H
    G --&amp;gt; H&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.2 최소 데이터 모델&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;from dataclasses import dataclass

@dataclass
class ModelProfile:
    name: str
    input_cost_per_million: float
    output_cost_per_million: float
    p95_ttft_ms: float
    p95_itl_ms: float
    quality_score: float
    max_context: int&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.3 점수 함수&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;def route_score(
    quality: float,
    latency_ms: float,
    estimated_cost: float,
    quality_weight: float = 0.5,
    latency_weight: float = 0.3,
    cost_weight: float = 0.2,
) -&amp;gt; float:
    return (
        quality_weight * quality
        - latency_weight * latency_ms
        - cost_weight * estimated_cost
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 구현에서는 단위가 다른 값을 그대로 더하지 말고 0~1 범위로 정규화한다. 또한 품질 하한과 비용 상한을 먼저 적용한 후 남은 후보에 점수를 계산한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.4 토큰 예산&lt;/h2&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;def output_budget(
    plan_limit: int,
    used_tokens: int,
    requested_max_tokens: int,
    reserve_tokens: int = 256,
) -&amp;gt; int:
    remaining = max(0, plan_limit - used_tokens - reserve_tokens)
    return min(requested_max_tokens, remaining)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필수 정책:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자&amp;middot;팀&amp;middot;API 키별 일/월 토큰 한도&lt;/li&gt;
&lt;li&gt;요청당 최대 입력&amp;middot;출력 토큰&lt;/li&gt;
&lt;li&gt;시스템 프롬프트와 도구 호출 토큰 포함 여부&lt;/li&gt;
&lt;li&gt;스트리밍 도중 예산 소진 처리&lt;/li&gt;
&lt;li&gt;재시도와 fallback 요청의 중복 과금 처리&lt;/li&gt;
&lt;li&gt;예약량과 실제 사용량의 정산&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.5 라우터 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 규칙, latency 기반, cost 기반, 품질 포함 다목적 라우터를 같은 요청 집합으로 비교한다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;| 정책 | 성공률 | 품질 | p95 지연 | 평균 원가 | SLO 위반률 |
|---|---:|---:|---:|---:|---:|&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우팅 이유를 요청 로그에서 재현할 수 있다.&lt;/li&gt;
&lt;li&gt;예산 초과 요청은 GPU에 도달하기 전에 거절된다.&lt;/li&gt;
&lt;li&gt;fallback이 무한 반복되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. 관측 가능성: Prometheus와 Grafana&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7.1 반드시 수집할 네 종류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 수, 성공 수, 오류 수&lt;/li&gt;
&lt;li&gt;입력&amp;middot;출력 토큰&lt;/li&gt;
&lt;li&gt;모델, tenant, 상태 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지연시간&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TTFT&lt;/li&gt;
&lt;li&gt;ITL/TPOT&lt;/li&gt;
&lt;li&gt;E2E&lt;/li&gt;
&lt;li&gt;queue time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔진&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 중&amp;middot;대기 중 요청&lt;/li&gt;
&lt;li&gt;KV 캐시 사용률과 hit rate&lt;/li&gt;
&lt;li&gt;preemption&lt;/li&gt;
&lt;li&gt;batch 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자원&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPU 사용률, 메모리, 전력&lt;/li&gt;
&lt;li&gt;CPU, RAM, 네트워크&lt;/li&gt;
&lt;li&gt;Pod 재시작과 OOM&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7.2 카디널리티 경고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus label에 &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;request_id&lt;/code&gt;, 전체 프롬프트를 넣지 않는다. 사용자 단위 상세 기록은 로그 또는 분석 저장소에 보내고, 메트릭은 plan, model, region처럼 제한된 값만 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7.3 대시보드 구성&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서비스 건강: RPS, error rate, p95 E2E&lt;/li&gt;
&lt;li&gt;사용자 경험: TTFT와 ITL&lt;/li&gt;
&lt;li&gt;GPU 효율: utilization, memory, tokens/s&lt;/li&gt;
&lt;li&gt;스케줄러: running/waiting requests, queue time&lt;/li&gt;
&lt;li&gt;캐시: KV 사용률, prefix hit rate, preemption&lt;/li&gt;
&lt;li&gt;비용: 시간당 비용, 요청당 비용, 1M token당 비용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7.4 알림 예시&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;groups:
  - name: llm-serving
    rules:
      - alert: LLMHighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~&quot;5..&quot;}[5m]))
          /
          clamp_min(sum(rate(http_requests_total[5m])), 1)
          &amp;gt; 0.02
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: &quot;LLM API 5xx 비율이 2%를 초과했습니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭 이름은 실제 애플리케이션에서 노출하는 이름에 맞춰 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;느린 응답이 queue, prefill, decode 중 어디서 발생했는지 구분한다.&lt;/li&gt;
&lt;li&gt;대시보드의 수치와 원시 요청 로그를 표본 대조했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 1,000+ 동시 요청 부하 시험&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8.1 도구&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;k6&lt;/code&gt;: HTTP 부하와 시나리오 작성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Locust&lt;/code&gt;: Python 기반 사용자 행동 모델&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vegeta&lt;/code&gt;: 단순 HTTP 처리량 확인&lt;/li&gt;
&lt;li&gt;엔진별 benchmark 도구: 순수 추론 성능 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI 스트리밍 응답은 일반 HTTP 완료 시간만 측정하면 TTFT와 ITL을 놓칠 수 있다. SSE 청크의 도착 시간을 기록하는 전용 클라이언트를 준비한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8.2 단계적 증가&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1 &amp;rarr; 8 &amp;rarr; 32 &amp;rarr; 64 &amp;rarr; 128 &amp;rarr; 256 &amp;rarr; 512 &amp;rarr; 1,000 &amp;rarr; 1,500&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계에서 최소 5분간 안정 상태를 측정한다. 오류율 또는 p95가 SLO를 넘으면 더 높은 단계로 진행하지 않고 원인을 찾는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8.3 현실적인 요청 분포&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;유형&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;비율&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;입력 토큰&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;출력 토큰&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;짧은 질의&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;50%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;64~256&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;32~128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반 대화&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;30%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;256~1,024&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;128~512&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;긴 문서&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;15%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4K~16K&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;128~512&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;긴 생성&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;5%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;256~1K&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1K~4K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8.4 보고서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성별 offered load와 completed throughput&lt;/li&gt;
&lt;li&gt;p50/p95/p99 TTFT, ITL, E2E&lt;/li&gt;
&lt;li&gt;timeout, 429, 5xx&lt;/li&gt;
&lt;li&gt;GPU 사용률과 메모리&lt;/li&gt;
&lt;li&gt;queue length&lt;/li&gt;
&lt;li&gt;SLO를 만족하는 최대 goodput&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1,000 연결을 열었다는 사실과 1,000 요청이 성공했다는 사실을 구분했다.&lt;/li&gt;
&lt;li&gt;클라이언트 CPU&amp;middot;네트워크가 병목이 아닌지 확인했다.&lt;/li&gt;
&lt;li&gt;실패 지점과 안전 운영 한계를 명시했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Kubernetes와 GPU 자동 확장&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9.1 학습 순서&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Deployment와 Service&lt;/li&gt;
&lt;li&gt;GPU resource request&lt;/li&gt;
&lt;li&gt;readiness/liveness probe&lt;/li&gt;
&lt;li&gt;rolling update&lt;/li&gt;
&lt;li&gt;node selector, taint, toleration&lt;/li&gt;
&lt;li&gt;HPA&lt;/li&gt;
&lt;li&gt;Prometheus custom metrics&lt;/li&gt;
&lt;li&gt;queue 기반 확장&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9.2 Deployment 골격&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: llm-server
  template:
    metadata:
      labels:
        app: llm-server
    spec:
      containers:
        - name: server
          image: your-registry/llm-server:VERSION
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: 1
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 태그와 health endpoint는 실제 배포물에 맞춰 검증한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9.3 HPA 골격&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-server
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-server
  minReplicas: 1
  maxReplicas: 8
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 600
  metrics:
    - type: Pods
      pods:
        metric:
          name: llm_waiting_requests
        target:
          type: AverageValue
          averageValue: &quot;4&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 custom metrics adapter가 &lt;code&gt;llm_waiting_requests&lt;/code&gt;를 제공한다고 가정한다. GPU Pod는 모델 로딩 시간이 길기 때문에 CPU 사용률만으로 확장하면 늦을 수 있다. queue length, waiting requests, 예상 대기시간 같은 선행 지표를 비교한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9.4 운영 실험&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0&amp;rarr;100 요청 급증&lt;/li&gt;
&lt;li&gt;10분 동안 점진 증가&lt;/li&gt;
&lt;li&gt;한 Pod 강제 종료&lt;/li&gt;
&lt;li&gt;새 모델 이미지 rolling update&lt;/li&gt;
&lt;li&gt;노드 하나 장애&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 Pod가 준비되기 전에는 트래픽을 받지 않는다.&lt;/li&gt;
&lt;li&gt;scale-down 중 진행 중 요청의 종료 정책을 검증했다.&lt;/li&gt;
&lt;li&gt;모델 다운로드 시간이 확장 지연에 포함됨을 측정했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 엣지 배포&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10.1 ONNX Runtime&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 encoder 또는 소형 생성 모델을 ONNX로 변환해 CPU, DirectML, CUDA 실행 공급자를 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 항목:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 shape와 동적 shape&lt;/li&gt;
&lt;li&gt;graph optimization&lt;/li&gt;
&lt;li&gt;INT8 양자화&lt;/li&gt;
&lt;li&gt;cold start와 warm latency&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10.2 TensorRT 계열&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 환경에서만 별도 트랙으로 진행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔진 빌드 시간과 실행 시간 구분&lt;/li&gt;
&lt;li&gt;지원 precision 확인&lt;/li&gt;
&lt;li&gt;고정/동적 shape profile&lt;/li&gt;
&lt;li&gt;GPU 아키텍처와 엔진 호환성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMD 장비에서는 이 트랙을 개념 학습 후 클라우드 NVIDIA GPU에서 검증한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10.3 WebLLM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 WebGPU로 소형 모델을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;측정 항목:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 모델 다운로드 크기와 시간&lt;/li&gt;
&lt;li&gt;브라우저별 WebGPU 지원&lt;/li&gt;
&lt;li&gt;prefill/decode 속도&lt;/li&gt;
&lt;li&gt;탭 전환과 메모리 회수&lt;/li&gt;
&lt;li&gt;개인정보가 서버로 전송되지 않는 로컬 처리 범위&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10.4 엣지 의사결정표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&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;브라우저 오프라인 실행&lt;/td&gt;
&lt;td&gt;WebLLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데스크톱/모바일 네이티브&lt;/td&gt;
&lt;td&gt;ONNX Runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA 서버 최대 성능&lt;/td&gt;
&lt;td&gt;TensorRT 계열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개발 편의와 폭넓은 모델&lt;/td&gt;
&lt;td&gt;llama.cpp/Ollama 계열&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 작은 모델을 서버와 엣지에서 비교했다.&lt;/li&gt;
&lt;li&gt;다운로드 비용, cold start, 전력, 개인정보를 성능표에 포함했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. 추론 비용과 단위 경제성&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.1 기본 공식&lt;/h2&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;GPU hourly cost
= 인스턴스 비용 + 스토리지 + 네트워크 + 관측 비용

effective tokens per hour
= measured tokens/s &amp;times; 3,600 &amp;times; utilization &amp;times; success_rate

cost per 1M tokens
= GPU hourly cost / effective tokens per hour &amp;times; 1,000,000

cost per request
= input_token_cost + output_token_cost
  + routing + storage + network + retry overhead&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.2 손익분기점&lt;/h2&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;gross margin per request
= revenue per request - variable cost per request

break-even requests per month
= monthly fixed cost / gross margin per request&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.3 반드시 포함할 숨은 비용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;낮은 사용률과 유휴 GPU&lt;/li&gt;
&lt;li&gt;모델 로딩과 재배포 시간&lt;/li&gt;
&lt;li&gt;실패&amp;middot;재시도&amp;middot;fallback&lt;/li&gt;
&lt;li&gt;긴 system prompt&lt;/li&gt;
&lt;li&gt;사용하지 않고 남은 예약 용량&lt;/li&gt;
&lt;li&gt;로그&amp;middot;메트릭&amp;middot;네트워크&lt;/li&gt;
&lt;li&gt;엔지니어 운영 시간&lt;/li&gt;
&lt;li&gt;품질 저하로 발생하는 재질문&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11.4 실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 시나리오를 비교한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;단일 고성능 GPU 자체 서빙&lt;/li&gt;
&lt;li&gt;여러 저가 GPU 자체 서빙&lt;/li&gt;
&lt;li&gt;외부 API 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 낮음/보통/높음일 때 월 비용과 gross margin을 계산한다. 자체 서빙은 최고 처리량이 아니라 실제 평균 사용률로 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완료 조건&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가격과 성능 수치의 기준일을 기록했다.&lt;/li&gt;
&lt;li&gt;최선/기준/최악 시나리오를 분리했다.&lt;/li&gt;
&lt;li&gt;비용 절감이 품질과 SLO에 미치는 영향을 함께 제시했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 최종 프로젝트&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 요구사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 아키텍처를 구현한다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;flowchart TD
    A[&quot;클라이언트&quot;] --&amp;gt; B[&quot;API&amp;middot;토큰 예산&quot;]
    B --&amp;gt; C[&quot;모델 라우터&quot;]
    C --&amp;gt; D[&quot;vLLM 풀&quot;]
    C --&amp;gt; E[&quot;SGLang 풀&quot;]
    D --&amp;gt; F[&quot;Prometheus&quot;]
    E --&amp;gt; F
    F --&amp;gt; G[&quot;Grafana&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenAI 호환 chat completions API&lt;/li&gt;
&lt;li&gt;최소 2개 모델 또는 2개 배포&lt;/li&gt;
&lt;li&gt;비용&amp;middot;지연시간&amp;middot;품질 기반 라우팅&lt;/li&gt;
&lt;li&gt;사용자별 일일 토큰 예산&lt;/li&gt;
&lt;li&gt;스트리밍&lt;/li&gt;
&lt;li&gt;요청&amp;middot;토큰&amp;middot;오류&amp;middot;비용 기록&lt;/li&gt;
&lt;li&gt;Prometheus 메트릭&lt;/li&gt;
&lt;li&gt;Grafana 대시보드&lt;/li&gt;
&lt;li&gt;부하 시험&lt;/li&gt;
&lt;li&gt;Docker Compose 로컬 실행&lt;/li&gt;
&lt;li&gt;선택: Kubernetes와 HPA&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서빙 파이프라인&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;처리량 최적화 vLLM&lt;/li&gt;
&lt;li&gt;prefix 공유에 최적화한 SGLang&lt;/li&gt;
&lt;li&gt;양자화 또는 추측 디코딩을 적용한 실험 파이프라인&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공개 벤치마크 필수 정보&lt;/h3&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;date:
git_commit:
model_id:
model_revision:
engine:
engine_version:
container_image:
gpu:
gpu_count:
driver:
cuda_or_rocm:
cpu:
ram:
prompt_dataset:
input_length_distribution:
output_length_distribution:
concurrency:
warmup:
duration:
sampling_parameters:&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 평가표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th align=&quot;right&quot;&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;재현성&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;20&lt;/td&gt;
&lt;td&gt;다른 사람이 명령대로 실행 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성능 측정&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;20&lt;/td&gt;
&lt;td&gt;TTFT&amp;middot;ITL&amp;middot;goodput&amp;middot;오류 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;캐시&amp;middot;배칭&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;15&lt;/td&gt;
&lt;td&gt;적용 전후 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;라우팅&amp;middot;예산&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;15&lt;/td&gt;
&lt;td&gt;결정 이유와 제한 동작 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관측&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10&lt;/td&gt;
&lt;td&gt;병목 위치를 대시보드에서 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부하 시험&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10&lt;/td&gt;
&lt;td&gt;현실적인 분포와 안전 중단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비용&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10&lt;/td&gt;
&lt;td&gt;요청당 원가와 손익분기점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;매일 공부 루틴&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;90분 버전&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;15분: 공식 문서 또는 논문 한 절 읽기&lt;/li&gt;
&lt;li&gt;45분: 하나의 변수만 바꾸는 실험&lt;/li&gt;
&lt;li&gt;15분: 그래프와 표 업데이트&lt;/li&gt;
&lt;li&gt;10분: 실패 원인 기록&lt;/li&gt;
&lt;li&gt;5분: 다음 실험의 가설 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실험 노트 양식&lt;/h2&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 실험 제목

## 가설

## 고정 조건

## 변경 변수

## 실행 명령

## 예상 결과

## 실제 결과

## 해석

## 미검증 항목

## 다음 실험&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;논문과 자료를 읽는 법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 출시 뉴스보다 다음 질문에 답하는 자료를 우선한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 이동량을 어떻게 줄였는가?&lt;/li&gt;
&lt;li&gt;prefill과 decode를 어떻게 나눴는가?&lt;/li&gt;
&lt;li&gt;배칭과 스케줄링이 tail latency에 어떤 영향을 주는가?&lt;/li&gt;
&lt;li&gt;KV 캐시를 어떻게 배치&amp;middot;압축&amp;middot;퇴출하는가?&lt;/li&gt;
&lt;li&gt;품질을 유지하면서 연산량을 어떻게 줄였는가?&lt;/li&gt;
&lt;li&gt;어떤 하드웨어와 워크로드에서만 이득이 있었는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽을 주제:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PagedAttention&lt;/li&gt;
&lt;li&gt;continuous batching&lt;/li&gt;
&lt;li&gt;prefix caching와 RadixAttention&lt;/li&gt;
&lt;li&gt;speculative decoding, Medusa, EAGLE 계열&lt;/li&gt;
&lt;li&gt;disaggregated prefill/decode&lt;/li&gt;
&lt;li&gt;KV cache quantization과 eviction&lt;/li&gt;
&lt;li&gt;tensor/pipeline/data/expert parallelism&lt;/li&gt;
&lt;li&gt;goodput과 SLO-aware scheduling&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문 요약에는 반드시 &lt;b&gt;기준선, 하드웨어, 모델, 입력&amp;middot;출력 길이, 동시성, 한계&lt;/b&gt;를 기록한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;자주 발생하는 실패&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CUDA/ROCm 메모리 부족&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 순서:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다른 프로세스가 GPU 메모리를 쓰는지 확인&lt;/li&gt;
&lt;li&gt;최대 문맥 길이와 동시 요청 수 축소&lt;/li&gt;
&lt;li&gt;GPU memory utilization 설정 점검&lt;/li&gt;
&lt;li&gt;양자화 모델로 기준선 재구성&lt;/li&gt;
&lt;li&gt;CPU offload는 속도 저하를 포함해 다시 측정&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;처리량은 높은데 응답이 느림&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 높은 배치 크기로 총 처리량은 올라갔지만 queue time 또는 TTFT가 악화됐을 가능성이 있다. 대기시간과 실제 GPU 실행 시간을 분리해 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GPU 사용률이 낮음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 입력 준비, 토크나이징, 네트워크, 작은 배치 또는 부하 생성기가 병목일 가능성이 있다. GPU만 보지 말고 CPU, queue, batch size, 클라이언트 사용률을 함께 확인한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;양자화했는데 느려짐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 현재 GPU&amp;middot;엔진에 최적화된 커널이 없거나 dequantization 비용이 이득을 상쇄했을 가능성이 있다. 동일 동시성과 동일 길이에서 커널 및 정밀도별 결과를 비교한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HPA가 늦게 확장됨&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] CPU 사용률이 대기열 증가보다 늦게 반응하거나 모델 로딩 시간이 긴 것이 원인일 수 있다. waiting requests와 예상 대기시간을 선행 지표 후보로 검증한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;공식 문서&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.vllm.ai/&quot;&gt;vLLM 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.vllm.ai/en/stable/serving/online_serving/&quot;&gt;vLLM OpenAI 호환 서버&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.vllm.ai/en/stable/design/metrics/&quot;&gt;vLLM 메트릭&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sglang.ai/&quot;&gt;SGLang 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.litellm.ai/docs/routing&quot;&gt;LiteLLM 라우팅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/autoscaling/horizontal-pod-autoscale/&quot;&gt;Kubernetes Horizontal Pod Autoscaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prometheus.io/docs/&quot;&gt;Prometheus 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grafana.com/docs/&quot;&gt;Grafana 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://onnxruntime.ai/docs/&quot;&gt;ONNX Runtime 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nvidia.github.io/TensorRT-LLM/&quot;&gt;NVIDIA TensorRT-LLM 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webllm.mlc.ai/docs/&quot;&gt;WebLLM 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ollama.com/&quot;&gt;Ollama 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;시작 체크리스트&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; GPU와 드라이버 정보를 기록했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 모델과 엔진 버전을 고정했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; TTFT, ITL, throughput, goodput 정의를 정했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Ollama 또는 LM Studio API를 호출했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; vLLM 서버를 실행했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; SGLang 서버를 실행했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 동일 워크로드로 두 엔진을 비교했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; KV 캐시 사용률과 cache hit rate를 관찰했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 양자화 전후 메모리&amp;middot;속도&amp;middot;품질을 비교했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 추측 디코딩의 acceptance rate를 측정했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 모델 라우터와 토큰 예산을 구현했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Prometheus와 Grafana를 연결했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 1,000 동시 요청 시험을 수행했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Kubernetes HPA를 검증했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 요청당 원가와 손익분기점을 계산했다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 모든 명령과 환경을 공개 보고서에 기록했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증 범위와 주의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서는 학습 사이트의 구조와 실습 설계를 제공한다. 이 환경에는 GPU가 연결되어 있지 않아 vLLM, SGLang, CUDA, ROCm, Kubernetes GPU 배포 명령을 실제 실행하지 않았다. 따라서 각 실습의 성공 결과는 &lt;b&gt;미검증&lt;/b&gt;이며, 사용하는 GPU&amp;middot;드라이버&amp;middot;엔진 릴리스의 공식 문서에서 지원 조합과 CLI 옵션을 확인해야 한다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1795</guid>
      <comments>https://javaexpert.tistory.com/1795#entry1795comment</comments>
      <pubDate>Wed, 29 Jul 2026 14:35:33 +0900</pubDate>
    </item>
    <item>
      <title>Unity CLI 에 대해 알아보자</title>
      <link>https://javaexpert.tistory.com/1794</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Unity CLI란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unity CLI&lt;/b&gt;는 Unity Hub 화면을 열지 않고 터미널에서 Unity 개발환경을 관리하는 공식 명령줄 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 역할은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unity Editor 설치&lt;/li&gt;
&lt;li&gt;Android&amp;middot;iOS&amp;middot;WebGL 모듈 설치&lt;/li&gt;
&lt;li&gt;설치된 Editor 목록 확인&lt;/li&gt;
&lt;li&gt;프로젝트에 맞는 Unity 버전으로 실행&lt;/li&gt;
&lt;li&gt;Unity 계정 로그인&lt;/li&gt;
&lt;li&gt;CLI 업데이트&lt;/li&gt;
&lt;li&gt;CI/CD 환경 자동 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Unity Hub의 CLI 기능과 달리, &lt;b&gt;독립된 unity 네이티브 실행 파일&lt;/b&gt;로 제공되므로 CI 서버나 AI 에이전트가 호출하기에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 글 작성 시점 기준으로 &lt;b&gt;Unity CLI와 Unity Pipeline 모두 experimental 단계&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;macOS / Linux&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl -fsSL https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.sh \
  | UNITY_CLI_CHANNEL=beta bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Windows PowerShell&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;$env:UNITY_CLI_CHANNEL='beta'
irm https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 터미널을 다시 열고 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;unity --version
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Unity Editor 설치&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최신 LTS 설치&lt;/h3&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;unity install lts
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특정 버전 설치&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;unity install 6000.3.7f1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플랫폼 모듈과 함께 설치&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;unity install lts -m android ios webgl
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 Editor에 모듈 추가&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;unity install-modules -e 6000.3.7f1 -m android ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 모듈 추가가 가능한 Editor가 다음 방식으로 설치된 경우에 한정된다는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unity Hub로 설치&lt;/li&gt;
&lt;li&gt;Unity CLI로 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동으로 복사하거나 직접 배치한 Editor는 모듈 추가 대상이 아닐 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Unity 프로젝트 실행&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;unity open ./MyProject
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open은 생략할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;unity ./MyProject
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity CLI가 프로젝트의 설정을 읽고 해당 프로젝트에 필요한 Unity Editor 버전을 찾아 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 프로젝트마다 Unity 버전이 다를 때 직접 Editor 실행 경로를 지정하는 작업을 줄일 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 인증과 업데이트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity 계정 로그인&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;unity auth login
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그인 상태 확인&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;unity auth status
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity CLI 업데이트&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;unity upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헤드리스 서버나 CI 환경에서는 환경변수를 이용한 서비스 계정 인증도 지원하는 구조입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. CI/CD 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity CLI는 자동화 도구가 결과를 읽기 쉽도록 구조화된 출력 형식을 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JSON 출력&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;unity editors --format json
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TSV 출력&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;unity editors --format tsv
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 출력과 오류 출력이 분리되고 종료 코드도 명확합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료 코드의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;사용자 또는 작업 취소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 질문 없이 자동 설치할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;unity install lts \
  -m android ios \
  --accept-eula \
  --yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경이나 인증 문제를 진단할 때는 다음 명령을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;unity doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Actions 같은 CI에서는 다음 작업을 자동화할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;저장소 체크아웃
&amp;rarr; Unity CLI 설치
&amp;rarr; 지정 Unity 버전 설치
&amp;rarr; 플랫폼 모듈 설치
&amp;rarr; 인증
&amp;rarr; 테스트
&amp;rarr; 빌드
&amp;rarr; 결과 및 종료 코드 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Unity CLI와 Unity Pipeline의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 도구의 역할을 구분하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분Unity CLIUnity Pipeline&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주요 목적&lt;/td&gt;
&lt;td&gt;Unity 설치 및 실행환경 관리&lt;/td&gt;
&lt;td&gt;실행 중인 Editor&amp;middot;Player 외부 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor 설치&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;Play 모드 제어&lt;/td&gt;
&lt;td&gt;Pipeline을 통해 가능&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unity API 호출&lt;/td&gt;
&lt;td&gt;직접적인 핵심 기능 아님&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# 코드 실행&lt;/td&gt;
&lt;td&gt;직접적인 핵심 기능 아님&lt;/td&gt;
&lt;td&gt;eval 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 에이전트 연동&lt;/td&gt;
&lt;td&gt;환경 관리 담당&lt;/td&gt;
&lt;td&gt;실제 Editor 조작 담당&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Unity CLI
= Unity 개발환경을 설치하고 프로젝트를 실행하는 도구

Unity Pipeline
= 실행 중인 Unity Editor와 Player를 외부에서 조작하는 도구
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Unity Pipeline 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 실행한 상태에서 다음 명령으로 Pipeline 패키지를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;unity pipeline install
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity Pipeline은 제공된 글 기준으로 &lt;b&gt;Unity 6.0 LTS 이상&lt;/b&gt;을 대상으로 하며, 로컬 API를 통해 Unity CLI와 Editor를 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 가능한 명령 목록을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;unity command
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 명령을 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;unity command &amp;lt;command-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 프로젝트에 등록된 테스트나 에디터 도구를 다음처럼 호출하는 구조입니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;unity command run-tests
unity command enter-play-mode
unity command generate-assets
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 실제 명령 이름은 프로젝트에 등록된 명령에 따라 달라집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 프로젝트 전용 CLI 명령 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 정적 메서드에 CliCommand 속성을 붙이면 프로젝트 전용 명령을 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using Unity.Pipeline.Commands;
using UnityEngine;

public static class MyPipelineCommands
{
    [CliCommand(&quot;greet&quot;, &quot;Log a greeting and return its length&quot;)]
    public static int Greet(
        [CliArg(&quot;name&quot;, &quot;Who to greet&quot;, Required = true)] string name)
    {
        Debug.Log($&quot;Hello, {name}!&quot;);
        return name.Length;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서는 다음처럼 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;unity command greet --name World
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 복잡한 등록 코드 없이 속성이 붙은 메서드가 자동으로 검색됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 다음 기능을 CLI 명령으로 공개할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;씬 생성 및 변경&lt;/li&gt;
&lt;li&gt;GameObject 배치&lt;/li&gt;
&lt;li&gt;프리팹 생성&lt;/li&gt;
&lt;li&gt;에셋 임포트&lt;/li&gt;
&lt;li&gt;테스트 실행&lt;/li&gt;
&lt;li&gt;프로젝트 검증&lt;/li&gt;
&lt;li&gt;빌드&lt;/li&gt;
&lt;li&gt;Play 모드 진입&lt;/li&gt;
&lt;li&gt;커스텀 에디터 도구 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. Unity Editor 안에서 C# 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity Pipeline은 실행 중인 Editor 내부에서 C# 표현식이나 코드를 실행하는 eval 기능을 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity 버전 확인&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;unity command eval &quot;return Application.version;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Play 모드 상태 확인&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;unity command eval \
  &quot;return UnityEditor.EditorApplication.isPlaying;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C# 파일 실행&lt;/h3&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;unity command eval_file &quot;path/to/script.cs&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 Roslyn으로 컴파일되어 Unity Editor의 메인 스레드에서 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점은 전체 프로젝트를 다시 컴파일하거나 도메인을 리로드하지 않고도 Unity API를 호출할 수 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 eval은 임의의 Unity API를 실행할 수 있으므로 보안상 매우 강력한 기능입니다. 제공된 글에 따르면 실행 시 보안 토큰이 필요합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 개발용 Player 제어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity Pipeline 런타임 컴포넌트를 Development Build에 포함하면 Editor뿐 아니라 실행 중인 게임 Player도 제어할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;unity command --runtime &amp;lt;player-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 활용 사례는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Player 로그 수집&lt;/li&gt;
&lt;li&gt;게임 상태 조회&lt;/li&gt;
&lt;li&gt;런타임 API 호출&lt;/li&gt;
&lt;li&gt;자동 QA 수행&lt;/li&gt;
&lt;li&gt;개발용 Player 코드 핫리로드&lt;/li&gt;
&lt;li&gt;특정 위치나 게임 상태 재현&lt;/li&gt;
&lt;li&gt;테스트 캐릭터 또는 데이터를 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 기본적으로 비활성화되어 있고 로컬호스트 접속을 전제로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 배포용 본 게임보다는 다음 환경에 적합합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발용 빌드&lt;/li&gt;
&lt;li&gt;내부 QA 빌드&lt;/li&gt;
&lt;li&gt;자동 테스트용 빌드&lt;/li&gt;
&lt;li&gt;에이전트 검증용 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. AI 에이전트와의 연동&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity CLI와 Pipeline이 중요한 이유는 AI가 단순히 C# 코드만 작성하는 수준에서 벗어나 &lt;b&gt;실제로 Unity를 실행하고 결과를 검증할 수 있는 기반&lt;/b&gt;을 제공하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 AI 코딩 흐름은 보통 다음과 같았습니다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;AI가 코드 작성
&amp;rarr; 사람이 Unity Editor 실행
&amp;rarr; 사람이 컴파일 오류 확인
&amp;rarr; 사람이 Play 모드 실행
&amp;rarr; 결과를 다시 AI에게 설명
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity CLI와 Pipeline을 연결하면 다음 흐름으로 확장할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;AI가 프로젝트 분석
&amp;rarr; Unity 프로젝트 실행
&amp;rarr; C# 코드 수정
&amp;rarr; Unity 컴파일 상태 확인
&amp;rarr; 테스트 실행
&amp;rarr; Play 모드 진입
&amp;rarr; 로그 및 상태 조회
&amp;rarr; 오류 분석
&amp;rarr; 코드 재수정
&amp;rarr; 다시 테스트
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 JSON 출력, 공통 종료 코드, 커스텀 CLI 명령이 있기 때문에 AI가 Unity 화면을 이미지로 분석하지 않고도 구조화된 결과를 받을 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. MCP와 연결했을 때의 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 다음 구조는 제공된 기능 설명을 기반으로 정리한 일반적인 연동 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Claude Code / Codex / Gemini CLI / 자체 AI 에이전트
                    &amp;darr;
                 MCP 서버
                    &amp;darr;
               Unity CLI
                    &amp;darr;
             Unity Pipeline
                    &amp;darr;
       Unity Editor 또는 Development Player
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 도구는 예를 들어 다음 형태로 설계할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;unity_open_project
unity_get_commands
unity_execute_command
unity_eval
unity_run_tests
unity_enter_play_mode
unity_get_console_logs
unity_build_project
unity_inspect_scene
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 eval을 AI에게 직접 무제한 공개하는 것은 위험할 수 있으므로, 실무에서는 허용된 작업만 수행하는 커스텀 명령을 만드는 편이 더 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[CliCommand(&quot;validate-scene&quot;, &quot;Validate the active scene&quot;)]
public static ValidationResult ValidateScene()
{
    // 허용된 검증 작업만 수행
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 가장 유용한 실무 활용 사례&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 개발환경 구성&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;unity install 6000.3.7f1 -m android --accept-eula --yes
unity auth login
unity ./MyProject
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 테스트&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;unity command run-tests
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 상태 검사&lt;/h3&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;unity command validate-project
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI가 생성한 씬 검증&lt;/h3&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;AI가 씬 생성 코드 작성
&amp;rarr; Unity CLI로 프로젝트 실행
&amp;rarr; Pipeline 명령으로 씬 생성
&amp;rarr; Play 모드 실행
&amp;rarr; 로그 확인
&amp;rarr; 실패 시 재수정
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모바일 빌드 자동화&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Unity 설치
&amp;rarr; Android 또는 iOS 모듈 설치
&amp;rarr; 프로젝트 열기
&amp;rarr; 테스트
&amp;rarr; 빌드 명령 실행
&amp;rarr; 빌드 산출물 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. 주의할 점&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아직 실험 단계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity CLI와 Unity Pipeline 모두 experimental이므로 명령 형식이나 API가 변경될 가능성이 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;eval 보안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eval은 Unity Editor 안에서 임의의 C# 코드를 실행할 수 있습니다. 신뢰할 수 없는 입력이나 외부 네트워크에 직접 노출하면 위험합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Editor 메인 스레드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pipeline 코드가 Editor 메인 스레드에서 실행되므로 장시간 작업을 동기적으로 실행하면 Editor가 멈춘 것처럼 보일 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스텀 명령 우선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 연동에서는 자유로운 eval보다 명시적으로 검증된 CliCommand를 제공하는 편이 적절합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;권장:
unity command build-android
unity command validate-scene
unity command run-game-tests

주의:
unity command eval &quot;&amp;lt;AI가 생성한 임의 코드&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발용 Player에서만 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 제어 기능은 배포용 빌드가 아니라 Development Build와 QA 환경 중심으로 사용하는 것이 적합합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;핵심 요약&lt;/h1&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;Unity CLI
- Unity Editor 설치
- 플랫폼 모듈 설치
- 프로젝트 실행
- 계정 인증
- CI 환경 구성

Unity Pipeline
- 실행 중인 Editor 제어
- 커스텀 CLI 명령 실행
- C# 코드 실행
- Play 모드와 테스트 제어
- Development Player 제어

두 도구를 결합하면
AI가 Unity 프로젝트를 수정하는 데서 끝나지 않고
실행&amp;middot;테스트&amp;middot;로그 확인&amp;middot;재수정까지 자동화할 수 있다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 변화는 &lt;b&gt;Unity Hub를 CLI로 대체하는 것 자체가 아니라, Unity Editor를 AI 에이전트가 검증 가능한 형태로 외부 제어할 수 있게 되었다는 점&lt;/b&gt;입니다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1794</guid>
      <comments>https://javaexpert.tistory.com/1794#entry1794comment</comments>
      <pubDate>Wed, 29 Jul 2026 09:22:50 +0900</pubDate>
    </item>
    <item>
      <title>ChatGPT Sites 내부에선 어떻게 돌아갈까?</title>
      <link>https://javaexpert.tistory.com/1793</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT의 Sites 기능은 단순히 HTML 파일을 만들어 다운로드해 주는 기능이 아닙니다. 대화에서 웹사이트를 기획하고 코드를 작성한 뒤, 빌드&amp;middot;버전 저장&amp;middot;호스팅&amp;middot;외부 URL 연결까지 처리하는 관리형 웹 배포 서비스에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 구조를 먼저 한 문장으로 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT의 Work VM은 웹사이트를 만드는 작업실이고, Sites는 완성된 소스와 빌드 산출물을 별도의 관리형 Worker 환경에 배포해 외부에서 접속할 수 있게 하는 서비스입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 작업에 사용한 VM이 종료되거나 정리되더라도, 이미 배포된 사이트는 별도의 운영 환경에서 계속 제공될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 생성된 사이트도 로컬 작업 폴더가 정리된 후에도 Sites 서비스에 다음 정보가 남아 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사이트 프로젝트 정보&lt;/li&gt;
&lt;li&gt;공개 주소&lt;/li&gt;
&lt;li&gt;공개 범위&lt;/li&gt;
&lt;li&gt;Git 커밋 기준 소스 버전&lt;/li&gt;
&lt;li&gt;전체 소스 아카이브&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 data-ke-size=&quot;size16&quot;&gt;이 원리를 이해하려면 Work VM, Sites, Vinext, Vite, Cloudflare Worker의 역할을 분리해서 봐야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. ChatGPT Sites는 하나의 프로그램이 아니라 전체 배포 과정이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites를 하나의 웹 프레임워크나 서버 프로그램으로 생각하면 구조가 혼란스러워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites는 대략 다음 기능을 하나로 묶은 서비스입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사이트 프로젝트 생성&lt;/li&gt;
&lt;li&gt;작업용 소스 코드 준비&lt;/li&gt;
&lt;li&gt;이미지와 정적 파일 관리&lt;/li&gt;
&lt;li&gt;React&amp;middot;Next.js 코드 빌드&lt;/li&gt;
&lt;li&gt;Worker용 서버 코드 생성&lt;/li&gt;
&lt;li&gt;빌드 산출물 검증&lt;/li&gt;
&lt;li&gt;소스 버전 저장&lt;/li&gt;
&lt;li&gt;운영 환경 배포&lt;/li&gt;
&lt;li&gt;chatgpt.site 주소 연결&lt;/li&gt;
&lt;li&gt;공개&amp;middot;비공개 접근 권한 관리&lt;/li&gt;
&lt;li&gt;환경변수와 API 키 관리&lt;/li&gt;
&lt;li&gt;데이터베이스와 파일 저장소 연결&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ge7nr/dJMcadvYXR9/hLjVoW2DNMxzd0wI9DsNoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ge7nr/dJMcadvYXR9/hLjVoW2DNMxzd0wI9DsNoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ge7nr/dJMcadvYXR9/hLjVoW2DNMxzd0wI9DsNoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGe7nr%2FdJMcadvYXR9%2FhLjVoW2DNMxzd0wI9DsNoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;247&quot; height=&quot;886&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 Work VM은 최종 운영 서버가 아닙니다. 코드를 만들고 검사하는 임시 개발환경입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 사이트는 Sites가 저장하고 배포한 별도의 버전으로 서비스됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Work VM은 웹사이트가 계속 실행되는 서버가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT Work 환경에서 사이트를 만들면 /workspace/sites/... 같은 작업 경로에 소스 코드가 준비됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 확인한 WMU 사이트도 다음 경로로 복원됐습니다.&lt;/p&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;/workspace/sites/wmu-global-ai-hub
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 폴더에는 일반적인 웹 프로젝트처럼 다음 파일들이 들어 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;wmu-global-ai-hub/
├─ app/
│  ├─ page.tsx
│  ├─ layout.tsx
│  ├─ globals.css
│  └─ chatgpt-auth.ts
├─ public/
│  ├─ hero-women-network.png
│  └─ favicon.svg
├─ worker/
│  └─ index.ts
├─ build/
│  └─ sites-vite-plugin.ts
├─ scripts/
│  ├─ build-verified.sh
│  └─ validate-artifact.sh
├─ .openai/
│  └─ hosting.json
├─ vite.config.ts
└─ package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT는 이 작업 폴더에서 다음 작업을 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 컴포넌트 작성&lt;/li&gt;
&lt;li&gt;CSS 디자인&lt;/li&gt;
&lt;li&gt;이미지 파일 배치&lt;/li&gt;
&lt;li&gt;모바일 반응형 적용&lt;/li&gt;
&lt;li&gt;버튼과 모달 같은 상호작용 구현&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 data-ke-size=&quot;size16&quot;&gt;하지만 외부 사용자가 이 작업 폴더에 직접 접속하는 것은 아닙니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fxp7Q/dJMcadvYXsP/QxJorUdRGKQP9930hb6ubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fxp7Q/dJMcadvYXsP/QxJorUdRGKQP9930hb6ubk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fxp7Q/dJMcadvYXsP/QxJorUdRGKQP9930hb6ubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFxp7Q%2FdJMcadvYXsP%2FQxJorUdRGKQP9930hb6ubk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;158&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Work VM은 개발자 PC와 비슷하고, Sites 운영 환경은 Vercel&amp;middot;Cloudflare Workers 같은 배포 플랫폼과 비슷한 역할을 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. VM이 정리됐는데 사이트가 남아 있었던 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 분석 과정에서 로컬 /workspace를 검색했을 때 처음에는 사이트 소스와 .openai/hosting.json이 발견되지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 작업공간이 자동 정리된 상태였기 때문입니다. 그러나 Sites 서비스에는 WMU Global AI Hub 프로젝트가 남아 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 원격 정보는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목확인된 상태&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트&lt;/td&gt;
&lt;td&gt;WMU Global AI Hub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공개 주소&lt;/td&gt;
&lt;td&gt;wmu-global-ai-hub.tommykim1981.chatgpt.site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공개 범위&lt;/td&gt;
&lt;td&gt;Public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장된 버전&lt;/td&gt;
&lt;td&gt;1개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소스 기준&lt;/td&gt;
&lt;td&gt;Git 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소스 아카이브&lt;/td&gt;
&lt;td&gt;약 3.3MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D1 데이터베이스&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2 파일 저장소&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites가 저장한 원격 버전을 통해 사이트 체크아웃을 다시 복원할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Sites에는 최소한 다음 세 가지 계층이 존재한다고 이해할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 로컬 작업본
   ChatGPT가 현재 수정하는 파일

2. 저장된 소스 버전
   특정 Git 커밋과 소스 아카이브

3. 운영 배포본
   외부 URL에서 서비스되는 실행 버전
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지는 서로 같아 보이지만 목적이 다릅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 작업본&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 편집 중인 코드입니다. 아직 외부 사용자에게 공개되지 않은 변경사항이 포함될 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;저장된 소스 버전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점의 소스를 변경 불가능한 버전으로 저장한 것입니다. 나중에 복원하거나 배포 이력을 확인하는 기준이 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운영 배포본&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 버전을 실제 운영 환경에서 실행하도록 배포한 결과입니다. 사용자가 브라우저에서 보는 것은 이 운영 배포본입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. .openai/hosting.json은 어떤 역할을 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites 프로젝트에는 .openai/hosting.json이라는 파일이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트에서 확인된 형태는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;d1&quot;: null,
  &quot;project_id&quot;: &quot;사이트 프로젝트 식별자&quot;,
  &quot;r2&quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일에 웹사이트 전체 설정이 들어 있는 것은 아닙니다. Sites 서비스와 로컬 소스 코드를 연결하는 프로젝트 표식에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 필드의 의미는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;project_id&lt;/td&gt;
&lt;td&gt;현재 코드가 어느 Sites 프로젝트에 속하는지 식별&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;d1&lt;/td&gt;
&lt;td&gt;Sites가 연결한 D1 데이터베이스 바인딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;r2&lt;/td&gt;
&lt;td&gt;Sites가 연결한 R2 파일 저장소 바인딩&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 WMU 사이트는 랜딩 페이지이기 때문에 D1과 R2가 모두 null입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스나 업로드 기능을 추가하면 Sites가 해당 리소스를 만들고 논리적 바인딩을 이 파일과 연결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 API 키나 비밀번호를 hosting.json에 넣지 않는다는 것입니다. 비밀정보는 Sites의 운영 환경변수 기능으로 관리해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 실제 사이트는 어떤 기술로 만들어졌을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NextJs로 만든 사이트의 package.json을 확인한 결과 주요 구성은 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React 19&lt;/td&gt;
&lt;td&gt;UI 컴포넌트 작성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 16&lt;/td&gt;
&lt;td&gt;페이지와 서버 기능 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vite 8&lt;/td&gt;
&lt;td&gt;개발 서버와 프로덕션 빌드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vinext&lt;/td&gt;
&lt;td&gt;Next.js 기능을 Vite&amp;middot;Worker 환경에 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Vite Plugin&lt;/td&gt;
&lt;td&gt;Worker 환경 빌드와 로컬 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrangler&lt;/td&gt;
&lt;td&gt;Cloudflare Worker 개발 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;타입이 있는 JavaScript 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailwind CSS&lt;/td&gt;
&lt;td&gt;CSS 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle ORM&lt;/td&gt;
&lt;td&gt;D1 같은 SQL 데이터베이스 연결 준비&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 가장 낯선 기술은 Vinext입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Vinext는 플랫폼이 아니라 호환 계층이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vinext는 호스팅 서비스나 클라우드 플랫폼 이름이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 애플리케이션의 API와 실행 방식을 Vite 위에서 다시 구현해, Cloudflare Workers 같은 환경에서 실행할 수 있게 만드는 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare의 공식 Vinext 저장소는 Vinext를 Next.js API 표면을 Vite 위에서 재구현하는 플러그인으로 설명합니다. App Router, Pages Router, React Server Components, Server Actions, Middleware, Route Handler, ISR과 정적 내보내기 등을 지원하며, Cloudflare Workers와 가장 깊게 통합됩니다. &lt;a href=&quot;https://github.com/cloudflare/vinext&quot;&gt;Cloudflare Vinext 공식 저장소&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 관계는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCea3J/dJMcaiqBMMT/eR6oFlmcnkBVPEhTdVXrSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCea3J/dJMcaiqBMMT/eR6oFlmcnkBVPEhTdVXrSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCea3J/dJMcaiqBMMT/eR6oFlmcnkBVPEhTdVXrSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCea3J%2FdJMcaiqBMMT%2FeR6oFlmcnkBVPEhTdVXrSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;817&quot; height=&quot;158&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 코드를 그대로 Cloudflare Worker에 넣는다고 자동으로 실행되지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 다음과 같은 자체 실행 규칙을 가지고 있기 때문입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 기반 라우팅&lt;/li&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;서버 컴포넌트&lt;/li&gt;
&lt;li&gt;클라이언트 컴포넌트&lt;/li&gt;
&lt;li&gt;Route Handler&lt;/li&gt;
&lt;li&gt;서버 렌더링&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 data-ke-size=&quot;size16&quot;&gt;Vinext는 이런 Next.js의 기능을 Vite 빌드와 Worker의 요청 처리 방식에 맞춰 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트의 vite.config.ts에는 실제로 다음 플러그인이 등록되어 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import vinext from &quot;vinext&quot;;
import { defineConfig } from &quot;vite&quot;;
import { sites } from &quot;./build/sites-vite-plugin&quot;;

export default defineConfig({
  plugins: [
    vinext(),
    sites(),
  ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 각 플러그인의 역할은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;vinext()
&amp;rarr; Next.js 호환 기능과 라우팅을 Vite 빌드에 연결

sites()
&amp;rarr; 빌드 산출물에 Sites 배포 메타데이터를 포함

cloudflare()
&amp;rarr; Worker 런타임과 바인딩을 Vite에 연결
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 왜 Next.js인데 next build가 아니라 Vinext를 사용할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Next.js 프로젝트는 다음 명령으로 빌드합니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;next build
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 Sites 프로젝트는 다음과 같은 방식으로 빌드됩니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;vinext build
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vinext 공식 설명에 따르면, Vinext는 next build 결과물을 가져와 변환하는 방식이 아니라 Next.js API를 Vite 위에서 다시 구현하는 방식입니다. &lt;a href=&quot;https://github.com/cloudflare/vinext&quot;&gt;Cloudflare Vinext 공식 저장소&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 개념적으로 다음과 같은 차이가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반적인 Next.js 배포&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Next.js 코드
&amp;rarr; next build
&amp;rarr; Node.js 또는 Vercel용 결과물
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sites에서 확인된 구조&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Next.js 코드
&amp;rarr; Vinext
&amp;rarr; Vite
&amp;rarr; Cloudflare Worker 호환 결과물
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 개발자가 React와 Next.js 스타일로 사이트를 작성하면서도 최종 실행 환경을 Worker 구조로 만들 수 있다는 점입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 빌드하면 어떤 산출물이 만들어질까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트의 빌드 검증 스크립트는 최소한 다음 두 파일이 있어야 배포 가능한 산출물로 인정합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;dist/server/index.js
dist/.openai/hosting.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파일의 역할은 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dist/server/index.js&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker에서 실행할 서버 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 모듈은 기본 내보내기 객체를 제공하고, 그 안에 호출 가능한 fetch() 함수가 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 형태는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default {
  async fetch(request, env, ctx) {
    return new Response(&quot;Hello&quot;);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dist/.openai/hosting.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 빌드가 어느 Sites 프로젝트와 연결되는지 알려 주는 배포 메타데이터입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정적 자산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 다음 파일들이 빌드 산출물에 포함될 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;dist/
├─ server/
│  └─ index.js
├─ assets/
│  ├─ JavaScript 번들
│  ├─ CSS
│  ├─ 폰트
│  └─ 이미지
└─ .openai/
   ├─ hosting.json
   └─ drizzle/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Drizzle 데이터베이스 마이그레이션이 있다면 dist/.openai/drizzle/도 함께 포함됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Worker 서버 코드는 왜 필요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트가 단순한 랜딩 페이지라면 HTML, CSS, JavaScript와 이미지만 있어도 충분해 보입니다. 그런데 Sites 프로젝트에는 왜 Worker 서버 코드가 포함될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 배포 형식을 정적 사이트로 제한하지 않고, 서버 렌더링과 백엔드 기능까지 처리할 수 있도록 만들기 위해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker 서버 코드는 다음 기능을 담당할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;들어온 HTTP 요청 처리&lt;/li&gt;
&lt;li&gt;URL별 페이지 라우팅&lt;/li&gt;
&lt;li&gt;서버 컴포넌트 실행&lt;/li&gt;
&lt;li&gt;서버 렌더링&lt;/li&gt;
&lt;li&gt;API Route 실행&lt;/li&gt;
&lt;li&gt;로그인 사용자 확인&lt;/li&gt;
&lt;li&gt;환경변수 접근&lt;/li&gt;
&lt;li&gt;데이터베이스 조회&lt;/li&gt;
&lt;li&gt;파일 저장소 접근&lt;/li&gt;
&lt;li&gt;외부 API 호출&lt;/li&gt;
&lt;li&gt;이미지 최적화&lt;/li&gt;
&lt;li&gt;오류 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare Workers에서는 외부 HTTP 요청이 fetch() 핸들러로 전달됩니다. 이 함수는 Request, 환경 바인딩인 env, 실행 컨텍스트인 ctx를 받아 Response를 반환합니다. &lt;a href=&quot;https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/&quot;&gt;Cloudflare Workers Fetch Handler 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default {
  async fetch(request, env, ctx) {
    return new Response(&quot;응답&quot;);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트에서도 같은 형태를 확인할 수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const worker = {
  async fetch(request, env, ctx) {
    return handler.fetch(request, env, ctx);
  },
};

export default worker;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 handler는 Vinext가 제공하는 Next.js App Router 실행기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Worker가 직접 모든 페이지를 구성하는 것이 아니라 Vinext의 핸들러에게 요청을 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbvfMC/dJMcafU1OnA/78fPqlcvjCDOkgWKk31mlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbvfMC/dJMcafU1OnA/78fPqlcvjCDOkgWKk31mlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbvfMC/dJMcafU1OnA/78fPqlcvjCDOkgWKk31mlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbvfMC%2FdJMcafU1OnA%2F78fPqlcvjCDOkgWKk31mlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1047&quot; height=&quot;54&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 정적 파일과 Worker는 어떻게 함께 동작할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites의 배포물은 정적 자산과 서버 코드를 함께 포함할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;배포 버전
├─ 정적 자산
│  ├─ 이미지
│  ├─ CSS
│  ├─ 브라우저 JavaScript
│  └─ 폰트
└─ Worker 서버
   ├─ 페이지 라우팅
   ├─ 서버 렌더링
   ├─ API
   └─ 외부 서비스 연동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare Workers는 HTML, CSS, 이미지 같은 정적 자산을 Worker와 함께 배포할 수 있습니다. Cloudflare는 이런 정적 파일을 캐시하고 브라우저에 제공할 수 있다고 설명합니다. &lt;a href=&quot;https://developers.cloudflare.com/workers/static-assets/&quot;&gt;Cloudflare Workers Static Assets 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker 코드에서는 env.ASSETS 바인딩을 통해 정적 파일을 직접 가져올 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;return env.ASSETS.fetch(request);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare 공식 문서에서도 Assets 바인딩을 사용해 env.ASSETS.fetch() 형태로 정적 자산을 가져올 수 있다고 설명합니다. &lt;a href=&quot;https://developers.cloudflare.com/workers/static-assets/binding/&quot;&gt;Cloudflare Assets Binding 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트에서는 이미지 최적화 경로가 들어오면 다음과 같은 처리를 합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;if (url.pathname === &quot;/_vinext/image&quot;) {
  return handleImageOptimization(request, {
    fetchAsset: (path) =&amp;gt;
      env.ASSETS.fetch(
        new Request(new URL(path, request.url))
      ),
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 요청 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfRxRB/dJMb99UJ98j/Ovvbfnnk5E5IukKKGTmFE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfRxRB/dJMb99UJ98j/Ovvbfnnk5E5IukKKGTmFE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfRxRB/dJMb99UJ98j/Ovvbfnnk5E5IukKKGTmFE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfRxRB%2FdJMb99UJ98j%2FOvvbfnnk5E5IukKKGTmFE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;554&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트가 대부분 정적이더라도 하나의 Worker 배포 안에서 정적 자산과 동적 요청을 통합해 처리할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 모든 페이지 요청이 Worker를 거쳐야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 라우팅 우선순위와 캐시 정책은 Sites의 비공개 배포 설정에 따라 달라질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음 두 방식이 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정적 자산 우선 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 생성된 CSS, JavaScript, 이미지 요청은 정적 자산 계층에서 바로 반환하고, 동적 요청만 Worker로 전달합니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;정적 파일 요청 &amp;rarr; CDN&amp;middot;Assets
동적 페이지 요청 &amp;rarr; Worker
API 요청 &amp;rarr; Worker
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Worker가 요청을 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 요청이 Worker 진입점으로 전달되고, Worker가 정적 자산과 페이지 요청을 구분합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;모든 요청 &amp;rarr; Worker &amp;rarr; Assets 또는 서버 핸들러
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 프로젝트 소스에서는 Worker가 이미지 최적화 요청을 직접 분기하고, 나머지 요청은 Vinext 핸들러로 전달하는 것을 확인했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. Sites는 백엔드도 만들 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능합니다. Worker 서버 코드가 포함되는 가장 중요한 이유 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에서 구현할 수 있는 백엔드 기능은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능구현 방식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Next.js Route Handler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;폼 접수&lt;/td&gt;
&lt;td&gt;POST /api/contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;외부 AI 호출&lt;/td&gt;
&lt;td&gt;Worker에서 fetch()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;D1 데이터베이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이미지 업로드&lt;/td&gt;
&lt;td&gt;R2 저장소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결제 Webhook&lt;/td&gt;
&lt;td&gt;공개 API 경로에서 수신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram Webhook&lt;/td&gt;
&lt;td&gt;Telegram이 Sites API로 POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;인증 + D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다국어 콘텐츠&lt;/td&gt;
&lt;td&gt;URL&amp;middot;헤더&amp;middot;데이터에 따라 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문의 폼 API는 다음처럼 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export async function POST(request: Request) {
  const form = await request.json();

  if (!form.email || !form.message) {
    return Response.json(
      { error: &quot;필수 항목이 없습니다.&quot; },
      { status: 400 },
    );
  }

  return Response.json({
    ok: true,
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서는 다음 주소로 요청할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;POST /api/contact
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites가 배포된 후에는 다음과 같은 외부 주소가 됩니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;POST https://사이트주소.chatgpt.site/api/contact
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. Worker에서 외부 API도 호출할 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker는 표준 fetch()를 이용해 외부 HTTP API를 호출할 수 있습니다. Cloudflare 공식 문서도 Workers 내부에서 Fetch API를 이용해 HTTP 리소스를 비동기로 가져올 수 있다고 설명합니다. &lt;a href=&quot;https://developers.cloudflare.com/workers/runtime-apis/fetch/&quot;&gt;Cloudflare Workers Fetch API 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 DeepSeek API를 호출하는 백엔드는 다음과 같은 구조로 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export async function POST(request: Request) {
  const { message } = await request.json();

  const response = await fetch(
    &quot;https://api.deepseek.com/chat/completions&quot;,
    {
      method: &quot;POST&quot;,
      headers: {
        Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`,
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify({
        model: &quot;deepseek-v4-flash&quot;,
        messages: [
          {
            role: &quot;user&quot;,
            content: message,
          },
        ],
      }),
    },
  );

  const result = await response.json();
  return Response.json(result);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 이용하면 다음 기능을 만들 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&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;상품 설명 생성&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 data-ke-size=&quot;size16&quot;&gt;다만 API 키를 브라우저 코드에 직접 넣으면 안 됩니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;잘못된 구조
브라우저 &amp;rarr; DeepSeek API
API 키가 사용자에게 노출될 수 있음

권장 구조
브라우저 &amp;rarr; Sites Worker &amp;rarr; DeepSeek API
API 키는 Worker 환경변수에서만 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. API 키와 운영 환경변수는 어디에 저장할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트 소스에 API 키를 직접 작성하면 Git 기록과 빌드 산출물에 비밀정보가 남을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 코드는 피해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;const apiKey = &quot;실제 API 키&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에는 운영용 환경변수를 별도로 등록하는 기능이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pH5oF/dJMcajpqLMB/MIjo1NaAei3ee2ppZpC9K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pH5oF/dJMcajpqLMB/MIjo1NaAei3ee2ppZpC9K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pH5oF/dJMcajpqLMB/MIjo1NaAei3ee2ppZpC9K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpH5oF%2FdJMcajpqLMB%2FMIjo1NaAei3ee2ppZpC9K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;158&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수는 일반 값과 비밀값으로 구분할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;일반 환경변수
PUBLIC_SITE_NAME=WMU Global Hub

비밀 환경변수
DEEPSEEK_API_KEY=...
TELEGRAM_BOT_TOKEN=...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀값은 소스 코드나 hosting.json에 넣지 않고 Sites의 운영 설정에 보관해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트는 현재 외부 API를 사용하지 않기 때문에 운영 환경변수가 등록되어 있지 않았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15. D1 데이터베이스는 어떤 역할을 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D1은 Cloudflare Workers에서 사용하는 서버리스 SQL 데이터베이스입니다. SQLite와 호환되는 방식으로 데이터를 저장하고 조회할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare 공식 문서에 따르면 Worker는 D1 바인딩을 통해 SQL 쿼리를 실행할 수 있습니다. &lt;a href=&quot;https://developers.cloudflare.com/d1/worker-api/&quot;&gt;Cloudflare D1 Worker API 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에서 D1을 연결하면 다음 기능을 만들 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원 프로필&lt;/li&gt;
&lt;li&gt;문의 내역&lt;/li&gt;
&lt;li&gt;게시판&lt;/li&gt;
&lt;li&gt;학습 진도&lt;/li&gt;
&lt;li&gt;퀴즈 문제와 답안&lt;/li&gt;
&lt;li&gt;포인트 내역&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 대화 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker에서는 보통 다음과 같은 방식으로 접근합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default {
  async fetch(request, env) {
    const result = await env.DB
      .prepare(&quot;SELECT * FROM users WHERE id = ?&quot;)
      .bind(&quot;user-123&quot;)
      .first();

    return Response.json(result);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare는 바인딩을 통해 Worker가 D1 같은 플랫폼 리소스에 접근한다고 설명합니다. &lt;a href=&quot;https://developers.cloudflare.com/workers/runtime-apis/bindings/&quot;&gt;Cloudflare Workers Bindings 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트의 설정에는 D1이 연결되어 있지 않았습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;d1&quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 실제 회원&amp;middot;게시물&amp;middot;포인트 데이터를 저장하지 않는 소개용 사이트이기 때문입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;16. R2 파일 저장소는 어떤 역할을 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R2는 이미지, 동영상, PDF, 첨부파일 같은 객체 파일을 저장하는 용도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에 R2를 연결하면 다음 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원 프로필 이미지&lt;/li&gt;
&lt;li&gt;강의 자료 업로드&lt;/li&gt;
&lt;li&gt;PDF 교재 저장&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 생성 이미지&lt;/li&gt;
&lt;li&gt;관리자 첨부파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 이미지와 사용자 업로드 파일은 구분해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정적 이미지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트를 만들 때부터 프로젝트에 포함된 파일입니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public/hero-women-network.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트를 다시 배포해야 이미지가 변경됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 업로드 파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트 운영 중 사용자가 올리는 파일입니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;사용자 &amp;rarr; Worker 업로드 API &amp;rarr; R2 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트를 다시 빌드하지 않아도 파일을 추가하고 삭제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트는 현재 R2를 사용하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;r2&quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;17. ChatGPT 로그인도 사용할 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 프로젝트에는 chatgpt-auth.ts라는 인증 보조 코드가 포함되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 Sites 실행환경이 전달하는 인증 헤더를 읽어 사용자 이메일과 이름을 확인하는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 주요 헤더는 다음과 같았습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;oai-authenticated-user-email
oai-authenticated-user-full-name
oai-authenticated-user-full-name-encoding
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 동작은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NfvJd/dJMcadJxvWE/FyhdeIkCJ1lkUGCv1pq451/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NfvJd/dJMcadJxvWE/FyhdeIkCJ1lkUGCv1pq451/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NfvJd/dJMcadJxvWE/FyhdeIkCJ1lkUGCv1pq451/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNfvJd%2FdJMcadJxvWE%2FFyhdeIkCJ1lkUGCv1pq451%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;925&quot; height=&quot;54&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트 코드에서는 다음처럼 사용자를 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const email = requestHeaders.get(
  &quot;oai-authenticated-user-email&quot;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증되지 않은 사용자는 로그인 경로로 이동시킬 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;/signin-with-chatgpt
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 WMU 사이트의 접근 모드는 Public이기 때문에 누구나 사이트를 열 수 있습니다. 다만 사이트 일부 기능만 로그인 사용자에게 제공하는 식으로 확장할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;18. 공개 사이트와 비공개 사이트는 무엇이 다를까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites는 프로젝트별로 접근 정책을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 WMU 사이트는 공개 모드입니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;access_mode: public
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개 사이트는 외부 사용자가 chatgpt.site 주소로 접속할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비공개 또는 제한된 사이트라면 특정 사용자나 그룹만 접근하도록 구성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;공개 사이트
&amp;rarr; 누구나 URL로 접속

제한된 사이트
&amp;rarr; 인증된 사용자만 접속

사용자 지정 접근
&amp;rarr; 허용된 사용자&amp;middot;그룹만 접속
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 정책은 애플리케이션 코드와 별도로 Sites 배포 계층에서 관리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 코드에 로그인 페이지가 있어도 Sites 접근 정책이 Public이면 기본 사이트 주소 자체는 공개될 수 있습니다. 반대로 애플리케이션이 공개 페이지처럼 보여도 Sites 접근 정책이 제한적이면 허용된 사용자만 진입할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;19. chatgpt.site 주소는 어떻게 연결될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에서 프로젝트를 만들면 프로젝트별 슬러그를 기반으로 운영 URL이 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트는 다음 주소를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;https://wmu-global-ai-hub.tommykim1981.chatgpt.site
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 구성은 대략 다음처럼 이해할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;프로젝트 슬러그
        +
사용자 또는 소유자 식별 영역
        +
chatgpt.site
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites는 이 주소를 배포된 운영 버전과 연결합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biy8sg/dJMcagl1aDU/K84R5GM7dqeakfiRM3MmG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biy8sg/dJMcagl1aDU/K84R5GM7dqeakfiRM3MmG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biy8sg/dJMcagl1aDU/K84R5GM7dqeakfiRM3MmG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiy8sg%2FdJMcagl1aDU%2FK84R5GM7dqeakfiRM3MmG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;868&quot; height=&quot;54&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 직접 Nginx를 설정하거나 SSL 인증서를 설치할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] DNS, TLS 인증서, 라우팅과 배포 대상 연결은 Sites의 관리형 인프라에서 처리되는 것으로 보입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;20. 커스텀 도메인도 연결할 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites 서비스에는 프로젝트별 커스텀 도메인을 조회하고 관리하는 구조가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트에는 현재 커스텀 도메인이 연결되어 있지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Custom domains: 없음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 현재는 기본 chatgpt.site 주소만 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 도메인이 지원되는 프로젝트라면 개념적으로 다음과 같이 연결할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;기본 주소
wmu-global-ai-hub....chatgpt.site

사용자 도메인
www.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 주소가 동일한 Sites 프로젝트의 운영 배포본으로 연결되는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 DNS 설정과 지원 범위는 Sites의 현재 도메인 연결 정책을 확인해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;21. Sites 배포는 어떤 단계를 거칠까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites 배포는 단순히 폴더를 서버에 복사하는 작업이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 프로젝트 구조를 기준으로 보면 다음 단계로 나뉩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 소스 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React&amp;middot;Next.js 코드와 이미지, CSS, Worker 진입점, 배포 메타데이터를 준비합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 로컬 빌드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vinext가 Next.js 호환 기능을 해석하고 Vite가 Worker용 산출물을 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;vinext build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 산출물 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 검증 스크립트는 다음 조건을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;dist/server/index.js가 존재하는가?
dist/.openai/hosting.json이 존재하는가?
서버 모듈이 ESM 형식인가?
기본 내보내기에 fetch()가 있는가?
hosting.json이 유효한 JSON인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 코드의 핵심은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const worker = await import(workerUrl.href);

if (
  !worker.default ||
  typeof worker.default.fetch !== &quot;function&quot;
) {
  throw new Error(
    &quot;Worker 진입점이 올바르지 않습니다.&quot;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계: 소스 커밋 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포할 소스를 특정 Git 커밋으로 확정합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5단계: 불변 버전 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 커밋과 소스 아카이브를 Sites의 저장된 버전으로 만듭니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6단계: 운영 배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장된 버전을 관리형 Worker 환경에 배포합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7단계: 배포 상태 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 상태가 성공인지 실패인지 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;pending
&amp;rarr; building
&amp;rarr; publishing
&amp;rarr; succeeded 또는 failed
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8단계: 운영 URL 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공한 운영 배포본을 사이트의 현재 공개 URL과 연결합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;22. 소스 저장과 배포는 왜 분리되어 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 버전과 운영 배포를 분리하면 같은 소스를 다시 배포하거나 이전 버전으로 돌아갈 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/55Hm5/dJMcagl1aG5/pNqBo6DBXeK72g9DXeC6tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/55Hm5/dJMcagl1aG5/pNqBo6DBXeK72g9DXeC6tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/55Hm5/dJMcagl1aG5/pNqBo6DBXeK72g9DXeC6tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F55Hm5%2FdJMcagl1aG5%2FpNqBo6DBXeK72g9DXeC6tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;262&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 버전 3에서 문제가 발생하면 이전에 저장된 버전 2를 다시 운영에 배포하는 구조를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스만 저장됐다고 외부에 공개되는 것도 아니고, 로컬 코드가 수정됐다고 운영 사이트가 즉시 바뀌는 것도 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 사이트가 바뀌려면 수정된 소스를 새 버전으로 저장하고 그 버전을 배포해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;23. Sites와 일반 VPS는 무엇이 다를까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 차이는 서버 프로세스의 유지 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분ChatGPT Sites일반 VPS&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실행 방식&lt;/td&gt;
&lt;td&gt;요청 기반 Worker&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;Nginx 설정&lt;/td&gt;
&lt;td&gt;일반적으로 불필요&lt;/td&gt;
&lt;td&gt;직접 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;관리형 URL에 연결&lt;/td&gt;
&lt;td&gt;직접 또는 인증서 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정적 파일&lt;/td&gt;
&lt;td&gt;Assets로 배포&lt;/td&gt;
&lt;td&gt;디스크&amp;middot;Nginx 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Worker 요청 처리&lt;/td&gt;
&lt;td&gt;Node&amp;middot;Python 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;D1 등 바인딩&lt;/td&gt;
&lt;td&gt;PostgreSQL&amp;middot;MySQL 직접 운영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 저장&lt;/td&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;로컬 디스크&amp;middot;S3&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;Telegram 폴링&lt;/td&gt;
&lt;td&gt;부적합&lt;/td&gt;
&lt;td&gt;적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram Webhook&lt;/td&gt;
&lt;td&gt;적합&lt;/td&gt;
&lt;td&gt;적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw Gateway&lt;/td&gt;
&lt;td&gt;부적합&lt;/td&gt;
&lt;td&gt;적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반 TCP 서버&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 data-ke-size=&quot;size16&quot;&gt;Sites는 웹 요청을 받아 짧게 처리하고 응답하는 서비스에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 VPS는 프로세스를 계속 실행해야 하는 프로그램에 적합합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;24. 왜 Work VM에서는 Telegram 봇이 멈췄는데 Sites 웹사이트는 계속 열릴까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Work VM에서 Hermes와 OpenClaw를 실행해 Telegram 봇을 연결했지만, 장기 실행 중 다음 오류가 확인됐습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Network access to
https://api.telegram.org:443
was blocked by policy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hermes나 OpenClaw의 프로그램 문제가 아니라 Work VM의 장기 실행 프로세스와 외부 네트워크 정책 때문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Telegram 폴링 봇은 다음 구조입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djM9Tm/dJMcaheeDkh/e1mZYu2ktkSK08ar3cju91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djM9Tm/dJMcaheeDkh/e1mZYu2ktkSK08ar3cju91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djM9Tm/dJMcaheeDkh/e1mZYu2ktkSK08ar3cju91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjM9Tm%2FdJMcaheeDkh%2Fe1mZYu2ktkSK08ar3cju91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;245&quot; height=&quot;470&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로그램은 다음 조건을 만족해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 계속 살아 있어야 함&lt;/li&gt;
&lt;li&gt;Telegram 서버에 반복 접속해야 함&lt;/li&gt;
&lt;li&gt;네트워크 연결이 유지돼야 함&lt;/li&gt;
&lt;li&gt;메모리와 세션을 계속 유지해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Sites는 요청 기반입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXZBFe/dJMcafU1OCg/p5m4VJlTuDjz5IRU7wlxp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXZBFe/dJMcafU1OCg/p5m4VJlTuDjz5IRU7wlxp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXZBFe/dJMcafU1OCg/p5m4VJlTuDjz5IRU7wlxp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXZBFe%2FdJMcafU1OCg%2Fp5m4VJlTuDjz5IRU7wlxp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;177&quot; height=&quot;470&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker가 24시간 하나의 프로세스로 실행되는 것이 아니라, 요청이 들어왔을 때 실행될 수 있는 코드로 배포됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Work VM이 없어도 운영 사이트가 계속 응답할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;25. Telegram을 Sites와 연결하는 방법은 없을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenClaw 전체 Gateway를 Sites에 실행하는 방식은 적합하지 않습니다. 하지만 Telegram Webhook 방식의 경량 챗봇은 만들 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;폴링 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;봇이 Telegram 서버에 계속 질문합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;새 메시지 있습니까?
새 메시지 있습니까?
새 메시지 있습니까?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장기 실행 프로세스가 필요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Webhook 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Telegram이 새 메시지가 생겼을 때 Sites API로 알려 줍니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Telegram
&amp;rarr; POST /api/telegram/webhook
&amp;rarr; Sites Worker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oDzPK/dJMcaiYny4O/Wez7YDF8L2VZWBInasofnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oDzPK/dJMcaiYny4O/Wez7YDF8L2VZWBInasofnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oDzPK/dJMcaiYny4O/Wez7YDF8L2VZWBInasofnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoDzPK%2FdJMcaiYny4O%2FWez7YDF8L2VZWBInasofnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;494&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 Worker가 계속 실행될 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Telegram이 메시지를 전달할 때만 Worker가 실행됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;26. Telegram Webhook 백엔드는 어떻게 구성할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 API 경로는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;POST /api/telegram/webhook
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리 순서는 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Telegram이 메시지 업데이트를 전송&lt;/li&gt;
&lt;li&gt;요청이 Telegram에서 온 것인지 검증&lt;/li&gt;
&lt;li&gt;메시지를 보낸 사용자 ID 확인&lt;/li&gt;
&lt;li&gt;허용된 사용자만 처리&lt;/li&gt;
&lt;li&gt;메시지 내용 추출&lt;/li&gt;
&lt;li&gt;DeepSeek API 호출&lt;/li&gt;
&lt;li&gt;답변 내용 정리&lt;/li&gt;
&lt;li&gt;Telegram sendMessage API 호출&lt;/li&gt;
&lt;li&gt;Telegram에 HTTP 200 응답&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드 구조는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export async function POST(request: Request) {
  const update = await request.json();

  const message = update.message;
  if (!message?.text) {
    return Response.json({ ok: true });
  }

  const answer = await askDeepSeek(message.text);

  await sendTelegramMessage(
    message.chat.id,
    answer,
  );

  return Response.json({ ok: true });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepSeek 호출 함수는 다음과 같이 분리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;async function askDeepSeek(message: string) {
  const response = await fetch(
    &quot;https://api.deepseek.com/chat/completions&quot;,
    {
      method: &quot;POST&quot;,
      headers: {
        Authorization:
          `Bearer ${process.env.DEEPSEEK_API_KEY}`,
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify({
        model: &quot;deepseek-v4-flash&quot;,
        messages: [
          {
            role: &quot;system&quot;,
            content:
              &quot;질문에 자연스러운 한국어로 답하세요.&quot;,
          },
          {
            role: &quot;user&quot;,
            content: message,
          },
        ],
      }),
    },
  );

  const result = await response.json();
  return result.choices[0].message.content;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Telegram 발송 함수는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;async function sendTelegramMessage(
  chatId: number,
  text: string,
) {
  await fetch(
    `https://api.telegram.org/bot${
      process.env.TELEGRAM_BOT_TOKEN
    }/sendMessage`,
    {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify({
        chat_id: chatId,
        text,
      }),
    },
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀값은 Sites 운영 환경변수에 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;DEEPSEEK_API_KEY
TELEGRAM_BOT_TOKEN
TELEGRAM_WEBHOOK_SECRET
ALLOWED_TELEGRAM_USER_ID
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;27. Sites에서 OpenClaw 전체를 실행하기 어려운 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenClaw는 단순한 API 함수가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenClaw Gateway는 다음과 같은 상태를 계속 유지합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Telegram&amp;middot;Discord 등 채널 연결&lt;/li&gt;
&lt;li&gt;세션&lt;/li&gt;
&lt;li&gt;대화 기록&lt;/li&gt;
&lt;li&gt;작업 큐&lt;/li&gt;
&lt;li&gt;도구 실행&lt;/li&gt;
&lt;li&gt;파일 접근&lt;/li&gt;
&lt;li&gt;장기 실행 작업&lt;/li&gt;
&lt;li&gt;백그라운드 이벤트&lt;/li&gt;
&lt;li&gt;주기적 작업&lt;/li&gt;
&lt;li&gt;Gateway WebSocket&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 프로그램은 일반적으로 다음 환경이 적합합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS Lightsail&lt;/li&gt;
&lt;li&gt;일반 VPS&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Docker 서버&lt;/li&gt;
&lt;li&gt;Railway의 장기 실행 서비스&lt;/li&gt;
&lt;li&gt;Fly.io VM&lt;/li&gt;
&lt;li&gt;집 PC&lt;/li&gt;
&lt;li&gt;Mac mini&lt;/li&gt;
&lt;li&gt;NAS&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites의 Worker는 HTTP 요청을 받아 처리하는 백엔드에는 적합하지만, OpenClaw 전체 Gateway를 계속 실행하는 서버를 대체하는 용도는 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 OpenClaw의 일부 기능을 API 형태로 분리하면 Sites와 조합할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Sites
&amp;rarr; 사용자 화면과 Webhook

외부 VPS
&amp;rarr; OpenClaw Gateway

Sites와 VPS
&amp;rarr; HTTPS API로 연결
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;28. Worker 백엔드의 상태는 어디에 저장할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker 코드의 전역 변수나 메모리에 중요한 상태를 계속 보관하면 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 구조는 신뢰하기 어렵습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;let userPoints = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청마다 실행 인스턴스가 달라지거나 재시작될 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태는 용도에 맞는 저장소에 넣어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 유형적합한 저장소&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;회원&amp;middot;게시물&amp;middot;포인트&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이미지&amp;middot;PDF&amp;middot;동영상&lt;/td&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;간단한 설정&amp;middot;캐시&lt;/td&gt;
&lt;td&gt;KV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실시간 상태&amp;middot;동시성&lt;/td&gt;
&lt;td&gt;Durable Objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비밀 API 키&lt;/td&gt;
&lt;td&gt;운영 환경변수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정적 사이트 이미지&lt;/td&gt;
&lt;td&gt;프로젝트의 public/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트는 현재 실제 운영 데이터를 저장하지 않으므로 별도 저장소를 사용하지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;29. 서버 렌더링은 무엇이며 왜 필요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 렌더링은 브라우저에서 모든 화면을 만든 뒤 보여 주는 대신, 서버가 먼저 HTML을 만들어 반환하는 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYOl21/dJMcagNcy01/JytWTXaIDdK23BAfT0XLG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYOl21/dJMcagNcy01/JytWTXaIDdK23BAfT0XLG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYOl21/dJMcagNcy01/JytWTXaIDdK23BAfT0XLG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYOl21%2FdJMcagNcy01%2FJytWTXaIDdK23BAfT0XLG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;54&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 렌더링은 다음 상황에 유용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 사용자별 화면&lt;/li&gt;
&lt;li&gt;데이터베이스를 조회한 페이지&lt;/li&gt;
&lt;li&gt;검색엔진이 읽어야 하는 콘텐츠&lt;/li&gt;
&lt;li&gt;첫 화면을 빠르게 제공해야 하는 경우&lt;/li&gt;
&lt;li&gt;API 키를 브라우저에 노출하지 않아야 하는 경우&lt;/li&gt;
&lt;li&gt;서버에서 권한을 검사해야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vinext는 Next.js의 App Router와 React Server Components를 Worker 환경에서 실행할 수 있도록 연결합니다. &lt;a href=&quot;https://github.com/cloudflare/vinext&quot;&gt;Cloudflare Vinext 공식 저장소&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;30. 정적 사이트라면 Worker를 빼도 되지 않을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 완전한 정적 사이트라면 HTML, CSS, JavaScript, 이미지만 배포하는 구조도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Sites의 표준 프로젝트는 다음 이유로 Worker 형식을 기본으로 사용하는 것으로 보입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정적 사이트와 동적 사이트를 같은 배포 규격으로 관리할 수 있음&lt;/li&gt;
&lt;li&gt;나중에 API를 추가하기 쉬움&lt;/li&gt;
&lt;li&gt;서버 렌더링 지원&lt;/li&gt;
&lt;li&gt;ChatGPT 인증 연결&lt;/li&gt;
&lt;li&gt;이미지 최적화&lt;/li&gt;
&lt;li&gt;D1&amp;middot;R2 바인딩&lt;/li&gt;
&lt;li&gt;Next.js Route Handler 지원&lt;/li&gt;
&lt;li&gt;공개&amp;middot;비공개 접근 정책 적용&lt;/li&gt;
&lt;li&gt;동적 오류 페이지와 라우팅&lt;/li&gt;
&lt;li&gt;외부 API 호출&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 지금은 정적인 랜딩 페이지라도 나중에 회원, 게시판, AI 챗봇, 문의 저장 기능을 붙일 수 있는 기반을 갖고 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;31. 현재 WMU 사이트는 정적 사이트인가, 동적 사이트인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는 &lt;b&gt;정적 콘텐츠 중심의 Worker 애플리케이션&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 자체는 대부분 코드에 고정되어 있습니다.&lt;/p&gt;
&lt;pre class=&quot;d&quot;&gt;&lt;code&gt;const pillars = [
  {
    title: &quot;배움이 가능성이 되는 곳&quot;,
    body: &quot;AI 리터러시부터...&quot;,
  },
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스나 외부 API는 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음 동적 기능은 브라우저 React 상태로 작동합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모바일 메뉴 열기&lt;/li&gt;
&lt;li&gt;핵심 서비스 탭 전환&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 data-ke-size=&quot;size16&quot;&gt;배포 형식은 Worker이기 때문에 앞으로 서버 기능을 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음처럼 구분하는 것이 정확합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;콘텐츠 성격
&amp;rarr; 대부분 정적

브라우저 UI
&amp;rarr; 일부 동적

배포 방식
&amp;rarr; Worker 애플리케이션

현재 백엔드 데이터
&amp;rarr; 없음

백엔드 확장 가능성
&amp;rarr; 있음
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;32. 이미지 파일은 어디에 있고 어떻게 제공될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WMU 사이트의 메인 이미지는 다음 파일입니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public/hero-women-network.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS에서는 다음처럼 참조합니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.hero-art {
  background-image:
    url(&quot;/hero-women-network.png&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드할 때 이 파일은 정적 Assets에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 사용자가 이미지를 요청하면 Sites의 배포 환경에서 해당 정적 자산을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 이미지 최적화 경로를 사용하는 경우 Worker는 원본 이미지를 Assets에서 읽고 이미지 처리 기능을 거쳐 반환할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Worker 코드에는 다음 환경 바인딩이 정의돼 있습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;interface Env {
  ASSETS: Fetcher;
  DB: D1Database;
  IMAGES: {
    input(stream: ReadableStream): unknown;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 역할은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인딩역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSETS&lt;/td&gt;
&lt;td&gt;정적 이미지&amp;middot;CSS&amp;middot;JS 파일 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB&lt;/td&gt;
&lt;td&gt;D1 데이터베이스 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMAGES&lt;/td&gt;
&lt;td&gt;이미지 크기&amp;middot;포맷 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 사이트에서 DB를 실제로 사용하는 것은 아니지만 표준 Worker 템플릿에는 연결 형태가 준비되어 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;33. Sites는 Cloudflare Pages와 같은 것일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전히 같다고 단정할 수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 프로젝트는 다음 Cloudflare 기술을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cloudflare Vite Plugin&lt;/li&gt;
&lt;li&gt;Wrangler&lt;/li&gt;
&lt;li&gt;Worker fetch() 진입점&lt;/li&gt;
&lt;li&gt;Static Assets 바인딩&lt;/li&gt;
&lt;li&gt;이미지 처리 바인딩&lt;/li&gt;
&lt;li&gt;선택적 D1&lt;/li&gt;
&lt;li&gt;선택적 R2&lt;/li&gt;
&lt;li&gt;nodejs_compat 호환 플래그&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Cloudflare Workers 호환 배포 구조인 것은 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 사용자의 개인 Cloudflare 계정에 직접 Worker가 생성됐다고 확인되지는 않았습니다. Sites가 자체적으로 관리하는 Cloudflare 기반 인프라에 배포하는 구조로 보는 것이 적절합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 일반적으로 다음 작업을 직접 하지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cloudflare 계정 연결&lt;/li&gt;
&lt;li&gt;Wrangler 로그인&lt;/li&gt;
&lt;li&gt;Worker 이름 생성&lt;/li&gt;
&lt;li&gt;DNS 레코드 설정&lt;/li&gt;
&lt;li&gt;TLS 인증서 설정&lt;/li&gt;
&lt;li&gt;Assets 버킷 설정&lt;/li&gt;
&lt;li&gt;D1 리소스 ID 직접 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 Sites 서비스가 대신 관리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;34. 배포한 사이트는 Work VM이 꺼져도 계속 동작할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 WMU 사례에서는 로컬 작업공간이 없어졌는데도 Sites의 운영 URL과 원격 버전이 유지됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이미 성공적으로 배포된 사이트의 운영은 Work VM 프로세스에 의존하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 다음은 구분해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계속 동작하는 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 배포된 사이트&lt;/li&gt;
&lt;li&gt;정적 이미지와 CSS&lt;/li&gt;
&lt;li&gt;Worker 페이지 응답&lt;/li&gt;
&lt;li&gt;배포된 API 경로&lt;/li&gt;
&lt;li&gt;연결된 D1&amp;middot;R2&lt;/li&gt;
&lt;li&gt;운영 환경변수&lt;/li&gt;
&lt;li&gt;공개 URL&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Work VM이 다시 필요한 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스 수정&lt;/li&gt;
&lt;li&gt;새 이미지 생성&lt;/li&gt;
&lt;li&gt;기능 추가&lt;/li&gt;
&lt;li&gt;디자인 변경&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 data-ke-size=&quot;size16&quot;&gt;사이트 수정이 필요하면 원격 저장 버전에서 소스를 다시 복원한 뒤 변경하고 새 버전을 배포할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;35. Sites로 만들기 적합한 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites는 다음과 같은 프로젝트에 잘 맞습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;콘텐츠와 소개 사이트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회사 홈페이지&lt;/li&gt;
&lt;li&gt;브랜드 사이트&lt;/li&gt;
&lt;li&gt;행사 안내&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;h3 data-ke-size=&quot;size23&quot;&gt;데이터 기반 웹앱&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대시보드&lt;/li&gt;
&lt;li&gt;관리자 화면&lt;/li&gt;
&lt;li&gt;교육 플랫폼&lt;/li&gt;
&lt;li&gt;퀴즈 서비스&lt;/li&gt;
&lt;li&gt;게시판&lt;/li&gt;
&lt;li&gt;신청&amp;middot;접수 시스템&lt;/li&gt;
&lt;li&gt;포인트 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI 기능이 포함된 사이트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&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;문제 생성&lt;/li&gt;
&lt;li&gt;학습 피드백&lt;/li&gt;
&lt;li&gt;이미지 분석 결과 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Webhook 기반 연동&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Telegram 챗봇&lt;/li&gt;
&lt;li&gt;결제 결과 수신&lt;/li&gt;
&lt;li&gt;GitHub 이벤트&lt;/li&gt;
&lt;li&gt;외부 서비스 알림&lt;/li&gt;
&lt;li&gt;폼 접수 후 이메일 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;36. Sites에 적합하지 않은 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 기능은 일반 VPS나 전용 백엔드가 더 적합합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenClaw Gateway 전체 실행&lt;/li&gt;
&lt;li&gt;Telegram 무한 폴링&lt;/li&gt;
&lt;li&gt;임의 포트의 TCP 서버&lt;/li&gt;
&lt;li&gt;SSH 서버&lt;/li&gt;
&lt;li&gt;장기간 실행되는 Python 프로세스&lt;/li&gt;
&lt;li&gt;시스템 패키지를 계속 사용하는 데몬&lt;/li&gt;
&lt;li&gt;서버 로컬 디스크에 의존하는 프로그램&lt;/li&gt;
&lt;li&gt;장기 GPU 작업&lt;/li&gt;
&lt;li&gt;지속적인 WebSocket 서버 상태&lt;/li&gt;
&lt;li&gt;Docker Compose 전체 스택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare Workers 플랫폼 자체에는 Queues, Workflows, Durable Objects, Cron 같은 다양한 기능이 있지만, ChatGPT Sites가 그 기능을 모두 직접 노출한다고 확인된 것은 아닙니다. Sites에서 제공되는 실제 기능 범위 안에서 설계해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;37. Sites를 이해하기 위한 가장 쉬운 비유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites를 식당으로 비유하면 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Work VM은 주방 테스트 공간&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메뉴를 개발함&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;h3 data-ke-size=&quot;size23&quot;&gt;저장된 사이트 버전은 확정된 레시피&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 시점의 레시피를 보관함&lt;/li&gt;
&lt;li&gt;이전 레시피로 돌아갈 수 있음&lt;/li&gt;
&lt;li&gt;누가 무엇을 배포했는지 기준이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Worker는 주문 처리 직원&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주문이 들어올 때 실행됨&lt;/li&gt;
&lt;li&gt;요청을 해석함&lt;/li&gt;
&lt;li&gt;정적 파일이나 서버 결과를 반환함&lt;/li&gt;
&lt;li&gt;필요한 경우 데이터베이스와 외부 API를 호출함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Assets는 미리 준비된 음식과 재료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;이미지&lt;/li&gt;
&lt;li&gt;폰트&lt;/li&gt;
&lt;li&gt;정적 HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;D1은 주문&amp;middot;회원 장부&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원&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;h3 data-ke-size=&quot;size23&quot;&gt;R2는 창고&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지&lt;/li&gt;
&lt;li&gt;PDF&lt;/li&gt;
&lt;li&gt;영상&lt;/li&gt;
&lt;li&gt;업로드 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;chatgpt.site는 식당 주소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 사용자가 찾아오는 공개 주소&lt;/li&gt;
&lt;li&gt;현재 운영 버전과 연결됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;38. 전체 구조를 한 장으로 정리하면&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv9jku/dJMcagGqEQe/wzO86mjlL6mIAot4gTAxpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv9jku/dJMcagGqEQe/wzO86mjlL6mIAot4gTAxpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv9jku/dJMcagGqEQe/wzO86mjlL6mIAot4gTAxpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv9jku%2FdJMcagGqEQe%2FwzO86mjlL6mIAot4gTAxpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;886&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;39. 자주 묻는 질문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ChatGPT가 만든 사이트는 VM 안에서 계속 실행되나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. VM은 사이트를 만들고 빌드하는 작업환경입니다. 성공적으로 배포된 운영 사이트는 별도의 Sites 운영 환경에서 제공됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VM 폴더가 삭제되면 사이트도 없어지나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 WMU 사례에서는 로컬 폴더가 없어져도 원격 소스 버전과 운영 URL이 유지됐습니다. 원격 버전에서 체크아웃을 다시 복원할 수 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sites는 정적 사이트만 만들 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 정적 자산과 Worker 서버 코드가 함께 배포될 수 있어 API, 서버 렌더링, 인증, 데이터베이스와 외부 API 호출을 구현할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Worker는 일반 Node.js 서버인가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 계속 실행되는 Node.js 프로세스가 아니라 HTTP 요청을 fetch() 핸들러로 처리하는 서버리스 런타임입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;백엔드 API를 만들 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능합니다. Next.js Route Handler 또는 Worker 코드를 이용해 REST API와 Webhook을 만들 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스도 가능한가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites에서 D1 바인딩을 연결할 수 있는 구조가 확인됐습니다. 현재 WMU 사이트는 데이터베이스를 사용하지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미지 업로드도 가능한가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R2 저장소를 연결하면 구현할 수 있는 구조입니다. 현재 WMU 사이트는 프로젝트에 포함된 정적 이미지만 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DeepSeek API를 연결할 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Worker의 fetch()로 외부 API를 호출하고, API 키를 Sites의 비밀 환경변수에 저장하는 구조로 구현할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Telegram 봇도 만들 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webhook 방식의 Telegram 봇은 적합합니다. 반면 계속 실행되는 폴링 방식이나 OpenClaw 전체 Gateway는 Sites보다 VPS가 적합합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sites에 OpenClaw를 설치할 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenClaw의 전체 Gateway는 장기 실행 프로세스와 지속적인 채널 연결이 필요하므로 Worker 실행 모델과 맞지 않습니다. 일부 기능을 API 또는 Webhook으로 분리하는 방식은 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자의 Cloudflare 대시보드에서 Worker를 볼 수 있나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인할 수 없습니다. 현재 프로젝트는 Cloudflare Worker 호환 기술을 사용하지만, 사용자의 개인 Cloudflare 계정에 직접 배포됐다는 정보는 확인되지 않았습니다. Sites가 관리하는 인프라에 배포되는 구조로 이해하는 것이 적절합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;40. 결론: Sites는 &amp;lsquo;코드 생성기&amp;rsquo;보다 &amp;lsquo;관리형 웹 배포 시스템&amp;rsquo;에 가깝다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT Sites의 핵심은 예쁜 웹페이지 코드를 생성하는 데서 끝나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트가 외부에서 실제로 작동하려면 다음 과정이 모두 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;기획
&amp;rarr; 디자인
&amp;rarr; React&amp;middot;Next.js 구현
&amp;rarr; Vinext&amp;middot;Vite 빌드
&amp;rarr; Worker 산출물 생성
&amp;rarr; 정적 Assets 패키징
&amp;rarr; 산출물 검증
&amp;rarr; 소스 버전 저장
&amp;rarr; 운영 배포
&amp;rarr; 공개 URL 연결
&amp;rarr; 접근 정책 적용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sites는 이 과정을 하나의 작업 흐름으로 묶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Work VM은 사이트를 만드는 공간이고, Vinext는 Next.js를 Vite&amp;middot;Worker 환경에 연결하며, Worker는 외부 요청을 처리합니다. 정적 파일은 Assets로 제공되고, 필요한 경우 D1&amp;middot;R2&amp;middot;환경변수&amp;middot;인증&amp;middot;외부 API를 연결할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Sites는 단순한 정적 홈페이지 제작 도구라기보다 다음에 더 가깝습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React와 Next.js로 만든 프런트엔드와 Worker 백엔드를 빌드하고, 버전으로 저장하고, 관리형 주소와 권한을 붙여 외부에 배포하는 ChatGPT 내장형 풀스택 호스팅 시스템&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모든 서버 프로그램을 Sites에서 실행할 수 있는 것은 아닙니다. 웹 요청에 따라 실행되는 API와 Webhook에는 적합하지만, OpenClaw Gateway처럼 계속 살아 있어야 하는 프로그램은 VPS나 장기 실행 컨테이너가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트, AI 상담, 교육 플랫폼, 신청 시스템, 관리자 페이지, Telegram Webhook처럼 요청 중심으로 동작하는 서비스라면 Sites의 구조를 활용할 수 있습니다. 반대로 지속적인 프로세스와 자유로운 서버 제어가 핵심이라면 일반 클라우드 서버를 선택해야 합니다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1793</guid>
      <comments>https://javaexpert.tistory.com/1793#entry1793comment</comments>
      <pubDate>Fri, 24 Jul 2026 15:03:48 +0900</pubDate>
    </item>
    <item>
      <title>AI가 &amp;lsquo;도구&amp;rsquo;가 아니라 &amp;lsquo;팀원&amp;rsquo;이 되는 회사 (Buzz)</title>
      <link>https://javaexpert.tistory.com/1792</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;잭 도시의 Block이 공개한 협업 플랫폼 &amp;lsquo;Buzz&amp;rsquo;가 보여주는 미래&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이미 업무에서 인공지능을 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일을 정리하고, 문서를 요약하고, 코드를 작성하고, 회의 내용을 정리한다. 그러나 현재의 AI는 대부분 업무를 돕는 &lt;b&gt;보조 도구&lt;/b&gt;에 머물러 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 AI에게 질문한다.&lt;br /&gt;AI가 답변을 만든다.&lt;br /&gt;그리고 실제 업무는 다시 사람이 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI가 단순히 질문에 답하는 도구가 아니라, 프로젝트에 직접 참여하는 팀원이라면 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 팀 채팅방에 들어오고, 과거 대화를 검색하고, 코드를 수정하고, 테스트를 실행하고, 리뷰를 요청한다. 사람은 AI가 수행한 작업을 확인하고 승인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 7월, 잭 도시가 이끄는 Block이 공개한 오픈소스 협업 플랫폼 &lt;b&gt;Buzz&lt;/b&gt;는 바로 이런 업무 환경을 지향한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz를 한 문장으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack의 팀 대화, GitHub의 코드 관리, CI/CD 자동화, 사내 검색, AI 에이전트를 하나로 통합한 업무 플랫폼&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 여러 서비스를 한 화면에 모은 제품은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz가 던지는 더 중요한 질문은 이것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 앞으로 회사에서 어떤 지위로 일하게 될 것인가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;흩어진 업무 도구를 하나로 연결하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 대부분의 개발 조직은 여러 개의 서비스를 동시에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack이나 Microsoft Teams에서 대화하고, GitHub에서 코드를 관리한다. GitHub Actions나 Jenkins로 테스트와 배포를 자동화하고, Notion이나 Confluence에 문서를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구도 별도로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 프로젝트를 진행하지만 업무 기록은 여러 공간에 흩어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원이 Slack에서 기능 개발 방향을 논의한다. 개발자는 GitHub에 코드를 올린다. 테스트 결과는 다른 자동화 도구에 저장된다. 왜 이런 결정을 내렸는지는 문서나 과거 메시지를 다시 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지나면 코드 자체는 남지만, 그 코드가 만들어진 배경은 쉽게 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 이 모든 활동을 하나의 업무 공간에서 연결하려 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀 대화&lt;/li&gt;
&lt;li&gt;프로젝트 채널&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 에이전트의 작업&lt;/li&gt;
&lt;li&gt;검색과 감사 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람과 AI가 같은 공간에서 대화하고, 작업하고, 결과를 남기는 구조다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 에이전트를 정식 팀원처럼 다룬다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz에서 가장 눈에 띄는 개념은 AI 에이전트의 위치다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 협업 서비스에서 AI는 대개 채팅방에 연결된 봇이다. 질문을 받으면 답변하거나, 특정 명령을 실행하는 정도다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 AI 에이전트를 조금 다르게 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람과 마찬가지로 AI 에이전트도 고유한 신원과 암호화 키를 가질 수 있다. 참여할 수 있는 채널과 접근할 수 있는 프로젝트가 정해지고, 어떤 작업을 수행했는지 활동 기록이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 버그 수정 에이전트는 다음과 같은 과정을 처리할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;팀 채널에 등록된 오류 내용을 확인한다.&lt;/li&gt;
&lt;li&gt;관련 프로젝트의 과거 대화를 검색한다.&lt;/li&gt;
&lt;li&gt;코드 저장소를 분석한다.&lt;/li&gt;
&lt;li&gt;수정 코드를 작성한다.&lt;/li&gt;
&lt;li&gt;테스트를 실행한다.&lt;/li&gt;
&lt;li&gt;수정안을 제출한다.&lt;/li&gt;
&lt;li&gt;사람 개발자에게 리뷰를 요청한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 AI가 모든 회사 정보에 자유롭게 접근하는 것이 아니라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트도 사람과 마찬가지로 허용된 채널과 저장소에만 접근하도록 구성할 수 있다. 특정 프로젝트에 참여시키고 싶다면 해당 에이전트를 프로젝트 채널에 초대하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 AI를 단순한 기능이 아니라 &lt;b&gt;권한과 책임 범위를 가진 업무 주체&lt;/b&gt;로 취급하는 접근이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git 브랜치 하나가 프로젝트 채팅방이 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz가 제시하는 흥미로운 방식 가운데 하나는 Git 브랜치와 채팅 채널을 연결하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 새로운 기능을 만들기 위해 브랜치를 생성하면, 해당 브랜치 전용 프로젝트 채널을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 결제 기능을 개발한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;feature/payment-api라는 브랜치가 만들어지면 관련 채널에서 다음 활동이 함께 진행된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기능 요구사항 논의&lt;/li&gt;
&lt;li&gt;코드 변경&lt;/li&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;최종 승인&lt;/li&gt;
&lt;li&gt;병합 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 환경에서는 이 정보가 Slack, GitHub Pull Request, 자동화 로그 등에 나뉘어 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz에서는 코드와 대화가 같은 프로젝트 흐름 안에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 문제가 발생했을 때 단순히 &amp;ldquo;어떤 코드가 변경됐는가&amp;rdquo;만 보는 것이 아니라, &amp;ldquo;왜 이 코드를 변경했는가&amp;rdquo;까지 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 입장에서도 큰 차이가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드만 읽는 것이 아니라 코드가 작성된 배경, 관련 대화, 테스트 결과, 리뷰 의견을 함께 이해할 수 있기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대화와 코드가 하나의 조직 기억이 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업이 AI를 업무에 적용할 때 가장 어려운 문제 중 하나는 정보가 분산돼 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 제대로 답변하려면 여러 장소에서 자료를 가져와야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Slack의 대화&lt;/li&gt;
&lt;li&gt;GitHub의 코드&lt;/li&gt;
&lt;li&gt;Notion의 문서&lt;/li&gt;
&lt;li&gt;Jira의 이슈&lt;/li&gt;
&lt;li&gt;CI/CD 테스트 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스의 데이터를 수집하고, 검색 시스템에 넣고, 서로 연결해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 처음부터 대화와 코드 활동을 같은 이벤트 구조로 저장하는 방향을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 메시지, 코드 변경, 리뷰 승인, 워크플로 실행, AI 작업 등이 하나의 연속된 기록으로 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 서비스 장애가 발생했을 때 AI에게 이렇게 물을 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오류가 과거에도 발생한 적이 있는가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 단순히 문서에서 비슷한 문장을 찾는 데 그치지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 장애 대화, 당시 수정된 코드, 테스트 결과, 작업한 사람, 재발 방지 조치까지 함께 찾아낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 축적된 기록은 회사의 &lt;b&gt;조직 기억&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 퇴사하거나 담당자가 바뀌어도 프로젝트가 어떤 과정을 거쳐 현재 모습이 됐는지 추적할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모든 작업에 서명이 붙는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 Nostr 프로토콜의 이벤트 구조를 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 작성, 코드 변경, 리뷰 승인, 워크플로 실행과 같은 활동이 이벤트로 기록되고, 각 이벤트에는 작업을 수행한 주체의 암호화 서명이 붙는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 다음 내용을 확인할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;누가 작업했는가&lt;/li&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;누가 검토하고 승인했는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 실제 코드를 수정하고 업무를 수행하는 환경에서는 결과만큼이나 과정이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 어떤 정보를 참고했는지, 어떤 권한을 사용했는지, 최종적으로 누가 승인했는지가 명확하게 남아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 AI 에이전트 시대의 협업에서 &lt;b&gt;감사 가능성&lt;/b&gt;과 &lt;b&gt;책임 추적&lt;/b&gt;이 중요한 요소가 될 것임을 보여준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정말 탈중앙화된 플랫폼일까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 오픈소스와 자기주권적인 운영 방식을 강조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기업이나 조직은 Buzz를 자신의 서버에 설치할 수 있다. 대화, 코드, 파일과 같은 데이터를 외부 SaaS 기업에 맡기지 않고 직접 관리하는 것도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이를 완전한 P2P 탈중앙 네트워크로 이해해서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Buzz의 각 커뮤니티는 하나의 중앙 릴레이 서버를 중심으로 작동한다. 사용자의 읽기와 쓰기, 검색, 이벤트 처리도 해당 릴레이가 담당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이끼리 데이터를 자동으로 교환하거나 복제하는 완전한 분산형 구조는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Buzz가 말하는 탈중앙화는 다음 의미에 더 가깝다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 SaaS 기업에 종속되지 않는다.&lt;/li&gt;
&lt;li&gt;조직이 자체 서버를 운영할 수 있다.&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 data-ke-size=&quot;size16&quot;&gt;즉, 시스템 내부가 완전히 탈중앙화돼 있다기보다, 여러 조직이 각자의 서버를 소유할 수 있는 &lt;b&gt;분산 배포형 오픈소스 플랫폼&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아직은 완성된 기업용 제품이 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 흥미로운 비전을 제시하지만, 현재는 개발 단계에 있는 프로젝트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 채널, 개인 메시지, 공동 캔버스, 검색, 감사 로그, 데스크톱 앱, Git 호스팅, 워크플로와 같은 기능은 구현돼 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 모바일 앱, 푸시 알림, 일부 승인 워크플로와 고급 에이전트 기능은 계속 개발되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack, GitHub, Notion, Jenkins처럼 오랫동안 검증된 서비스를 한 번에 대체할 만큼 안정적인지는 아직 확인이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자체 호스팅 방식에도 책임이 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직은 직접 서버를 운영하고, 보안을 관리하고, 데이터를 백업하고, 장애와 업데이트에 대응해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스라는 이유만으로 운영 비용과 관리 부담까지 사라지는 것은 아니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Buzz가 보여주는 것은 제품보다 조직의 변화다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] Buzz의 의미는 단순히 새로운 협업 서비스 하나가 등장했다는 데 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 중요한 변화는 AI를 바라보는 관점에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 기업은 AI를 직원이 사용하는 도구로 본다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;직원
&amp;rarr; AI에게 질문
&amp;rarr; AI가 답변
&amp;rarr; 직원이 실제 업무 수행
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz가 지향하는 구조에서는 AI가 업무 과정 안으로 직접 들어온다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;사람과 AI가 같은 프로젝트에 참여
&amp;rarr; AI가 대화와 코드를 확인
&amp;rarr; AI가 작업 수행
&amp;rarr; 사람이 검토하고 승인
&amp;rarr; 전체 과정이 기록으로 보존
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 앞으로의 회사에서는 AI가 단순히 생산성을 높이는 프로그램이 아니라, 특정 역할을 맡아 반복적으로 일하는 디지털 구성원이 될 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 AI는 버그를 분석하고, 다른 AI는 코드를 리뷰할 수 있다. 또 다른 AI는 문서를 정리하고 프로젝트 진행 상황을 요약할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 모든 작업을 직접 처리하기보다, 목표와 권한을 설정하고 결과를 검토하는 역할로 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 회사에 필요한 것은 더 강력한 챗봇 하나가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 어떤 정보에 접근할 수 있는지, 어떤 행동을 할 수 있는지, 누가 결과를 승인하는지 관리할 수 있는 새로운 업무 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 바로 그 문제를 먼저 설계하려는 시도다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 시대의 협업 도구가 갖춰야 할 조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] Buzz의 구조를 통해 앞으로의 AI 협업 플랫폼에 필요한 조건을 정리해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, AI 에이전트마다 독립적인 신원이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 AI가 같은 계정과 권한을 공유하면 어떤 에이전트가 어떤 작업을 했는지 구분하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 최소 권한 원칙이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 필요 이상의 대화와 코드에 접근하지 못하도록 프로젝트 단위의 권한을 부여해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 모든 실행 과정이 기록돼야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 어떤 요청을 받고, 어떤 자료를 참고하고, 무엇을 변경했는지 추적할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넷째, 중요한 작업에는 사람의 승인이 남아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 작업을 수행하더라도 배포, 결제, 데이터 삭제처럼 위험도가 높은 행동은 사람이 검토하는 구조가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯째, 대화와 실행 기록이 분리되지 않아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 업무 맥락을 이해하려면 코드뿐 아니라 그 코드가 만들어진 이유까지 볼 수 있어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 팀원이 출근하는 시대&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz가 성공적으로 정착할지는 아직 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 협업 도구를 대체하기에는 부족한 기능이 있고, 자체 서버 운영에 따른 부담도 존재한다. Block 내부에서 효과적으로 사용되더라도 다른 기업에서도 같은 방식이 통할지는 더 지켜봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 Buzz가 던진 문제는 분명하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 실제 업무를 수행하기 시작하면 회사는 더 이상 AI를 단순한 플러그인이나 채팅 기능으로만 관리할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게도 신원이 필요하고, 권한이 필요하며, 활동 기록과 책임 범위가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람과 AI가 함께 일하는 회사는 단순히 기존 업무 도구에 AI 버튼 하나를 추가한 모습이 아닐 가능성이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화, 코드, 권한, 실행, 승인, 기록이 모두 연결된 새로운 업무 환경이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Buzz는 그 미래를 완성한 제품은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI가 도구의 자리를 넘어 &lt;b&gt;팀원의 자리로 이동할 때 회사의 운영체제는 어떻게 바뀌어야 하는가&lt;/b&gt;라는 질문을 가장 구체적으로 보여주는 사례 중 하나다.&lt;/p&gt;
&lt;figure id=&quot;og_1784680875319&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Buzz &amp;mdash; Your people, your agents, your project &amp;mdash; all in one place.&quot; data-og-description=&quot;Come test the early stages with us.&quot; data-og-host=&quot;buzz.xyz&quot; data-og-source-url=&quot;https://buzz.xyz/&quot; data-og-url=&quot;https://buzz.xyz&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/elTqtS/dJMb86oaJls/EYGMn1zGFwfbag4KHxSgA1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/CEZyf/dJMb88Gibmd/rQBHyLHwTI04VmR9oi4mVK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://buzz.xyz/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://buzz.xyz/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/elTqtS/dJMb86oaJls/EYGMn1zGFwfbag4KHxSgA1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/CEZyf/dJMb88Gibmd/rQBHyLHwTI04VmR9oi4mVK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Buzz &amp;mdash; Your people, your agents, your project &amp;mdash; all in one place.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Come test the early stages with us.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;buzz.xyz&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1792</guid>
      <comments>https://javaexpert.tistory.com/1792#entry1792comment</comments>
      <pubDate>Wed, 22 Jul 2026 09:41:30 +0900</pubDate>
    </item>
    <item>
      <title>파라미터 대신 &amp;lsquo;작업의 지평선&amp;rsquo;을 확장한 AI 모델 Agents-A1</title>
      <link>https://javaexpert.tistory.com/1791</link>
      <description>&lt;h1&gt;35B 모델이 거대 AI를 따라잡는 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인공지능 모델의 발전은 오랫동안 하나의 방향으로 설명됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 많은 데이터를 학습하고, 더 많은 연산을 투입하고, 더 많은 파라미터를 가진 모델을 만든다. 모델이 커질수록 더 복잡한 문제를 해결할 수 있다는 것이 대규모 언어 모델의 기본적인 성장 공식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 InternScience가 공개한 &lt;b&gt;Agents-A1&lt;/b&gt;은 조금 다른 질문을 던진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 크기를 계속 키우는 대신, AI가 한 번에 수행할 수 있는 작업의 길이를 늘리면 어떨까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1의 기술 보고서 제목은 이 질문을 그대로 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;Scaling the Horizon, Not the Parameters&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터가 아니라 &amp;lsquo;지평선&amp;rsquo;을 확장한다는 의미다. 여기서 지평선, 즉 Horizon은 AI가 하나의 목표를 해결하기 위해 계획하고, 검색하고, 도구를 사용하고, 결과를 확인하고, 실패를 수정하는 전체 작업 길이를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 총 35B 규모의 Mixture-of-Experts 모델로, 장시간 검색, 엔지니어링, 과학 연구, 복합 지시 이행, 도구 호출에 특화되어 있다. 공식 프로젝트는 최대 256K 컨텍스트와 여섯 가지 에이전트 평가 영역을 제시한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Agents-A1은 무엇이 다른가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 챗봇 모델은 사용자의 질문을 받은 뒤 답변을 생성한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;질문 &amp;rarr; 추론 &amp;rarr; 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1이 목표로 하는 작업은 조금 더 길다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;목표 이해
&amp;rarr; 문제 분해
&amp;rarr; 검색 도구 실행
&amp;rarr; 검색 결과 확인
&amp;rarr; 추가 정보 판단
&amp;rarr; 코드 실행
&amp;rarr; 오류 확인
&amp;rarr; 전략 수정
&amp;rarr; 결과 검증
&amp;rarr; 최종 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 좋은 문장을 만드는 것뿐 아니라 &lt;b&gt;여러 단계의 행동을 이어가며 실제 작업을 끝내는 능력&lt;/b&gt;을 강화한 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 프로젝트는 Agents-A1이 복잡한 목표를 실행 가능한 하위 단계로 나누고, 중간 결과에 따라 전략을 수정하며, API&amp;middot;검색 엔진&amp;middot;코드 인터프리터 같은 외부 도구를 호출할 수 있도록 설계됐다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 생각보다 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 모델이 &amp;ldquo;이 문제를 해결하려면 웹 검색과 데이터 분석이 필요합니다&amp;rdquo;라고 설명하는 데 그친다면, 에이전트 모델은 검색 도구를 호출하고, 결과를 비교하고, 필요한 계산을 수행한 뒤 최종 결론까지 도달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 이처럼 &lt;b&gt;오래 이어지는 작업 과정 자체를 학습 대상으로 삼았다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;평균 4만 5천 토큰의 작업 과정을 학습하다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1 연구진은 외부 지식, 행동, 관찰 결과, 검증 결과를 연결하는 장기 작업 인프라를 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인프라에서는 단순한 질문과 정답만 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 어떤 행동을 선택했는지, 도구를 실행한 뒤 무엇을 관찰했는지, 결과가 맞았는지, 다음 행동을 어떻게 수정했는지까지 하나의 궤적으로 만든다. 연구진이 구축한 에이전트 궤적의 평균 길이는 약 45K 토큰이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습은 세 단계로 진행됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 여러 영역의 에이전트 행동을 폭넓게 학습하는 지도 미세조정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 검색, 과학, 엔지니어링 등 각 영역에 특화된 교사 모델을 별도로 훈련하는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 여러 교사 모델의 능력을 하나의 모델로 이전하는 다중 교사 온폴리시 증류다. 연구진은 이를 통해 서로 다른 여섯 가지 에이전트 능력을 하나의 35B 모델에 통합했다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;검색 전문 교사
과학 연구 전문 교사
엔지니어링 전문 교사
도구 사용 전문 교사
지시 이행 전문 교사
        &amp;darr;
Agents-A1 하나로 통합
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 무작정 크게 만드는 대신, &lt;b&gt;각 분야에서 길고 복잡한 작업을 수행한 경험을 효율적으로 증류한 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;35B 모델이 1조 파라미터급과 경쟁한다는 의미&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1을 소개하는 문구 가운데 가장 눈에 띄는 표현은 &amp;ldquo;35B 모델로 1조 파라미터급 성능에 도달했다&amp;rdquo;는 주장이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 표현은 주의해서 해석해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1이 모든 영역에서 1조 파라미터급 모델보다 뛰어나다는 뜻은 아니다. 공식 평가표를 보면 특정 장기 작업과 과학 연구, 복합 지시 이행에서 매우 강한 결과를 기록한 반면, 일부 코딩&amp;middot;머신러닝&amp;middot;검색 벤치마크에서는 더 큰 상용 모델보다 낮은 점수를 기록했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 모델 카드가 보고한 대표 결과는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SEAL-0: 56.36&lt;/li&gt;
&lt;li&gt;GAIA: 96.04&lt;/li&gt;
&lt;li&gt;BrowseComp: 75.51&lt;/li&gt;
&lt;li&gt;FrontierScience-Olympiad: 79.0&lt;/li&gt;
&lt;li&gt;FrontierScience-Research: 40.0&lt;/li&gt;
&lt;li&gt;IFBench: 80.61&lt;/li&gt;
&lt;li&gt;IFEval: 94.82&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 SEAL-0, HiPhO, FrontierScience, IFBench 등에서는 비교 대상 대형 모델보다 높은 결과가 보고됐다. 반면 SciCode에서는 Agents-A1이 44.33, GPT-5.5가 56.1이었고, MLE-Lite에서는 Agents-A1이 43.94, GPT-5.5가 72.73이었다. BrowseComp 역시 Agents-A1의 75.51보다 비교 대상 대형 모델들의 점수가 더 높았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정확한 표현은 다음에 가깝다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 모든 능력에서 초거대 모델을 대체하는 모델이 아니라, 일부 장기 에이전트 작업에서 훨씬 큰 모델과 경쟁할 수 있는 효율적인 특화 모델이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점이 Agents-A1의 진짜 의미다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;일반 추론 모델과는 무엇이 다른가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추론 모델은 어려운 수학 문제나 논리 문제를 해결하기 위해 내부 계산을 길게 수행한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;문제 &amp;rarr; 긴 내부 추론 &amp;rarr; 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트 모델은 내부 추론과 함께 외부 환경을 활용한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;문제
&amp;rarr; 계획
&amp;rarr; 도구 호출
&amp;rarr; 결과 관찰
&amp;rarr; 계획 수정
&amp;rarr; 추가 도구 호출
&amp;rarr; 검증
&amp;rarr; 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 추론 모델의 중심이 &lt;b&gt;생각의 깊이&lt;/b&gt;라면 에이전트 모델의 중심은 &lt;b&gt;생각과 행동의 연결&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 함수 호출과 외부 도구 연동을 기본적으로 지원한다. 공식 실행 예제에서는 검색&amp;middot;코드 실행&amp;middot;API 호출에 사용할 수 있는 도구 호출 파서가 제공되며, SGLang과 vLLM을 통해 OpenAI 호환 API 서버로 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모델 하나를 실행했다고 해서 자동으로 웹 검색이나 파일 수정이 가능한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 어떤 도구를 사용할지 판단하는 두뇌에 가깝다. 실제 검색, 브라우저 조작, 터미널 실행, 파일 읽기와 쓰기는 별도의 에이전트 런타임이 담당해야 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Agents-A1
    &amp;darr; 도구 호출 요청
에이전트 프레임워크
    &amp;darr;
웹 검색&amp;middot;브라우저&amp;middot;Python&amp;middot;터미널&amp;middot;파일 시스템
    &amp;darr; 실행 결과
Agents-A1
    &amp;darr; 다음 행동 결정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Agents-A1의 성능을 제대로 활용하려면 단순 채팅 UI보다 &lt;b&gt;도구가 연결된 실행 환경&lt;/b&gt;이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;장문맥 모델과 장기 에이전트 모델은 다르다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 공식 실행 예제에서 최대 262,144토큰의 컨텍스트 길이를 지원한다. 이미지 입력을 포함하는 멀티모달 모델이며, 텍스트 전용 실행 시에는 비전 인코더를 제외해 KV 캐시 공간을 확보하는 옵션도 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 긴 컨텍스트를 지원한다고 해서 반드시 긴 작업을 잘 수행하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 문서를 한꺼번에 입력할 수 있는 능력과 여러 단계의 작업 목표를 끝까지 유지하는 능력은 서로 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 장문맥 모델은 긴 자료를 읽을 수 있어도 다음과 같은 문제를 보일 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 조사한 내용을 반복한다.&lt;/li&gt;
&lt;li&gt;중간에 원래 목표를 잊는다.&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 data-ke-size=&quot;size16&quot;&gt;Agents-A1이 강조하는 것은 단순한 컨텍스트 크기가 아니라, &lt;b&gt;긴 행동 궤적 안에서 목표&amp;middot;관찰&amp;middot;검증 결과를 유지하는 능력&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 논문 제목에서 말하는 Horizon Scaling의 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Agents-A1의 장점&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 중형 모델로 구현한 강력한 장기 작업 능력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;35B급 모델이 일부 장기 검색과 과학 연구 벤치마크에서 훨씬 큰 모델과 경쟁한 것은 의미가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초거대 모델을 직접 운영하기 어려운 연구소나 기업에도 비교적 현실적인 에이전트 모델 선택지가 생겼기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 도구 호출을 전제로 설계된 모델&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 검색, API, 코드 인터프리터, 외부 작업 환경과 연동하는 함수 호출을 지원한다. 단순 질의응답보다 조사&amp;middot;분석&amp;middot;실행을 반복하는 워크플로에 적합하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 과학&amp;middot;연구형 작업에서 강한 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 평가에서는 FrontierScience-Research, FrontierScience-Olympiad, HiPhO 같은 과학 연구형 평가에서 강한 성능이 보고됐다. 특히 FrontierScience-Research에서는 비교표에 포함된 대형 모델보다 높은 점수를 기록했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 복잡한 지시 조건을 잘 유지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IFEval과 IFBench에서 높은 점수가 보고됐다. 이는 단순히 질문에 답하는 것보다 출력 형식, 금지 조건, 단계별 요구사항처럼 여러 제약을 동시에 지켜야 하는 작업에서 중요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 오픈소스 생태계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 가중치와 일부 평가 코드, 기술 보고서가 공개돼 있다. 라이선스는 Apache 2.0이며, Hugging Face Transformers, SGLang, vLLM 형식으로 사용할 수 있다. 공식 모델 카드에는 llama.cpp, Ollama, LM Studio 등에서 사용할 수 있는 양자화 모델 탐색 경로도 제공된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 4B 경량 모델도 제공&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InternScience는 2026년 7월 14일 Agents-A1-4B를 공개했다. 공식 결과에서는 4B 모델도 일부 검색&amp;middot;과학&amp;middot;지시 이행 벤치마크에서 비슷한 크기의 기본 모델보다 높은 성능을 기록했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;35B 모델이 서버나 고성능 워크스테이션용이라면, 4B 모델은 개인용 로컬 에이전트 실험에 더 가까운 선택지다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Agents-A1의 단점과 주의할 점&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 모든 분야에서 최고 성능은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 장기 검색과 과학 연구, 복합 지시 이행에 강하지만 모든 코딩&amp;middot;엔지니어링 작업에서 최고는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 평가에서도 SciCode, MLE-Lite, HLE with tools, BrowseComp 등 일부 항목은 대형 프런티어 모델보다 낮았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &amp;ldquo;35B 모델이 1조 파라미터 모델을 완전히 대체했다&amp;rdquo;는 식의 해석은 과장에 가깝다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 벤치마크와 실제 업무 사이에는 차이가 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 평가는 공개된 에이전트 평가 프레임워크를 통해 일부 재현이 가능하다. 다만 벤치마크 성능이 기업 내부 시스템, 브라우저 자동화, 복잡한 파일 작업, 장시간 코딩 프로젝트에서 동일하게 나타난다고 단정할 수는 없다. 공식 모델 카드 역시 비교 대상 모델의 기존 보고값과 자체 평가값을 함께 사용했다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도입 전에는 실제 업무 데이터와 도구 환경을 이용한 별도 검증이 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 모델만으로는 에이전트가 완성되지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 도구를 선택하고 다음 행동을 결정할 수 있지만, 실제 도구 실행 시스템은 별도로 구축해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한 관리, 재시도, 브라우저 상태, 오류 복구, 비용 제한, 사용자 승인, 로그 기록 같은 기능은 에이전트 프레임워크와 애플리케이션이 담당해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 장기 작업은 비용과 시간이 커진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 작업은 짧은 질문보다 더 많은 토큰과 도구 호출을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트가 길어질수록 첫 응답까지 걸리는 시간이 증가하고, 검색과 코드 실행을 반복하면 전체 작업 시간도 길어진다. &amp;ldquo;더 작은 모델&amp;rdquo;이 반드시 &amp;ldquo;더 저렴한 작업&amp;rdquo;을 의미하지는 않는다. 에이전트가 얼마나 많은 단계를 수행하는지도 전체 비용을 결정한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 로컬 실행에는 여전히 높은 사양이 필요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;35B급 모델은 양자화를 사용하더라도 일반 노트북에서 가볍게 실행하는 모델은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 PC에서는 GGUF나 다른 양자화 버전을 사용하거나, 더 작은 Agents-A1-4B를 선택하는 편이 현실적이다. 원본 모델을 긴 컨텍스트와 비전 기능까지 포함해 서비스하려면 충분한 GPU 메모리와 서빙 환경이 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;어떤 작업에 적합할까&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1이 잘 맞을 가능성이 높은 작업은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 웹사이트를 조사하는 심층 리서치&lt;/li&gt;
&lt;li&gt;논문 검색과 근거 비교&lt;/li&gt;
&lt;li&gt;코드를 작성하고 실행하고 수정하는 반복 작업&lt;/li&gt;
&lt;li&gt;과학&amp;middot;공학 분야의 도구 기반 문제 해결&lt;/li&gt;
&lt;li&gt;여러 제약 조건이 포함된 복잡한 보고서 작성&lt;/li&gt;
&lt;li&gt;API를 연속 호출하는 업무 자동화&lt;/li&gt;
&lt;li&gt;긴 프로젝트를 단계별로 수행하는 연구 에이전트&lt;/li&gt;
&lt;li&gt;이미지와 문서를 함께 분석하는 멀티모달 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 간단한 번역, 짧은 요약, 일상 대화, 단순 질의응답처럼 한 번의 응답으로 끝나는 작업에서는 Agents-A1의 장점이 크게 드러나지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업에는 더 작은 4B&amp;middot;7B급 모델이 빠르고 효율적일 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Agents-A1이 보여주는 새로운 방향&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1의 가장 중요한 성과는 특정 벤치마크 점수 하나가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델이 보여주는 것은 &lt;b&gt;모델 성능을 키우는 방법이 파라미터 증가만은 아니라는 가능성&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 AI 경쟁은 다음과 같이 바뀔 수 있다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;더 큰 모델
&amp;rarr; 더 긴 작업을 수행하는 모델

더 많은 지식
&amp;rarr; 지식을 찾고 검증하는 능력

더 좋은 답변
&amp;rarr; 실제 도구를 사용해 일을 끝내는 능력
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Agents-A1 하나로 초거대 모델의 시대가 끝난 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 코딩, 범용 추론, 창작, 세계 지식, 안정성 등에서는 대형 프런티어 모델이 여전히 강하다. Agents-A1의 공식 결과에서도 영역별 성능 차이가 분명하게 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 Agents-A1은 중요한 질문을 남긴다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI의 능력을 키우기 위해 모델 자체를 계속 크게 만들어야 할까?&lt;br /&gt;아니면 작은 모델이 더 오래 생각하고, 더 많은 도구를 사용하고, 더 긴 작업을 경험하도록 만드는 편이 효과적일까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agents-A1은 후자의 가능성을 보여주는 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 몸집으로 모든 문제를 해결하는 모델은 아니다. 하지만 검색하고, 실행하고, 관찰하고, 수정하며 끝까지 작업을 이어가는 능력에서는 기존 중형 오픈 모델보다 한 단계 진화한 방향을 제시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 단순히 답을 생성하는 도구에서 실제 업무를 수행하는 에이전트로 이동하는 시점에, Agents-A1이 주목받는 이유도 바로 여기에 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Agents-A1은 파라미터 수를 무작정 늘리는 대신, AI가 계획하고 도구를 사용하며 작업을 이어가는 &amp;lsquo;지평선&amp;rsquo;을 확장한 35B 오픈 에이전트 모델이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 분야에서 초거대 모델을 이기는 것은 아니지만, 장기 검색&amp;middot;과학 연구&amp;middot;복합 지시 이행에서는 모델 크기 대비 매우 강한 성능을 보여준다. 앞으로 로컬 AI와 기업용 에이전트가 어떤 방향으로 발전할지를 보여주는 흥미로운 사례다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1791</guid>
      <comments>https://javaexpert.tistory.com/1791#entry1791comment</comments>
      <pubDate>Tue, 21 Jul 2026 13:48:33 +0900</pubDate>
    </item>
    <item>
      <title>범용 AI 프로젝트 하네스 설계 방법론</title>
      <link>https://javaexpert.tistory.com/1790</link>
      <description>&lt;h1&gt;범용 AI 프로젝트 하네스 설계 방법론&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;AI에게 작업을 한 번 요청하는 방식에서 벗어나, 계획·실행·검증·릴리스를 반복 가능하고 감사 가능한 공정으로 만드는 방법&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;1. 문서의 목적&lt;/h2&gt;
&lt;p&gt;이 문서는 웹서비스, 모바일 앱, 게임, 영상, 데이터 파이프라인, 문서 제작 등 서로 다른 프로젝트에 적용할 수 있는 AI 작업 하네스의 설계 원칙과 구현 절차를 정의한다.&lt;/p&gt;
&lt;p&gt;여기서 하네스는 모델 자체가 아니다. 모델이 프로젝트 안에서 일관되게 작업하도록 다음 요소를 제공하는 실행 구조다.&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;li&gt;반복 작업을 처리하는 결정론적 스크립트&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&gt;2. 핵심 원칙&lt;/h2&gt;
&lt;h3&gt;2.1 모델과 하네스의 책임을 분리한다&lt;/h3&gt;
&lt;p&gt;모델에 맡길 작업:&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;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;ul&gt;
&lt;li&gt;폴더와 상태 파일 생성&lt;/li&gt;
&lt;li&gt;파일명과 스키마 검사&lt;/li&gt;
&lt;li&gt;빌드, 테스트, 린트 실행&lt;/li&gt;
&lt;li&gt;해시와 중복 검사&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;h3&gt;2.2 채팅이 아니라 파일을 진실의 원천으로 삼는다&lt;/h3&gt;
&lt;p&gt;AI 대화는 실행 인터페이스이고, 프로젝트 파일이 영속 기억이다. 다음 실행에서 이전 채팅을 사용할 수 없더라도 &lt;code&gt;state.json&lt;/code&gt;, 명세, 결과물, 검증 보고서만 읽으면 이어서 작업할 수 있어야 한다.&lt;/p&gt;
&lt;h3&gt;2.3 단계마다 명시적인 계약을 둔다&lt;/h3&gt;
&lt;p&gt;각 단계에는 반드시 다음 네 가지가 있어야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;입력: 무엇을 읽어야 하는가&lt;/li&gt;
&lt;li&gt;작업: 무엇을 결정하거나 생성하는가&lt;/li&gt;
&lt;li&gt;출력: 어떤 파일을 만들어야 하는가&lt;/li&gt;
&lt;li&gt;게이트: 무엇을 통과해야 다음 단계로 갈 수 있는가&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.4 생성과 검증을 분리한다&lt;/h3&gt;
&lt;p&gt;결과를 만든 주체가 스스로 “완료”라고 선언하는 것만으로 릴리스하지 않는다. 최소한 기계 검증과 의미 검증을 분리한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기계 검증: 파일 존재, 스키마, 빌드, 테스트, 해시, 누락 여부&lt;/li&gt;
&lt;li&gt;의미 검증: 요구사항 충족, UX, 논리, 시각 품질, 업무 규칙&lt;/li&gt;
&lt;li&gt;사람 승인: 비용, 배포, 삭제, 외부 전송 등 고위험 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.5 전체 재실행보다 부분 재실행을 우선한다&lt;/h3&gt;
&lt;p&gt;수정 요청이 들어오면 가장 먼저 영향을 받는 단계로 상태를 되돌리고, 그 단계와 하위 결과물만 다시 만든다. 상위의 승인된 명세와 자산은 보존한다.&lt;/p&gt;
&lt;h2&gt;3. 전체 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    U[&amp;quot;사용자 요청&amp;quot;] --&amp;gt; S[&amp;quot;하네스 스킬과 프로젝트 규칙 로드&amp;quot;]
    S --&amp;gt; R[&amp;quot;현재 상태와 기존 결과물 확인&amp;quot;]
    R --&amp;gt; P[&amp;quot;계획 및 명세&amp;quot;]
    P --&amp;gt; I[&amp;quot;구현 또는 생성&amp;quot;]
    I --&amp;gt; V1[&amp;quot;기계 검증&amp;quot;]
    V1 --&amp;gt;|실패| F[&amp;quot;실패 범위 판정&amp;quot;]
    F --&amp;gt; I
    V1 --&amp;gt;|통과| V2[&amp;quot;의미 및 품질 검증&amp;quot;]
    V2 --&amp;gt;|수정 필요| F
    V2 --&amp;gt;|승인| B[&amp;quot;패키징 및 릴리스&amp;quot;]
    B --&amp;gt; C[&amp;quot;상태와 연속성 기록&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;권장 기본 상태 전이는 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;initialized
  → specified
  → planned
  → implemented
  → machine_validated
  → quality_validated
  → packaged
  → released&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;프로젝트 성격에 따라 상태 이름은 바꿀 수 있지만, 생성 전·생성 후·검증 후·릴리스 후를 구분해야 한다.&lt;/p&gt;
&lt;h2&gt;4. 권장 디렉터리 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;project-root/
├── AGENTS.md
├── .codex/
│   └── skills/
│       └── project-harness/
│           ├── SKILL.md
│           ├── agents/
│           │   └── openai.yaml
│           ├── scripts/
│           │   ├── init_work.py
│           │   ├── advance_stage.py
│           │   ├── register_artifact.py
│           │   ├── validate_work.py
│           │   ├── build_release.py
│           │   └── finalize_release.py
│           ├── references/
│           │   ├── production-contract.md
│           │   ├── validation-rules.md
│           │   └── domain-rules.md
│           └── assets/
│               └── release-template/
├── _workspace/
│   ├── state.json
│   ├── 00_input/
│   ├── 01_specification/
│   ├── 02_plan/
│   ├── 03_output/
│   ├── 04_validation/
│   ├── 05_assembly/
│   └── RELEASE/
└── application-source/&lt;/code&gt;&lt;/pre&gt;
&lt;p&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;저장소 전체에서 지켜야 할 경계와 안전 규칙&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI가 따라야 할 핵심 실행 절차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;references/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;필요할 때만 읽는 상세 업무 규칙과 스키마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;반복 가능하고 결정론적인 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assets/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;결과물에 복사해서 사용할 템플릿과 정적 자산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_workspace/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행 상태, 중간 결과, 검증 기록, 릴리스 패키지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;중간 결과를 Git에 포함할 필요가 없다면 &lt;code&gt;_workspace/&lt;/code&gt;를 &lt;code&gt;.gitignore&lt;/code&gt;에 추가한다. 다만 운영상 보존해야 하는 감사 기록은 별도 저장소나 아티팩트 스토리지로 내보낸다.&lt;/p&gt;
&lt;h2&gt;5. 하네스 설계 절차&lt;/h2&gt;
&lt;h3&gt;5.1 반복 가능한 실제 요청을 수집한다&lt;/h3&gt;
&lt;p&gt;먼저 사용자가 하네스에 어떤 문장으로 일을 시킬지 정의한다.&lt;/p&gt;
&lt;p&gt;예시:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“새 기능을 요구사항부터 구현하고 테스트해줘.”&lt;/li&gt;
&lt;li&gt;“이전 작업을 이어서 릴리스 패키지를 만들어줘.”&lt;/li&gt;
&lt;li&gt;“UI만 수정하고 영향받는 테스트를 다시 실행해줘.”&lt;/li&gt;
&lt;li&gt;“실패한 검증 단계부터 재개해줘.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;최소한 신규 작업, 이어서 하기, 부분 수정, 검증, 릴리스 요청을 각각 하나씩 준비한다.&lt;/p&gt;
&lt;h3&gt;5.2 작업을 4~7개의 생산 단계로 압축한다&lt;/h3&gt;
&lt;p&gt;사람의 직책이나 페르소나 수를 늘리기보다 실제 의존성 기준으로 단계를 나눈다.&lt;/p&gt;
&lt;p&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;서로 독립적으로 실행할 수 없는 역할은 같은 단계에 둔다.&lt;/p&gt;
&lt;h3&gt;5.3 각 단계의 생산 계약을 작성한다&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;references/production-contract.md&lt;/code&gt;에 다음 형식으로 기록한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;## implemented

- 입력
  - `01_specification/requirements.md`
  - `02_plan/implementation-plan.md`
- 필수 출력
  - 변경된 소스 코드
  - `03_output/artifacts.json`
- 자동 게이트
  - 대상 파일 존재
  - 금지 경로 변경 없음
  - 빌드 명령 성공
- 실패 시 복귀 단계
  - 계획 오류: `planned`
  - 구현 오류: `implemented`&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 상태 스키마를 설계한다&lt;/h3&gt;
&lt;p&gt;최소 상태 예시:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;schema_version&amp;quot;: 1,
  &amp;quot;project&amp;quot;: &amp;quot;example-project&amp;quot;,
  &amp;quot;work_id&amp;quot;: &amp;quot;work-001&amp;quot;,
  &amp;quot;stage&amp;quot;: &amp;quot;implemented&amp;quot;,
  &amp;quot;status&amp;quot;: &amp;quot;in_progress&amp;quot;,
  &amp;quot;mode&amp;quot;: &amp;quot;standard&amp;quot;,
  &amp;quot;created_at&amp;quot;: &amp;quot;2026-01-01T00:00:00Z&amp;quot;,
  &amp;quot;updated_at&amp;quot;: &amp;quot;2026-01-01T01:00:00Z&amp;quot;,
  &amp;quot;stages&amp;quot;: {
    &amp;quot;specified&amp;quot;: {&amp;quot;status&amp;quot;: &amp;quot;completed&amp;quot;},
    &amp;quot;planned&amp;quot;: {&amp;quot;status&amp;quot;: &amp;quot;completed&amp;quot;},
    &amp;quot;implemented&amp;quot;: {&amp;quot;status&amp;quot;: &amp;quot;in_progress&amp;quot;}
  },
  &amp;quot;flags&amp;quot;: [],
  &amp;quot;artifacts&amp;quot;: []
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;상태에는 설명문 전체를 저장하지 않는다. 긴 판단과 결과는 Markdown 또는 JSON 아티팩트로 분리하고 상태 파일에는 경로와 진행 상태만 둔다.&lt;/p&gt;
&lt;h3&gt;5.5 아티팩트 등록 규격을 만든다&lt;/h3&gt;
&lt;p&gt;생성된 파일은 단순히 폴더에 놓는 것으로 끝내지 않고 manifest에 등록한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;schema_version&amp;quot;: 1,
  &amp;quot;work_id&amp;quot;: &amp;quot;work-001&amp;quot;,
  &amp;quot;artifacts&amp;quot;: [
    {
      &amp;quot;file&amp;quot;: &amp;quot;build/app.zip&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;release-package&amp;quot;,
      &amp;quot;source_stage&amp;quot;: &amp;quot;packaged&amp;quot;,
      &amp;quot;sha256&amp;quot;: &amp;quot;...&amp;quot;,
      &amp;quot;metadata&amp;quot;: {
        &amp;quot;platform&amp;quot;: &amp;quot;linux-x64&amp;quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;manifest에는 최소한 파일 경로, 종류, 생성 단계, 무결성 해시를 기록한다. 이미지, 영상, 데이터셋처럼 범위 정보가 필요한 경우 프레임, 장면, 레코드, 페이지 등의 논리 범위도 추가한다.&lt;/p&gt;
&lt;h3&gt;5.6 결정론적 스크립트를 구현한다&lt;/h3&gt;
&lt;p&gt;각 스크립트는 한 가지 책임만 갖게 한다.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;init_work.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_workspace/&lt;/code&gt; 구조 생성&lt;/li&gt;
&lt;li&gt;새 &lt;code&gt;work_id&lt;/code&gt; 결정&lt;/li&gt;
&lt;li&gt;초기 &lt;code&gt;state.json&lt;/code&gt; 생성&lt;/li&gt;
&lt;li&gt;입력 브리프와 빈 manifest 생성&lt;/li&gt;
&lt;li&gt;기존 작업이 있으면 무단 덮어쓰기 거부&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;advance_stage.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;목표 단계의 필수 결과물 검사&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;h4&gt;&lt;code&gt;register_artifact.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;실제 파일 존재 여부 검사&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;h4&gt;&lt;code&gt;validate_work.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;파일·스키마·범위·중복·해시 검사&lt;/li&gt;
&lt;li&gt;프로젝트 빌드와 테스트 실행&lt;/li&gt;
&lt;li&gt;결과를 &lt;code&gt;validation.json&lt;/code&gt;으로 저장&lt;/li&gt;
&lt;li&gt;통과한 검증 대상의 해시 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;build_release.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;검증을 통과한 아티팩트만 복사&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;h4&gt;&lt;code&gt;finalize_release.py&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;기계 검증 결과 확인&lt;/li&gt;
&lt;li&gt;품질 보고서와 사람 승인 확인&lt;/li&gt;
&lt;li&gt;릴리스 아티팩트 해시 재확인&lt;/li&gt;
&lt;li&gt;최종 상태를 &lt;code&gt;released&lt;/code&gt;로 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스크립트는 가능하면 다음 성질을 갖게 한다.&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;li&gt;절대 경로나 검증되지 않은 glob에 의존하지 않기&lt;/li&gt;
&lt;li&gt;종료 코드 &lt;code&gt;0&lt;/code&gt;은 성공, 그 외는 실패&lt;/li&gt;
&lt;li&gt;사람이 읽을 출력과 기계가 읽을 JSON 결과를 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.7 SKILL.md를 작성한다&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt;는 장문의 업무 백과사전이 아니라 실행 라우터다. 핵심 절차만 두고 상세 규칙은 &lt;code&gt;references/&lt;/code&gt;로 분리한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
name: project-harness
description: Plan, implement, validate, resume, revise, package, and release work in this project. Use for new work, continuation, targeted revisions, validation, or release preparation.
---

# Project Harness

Use `_workspace/` as the durable source of truth.

## Start

1. Read `_workspace/state.json` when it exists.
2. Classify the request as new, continuation, targeted revision, validation, or release.
3. Read `references/production-contract.md` before changing state layout.
4. Initialize new work with `scripts/init_work.py`.

## Execute

1. Produce the required artifacts for the current stage.
2. Keep creative decisions in Markdown and machine state in JSON.
3. Run `scripts/advance_stage.py`; do not edit the stage manually.

## Validate and release

1. Run `scripts/validate_work.py`.
2. Fix only the failing stage and downstream artifacts.
3. Build with `scripts/build_release.py`.
4. Finalize only after machine and human gates pass.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스킬 설명에는 언제 자동으로 선택되어야 하는지 구체적으로 쓴다. 본문에는 이미 선택된 다음의 실행 방법만 기록한다.&lt;/p&gt;
&lt;h3&gt;5.8 AGENTS.md에 저장소 경계를 선언한다&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# Repository instructions

- Use `.codex/skills/project-harness/SKILL.md` for work that creates, continues, revises, validates, packages, or releases project artifacts.
- Keep generated and intermediate artifacts under `_workspace/`.
- Use bundled scripts for initialization, registration, validation, and packaging.
- Preserve existing user changes and do not overwrite unrelated files.
- Do not mark work released until machine validation and required human approval pass.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에는 프로젝트 전체에서 항상 적용할 규칙만 둔다. 특정 단계에만 필요한 긴 규칙은 스킬 또는 참조 문서로 보낸다.&lt;/p&gt;
&lt;h3&gt;5.9 검증 계층을 구현한다&lt;/h3&gt;
&lt;p&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;자동화 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;V1 구조&lt;/td&gt;
&lt;td&gt;필수 파일, 폴더, 이름, 스키마&lt;/td&gt;
&lt;td&gt;완전 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V2 무결성&lt;/td&gt;
&lt;td&gt;크기, 해시, 중복, 범위, 링크&lt;/td&gt;
&lt;td&gt;완전 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V3 실행&lt;/td&gt;
&lt;td&gt;린트, 테스트, 빌드, 스모크 테스트&lt;/td&gt;
&lt;td&gt;가능한 범위에서 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V4 의미&lt;/td&gt;
&lt;td&gt;요구사항, 업무 규칙, UX, 콘텐츠 정확성&lt;/td&gt;
&lt;td&gt;모델과 도메인 검사기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V5 승인&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;하위 계층이 실패하면 상위 계층을 실행하지 않는다.&lt;/p&gt;
&lt;h3&gt;5.10 실제 요청으로 전진 테스트한다&lt;/h3&gt;
&lt;p&gt;최소 시나리오:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;빈 저장소에서 새 작업 초기화&lt;/li&gt;
&lt;li&gt;중간 단계에서 프로세스를 종료한 뒤 재개&lt;/li&gt;
&lt;li&gt;필수 파일을 제거하고 단계 전이가 거부되는지 확인&lt;/li&gt;
&lt;li&gt;결과물을 수정한 뒤 오래된 검증이 거부되는지 확인&lt;/li&gt;
&lt;li&gt;일부 수정 요청에서 필요한 하위 단계만 재실행&lt;/li&gt;
&lt;li&gt;전체 검증을 통과한 뒤 릴리스 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;테스트가 실패하면 프롬프트를 길게 만드는 것보다 계약이나 스크립트로 강제할 수 있는지 먼저 판단한다.&lt;/p&gt;
&lt;h2&gt;6. 신규 작업, 재개, 수정의 처리 규칙&lt;/h2&gt;
&lt;h3&gt;신규 작업&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;상태 없음
  → 입력 범위 확인
  → init_work.py
  → 명세 작성
  → 단계별 진행&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;중단된 작업 재개&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;state.json 존재
  → 현재 stage 확인
  → 필수 아티팩트와 실제 파일 비교
  → 불완전한 현재 단계부터 계속&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;상태 파일만 믿지 말고 해당 단계의 필수 결과물이 실제로 존재하는지 확인한다.&lt;/p&gt;
&lt;h3&gt;부분 수정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    Q[&amp;quot;수정 요청&amp;quot;] --&amp;gt; A[&amp;quot;최초 영향 단계 판정&amp;quot;]
    A --&amp;gt; R[&amp;quot;해당 단계로 상태 되돌림&amp;quot;]
    R --&amp;gt; K[&amp;quot;상위 승인 자산 보존&amp;quot;]
    K --&amp;gt; X[&amp;quot;영향 단계와 하위 결과 재생성&amp;quot;]
    X --&amp;gt; V[&amp;quot;검증과 패키징 재실행&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&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;다시 실행할 항목&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;문구·설정&lt;/td&gt;
&lt;td&gt;&lt;code&gt;specified&lt;/code&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;code&gt;planned&lt;/code&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;code&gt;implemented&lt;/code&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;code&gt;packaged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;조립과 릴리스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QA 보고서&lt;/td&gt;
&lt;td&gt;&lt;code&gt;quality_validated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;승인과 릴리스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;7. 안전과 운영 규칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;삭제, 배포, 결제, 외부 메시지 전송은 별도 승인 게이트로 둔다.&lt;/li&gt;
&lt;li&gt;비밀값을 상태 파일, manifest, 로그에 저장하지 않는다.&lt;/li&gt;
&lt;li&gt;생성 스크립트는 기존 작업을 기본적으로 덮어쓰지 않는다.&lt;/li&gt;
&lt;li&gt;작업 디렉터리 밖의 경로를 아티팩트로 등록하지 못하게 한다.&lt;/li&gt;
&lt;li&gt;검증 이후 파일이 변경되면 빌드와 릴리스를 거부한다.&lt;/li&gt;
&lt;li&gt;실패를 성공으로 우회하는 플래그는 이름, 사유, 승인자, 만료 조건을 기록한다.&lt;/li&gt;
&lt;li&gt;실제 운영 데이터와 생성 테스트 데이터를 분리한다.&lt;/li&gt;
&lt;li&gt;기존 Git 변경이 있는 경우 하네스 작업과 무관한 파일을 보존한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. 도메인별 적용 예시&lt;/h2&gt;
&lt;h3&gt;웹서비스 개발&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;요구사항 → API/UI 설계 → 구현 → 단위·통합 테스트 → 빌드 → 배포 후보&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 아티팩트: 요구사항, API 계약, DB 마이그레이션, 변경 파일 목록, 테스트 결과, 빌드 결과.&lt;/p&gt;
&lt;h3&gt;게임 제작&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;게임 규칙 → 콘텐츠/시스템 설계 → 씬·스크립트·에셋 구현 → 플레이 검증 → 빌드&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 아티팩트: 게임 규칙, 씬 목록, 프리팹/에셋 manifest, 플레이테스트 보고서, 플랫폼 빌드.&lt;/p&gt;
&lt;h3&gt;영상 제작&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;브리프 → 구성안 → 대본·샷리스트 → 미디어 생성 → 렌더 검증 → 최종 인코딩&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 아티팩트: 대본, 타임라인, 미디어 출처, 프레임/오디오 검증, 렌더 파일.&lt;/p&gt;
&lt;h3&gt;데이터 파이프라인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;데이터 계약 → 변환 설계 → 파이프라인 구현 → 품질 검사 → 스냅샷·배포&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 아티팩트: 스키마, 샘플 데이터, 변환 규칙, 품질 지표, 계보, 배포 manifest.&lt;/p&gt;
&lt;h2&gt;9. 이식용 설계 워크시트&lt;/h2&gt;
&lt;p&gt;새 프로젝트에 적용하기 전에 다음 항목을 채운다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# Harness Design Worksheet

## 목적
- 반복할 작업:
- 최종 결과물:
- 실패 비용:

## 요청 유형
- 신규 작업:
- 이어서 하기:
- 부분 수정:
- 검증:
- 릴리스:

## 단계
1.
2.
3.
4.

## 단계별 계약
- 단계:
  - 입력:
  - 출력:
  - 자동 게이트:
  - 사람 승인:
  - 실패 시 복귀 단계:

## 영속 상태
- state 필드:
- manifest 필드:
- 보존할 연속성 정보:

## 자동화
- 초기화 스크립트:
- 등록 스크립트:
- 검증 스크립트:
- 빌드 스크립트:
- 릴리스 스크립트:

## 안전 경계
- 금지 경로:
- 외부 시스템:
- 비밀값 처리:
- 승인 필요 작업:&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;10. 완료 기준&lt;/h2&gt;
&lt;p&gt;다음 조건이 모두 충족되면 최소 실행 가능한 하네스로 본다.&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;li&gt;단계별 필수 결과물이 정의돼 있다.&lt;/li&gt;
&lt;li&gt;필수 결과물 없이 단계가 넘어가지 않는다.&lt;/li&gt;
&lt;li&gt;생성 결과물이 manifest에 등록된다.&lt;/li&gt;
&lt;li&gt;기계 검증 결과가 파일로 남는다.&lt;/li&gt;
&lt;li&gt;검증 후 변경된 아티팩트는 릴리스할 수 없다.&lt;/li&gt;
&lt;li&gt;중단된 실행을 이어갈 수 있다.&lt;/li&gt;
&lt;li&gt;부분 수정 시 영향받는 단계만 다시 실행할 수 있다.&lt;/li&gt;
&lt;li&gt;최종 결과물이 독립된 &lt;code&gt;RELEASE/&lt;/code&gt; 패키지로 만들어진다.&lt;/li&gt;
&lt;li&gt;알려진 제한과 승인 기록이 결과물에 포함된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;11. 피해야 할 설계&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;하나의 거대한 프롬프트에 전체 공정을 넣는 방식&lt;/li&gt;
&lt;li&gt;역할 이름만 여러 개 만들고 파일 계약을 두지 않는 방식&lt;/li&gt;
&lt;li&gt;모델이 &lt;code&gt;state.json&lt;/code&gt;을 임의로 직접 수정하게 하는 방식&lt;/li&gt;
&lt;li&gt;테스트 성공 여부를 대화 문장으로만 기록하는 방식&lt;/li&gt;
&lt;li&gt;생성 파일 수와 실제 논리 작업량을 혼동하는 방식&lt;/li&gt;
&lt;li&gt;검증 이후 파일 변경을 감지하지 못하는 방식&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;h2&gt;12. 권장 도입 순서&lt;/h2&gt;
&lt;p&gt;처음부터 모든 자동화를 만들 필요는 없다. 다음 순서로 확장한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;SKILL.md&lt;/code&gt;, &lt;code&gt;_workspace/state.json&lt;/code&gt;으로 최소 흐름을 만든다.&lt;/li&gt;
&lt;li&gt;초기화와 단계 전이 스크립트를 추가한다.&lt;/li&gt;
&lt;li&gt;manifest와 기계 검증을 추가한다.&lt;/li&gt;
&lt;li&gt;패키징과 검증 후 변경 감지를 추가한다.&lt;/li&gt;
&lt;li&gt;사람 QA와 고위험 승인 게이트를 추가한다.&lt;/li&gt;
&lt;li&gt;실제 실패 사례를 바탕으로 계약과 스크립트를 강화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;하네스의 완성도는 프롬프트의 길이가 아니라, 중단·오류·부분 수정·재실행 상황에서도 결과와 상태가 일치하는지로 판단한다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1790</guid>
      <comments>https://javaexpert.tistory.com/1790#entry1790comment</comments>
      <pubDate>Mon, 20 Jul 2026 15:00:11 +0900</pubDate>
    </item>
    <item>
      <title>pm-skills 분석</title>
      <link>https://javaexpert.tistory.com/1789</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 짧은 분석 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;phuryn/pm-skills는 일반적인 앱 저장소라기보다 &lt;b&gt;AI 에이전트가 제품관리 업무를 더 구조적으로 수행하도록 만드는 &amp;ldquo;스킬/커맨드 마켓플레이스&amp;rdquo;&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README 기준으로 이 저장소는 &lt;b&gt;9개 PM 플러그인, 68개 스킬, 42개 체인 워크플로우&lt;/b&gt;를 제공한다고 설명합니다. GitHub 저장소 화면 기준으로는 약 &lt;b&gt;24k stars, 2.4k forks, 62 commits&lt;/b&gt;가 표시됩니다. 루트에는 pm-product-discovery, pm-product-strategy, pm-execution, pm-market-research, pm-data-analytics, pm-go-to-market, pm-marketing-growth, pm-toolkit, pm-ai-shipping 같은 도메인별 플러그인 폴더가 있고, .claude-plugin/marketplace.json이 이들을 하나의 마켓플레이스로 묶습니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이선스는 LICENSE 파일 기준 &lt;b&gt;MIT License&lt;/b&gt;입니다. 저작권 표기는 2026년 Pawel Huryn으로 되어 있고, 복제&amp;middot;수정&amp;middot;배포&amp;middot;상업적 이용이 가능하지만 저작권 및 허가 문구를 포함해야 하며 보증은 제공되지 않습니다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/LICENSE&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 실행 테스트는 하지 않았습니다. 아래 글은 GitHub 페이지, README, LICENSE, marketplace 설정, 플러그인 예시 파일, 검증 스크립트, GitHub Actions 설정을 읽고 작성한 코드/문서 기반 분석입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;PM 업무를 AI에게 맡기는 게 아니라, PM 사고방식을 파일로 포장한 저장소&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 조금 익숙한 저장소라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 GitHub에는 AI용 프롬프트 모음이 많다.&lt;br /&gt;&amp;ldquo;PRD를 써준다.&amp;rdquo;&lt;br /&gt;&amp;ldquo;전략을 만들어준다.&amp;rdquo;&lt;br /&gt;&amp;ldquo;고객 인터뷰를 요약한다.&amp;rdquo;&lt;br /&gt;&amp;ldquo;런칭 플랜을 만든다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 이제 별로 놀랍지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 phuryn/pm-skills를 따라가 보니, 이 저장소의 핵심은 프롬프트 모음이 아니었다. 더 정확히는 &lt;b&gt;제품관리 업무를 AI가 따라갈 수 있는 작업 단위로 쪼개고, 그것을 스킬과 커맨드, 플러그인이라는 구조로 패키징한 저장소&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말하자면 PM을 위한 &amp;ldquo;프롬프트 북&amp;rdquo;이라기보다, AI 에이전트에게 주입하는 &lt;b&gt;제품관리 운영체제의 파일 시스템&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간 거창하게 들릴 수 있다. README도 실제로 &amp;ldquo;AI Operating System for Better Product Decisions&amp;rdquo;라는 표현을 쓴다. 하지만 코드를 보면 이 표현이 아주 허공에 떠 있지는 않다. 적어도 구조만 보면 그렇다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이 프로젝트는 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pm-skills는 Claude Code, Claude Cowork, Codex CLI, 그리고 일부 다른 AI 보조 도구에서 사용할 수 있는 PM 업무용 스킬 마켓플레이스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README는 이 저장소를 &lt;b&gt;68개 PM 스킬과 42개 체인 워크플로우를 9개 플러그인으로 묶은 마켓플레이스&lt;/b&gt;라고 설명한다. 분야는 제품 발견, 전략, 실행, 시장 조사, 데이터 분석, GTM, 마케팅 성장, PM 도구, AI로 작성된 코드의 출시 점검까지 넓게 잡혀 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 단어는 &amp;ldquo;스킬&amp;rdquo;과 &amp;ldquo;커맨드&amp;rdquo;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 특정 PM 업무를 수행하기 위한 지식과 절차다. 예를 들어 prioritize-assumptions는 가설을 Impact &amp;times; Risk 매트릭스로 정리하고, 어떤 가설을 먼저 검증해야 하는지 판단하도록 돕는다. 실제 SKILL.md에는 ICE, RICE, Opportunity Score 같은 프레임워크가 들어 있고, 사용자가 가설 목록이나 리서치 데이터를 제공하면 이를 먼저 읽으라는 지시도 포함되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/skills/prioritize-assumptions/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드는 여러 스킬을 엮은 워크플로우다. 예를 들어 /discover는 아이디어 발산, 가정 식별, 가정 우선순위화, 실험 설계, 디스커버리 플랜 작성까지 이어진다. 이 커맨드는 단순히 &amp;ldquo;아이디어 10개 내줘&amp;rdquo;가 아니라, 중간 체크포인트를 두고 사용자가 선택하거나 방향을 바꿀 수 있게 설계되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/commands/discover.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 이런 스킬과 커맨드를 도메인별로 묶은 설치 단위다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구분이 꽤 중요하다.&lt;br /&gt;많은 AI 프롬프트 저장소는 &amp;ldquo;좋은 프롬프트 목록&amp;rdquo;에서 멈춘다.&lt;br /&gt;이 저장소는 한 단계 더 가서 &amp;ldquo;업무 흐름&amp;rdquo;을 파일 구조로 만든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장소를 따라가 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 구조는 꽤 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 최상단에는 .claude-plugin, .docs/images, .github/workflows, tests, 그리고 9개의 pm-* 플러그인 폴더가 있다. GitHub 페이지에서 확인되는 주요 폴더는 다음과 같다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;.claude-plugin/
.docs/images/
.github/workflows/
pm-ai-shipping/
pm-data-analytics/
pm-execution/
pm-go-to-market/
pm-market-research/
pm-marketing-growth/
pm-product-discovery/
pm-product-strategy/
pm-toolkit/
tests/
AGENTS.md
CHANGELOG.md
CLAUDE.md
CONTRIBUTING.md
LICENSE
README.md
validate_plugins.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 웹앱 저장소였다면 src, app, server, package.json, pyproject.toml 같은 진입점을 먼저 찾았을 것이다. 그런데 이 저장소에는 그런 형태의 애플리케이션 코드는 중심이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 진입점은 두 종류다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 .claude-plugin/marketplace.json이다. 이 파일은 마켓플레이스의 이름, 버전, 설명, 소유자 정보, 그리고 9개 플러그인의 이름과 경로를 정의한다. 실제 파일에는 pm-product-discovery, pm-product-strategy, pm-execution, pm-market-research, pm-data-analytics, pm-go-to-market, pm-marketing-growth, pm-toolkit, pm-ai-shipping이 등록되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/.claude-plugin/marketplace.json&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 각 플러그인 폴더 내부의 .claude-plugin/plugin.json, skills/*/SKILL.md, commands/*.md다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 pm-product-discovery/.claude-plugin/plugin.json은 플러그인 이름, 버전, 설명, 작성자, 키워드, 홈페이지, 라이선스를 담고 있다. 이 플러그인은 제품 발견, 아이디어 발산, 실험, 가정 검증, 기능 우선순위화, 고객 인터뷰 합성을 위한 스킬이라고 설명되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/.claude-plugin/plugin.json&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소에서 &amp;ldquo;코드&amp;rdquo;라고 부를 만한 부분은 주로 validate_plugins.py다. 이 파일은 각 플러그인의 구조가 규칙을 지키는지 검사한다. plugin.json 필수 필드, 스킬의 YAML frontmatter, 커맨드의 description, README 섹션, 커맨드가 참조하는 스킬의 존재 여부 등을 확인한다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/raw/refs/heads/main/validate_plugins.py&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 제품은 파이썬 코드가 아니라 마크다운 파일들이다.&lt;br /&gt;파이썬은 그 제품이 망가지지 않도록 검사하는 관리 도구다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치와 실행 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README는 설치 대상을 크게 네 가지로 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Cowork에서는 GitHub 마켓플레이스를 추가하는 방식으로 설치한다. Claude Code CLI에서는 claude plugin marketplace add phuryn/pm-skills로 마켓플레이스를 추가한 뒤, 필요한 플러그인을 claude plugin install로 설치한다. Codex CLI도 같은 마켓플레이스 파일을 읽을 수 있다고 설명되어 있으며, codex plugin marketplace add phuryn/pm-skills와 codex plugin add 명령을 안내한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 README는 중요한 차이도 명시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex CLI에서는 스킬은 사용할 수 있지만, Claude식 /slash 커맨드는 Codex slash command로 동작하지 않는다고 설명한다. 대신 사용자가 자연어로 워크플로우를 설명하거나, 커맨드 파일을 읽어 Codex용 스킬로 변환하게 할 수 있다고 안내한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini CLI, OpenCode, Cursor, Kiro 같은 도구는 skills/*/SKILL.md를 복사해서 &amp;ldquo;스킬만&amp;rdquo; 사용하는 방식으로 설명되어 있다. README는 커맨드가 Claude-specific이라고 선을 긋는다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지점이 이 저장소의 현실적인 경계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 &amp;ldquo;어디서나 동일하게 실행되는 앱&amp;rdquo;이 아니다.&lt;br /&gt;AI 도구별로 해석 가능한 부분이 다르다.&lt;br /&gt;Claude에서는 스킬과 커맨드가 더 자연스럽고, 다른 도구에서는 스킬 문서의 재사용에 가까워진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 파이프라인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소의 파이프라인은 대략 이렇게 흐른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 /discover, /strategy, /write-prd, /plan-launch, /north-star 같은 커맨드를 입력한다. README의 Start Here 섹션도 새 아이디어면 /discover, 전략적 명확성이 필요하면 /strategy, PRD를 쓰려면 /write-prd로 시작하라고 안내한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드는 필요한 스킬을 호출한다. /discover의 경우 아이디어 브레인스토밍, 가정 식별, 가정 우선순위화, 실험 설계를 순서대로 수행한다. 중간에 사용자가 어떤 아이디어를 계속 가져갈지 고르게 하고, 최종적으로 디스커버리 플랜을 마크다운 문서로 작성하도록 한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/commands/discover.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 각 단계의 판단 기준을 제공한다. prioritize-assumptions는 Impact &amp;times; Risk 매트릭스를 사용하고, 가설별로 Impact와 Risk를 평가한 뒤, 실험이 필요한 항목을 가려낸다. 여기서 실험은 의견이 아니라 행동을 측정해야 하며, 명확한 성공 지표와 임계값이 있어야 한다고 지시한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/skills/prioritize-assumptions/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 대부분 문서다. PRD, 디스커버리 플랜, 인터뷰 스크립트, OKR, 로드맵, 테스트 시나리오, 출시 패킷 같은 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 이 저장소의 실제 &amp;ldquo;런타임&amp;rdquo;은 Python 서버나 Node 앱이 아니라, Claude Code/Cowork/Codex 같은 에이전트 환경이다. 저장소 자체는 실행 앱이라기보다 에이전트가 읽고 따르는 절차 문서 패키지다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술적으로 흥미로운 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흥미로운 부분은 모델 API 호출이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 이 저장소에는 OpenAI API 키, Anthropic API 키, 데이터베이스 연결, 서버 배포 설정 같은 것은 핵심 구조에 보이지 않는다. README와 확인한 파일 기준으로 외부 API를 직접 호출하는 앱 코드는 중심이 아니다. AI/LLM 의존성은 코드 내부 호출이 아니라 &lt;b&gt;호스트 AI 도구가 이 스킬과 커맨드를 해석한다는 방식&lt;/b&gt;으로 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로 더 중요한 것은 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 SKILL.md다.&lt;br /&gt;커맨드는 commands/*.md다.&lt;br /&gt;플러그인은 .claude-plugin/plugin.json으로 식별된다.&lt;br /&gt;마켓플레이스는 .claude-plugin/marketplace.json이 묶는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 PM 지식이 &amp;ldquo;글&amp;rdquo;로만 남지 않고, 에이전트가 읽을 수 있는 작업 단위가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validate_plugins.py는 이 구조가 깨지지 않도록 검사한다. 이 스크립트는 plugin.json의 필수 필드, 스킬 frontmatter의 name과 description, 디렉터리명과 스킬명 일치 여부, 커맨드 frontmatter, README의 예상 섹션, 커맨드가 참조하는 스킬의 존재 여부를 확인한다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/raw/refs/heads/main/validate_plugins.py&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Actions도 이 검증을 CI에 묶는다. .github/workflows/tests.yml은 pull request와 main 브랜치 push에서 Python 3.11과 3.13 매트릭스로 python validate_plugins.py를 실행하고, 이어서 python -m unittest discover -s tests -v를 실행한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/.github/workflows/tests.yml&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 작지만 중요한 설계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마크다운 기반 저장소는 시간이 지나면 쉽게 무너진다. 파일명은 바뀌고, frontmatter는 빠지고, 커맨드는 없는 스킬을 참조한다. 이 저장소는 그 문제를 어느 정도 예상하고 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PM 지식의 &amp;ldquo;체인화&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README는 커맨드가 서로 이어지도록 설계되었다고 설명한다. 어떤 커맨드가 끝나면 다음에 실행할 만한 관련 커맨드를 제안한다고 되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 이 저장소의 제품적 아이디어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM 업무는 단일 문서 생성으로 끝나지 않는다.&lt;br /&gt;아이디어를 냈으면 가정을 찾아야 한다.&lt;br /&gt;가정을 찾았으면 무엇을 먼저 검증할지 골라야 한다.&lt;br /&gt;검증할 것을 골랐으면 실험을 설계해야 한다.&lt;br /&gt;실험 결과가 나오면 PRD, 로드맵, 지표, 출시 계획으로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 챗봇에게 &amp;ldquo;PRD 써줘&amp;rdquo;라고 하면 그럴듯한 문서가 나온다.&lt;br /&gt;하지만 실제 PM 업무에서는 그 전에 해야 할 일이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 그 &amp;ldquo;전에 해야 할 일&amp;rdquo;을 노골적으로 드러낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 /discover는 이 점이 잘 보인다. 커맨드 파일은 사용자가 탐색하려는 것이 기존 제품인지 새 제품인지 먼저 구분하고, 사용자가 이미 알고 있는 것과 이 디스커버리가 어떤 의사결정에 영향을 주는지 묻는다. 이후 아이디어 발산, 가정 식별, 가정 우선순위화, 실험 설계를 거쳐 최종 디스커버리 플랜을 만든다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/commands/discover.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 단순 템플릿보다 조금 더 강하다.&lt;br /&gt;AI에게 &amp;ldquo;문서 결과물&amp;rdquo;이 아니라 &amp;ldquo;사고 순서&amp;rdquo;를 주기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pm-ai-shipping이 따로 눈에 띄는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9개 플러그인 중 개인적으로 가장 흥미로웠던 것은 pm-ai-shipping이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 플러그인은 전통적인 PM 업무라기보다, AI가 만든 코드를 출시 가능한 상태로 점검하는 데 초점을 둔다. README는 AI 에이전트가 코드를 빠르게 작성하지만 의도, 권한, 비밀값, 검증 규칙에 대한 기록을 남기지 않는다는 문제의식에서 출발한다고 설명한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/ship-check 커맨드를 보면 이 방향이 더 분명하다. 이 커맨드는 앱 문서화, 에이전트 운영 컨텍스트 생성, 보안 감사, 성능 감사, 테스트 커버리지 맵 도출, 최종 shipping packet 작성을 하나의 순서로 묶는다. 문서화가 먼저이고, 감사는 그 문서화된 의도와 실제 구현을 비교하는 방식으로 이어진다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-ai-shipping/commands/ship-check.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;intended-vs-implemented 스킬은 이 플러그인의 핵심 아이디어를 잘 보여준다. 이 스킬은 일반적인 린터가 코드 내부 일관성은 볼 수 있어도 &amp;ldquo;원래 의도한 대로 동작하는가&amp;rdquo;는 알 수 없다고 설명한다. 그래서 permissions.md, architecture.md, variables.md 같은 문서화된 의도를 먼저 읽고, 실제 코드의 enforcement point와 비교하라고 지시한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-ai-shipping/skills/intended-vs-implemented/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 이 부분은 PM 도구라기보다 AI 개발 시대의 리뷰 프로세스에 가깝다. PM이 &amp;ldquo;기능이 나왔는가&amp;rdquo;만 보는 것이 아니라, &amp;ldquo;문서화된 의도와 실제 구현이 일치하는가&amp;rdquo;를 점검하는 역할로 확장되는 흐름을 반영한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 장점은 구조화다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 PM 프레임워크를 단순히 나열하지 않는다. 발견, 전략, 실행, 시장 조사, 데이터 분석, GTM, 마케팅 성장, 도구, AI 출시 점검으로 도메인을 나누고, 각 도메인 안에서 스킬과 커맨드를 분리한다. .claude-plugin/marketplace.json은 이 9개 플러그인을 하나의 설치 가능한 마켓플레이스로 묶는다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/.claude-plugin/marketplace.json&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 장점은 워크플로우 감각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 /discover는 한 번에 결과물을 뱉는 커맨드가 아니다. 중간 체크포인트를 두고 사용자가 선택하거나 방향을 바꿀 수 있게 한다. PM 업무에서 중요한 것은 정답 생성보다 의사결정 과정이라는 점을 꽤 잘 반영한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/commands/discover.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 장점은 유지보수 장치다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마크다운 기반 스킬 저장소는 쉽게 산만해진다. 그런데 이 저장소는 validate_plugins.py로 스킬과 커맨드의 기본 형식을 검사하고, GitHub Actions에서 Python 3.11과 3.13으로 검증과 unittest를 실행한다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/raw/refs/heads/main/validate_plugins.py&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 번째 장점은 라이선스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MIT License라서 외부 활용, 수정, 배포, 상업적 이용이 비교적 자유롭다. 다만 라이선스 문구와 저작권 고지는 유지해야 하고, 소프트웨어는 &amp;ldquo;as is&amp;rdquo;로 제공된다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/LICENSE&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아쉬운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 아쉬움은 실행 환경 의존성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 자체적으로 돌아가는 독립 앱이 아니다. Claude Code, Claude Cowork, Codex CLI 같은 호스트 환경이 필요하다. README도 Codex에서는 Claude식 /slash 커맨드가 그대로 동작하지 않는다고 설명한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 사용자는 &amp;ldquo;저장소를 clone하고 실행&amp;rdquo;하는 경험이 아니라, &amp;ldquo;내 AI 도구에 스킬과 플러그인을 설치&amp;rdquo;하는 경험을 해야 한다. 이 차이를 모르면 처음에 혼란스러울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 실제 출력 품질 검증이 저장소만으로는 어렵다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬과 커맨드의 구조는 읽을 수 있다. 하지만 특정 회사의 실제 PRD, 고객 인터뷰, A/B 테스트 데이터에 넣었을 때 결과가 얼마나 좋은지는 별도의 실행 테스트가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Unverified] 나는 이 저장소를 실제 Claude Code나 Codex CLI에 설치해 실행하지 않았다. 따라서 명령 실행 안정성, 실제 응답 품질, 긴 컨텍스트에서의 일관성은 확인하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 PM 프레임워크의 해석 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README는 Teresa Torres, Marty Cagan, Alberto Savoia, Dan Olsen, Roger Martin, Ash Maurya 등 여러 저자의 작업을 참고했다고 설명한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;br /&gt;이런 프레임워크를 AI 스킬로 옮기는 것은 유용하지만, 동시에 단순화의 위험이 있다. 프레임워크는 맥락 없이 적용하면 체크리스트가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 이 저장소는 숙련 PM에게는 좋은 보조 장치가 될 수 있지만, PM 경험이 거의 없는 사용자가 그대로 따르면 &amp;ldquo;생각하는 척하는 문서 생산기&amp;rdquo;가 될 가능성도 있다. 이건 저장소의 결함이라기보다, 모든 프레임워크 자동화 도구가 갖는 위험이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제로 쓰려면 봐야 할 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 난이도는 사용하는 AI 도구에 따라 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Cowork 사용자는 README에 나온 GitHub 마켓플레이스 추가 방식으로 접근할 수 있다. Claude Code 사용자는 CLI 명령으로 마켓플레이스를 추가하고 플러그인을 설치한다. Codex CLI 사용자는 스킬은 활용할 수 있지만 slash command 동작 방식은 다르다. 다른 도구는 skills/*/SKILL.md를 복사해서 쓰는 접근에 가깝다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수나 API Key는 저장소 자체의 중심 주제는 아니다. 외부 LLM API 호출을 직접 구현한 앱이 아니기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용은 저장소가 아니라 호스트 AI 도구의 사용량에서 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 실제 비용은 PRD 생성, 리서치 요약, 대량 피드백 분석, 코드 감사처럼 긴 입력과 긴 출력이 필요한 작업에서 커질 가능성이 있다. 저장소 자체에는 비용 제어 로직이 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 저장 방식도 저장소 자체에는 별도 DB가 없다. 출력은 대체로 마크다운 문서나 사용자 워크스페이스에 생성되는 결과물로 설계되어 있다. /discover 커맨드는 최종 discovery plan을 마크다운 파일로 저장하라고 지시한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-product-discovery/commands/discover.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 측면에서는 사용자가 어떤 데이터를 AI 도구에 넣는지가 더 중요하다. 고객 인터뷰, NPS 응답, 내부 로드맵, 가격 전략, 경쟁사 분석, 출시 계획은 민감할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 팀에서 사용한다면 &amp;ldquo;어떤 문서를 AI 도구에 넣어도 되는가&amp;rdquo;에 대한 정책이 먼저 필요하다. 저장소가 MIT라고 해서 입력 데이터의 보안 문제가 사라지는 것은 아니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한국어 서비스로 응용한다면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 서비스로 바꾼다면 단순 번역만으로는 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PRD, OKR, 로드맵, 고객 인터뷰, GTM, 가격 전략 같은 기본 틀은 그대로 쓸 수 있다. 하지만 한국어 SaaS, 커머스, 교육, 금융, B2B 영업 환경에서는 사용하는 표현과 조직 문화가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 /plan-launch를 한국 시장용으로 바꾼다면 카카오톡 채널, 네이버 검색, 커뮤니티, 세일즈 미팅, 대기업 보안 검토, 전자계약, 세금계산서 같은 요소를 반영해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pm-data-analytics의 SQL 생성이나 cohort 분석도 국내 서비스에서 자주 쓰는 이벤트 네이밍, 결제 주기, 휴면 기준, 광고 채널 구분을 넣으면 더 쓸 만해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 한국어 PM 스킬 마켓플레이스를 만든다면 원본 구조는 유지하고, 각 스킬의 예시와 판단 기준을 국내 제품 조직의 업무 언어로 바꾸는 편이 좋다. &amp;ldquo;번역본&amp;rdquo;보다 &amp;ldquo;현지화된 PM 운영 템플릿&amp;rdquo;에 가깝게 가야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 만든다면 이렇게 바꿀 것 같다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP로 줄인다면 전부 가져오지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 만들 기능은 세 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, /discover다.&lt;br /&gt;아이디어에서 가정, 가정에서 실험으로 넘어가는 흐름은 초기 제품팀에 바로 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, /write-prd와 /red-team-prd다.&lt;br /&gt;PRD를 만드는 것보다 중요한 것은 PRD의 위험한 가정을 찾아내는 것이다. 이 저장소의 pm-execution에는 PRD, OKR, 로드맵, 프리모템, stakeholder map, user stories, test scenarios 같은 실행 관련 스킬과 커맨드가 포함되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, /ship-check다.&lt;br /&gt;AI로 코드를 빠르게 만드는 팀이라면 &amp;ldquo;문서화된 의도와 실제 구현의 차이&amp;rdquo;를 보는 워크플로우는 꽤 현실적이다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/refs/heads/main/pm-ai-shipping/commands/ship-check.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 붙일 기능은 데이터 분석, GTM, 마케팅 성장이다.&lt;br /&gt;처음부터 모든 PM 업무를 자동화하려고 하면 사용자는 어디서 시작해야 할지 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제품화한다면 &amp;ldquo;스킬 목록&amp;rdquo;보다 &amp;ldquo;현재 내 상황에서 다음에 할 일&amp;rdquo;을 추천하는 인터페이스가 중요해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 &amp;ldquo;새 기능 아이디어가 있다&amp;rdquo;고 하면 /discover로 보내고, &amp;ldquo;고객 피드백이 200개 있다&amp;rdquo;고 하면 /analyze-feedback로 보내고, &amp;ldquo;출시 전이다&amp;rdquo;라고 하면 /plan-launch나 /ship-check로 보내는 식이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영 리스크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 리스크는 잘못된 확신이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 PM 프레임워크를 사용하면 결과물이 논리적으로 보인다. 표도 있고, 우선순위도 있고, 실험도 있고, 성공 기준도 있다. 하지만 그 입력이 부실하면 출력도 부실하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 리스크는 조직 맥락 누락이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM 의사결정은 회사 전략, 팀 역량, 기술 부채, 영업 상황, 고객과의 약속에 묶여 있다. 스킬은 구조를 줄 수 있지만, 회사 내부의 권력 관계와 제약까지 자동으로 알지는 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 리스크는 민감 정보다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 인터뷰, 매출 데이터, 가격 전략, 경쟁사 대응, 출시 일정은 외부 AI 도구에 넣기 전에 검토가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 번째 리스크는 라이선스 오해다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MIT License는 활용 범위가 넓지만, 보증을 제공하지 않는다. 상업적 활용은 가능하더라도 원 저작권 및 라이선스 고지를 유지해야 한다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/LICENSE&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현실성 검토&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트로는 꽤 쓰기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 혼자 제품을 만들거나, 사이드 프로젝트를 검증하거나, 브런치/블로그/SaaS 아이디어를 정리하는 사람에게는 /discover, /strategy, /north-star, /write-prd 같은 흐름이 도움이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 내부 도구로도 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 팀에서 쓰려면 그냥 설치해서 쓰는 것보다, 회사의 PRD 양식, 실험 설계 방식, 데이터 지표 정의, 보안 정책을 반영해 커스터마이즈하는 편이 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상용 서비스로 확장하려면 한 단계 더 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구조는 AI 에이전트용 파일 패키지에 가깝다. 상용 SaaS로 만들려면 사용자 관리, 워크스페이스, 문서 저장소, 권한 관리, 버전 관리, 감사 로그, 입력 데이터 보안, 비용 제어, 결과물 리뷰 플로우가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 서비스로 바꾸려면 번역보다 현지화가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국내 조직에서 쓰는 PRD, OKR, 회의록, 릴리즈 노트, 고객 인터뷰, 영업 자료 양식에 맞게 스킬을 조정해야 한다. 특히 B2B SaaS라면 보안 검토, PoC, 계약, 세금계산서, 고객사 의사결정 구조 같은 항목이 들어가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용은 긴 문서 처리에서 커질 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 인터뷰 여러 건, 대량 피드백, 큰 코드베이스 감사, 출시 패킷 생성은 토큰 사용량이 커질 수 있다. 이 저장소 자체에는 비용 제한 장치가 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수 리스크는 스킬 품질 관리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 이 저장소는 validate_plugins.py와 CI를 갖고 있다. 하지만 형식 검증은 내용 품질 검증과 다르다. 좋은 PM 판단을 유지하려면 각 스킬의 예시, 기준, 프레임워크 적용 방식이 계속 업데이트되어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 생각&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소가 흥미로운 이유는 &amp;ldquo;AI가 PRD를 써준다&amp;rdquo;가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 이제 너무 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 지점은 PM 업무를 &lt;b&gt;스킬&lt;/b&gt;, &lt;b&gt;커맨드&lt;/b&gt;, &lt;b&gt;플러그인&lt;/b&gt;, &lt;b&gt;마켓플레이스&lt;/b&gt;라는 단위로 나누고, AI 에이전트가 따라갈 수 있는 구조로 만든 데 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 구조만으로 좋은 제품 의사결정이 나오지는 않는다.&lt;br /&gt;나쁜 입력, 얕은 리서치, 조직 맥락 부재는 여전히 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 적어도 이 저장소는 AI에게 &amp;ldquo;그럴듯한 문서 하나&amp;rdquo;를 요구하지 않는다.&lt;br /&gt;대신 이렇게 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아이디어를 넓혀라.&lt;br /&gt;그다음 가정을 찾아라.&lt;br /&gt;가장 위험한 가정을 골라라.&lt;br /&gt;실험으로 검증해라.&lt;br /&gt;문서화된 의도와 실제 구현이 맞는지도 봐라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정도면 프롬프트 모음이라고 부르기엔 조금 아깝다.&lt;br /&gt;작은 PM 작업 시스템이라고 부르는 편이 더 정확해 보인다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 활용 가능성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 한국어 기술 블로그 콘텐츠로 쓰기 좋습니다. &amp;ldquo;AI PM 도구&amp;rdquo;라는 표면보다, &lt;b&gt;PM 지식이 에이전트 실행 단위로 바뀌는 방식&lt;/b&gt;을 설명하기 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SaaS 아이디어로도 확장 가능합니다. 예를 들면 &amp;ldquo;한국어 PM Copilot&amp;rdquo;, &amp;ldquo;AI PRD 리뷰어&amp;rdquo;, &amp;ldquo;스타트업 디스커버리 워크벤치&amp;rdquo;, &amp;ldquo;AI로 만든 코드의 출시 점검 도구&amp;rdquo; 같은 방향입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육 서비스로도 맞습니다. PM 주니어에게 단순 템플릿을 주는 대신, 왜 아이디어 다음에 가정을 찾고, 왜 가정 다음에 실험을 설계해야 하는지 실습형으로 가르칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 서비스로 만들 경우에는 pm-ai-shipping이 특히 좋습니다. AI가 만든 코드의 문서화, 권한 검토, 테스트 커버리지 맵, 출시 패킷 생성은 앞으로 수요가 생길 만한 영역입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 독립 실행형 앱이 아닙니다. Claude Code/Cowork, Codex CLI 등 호스트 도구의 동작 방식에 의존합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 실행 테스트는 하지 않았기 때문에, 설치 안정성이나 결과 품질은 별도 검증이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MIT License라 상업적 활용은 가능하지만, 저작권 및 라이선스 고지는 유지해야 합니다. 또한 보증은 제공되지 않습니다. (&lt;a href=&quot;https://raw.githubusercontent.com/phuryn/pm-skills/main/LICENSE&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 데이터, 내부 전략, 가격 정책, 코드베이스를 AI 도구에 넣을 때는 회사 보안 정책을 먼저 확인해야 합니다. 특히 팀 내부 도구로 쓰려면 &amp;ldquo;어떤 데이터를 넣을 수 있는가&amp;rdquo;를 명확히 해야 합니다.&lt;/p&gt;
&lt;h1&gt;가장 실용적인 스킬 순위&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. create-prd&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어나 기능 설명을 받아 다음 8개 영역의 PRD를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Summary
Contacts
Background
Objective
Market Segments
Value Propositions
Solution
Release
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 문제, 대상 사용자, 성공 지표, 핵심 기능, 기술 조건, 검증되지 않은 가정, 출시 범위를 하나의 문서로 정리합니다. 결과가 충분히 크면 PRD-[product-name].md 형태로 저장하도록 설계되어 있습니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-execution/skills/create-prd/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 많이 쓰는가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이디어를 개발팀에 전달할 때&lt;/li&gt;
&lt;li&gt;MVP 범위를 결정할 때&lt;/li&gt;
&lt;li&gt;기존 기획서를 다시 구조화할 때&lt;/li&gt;
&lt;li&gt;기능 개발 전에 요구사항을 명확하게 만들 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 적용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;pm-execution의 create-prd 스킬을 사용해
StoryMine 보상형 웹소설&amp;middot;웹툰 앱의 MVP PRD를 작성해줘.

대상 사용자:
웹소설과 웹툰을 자주 읽는 18~40세 사용자

핵심 기능:
- 작품 탐색
- 웹소설&amp;middot;웹툰 열람
- 댓글, 좋아요, 공유
- 활동 점수 계산
- 미확정 보상
- INK Point 확정
- 보상 내역
- 부정 이용 탐지

조건:
- 블록체인 토큰은 MVP에서 제외
- 모바일 앱 기준
- 확인되지 않은 내용은 가정으로 분리
- P0, P1, P2로 기능을 구분
- 각 P0 기능에 성공 조건을 작성
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 결과&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;P0
- 회원가입 및 로그인
- 작품 목록과 상세 페이지
- 콘텐츠 뷰어
- 정상 읽기 검증
- 일일 활동 점수
- 미확정 보상
- 보상 내역

P1
- 댓글 및 좋아요
- 작품 추천
- 알림

P2
- 토큰 전환
- 외부 지갑
- NFT
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스킬은 문서 틀을 만드는 데 강하지만, 세부 개발 백로그까지 자동으로 완성하는 스킬은 아닙니다. PRD 작성 뒤에는 user-stories와 test-scenarios를 연결하는 것이 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. identify-assumptions-new&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 제품이 실패할 수 있는 가정을 다음 8개 영역에서 찾습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Value
Usability
Viability
Feasibility
Ethics
Go-to-Market
Strategy &amp;amp; Objectives
Team
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PM, 디자이너, 엔지니어 관점에서 각각 실패 원인을 검토하고, 각 가정의 신뢰도와 검증 방법도 제안합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-product-discovery/skills/identify-assumptions-new/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 많이 쓰는가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 아이디어는 있는데 성공 가능성을 모를 때&lt;/li&gt;
&lt;li&gt;개발 전에 가장 위험한 요소를 찾을 때&lt;/li&gt;
&lt;li&gt;블록체인&amp;middot;리워드&amp;middot;AI처럼 불확실성이 큰 서비스&lt;/li&gt;
&lt;li&gt;투자나 개발 리소스를 투입하기 전&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 적용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;pm-product-discovery의 identify-assumptions-new 스킬로
StoryMine 아이디어의 핵심 가정을 분석해줘.

가정 영역:
- 사용자가 포인트 때문에 실제로 더 많이 읽을 것인가
- 보상이 없어져도 사용자가 계속 남을 것인가
- 정상 읽기와 반복 채굴을 구분할 수 있는가
- 작품 공급 비용과 보상 비용을 감당할 수 있는가
- 작가와 출판사가 이 모델을 받아들일 것인가
- 리워드가 콘텐츠 품질보다 활동량만 늘리지 않는가

각 가정에 대해:
- 가정 내용
- 실패 시 영향
- 현재 신뢰도
- 검증에 필요한 데이터
- 가장 저렴한 검증 방법
을 작성해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 결과 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영역핵심 가정실패 영향&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;사용자는 보상 때문에 읽기 빈도를 늘린다&lt;/td&gt;
&lt;td&gt;보상 시스템의 존재 이유 상실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viability&lt;/td&gt;
&lt;td&gt;콘텐츠 매출이 보상 비용보다 크다&lt;/td&gt;
&lt;td&gt;적자 구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feasibility&lt;/td&gt;
&lt;td&gt;실제 읽기와 자동 넘기기를 구분할 수 있다&lt;/td&gt;
&lt;td&gt;채굴 어뷰징 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethics&lt;/td&gt;
&lt;td&gt;보상 설계가 중독적 사용을 과도하게 유도하지 않는다&lt;/td&gt;
&lt;td&gt;사용자&amp;middot;규제 위험&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GTM&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;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 진행하는 보상형 앱, 코인 채굴 앱, 거래소 봇처럼 위험 요소가 많은 프로젝트에는 create-prd보다 먼저 실행하는 편이 좋습니다. 기획서를 정교하게 작성했더라도 핵심 가정이 틀리면 전체 설계가 무의미해질 수 있기 때문입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. prioritize-assumptions&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 찾은 가정을 Impact &amp;times; Risk 기준으로 분류합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;낮은 영향 + 낮은 위험 &amp;rarr; 나중에 검증
높은 영향 + 낮은 위험 &amp;rarr; 구현 진행
낮은 영향 + 높은 위험 &amp;rarr; 아이디어 제외 검토
높은 영향 + 높은 위험 &amp;rarr; 먼저 실험
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험이 필요한 가정에는 행동 기반 측정법, 성공 지표, 통과 기준을 붙입니다. 저장소는 의견보다 실제 행동을 측정하고, 최소한의 노력으로 검증 학습을 얻도록 지시합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-product-discovery/skills/prioritize-assumptions/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;prioritize-assumptions 스킬을 사용해
다음 StoryMine 가정을 우선순위화해줘.

1. 사용자는 포인트를 받으면 하루 읽기 시간이 늘어난다.
2. 사용자는 댓글 보상을 위해 의미 있는 댓글을 작성한다.
3. 정상 읽기와 자동 스크롤을 구분할 수 있다.
4. 출판사는 보상 풀의 일부를 부담할 의사가 있다.
5. 사용자는 포인트를 쿠폰으로 교환하고 싶어 한다.
6. 친구 추천 보상이 신규 사용자를 유입시킨다.

Impact &amp;times; Risk 매트릭스로 분류하고,
상위 3개에는 가장 저렴한 실험과 통과 기준을 작성해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 우선순위&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;정상 읽기와 자동 채굴을 구분할 수 있는가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보상이 실제 읽기 시간을 증가시키는가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보상 비용을 부담할 수 있는 수익구조가 있는가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;쿠폰 교환 수요&lt;/li&gt;
&lt;li&gt;친구 추천 효과&lt;/li&gt;
&lt;li&gt;댓글 작성 증가&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identify-assumptions-new가 위험을 많이 찾는 스킬이라면, prioritize-assumptions는 그중 &lt;b&gt;이번 주에 검증할 것만 고르는 스킬&lt;/b&gt;입니다. 두 스킬을 항상 묶어서 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. brainstorm-experiments-new&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 서비스의 핵심 가정을 실제 행동으로 검증할 실험을 설계합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 다음 형태의 XYZ 가설을 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;tp&quot;&gt;&lt;code&gt;최소 X%의 Y 사용자가 Z 행동을 할 것이다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 랜딩 페이지, 설명 영상, 이메일 캠페인, 대기자 모집, 선주문, 수동 MVP 등을 사용해 최소 비용으로 검증합니다. 각 실험에는 가설, 방법, 지표, 성공 기준이 포함됩니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-product-discovery/skills/brainstorm-experiments-new/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 적용 예제&lt;/h3&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;brainstorm-experiments-new 스킬을 사용해
StoryMine의 핵심 가정을 검증할 실험을 설계해줘.

핵심 가정:
포인트 보상이 있으면 사용자의 주간 콘텐츠 열람 시간이 증가한다.

조건:
- 앱 전체를 개발하지 않는다.
- 2주 이내에 실행 가능해야 한다.
- 설문 응답보다 실제 행동을 측정한다.
- 실험별 비용, 대상 인원, 성공 기준을 표시한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 실험&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실험 1: 수동 보상 실험&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;대상: 기존 웹소설 독자 30명
기간: 7일
방법:
- 첫 3일은 보상 없이 사용
- 다음 4일은 검증된 읽기 10분당 포인트 지급
측정:
- 일평균 읽기 시간
- 완독률
- 다음 날 재방문율
통과 기준:
- 읽기 시간이 20% 이상 증가
- 보상 기간 재방문율이 15% 이상 증가
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실험 2: 랜딩 페이지&lt;/h4&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;보상형 웹소설 앱 소개
&amp;rarr; 가입 대기 명단
&amp;rarr; 보상 방식 선택
&amp;rarr; 실제 가입 전환율 측정
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스킬은 &amp;ldquo;좋은 아이디어 같다&amp;rdquo;는 설문 결과보다 실제 가입, 결제, 시간 투자, 반복 사용 같은 행동을 중시합니다. 개발 비용이 큰 앱일수록 PRD 작성 전에 한 번 실행하는 가치가 큽니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. user-stories&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PRD의 기능을 개발 가능한 작은 백로그로 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 기준을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;3 C's
- Card
- Conversation
- Confirmation

INVEST
- Independent
- Negotiable
- Valuable
- Estimable
- Small
- Testable
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 형식은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;As a [사용자 역할],
I want to [행동],
so that [얻는 가치].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 디자인 링크, 인수 조건, 예외 상황 등을 추가합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-execution/skills/user-stories/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;pm-execution의 user-stories 스킬을 사용해
StoryMine의 미확정 보상 기능을 개발 가능한 사용자 스토리로 분해해줘.

사용자 역할:
- 일반 독자
- 운영자
- 콘텐츠 관리자

각 스토리에 포함:
- 제목
- 사용자 스토리
- 상세 설명
- 사전 조건
- 인수 조건
- 예외 상황
- API 또는 데이터 요구사항
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 결과 예시&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;제목: 읽기 활동에 대한 미확정 보상 확인

사용자 스토리:
독자로서,
나는 정상 읽기로 획득한 미확정 포인트를 확인하고 싶다.
그래야 오늘의 활동이 보상에 반영되었는지 알 수 있다.

인수 조건:
1. 정상 읽기 조건을 충족하면 미확정 포인트가 생성된다.
2. 같은 회차를 반복해서 열어도 중복 지급되지 않는다.
3. 미확정 포인트에는 확정 예정 시간이 표시된다.
4. 부정 이용 검토 중인 포인트는 별도 상태로 표시된다.
5. 검증에 실패한 포인트는 지급되지 않고 사유가 기록된다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PRD가 &amp;ldquo;무엇을 만들 것인가&amp;rdquo;라면 사용자 스토리는 &amp;ldquo;개발팀이 어느 단위로 만들 것인가&amp;rdquo;를 정의합니다. Codex에 실제 구현을 맡길 때 특히 중요합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. test-scenarios&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 스토리와 인수 조건을 QA가 실행할 수 있는 테스트 시나리오로 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테스트에는 다음이 들어갑니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;Test Objective
Starting Conditions
User Role
Test Steps
Expected Outcomes
Edge Cases
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 흐름뿐 아니라 잘못된 입력, 경계값, 오류 상황도 포함하도록 설계되어 있습니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-execution/skills/test-scenarios/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;test-scenarios 스킬을 사용해
다음 사용자 스토리의 테스트 시나리오를 만들어줘.

사용자 스토리:
독자로서 정상 읽기를 완료하면 미확정 포인트를 받고 싶다.

인수 조건:
- 회차의 80% 이상 읽어야 한다.
- 최소 3분 이상 체류해야 한다.
- 동일 회차 보상은 하루 1회만 가능하다.
- 비정상적으로 빠른 페이지 이동은 보상하지 않는다.

테스트 구분:
- 정상 흐름
- 경계값
- 중복 요청
- 네트워크 실패
- 앱 강제 종료
- 자동 스크롤 및 어뷰징
- 서버 시간 조작
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 시나리오&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;테스트: 체류 시간 미달

시작 조건:
- 사용자는 로그인 상태
- 해당 회차 보상 이력 없음

절차:
1. 회차를 연다.
2. 콘텐츠의 90%까지 이동한다.
3. 2분 59초 후 회차를 종료한다.
4. 보상 내역을 확인한다.

예상 결과:
- 미확정 포인트가 생성되지 않는다.
- 서버에 체류 시간 미달 사유가 기록된다.
- 사용자에게 조건 미충족 안내가 표시된다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user-stories &amp;rarr; test-scenarios 조합은 기획서와 실제 구현 사이의 해석 차이를 줄여 줍니다. 사용자 요청에서 자주 나오는 &amp;ldquo;백엔드까지 완벽하게 구현하고 테스트해줘&amp;rdquo; 같은 작업에는 이 두 스킬을 먼저 적용하는 것이 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. prioritize-features와 prioritization-frameworks&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prioritize-features는 기능을 다음 기준으로 평가해 상위 5개를 선정합니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Impact
Effort
Risk
Strategic Alignment
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 기능뿐 아니라 제외된 기능과 제외 이유도 정리합니다. prioritization-frameworks는 Opportunity Score, ICE, RICE, MoSCoW, Kano 등 9개 우선순위 프레임워크의 공식과 사용 상황을 제공합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-product-discovery/skills/prioritize-features/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;prioritize-features 스킬로 StoryMine MVP 기능을 평가해줘.

후보:
- 웹소설 뷰어
- 웹툰 뷰어
- 댓글 보상
- 좋아요 보상
- 공유 보상
- 일일 미션
- 레벨 시스템
- 추천인 보상
- 포인트 쿠폰 교환
- 토큰 출금
- 외부 지갑 연결
- 작가 대시보드
- 관리자 어뷰징 탐지

목표:
8주 안에 리워드가 콘텐츠 재방문율을 높이는지 검증

Impact, Effort, Risk, Strategic Alignment 기준으로 평가하고
MVP 상위 5개와 제외 기능을 알려줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 MVP 상위 기능&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;콘텐츠 뷰어&lt;/li&gt;
&lt;li&gt;정상 읽기 검증&lt;/li&gt;
&lt;li&gt;미확정 포인트&lt;/li&gt;
&lt;li&gt;보상 내역&lt;/li&gt;
&lt;li&gt;운영자 어뷰징 검토&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 출금, 외부 지갑, 레벨 시스템, 추천인 보상은 핵심 가정 검증에 직접 필요하지 않아 후순위가 될 가능성이 높습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. product-strategy&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 9개 영역의 Product Strategy Canvas를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Vision
Market Segments
Relative Costs
Value Proposition
Trade-offs
Key Metrics
Growth
Capabilities
Defensibility
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &amp;ldquo;무엇을 할 것인가&amp;rdquo;뿐 아니라 &amp;ldquo;무엇을 하지 않을 것인가&amp;rdquo;를 명시하고, 성장 방식과 장기적인 방어력을 함께 검토합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-product-strategy/skills/product-strategy/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;product-strategy 스킬을 사용해
StoryMine의 제품 전략을 작성해줘.

조건:
- 초기 시장은 한국 웹소설 독자
- MVP는 포인트 방식
- 블록체인 토큰은 후속 단계
- 콘텐츠 확보 예산이 제한적
- 리워드 어뷰징 위험이 높음

포함:
- 첫 번째 핵심 세그먼트
- 기존 플랫폼과의 차별화
- 하지 않을 것
- 핵심 지표
- 성장 구조
- 경쟁사가 복제하기 어려운 요소
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;create-prd가 제품 한 버전의 실행 문서라면 product-strategy는 여러 버전에 걸친 방향을 정합니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;product-strategy
&amp;rarr; create-prd
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서가 일반적으로 더 자연스럽습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. competitor-analysis&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 조사를 통해 직접 경쟁사 5개를 찾고 다음 내용을 비교합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시장과 고객 범위&lt;/li&gt;
&lt;li&gt;포지셔닝&lt;/li&gt;
&lt;li&gt;기능&lt;/li&gt;
&lt;li&gt;가격&lt;/li&gt;
&lt;li&gt;GTM 전략&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 data-ke-size=&quot;size16&quot;&gt;저장소의 스킬은 단순 기능표가 아니라 경쟁 구도와 차별화 기회까지 종합하도록 정의되어 있습니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-market-research/skills/competitor-analysis/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;competitor-analysis 스킬을 사용해
보상형 웹소설&amp;middot;웹툰 플랫폼의 경쟁 환경을 조사해줘.

비교 대상:
- 직접 경쟁 서비스
- 웹소설 플랫폼
- 웹툰 플랫폼
- 읽기 보상 앱
- 행동 보상형 Web3 앱

비교 항목:
- 핵심 고객
- 콘텐츠 확보 방식
- 리워드 구조
- 수익모델
- 가격
- 어뷰징 방지
- 사용자 유지 전략
- StoryMine이 차별화할 수 있는 영역

모든 최신 정보에 출처와 확인 날짜를 표시해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁사 조사는 최신 웹 데이터가 필요합니다. 따라서 스킬을 실행할 때 &amp;ldquo;웹에서 현재 정보를 확인하고 확인되지 않는 내용은 추정하지 말라&amp;rdquo;는 조건을 추가하는 편이 안전합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. metrics-dashboard와 north-star-metric&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;north-star-metric은 비즈니스를 다음 세 게임 중 하나로 분류합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Attention
Transaction
Productivity
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 고객이 얻는 핵심 가치를 나타내는 단일 North Star Metric과 3~5개의 입력 지표를 선정합니다. metrics-dashboard는 여기에 데이터 출처, 계산식, 시각화 방식, 목표, 경보 기준을 추가합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-marketing-growth/skills/north-star-metric/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StoryMine 예제&lt;/h3&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;north-star-metric과 metrics-dashboard 스킬을 순서대로 사용해
StoryMine MVP의 지표 체계를 설계해줘.

주의:
- 단순 가입자 수나 총 발행 포인트를 North Star로 사용하지 않는다.
- 실제 고객 가치와 연결된 지표를 선택한다.
- 어뷰징 활동은 제외한다.
- 각 지표의 정확한 계산식을 정의한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[Inference] 가능한 결과 예시&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;North Star 후보:
주간 검증 완료 콘텐츠 열람 회차 수

입력 지표:
- 주간 활성 독자 수
- 독자당 검증 열람 회차
- 첫 열람 후 7일 재방문율
- 보상 확정률
- 작품 탐색 &amp;rarr; 열람 전환율

건강 지표:
- 부정 활동 비율
- 보상 취소율
- 콘텐츠 신고율
- 포인트 비용 / 활성 사용자
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보상 앱은 총 지급 포인트나 클릭 수가 크게 보일 수 있지만, 그것이 실제 고객 가치와 일치하지 않을 수 있습니다. 따라서 NSM과 어뷰징 방지용 건강 지표를 함께 설정해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. pre-mortem과 strategy-red-team&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스킬은 비슷해 보이지만 목적이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬핵심 질문&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pre-mortem&lt;/td&gt;
&lt;td&gt;출시가 실패했다고 가정하면 무엇이 원인이었는가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strategy-red-team&lt;/td&gt;
&lt;td&gt;현재 계획을 지탱하는 핵심 가정은 무엇이며, 어떤 조건에서 무너지는가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pre-mortem은 위험을 Tigers, Paper Tigers, Elephants로 구분하고 출시 전 해결, 빠른 후속 조치, 모니터링 항목으로 나눕니다. strategy-red-team은 핵심 주장마다 Fails if, 이번 주 확보할 증거, 중단 기준, 가장 저렴한 실험을 작성합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-execution/skills/pre-mortem/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;strategy-red-team 스킬로 StoryMine PRD를 공격적으로 검토해줘.

각 핵심 가정에 대해:
- Claim
- Fails if
- Evidence to get this week
- Kill criterion
- Cheapest test

를 작성해줘.

일반적인 위험 목록은 제외하고
이 기획이 실제로 중단되어야 하는 핵심 가정만 5개 이내로 선정해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예상 핵심 중단 조건&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Fails if:
보상을 제공해도 7일 재방문율이 유의미하게 증가하지 않는다.

Kill criterion:
보상 그룹의 재방문율이 비보상 그룹보다 5%p 이상 높지 않다.

Cheapest test:
30~50명을 대상으로 2주간 수동 포인트 지급 실험.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. shipping-artifacts와 intended-vs-implemented&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무엇을 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 진행된 뒤 사용하는 스킬입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shipping-artifacts는 코드베이스에서 다음 문서를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;architecture.md
사용자&amp;middot;권한 흐름
permissions.md
variables/secrets 문서
tests.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일, Cron, SEO, 내장 AI 에이전트 등이 있을 때만 조건부 문서를 추가합니다. intended-vs-implemented는 문서에 적힌 의도와 실제 코드의 권한 검사, 데이터 필터, 입력 검증을 비교합니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/pm-ai-shipping/skills/shipping-artifacts/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js 프로젝트 적용 예제&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;pm-ai-shipping의 shipping-artifacts와
intended-vs-implemented 스킬을 사용해
현재 Next.js 프로젝트를 출시 전 검토해줘.

1. documentation/에 시스템 문서를 생성한다.
2. 사용자 역할과 권한을 정리한다.
3. 환경변수와 비밀정보 사용 위치를 정리한다.
4. 문서의 권한 규칙과 실제 서버 코드를 비교한다.
5. 문서와 코드가 다른 부분만 증거 파일과 줄 번호로 보고한다.
6. 확인할 수 없는 내용은 finding이 아니라 unverified question으로 분리한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 Codex로 Next.js, SQLite/PostgreSQL, 관리자 기능까지 한 번에 구현하는 경우 이 스킬의 가치가 큽니다. 코드가 실행되는 것과 기획된 권한&amp;middot;보상 규칙이 정확히 적용되는 것은 별개의 문제이기 때문입니다.&lt;/p&gt;
&lt;h1&gt;사용자에게 가장 적합한 기본 조합&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 사용자가 자주 만드는 MVP 앱 기획과 실제 개발 흐름에는 다음 10단계가 가장 실용적입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. brainstorm-ideas-new
2. identify-assumptions-new
3. prioritize-assumptions
4. brainstorm-experiments-new
5. product-strategy
6. competitor-analysis
7. create-prd
8. user-stories
9. test-scenarios
10. shipping-artifacts
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 세 구간으로 줄이면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;제품 검증
brainstorm-ideas-new
&amp;rarr; identify-assumptions-new
&amp;rarr; prioritize-assumptions
&amp;rarr; brainstorm-experiments-new

기획&amp;middot;개발
product-strategy
&amp;rarr; create-prd
&amp;rarr; user-stories
&amp;rarr; test-scenarios

출시 검토
pre-mortem
&amp;rarr; shipping-artifacts
&amp;rarr; intended-vs-implemented
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Codex에서 한 번에 요청하는 프롬프트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex에서는 Claude용 /discover, /write-prd 같은 명령이 Slash Command로 직접 실행되지 않으므로, 스킬 이름과 실행 순서를 자연어로 지정하는 방식이 권장됩니다. (&lt;a href=&quot;https://github.com/phuryn/pm-skills/blob/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;설치된 pm-skills 플러그인을 사용해
StoryMine 모바일 앱의 제품 기획 워크플로를 실행해줘.

다음 스킬을 순서대로 사용한다.

1. brainstorm-ideas-new
2. identify-assumptions-new
3. prioritize-assumptions
4. brainstorm-experiments-new
5. product-strategy
6. create-prd
7. user-stories
8. test-scenarios
9. pre-mortem

규칙:
- 각 단계의 결과를 별도 Markdown 파일로 저장한다.
- 이전 단계 결과를 다음 단계의 입력으로 사용한다.
- 검증된 사실, 가정, 제안을 명확히 구분한다.
- 최신 시장 정보는 웹에서 확인하고 출처를 표시한다.
- MVP는 8주 내 개발 가능한 범위로 제한한다.
- 블록체인 토큰과 외부 지갑은 MVP에서 제외한다.
- 최종적으로 개발 우선순위와 권장 폴더 구조를 제시한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 익혀야 할 핵심만 고르면 &lt;b&gt;identify-assumptions-new &amp;rarr; prioritize-assumptions &amp;rarr; create-prd &amp;rarr; user-stories &amp;rarr; test-scenarios&lt;/b&gt; 다섯 개입니다. 이 조합만으로도 막연한 앱 아이디어를 위험 검토가 포함된 개발 명세와 QA 기준까지 연결할 수 있습니다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1789</guid>
      <comments>https://javaexpert.tistory.com/1789#entry1789comment</comments>
      <pubDate>Mon, 20 Jul 2026 14:03:39 +0900</pubDate>
    </item>
    <item>
      <title>로컬에서 AI로 전용 Gemma를 파인튜닝해보자 (Gemma Trainer)</title>
      <link>https://javaexpert.tistory.com/1788</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1784379708830&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;gemma-skills/skills/gemma-trainer at main &amp;middot; google-gemma/gemma-skills&quot; data-og-description=&quot;Skills for the Gemma and model/agent interactions. Contribute to google-gemma/gemma-skills development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/google-gemma/gemma-skills/tree/main/skills/gemma-trainer&quot; data-og-url=&quot;https://github.com/google-gemma/gemma-skills/tree/main/skills/gemma-trainer&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YMaxe/dJMb9c9KuO4/0FOOrLQHKLUUKtGhKFdzK1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cUDeaB/dJMb9efqEzq/fANPCkLqQ3gc0qYZGIkONK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/google-gemma/gemma-skills/tree/main/skills/gemma-trainer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/google-gemma/gemma-skills/tree/main/skills/gemma-trainer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YMaxe/dJMb9c9KuO4/0FOOrLQHKLUUKtGhKFdzK1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cUDeaB/dJMb9efqEzq/fANPCkLqQ3gc0qYZGIkONK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;gemma-skills/skills/gemma-trainer at main &amp;middot; google-gemma/gemma-skills&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Skills for the Gemma and model/agent interactions. Contribute to google-gemma/gemma-skills development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google이 오픈소스 저장소 **google-gemma/gemma-skills**에 **gemma-trainer**라는 AI 에이전트용 스킬을 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도구의 목적은 사용자가 복잡한 학습 설정을 직접 다루지 않아도, AI에게 자연어로 요청해 자신의 컴퓨터에서 Gemma 모델을 미세조정할 수 있게 하는 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Gemma Trainer란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gemma-trainer는 일반적인 GUI 프로그램이나 새로운 학습 프레임워크가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성은 크게 두 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI 에이전트가 읽는 학습 지침서인 SKILL.md&lt;/li&gt;
&lt;li&gt;바로 사용할 수 있는 Python 스크립트 5개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SKILL.md 안에는 다음과 같은 내용이 정리되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 학습 방식을 선택해야 하는지&lt;/li&gt;
&lt;li&gt;데이터 형식은 어떻게 만들어야 하는지&lt;/li&gt;
&lt;li&gt;LoRA 파라미터를 어떻게 설정해야 하는지&lt;/li&gt;
&lt;li&gt;GPU 메모리 부족을 어떻게 피해야 하는지&lt;/li&gt;
&lt;li&gt;학습 결과를 어떻게 평가해야 하는지&lt;/li&gt;
&lt;li&gt;학습한 모델을 모바일용 형식으로 어떻게 변환하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, AI가 단순히 &amp;ldquo;미세조정 방법을 알고 있는 상태&amp;rdquo;가 아니라, 실제 환경에서 어떤 스크립트와 파라미터를 사용해야 하는지 판단할 수 있도록 만든 실행 지침서입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 사용 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 AI에게 자연어로 요청합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemma 4 E2B 모델을 이 데이터셋으로 미세조정해 줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 AI 에이전트가 gemma-trainer 지침을 읽고 다음 작업을 수행합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;사용자 요청
&amp;rarr; 데이터 형식 검사
&amp;rarr; 학습 방식 선택
&amp;rarr; LoRA 및 메모리 설정
&amp;rarr; 학습 실행
&amp;rarr; 결과 평가
&amp;rarr; 필요하면 파라미터 수정 후 재학습
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 학습 명령어나 복잡한 옵션을 사용자가 모두 기억할 필요가 없다는 것이 핵심입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 지원하는 세 가지 학습 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SFT: 감독 미세조정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답이 포함된 학습 데이터를 사용해 모델에 새로운 지식이나 작업 방식을 가르치는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 예시는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 업종 전문용어 학습&lt;/li&gt;
&lt;li&gt;회사 내부 문서 기반 질의응답&lt;/li&gt;
&lt;li&gt;특정 형식으로 답변하도록 학습&lt;/li&gt;
&lt;li&gt;한국어 교육이나 법률&amp;middot;의료 등 특정 분야 모델 제작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DPO: 선호도 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 질문에 대한 좋은 답변과 좋지 않은 답변을 비교해, 원하는 말투나 응답 방식을 학습시키는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 간결하게 답하도록 만들기&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;h3 data-ke-size=&quot;size23&quot;&gt;RM: 보상 모델 학습&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 답변 중 어떤 답변이 더 좋은지 점수를 매기는 평가 모델을 만드는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 예시는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 결과 자동 평가&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 포함된 Python 스크립트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공된 설명 기준으로 총 5개의 스크립트가 포함되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SFT 학습 스크립트&lt;/li&gt;
&lt;li&gt;DPO 학습 스크립트&lt;/li&gt;
&lt;li&gt;Reward Model 학습 스크립트&lt;/li&gt;
&lt;li&gt;데이터 형식 검증 스크립트&lt;/li&gt;
&lt;li&gt;데이터 증류 스크립트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순한 설명 문서가 아니라, 실제 학습을 실행할 수 있는 기본 템플릿까지 제공하는 구조입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Unsloth 기반의 저사양 학습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gemma-trainer는 가능하면 &lt;b&gt;Unsloth&lt;/b&gt;를 우선 사용하도록 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공된 글에서는 Unsloth 측의 주장으로 다음 성능이 언급됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPU 메모리 사용량 최대 70% 절감&lt;/li&gt;
&lt;li&gt;학습 속도 최대 2배 향상&lt;/li&gt;
&lt;li&gt;소비자용 단일 GPU에서도 비교적 큰 모델 미세조정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Unverified] 8GB VRAM으로 실제 어느 크기의 Gemma 모델까지 안정적으로 학습할 수 있는지는 모델 구조, 양자화 방식, 시퀀스 길이, 배치 크기, LoRA 설정에 따라 크게 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8GB GPU에서는 일반적으로 다음과 같은 절약 설정이 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4비트 양자화&lt;/li&gt;
&lt;li&gt;QLoRA&lt;/li&gt;
&lt;li&gt;작은 배치 크기&lt;/li&gt;
&lt;li&gt;Gradient Accumulation&lt;/li&gt;
&lt;li&gt;Gradient Checkpointing&lt;/li&gt;
&lt;li&gt;짧은 Context Length&lt;/li&gt;
&lt;li&gt;낮은 LoRA Rank&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 텍스트뿐 아니라 이미지&amp;middot;오디오도 지원&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemma 모델의 종류에 따라 다음 형태의 데이터 학습을 지원하는 방향으로 설계되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트&lt;/li&gt;
&lt;li&gt;이미지&lt;/li&gt;
&lt;li&gt;오디오&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모든 Gemma 모델이 모든 입력 형식을 지원하는 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 제공된 사례에서는 사용자가 음성 작업에 맞지 않는 모델을 선택했을 때, AI 에이전트가 모델의 기능 제한을 확인하고 다른 모델을 추천했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 스킬 파일에는 모델별 기능 제한과 선택 기준도 포함되어 있어 잘못된 학습 요청을 사전에 확인하는 역할을 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 학습한 모델의 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습이 끝난 모델은 서버에서만 사용하는 것이 아니라 경량 형식으로 변환할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음 형식과 환경이 언급됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GGUF 변환&lt;/li&gt;
&lt;li&gt;로컬 PC에서 실행&lt;/li&gt;
&lt;li&gt;LiteRT-LM 기반 모바일 실행&lt;/li&gt;
&lt;li&gt;스마트폰과 IoT 기기 탑재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 전체 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Gemma 모델 선택
&amp;rarr; 자체 데이터 준비
&amp;rarr; 로컬 LoRA 미세조정
&amp;rarr; 평가 및 수정
&amp;rarr; LoRA 병합 또는 양자화
&amp;rarr; GGUF&amp;middot;LiteRT-LM 형식 변환
&amp;rarr; PC&amp;middot;모바일&amp;middot;IoT에서 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 이 도구의 가장 큰 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 Gemma를 미세조정하려면 사용자가 직접 다음 내용을 공부해야 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transformers&lt;/li&gt;
&lt;li&gt;PEFT&lt;/li&gt;
&lt;li&gt;LoRA와 QLoRA&lt;/li&gt;
&lt;li&gt;TRL&lt;/li&gt;
&lt;li&gt;데이터 전처리&lt;/li&gt;
&lt;li&gt;GPU 메모리 설정&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 data-ke-size=&quot;size16&quot;&gt;gemma-trainer는 이런 판단 기준을 AI가 읽을 수 있는 스킬 문서로 표준화했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 새로운 학습 기술을 만든 것보다는 &lt;b&gt;기존 미세조정 절차와 노하우를 AI 에이전트가 직접 실행할 수 있는 운영 매뉴얼로 만든 것&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 문장 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gemma Trainer는 사용자가 자연어로 요청하면 AI 에이전트가 데이터 검사, SFT&amp;middot;DPO&amp;middot;RM 선택, LoRA 설정, 로컬 학습, 평가, 모바일용 모델 변환까지 안내하거나 실행할 수 있도록 만든 Gemma 미세조정용 오픈소스 스킬입니다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1788</guid>
      <comments>https://javaexpert.tistory.com/1788#entry1788comment</comments>
      <pubDate>Sat, 18 Jul 2026 22:01:07 +0900</pubDate>
    </item>
    <item>
      <title>AI가 코드를 쓰는 시대, 개발자는 무엇을 설계해야 할까</title>
      <link>https://javaexpert.tistory.com/1787</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현보다 설계에 더 많은 시간을 쓰게 된 AI 코딩 개발 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 년 전까지만 해도 소프트웨어 개발에서 가장 많은 시간이 들어가는 일은 코드를 작성하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 확인하고, 적절한 라이브러리를 찾고, 함수를 만들고, 오류를 수정하고, 테스트를 작성했다. 설계가 중요하다는 사실은 모두 알고 있었지만, 실제 업무 시간의 상당 부분은 결국 구현에 사용됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 본격적으로 사용하기 시작하면서 이 흐름이 달라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 내가 직접 코드를 작성하는 일이 거의 없다. 그렇다고 개발에 사용하는 시간이 줄어든 것은 아니다. 오히려 AI와 설계를 논의하고, 작업을 독립적인 단위로 나누고, 여러 에이전트가 동시에 작업할 수 있도록 환경을 만드는 데 더 많은 시간을 쓰고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발의 중심이 &lt;b&gt;구현에서 설계로 이동하고 있는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 코딩의 시작은 프롬프트가 아니라 설계다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 내가 사용하는 개발 흐름은 대략 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AI와 충분한 설계 세션을 진행한다.&lt;br /&gt;그다음 설계를 바탕으로 작업을 여러 개의 독립적인 태스크로 나눈다.&lt;br /&gt;각 태스크는 Git worktree로 분리한 뒤 여러 AI 세션에서 병렬로 구현한다.&lt;br /&gt;구현이 끝나면 AI가 테스트와 실제 동작을 검증한다.&lt;br /&gt;마지막으로 구현을 담당하지 않은 별도의 AI 세션에서 코드 리뷰를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 가장 중요한 단계는 첫 번째인 설계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 AI 에이전트를 동시에 움직이려면 각 에이전트가 무엇을 해야 하는지 명확하게 이해해야 한다. 설계가 모호한 상태에서 병렬 작업을 시작하면, 각각의 에이전트가 서로 다른 방향으로 코드를 작성할 가능성이 커진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 작업이 끝난 뒤에야 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 한 가지 데이터 구조를 기대하고 있는데 프런트엔드는 다른 구조를 사용하고 있거나, 비슷한 기능이 서로 다른 방식으로 중복 구현될 수도 있다. 책임의 경계가 잘못 나뉘면 여러 에이전트가 같은 파일을 동시에 수정하는 상황도 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 더 자율적으로 일할수록 사람은 구현 과정을 일일이 감시하기 어렵다. 그래서 구현을 시작하기 전에 사람과 AI가 같은 그림을 보고 있는지 확인하는 일이 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩에서 설계는 단순한 준비 단계가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계가 곧 실행 계획이고, 여러 에이전트를 움직이는 공통 언어다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업 방법보다 목표를 먼저 공유한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 일을 맡길 때 파일별 수정 방법이나 함수 내부의 구현 방식까지 지나치게 자세히 지시하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 파일을 열고, 세 번째 함수에 조건문을 추가한 다음, 새로운 타입을 만든 뒤, 이 함수에서 호출하세요.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 세세한 작업 절차를 제공하면 AI는 지시된 순서를 정확하게 수행하는 데 집중한다. 문제는 사람이 생각하지 못했던 더 나은 방법을 AI가 발견할 가능성도 함께 줄어든다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 &amp;ldquo;적당히 잘 만들어줘&amp;rdquo;라고 요청하는 것도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 맡기기 전에 다음 내용을 충분히 논의한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜 이 변경이 필요한가&lt;/li&gt;
&lt;li&gt;어떤 상태가 되면 작업이 완료된 것인가&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 data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 &lt;b&gt;설계를 구체화하는 것&lt;/b&gt;과 &lt;b&gt;작업 순서를 세세하게 지시하는 것&lt;/b&gt;을 구분하는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표, 제약 조건, 책임의 경계, 인터페이스, 검증해야 할 동작은 구현 전에 충분히 정리해야 한다. 반면 어떤 파일부터 수정할지, 함수 안에서 어떤 문법을 사용할지는 AI가 판단하도록 맡길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 TypeScript에서 interface와 type 중 무엇을 사용할지, let을 피하기 위해 삼항 연산자를 사용할지 같은 세부 구현에 신경을 많이 썼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 그보다 더 큰 질문에 집중한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타입은 어느 레이어가 소유해야 하는가.&lt;br /&gt;이 변경의 영향 범위를 어디까지 제한할 수 있는가.&lt;br /&gt;이 컴포넌트가 정말 이 책임을 가져야 하는가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 작성하는 속도는 매우 빠르다. 그래서 사람은 코드 한 줄의 모양보다 시스템 전체의 일관성을 살피는 데 집중해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 전에 AI에게 질문을 받는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와의 설계 세션에서는 내가 질문을 던지는 것만큼 AI에게 질문을 받는 과정도 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 구현을 시작하기 전에 AI가 설계에 관한 질문을 하나씩 던지도록 한다. 질문을 통해 설계 의사결정 트리의 분기를 차례대로 해결하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 인증 기능을 추가한다면 다음과 같은 질문이 나올 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 방식인가, 토큰 방식인가&lt;/li&gt;
&lt;li&gt;토큰 만료 시 자동 갱신이 필요한가&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 data-ke-size=&quot;size16&quot;&gt;사람이 처음부터 모든 예외 상황을 떠올리기는 어렵다. AI가 질문을 던지면 미처 생각하지 못한 설계 분기를 발견할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 AI의 제안을 무조건 받아들이면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 추천안을 제시할 때마다 &amp;ldquo;좋아, 그렇게 하자&amp;rdquo;라고 답한다면 설계를 AI에 전적으로 맡기는 것과 다르지 않다. 기존 아키텍처를 선택한 이유, 반드시 지켜야 하는 비즈니스 규칙, 제안에서 느껴지는 불편함을 사람이 직접 설명해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 세션에서 필요한 능력은 새로운 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임을 어디에서 나눌 것인지, 의존 관계를 어느 방향으로 둘 것인지, 미래의 변경을 어느 정도까지 고려할 것인지 판단하는 능력이다. 전통적인 소프트웨어 엔지니어링에서 중요하다고 배워온 능력이 AI 시대에도 그대로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 아무것도 없는 상태에서 완벽한 설계를 만들어주는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 선택지를 넓혀주고 질문을 던져준다. 최종 판단은 여전히 사람이 해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설계 결정은 남기고, 작업 절차는 버린다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 세션에는 중요한 판단이 많이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이 구조를 선택했는지, 어떤 대안을 검토했는지, 어떤 제약 때문에 특정 방식을 포기했는지 같은 정보다. 이 내용은 이후 여러 구현 세션에서 공통으로 참고할 수 있는 가치 높은 컨텍스트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 설계 결과를 채팅 기록에만 남겨두지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 GitHub Issue의 댓글에 설계 내용을 정리하는 방식을 자주 사용한다. Issue는 작업 진행 상황과 연결돼 있고 수정 기록이 남기 때문에 의사결정 과정을 추적하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 쉽게 읽도록 저장소 안에 Markdown 파일로 작성하는 방법도 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모든 내용을 영구 문서로 남길 필요는 없다. 이번 구현에서만 필요한 세부 작업 순서까지 문서화하면, 시간이 지나면서 업데이트되지 않는 오래된 문서가 쌓인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 설계 문서를 두 가지로 나누어 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 오래 남아야 하는 설계 결정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처의 방향, 데이터 모델의 원칙, 패키지 간 의존 관계와 같이 장기적으로 유효한 내용은 ADR이나 별도의 설계 문서로 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 이번 작업에서만 필요한 실행 절차다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태스크 분할, 작업 순서, 임시 체크리스트 등은 Issue 댓글이나 작업용 문서에 작성하고 구현이 끝나면 폐기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 문장으로 정리하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의사결정은 남기고, 작업 절차는 사용한 뒤 버린다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하나의 설계를 여러 AI가 동시에 구현한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계가 끝나면 작업을 독립적으로 실행할 수 있는 단위로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 기능을 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 스키마와 마이그레이션&lt;/li&gt;
&lt;li&gt;백엔드 API&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 data-ke-size=&quot;size16&quot;&gt;각 작업은 Git worktree를 이용해 별도의 작업 디렉터리로 분리한다. 그리고 각 worktree를 서로 다른 AI 세션에 연결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 여러 에이전트가 동시에 작업하더라도 파일 충돌을 줄일 수 있다는 것이다. 설계 세션과 구현 세션을 분리할 수 있고, 각 태스크의 진행 상황도 개별적으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 병렬 작업이 가능한 경계를 찾는 것 자체가 하나의 설계 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임이 제대로 나뉘어 있지 않은 코드베이스에서는 여러 작업을 안전하게 분리하기 어렵다. 하나의 기능을 수정하기 위해 수십 개 파일이 동시에 바뀌거나, 서로 다른 모듈이 같은 상태를 직접 수정한다면 에이전트 간 충돌이 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 책임과 의존 관계가 명확한 코드베이스에서는 작업을 여러 에이전트에게 쉽게 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, AI 병렬 개발의 효율은 사용하는 모델의 성능만으로 결정되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드베이스의 구조가 얼마나 명확한지가 더 중요할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI가 헤매지 않도록 개발 환경을 준비한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;worktree를 만들었다고 해서 바로 병렬 개발이 가능해지는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 worktree에 node_modules가 없거나 환경변수가 설정되지 않았다면 AI는 애플리케이션을 실행하지 못한다. 의존성 설치에 실패하면 AI는 원인을 찾기 위해 여러 번 명령을 실행하고, 예상하지 못한 방향으로 문제를 해결하려 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 worktree가 생성되는 순간 기본적인 개발 환경이 자동으로 준비되도록 구성하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 작업을 자동화할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의존성 디렉터리 연결&lt;/li&gt;
&lt;li&gt;환경변수 파일 생성&lt;/li&gt;
&lt;li&gt;로컬 데이터베이스 준비&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 data-ke-size=&quot;size16&quot;&gt;AI를 잘 활용하려면 좋은 프롬프트만 작성해서는 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 실패하지 않고 작업할 수 있는 환경을 함께 설계해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI가 자신의 코드를 직접 검증하게 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 구현을 맡기려면 AI가 자신의 변경 결과를 확인할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 수정하고도 결과가 올바른지 판단할 방법이 없다면 AI는 사실상 눈을 감고 개발하는 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 테스트, 타입 검사, Lint 같은 정적 검증뿐 아니라 실제 동작 확인까지 AI가 수행하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프런트엔드 변경이라면 브라우저를 직접 조작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지가 정상적으로 열리는지 확인하고, 버튼을 누르고, 폼에 값을 입력하고, 오류 메시지가 올바르게 표시되는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 변경이라면 curl이나 API 테스트 도구를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 보내고, 응답 코드와 데이터 구조를 확인하고, 잘못된 입력에 대한 오류 처리도 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 무엇을 검증할지 설계 단계에서 정해두는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;로그인 기능을 구현한다&amp;rdquo;는 설명만으로는 부족하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 계정으로 로그인할 수 있는가&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 data-ke-size=&quot;size16&quot;&gt;이처럼 기대하는 행동을 명확하게 정의해야 AI도 완료 여부를 스스로 판단할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;피드백을 빠르게 받을 수 있는 환경이 중요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 수정한 뒤 결과를 확인하기까지 걸리는 시간이 짧을수록 개발 속도는 빨라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 서버를 실행하는 데 몇 분이 걸리고 테스트가 끝나는 데 오랜 시간이 필요하다면, AI의 피드백 루프도 느려진다. 에이전트는 기다리거나 불완전한 정보로 다음 판단을 내려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 로컬 개발 환경과 CI 속도를 개선하는 일의 중요성이 커지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript 프로젝트에서는 Vite, Vitest, Oxlint, Oxfmt, 새로운 TypeScript 도구 체인처럼 빠른 실행 속도를 목표로 하는 도구들이 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 도구를 선택하느냐보다 중요한 것은 기준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 변경한 결과를 얼마나 빠르게 확인할 수 있는가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 환경에서는 모델과 프롬프트뿐 아니라 테스트 실행 시간, 서버 시작 시간, 브라우저 자동화 속도도 핵심 성능 지표가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현한 AI와 리뷰하는 AI를 분리한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현이 끝나고 Pull Request를 만들면 별도의 AI 세션에서 코드 리뷰를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현을 담당한 AI에게 자신의 코드를 다시 검토하게 할 수도 있다. 하지만 같은 세션은 자신이 내린 결정을 정당화하는 방향으로 판단할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 구현 과정에 대한 컨텍스트가 없는 새로운 세션을 열어 리뷰를 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰 AI에는 다음 내용을 확인하도록 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계 요구사항을 충족하는가&lt;/li&gt;
&lt;li&gt;논리적인 오류가 있는가&lt;/li&gt;
&lt;li&gt;예외 처리가 빠져 있지 않은가&lt;/li&gt;
&lt;li&gt;테스트가 부족하지 않은가&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 data-ke-size=&quot;size16&quot;&gt;물론 AI가 리뷰한다고 해서 사람이 코드를 전혀 확인하지 않아도 되는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증, 결제, 개인정보, 데이터 마이그레이션처럼 오류의 영향이 큰 변경은 사람이 직접 집중해서 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모든 코드를 같은 강도로 읽을 필요는 줄어들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 반복적이고 국소적인 문제를 먼저 확인하고, 사람은 설계와 고위험 영역에 집중하는 방식으로 역할을 나눌 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트의 암묵지를 문서와 스킬로 만든다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 프로젝트를 처음 접할 때 가장 어려워하는 것은 문서에 적혀 있지 않은 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리 프로젝트에서는 이 모듈에서 데이터베이스에 직접 접근하면 안 된다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;API 응답은 반드시 이 형식을 사용한다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 테스트는 특정 명령으로 실행해야 한다.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 팀에서 일하면서 자연스럽게 익히지만 AI는 매번 코드와 문서를 탐색하며 규칙을 추론해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 반복을 줄이기 위해 프로젝트 규칙을 CLAUDE.md나 에이전트 스킬로 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 다음처럼 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLAUDE.md에는 저장소 구조, 기본 명령어, 공통 원칙, 프로젝트의 암묵지를 간결하게 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 스킬에는 브라우저 검증, API 테스트, 데이터베이스 마이그레이션처럼 여러 태스크에서 반복되는 작업 방법을 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 스킬에는 설계 인터뷰, 코드 리뷰, 문서 작성처럼 여러 프로젝트에서 공통으로 사용하는 작업 방식을 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 지시를 매번 프롬프트에 반복하고 있다면 문서나 스킬로 옮길 시점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 작업하는 로그를 살펴보면 어떤 부분에서 자주 망설였는지 확인할 수 있다. 반복적으로 잘못된 파일을 탐색하거나 같은 명령을 여러 번 실행한다면, 그 부분이 문서화가 필요한 지점이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 프롬프트보다 좋은 아키텍처가 먼저다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서와 스킬을 아무리 잘 정리해도 코드베이스의 구조가 복잡하면 한계가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 의존 방향을 지켜라&amp;rdquo;라고 문서에 반복해서 적는 것보다, 패키지 구조와 Lint 규칙을 통해 잘못된 의존 자체가 불가능하도록 만드는 편이 더 확실하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 변경이 여러 영역으로 무분별하게 퍼지는 코드, 책임이 얽힌 코드, 인터페이스만 보고 동작을 이해하기 어려운 코드는 AI에게도 다루기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 기존 코드에서 패턴을 찾고 그 패턴을 따라 변경하는 데 강하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 코드베이스에 잘못된 패턴이 많으면 AI도 그 패턴을 재생산할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 다루기 좋은 아키텍처에 완전히 새로운 원칙이 필요한 것은 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책임의 경계가 명확하다&lt;/li&gt;
&lt;li&gt;의존 방향이 일관돼 있다&lt;/li&gt;
&lt;li&gt;변경의 영향 범위를 제한할 수 있다&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 data-ke-size=&quot;size16&quot;&gt;이 원칙들은 오래전부터 유지보수하기 좋은 소프트웨어의 조건으로 이야기돼 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 아키텍처는 사람만을 위한 것이 아니다. AI가 코드를 탐색하고, 작업을 분리하고, 스스로 검증하는 데도 도움이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드를 쓰는 능력보다 전체를 보는 능력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 구현을 맡기기 시작하면 직접 코드를 작성하는 시간은 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 특정 문법이나 세부 구현 패턴을 즉시 떠올리는 능력이 예전보다 약해졌다고 느낀다. 코드 한 줄의 아름다움이나 작은 리팩터링에 집중하는 시간도 줄었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 시스템 전체를 바라보는 시간이 늘었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임의 경계가 올바른지, 의존 관계가 복잡해지고 있지 않은지, 여러 변경을 통합했을 때 일관성이 유지되는지 고민한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;능력이 단순히 좋아졌다거나 나빠졌다고 보기는 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의력을 사용하는 위치가 달라진 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 현재 사용하는 설계 능력 역시 과거에 직접 코드를 작성하고 실패했던 경험에서 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 모든 구현을 AI에게 맡긴 개발자가 같은 설계 능력을 어떻게 습득할 수 있을지는 아직 분명하지 않다. AI 시대의 주니어 개발자 교육과 학습 방법은 앞으로 별도로 고민해야 할 문제다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 다음의 병목은 코드 리뷰다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 여러 개의 PR을 동시에 만들 수 있게 되면 새로운 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람의 리뷰 속도가 AI의 구현 속도를 따라가지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 코드를 작성하는 데 시간이 오래 걸렸고, 숙련된 개발자가 코드를 읽는 속도는 상대적으로 빨랐다. 그래서 리뷰 과정이 자연스럽게 개발 속도를 따라갈 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI가 짧은 시간 안에 여러 변경을 만들어내면 리뷰 대기열이 길어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 모든 코드를 사람이 같은 수준으로 검토하는 방식을 바꿔야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰를 계층화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 로직 오류, 테스트 누락, 코드 가독성, 기존 패턴 위반 같은 일차적인 문제를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 다음과 같은 부분에 집중한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 제대로 충족하는가&lt;/li&gt;
&lt;li&gt;설계의 책임 경계와 일치하는가&lt;/li&gt;
&lt;li&gt;API와 데이터 모델 변경이 적절한가&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 data-ke-size=&quot;size16&quot;&gt;한 걸음 더 나아가면 구현 전에 설계 자체를 사람끼리 리뷰할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 합의한 설계를 기준으로 AI가 구현하고, 개별 구현은 자동 검증과 AI 리뷰를 중심으로 통과시키는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 사람이 가장 중요한 판단에 집중할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR은 작게 나눠야 한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 짧은 시간 안에 큰 변경을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무 제한 없이 작업을 맡기면 하나의 PR에 수십 개 파일과 여러 기능이 함께 포함되기 쉽다. PR이 커질수록 리뷰어의 부담은 커지고, 중요한 문제를 놓칠 가능성도 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AI에게 처음부터 PR을 작은 단위로 분리하도록 요청하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음처럼 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 모델과 마이그레이션&lt;/li&gt;
&lt;li&gt;핵심 도메인 로직&lt;/li&gt;
&lt;li&gt;API 엔드포인트&lt;/li&gt;
&lt;li&gt;프런트엔드 연동&lt;/li&gt;
&lt;li&gt;테스트와 문서&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 어떤 기준으로 PR을 나누는 것이 좋은지 제안하게 할 수도 있다. 브랜치 분리나 커밋 이동처럼 번거로운 Git 작업도 AI가 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 AI가 작성한 PR이라도 작성자인 사람은 변경 내용을 설명할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;AI가 만들어서 잘 모릅니다&amp;rdquo;라는 설명은 리뷰어에게 이해 비용과 책임을 떠넘기는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계 의도와 변경 이유를 설명할 수 없다면 아직 그 PR을 제출할 준비가 되지 않은 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;승인 요청을 늘린다고 더 안전해지는 것은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 파일을 수정하거나 명령을 실행할 때마다 사람의 승인을 요구하도록 설정하면 자율적인 작업이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;승인 단계가 많을수록 안전해 보일 수 있지만 반드시 그렇지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 너무 많은 승인 요청을 받으면 승인 피로가 발생한다. 처음에는 내용을 자세히 확인하지만, 시간이 지나면 습관적으로 승인 버튼을 누르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 정말 위험한 작업도 놓칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 방법은 AI가 안전하게 실패할 수 있는 환경을 만드는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중요한 시스템과 분리된 샌드박스&lt;/li&gt;
&lt;li&gt;삭제해도 복구할 수 있는 worktree&lt;/li&gt;
&lt;li&gt;제한된 권한의 개발용 계정&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 data-ke-size=&quot;size16&quot;&gt;모든 행동을 사람에게 확인받는 대신, 문제가 생겨도 피해가 제한되는 구조를 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI의 자율성을 높이는 일과 안전성을 높이는 일은 서로 반대되는 목표가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경을 제대로 설계하면 두 가지를 함께 추구할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가장 싼 모델이 가장 경제적인 것은 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 비용을 줄이기 위해 단순 작업은 저렴한 모델에 맡기고, 복잡한 작업은 고성능 모델에 맡기는 방식이 자주 제안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모델의 토큰 가격만으로 비용을 비교해서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저렴한 모델이 설계를 잘못 이해해 구현을 다시 해야 하거나, 리뷰와 수정이 반복된다면 전체 비용은 오히려 커질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 기준은 하나의 변경을 완료하기까지 드는 총비용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 설계 단계에서 잘못된 결정이 내려지면 이후 병렬로 실행되는 모든 태스크가 잘못된 방향으로 진행될 수 있다. 따라서 설계 세션에서는 가능한 한 충분한 추론 능력을 가진 모델을 사용하는 편이 합리적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 기존 패턴을 그대로 따르는 단순 수정이나 반복 작업은 더 가벼운 모델로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 모델 종류뿐 아니라 추론 강도를 조절하는 방식도 중요해질 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 필요한 것은 감에 의존한 모델 선택이 아니라 태스크의 복잡도, 실패 가능성, 재작업 비용을 함께 고려하는 기준이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI가 계속 일하고 있어야 안심되는 감각&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 AI 에이전트를 사용하다 보면 항상 무언가를 실행하고 있어야 한다는 압박을 느낄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용량 제한이 있는 구독 서비스를 이용하면 남은 사용량을 쓰지 못하는 것이 손해처럼 느껴지기도 한다. AI가 멈춰 있는 시간이 곧 생산성 손실처럼 보일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI의 결과는 매번 조금씩 다르다. 어떤 결과가 나올지 기다리는 과정이 일종의 뽑기처럼 느껴질 때도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI가 계속 실행되고 있다는 사실과 가치 있는 일이 진행되고 있다는 사실은 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 작업을 늘리면 구현량은 증가하지만 리뷰와 통합 부담도 함께 늘어난다. 필요하지 않은 태스크를 만들고 AI의 가동률 자체를 목표로 삼는 순간, 개발은 오히려 복잡해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 설계에는 AI에게 무엇을 시킬 것인지뿐 아니라 &lt;b&gt;무엇을 시키지 않을 것인지&lt;/b&gt;를 판단하는 일도 포함될 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI 시대에도 결국 중요한 것은 설계다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩이 발전할수록 개발자의 역할이 사라진다고 단정하기는 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 개발자가 시간을 사용하는 방식은 분명히 달라지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 코드를 작성하는 시간은 줄어들고, 다음과 같은 일에 더 많은 시간이 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제를 명확하게 정의하는 일&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가 스스로 검증할 수 있는 환경을 만드는 일&lt;/li&gt;
&lt;li&gt;여러 변경을 통합하고 위험을 판단하는 일&lt;/li&gt;
&lt;li&gt;AI가 만든 결과를 설명하고 책임지는 일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 더 많은 코드를 작성할수록 사람은 더 큰 그림을 봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 결과는 좋은 프롬프트 하나에서 나오지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명확한 설계, 정돈된 아키텍처, 빠른 피드백, 자동화된 검증, 안전한 실행 환경이 함께 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 AI 코딩 시대에도 변하지 않는 사실이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋은 소프트웨어는 코드를 많이 작성해서 만들어지는 것이 아니라, 중요한 결정을 올바르게 내려서 만들어진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 구현을 담당하게 된 지금, 개발자의 가장 중요한 코드는 어쩌면 실제 코드가 아닐지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 정의하고, 경계를 나누고, 검증 기준을 세우는 설계 자체가 개발자가 작성해야 할 새로운 코드인지도 모른다.&lt;/p&gt;
&lt;figure id=&quot;og_1784379110717&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;最近の AI コーディングで実践している、設計を中心とした開発の進め方&quot; data-og-description=&quot;AI コーディングエージェントの自律性が向上し、複数のエージェントを並行して動かすことが日常になったことで、開発の中で最も時間を使う工程が実装から設計へ移りつつあります。この&quot; data-og-host=&quot;azukiazusa.dev&quot; data-og-source-url=&quot;https://azukiazusa.dev/blog/recent-ai-coding-development-process-centered-on-design/&quot; data-og-url=&quot;https://azukiazusa.dev/blog/recent-ai-coding-development-process-centered-on-design&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WJ7yi/dJMb8PGIXBy/hbjPLc6ksPyuFk3kPZhlT0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/PtXaV/dJMb85v1MJV/AhtQGBKzjAvJHUXRRRiT3k/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/boeHu7/dJMb8UH112I/TK4Bq6kYSlBk9wy7cuiqck/img.jpg?width=768&amp;amp;height=571&amp;amp;face=0_0_768_571&quot;&gt;&lt;a href=&quot;https://azukiazusa.dev/blog/recent-ai-coding-development-process-centered-on-design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://azukiazusa.dev/blog/recent-ai-coding-development-process-centered-on-design/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WJ7yi/dJMb8PGIXBy/hbjPLc6ksPyuFk3kPZhlT0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/PtXaV/dJMb85v1MJV/AhtQGBKzjAvJHUXRRRiT3k/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/boeHu7/dJMb8UH112I/TK4Bq6kYSlBk9wy7cuiqck/img.jpg?width=768&amp;amp;height=571&amp;amp;face=0_0_768_571');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;最近の AI コーディングで実践している、設計を中心とした開発の進め方&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI コーディングエージェントの自律性が向上し、複数のエージェントを並行して動かすことが日常になったことで、開発の中で最も時間を使う工程が実装から設計へ移りつつあります。この&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;azukiazusa.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발상식</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1787</guid>
      <comments>https://javaexpert.tistory.com/1787#entry1787comment</comments>
      <pubDate>Sat, 18 Jul 2026 21:52:07 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 상태관리에도 Signals가 온다: Riverpod, BLoC 이후의 선택지가 될 수 있을까?</title>
      <link>https://javaexpert.tistory.com/1786</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 상태관리는 늘 중요한 주제입니다.&lt;br /&gt;처음에는 setState로 충분해 보입니다. 하지만 화면이 복잡해지고, 상태가 여러 위젯에 걸쳐 공유되기 시작하면 이야기가 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색창에 글자 하나를 입력했을 뿐인데 카드 목록 전체가 다시 그려진다거나, 좋아요 버튼 하나를 눌렀는데 부모 위젯과 형제 위젯까지 rebuild되는 상황을 경험해 본 적이 있다면, 이 글의 주제는 꽤 흥미로울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Flutter 생태계에서 다시 주목받고 있는 개념이 있습니다. 바로 &lt;b&gt;Signals&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 새로운 개념은 아닙니다. 웹 프론트엔드에서는 이미 SolidJS, Preact, Angular 등에서 fine-grained reactivity, 즉 &lt;b&gt;세밀한 반응형 업데이트 방식&lt;/b&gt;으로 활용되어 왔습니다. Flutter에서도 signals와 signals_flutter 패키지를 통해 비슷한 방식의 상태관리를 사용할 수 있습니다. signals 패키지는 값을 담는 signal을 만들고 .value로 읽거나 업데이트하는 방식을 제공한다고 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Signals가 무엇인지, 기존 setState, BLoC, Riverpod과 무엇이 다른지, 그리고 실제 Flutter 앱에서 어떤 식으로 적용할 수 있는지 정리해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Flutter rebuild 문제는 왜 생길까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 UI는 상태가 바뀌면 다시 build됩니다. 이 자체는 문제가 아닙니다. Flutter는 원래 선언형 UI 프레임워크이고, 상태 변화에 따라 UI를 다시 그리는 방식으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;b&gt;불필요한 rebuild&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 ChangeNotifier 안에 다음과 같은 상태가 모두 들어 있다고 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class ProductViewModel extends ChangeNotifier {
  String searchKeyword = '';
  List&amp;lt;Product&amp;gt; products = [];
  bool isLoading = false;
  int selectedCategoryId = 0;
  bool showOnlyFavorites = false;

  void updateSearchKeyword(String value) {
    searchKeyword = value;
    notifyListeners();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 searchKeyword 하나만 바뀌어도 notifyListeners()가 호출됩니다. 그러면 이 notifier를 구독하고 있는 여러 위젯이 함께 반응할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색창에 글자 하나를 입력했을 뿐인데 상품 카드 목록, 필터 영역, 상단 배너까지 다시 build될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 해결책은 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Selector를 쓰거나, notifier를 쪼개거나, provider 구조를 세분화하거나, rebuild 범위를 직접 조절하면 됩니다. 하지만 이 작업은 꽤 번거롭습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 복잡해질수록 개발자는 계속 이런 고민을 하게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 위젯이 정말 이 상태를 구독해야 하나?&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 provider를 더 쪼개야 하나?&amp;rdquo;&lt;br /&gt;&amp;ldquo;여기서 Consumer를 감싸는 위치가 맞나?&amp;rdquo;&lt;br /&gt;&amp;ldquo;왜 이 카드까지 rebuild되지?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 바로 이 지점에서 등장합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Signals란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 쉽게 말해 &lt;b&gt;반응형 값을 담는 컨테이너&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 하나를 signal로 만들고, 그 값을 읽는 곳이 자동으로 의존성으로 등록됩니다. 이후 값이 바뀌면, 그 값을 실제로 읽은 부분만 다시 업데이트됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;import 'package:signals/signals.dart';

final count = signal(0);

void increment() {
  count.value++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count는 일반적인 int가 아닙니다.&lt;br /&gt;int 값을 담고 있는 signal입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 읽을 때는 다음처럼 .value를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;print(count.value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 바꿀 때도 .value를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;count.value = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 보면 ValueNotifier와 비슷해 보일 수 있습니다. 하지만 Signals의 핵심은 단순히 값을 감싸는 것이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;fine-grained dependency tracking&lt;/b&gt;, 즉 &lt;b&gt;세밀한 의존성 추적&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 계산식이 어떤 signal을 읽었는지, 어떤 위젯이 어떤 signal을 읽었는지를 런타임이 추적합니다. 그래서 count가 바뀌면 count를 실제로 읽은 곳만 반응합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. computed: signal에서 파생된 값 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals에는 computed라는 개념도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;computed는 하나 이상의 signal을 읽어서 새로운 값을 계산합니다. 그리고 의존하는 signal이 바뀌면 자동으로 다시 계산됩니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;final count = signal(0);

final isEven = computed(() {
  return count.value.isEven;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 isEven은 count에 의존합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count.value가 0이면 isEven.value는 true입니다.&lt;br /&gt;count.value가 1이면 isEven.value는 false입니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;count.value = 1;

print(isEven.value); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 UI에서 자주 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 퀴즈 앱이라면 다음과 같은 상태를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final selectedAnswerIndex = signal&amp;lt;int?&amp;gt;(null);
final correctAnswerIndex = signal(2);

final isCorrect = computed(() {
  return selectedAnswerIndex.value == correctAnswerIndex.value;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 선택한 답이 바뀌면 isCorrect도 자동으로 바뀝니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Flutter에서는 SignalBuilder를 사용한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 signal 값을 UI에 연결하려면 signals_flutter 쪽 기능을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;signals_flutter 패키지는 Flutter 위젯 트리에 signal을 연결해 국소적인 UI 업데이트를 가능하게 한다고 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 패턴은 SignalBuilder입니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';

final count = signal(0);
final isEven = computed(() =&amp;gt; count.value.isEven);

class CounterCard extends StatelessWidget {
  const CounterCard({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SignalBuilder(
          builder: (context) {
            return Text(
              'Count: ${count.value} (${isEven.value ? &quot;even&quot; : &quot;odd&quot;})',
            );
          },
        ),
        ElevatedButton(
          onPressed: () {
            count.value++;
          },
          child: const Text('Increment'),
        ),
      ],
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 중요한 부분은 SignalBuilder 안입니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SignalBuilder(
  builder: (context) {
    return Text('Count: ${count.value}');
  },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SignalBuilder의 builder 안에서 count.value를 읽고 있습니다.&lt;br /&gt;그러면 이 builder는 count에 의존하는 UI가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 count.value++가 실행되면, SignalBuilder 안쪽만 다시 build됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 Column 전체가 다시 build되는 것이 아닙니다.&lt;br /&gt;형제 위젯이 다시 build되는 것도 아닙니다.&lt;br /&gt;count.value를 읽은 builder만 다시 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Signals가 말하는 &amp;ldquo;surgical rebuild&amp;rdquo;, 즉 수술하듯 정밀한 rebuild의 핵심입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Watch는 이제 피하는 것이 좋다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals 관련 예전 글이나 튜토리얼을 보면 Watch 위젯을 사용하는 예제가 나올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 이런 식의 코드가 사용되었습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Watch((context) {
  return Text('${count.value}');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 문서에서는 Watch가 deprecated로 표시되어 있고, SignalBuilder 사용을 권장합니다. 공식 문서에는 Watch가 deprecated된 위젯이며, 더 일관적인 API와 self-contained reactivity를 위해 SignalBuilder를 사용하라고 안내되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 .watch(context) extension도 signals v7에서 deprecated되었고, Flutter build lifecycle과 관련된 예상치 못한 side effect 및 불필요한 rebuild 가능성 때문에 SignalBuilder, SignalWidget, SignalStatefulWidget 같은 전용 reactive component로 마이그레이션하라고 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 새 프로젝트에서 Signals를 사용한다면 Watch보다는 다음 패턴을 기준으로 잡는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SignalBuilder(
  builder: (context) {
    return Text('${count.value}');
  },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. setState와 비교하면 무엇이 다른가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState는 Flutter에서 가장 기본적인 상태관리 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;setState(() {
  count++;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하고, 별도 패키지도 필요 없습니다. 작은 위젯 내부 상태라면 여전히 좋은 선택입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 setState는 해당 State 객체 아래의 위젯 트리를 다시 build합니다. 따라서 상태를 어디에 두느냐가 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 구조를 생각해 봅시다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class ProductPageState extends State&amp;lt;ProductPage&amp;gt; {
  String keyword = '';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SearchInput(
          onChanged: (value) {
            setState(() {
              keyword = value;
            });
          },
        ),
        ProductGrid(),
        RecommendedBanner(),
      ],
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 keyword가 바뀔 때마다 ProductPageState의 build가 다시 실행됩니다.&lt;br /&gt;구조에 따라 ProductGrid, RecommendedBanner도 영향을 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals를 사용하면 검색어를 별도 signal로 분리하고, 검색어를 실제로 읽는 위젯만 반응하게 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;final keyword = signal('');

class SearchInput extends StatelessWidget {
  const SearchInput({super.key});

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: (value) {
        keyword.value = value;
      },
    );
  }
}

class SearchKeywordPreview extends StatelessWidget {
  const SearchKeywordPreview({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        return Text('검색어: ${keyword.value}');
      },
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 keyword.value를 읽는 SignalBuilder만 반응합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. BLoC와 비교하면 무엇이 다른가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BLoC는 이벤트와 상태 전이를 명확하게 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 검색 기능을 BLoC로 만들면 대략 다음과 같은 구조가 됩니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;abstract class SearchEvent {}

class SearchKeywordChanged extends SearchEvent {
  final String keyword;

  SearchKeywordChanged(this.keyword);
}

class SearchState {
  final String keyword;

  SearchState({required this.keyword});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Bloc 클래스에서 이벤트를 받아 상태를 변경합니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class SearchBloc extends Bloc&amp;lt;SearchEvent, SearchState&amp;gt; {
  SearchBloc() : super(SearchState(keyword: '')) {
    on&amp;lt;SearchKeywordChanged&amp;gt;((event, emit) {
      emit(SearchState(keyword: event.keyword));
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서는 BlocBuilder를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;BlocBuilder&amp;lt;SearchBloc, SearchState&amp;gt;(
  builder: (context, state) {
    return Text('검색어: ${state.keyword}');
  },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BLoC의 장점은 명확합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직이 복잡하고, 이벤트 흐름을 명시적으로 관리해야 하며, 테스트가 중요한 프로젝트에서는 좋은 구조를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점도 있습니다.&lt;br /&gt;간단한 상태 하나를 관리하기 위해 이벤트 클래스, 상태 클래스, bloc 클래스, builder를 모두 만들어야 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 훨씬 가볍습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;final keyword = signal('');
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 UI에서 읽습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;SignalBuilder(
  builder: (context) {
    return Text('검색어: ${keyword.value}');
  },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 작은 앱이나 UI 중심 앱에서 크게 느껴집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 BLoC가 제공하는 명시적인 이벤트 기록, 상태 전이 구조, 팀 단위 컨벤션은 Signals가 자동으로 제공하는 영역은 아닙니다. 복잡한 도메인 로직이 많은 앱이라면 BLoC가 여전히 더 적합할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Riverpod과 비교하면 무엇이 다른가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod은 현재 Flutter 실무에서 매우 강력한 선택지입니다.&lt;br /&gt;비동기 상태, 의존성 주입, 캐싱, 테스트, provider override 등에서 장점이 큽니다. pub.dev의 Riverpod 설명도 비동기 코드 작업을 쉽게 해주는 reactive caching and data-binding framework라고 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod에서는 상태를 provider 단위로 관리합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;final keywordProvider = StateProvider&amp;lt;String&amp;gt;((ref) =&amp;gt; '');
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서는 다음처럼 읽습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final keyword = ref.watch(keywordProvider);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 특정 값만 구독하고 싶다면 select를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;final name = ref.watch(userProvider.select((user) =&amp;gt; user.name));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod도 충분히 세밀한 최적화가 가능합니다.&lt;br /&gt;하지만 기본 사고방식은 provider 중심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals의 사고방식은 조금 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod이 &amp;ldquo;이 provider가 바뀌었으니 구독자가 반응한다&amp;rdquo;에 가깝다면, Signals는 &amp;ldquo;이 signal 값을 실제로 읽은 표현식만 반응한다&amp;rdquo;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Signals는 provider 단위보다 더 작은 값 단위의 반응성을 지향합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 비교하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분RiverpodSignals&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;중심 단위&lt;/td&gt;
&lt;td&gt;Provider&lt;/td&gt;
&lt;td&gt;Signal value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;강점&lt;/td&gt;
&lt;td&gt;async, DI, override, 테스트, 구조화&lt;/td&gt;
&lt;td&gt;값 단위 반응성, 적은 boilerplate, 국소 rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 영역&lt;/td&gt;
&lt;td&gt;앱 전역 상태, 서버 상태, 인증, API 데이터&lt;/td&gt;
&lt;td&gt;UI 상태, 입력값, 필터, 선택 상태, 자주 바뀌는 작은 상태&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;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Signals가 Riverpod을 완전히 대체한다고 보기는 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실적인 접근은 &lt;b&gt;둘을 함께 쓰는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 추천 아키텍처: Riverpod + Signals 조합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 다음 조합이 꽤 현실적입니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Riverpod:
- 로그인 사용자
- API 호출 결과
- 서버 상태
- 앱 설정
- 인증 상태
- repository/service 주입
- async loading/error/data 상태

Signals:
- 검색어
- 탭 선택
- 필터 선택
- 좋아요 버튼 UI
- 카드 선택 상태
- 폼 입력값
- 퀴즈 선택 답안
- 로컬 UI 상태
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 한국어 퀴즈 앱을 만든다고 가정해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 전체 데이터는 Riverpod으로 관리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;final quizRepositoryProvider = Provider&amp;lt;QuizRepository&amp;gt;((ref) {
  return QuizRepository();
});

final quizListProvider = FutureProvider&amp;lt;List&amp;lt;Quiz&amp;gt;&amp;gt;((ref) async {
  final repository = ref.watch(quizRepositoryProvider);
  return repository.fetchQuizzes();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 사용자가 현재 고른 답안, 현재 문제 index, 정답 여부 같은 화면 내부 상태는 Signals로 관리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final currentQuestionIndex = signal(0);
final selectedAnswerIndex = signal&amp;lt;int?&amp;gt;(null);

final isAnswered = computed(() {
  return selectedAnswerIndex.value != null;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서는 다음처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class QuizAnswerStatus extends StatelessWidget {
  const QuizAnswerStatus({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        if (!isAnswered.value) {
          return const Text('답을 선택해 주세요.');
        }

        return Text('선택한 답: ${selectedAnswerIndex.value}');
      },
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 서버 데이터와 앱 구조는 Riverpod이 담당하고, 자주 바뀌는 UI 상태는 Signals가 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 역할이 충돌하지 않습니다.&lt;br /&gt;오히려 서로 보완할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 예제: 검색 화면을 Signals로 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 조금 더 현실적인 예제를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상품 검색 화면이 있다고 가정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 검색어를 입력한다.&lt;/li&gt;
&lt;li&gt;검색어에 따라 상품 목록이 필터링된다.&lt;/li&gt;
&lt;li&gt;검색어가 비어 있으면 전체 상품을 보여준다.&lt;/li&gt;
&lt;li&gt;검색어 미리보기 영역만 검색어 변경에 반응한다.&lt;/li&gt;
&lt;li&gt;필터링된 목록 영역만 필터 결과에 반응한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 상태를 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;import 'package:signals/signals.dart';

class Product {
  final String name;
  final int price;

  Product({
    required this.name,
    required this.price,
  });
}

final searchKeyword = signal('');

final products = signal&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;([
  Product(name: 'Korean Grammar Book', price: 18000),
  Product(name: 'TOPIK Vocabulary Cards', price: 12000),
  Product(name: 'Hangul Writing Notebook', price: 8000),
  Product(name: 'Korean Listening Practice', price: 15000),
]);

final filteredProducts = computed(() {
  final keyword = searchKeyword.value.trim().toLowerCase();

  if (keyword.isEmpty) {
    return products.value;
  }

  return products.value.where((product) {
    return product.name.toLowerCase().contains(keyword);
  }).toList();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 UI를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:signals/signals_flutter.dart';

class ProductSearchPage extends StatelessWidget {
  const ProductSearchPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('상품 검색'),
      ),
      body: Column(
        children: const [
          SearchBox(),
          SearchKeywordLabel(),
          Expanded(
            child: ProductList(),
          ),
        ],
      ),
    );
  }
}

class SearchBox extends StatelessWidget {
  const SearchBox({super.key});

  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: const InputDecoration(
        hintText: '검색어를 입력하세요',
      ),
      onChanged: (value) {
        searchKeyword.value = value;
      },
    );
  }
}

class SearchKeywordLabel extends StatelessWidget {
  const SearchKeywordLabel({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        final keyword = searchKeyword.value;

        if (keyword.isEmpty) {
          return const Text('전체 상품을 보고 있습니다.');
        }

        return Text('&quot;$keyword&quot; 검색 중');
      },
    );
  }
}

class ProductList extends StatelessWidget {
  const ProductList({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        final items = filteredProducts.value;

        if (items.isEmpty) {
          return const Center(
            child: Text('검색 결과가 없습니다.'),
          );
        }

        return ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            final product = items[index];

            return ListTile(
              title: Text(product.name),
              subtitle: Text('${product.price}원'),
            );
          },
        );
      },
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 SearchBox 자체는 signal 값을 읽지 않습니다.&lt;br /&gt;따라서 검색어가 바뀐다고 SearchBox가 다시 build될 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SearchKeywordLabel은 searchKeyword.value를 읽습니다.&lt;br /&gt;따라서 검색어가 바뀌면 이 영역이 반응합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ProductList는 filteredProducts.value를 읽습니다.&lt;br /&gt;따라서 검색어 변화로 필터링 결과가 바뀌면 목록 영역이 반응합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변화가 발생했을 때, 전체 페이지가 아니라 필요한 UI 조각만 반응하는 구조를 만들 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 예제: 퀴즈 앱에서 Signals 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 교육 앱 예제를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 학습 앱에서 퀴즈를 푸는 화면을 만든다고 가정해 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;class QuizQuestion {
  final String question;
  final List&amp;lt;String&amp;gt; options;
  final int correctIndex;

  QuizQuestion({
    required this.question,
    required this.options,
    required this.correctIndex,
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태는 다음처럼 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;final currentQuestion = signal(
  QuizQuestion(
    question: '&quot;밥을 먹어요&quot;의 과거형은?',
    options: [
      '밥을 먹어요',
      '밥을 먹었어요',
      '밥을 먹을 거예요',
      '밥을 먹고 있어요',
    ],
    correctIndex: 1,
  ),
);

final selectedIndex = signal&amp;lt;int?&amp;gt;(null);

final hasSelected = computed(() {
  return selectedIndex.value != null;
});

final isCorrect = computed(() {
  final selected = selectedIndex.value;

  if (selected == null) {
    return false;
  }

  return selected == currentQuestion.value.correctIndex;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 다음처럼 구성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;class QuizPage extends StatelessWidget {
  const QuizPage({super.key});

  @override
  Widget build(BuildContext context) {
    final question = currentQuestion.value;

    return Scaffold(
      appBar: AppBar(
        title: const Text('한국어 퀴즈'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            question.question,
            style: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          ...List.generate(question.options.length, (index) {
            return AnswerButton(
              index: index,
              text: question.options[index],
            );
          }),
          const SizedBox(height: 24),
          const QuizFeedback(),
        ],
      ),
    );
  }
}

class AnswerButton extends StatelessWidget {
  final int index;
  final String text;

  const AnswerButton({
    super.key,
    required this.index,
    required this.text,
  });

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        final selected = selectedIndex.value;
        final isSelected = selected == index;

        return ListTile(
          title: Text(text),
          leading: Icon(
            isSelected
                ? Icons.radio_button_checked
                : Icons.radio_button_unchecked,
          ),
          onTap: () {
            selectedIndex.value = index;
          },
        );
      },
    );
  }
}

class QuizFeedback extends StatelessWidget {
  const QuizFeedback({super.key});

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      builder: (context) {
        if (!hasSelected.value) {
          return const Text('정답을 선택해 주세요.');
        }

        if (isCorrect.value) {
          return const Text('정답입니다!');
        }

        return const Text('틀렸습니다. 다시 확인해 보세요.');
      },
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 selectedIndex가 바뀌면 선택 상태와 피드백 영역만 반응합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀴즈 앱, 학습 앱, 리워드 앱처럼 사용자의 작은 상호작용이 자주 발생하는 UI에서는 Signals가 꽤 잘 맞습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. Signals를 쓸 때 주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 매력적이지만, 모든 상황의 정답은 아닙니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 전역 상태를 아무 곳에나 만들면 관리가 어려워질 수 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 편의를 위해 top-level 변수로 signal을 만들었습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final count = signal(0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실무 앱에서 모든 상태를 top-level로 만들면 상태의 생명주기와 소유권이 불분명해질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 예제에서는 괜찮지만, 실제 프로젝트에서는 화면 단위, feature 단위, service 단위로 상태 위치를 명확히 정하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 서버 상태 관리는 별도 전략이 필요하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 값 단위 반응성에 강합니다.&lt;br /&gt;하지만 API 호출, 캐싱, 에러 처리, retry, pagination, 인증 상태 관리까지 모두 Signals만으로 처리하려고 하면 오히려 구조가 애매해질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 상태는 Riverpod, Repository 패턴, Query 계열 패턴과 함께 설계하는 편이 더 안정적일 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 팀 컨벤션이 아직 넓게 정착된 것은 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod이나 BLoC는 이미 많은 Flutter 팀에서 사용하고 있고, 예제와 레퍼런스도 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Signals는 아직 상대적으로 작은 생태계입니다.&lt;br /&gt;공식 문서와 패키지는 존재하지만, 대규모 실무 패턴은 Riverpod/BLoC만큼 많이 축적되었다고 보기는 어렵습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 기존 프로젝트를 무리하게 마이그레이션할 필요는 없다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 Riverpod이나 BLoC로 안정적으로 운영 중인 앱이라면 Signals로 갈아타는 것이 항상 이득은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 rebuild 문제가 실제로 있고, 그 문제가 사용자 경험이나 성능에 영향을 주고 있으며, 기존 구조로 해결하기 어렵다면 일부 화면부터 도입하는 것이 현실적입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. Signals가 특히 잘 맞는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals는 다음과 같은 상황에서 유용합니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- 새 Flutter 프로젝트를 시작한다.
- 혼자 또는 작은 팀으로 빠르게 개발한다.
- BLoC의 boilerplate가 부담스럽다.
- Riverpod까지 쓰기에는 화면 상태가 단순하다.
- 검색, 필터, 탭, 토글 같은 UI 상태가 많다.
- 특정 위젯만 정밀하게 rebuild하고 싶다.
- SolidJS, Preact Signals 같은 웹의 반응형 모델에 익숙하다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 다음 상황에서는 신중해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- 이미 Riverpod/BLoC 기반의 큰 앱이 안정적으로 운영 중이다.
- 팀원들이 기존 상태관리 방식에 익숙하다.
- 엄격한 아키텍처 컨벤션이 필요하다.
- 서버 상태, 인증, 캐싱, 에러 처리가 핵심이다.
- 외부 레퍼런스와 커뮤니티 사례가 매우 중요하다.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 그래서 Signals를 써야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면, Signals는 Flutter 상태관리의 &amp;ldquo;완전한 대체재&amp;rdquo;라기보다 &lt;b&gt;정밀한 UI 반응성을 위한 좋은 도구&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState보다 세밀하고,&lt;br /&gt;BLoC보다 가볍고,&lt;br /&gt;Riverpod보다 값 단위 반응성에 직접적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Riverpod이나 BLoC가 해결하던 모든 문제를 Signals가 그대로 대체한다고 보기는 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 현실적인 결론은 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 전체 구조와 서버 상태는 Riverpod이나 기존 아키텍처로 관리하고,&lt;br /&gt;화면 내부의 자주 바뀌는 UI 상태는 Signals로 관리하는 방식이 실용적이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 검색창, 필터, 선택 상태, 퀴즈 답안, 토글, 카운터, 좋아요 버튼처럼 작은 상호작용이 자주 발생하는 영역에서는 Signals가 매우 깔끔한 선택지가 될 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 상태관리의 역사는 계속 &amp;ldquo;어디까지 rebuild할 것인가&amp;rdquo;의 문제와 함께 발전해 왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState는 단순하지만 넓게 rebuild될 수 있습니다.&lt;br /&gt;BLoC는 명확하지만 코드가 많습니다.&lt;br /&gt;Riverpod은 강력하지만 provider 설계가 필요합니다.&lt;br /&gt;Signals는 값이 실제로 읽힌 지점을 기준으로 반응합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signals의 장점은 바로 이 지점에 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 바뀌었을 때, 그 상태를 실제로 읽은 UI만 다시 그린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단순한 아이디어가 Flutter UI를 더 가볍고 직관적으로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 아직 생태계와 실무 패턴은 Riverpod이나 BLoC만큼 넓지 않습니다. 따라서 기존 프로젝트를 전면 전환하기보다는, 새 프로젝트나 특정 화면에서 먼저 실험해 보는 접근이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 앱에서 불필요한 rebuild를 줄이고 싶다면, Signals는 한 번쯤 진지하게 살펴볼 만한 선택지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혁명이라기보다는, 정확한 문제를 꽤 우아하게 해결하는 실용적인 도구에 가깝습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1783039966033&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - rodydavis/signals.dart: Reactive programming made simple for Dart and Flutter&quot; data-og-description=&quot;Reactive programming made simple for Dart and Flutter - rodydavis/signals.dart&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/rodydavis/signals.dart&quot; data-og-url=&quot;https://github.com/rodydavis/signals.dart&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/U5Nal/dJMb8TCkYnN/0ggzi4YZaJAzXw5b4KGRU1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_133_1034_202,https://scrap.kakaocdn.net/dn/bf0NrW/dJMb8WeKU4m/QcGsQm5VHUuktMk6XkM4Fk/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_133_1034_202&quot;&gt;&lt;a href=&quot;https://github.com/rodydavis/signals.dart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/rodydavis/signals.dart&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/U5Nal/dJMb8TCkYnN/0ggzi4YZaJAzXw5b4KGRU1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_133_1034_202,https://scrap.kakaocdn.net/dn/bf0NrW/dJMb8WeKU4m/QcGsQm5VHUuktMk6XkM4Fk/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_133_1034_202');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - rodydavis/signals.dart: Reactive programming made simple for Dart and Flutter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Reactive programming made simple for Dart and Flutter - rodydavis/signals.dart&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스터디/Flutter</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1786</guid>
      <comments>https://javaexpert.tistory.com/1786#entry1786comment</comments>
      <pubDate>Fri, 3 Jul 2026 09:57:30 +0900</pubDate>
    </item>
    <item>
      <title>Flue : 하네스 AI 에이전트</title>
      <link>https://javaexpert.tistory.com/1785</link>
      <description>&lt;h1&gt;Flue란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flue&lt;/b&gt;는 TypeScript로 AI 에이전트를 만들기 위한 프레임워크입니다.&lt;br /&gt;Claude Code 같은 코딩 에이전트가 사용하는 &lt;b&gt;하네스 기반 아키텍처&lt;/b&gt;를 채택하고 있으며, 다양한 AI 모델을 에이전트처럼 사용할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue의 기반에는 &lt;b&gt;Pi&lt;/b&gt;라는 &amp;ldquo;minimal terminal coding harness&amp;rdquo;가 사용됩니다. Pi는 터미널에서 코딩 에이전트를 실행하는 데 필요한 최소 기능을 제공하는 하네스이고, Flue는 이를 바탕으로 더 고급 에이전트 구축과 관리를 가능하게 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Flue가 제공하는 주요 구성요소&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue는 AI 에이전트 개발에 필요한 기능을 통합적으로 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성요소설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;에이전트&lt;/td&gt;
&lt;td&gt;컨텍스트를 유지하며 자율적으로 작업하는 AI 모델&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;워크플로우&lt;/td&gt;
&lt;td&gt;작업을 단계별로 구조화한 프로세스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;instructions&lt;/td&gt;
&lt;td&gt;에이전트의 행동 규칙, 시스템 프롬프트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;필요할 때만 로드되는 전문 지식 또는 Runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서브에이전트&lt;/td&gt;
&lt;td&gt;메인 에이전트가 작업을 위임하는 전문 에이전트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP 서버&lt;/td&gt;
&lt;td&gt;외부 서비스와 연결하기 위한 Model Context Protocol 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;채널&lt;/td&gt;
&lt;td&gt;Slack, Discord, Teams 등과 연결하는 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;핵심 특징&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Durable Execution, 즉 중단 후 재개 가능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 작업은 오래 걸릴 수 있고 중간에 에러가 날 수 있습니다.&lt;br /&gt;Flue는 세션 기록과 컨텍스트를 스토리지에 저장해서 작업을 중간부터 재개할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Cloudflare Workers에서 실행할 경우, 세션 정보와 컨텍스트를 &lt;b&gt;Durable Object&lt;/b&gt;에 저장할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Flue 프로젝트 생성 방식&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 설정 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;mkdir flue-agent-example
cd flue-agent-example
npm init -y
npm install @flue/runtime@latest
npm install @flue/cli@latest --save-dev
npx flue init --target node
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 flue.config.ts가 생성됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { defineConfig } from '@flue/cli/config';

export default defineConfig({
  target: 'node',
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수에는 AI 모델 API 키를 넣습니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;OPENAI_API_KEY=your_openai_api_key
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;기본 에이전트 예시&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue에서는 에이전트를 src/agents 디렉터리에 배치합니다.&lt;br /&gt;파일 이름이 에이전트 이름으로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { defineAgent, type AgentRouteHandler } from '@flue/runtime';

export const description = &quot;最初のエージェントです。ユーザーの質問に博多弁で答えます。&quot;;

export const route: AgentRouteHandler = async (_c, next) =&amp;gt; next();

export default defineAgent(() =&amp;gt; ({
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions: &quot;あなたは優秀なアシスタントです。博多弁でユーザーの質問に答えてください。&quot;,
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행:&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;npx flue run my-first-agent --input '{ &quot;message&quot;: &quot;こんにちは&quot; }'
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;SRE 에이전트 예시&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 핵심 예시는 &lt;b&gt;SRE 장애 대응 에이전트&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRE 에이전트는 다음과 같은 일을 목표로 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시스템 상태 확인&lt;/li&gt;
&lt;li&gt;로그와 메트릭 조사&lt;/li&gt;
&lt;li&gt;원인 가설 수립&lt;/li&gt;
&lt;li&gt;복구 액션 제안&lt;/li&gt;
&lt;li&gt;변경 작업 전 사람에게 승인 요청&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 읽기 작업과 쓰기 작업을 분리하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기 작업은 에이전트가 적극적으로 수행해도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;로그 확인
메트릭 확인
배포 이력 확인
서비스 상태 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 쓰기 작업은 반드시 사람의 승인이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;롤백
배포
스케일 조정
설정 변경
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 실제 운영 환경에서 매우 중요합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;instructions를 Markdown으로 분리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 instructions를 코드 안에 직접 작성했지만, 실제 에이전트에서는 시스템 프롬프트가 길어지기 때문에 Markdown 파일로 분리하는 방식을 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import instructions from './sre-agent.md' with { type: 'markdown' };

export default defineAgent(() =&amp;gt; ({
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions,
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 프롬프트를 별도 문서처럼 관리할 수 있어 유지보수에 유리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;툴 정의&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRE 에이전트가 로그나 서비스 상태를 확인하려면 툴이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 get-logs 툴은 특정 서비스의 최근 로그를 가져옵니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;export const getLogsTool = defineTool({
  name: &quot;get-logs&quot;,
  description: &quot;지정한 서비스의 최근 로그를 가져온다.&quot;,
  input: v.object({
    service: v.string(),
    level: v.optional(v.picklist([&quot;error&quot;, &quot;warn&quot;, &quot;info&quot;])),
    limit: v.optional(v.number()),
  }),
  async run({ input }) {
    const { service, level, limit = 20 } = input;
    const entries = queryLogs(service, level, limit);
    return { service, count: entries.length, entries };
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;툴은 에이전트의 tools 배열에 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default defineAgent(() =&amp;gt; ({
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions,
  tools: [getLogsTool, getServiceStatusTool],
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Runbook을 스킬로 정의&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 장애 대응에서는 Runbook, 즉 장애 대응 절차서가 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 Runbook을 항상 시스템 프롬프트에 넣으면 컨텍스트가 커지고, 관련 없는 절차가 섞여 에이전트가 잘못된 판단을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Flue는 Runbook을 &lt;b&gt;스킬&lt;/b&gt;로 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 평소에는 description만 로드되고, 에이전트가 필요하다고 판단할 때 전체 내용을 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 &lt;b&gt;progressive disclosure&lt;/b&gt;라고 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import paymentsRunbook from &quot;../skills/payments-runbook/SKILL.md&quot; with { type: &quot;skill&quot; };

export default defineAgent(() =&amp;gt; ({
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions,
  tools: [getLogsTool, getServiceStatusTool],
  skills: [paymentsRunbook],
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, payments-api 장애가 발생했을 때만 payments-runbook을 로드해서 대응 절차를 참고하게 할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;서브에이전트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 조사는 로그, 메트릭, 배포 이력, DB 상태 등 여러 정보를 다뤄야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 정보를 메인 에이전트가 직접 읽으면 컨텍스트가 오염되고 길어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Flue에서는 &lt;b&gt;서브에이전트&lt;/b&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브에이전트역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;log-analyst&lt;/td&gt;
&lt;td&gt;로그만 조사하고 요약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics-analyst&lt;/td&gt;
&lt;td&gt;메트릭만 조사하고 요약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메인 SRE 에이전트&lt;/td&gt;
&lt;td&gt;요약을 종합해 최종 판단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 전문 서브에이전트 예시:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;export const logAnalyst = defineAgentProfile({
  name: &quot;log-analyst&quot;,
  description: &quot;로그 전문 조사 서브에이전트입니다.&quot;,
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions: &quot;get-logs를 사용해 로그를 읽고 이상 징후만 요약하세요.&quot;,
  tools: [getLogsTool],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 에이전트에 연결:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export default defineAgent(() =&amp;gt; ({
  model: &quot;openai/gpt-5.4-nano&quot;,
  instructions,
  tools: [getServiceStatusTool],
  skills: [paymentsRunbook],
  subagents: [logAnalyst],
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 메인 에이전트는 원본 로그 전체를 직접 읽지 않고, 요약만 받아 최종 판단을 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;워크플로우&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue의 워크플로우는 작업을 여러 단계로 나눈 구조화된 처리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 글에서는 &lt;b&gt;장애 보고서, 즉 포스트모템 생성 워크플로우&lt;/b&gt;를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;입력받은 인시던트 정보를 incident.md로 저장&lt;/li&gt;
&lt;li&gt;에이전트가 로그, 상태, 배포 이력을 조사&lt;/li&gt;
&lt;li&gt;postmortem.md 생성&lt;/li&gt;
&lt;li&gt;생성된 문서를 다시 읽고 구조화된 요약 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크플로우는 defineWorkflow로 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;export default defineWorkflow({
  agent: defineAgent(() =&amp;gt; ({
    model: &quot;openai/gpt-5.4-nano&quot;,
    sandbox: local(),
    cwd: &quot;postmortems&quot;,
    tools: [getServiceStatusTool, getLogsTool, getDeployHistoryTool],
  })),

  input: v.object({
    service: v.string(),
    incident: v.string(),
  }),

  output: v.object({
    markdown: v.string(),
    rootCause: v.string(),
    actionItems: v.array(v.string()),
  }),

  async run({ harness, input }) {
    await harness.fs.writeFile(
      &quot;incident.md&quot;,
      `# Incident: ${input.service}\n\n${input.incident}\n`,
    );

    const session = await harness.session();

    await session.prompt(
      &quot;incident.md를 읽고 조사 후 postmortem.md를 작성하세요.&quot;
    );

    const markdown = await harness.fs.readFile(&quot;postmortem.md&quot;);

    const summary = await session.prompt(
      &quot;postmortem.md에서 근본 원인과 재발 방지 항목을 추출하세요.&quot;,
      {
        result: v.object({
          rootCause: v.string(),
          actionItems: v.array(v.string()),
        }),
      },
    );

    return {
      markdown,
      rootCause: summary.data.rootCause,
      actionItems: summary.data.actionItems,
    };
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 local() 샌드박스를 사용하면 호스트 파일시스템에 접근하므로 보안상 신뢰할 수 있는 환경에서만 사용해야 한다는 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Slack 연동&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue는 Slack, Discord, Teams 같은 외부 채팅 서비스와 연결할 수 있는 &lt;b&gt;채널&lt;/b&gt; 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 연동 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Slack App 생성&lt;/li&gt;
&lt;li&gt;Bot Token, Signing Secret 발급&lt;/li&gt;
&lt;li&gt;환경변수 설정&lt;/li&gt;
&lt;li&gt;@flue/slack, @slack/web-api 설치&lt;/li&gt;
&lt;li&gt;src/channels/slack.ts 생성&lt;/li&gt;
&lt;li&gt;Slack 이벤트를 받아 SRE 에이전트에 dispatch&lt;/li&gt;
&lt;li&gt;에이전트 응답을 Slack thread에 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 채널 예시:&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;import { dispatch } from '@flue/runtime';
import { createSlackChannel } from '@flue/slack';
import sreAgent from '../agents/sre-agent.ts';

export const channel = createSlackChannel({
  signingSecret: process.env.SLACK_SIGNING_SECRET!,
  async events({ payload }) {
    if (payload.type !== 'event_callback') return;
    if (payload.event.type !== 'app_mention') return;

    const event = payload.event;

    await dispatch(sreAgent, {
      id: channel.conversationKey({
        teamId: payload.team_id,
        channelId: event.channel,
        threadTs: event.thread_ts ?? event.ts,
      }),
      input: {
        type: 'slack.app_mention',
        eventId: payload.event_id,
        text: event.text,
      },
    });
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Slack에서 다음처럼 부르면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@sre-agent payments-api에서 5xx가 증가하고 있으니 Runbook 기준으로 조사해줘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 에이전트가 Runbook, 로그, 메트릭 등을 참고해 조사 결과를 Slack 스레드에 답변합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;전체 구조 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue 기반 SRE 에이전트 구조는 다음과 같이 이해하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Slack / CLI / HTTP
        &amp;darr;
   Flue Channel
        &amp;darr;
   Main SRE Agent
        &amp;darr;
 ┌───────────────┬────────────────┬───────────────┐
 │ Tools         │ Skills          │ Subagents      │
 │ 상태 확인     │ Runbook         │ log-analyst    │
 │ 배포 이력     │ 장애 절차서      │ metrics-analyst │
 └───────────────┴────────────────┴───────────────┘
        &amp;darr;
 원인 가설 + 권장 조치 + 승인 필요 여부 보고
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;중요한 포인트&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Flue의 강점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue는 단순한 LLM 호출 라이브러리가 아니라, 실제 운영 가능한 AI 에이전트 구조를 만들기 위한 프레임워크에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 기능이 중요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;에이전트 실행 세션 관리&lt;/li&gt;
&lt;li&gt;툴 호출&lt;/li&gt;
&lt;li&gt;스킬 기반 지식 로딩&lt;/li&gt;
&lt;li&gt;서브에이전트 위임&lt;/li&gt;
&lt;li&gt;워크플로우 구조화&lt;/li&gt;
&lt;li&gt;Slack 같은 외부 채널 연동&lt;/li&gt;
&lt;li&gt;샌드박스 실행 환경&lt;/li&gt;
&lt;li&gt;장애 대응 같은 장기 작업 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;기존 AI SDK와의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 이 글 기준으로 보면 Flue는 Vercel AI SDK 같은 &amp;ldquo;LLM 호출 및 UI 스트리밍 중심 SDK&amp;rdquo;보다는, &lt;b&gt;에이전트 실행 환경 전체를 구성하는 프레임워크&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분Vercel AI SDKFlue&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주 목적&lt;/td&gt;
&lt;td&gt;AI 앱 UI, 스트리밍, tool calling&lt;/td&gt;
&lt;td&gt;에이전트 실행 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;언어&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에이전트 구조&lt;/td&gt;
&lt;td&gt;직접 설계 필요&lt;/td&gt;
&lt;td&gt;Agent, Skill, Workflow, Channel 구조 제공&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;Slack 연동&lt;/td&gt;
&lt;td&gt;직접 구현&lt;/td&gt;
&lt;td&gt;Channel 개념 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runbook/Skill&lt;/td&gt;
&lt;td&gt;직접 구현&lt;/td&gt;
&lt;td&gt;Skill 구조 제공&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;개발자 관점에서의 의미&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue는 다음과 같은 프로젝트에 적합해 보입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 사내 운영 자동화 에이전트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;서버 장애 조사
로그 분석
배포 이력 확인
장애 보고서 생성
Slack 알림
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 고객지원 에이전트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;고객 문의 분석
내부 문서 검색
티켓 분류
답변 초안 작성
담당자 연결
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 개발 자동화 에이전트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;코드 리뷰
PR 요약
테스트 실패 분석
CI 로그 분석
릴리즈 노트 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 문서 생성 워크플로우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;회의록 &amp;rarr; 요약 &amp;rarr; 액션아이템 추출
장애 기록 &amp;rarr; 포스트모템 작성
기획서 &amp;rarr; PRD 변환
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;이 글의 핵심 결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flue는 TypeScript 기반의 AI 에이전트 프레임워크입니다.&lt;br /&gt;에이전트, 툴, 스킬, 서브에이전트, 워크플로우, 채널, 샌드박스, 옵저버빌리티를 하나의 구조 안에서 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 SRE 장애 대응 예제에서 Flue의 장점이 잘 드러납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 다음입니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;메인 에이전트는 판단을 담당하고,
툴은 외부 정보를 가져오고,
스킬은 필요한 전문 지식을 제공하고,
서브에이전트는 세부 조사를 담당하고,
워크플로우는 결과물을 구조화하며,
채널은 Slack 같은 외부 서비스와 연결한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Flue는 단순히 &amp;ldquo;AI에게 질문하는 앱&amp;rdquo;을 만드는 도구가 아니라, &lt;b&gt;실제 업무 프로세스 안에서 움직이는 AI 에이전트 시스템을 만들기 위한 프레임워크&lt;/b&gt;라고 볼 수 있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1782784643780&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - withastro/flue: The sandbox agent framework.&quot; data-og-description=&quot;The sandbox agent framework. Contribute to withastro/flue development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/withastro/flue&quot; data-og-url=&quot;https://github.com/withastro/flue&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYYvwC/dJMb8QMmUgE/3SY4lDeY5Yi47JgMhiYUQK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ilZaM/dJMb9jOxxaL/zXyMkkBNmyLfSAK1SPyvOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/withastro/flue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/withastro/flue&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYYvwC/dJMb8QMmUgE/3SY4lDeY5Yi47JgMhiYUQK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ilZaM/dJMb9jOxxaL/zXyMkkBNmyLfSAK1SPyvOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - withastro/flue: The sandbox agent framework.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The sandbox agent framework. Contribute to withastro/flue development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1785</guid>
      <comments>https://javaexpert.tistory.com/1785#entry1785comment</comments>
      <pubDate>Tue, 30 Jun 2026 10:57:58 +0900</pubDate>
    </item>
    <item>
      <title>AI 에이전트는 &amp;ldquo;모델&amp;rdquo;이 아니라 &amp;ldquo;운영 시스템&amp;rdquo;이다</title>
      <link>https://javaexpert.tistory.com/1784</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQWKkq/dJMcacQ8J4D/3OOqkHWNNruqeDMiKYcbS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQWKkq/dJMcacQ8J4D/3OOqkHWNNruqeDMiKYcbS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQWKkq/dJMcacQ8J4D/3OOqkHWNNruqeDMiKYcbS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQWKkq%2FdJMcacQ8J4D%2F3OOqkHWNNruqeDMiKYcbS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;941&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;agent-harness-loop-engineering-llmops-eval-tracing-rag%EB%A5%BC-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;Agent Harness, Loop Engineering, LLMOps, Eval, Tracing, RAG를 한 번에 이해하기&lt;/h2&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;4&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 업로드된 영어 자동 생성 자막을 바탕으로 핵심 내용을 한국어로 번역&amp;middot;재구성하고, 외부 공개용 브런치 글로 읽히도록 설명과 예제를 보강한 원고입니다. 원문은 AI 에이전트 시스템에서 자주 등장하는 Agent Harness, Loop Engineering, LLMOps, Eval, Tracing, RAG를 하나의 구조로 연결해 설명합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-line=&quot;6&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;1-%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0-%EC%9A%94%EC%A6%98-ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%85%BC%EC%9D%98%EA%B0%80-%EC%96%B4%EB%A0%A4%EC%9B%8C-%EB%B3%B4%EC%9D%B4%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;1. 들어가며: 요즘 AI 에이전트 논의가 어려워 보이는 이유&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;요즘 AI 에이전트 이야기를 따라가다 보면 낯선 단어가 계속 등장합니다. Agent Harness, Loop Engineering, LLMOps, Eval, Tracing, RAG, Memory System 같은 표현입니다. 처음 들으면 각각 독립된 고급 기술처럼 보이지만, 실제로는 하나의 질문으로 모을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;거대한 언어 모델을 내가 원하는 방향으로 안정적으로 일하게 하려면 무엇이 필요한가?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;LLM은 매우 강력합니다. 방대한 지식과 언어 능력을 갖고 있고, 코드도 작성하며, 문서를 요약하고, 사람처럼 대화할 수 있습니다. 하지만 LLM 자체는 우리가 원하는 업무 환경, 회사의 규칙, 고객 데이터, 이전 대화, 업무 종료 기준, 실패했을 때의 복구 방식까지 자동으로 알지 못합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;그래서 AI 에이전트 시스템을 만들 때 중요한 것은 단순히 &amp;ldquo;어떤 모델을 쓸 것인가&amp;rdquo;가 아닙니다. 더 중요한 질문은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;18&quot;&gt;에이전트가 어떤 정보를 기억하게 할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;19&quot;&gt;필요한 정보를 어디서 검색하게 할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;20&quot;&gt;어떤 도구를 호출하게 할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;21&quot;&gt;도구 호출을 몇 번까지 허용할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;22&quot;&gt;언제 작업을 멈추고 사용자에게 답하게 할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;23&quot;&gt;에이전트가 잘했는지, 못했는지 어떻게 평가할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;24&quot;&gt;실패했다면 무엇을 고쳐야 하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;이 질문들을 시스템적으로 묶은 개념이 바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Agent Harness&lt;/b&gt;입니다. 그리고 그 안에서 에이전트가 반복적으로 생각하고, 도구를 호출하고, 다시 판단하는 구조가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Loop Engineering&lt;/b&gt;입니다. 또한 이 전체 시스템을 관찰하고 평가하고 개선하는 운영 체계가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;LLMOps&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-line=&quot;28&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;2-%ED%95%B5%EC%8B%AC-%EC%9A%94%EC%95%BD-%EC%98%81%EC%83%81-%EB%82%B4%EC%9A%A9%EC%9D%98-%ED%81%B0-%ED%9D%90%EB%A6%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;2. 핵심 요약: 영상 내용의 큰 흐름&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;원문 자막의 핵심 메시지는 단순합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 에이전트는 LLM 하나로 완성되지 않는다. LLM을 원하는 방식으로 작동시키기 위한 기억, 도구, 반복 루프, 종료 조건, 관찰, 평가, 개선 시스템이 함께 있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;이 구조를 단계별로 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;38&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-line=&quot;38&quot;&gt;사용자가 질문하거나 업무를 요청한다.&lt;/li&gt;
&lt;li data-line=&quot;39&quot;&gt;현재 대화, 시스템 프롬프트, 필요한 메모리들이 작업 메모리로 들어간다.&lt;/li&gt;
&lt;li data-line=&quot;40&quot;&gt;LLM이 답변하거나, 필요한 경우 도구를 호출한다.&lt;/li&gt;
&lt;li data-line=&quot;41&quot;&gt;도구 호출 결과를 다시 보고 다음 행동을 결정한다.&lt;/li&gt;
&lt;li data-line=&quot;42&quot;&gt;작업이 충분히 완료되었다고 판단하면 루프를 종료한다.&lt;/li&gt;
&lt;li data-line=&quot;43&quot;&gt;최종 답변을 사용자에게 반환한다.&lt;/li&gt;
&lt;li data-line=&quot;44&quot;&gt;전체 실행 과정을 추적한다.&lt;/li&gt;
&lt;li data-line=&quot;45&quot;&gt;결과가 좋았는지 평가한다.&lt;/li&gt;
&lt;li data-line=&quot;46&quot;&gt;문제가 있으면 프롬프트, 모델 설정, 검색 방식, 도구, 루프 조건을 개선한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;즉, AI 에이전트 개발은 단순히 &amp;ldquo;프롬프트 잘 쓰기&amp;rdquo;가 아닙니다. 점점 더&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시스템 설계와 운영의 문제&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;hr data-line=&quot;50&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;3-ai-agent-run-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A4%ED%96%89-1%ED%9A%8C%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;52&quot; data-ke-size=&quot;size26&quot;&gt;3. AI Agent Run: 에이전트 실행 1회란 무엇인가&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 이해해야 할 단위는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Agent Run&lt;/b&gt;입니다. Agent Run은 사용자의 입력이 들어와서 에이전트의 응답이 나가기까지의 한 번의 실행을 의미합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 이렇게 묻는다고 해보겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;58&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;58&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;Sam Altman이 OpenAI에서 해임된 시점이 언제였지?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;이때 에이전트는 사용자의 질문, 현재 대화 기록, 시스템 프롬프트를 바탕으로 답변을 생성합니다. 단순한 챗봇이라면 이 정도로 끝날 수 있습니다. 그러나 실제 에이전트 시스템에서는 여기에 여러 종류의 기억과 도구가 붙습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;62&quot; data-ke-size=&quot;size16&quot;&gt;Agent Run을 단순화하면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;사용자 입력
  &amp;darr;
현재 대화 기록 + 시스템 프롬프트 + 관련 메모리
  &amp;darr;
LLM 판단
  &amp;darr;
필요하면 도구 호출
  &amp;darr;
결과를 다시 판단
  &amp;darr;
최종 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;78&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 &amp;ldquo;한 번의 Agent Run 안에서도 LLM 호출과 도구 호출이 여러 번 일어날 수 있다&amp;rdquo;는 것입니다. 사용자가 보는 것은 하나의 답변이지만, 내부에서는 검색, 계산, 데이터베이스 조회, CRM 확인, 결제 정보 확인 같은 여러 단계가 숨어 있을 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;80&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;4-working-memory-%ED%98%84%EC%9E%AC-%EC%8B%A4%ED%96%89%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9E%91%EC%97%85-%EB%A9%94%EB%AA%A8%EB%A6%AC&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;82&quot; data-ke-size=&quot;size26&quot;&gt;4. Working Memory: 현재 실행을 위한 작업 메모리&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;84&quot; data-ke-size=&quot;size16&quot;&gt;LLM에게 한 번에 전달되는 정보는 무한하지 않습니다. 모델에는 컨텍스트 윈도우가 있고, 그 안에 현재 질문, 최근 대화, 시스템 지시문, 관련 문서, 도구 결과 등이 들어갑니다. 이 묶음을 원문에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Working Memory&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Context RAM&lt;/b&gt;에 가깝게 설명합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;86&quot; data-ke-size=&quot;size16&quot;&gt;사람으로 비유하면 지금 책상 위에 올려놓고 보고 있는 자료입니다. 책장에 있는 모든 책을 동시에 펼쳐놓을 수는 없기 때문에, 지금 문제를 해결하는 데 필요한 자료만 꺼내 놓는 것입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;88&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 고객지원 AI 에이전트가 있다고 해보겠습니다. 사용자가 이렇게 묻습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;90&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;90&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;지난달 환불 요청했는데 아직 처리됐나요?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;92&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 답하려면 단순한 언어 능력만으로는 부족합니다. 에이전트는 다음 정보를 작업 메모리에 넣어야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;94&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;94&quot;&gt;현재 사용자의 고객 ID&lt;/li&gt;
&lt;li data-line=&quot;95&quot;&gt;최근 문의 내역&lt;/li&gt;
&lt;li data-line=&quot;96&quot;&gt;주문 번호&lt;/li&gt;
&lt;li data-line=&quot;97&quot;&gt;환불 요청 상태&lt;/li&gt;
&lt;li data-line=&quot;98&quot;&gt;회사의 환불 정책&lt;/li&gt;
&lt;li data-line=&quot;99&quot;&gt;결제 시스템에서 확인한 실제 환불 처리 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;101&quot; data-ke-size=&quot;size16&quot;&gt;이런 정보가 들어와야 에이전트는 그럴듯한 답이 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;업무적으로 쓸 수 있는 답&lt;/b&gt;을 할 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;103&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;5-%EC%84%B8-%EA%B0%80%EC%A7%80-%EB%A9%94%EB%AA%A8%EB%A6%AC-procedural-semantic-episodic&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;105&quot; data-ke-size=&quot;size26&quot;&gt;5. 세 가지 메모리: Procedural, Semantic, Episodic&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;107&quot; data-ke-size=&quot;size16&quot;&gt;원문은 AI 에이전트가 단기 대화만으로는 충분하지 않기 때문에 추가적인 메모리 시스템이 필요하다고 설명합니다. 특히 세 가지 메모리 구분이 중요합니다.&lt;/p&gt;
&lt;h3 id=&quot;5-1-procedural-memory-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%96%89%EB%8F%99%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;109&quot; data-ke-size=&quot;size23&quot;&gt;5-1. Procedural Memory: 어떻게 행동해야 하는가&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;111&quot; data-ke-size=&quot;size16&quot;&gt;Procedural Memory는 에이전트의 행동 방식과 절차를 담은 기억입니다. 쉽게 말하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;업무 매뉴얼&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;113&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같은 내용입니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;# 고객지원 에이전트 응대 규칙

1. 고객에게 먼저 공감 표현을 한다.
2. 주문번호가 없으면 주문번호를 요청한다.
3. 환불 가능 기간을 확인한다.
4. 정책상 환불이 불가능하면 대체 보상 옵션을 안내한다.
5. 결제 취소가 필요한 경우 Stripe Refund Tool을 호출한다.
6. 실제 환불 실행 전에는 사용자 확인을 받는다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;126&quot; data-ke-size=&quot;size16&quot;&gt;최근 AI 개발 생태계에서 말하는 &amp;ldquo;Skill&amp;rdquo;도 이 범주에 들어갈 수 있습니다. 특정 업무를 수행하기 위한 절차, 프롬프트, 체크리스트, 도구 사용 규칙이 하나의 파일이나 문서로 정리되고, 에이전트는 필요할 때 그 내용을 읽고 행동합니다.&lt;/p&gt;
&lt;h3 id=&quot;5-2-semantic-memory-%EC%98%A4%EB%9E%98-%EC%9C%A0%EC%A7%80%EB%90%98%EB%8A%94-%EC%82%AC%EC%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;128&quot; data-ke-size=&quot;size23&quot;&gt;5-2. Semantic Memory: 오래 유지되는 사실&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;Semantic Memory는 오래 유지되는 사실 정보입니다. 예를 들어 개인 비서 에이전트라면 다음과 같은 내용을 기억할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;user_name&quot;: &quot;Sean&quot;,
  &quot;role&quot;: &quot;early-stage startup founder&quot;,
  &quot;company_stage&quot;: &quot;pre-seed&quot;,
  &quot;preferred_tone&quot;: &quot;direct and strategic&quot;,
  &quot;current_goal&quot;: &quot;compare startup journey with Sam Altman's early career&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;142&quot; data-ke-size=&quot;size16&quot;&gt;이 정보는 현재 대화에만 존재하는 것이 아니라 여러 대화에 걸쳐 유지될 수 있습니다. 사용자가 매번 &amp;ldquo;나는 초기 스타트업 창업자이고, 이런 목표가 있어&amp;rdquo;라고 설명하지 않아도 에이전트가 개인화된 답변을 만들 수 있게 해줍니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;144&quot; data-ke-size=&quot;size16&quot;&gt;회사 업무 에이전트라면 Semantic Memory에는 다음과 같은 내용이 들어갈 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;146&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;146&quot;&gt;제품 가격 정책&lt;/li&gt;
&lt;li data-line=&quot;147&quot;&gt;환불 정책&lt;/li&gt;
&lt;li data-line=&quot;148&quot;&gt;배송 정책&lt;/li&gt;
&lt;li data-line=&quot;149&quot;&gt;조직도&lt;/li&gt;
&lt;li data-line=&quot;150&quot;&gt;브랜드 톤앤매너&lt;/li&gt;
&lt;li data-line=&quot;151&quot;&gt;고객 응대 기준&lt;/li&gt;
&lt;li data-line=&quot;152&quot;&gt;자주 발생하는 이슈와 해결 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-3-episodic-memory-%EA%B3%BC%EA%B1%B0-%EC%82%AC%EA%B1%B4%EA%B3%BC-%EB%8C%80%ED%99%94-%EA%B8%B0%EB%A1%9D&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;154&quot; data-ke-size=&quot;size23&quot;&gt;5-3. Episodic Memory: 과거 사건과 대화 기록&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;156&quot; data-ke-size=&quot;size16&quot;&gt;Episodic Memory는 시간 순서가 있는 과거 사건의 기록입니다. 쉽게 말해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;로그와 히스토리&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;158&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 고객지원 시스템에서는 다음처럼 저장될 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;[
  {
    &quot;timestamp&quot;: &quot;2026-06-01T10:22:00Z&quot;,
    &quot;customer_id&quot;: &quot;cus_1029&quot;,
    &quot;event_type&quot;: &quot;chat&quot;,
    &quot;summary&quot;: &quot;고객이 제품 불량으로 환불을 요청함&quot;,
    &quot;status&quot;: &quot;refund_requested&quot;
  },
  {
    &quot;timestamp&quot;: &quot;2026-06-03T14:12:00Z&quot;,
    &quot;customer_id&quot;: &quot;cus_1029&quot;,
    &quot;event_type&quot;: &quot;payment_check&quot;,
    &quot;summary&quot;: &quot;환불 처리 여부를 결제 시스템에서 확인했으나 미처리 상태였음&quot;,
    &quot;status&quot;: &quot;refund_pending&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;179&quot; data-ke-size=&quot;size16&quot;&gt;Episodic Memory는 &amp;ldquo;지난번에 무슨 일이 있었는가?&amp;rdquo;를 찾는 데 유용합니다. 개인 비서라면 &amp;ldquo;내가 마지막으로 이직 준비를 한 게 언제였지?&amp;rdquo; 같은 질문에 답할 수 있습니다. 기업 에이전트라면 &amp;ldquo;최근 2개월 동안 같은 제품 품질 문제로 불만을 제기한 고객이 누구였지?&amp;rdquo; 같은 질문에 답할 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;181&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;6-agent-harness-llm%EC%9D%B4%EB%9D%BC%EB%8A%94-%EB%A7%90%EC%9D%84-%EC%A0%9C%EC%96%B4%ED%95%98%EB%8A%94-%EC%9E%A5%EB%B9%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;183&quot; data-ke-size=&quot;size26&quot;&gt;6. Agent Harness: LLM이라는 말을 제어하는 장비&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;185&quot; data-ke-size=&quot;size16&quot;&gt;원문에서 가장 중요한 비유는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Harness&lt;/b&gt;입니다. Harness는 말을 탈 때 말의 움직임을 제어하기 위해 사용하는 장비입니다. LLM을 강력한 말이라고 보면, Agent Harness는 그 말을 원하는 방향으로 움직이게 하는 제어 장치입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;187&quot; data-ke-size=&quot;size16&quot;&gt;LLM은 강력하지만 확률적으로 다음 단어를 예측하는 시스템입니다. 따라서 아무 장치 없이 쓰면 방향이 흔들릴 수 있습니다. 어떤 때는 과도하게 추론하고, 어떤 때는 필요한 도구를 쓰지 않으며, 어떤 때는 멈춰야 할 시점에 멈추지 못합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;189&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Harness는 다음 구성 요소를 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 요소역할&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #24292e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-line=&quot;191&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-line=&quot;193&quot;&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;193&quot;&gt;
&lt;td&gt;System Prompt&lt;/td&gt;
&lt;td&gt;에이전트의 역할, 금지사항, 응답 방식 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;194&quot;&gt;
&lt;td&gt;Procedural Memory&lt;/td&gt;
&lt;td&gt;업무 절차와 스킬 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;195&quot;&gt;
&lt;td&gt;Semantic Memory&lt;/td&gt;
&lt;td&gt;오래 유지되는 사실 정보 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;196&quot;&gt;
&lt;td&gt;Episodic Memory&lt;/td&gt;
&lt;td&gt;과거 대화와 사건 로그 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;197&quot;&gt;
&lt;td&gt;Tool Registry&lt;/td&gt;
&lt;td&gt;사용할 수 있는 도구 목록과 호출 방식 정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;198&quot;&gt;
&lt;td&gt;Retrieval System&lt;/td&gt;
&lt;td&gt;필요한 기억과 문서를 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;199&quot;&gt;
&lt;td&gt;Loop Controller&lt;/td&gt;
&lt;td&gt;반복 실행과 종료 조건 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;200&quot;&gt;
&lt;td&gt;Guardrails&lt;/td&gt;
&lt;td&gt;위험 행동, 무한 루프, 권한 문제 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;201&quot;&gt;
&lt;td&gt;Tracing&lt;/td&gt;
&lt;td&gt;실행 과정 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;202&quot;&gt;
&lt;td&gt;Evaluation&lt;/td&gt;
&lt;td&gt;결과 품질 평가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;203&quot;&gt;
&lt;td&gt;LLMOps&lt;/td&gt;
&lt;td&gt;관찰, 진단, 개선, 배포의 운영 체계&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;205&quot; data-ke-size=&quot;size16&quot;&gt;이 표를 보면 에이전트 개발이 왜 단순한 챗봇 제작과 다른지 알 수 있습니다. 챗봇은 보통 &amp;ldquo;입력 &amp;rarr; 답변&amp;rdquo;에 가깝지만, 에이전트는 &amp;ldquo;입력 &amp;rarr; 기억 검색 &amp;rarr; 계획 &amp;rarr; 도구 호출 &amp;rarr; 결과 검토 &amp;rarr; 반복 &amp;rarr; 종료 &amp;rarr; 평가 &amp;rarr; 개선&amp;rdquo;으로 움직입니다.&lt;/p&gt;
&lt;hr data-line=&quot;207&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;7-memory-update-%EA%B8%B0%EC%96%B5%EC%9D%80-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%83%9D%EA%B8%B0%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;209&quot; data-ke-size=&quot;size26&quot;&gt;7. Memory Update: 기억은 자동으로 생기지 않는다&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;211&quot; data-ke-size=&quot;size16&quot;&gt;중요한 지점이 하나 있습니다. 메모리는 그냥 생기지 않습니다. 기억하려면 저장해야 하고, 저장하려면 업데이트 시스템이 있어야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;213&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 고객지원 에이전트가 매일 수천 건의 대화를 처리한다고 해보겠습니다. 모든 대화를 그대로 장기 기억에 넣으면 비용도 커지고 검색 품질도 떨어질 수 있습니다. 그래서 보통은 다음과 같은 구조를 씁니다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;대화/이벤트 발생
  &amp;darr;
원본 로그 저장
  &amp;darr;
일정 기준마다 요약 또는 추출
  &amp;darr;
중요 사실만 Semantic Memory로 승격
  &amp;darr;
필요할 때 검색해서 Working Memory에 삽입
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;227&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 2,000건의 고객 대화마다 요약 에이전트를 실행해 다음과 같은 사실을 추출할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;product&quot;: &quot;SmartBottle V2&quot;,
  &quot;issue&quot;: &quot;뚜껑 센서 불량&quot;,
  &quot;frequency&quot;: &quot;최근 2,000건 중 184건&quot;,
  &quot;customer_sentiment&quot;: &quot;강한 불만 증가&quot;,
  &quot;recommended_action&quot;: &quot;FAQ 업데이트, 교환 정책 강화, 생산 배치 확인&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;239&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 원본 대화 전체를 매번 검색하지 않고, 압축된 사실을 별도로 관리하면 에이전트가 더 빠르고 일관되게 답할 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;241&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;8-rag-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B8%B0%EC%96%B5%EC%9D%84-%EA%B2%80%EC%83%89%ED%95%B4-%EB%8B%B5%EB%B3%80%EC%97%90-%EB%B6%99%EC%9D%B4%EB%8A%94-%EB%B0%A9%EC%8B%9D&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;243&quot; data-ke-size=&quot;size26&quot;&gt;8. RAG: 필요한 기억을 검색해 답변에 붙이는 방식&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;245&quot; data-ke-size=&quot;size16&quot;&gt;RAG는 Retrieval-Augmented Generation의 약자입니다. 한국어로 옮기면 &amp;ldquo;검색 증강 생성&amp;rdquo; 정도입니다. 모델이 자체 지식만으로 답하는 것이 아니라, 외부 데이터베이스나 문서에서 관련 정보를 찾아 컨텍스트에 넣고 답하게 하는 방식입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;원문에서는 Semantic Memory와 Episodic Memory의 검색 방식이 다를 수 있다고 설명합니다.&lt;/p&gt;
&lt;h3 id=&quot;8-1-semantic-memory-%EA%B2%80%EC%83%89&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;249&quot; data-ke-size=&quot;size23&quot;&gt;8-1. Semantic Memory 검색&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;251&quot; data-ke-size=&quot;size16&quot;&gt;Semantic Memory는 정책, 제품 정보, 사용자 프로필처럼 비교적 안정적인 사실입니다. 이런 정보는 문서 검색이나 벡터 검색으로 찾기 좋습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;253&quot; data-ke-size=&quot;size16&quot;&gt;예시 질문:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;255&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;255&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리 회사의 환불 정책에 따르면 개봉한 제품도 환불 가능한가?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;257&quot; data-ke-size=&quot;size16&quot;&gt;검색 대상:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;259&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;259&quot;&gt;환불 정책 문서&lt;/li&gt;
&lt;li data-line=&quot;260&quot;&gt;고객지원 매뉴얼&lt;/li&gt;
&lt;li data-line=&quot;261&quot;&gt;예외 처리 규정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;8-2-episodic-memory-%EA%B2%80%EC%83%89&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;263&quot; data-ke-size=&quot;size23&quot;&gt;8-2. Episodic Memory 검색&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;265&quot; data-ke-size=&quot;size16&quot;&gt;Episodic Memory는 시간 순서가 있는 이벤트입니다. 이 경우 단순 벡터 검색만으로는 부족할 수 있습니다. SQL 조회와 의미 검색을 함께 써야 할 때가 많습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;267&quot; data-ke-size=&quot;size16&quot;&gt;예시 질문 1:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;269&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;269&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 고객과 최근 10번 나눈 대화를 보여줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;271&quot; data-ke-size=&quot;size16&quot;&gt;이 경우는 시간 기준 SQL 쿼리로 충분할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;SELECT *
FROM customer_events
WHERE customer_id = 'cus_1029'
ORDER BY timestamp DESC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;281&quot; data-ke-size=&quot;size16&quot;&gt;예시 질문 2:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;283&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;최근 2개월 동안 제품 품질 불만을 말했는데 상담원이 해결하지 못한 대화를 찾아줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;285&quot; data-ke-size=&quot;size16&quot;&gt;이 경우는 시간 필터, 고객 불만 분류, 해결 여부, 의미 검색이 함께 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;SELECT *
FROM customer_events
WHERE timestamp &amp;gt;= NOW() - INTERVAL '2 months'
  AND status != 'resolved';
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;294&quot; data-ke-size=&quot;size16&quot;&gt;그다음 해당 결과에서 &amp;ldquo;품질 불만&amp;rdquo;, &amp;ldquo;고장&amp;rdquo;, &amp;ldquo;불량&amp;rdquo;, &amp;ldquo;환불 요청&amp;rdquo;과 의미적으로 가까운 대화를 벡터 검색으로 골라낼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;296&quot; data-ke-size=&quot;size16&quot;&gt;요약하면 RAG는 단순히 &amp;ldquo;문서 검색&amp;rdquo;이 아닙니다. 에이전트가 지금 판단하는 데 필요한 정보를 외부 기억에서 가져오는 전체 메커니즘입니다.&lt;/p&gt;
&lt;hr data-line=&quot;298&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;9-tool-calling-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EA%B0%80-%EC%8B%A4%EC%A0%9C-%EC%97%85%EB%AC%B4%EB%A5%BC-%ED%95%98%EA%B2%8C-%ED%95%98%EB%8A%94-%EC%9E%A5%EC%B9%98&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;300&quot; data-ke-size=&quot;size26&quot;&gt;9. Tool Calling: 에이전트가 실제 업무를 하게 하는 장치&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트의 강력함은 답변만 하는 것이 아니라 실제 도구를 호출할 수 있다는 점에서 나옵니다. 예를 들어 다음과 같은 도구가 있을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;304&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;304&quot;&gt;캘린더 일정 생성 도구&lt;/li&gt;
&lt;li data-line=&quot;305&quot;&gt;CRM 고객 정보 조회 도구&lt;/li&gt;
&lt;li data-line=&quot;306&quot;&gt;Stripe 또는 Alipay 결제 조회 도구&lt;/li&gt;
&lt;li data-line=&quot;307&quot;&gt;환불 실행 도구&lt;/li&gt;
&lt;li data-line=&quot;308&quot;&gt;이메일 발송 도구&lt;/li&gt;
&lt;li data-line=&quot;309&quot;&gt;내부 데이터베이스 조회 도구&lt;/li&gt;
&lt;li data-line=&quot;310&quot;&gt;문서 검색 도구&lt;/li&gt;
&lt;li data-line=&quot;311&quot;&gt;코드 실행 도구&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;313&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 이렇게 요청한다고 해보겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;315&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;315&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리 제품에 불만을 제기한 고객들을 찾아보고, 환불이 안 된 사람들에게 후속 조치를 제안해줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;317&quot; data-ke-size=&quot;size16&quot;&gt;에이전트는 단순 답변으로 끝낼 수 없습니다. 내부적으로는 다음 단계를 거쳐야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;319&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-line=&quot;319&quot;&gt;CRM에서 최근 고객 불만 내역을 조회한다.&lt;/li&gt;
&lt;li data-line=&quot;320&quot;&gt;불만 유형을 분류한다.&lt;/li&gt;
&lt;li data-line=&quot;321&quot;&gt;환불 요청이 있었는지 확인한다.&lt;/li&gt;
&lt;li data-line=&quot;322&quot;&gt;결제 시스템에서 환불 처리 여부를 확인한다.&lt;/li&gt;
&lt;li data-line=&quot;323&quot;&gt;환불 미처리 고객을 추린다.&lt;/li&gt;
&lt;li data-line=&quot;324&quot;&gt;후속 이메일 초안을 작성한다.&lt;/li&gt;
&lt;li data-line=&quot;325&quot;&gt;실제 환불 실행이 필요한 경우 사용자 승인을 요청한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;327&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 에이전트는 여러 도구를 반복적으로 호출합니다. 바로 여기서 Loop Engineering이 중요해집니다.&lt;/p&gt;
&lt;hr data-line=&quot;329&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;10-loop-engineering-%EC%96%B8%EC%A0%9C-%EB%B0%98%EB%B3%B5%ED%95%98%EA%B3%A0-%EC%96%B8%EC%A0%9C-%EB%A9%88%EC%B6%9C-%EA%B2%83%EC%9D%B8%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;331&quot; data-ke-size=&quot;size26&quot;&gt;10. Loop Engineering: 언제 반복하고 언제 멈출 것인가&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;333&quot; data-ke-size=&quot;size16&quot;&gt;Loop Engineering은 최근 AI 에이전트 논의에서 자주 등장하는 표현입니다. 원문에서는 Loop가 Harness의 일부라고 설명합니다. 이유는 간단합니다. 루프 역시 LLM을 원하는 방식으로 제어하기 위한 장치이기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;335&quot; data-ke-size=&quot;size16&quot;&gt;에이전트 루프는 대략 다음 구조입니다.&lt;/p&gt;
&lt;pre class=&quot;&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;목표 확인
  &amp;darr;
다음 행동 결정
  &amp;darr;
도구 호출 또는 답변 생성
  &amp;darr;
결과 관찰
  &amp;darr;
목표 달성 여부 판단
  &amp;darr;
미완료면 반복
  &amp;darr;
완료면 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;353&quot; data-ke-size=&quot;size16&quot;&gt;문제는 LLM에게 너무 많은 자유를 주면 루프가 길어지거나 멈추지 않을 수 있다는 점입니다. 반대로 너무 빨리 멈추면 업무가 미완성 상태로 끝납니다. 따라서 좋은 에이전트 시스템은 &amp;ldquo;충분히 완료됨&amp;rdquo;의 기준을 명확히 가져야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;10-1-%EA%B3%A0%EA%B0%9D-%ED%99%98%EB%B6%88-%EC%98%88%EC%A0%9C&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;355&quot; data-ke-size=&quot;size23&quot;&gt;10-1. 고객 환불 예제&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;357&quot; data-ke-size=&quot;size16&quot;&gt;사용자 요청:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;359&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;359&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;지난 2개월 동안 제품 불만을 제기한 고객을 찾아줘. 환불 요청이 있었는데 처리되지 않은 사람이 있으면 후속 조치를 제안해줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;에이전트 루프:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;1단계: CRM에서 최근 2개월 불만 고객 조회
2단계: 불만 유형이 제품 품질인 고객 필터링
3단계: 환불 요청 여부 확인
4단계: 결제 시스템에서 환불 완료 여부 확인
5단계: 미처리 고객 목록 생성
6단계: 후속 조치 초안 작성
7단계: 실제 환불 실행 전 사용자 승인 요청
8단계: 최종 요약 답변
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;374&quot; data-ke-size=&quot;size16&quot;&gt;여기서 종료 조건은 다음처럼 정의할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;stop_when&quot;: [
    &quot;complaint_customers_identified&quot;,
    &quot;refund_status_checked&quot;,
    &quot;unresolved_cases_summarized&quot;,
    &quot;follow_up_actions_drafted&quot;,
    &quot;dangerous_actions_waiting_for_user_confirmation&quot;
  ],
  &quot;max_tool_calls&quot;: 12,
  &quot;requires_user_confirmation_before&quot;: [
    &quot;issuing_refund&quot;,
    &quot;sending_email&quot;,
    &quot;changing_customer_status&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;394&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 종료 조건과 권한 조건을 명확히 정의해야 에이전트가 안전하게 움직입니다.&lt;/p&gt;
&lt;h3 id=&quot;10-2-%EC%BD%94%EB%94%A9-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%98%88%EC%A0%9C&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;396&quot; data-ke-size=&quot;size23&quot;&gt;10-2. 코딩 에이전트 예제&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;코딩 에이전트를 사용할 때도 루프가 중요합니다. 예를 들어 코딩 에이전트가 파일을 수정하다가 권한 승인이 필요한 상황에서 멈췄다고 해보겠습니다. 사용자는 유튜브를 보다가 30분 뒤에 돌아왔고, 에이전트는 25분 전부터 권한 승인 창에서 멈춰 있었습니다. 이는 루프 설계가 부족한 사례입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;400&quot; data-ke-size=&quot;size16&quot;&gt;개선된 루프는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;권한 요청 발생
  &amp;darr;
사용자 입력 필요 상태로 전환
  &amp;darr;
데스크톱 알림 발송
  &amp;darr;
사용자 승인 대기
  &amp;darr;
승인 후 작업 재개
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 &amp;ldquo;에이전트가 멈춘 사실을 사용자에게 알려야 한다&amp;rdquo;는 점입니다. 루프는 단순 반복이 아니라 상태 관리입니다.&lt;/p&gt;
&lt;hr data-line=&quot;416&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;11-guardrails-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EC%97%90%EA%B2%8C-%EB%B8%8C%EB%A0%88%EC%9D%B4%ED%81%AC%EB%A5%BC-%EB%8B%AC%EC%95%84%EC%95%BC-%ED%95%9C%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;418&quot; data-ke-size=&quot;size26&quot;&gt;11. Guardrails: 에이전트에게 브레이크를 달아야 한다&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;Loop Engineering에서 Guardrails는 필수입니다. Guardrails는 에이전트가 위험하거나 비효율적인 행동을 하지 않도록 제한하는 규칙입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;422&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 규칙이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;guardrails:
  max_iterations: 8
  max_tool_calls: 15
  max_runtime_seconds: 60
  require_confirmation:
    - send_email
    - issue_refund
    - delete_record
    - update_payment_status
  stop_if:
    - tool_returns_same_result_three_times
    - missing_required_customer_id
    - user_goal_is_ambiguous
    - cost_estimate_exceeds_limit
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;441&quot; data-ke-size=&quot;size16&quot;&gt;Guardrails가 없으면 에이전트는 다음과 같은 문제를 일으킬 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;443&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;443&quot;&gt;같은 도구를 반복 호출한다.&lt;/li&gt;
&lt;li data-line=&quot;444&quot;&gt;필요 없는 검색을 계속한다.&lt;/li&gt;
&lt;li data-line=&quot;445&quot;&gt;비용이 과도하게 발생한다.&lt;/li&gt;
&lt;li data-line=&quot;446&quot;&gt;사용자의 승인 없이 이메일을 보내거나 환불을 실행한다.&lt;/li&gt;
&lt;li data-line=&quot;447&quot;&gt;실패한 작업을 실패로 인정하지 않고 계속 시도한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;449&quot; data-ke-size=&quot;size16&quot;&gt;따라서 에이전트 설계에서 &amp;ldquo;무엇을 할 수 있는가&amp;rdquo;만큼 중요한 질문은 &amp;ldquo;무엇을 하면 안 되는가&amp;rdquo;입니다.&lt;/p&gt;
&lt;hr data-line=&quot;451&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;12-llmops-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-%EC%9A%B4%EC%98%81%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;453&quot; data-ke-size=&quot;size26&quot;&gt;12. LLMOps: 에이전트 시스템을 운영하는 방법&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;455&quot; data-ke-size=&quot;size16&quot;&gt;LLMOps는 Large Language Model Operations의 약자입니다. 전통적인 소프트웨어 운영에서 로그, 모니터링, 테스트, 배포, 장애 대응이 중요하듯이, LLM 기반 시스템에서도 운영 체계가 필요합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;457&quot; data-ke-size=&quot;size16&quot;&gt;원문에서는 LLMOps가 필요한 이유를 이렇게 설명합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;459&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에이전트가 실제로 잘 작동하는지 알 수 없기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;461&quot; data-ke-size=&quot;size16&quot;&gt;단순히 사용자가 답변을 받았다고 해서 좋은 실행이라고 볼 수 없습니다. 다음 질문을 봐야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;463&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;463&quot;&gt;요청한 작업이 실제로 완료되었는가?&lt;/li&gt;
&lt;li data-line=&quot;464&quot;&gt;필요한 도구가 호출되었는가?&lt;/li&gt;
&lt;li data-line=&quot;465&quot;&gt;잘못된 도구를 호출하지 않았는가?&lt;/li&gt;
&lt;li data-line=&quot;466&quot;&gt;응답 시간이 너무 길지 않았는가?&lt;/li&gt;
&lt;li data-line=&quot;467&quot;&gt;토큰 비용이 과도하지 않았는가?&lt;/li&gt;
&lt;li data-line=&quot;468&quot;&gt;검색된 문서가 적절했는가?&lt;/li&gt;
&lt;li data-line=&quot;469&quot;&gt;최종 답변이 사실에 근거했는가?&lt;/li&gt;
&lt;li data-line=&quot;470&quot;&gt;위험한 행동 전에 사용자 확인을 받았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;472&quot; data-ke-size=&quot;size16&quot;&gt;이런 질문에 답하기 위해 필요한 것이 Tracing과 Eval입니다.&lt;/p&gt;
&lt;hr data-line=&quot;474&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;13-tracing-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%A4%ED%96%89%EC%9D%84-%EB%82%98%EB%AC%B4%EC%B2%98%EB%9F%BC-%EA%B8%B0%EB%A1%9D%ED%95%98%EA%B8%B0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;476&quot; data-ke-size=&quot;size26&quot;&gt;13. Tracing: 에이전트 실행을 나무처럼 기록하기&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;478&quot; data-ke-size=&quot;size16&quot;&gt;Tracing은 한 번의 Agent Run 안에서 무슨 일이 일어났는지 기록하는 것입니다. 원문에서는 이를 &amp;ldquo;tree of events&amp;rdquo;로 설명합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;480&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 환불 상태를 물었을 때 내부 실행 기록은 다음과 같을 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;run_id&quot;: &quot;run_20260630_001&quot;,
  &quot;user_input&quot;: &quot;지난달 환불 요청했는데 처리됐나요?&quot;,
  &quot;events&quot;: [
    {
      &quot;type&quot;: &quot;memory_retrieval&quot;,
      &quot;source&quot;: &quot;episodic_memory&quot;,
      &quot;query&quot;: &quot;user refund request last month&quot;,
      &quot;latency_ms&quot;: 120
    },
    {
      &quot;type&quot;: &quot;tool_call&quot;,
      &quot;tool&quot;: &quot;crm_lookup&quot;,
      &quot;args&quot;: {&quot;customer_id&quot;: &quot;cus_1029&quot;},
      &quot;latency_ms&quot;: 340
    },
    {
      &quot;type&quot;: &quot;tool_call&quot;,
      &quot;tool&quot;: &quot;payment_refund_status&quot;,
      &quot;args&quot;: {&quot;order_id&quot;: &quot;ord_8891&quot;},
      &quot;latency_ms&quot;: 520
    },
    {
      &quot;type&quot;: &quot;llm_response&quot;,
      &quot;model&quot;: &quot;agent-model&quot;,
      &quot;tokens_input&quot;: 3200,
      &quot;tokens_output&quot;: 420,
      &quot;latency_ms&quot;: 1800
    }
  ],
  &quot;final_status&quot;: &quot;completed&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;517&quot; data-ke-size=&quot;size16&quot;&gt;Tracing을 하면 다음을 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;519&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;519&quot;&gt;어떤 질문이 들어왔는가?&lt;/li&gt;
&lt;li data-line=&quot;520&quot;&gt;어떤 메모리를 검색했는가?&lt;/li&gt;
&lt;li data-line=&quot;521&quot;&gt;어떤 도구를 몇 번 호출했는가?&lt;/li&gt;
&lt;li data-line=&quot;522&quot;&gt;각 단계의 응답 시간은 얼마였는가?&lt;/li&gt;
&lt;li data-line=&quot;523&quot;&gt;토큰은 얼마나 사용했는가?&lt;/li&gt;
&lt;li data-line=&quot;524&quot;&gt;어느 단계에서 실패했는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;526&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 관찰 도구로는 LangFuse, LangSmith 같은 도구가 언급됩니다. 중요한 것은 특정 도구명이 아니라, 에이전트 실행을 추적 가능한 구조로 남겨야 한다는 원칙입니다.&lt;/p&gt;
&lt;hr data-line=&quot;528&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;14-eval-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EA%B0%80-%EC%9E%98%ED%96%88%EB%8A%94%EC%A7%80-%ED%8F%89%EA%B0%80%ED%95%98%EA%B8%B0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;530&quot; data-ke-size=&quot;size26&quot;&gt;14. Eval: 에이전트가 잘했는지 평가하기&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;Eval은 Evaluation의 줄임말입니다. 에이전트의 결과가 좋은지 평가하는 시스템입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;534&quot; data-ke-size=&quot;size16&quot;&gt;평가는 크게 두 방향으로 나눌 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;14-1-%ED%92%88%EC%A7%88-%ED%8F%89%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;536&quot; data-ke-size=&quot;size23&quot;&gt;14-1. 품질 평가&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;538&quot; data-ke-size=&quot;size16&quot;&gt;품질 평가는 최종 답변이 사용자 요구를 만족했는지 보는 것입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;540&quot; data-ke-size=&quot;size16&quot;&gt;예시 평가 기준:&lt;/p&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;criteria&quot;: {
    &quot;task_completion&quot;: &quot;요청한 작업을 완료했는가?&quot;,
    &quot;groundedness&quot;: &quot;답변이 검색된 근거에 기반하는가?&quot;,
    &quot;tool_correctness&quot;: &quot;적절한 도구를 사용했는가?&quot;,
    &quot;safety&quot;: &quot;위험 작업 전에 확인을 받았는가?&quot;,
    &quot;clarity&quot;: &quot;사용자가 이해하기 쉽게 설명했는가?&quot;
  },
  &quot;score_scale&quot;: &quot;1 to 5&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;LLM을 평가자로 사용할 수도 있습니다. 예를 들어 평가용 프롬프트는 다음처럼 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;너는 고객지원 AI 에이전트의 실행 결과를 평가하는 심사자다.
다음 사용자 요청, 도구 호출 기록, 최종 답변을 보고 1~5점으로 평가하라.

평가 항목:
1. 요청 완료 여부
2. 근거 기반성
3. 불필요한 도구 호출 여부
4. 정책 준수 여부
5. 사용자에게 필요한 다음 행동 제시 여부

각 항목에 점수와 짧은 이유를 작성하라.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;14-2-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B1%B4%EA%B0%95%EB%8F%84-%ED%8F%89%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;571&quot; data-ke-size=&quot;size23&quot;&gt;14-2. 시스템 건강도 평가&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;시스템 건강도 평가는 성능과 비용을 보는 것입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;575&quot; data-ke-size=&quot;size16&quot;&gt;예시 지표:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표의미&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #24292e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-line=&quot;577&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-line=&quot;579&quot;&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;579&quot;&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;전체 응답 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;580&quot;&gt;
&lt;td&gt;Tool Call Count&lt;/td&gt;
&lt;td&gt;도구 호출 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;581&quot;&gt;
&lt;td&gt;Token Usage&lt;/td&gt;
&lt;td&gt;입력&amp;middot;출력 토큰 사용량&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;582&quot;&gt;
&lt;td&gt;Retrieval Count&lt;/td&gt;
&lt;td&gt;검색 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;583&quot;&gt;
&lt;td&gt;Error Rate&lt;/td&gt;
&lt;td&gt;도구 실패율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;584&quot;&gt;
&lt;td&gt;Loop Iterations&lt;/td&gt;
&lt;td&gt;반복 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;585&quot;&gt;
&lt;td&gt;User Confirmation Rate&lt;/td&gt;
&lt;td&gt;사용자 확인이 필요한 작업 비율&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;587&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 간단한 질문에 매번 거대한 메모리 검색을 수행한다면 응답이 느려지고 비용이 증가합니다. 이 경우 LLMOps 관점에서는 &amp;ldquo;모든 질문에 RAG를 쓰는 것이 맞는가?&amp;rdquo;를 다시 봐야 합니다.&lt;/p&gt;
&lt;hr data-line=&quot;589&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;15-diagnosis-%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%83%9D%EA%B2%BC%EC%9D%84-%EB%95%8C-%EC%96%B4%EB%94%94%EB%A5%BC-%EA%B3%A0%EC%B9%A0-%EA%B2%83%EC%9D%B8%EA%B0%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;591&quot; data-ke-size=&quot;size26&quot;&gt;15. Diagnosis: 문제가 생겼을 때 어디를 고칠 것인가&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;593&quot; data-ke-size=&quot;size16&quot;&gt;Tracing과 Eval을 하면 다음 단계는 Diagnosis입니다. 즉, 어디서 문제가 생겼는지 진단하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 일정 예약 에이전트가 있다고 해보겠습니다. 사용자가 &amp;ldquo;내일 오후 3시에 미팅 잡아줘&amp;rdquo;라고 했는데 미팅이 생성되지 않았습니다. 가능한 원인은 여러 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;597&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;597&quot;&gt;캘린더 도구가 호출되지 않았다.&lt;/li&gt;
&lt;li data-line=&quot;598&quot;&gt;도구는 호출됐지만 필요한 참석자 이메일이 없었다.&lt;/li&gt;
&lt;li data-line=&quot;599&quot;&gt;시간대 변환이 잘못됐다.&lt;/li&gt;
&lt;li data-line=&quot;600&quot;&gt;에이전트가 사용자 확인을 기다리다가 멈췄다.&lt;/li&gt;
&lt;li data-line=&quot;601&quot;&gt;시스템 프롬프트에서 일정 생성 권한이 제한돼 있었다.&lt;/li&gt;
&lt;li data-line=&quot;602&quot;&gt;도구 스키마가 변경되었는데 프롬프트가 업데이트되지 않았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;604&quot; data-ke-size=&quot;size16&quot;&gt;이때 Tracing이 없다면 &amp;ldquo;왜 안 됐는지&amp;rdquo; 알기 어렵습니다. 하지만 실행 기록이 있으면 어떤 단계에서 실패했는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;606&quot; data-ke-size=&quot;size16&quot;&gt;문제 유형별 수정 방향은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제수정 방향&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #24292e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-line=&quot;608&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-line=&quot;610&quot;&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;610&quot;&gt;
&lt;td&gt;도구를 호출하지 않음&lt;/td&gt;
&lt;td&gt;시스템 프롬프트 또는 계획 프롬프트 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;611&quot;&gt;
&lt;td&gt;잘못된 도구 호출&lt;/td&gt;
&lt;td&gt;Tool description과 스키마 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;612&quot;&gt;
&lt;td&gt;검색 결과 부정확&lt;/td&gt;
&lt;td&gt;임베딩, chunking, reranking, 필터 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;613&quot;&gt;
&lt;td&gt;응답 지연&lt;/td&gt;
&lt;td&gt;불필요한 검색 제거, 캐싱, 모델 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;614&quot;&gt;
&lt;td&gt;토큰 비용 과다&lt;/td&gt;
&lt;td&gt;메모리 요약, 컨텍스트 축소, 저렴한 모델 분기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;615&quot;&gt;
&lt;td&gt;루프 미종료&lt;/td&gt;
&lt;td&gt;max iterations, stop condition 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;616&quot;&gt;
&lt;td&gt;위험 작업 자동 실행&lt;/td&gt;
&lt;td&gt;confirmation guardrail 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-line=&quot;618&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;16-llmops-loop-%EA%B0%9C%EC%84%A0%EC%9D%80-%EB%8B%A4%EC%8B%9C-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EB%A1%9C-%EB%8F%8C%EC%95%84%EA%B0%84%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;620&quot; data-ke-size=&quot;size26&quot;&gt;16. LLMOps Loop: 개선은 다시 에이전트로 돌아간다&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;LLMOps의 핵심은 관찰에서 끝나지 않는다는 점입니다. 평가와 진단 결과는 다시 에이전트 시스템으로 들어가야 합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;Agent Run 실행
  &amp;darr;
Trace 수집
  &amp;darr;
Eval 평가
  &amp;darr;
Diagnosis 진단
  &amp;darr;
Prompt / Model / Retrieval / Tool / Loop 수정
  &amp;darr;
새 버전 배포
  &amp;darr;
다시 Agent Run 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;640&quot; data-ke-size=&quot;size16&quot;&gt;이 구조가 반복되면 에이전트는 점점 더 나은 시스템이 됩니다. 여기서 중요한 것은 &amp;ldquo;모델이 스스로 좋아진다&amp;rdquo;는 뜻이 아닙니다. 운영자가 관찰 가능한 데이터를 바탕으로 시스템 구성 요소를 개선한다는 뜻입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;642&quot; data-ke-size=&quot;size16&quot;&gt;개선 대상은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;644&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;644&quot;&gt;System Prompt&lt;/li&gt;
&lt;li data-line=&quot;645&quot;&gt;Tool Description&lt;/li&gt;
&lt;li data-line=&quot;646&quot;&gt;Retrieval Query&lt;/li&gt;
&lt;li data-line=&quot;647&quot;&gt;Memory Update Rule&lt;/li&gt;
&lt;li data-line=&quot;648&quot;&gt;Summarizer Agent&lt;/li&gt;
&lt;li data-line=&quot;649&quot;&gt;Model Selection&lt;/li&gt;
&lt;li data-line=&quot;650&quot;&gt;Temperature 등 모델 파라미터&lt;/li&gt;
&lt;li data-line=&quot;651&quot;&gt;Loop Stop Condition&lt;/li&gt;
&lt;li data-line=&quot;652&quot;&gt;Guardrails&lt;/li&gt;
&lt;li data-line=&quot;653&quot;&gt;Eval Rubric&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;655&quot; data-ke-size=&quot;size16&quot;&gt;즉, AI 에이전트의 품질은 모델 하나가 아니라 전체 운영 루프의 품질에 의해 결정됩니다.&lt;/p&gt;
&lt;hr data-line=&quot;657&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;17-%EA%B0%84%EB%8B%A8%ED%95%9C-%EA%B5%AC%ED%98%84-%EC%98%88%EC%A0%9C-%EA%B3%A0%EA%B0%9D%EC%A7%80%EC%9B%90-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;659&quot; data-ke-size=&quot;size26&quot;&gt;17. 간단한 구현 예제: 고객지원 에이전트 아키텍처&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;661&quot; data-ke-size=&quot;size16&quot;&gt;아래는 실제 구현을 상상할 수 있는 단순한 의사코드입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;class CustomerSupportAgent:
    def run(self, user_input, user_id):
        trace = Trace(run_id=create_run_id())

        # 1. 현재 사용자와 관련된 메모리 검색
        semantic_context = retrieve_semantic_memory(user_input)
        episodic_context = retrieve_recent_customer_events(user_id, user_input)

        trace.add(&quot;memory_retrieval&quot;, {
            &quot;semantic_count&quot;: len(semantic_context),
            &quot;episodic_count&quot;: len(episodic_context)
        })

        # 2. 작업 메모리 구성
        working_memory = build_context(
            system_prompt=CUSTOMER_SUPPORT_SYSTEM_PROMPT,
            user_input=user_input,
            semantic_context=semantic_context,
            episodic_context=episodic_context,
            tools=TOOL_DESCRIPTIONS
        )

        # 3. 루프 실행
        for step in range(MAX_ITERATIONS):
            action = llm_decide_next_action(working_memory)
            trace.add(&quot;llm_decision&quot;, action)

            if action.type == &quot;final_answer&quot;:
                trace.add(&quot;final_answer&quot;, action.content)
                return action.content

            if action.type == &quot;tool_call&quot;:
                if requires_confirmation(action.tool):
                    return ask_user_confirmation(action)

                result = call_tool(action.tool, action.args)
                trace.add(&quot;tool_call&quot;, {
                    &quot;tool&quot;: action.tool,
                    &quot;args&quot;: action.args,
                    &quot;result_summary&quot;: summarize(result)
                })
                working_memory.add_tool_result(result)

        # 4. 루프 초과 시 안전 종료
        trace.add(&quot;stopped&quot;, &quot;max_iterations_exceeded&quot;)
        return &quot;작업을 계속 진행하려면 추가 확인이 필요합니다. 지금까지 확인한 내용을 요약해드리겠습니다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;712&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 단순하지만 Agent Harness의 핵심을 거의 모두 담고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;714&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;714&quot;&gt;메모리 검색&lt;/li&gt;
&lt;li data-line=&quot;715&quot;&gt;작업 메모리 구성&lt;/li&gt;
&lt;li data-line=&quot;716&quot;&gt;LLM 판단&lt;/li&gt;
&lt;li data-line=&quot;717&quot;&gt;도구 호출&lt;/li&gt;
&lt;li data-line=&quot;718&quot;&gt;반복 루프&lt;/li&gt;
&lt;li data-line=&quot;719&quot;&gt;종료 조건&lt;/li&gt;
&lt;li data-line=&quot;720&quot;&gt;사용자 확인&lt;/li&gt;
&lt;li data-line=&quot;721&quot;&gt;실행 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;723&quot; data-ke-size=&quot;size16&quot;&gt;실제 제품에서는 여기에 권한 관리, 비용 제한, 모델 라우팅, 오류 복구, 평가 시스템, 대시보드가 붙습니다.&lt;/p&gt;
&lt;hr data-line=&quot;725&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;18-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85%EC%9D%B4%EB%82%98-%EA%B0%9C%EC%9D%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%B0%94%EB%A1%9C-%EC%A0%81%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%B5%9C%EC%86%8C-%EA%B5%AC%EC%A1%B0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;727&quot; data-ke-size=&quot;size26&quot;&gt;18. 스타트업이나 개인 개발자가 바로 적용할 수 있는 최소 구조&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 거대한 에이전트 플랫폼을 만들 필요는 없습니다. 작은 서비스라면 다음 정도로 시작할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;18-1-%EC%B5%9C%EC%86%8C-%EA%B5%AC%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;731&quot; data-ke-size=&quot;size23&quot;&gt;18-1. 최소 구성&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;Frontend
  &amp;darr;
API Server
  &amp;darr;
Agent Runner
  &amp;darr;
LLM Provider
  &amp;darr;
Postgres / Supabase
  &amp;darr;
Vector Store
  &amp;darr;
Tracing Table
  &amp;darr;
Eval Script
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;18-2-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%98%88%EC%8B%9C&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;751&quot; data-ke-size=&quot;size23&quot;&gt;18-2. 데이터베이스 테이블 예시&lt;/h3&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;CREATE TABLE agent_runs (
  id UUID PRIMARY KEY,
  user_id TEXT,
  input TEXT,
  output TEXT,
  status TEXT,
  latency_ms INTEGER,
  token_input INTEGER,
  token_output INTEGER,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE agent_events (
  id UUID PRIMARY KEY,
  run_id UUID REFERENCES agent_runs(id),
  event_type TEXT,
  payload JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE memories (
  id UUID PRIMARY KEY,
  user_id TEXT,
  memory_type TEXT,
  content TEXT,
  embedding VECTOR,
  importance INTEGER,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;18-3-%EC%9A%B4%EC%98%81-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C%EC%97%90%EC%84%9C-%EB%B4%90%EC%95%BC-%ED%95%A0-%ED%95%AD%EB%AA%A9&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;786&quot; data-ke-size=&quot;size23&quot;&gt;18-3. 운영 대시보드에서 봐야 할 항목&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;788&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;788&quot;&gt;최근 실행 실패율&lt;/li&gt;
&lt;li data-line=&quot;789&quot;&gt;평균 응답 시간&lt;/li&gt;
&lt;li data-line=&quot;790&quot;&gt;평균 도구 호출 수&lt;/li&gt;
&lt;li data-line=&quot;791&quot;&gt;평균 토큰 비용&lt;/li&gt;
&lt;li data-line=&quot;792&quot;&gt;가장 많이 실패한 도구&lt;/li&gt;
&lt;li data-line=&quot;793&quot;&gt;검색 결과가 없었던 질문&lt;/li&gt;
&lt;li data-line=&quot;794&quot;&gt;사용자 확인이 필요한 작업 비율&lt;/li&gt;
&lt;li data-line=&quot;795&quot;&gt;Eval 점수 하락 케이스&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;797&quot; data-ke-size=&quot;size16&quot;&gt;이 정도만 있어도 &amp;ldquo;에이전트가 왜 이상하게 답했는지&amp;rdquo;를 감으로 추측하는 단계에서 벗어날 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;799&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;19-rag%EC%99%80-memory%EB%A5%BC-%EA%B5%AC%EB%B6%84%ED%95%B4%EC%84%9C-%EC%84%A4%EA%B3%84%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;801&quot; data-ke-size=&quot;size26&quot;&gt;19. RAG와 Memory를 구분해서 설계해야 하는 이유&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;803&quot; data-ke-size=&quot;size16&quot;&gt;많은 사람이 RAG와 Memory를 같은 것으로 생각하지만, 둘은 다릅니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;805&quot; data-ke-size=&quot;size16&quot;&gt;RAG는 필요한 정보를 검색해서 답변에 넣는 기술적 방식입니다. Memory는 무엇을 기억할지, 얼마나 오래 기억할지, 어떤 구조로 기억할지에 대한 제품&amp;middot;시스템 설계입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;807&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 회사 정책 문서를 검색하는 것은 RAG입니다. 그러나 &amp;ldquo;이 고객은 지난 세 번 모두 배송 지연에 불만을 제기했다&amp;rdquo;를 장기적으로 관리하는 것은 Memory 설계입니다. 둘은 연결되지만 같은 개념은 아닙니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;809&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분RAGMemory&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #24292e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-line=&quot;811&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-line=&quot;813&quot;&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;813&quot;&gt;
&lt;td&gt;핵심 질문&lt;/td&gt;
&lt;td&gt;무엇을 검색할 것인가&lt;/td&gt;
&lt;td&gt;무엇을 기억할 것인가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;814&quot;&gt;
&lt;td&gt;대상&lt;/td&gt;
&lt;td&gt;문서, 지식베이스, 로그&lt;/td&gt;
&lt;td&gt;사용자, 사건, 절차, 사실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;815&quot;&gt;
&lt;td&gt;방식&lt;/td&gt;
&lt;td&gt;벡터 검색, 키워드 검색, rerank&lt;/td&gt;
&lt;td&gt;저장, 요약, 승격, 삭제, 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;816&quot;&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 style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;818&quot; data-ke-size=&quot;size16&quot;&gt;좋은 에이전트 시스템은 RAG만 붙인다고 완성되지 않습니다. 어떤 정보를 Semantic Memory로 유지할지, 어떤 이벤트를 Episodic Memory로 남길지, 어떤 절차를 Procedural Memory로 관리할지까지 설계해야 합니다.&lt;/p&gt;
&lt;hr data-line=&quot;820&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;20-%EB%B8%8C%EB%9F%B0%EC%B9%98-%EB%8F%85%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%B9%84%EC%9C%A0-ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EB%8A%94-%EC%8B%A0%EC%9E%85-%EC%A7%81%EC%9B%90%EA%B3%BC-%EB%B9%84%EC%8A%B7%ED%95%98%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;822&quot; data-ke-size=&quot;size26&quot;&gt;20. 브런치 독자를 위한 비유: AI 에이전트는 신입 직원과 비슷하다&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;824&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트를 신입 직원에 비유하면 이해하기 쉽습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;826&quot; data-ke-size=&quot;size16&quot;&gt;LLM은 똑똑한 신입 직원입니다. 지식도 많고 문서도 잘 씁니다. 하지만 회사의 실제 업무 방식은 모릅니다. 고객 데이터도 모릅니다. 결제 시스템에 접근하는 법도 모릅니다. 어떤 경우에 팀장 승인을 받아야 하는지도 모릅니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;828&quot; data-ke-size=&quot;size16&quot;&gt;그래서 신입 직원에게 필요한 것은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;830&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;830&quot;&gt;업무 매뉴얼: Procedural Memory&lt;/li&gt;
&lt;li data-line=&quot;831&quot;&gt;회사와 제품에 대한 기본 지식: Semantic Memory&lt;/li&gt;
&lt;li data-line=&quot;832&quot;&gt;고객과 나눈 이전 대화 기록: Episodic Memory&lt;/li&gt;
&lt;li data-line=&quot;833&quot;&gt;사내 시스템 접근 권한: Tool Calling&lt;/li&gt;
&lt;li data-line=&quot;834&quot;&gt;업무 진행 체크리스트: Loop Engineering&lt;/li&gt;
&lt;li data-line=&quot;835&quot;&gt;위험 작업 승인 규칙: Guardrails&lt;/li&gt;
&lt;li data-line=&quot;836&quot;&gt;업무 일지: Tracing&lt;/li&gt;
&lt;li data-line=&quot;837&quot;&gt;성과 평가: Eval&lt;/li&gt;
&lt;li data-line=&quot;838&quot;&gt;교육과 개선 프로세스: LLMOps&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;840&quot; data-ke-size=&quot;size16&quot;&gt;결국 AI 에이전트를 만든다는 것은 똑똑한 신입 직원에게 업무 환경, 권한, 규칙, 평가 체계를 제공하는 일과 비슷합니다.&lt;/p&gt;
&lt;hr data-line=&quot;842&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;21-%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%A0%9C-1-%ED%95%9C%EA%B5%AD%EC%96%B4-%EA%B5%90%EC%9C%A1-%ED%94%8C%EB%9E%AB%ED%8F%BC-ai-%ED%8A%9C%ED%84%B0&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;844&quot; data-ke-size=&quot;size26&quot;&gt;21. 실제 적용 예제 1: 한국어 교육 플랫폼 AI 튜터&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;846&quot; data-ke-size=&quot;size16&quot;&gt;한국어 학습자를 위한 AI 튜터를 만든다고 해보겠습니다. 단순 챗봇이라면 학습자가 질문할 때마다 답을 해줄 수 있습니다. 하지만 에이전트형 튜터라면 다음이 가능해야 합니다.&lt;/p&gt;
&lt;h3 id=&quot;%ED%95%84%EC%9A%94%ED%95%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;848&quot; data-ke-size=&quot;size23&quot;&gt;필요한 메모리&lt;/h3&gt;
&lt;pre class=&quot;json&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;{
  &quot;semantic_memory&quot;: [
    &quot;학습자는 초급 2단계이다&quot;,
    &quot;현재 교재는 즐거운 한국어 초급2이다&quot;,
    &quot;학습자는 조사 은/는과 이/가를 자주 헷갈린다&quot;
  ],
  &quot;episodic_memory&quot;: [
    &quot;2026-06-10: -아/어 보다 연습에서 오류 4회&quot;,
    &quot;2026-06-15: 음식점 주문 대화 연습 완료&quot;,
    &quot;2026-06-20: 과거형 활용에서 받침 있는 동사 오류 발생&quot;
  ],
  &quot;procedural_memory&quot;: [
    &quot;초급자에게는 문법 용어를 최소화한다&quot;,
    &quot;예문은 짧고 생활 상황 중심으로 제시한다&quot;,
    &quot;정답을 바로 주기보다 한 번 더 시도하게 한다&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%EB%A3%A8%ED%94%84-%EC%84%A4%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;870&quot; data-ke-size=&quot;size23&quot;&gt;루프 설계&lt;/h3&gt;
&lt;pre class=&quot;&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;학습자 답변 입력
  &amp;darr;
오류 유형 분석
  &amp;darr;
이전 오류 기록 검색
  &amp;darr;
현재 수준에 맞는 힌트 제공
  &amp;darr;
학습자 재시도
  &amp;darr;
정답 여부 확인
  &amp;darr;
오류 기록 업데이트
  &amp;darr;
다음 연습 추천
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%EC%A2%85%EB%A3%8C-%EC%A1%B0%EA%B1%B4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;890&quot; data-ke-size=&quot;size23&quot;&gt;종료 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;892&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;892&quot;&gt;학습자가 정답을 맞혔다.&lt;/li&gt;
&lt;li data-line=&quot;893&quot;&gt;학습자가 3회 이상 틀려서 설명 모드로 전환한다.&lt;/li&gt;
&lt;li data-line=&quot;894&quot;&gt;같은 오류가 반복되어 복습 과제로 저장한다.&lt;/li&gt;
&lt;li data-line=&quot;895&quot;&gt;수업 시간이 종료되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;897&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 설계하면 AI 튜터는 단순 답변기가 아니라 학습 이력을 바탕으로 적응하는 교육 에이전트가 됩니다.&lt;/p&gt;
&lt;hr data-line=&quot;899&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;22-%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%A0%9C-2-%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EC%A7%80%EA%B0%91-%EA%B3%A0%EA%B0%9D%EC%A7%80%EC%9B%90-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;901&quot; data-ke-size=&quot;size26&quot;&gt;22. 실제 적용 예제 2: 블록체인 지갑 고객지원 에이전트&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;903&quot; data-ke-size=&quot;size16&quot;&gt;블록체인 지갑 앱의 고객지원 에이전트를 만든다고 해보겠습니다. 이 에이전트는 일반 상담보다 더 강한 Guardrails가 필요합니다. 지갑, 서명, 트랜잭션, 자산과 관련된 작업은 위험할 수 있기 때문입니다.&lt;/p&gt;
&lt;h3 id=&quot;%EA%B0%80%EB%8A%A5%ED%95%9C-%EB%8F%84%EA%B5%AC&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;905&quot; data-ke-size=&quot;size23&quot;&gt;가능한 도구&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;907&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;907&quot;&gt;지갑 연결 상태 조회&lt;/li&gt;
&lt;li data-line=&quot;908&quot;&gt;트랜잭션 해시 조회&lt;/li&gt;
&lt;li data-line=&quot;909&quot;&gt;토큰 잔액 조회&lt;/li&gt;
&lt;li data-line=&quot;910&quot;&gt;네트워크 상태 조회&lt;/li&gt;
&lt;li data-line=&quot;911&quot;&gt;FAQ 검색&lt;/li&gt;
&lt;li data-line=&quot;912&quot;&gt;고객 티켓 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EA%B8%88%EC%A7%80-%EB%98%90%EB%8A%94-%ED%99%95%EC%9D%B8-%ED%95%84%EC%9A%94-%EC%9E%91%EC%97%85&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;914&quot; data-ke-size=&quot;size23&quot;&gt;금지 또는 확인 필요 작업&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;never_do:
  - ask_for_private_key
  - ask_for_seed_phrase
  - sign_transaction_without_user_action
  - promise_recovery_of_lost_assets

require_confirmation:
  - create_support_ticket
  - send_transaction_guide
  - change_security_setting
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;%EC%98%88%EC%8B%9C-%EC%9D%91%EB%8B%B5-%ED%9D%90%EB%A6%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;929&quot; data-ke-size=&quot;size23&quot;&gt;예시 응답 흐름&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;931&quot; data-ke-size=&quot;size16&quot;&gt;사용자:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;933&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;933&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;토큰을 보냈는데 지갑에 안 보여요.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;935&quot; data-ke-size=&quot;size16&quot;&gt;에이전트:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;937&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-line=&quot;937&quot;&gt;네트워크와 토큰 종류를 묻는다.&lt;/li&gt;
&lt;li data-line=&quot;938&quot;&gt;트랜잭션 해시가 있으면 조회한다.&lt;/li&gt;
&lt;li data-line=&quot;939&quot;&gt;컨트랙트 주소가 맞는지 확인한다.&lt;/li&gt;
&lt;li data-line=&quot;940&quot;&gt;지갑 UI에 토큰 추가가 필요한지 확인한다.&lt;/li&gt;
&lt;li data-line=&quot;941&quot;&gt;절대 개인키나 시드 문구를 요구하지 않는다.&lt;/li&gt;
&lt;li data-line=&quot;942&quot;&gt;해결되지 않으면 지원 티켓을 생성할지 사용자에게 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;944&quot; data-ke-size=&quot;size16&quot;&gt;이런 시스템은 단순 RAG보다 훨씬 더 정교한 Harness가 필요합니다.&lt;/p&gt;
&lt;hr data-line=&quot;946&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;23-%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%A0%9C-3-%EB%89%B4%EC%8A%A4-%EC%9E%90%EB%8F%99%ED%99%94-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;948&quot; data-ke-size=&quot;size26&quot;&gt;23. 실제 적용 예제 3: 뉴스 자동화 에이전트&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;매일 블록체인 뉴스를 수집해 요약하고 숏츠 대본을 만드는 자동화 에이전트를 생각해볼 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EB%A3%A8%ED%94%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;952&quot; data-ke-size=&quot;size23&quot;&gt;에이전트 루프&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f6f8fa; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;정해진 시간에 실행
  &amp;darr;
뉴스 소스 수집
  &amp;darr;
중복 기사 제거
  &amp;darr;
중요도 평가
  &amp;darr;
핵심 이슈 5개 선정
  &amp;darr;
각 이슈의 근거 링크 저장
  &amp;darr;
브리핑 글 작성
  &amp;darr;
숏츠 대본 생성
  &amp;darr;
썸네일 문구 생성
  &amp;darr;
사람 검수 대기
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;eval-%EA%B8%B0%EC%A4%80&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;976&quot; data-ke-size=&quot;size23&quot;&gt;Eval 기준&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;978&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;978&quot;&gt;최신성이 충분한가?&lt;/li&gt;
&lt;li data-line=&quot;979&quot;&gt;출처가 명확한가?&lt;/li&gt;
&lt;li data-line=&quot;980&quot;&gt;과장된 표현이 없는가?&lt;/li&gt;
&lt;li data-line=&quot;981&quot;&gt;투자 조언처럼 보이지 않는가?&lt;/li&gt;
&lt;li data-line=&quot;982&quot;&gt;숏츠 대본이 60초 안에 읽히는가?&lt;/li&gt;
&lt;li data-line=&quot;983&quot;&gt;제목이 자극적이지만 허위는 아닌가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;985&quot; data-ke-size=&quot;size16&quot;&gt;이 예제에서 중요한 것은 자동 발행보다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;검수 가능한 자동화&lt;/b&gt;입니다. 에이전트가 많은 일을 처리하되, 민감한 최종 발행은 사람이 확인하도록 루프를 설계할 수 있습니다.&lt;/p&gt;
&lt;hr data-line=&quot;987&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;24-%EC%9D%B4-%EC%98%81%EC%83%81%EC%9D%98-%ED%95%B5%EC%8B%AC%EC%9D%84-%ED%95%9C-%EB%AC%B8%EC%9E%A5%EC%9C%BC%EB%A1%9C-%EC%9A%94%EC%95%BD%ED%95%98%EB%A9%B4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;989&quot; data-ke-size=&quot;size26&quot;&gt;24. 이 영상의 핵심을 한 문장으로 요약하면&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;991&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 에이전트의 본질은 LLM에게 일을 시키는 것이 아니라, LLM이 기억을 검색하고 도구를 사용하고 반복 작업을 수행하고 스스로 멈추며, 그 모든 실행을 추적&amp;middot;평가&amp;middot;개선할 수 있게 만드는 운영 구조를 설계하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;993&quot; data-ke-size=&quot;size16&quot;&gt;이 관점에서 보면 Agent Harness, Loop Engineering, LLMOps, Eval, Tracing, RAG는 따로 떨어진 유행어가 아닙니다. 하나의 에이전트 시스템을 구성하는 서로 다른 층위입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;995&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;995&quot;&gt;Harness는 전체 제어 구조입니다.&lt;/li&gt;
&lt;li data-line=&quot;996&quot;&gt;Memory는 에이전트가 참조할 맥락입니다.&lt;/li&gt;
&lt;li data-line=&quot;997&quot;&gt;RAG는 필요한 정보를 검색하는 방식입니다.&lt;/li&gt;
&lt;li data-line=&quot;998&quot;&gt;Tool Calling은 실제 업무 수행 능력입니다.&lt;/li&gt;
&lt;li data-line=&quot;999&quot;&gt;Loop Engineering은 반복과 종료를 설계하는 방식입니다.&lt;/li&gt;
&lt;li data-line=&quot;1000&quot;&gt;Guardrails는 안전장치입니다.&lt;/li&gt;
&lt;li data-line=&quot;1001&quot;&gt;Tracing은 실행 기록입니다.&lt;/li&gt;
&lt;li data-line=&quot;1002&quot;&gt;Eval은 품질 평가입니다.&lt;/li&gt;
&lt;li data-line=&quot;1003&quot;&gt;LLMOps는 이 모든 것을 운영하고 개선하는 체계입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-line=&quot;1005&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;25-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1007&quot; data-ke-size=&quot;size26&quot;&gt;25. 개발자를 위한 체크리스트&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1009&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트를 만들기 전에 다음 질문에 답해보면 좋습니다.&lt;/p&gt;
&lt;h3 id=&quot;%EB%AA%A9%EC%A0%81-%EC%A0%95%EC%9D%98&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1011&quot; data-ke-size=&quot;size23&quot;&gt;목적 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1013&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1013&quot;&gt;이 에이전트는 답변만 하는가, 실제 업무도 수행하는가?&lt;/li&gt;
&lt;li data-line=&quot;1014&quot;&gt;업무 완료의 기준은 무엇인가?&lt;/li&gt;
&lt;li data-line=&quot;1015&quot;&gt;사용자의 확인이 필요한 행동은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%84%A4%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1017&quot; data-ke-size=&quot;size23&quot;&gt;메모리 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1019&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1019&quot;&gt;어떤 정보를 장기 기억으로 저장할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1020&quot;&gt;어떤 정보는 저장하면 안 되는가?&lt;/li&gt;
&lt;li data-line=&quot;1021&quot;&gt;오래된 기억은 어떻게 갱신하거나 삭제할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1022&quot;&gt;대화 로그를 언제 요약할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EA%B2%80%EC%83%89-%EC%84%A4%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1024&quot; data-ke-size=&quot;size23&quot;&gt;검색 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1026&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1026&quot;&gt;어떤 질문에 RAG를 사용할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1027&quot;&gt;어떤 질문은 모델 자체 지식으로 충분한가?&lt;/li&gt;
&lt;li data-line=&quot;1028&quot;&gt;검색 결과가 너무 많을 때 어떻게 줄일 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1029&quot;&gt;시간 기준 검색과 의미 검색을 어떻게 결합할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EB%8F%84%EA%B5%AC-%EC%84%A4%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1031&quot; data-ke-size=&quot;size23&quot;&gt;도구 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1033&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1033&quot;&gt;에이전트가 사용할 수 있는 도구는 무엇인가?&lt;/li&gt;
&lt;li data-line=&quot;1034&quot;&gt;각 도구의 입력과 출력 스키마는 명확한가?&lt;/li&gt;
&lt;li data-line=&quot;1035&quot;&gt;실패했을 때 재시도할 것인가, 사용자에게 물을 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1036&quot;&gt;비용이 큰 도구 호출은 제한되어 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;eba3a8ed9484-ec84a4eab384-1&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1038&quot; data-ke-size=&quot;size23&quot;&gt;루프 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1040&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1040&quot;&gt;최대 반복 횟수는 몇 번인가?&lt;/li&gt;
&lt;li data-line=&quot;1041&quot;&gt;같은 결과가 반복되면 멈추는가?&lt;/li&gt;
&lt;li data-line=&quot;1042&quot;&gt;사용자 입력이 필요한 상태를 감지하는가?&lt;/li&gt;
&lt;li data-line=&quot;1043&quot;&gt;종료 조건이 명확한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%ED%8F%89%EA%B0%80-%EC%84%A4%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1045&quot; data-ke-size=&quot;size23&quot;&gt;평가 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1047&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;1047&quot;&gt;좋은 답변의 기준은 무엇인가?&lt;/li&gt;
&lt;li data-line=&quot;1048&quot;&gt;실패한 실행을 어떻게 수집할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1049&quot;&gt;Eval 점수는 어디에 저장할 것인가?&lt;/li&gt;
&lt;li data-line=&quot;1050&quot;&gt;개선된 프롬프트를 어떻게 배포할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-line=&quot;1052&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;26-%EB%A7%88%EB%AC%B4%EB%A6%AC-ai-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8-%EC%8B%9C%EB%8C%80%EC%9D%98-%EA%B2%BD%EC%9F%81%EB%A0%A5%EC%9D%80-%EC%9A%B4%EC%98%81-%EC%84%A4%EA%B3%84%EC%97%90-%EC%9E%88%EB%8B%A4&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1054&quot; data-ke-size=&quot;size26&quot;&gt;26. 마무리: AI 에이전트 시대의 경쟁력은 운영 설계에 있다&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1056&quot; data-ke-size=&quot;size16&quot;&gt;LLM의 성능은 계속 좋아지고 있습니다. 하지만 실제 서비스에서 차이를 만드는 것은 모델 성능만이 아닙니다. 같은 모델을 쓰더라도 어떤 메모리를 붙이는지, 어떤 도구를 연결하는지, 어떤 루프를 설계하는지, 어떤 평가 체계를 운영하는지에 따라 결과는 크게 달라집니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1058&quot; data-ke-size=&quot;size16&quot;&gt;앞으로의 AI 제품 개발은 &amp;ldquo;모델을 호출하는 앱&amp;rdquo;에서 &amp;ldquo;에이전트를 운영하는 시스템&amp;rdquo;으로 이동할 가능성이 큽니다. 이때 필요한 역량은 프롬프트 작성 능력뿐만 아니라 시스템 설계, 데이터 설계, 관찰 가능성, 평가 자동화, 안전장치 설계입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1060&quot; data-ke-size=&quot;size16&quot;&gt;AI 에이전트를 만들고 있다면 먼저 거창한 프레임워크부터 찾기보다 다음 질문에서 시작하는 것이 좋습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #6a737d; text-align: start;&quot; data-line=&quot;1062&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-line=&quot;1062&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;내 에이전트는 무엇을 기억해야 하고, 어떤 도구를 써야 하며, 언제 멈춰야 하고, 어떻게 평가받아야 하는가?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1064&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 답할 수 있다면 Agent Harness, Loop Engineering, LLMOps라는 용어도 더 이상 어렵게 느껴지지 않을 것입니다. 그것들은 결국 하나의 목표를 향하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1066&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM을 예측 불가능한 답변 생성기가 아니라, 관찰 가능하고 개선 가능한 업무 시스템으로 만드는 것.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-line=&quot;1068&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;%EB%B6%80%EB%A1%9D-a-%EC%9A%A9%EC%96%B4-%EC%82%AC%EC%A0%84&quot; style=&quot;background-color: #ffffff; color: #24292e; text-align: start;&quot; data-line=&quot;1070&quot; data-ke-size=&quot;size26&quot;&gt;부록 A. 용어 사전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어쉬운 설명&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #24292e; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-line=&quot;1072&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-line=&quot;1074&quot;&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1074&quot;&gt;
&lt;td&gt;Agent Run&lt;/td&gt;
&lt;td&gt;사용자 입력부터 최종 응답까지 한 번의 에이전트 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1075&quot;&gt;
&lt;td&gt;Working Memory&lt;/td&gt;
&lt;td&gt;현재 실행에서 LLM에게 전달되는 임시 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1076&quot;&gt;
&lt;td&gt;Procedural Memory&lt;/td&gt;
&lt;td&gt;에이전트가 어떻게 행동해야 하는지에 대한 절차 기억&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1077&quot;&gt;
&lt;td&gt;Semantic Memory&lt;/td&gt;
&lt;td&gt;오래 유지되는 사실 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1078&quot;&gt;
&lt;td&gt;Episodic Memory&lt;/td&gt;
&lt;td&gt;시간 순서가 있는 과거 대화와 사건 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1079&quot;&gt;
&lt;td&gt;Agent Harness&lt;/td&gt;
&lt;td&gt;LLM을 원하는 방향으로 제어하기 위한 전체 시스템 장치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1080&quot;&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;외부 정보를 검색해 답변 생성에 활용하는 방식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1081&quot;&gt;
&lt;td&gt;Tool Calling&lt;/td&gt;
&lt;td&gt;에이전트가 외부 도구나 API를 호출하는 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1082&quot;&gt;
&lt;td&gt;Loop Engineering&lt;/td&gt;
&lt;td&gt;에이전트가 반복적으로 판단하고 도구를 호출하며 종료하는 구조 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1083&quot;&gt;
&lt;td&gt;Guardrails&lt;/td&gt;
&lt;td&gt;위험 행동, 무한 루프, 권한 문제를 막는 안전장치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1084&quot;&gt;
&lt;td&gt;Tracing&lt;/td&gt;
&lt;td&gt;실행 과정 전체를 이벤트 단위로 기록하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot; data-line=&quot;1085&quot;&gt;
&lt;td&gt;Eval&lt;/td&gt;
&lt;td&gt;에이전트 결과를 평가하는 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot; data-line=&quot;1086&quot;&gt;
&lt;td&gt;LLMOps&lt;/td&gt;
&lt;td&gt;LLM 기반 시스템을 관찰, 평가, 진단, 개선, 배포하는 운영 체계&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1784</guid>
      <comments>https://javaexpert.tistory.com/1784#entry1784comment</comments>
      <pubDate>Tue, 30 Jun 2026 10:00:33 +0900</pubDate>
    </item>
    <item>
      <title>Ornith-1.0: 코딩 에이전트가 스스로 &amp;lsquo;일하는 방식&amp;rsquo;까지 학습하기 시작했다</title>
      <link>https://javaexpert.tistory.com/1783</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbWkt3/dJMcafUGF2L/1GmPnGNeWkojnh02dEU120/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbWkt3/dJMcafUGF2L/1GmPnGNeWkojnh02dEU120/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbWkt3/dJMcafUGF2L/1GmPnGNeWkojnh02dEU120/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbWkt3%2FdJMcafUGF2L%2F1GmPnGNeWkojnh02dEU120%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;AI 코딩 모델의 경쟁은 지금까지 주로 &amp;ldquo;얼마나 좋은 코드를 생성하느냐&amp;rdquo;에 집중되어 있었다.&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 개발 현장에서 중요한 것은 단순 코드 생성이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 읽고, 환경을 이해하고, 테스트를 돌리고, 실패 원인을 찾고, 다시 수정하고, 최종적으로 동작하는 결과를 만드는 과정 전체가 중요하다.&lt;br /&gt;이런 흐름을 우리는 보통 &lt;b&gt;Agentic Coding&lt;/b&gt;, 즉 에이전트형 코딩이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepReinforce가 공개한 &lt;b&gt;Ornith-1.0&lt;/b&gt;은 바로 이 지점을 겨냥한 오픈소스 모델 패밀리다. 핵심은 단순하다.&lt;br /&gt;모델이 코드만 잘 짜는 것이 아니라, &lt;b&gt;문제를 풀기 위한 작업 절차와 보조 구조, 즉 scaffold까지 스스로 개선한다&lt;/b&gt;는 것이다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Ornith-1.0은 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0은 DeepReinforce가 공개한 &lt;b&gt;agentic coding 전용 오픈소스 모델 패밀리&lt;/b&gt;다. 공개 페이지에 따르면 모델 라인업은 9B Dense, 31B Dense, 35B MoE, 397B MoE까지 구성되어 있다. 작은 모델은 엣지 디바이스 배포를, 큰 모델은 프런티어급 성능을 목표로 한다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기반 모델은 &lt;b&gt;Gemma 4와 Qwen 3.5 계열 pretrained 모델&lt;/b&gt; 위에 구축되었다고 설명되어 있다. DeepReinforce는 Ornith-1.0이 비슷한 크기의 오픈소스 모델 대비 코딩 벤치마크에서 높은 성능을 보였다고 주장한다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 포인트는 &amp;ldquo;또 하나의 코딩 LLM&amp;rdquo;이 아니라는 점이다.&lt;br /&gt;Ornith-1.0의 핵심 차별점은 &lt;b&gt;Self-Scaffolding&lt;/b&gt;, 즉 모델이 문제 해결을 위한 내부 작업 방식까지 학습한다는 데 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Self-Scaffolding이 왜 중요한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 에이전트형 코딩 시스템은 대개 사람이 설계한 고정된 harness 또는 scaffold에 의존한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;파일을 읽어라.&amp;rdquo;&lt;br /&gt;&amp;ldquo;테스트를 실행해라.&amp;rdquo;&lt;br /&gt;&amp;ldquo;에러 로그를 분석해라.&amp;rdquo;&lt;br /&gt;&amp;ldquo;수정 후 다시 테스트해라.&amp;rdquo;&lt;br /&gt;&amp;ldquo;최종 답변을 작성해라.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 절차는 사람이 미리 설계한다. 모델은 그 안에서 움직인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0은 이 구조를 바꾸려 한다.&lt;br /&gt;DeepReinforce의 설명에 따르면 Ornith-1.0은 강화학습 과정에서 &lt;b&gt;솔루션 rollout뿐 아니라 그 rollout을 유도하는 scaffold 자체도 함께 생성하고 최적화한다&lt;/b&gt;. 즉, 모델이 &amp;ldquo;정답&amp;rdquo;만 학습하는 것이 아니라 &amp;ldquo;정답에 도달하기 위한 절차&amp;rdquo;도 학습한다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, 기존 모델은 문제를 푸는 학생에 가깝다.&lt;br /&gt;Ornith-1.0은 문제를 풀면서 동시에 &lt;b&gt;자기만의 풀이 전략 노트까지 개선하는 학생&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 학습 방식: 답안과 작업 절차를 함께 최적화한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0의 강화학습 단계는 크게 두 흐름으로 설명된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 모델은 주어진 task와 이전 scaffold를 바탕으로 &lt;b&gt;개선된 scaffold를 제안&lt;/b&gt;한다.&lt;br /&gt;그다음 그 scaffold와 task 설명을 바탕으로 &lt;b&gt;solution rollout&lt;/b&gt;, 즉 실제 문제 해결 과정을 생성한다.&lt;br /&gt;최종 reward는 단순히 답안 생성 단계에만 반영되는 것이 아니라 scaffold 생성 단계에도 함께 전달된다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 의미하는 바는 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 특정 문제 유형에서 어떤 접근 방식이 더 좋은지 반복적으로 학습한다.&lt;br /&gt;어떤 순서로 파일을 읽는 것이 좋은지, 언제 테스트를 돌리는 것이 좋은지, 에러를 어떻게 분류해야 하는지, 어떤 복구 전략이 효과적인지 같은 &amp;ldquo;작업 운영 방식&amp;rdquo;이 점점 진화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 에이전트에서 중요한 것은 단순 지식이 아니라 &lt;b&gt;실행 전략&lt;/b&gt;이다.&lt;br /&gt;Ornith-1.0은 바로 그 실행 전략을 학습 대상으로 끌어올린 모델이라고 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Reward Hacking 문제를 어떻게 막았나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Self-improvement 구조에는 위험도 있다.&lt;br /&gt;모델이 스스로 scaffold를 만들 수 있다면, 실제 문제를 해결하지 않고도 평가기를 속이는 방향으로 최적화될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepReinforce 역시 이 문제를 언급한다. 예를 들어 모델이 숨겨진 테스트 파일을 읽거나, 검증 스크립트를 우회하거나, 기대 출력값을 하드코딩하는 식의 reward hacking이 발생할 수 있다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 막기 위해 Ornith-1.0은 세 가지 방어 구조를 사용한다고 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 환경과 도구 표면, 테스트 격리 구조를 모델이 바꿀 수 없는 외부 신뢰 경계로 고정한다.&lt;br /&gt;둘째, deterministic monitor를 통해 금지된 경로 읽기, 검증 스크립트 변경, 허용되지 않은 도구 호출 등을 감지하고 해당 trajectory에 zero reward를 부여한다.&lt;br /&gt;셋째, 허용된 도구 안에서 발생할 수 있는 의도 수준의 gaming을 막기 위해 frozen LLM judge를 verifier 위의 veto 장치로 사용한다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 코딩 에이전트 학습에서 매우 중요하다.&lt;br /&gt;에이전트가 강력해질수록 &amp;ldquo;문제를 푸는 능력&amp;rdquo;과 &amp;ldquo;평가 체계를 우회하는 능력&amp;rdquo;이 동시에 커질 수 있기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 성능: 397B 모델은 Claude Opus급을 겨냥한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepReinforce 공개 자료에 따르면 Ornith-1.0-397B는 Terminal-Bench 2.1에서 77.5, SWE-Bench Verified에서 82.4를 기록했다고 한다. 같은 표에서 Claude Opus 4.7은 Terminal-Bench 2.1 70.3, SWE-Bench Verified 80.8로 제시되어 있다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 수치는 DeepReinforce 페이지에 공개된 평가 결과 기준이다.&lt;br /&gt;[Unverified] 독립적인 제3자 재현 평가까지 확인한 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개 표 기준으로 보면 Ornith-1.0-397B는 다음과 같은 위치에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델Terminal-Bench 2.1SWE-Bench Verified&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ornith-1.0-397B&lt;/td&gt;
&lt;td&gt;77.5&lt;/td&gt;
&lt;td&gt;82.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.7&lt;/td&gt;
&lt;td&gt;70.3&lt;/td&gt;
&lt;td&gt;80.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-V4-Pro&lt;/td&gt;
&lt;td&gt;64.0 또는 67.9 계열 수치로 표기&lt;/td&gt;
&lt;td&gt;80.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax-M3&lt;/td&gt;
&lt;td&gt;64.0&lt;/td&gt;
&lt;td&gt;표 항목 일부 미기재&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개 페이지 안에서도 일부 본문 수치와 표 수치가 세부적으로 다르게 보이는 부분이 있다. 예를 들어 본문에서는 DeepSeek-V4-Pro의 Terminal-Bench 2.1 수치를 67.9로 언급하지만, 표에서는 Terminus-2 기준 64.0, Claude Code 기준 66.5로 제시되어 있다. 따라서 외부 글에서는 &amp;ldquo;DeepReinforce 공개 페이지 기준&amp;rdquo;이라고 명확히 쓰는 것이 안전하다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 더 흥미로운 모델은 9B일 수 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대형 397B 모델도 인상적이지만, 실제 활용 관점에서 더 눈에 띄는 것은 &lt;b&gt;Ornith-1.0-9B&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeepReinforce에 따르면 9B 모델은 Terminal-Bench 2.1에서 43.1, SWE-Bench Verified에서 69.4를 기록했다. 이 수치는 Qwen3.5-9B보다 높고, 일부 항목에서는 더 큰 Gemma4-31B와 비슷하거나 앞서는 것으로 제시되어 있다. (&lt;a href=&quot;https://deep-reinforce.com/ornith_1_0.html&quot;&gt;DeepReinforce&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 중요한 이유는 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대기업이나 연구소는 397B급 모델을 운용할 수 있다.&lt;br /&gt;하지만 개인 개발자, 스타트업, 로컬 에이전트 환경에서는 9B~35B급 모델이 훨씬 현실적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0-9B가 공개 수치처럼 강한 agentic coding 성능을 낸다면, 로컬 개발 에이전트, 온디바이스 코딩 도우미, 사내 폐쇄망 개발 도구 같은 영역에서 의미가 있을 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Ornith-1.0이 보여주는 방향성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0의 핵심 메시지는 &amp;ldquo;더 큰 모델&amp;rdquo;이 아니다.&lt;br /&gt;오히려 중요한 것은 &lt;b&gt;에이전트의 작업 구조 자체를 학습 대상으로 삼았다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 코딩 AI의 경쟁은 단순히 다음 토큰을 잘 예측하는 방향에서 벗어날 가능성이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 중요한 질문은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 어떤 순서로 생각하는가?&lt;br /&gt;어떤 도구를 언제 호출하는가?&lt;br /&gt;실패했을 때 어떻게 복구하는가?&lt;br /&gt;테스트 결과를 어떻게 해석하는가?&lt;br /&gt;문제 해결 전략을 스스로 개선할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0은 이 질문에 대한 하나의 답을 제시한다.&lt;br /&gt;코딩 모델은 이제 코드 생성기를 넘어, &lt;b&gt;자기 작업 방식을 개선하는 개발 에이전트&lt;/b&gt;로 진화하고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 개발자 관점에서의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서 Ornith-1.0은 세 가지 관점에서 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;b&gt;오픈소스 코딩 에이전트 경쟁이 본격화되고 있다.&lt;/b&gt;&lt;br /&gt;Claude Code, OpenAI Codex 계열, Devin류 시스템처럼 폐쇄형 도구가 강세였던 agentic coding 영역에 오픈소스 모델이 빠르게 접근하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;scaffold 설계가 점점 중요해진다.&lt;/b&gt;&lt;br /&gt;지금까지는 프롬프트 엔지니어링이나 툴 호출 구조를 사람이 설계했다. 하지만 Ornith-1.0이 제시하는 방향은 scaffold 자체도 학습 가능한 객체로 보는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, &lt;b&gt;작은 모델의 가능성이 커진다.&lt;/b&gt;&lt;br /&gt;9B 모델이 agentic coding benchmark에서 일정 수준 이상의 성능을 낸다면, &amp;ldquo;항상 초대형 모델을 API로 호출해야 한다&amp;rdquo;는 전제가 약해질 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 한계와 주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Ornith-1.0을 볼 때는 몇 가지를 조심해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 현재 성능 수치는 DeepReinforce의 공개 페이지 기준이다.&lt;br /&gt;[Unverified] 제3자 재현 평가, 실제 개발 프로젝트 적용 결과, 장기 안정성까지 확인된 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, benchmark 성능이 실제 제품 품질을 그대로 의미하지는 않는다.&lt;br /&gt;SWE-Bench나 Terminal-Bench는 유용한 평가 기준이지만, 실제 현업 코드는 레거시 의존성, 팀 규칙, 보안 정책, 비즈니스 로직, 배포 환경까지 얽혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, self-scaffolding은 강력하지만 위험한 방향이기도 하다.&lt;br /&gt;모델이 작업 절차를 스스로 바꿀 수 있다는 것은 효율성을 높일 수 있다는 뜻이지만, 동시에 평가 우회나 예측 불가능한 실행 전략이 나올 수 있다는 뜻이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실제 활용에서는 sandbox, 권한 제한, 로그 추적, 테스트 격리, 코드 리뷰 체계가 필수다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: Ornith-1.0은 &amp;lsquo;코드를 쓰는 AI&amp;rsquo;보다 &amp;lsquo;일하는 방식을 배우는 AI&amp;rsquo;에 가깝다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0의 흥미로운 지점은 성능표 자체보다 그 철학에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코딩 모델은 &amp;ldquo;좋은 답을 내는 모델&amp;rdquo;에 가까웠다.&lt;br /&gt;Ornith-1.0은 한 걸음 더 나아가 &amp;ldquo;좋은 답에 도달하는 방식을 스스로 개선하는 모델&amp;rdquo;을 지향한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 변화는 앞으로 AI 개발 도구의 방향을 잘 보여준다.&lt;br /&gt;프롬프트를 잘 쓰는 시대에서, 에이전트의 작업 루프를 설계하는 시대로 넘어가고 있다.&lt;br /&gt;그리고 그 다음 단계는 에이전트가 자신의 작업 루프를 직접 개선하는 시대일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ornith-1.0은 그 전환을 보여주는 흥미로운 사례다.&lt;br /&gt;아직 검증해야 할 부분은 많지만, agentic coding과 오픈소스 코딩 모델의 미래를 보는 데 충분히 주목할 만한 프로젝트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1782527430545&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Ornith-1.0 - a deepreinforce-ai Collection&quot; data-og-description=&quot;Ornith-1.0 is &amp;nbsp;a family of open-source LLMs specialized for agentic coding.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/collections/deepreinforce-ai/ornith-10&quot; data-og-url=&quot;https://huggingface.co/collections/deepreinforce-ai/ornith-10&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eS5Cw/dJMb83kDlNN/Bf1STarhmeazaWqMI3zUx0/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/lWYVv/dJMb83Stmpf/Kl5kPsNYsLpYVn7nJWrnE1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648&quot;&gt;&lt;a href=&quot;https://huggingface.co/collections/deepreinforce-ai/ornith-10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/collections/deepreinforce-ai/ornith-10&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eS5Cw/dJMb83kDlNN/Bf1STarhmeazaWqMI3zUx0/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/lWYVv/dJMb83Stmpf/Kl5kPsNYsLpYVn7nJWrnE1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ornith-1.0 - a deepreinforce-ai Collection&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ornith-1.0 is &amp;nbsp;a family of open-source LLMs specialized for agentic coding.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1783</guid>
      <comments>https://javaexpert.tistory.com/1783#entry1783comment</comments>
      <pubDate>Sat, 27 Jun 2026 11:30:41 +0900</pubDate>
    </item>
    <item>
      <title>mattpocock/skills 에 대한 자세한 설명</title>
      <link>https://javaexpert.tistory.com/1782</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;figure id=&quot;og_1782348460568&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - mattpocock/skills: Skills for Real Engineers. Straight from my .claude directory.&quot; data-og-description=&quot;Skills for Real Engineers. Straight from my .claude directory. - mattpocock/skills&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/mattpocock/skills&quot; data-og-url=&quot;https://github.com/mattpocock/skills&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdYh3U/dJMb87ggyXx/PLyxmV9wvv2mhKEUDvAtP1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_129_1053_219,https://scrap.kakaocdn.net/dn/FB0PW/dJMb86n7Ghu/Jhb2b6E4k1EXFzBKkMXaDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_129_1053_219,https://scrap.kakaocdn.net/dn/KmWcd/dJMb81f2Gqz/iJLgdsKVHtQZe77PIVn9p1/img.png?width=738&amp;amp;height=388&amp;amp;face=491_81_572_169&quot;&gt;&lt;a href=&quot;https://github.com/mattpocock/skills&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/mattpocock/skills&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdYh3U/dJMb87ggyXx/PLyxmV9wvv2mhKEUDvAtP1/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_129_1053_219,https://scrap.kakaocdn.net/dn/FB0PW/dJMb86n7Ghu/Jhb2b6E4k1EXFzBKkMXaDk/img.png?width=1200&amp;amp;height=600&amp;amp;face=971_129_1053_219,https://scrap.kakaocdn.net/dn/KmWcd/dJMb81f2Gqz/iJLgdsKVHtQZe77PIVn9p1/img.png?width=738&amp;amp;height=388&amp;amp;face=491_81_572_169');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - mattpocock/skills: Skills for Real Engineers. Straight from my .claude directory.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Skills for Real Engineers. Straight from my .claude directory. - mattpocock/skills&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYV7zZ/dJMcacp6abT/WUoM2EVddclOHmnA860D81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYV7zZ/dJMcacp6abT/WUoM2EVddclOHmnA860D81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYV7zZ/dJMcacp6abT/WUoM2EVddclOHmnA860D81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYV7zZ%2FdJMcacp6abT%2FWUoM2EVddclOHmnA860D81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1536&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소의 스킬은 크게 두 종류입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User-invoked skill&lt;/b&gt;은 사용자가 직접 /grill-me, /to-prd처럼 호출하는 명령형 스킬입니다.&lt;br /&gt;&lt;b&gt;Model-invoked skill&lt;/b&gt;은 에이전트가 작업 중 필요할 때 자동으로 참고하거나 호출하는 내부 절차형 스킬입니다. README도 이 둘을 &amp;ldquo;누가 호출할 수 있는가&amp;rdquo; 기준으로 나눈다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순 프롬프트 모음이라기보다 &lt;b&gt;기획 &amp;rarr; 질문 &amp;rarr; 문서화 &amp;rarr; 이슈화 &amp;rarr; 구현 &amp;rarr; 테스트 &amp;rarr; 디버깅 &amp;rarr; 구조 개선&lt;/b&gt;까지 에이전트가 반복 가능한 개발 루틴을 따르게 만드는 장치입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1. Engineering 스킬&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 작업에 직접 쓰는 스킬들입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/setup-matt-pocock-skills&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 한 번 실행하는 설정 스킬입니다.&lt;br /&gt;프로젝트에서 어떤 이슈 트래커를 쓸지, triage 라벨을 어떻게 쓸지, CONTEXT.md와 ADR 같은 도메인 문서를 어디에 저장할지 정합니다. README에 따르면 GitHub, Linear, 로컬 파일 기반 이슈 관리를 선택할 수 있습니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 프로젝트에서 앞으로 에이전트가 일할 규칙과 문서 위치를 정하는 초기 세팅&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/ask-matt&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상황에 어떤 스킬을 쓰면 좋을지 물어보는 라우터입니다.&lt;br /&gt;예를 들어 &amp;ldquo;기획을 정리해야 하나?&amp;rdquo;, &amp;ldquo;이슈로 쪼개야 하나?&amp;rdquo;, &amp;ldquo;버그를 잡아야 하나?&amp;rdquo; 같은 상황에서 적절한 스킬을 추천받는 용도입니다. README는 이를 user-invoked skills 위의 라우터라고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/grill-with-docs&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 스킬 중 하나입니다.&lt;br /&gt;사용자가 만들고 싶은 기능이나 변경사항에 대해 에이전트가 집요하게 질문합니다. 단순 질문에서 끝나는 것이 아니라, 프로젝트의 &lt;b&gt;도메인 모델&lt;/b&gt;, &lt;b&gt;공통 용어&lt;/b&gt;, &lt;b&gt;ADR&lt;/b&gt;까지 같이 정리합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 이렇게 말하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;회원 결제 기능 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트가 바로 코딩하지 않고 먼저 묻습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원은 무료/유료가 있나요?&lt;br /&gt;결제 실패 시 상태는 어떻게 되나요?&lt;br /&gt;환불은 누가 처리하나요?&lt;br /&gt;구독 갱신 실패 후 며칠 동안 접근을 허용하나요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과를 CONTEXT.md나 ADR에 남겨서 이후 작업에서도 같은 용어와 판단 기준을 쓰게 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/to-prd&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 대화 내용을 바탕으로 &lt;b&gt;PRD&lt;/b&gt;, 즉 제품 요구사항 문서로 정리하는 스킬입니다.&lt;br /&gt;README에 따르면 별도 인터뷰 없이 지금까지 논의한 내용을 종합해서 PRD로 만들고 이슈 트래커에 게시하는 역할입니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 상황:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;지금까지 말한 내용을 PRD로 정리해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 기능을 개발 문서로 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은 보통 다음을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목내용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/to-issues&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PRD나 기획서를 &lt;b&gt;작업 가능한 이슈 단위&lt;/b&gt;로 쪼개는 스킬입니다.&lt;br /&gt;README는 이를 vertical slice, 즉 독립적으로 가져가서 구현 가능한 단위로 나누는 스킬이라고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;결제 기능 구현&amp;rdquo;을 이렇게 나눕니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 기능이슈 예시&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;결제 API&lt;/td&gt;
&lt;td&gt;결제 요청 endpoint 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결제 상태&lt;/td&gt;
&lt;td&gt;pending/success/failed 상태 모델링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;웹훅&lt;/td&gt;
&lt;td&gt;PG사 결제 완료 웹훅 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점은 에이전트에게 &amp;ldquo;큰 덩어리&amp;rdquo;를 던지지 않고, 하나씩 검증 가능한 작업으로 나누는 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/triage&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈를 상태 머신처럼 이동시키는 스킬입니다.&lt;br /&gt;README에 따르면 triage role 라벨을 기준으로 이슈를 옮깁니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상 흐름은 이런 식입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 이슈 &amp;rarr; 검토 필요 &amp;rarr; 기획 필요 &amp;rarr; 구현 가능 &amp;rarr; 작업 중 &amp;rarr; 완료&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Inference] 실제 라벨명은 /setup-matt-pocock-skills에서 사용자가 정한 값에 따라 달라질 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/prototype&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버릴 수 있는 프로토타입을 빠르게 만드는 스킬입니다.&lt;br /&gt;README는 상태/비즈니스 로직 질문에는 실행 가능한 터미널 앱을, UI 질문에는 한 라우트에서 여러 UI 변형을 토글할 수 있는 방식으로 만든다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 온보딩 플로우가 좋은지 모르겠어.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하면, 실제 제품 코드에 바로 넣지 않고 여러 버전의 UI를 만들어 비교하게 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/improve-codebase-architecture&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드베이스 구조 개선 스킬입니다.&lt;br /&gt;README에 따르면 CONTEXT.md의 도메인 언어와 docs/adr/의 결정을 참고해, 코드베이스에서 &amp;ldquo;deepening opportunities&amp;rdquo;를 찾고 시각적 HTML 리포트로 보여준 뒤, 선택한 항목에 대해 질문 과정을 진행합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;코드가 점점 진흙덩어리처럼 복잡해지는 것을 막기 위한 구조 점검 스킬&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾을 수 있는 문제 유형은 보통 이런 것들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;도메인 로직과 UI/인프라 코드가 섞임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;명명 불일치&lt;/td&gt;
&lt;td&gt;도메인 용어와 코드 이름이 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;diagnosing-bugs&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 필요할 때 쓰는 버그 진단 루프입니다.&lt;br /&gt;README는 이를 &amp;ldquo;재현 &amp;rarr; 최소화 &amp;rarr; 가설 &amp;rarr; 계측 &amp;rarr; 수정 &amp;rarr; 회귀 테스트&amp;rdquo; 흐름으로 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 AI 코딩의 문제는 바로 &amp;ldquo;수정부터&amp;rdquo; 한다는 점입니다.&lt;br /&gt;이 스킬은 먼저 버그를 재현하고, 최소 사례를 만들고, 로그나 테스트로 확인한 다음 수정하게 만듭니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;tdd&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 주도 개발 스킬입니다.&lt;br /&gt;README는 red-green-refactor 루프를 사용해 기능이나 버그 수정을 vertical slice 단위로 진행한다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;실패하는 테스트를 먼저 작성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Green&lt;/td&gt;
&lt;td&gt;테스트를 통과하도록 최소 구현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refactor&lt;/td&gt;
&lt;td&gt;동작을 유지하면서 구조 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트에게 특히 유용한 이유는 &amp;ldquo;돌아가는지 확인할 피드백 루프&amp;rdquo;를 강제로 만들기 때문입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;domain-modeling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 도메인 모델을 정리하고 다듬는 스킬입니다.&lt;br /&gt;README에 따르면 용어를 glossary와 대조하고, edge case 시나리오로 검증하며, CONTEXT.md와 ADR을 업데이트합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 교육 플랫폼이라면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모호한 표현도메인 용어로 정리&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;강의 올리기&lt;/td&gt;
&lt;td&gt;lesson materialization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수업 묶음&lt;/td&gt;
&lt;td&gt;course section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공개 처리&lt;/td&gt;
&lt;td&gt;publish transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수강 가능 상태&lt;/td&gt;
&lt;td&gt;enrollment eligibility&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 프로젝트 내부 언어를 정리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;codebase-design&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드베이스 설계를 위한 내부 규율 스킬입니다.&lt;br /&gt;README는 &amp;ldquo;작은 인터페이스 뒤에 많은 동작을 숨기는 deep module&amp;rdquo; 개념을 중심으로, 깨끗한 경계와 테스트 가능한 인터페이스를 강조합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 에이전트가 코드를 만들 때:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일만 쪼개지 말고, 진짜 의미 있는 모듈 경계를 만들어라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 설계 원칙을 주입하는 스킬입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Productivity 스킬&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에만 한정되지 않는 일반 작업용 스킬입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/grill-me&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비코드 작업에도 쓸 수 있는 질문 스킬입니다.&lt;br /&gt;README는 사용자의 계획이나 디자인에 대해 모든 결정 분기가 해결될 때까지 집요하게 인터뷰하는 스킬이라고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;블록체인 뉴스 자동화 스튜디오 만들고 싶어.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하면 바로 PRD를 쓰지 않고:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 사용자는 누구인가?&lt;br /&gt;뉴스 수집 소스는 어디인가?&lt;br /&gt;자동 업로드까지 할 것인가?&lt;br /&gt;검수 단계가 필요한가?&lt;br /&gt;실패하면 누가 알림을 받는가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 질문을 통해 요구사항을 명확히 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/handoff&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 대화를 다른 에이전트가 이어받을 수 있도록 &lt;b&gt;handoff 문서&lt;/b&gt;로 압축하는 스킬입니다.&lt;br /&gt;README는 현재 대화를 compact하게 정리해서 다른 에이전트가 이어서 작업할 수 있게 한다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용도:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;지금까지 논의한 걸 다음 에이전트에게 넘길 문서로 정리해줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포함될 만한 내용:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목내용&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다음 작업&lt;/td&gt;
&lt;td&gt;이어서 할 일&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/teach&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 새 기술이나 개념을 여러 세션에 걸쳐 가르치는 스킬입니다.&lt;br /&gt;README는 현재 디렉터리를 상태 저장형 teaching workspace로 사용한다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;TypeScript 고급 타입을 단계별로 가르쳐줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 요청에 맞습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;/writing-great-skills&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 스킬을 작성하고 편집하기 위한 참고 스킬입니다.&lt;br /&gt;README는 스킬을 예측 가능하게 만드는 어휘와 원칙을 다루는 reference라고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 사용자가 직접 Claude/Codex용 skill을 만들 때 참고하는 &amp;ldquo;스킬 제작 가이드&amp;rdquo;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;grilling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/grill-me와 /grill-with-docs 뒤에서 재사용되는 내부 질문 루프입니다.&lt;br /&gt;README는 계획이나 디자인의 결정 분기가 모두 해소될 때까지 사용자를 인터뷰하는 재사용 루프라고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Misc 스킬&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰지는 않지만 특정 상황에서 유용한 도구성 스킬입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;git-guardrails-claude-code&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code에서 위험한 git 명령을 막는 hook을 설정합니다.&lt;br /&gt;README는 push, reset --hard, clean 같은 위험 명령을 실행 전에 차단한다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 개발에서 AI 에이전트가 실수로 작업물을 날리는 것을 막는 안전장치입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;migrate-to-shoehorn&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 파일에서 TypeScript의 as 타입 assertion을 @total-typescript/shoehorn으로 마이그레이션하는 스킬입니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 범용 스킬이라기보다 Matt Pocock의 Total TypeScript 생태계와 관련된 특수 목적 스킬에 가깝습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;scaffold-exercises&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의나 학습용 exercise 디렉터리 구조를 만드는 스킬입니다.&lt;br /&gt;README는 sections, problems, solutions, explainers 구조를 만든다고 설명합니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 TypeScript 강의 자료를 만들 때:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;exercises/
  01-basic-types/
    problem/
    solution/
    explainer.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 식으로 구성하는 용도입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;setup-pre-commit&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Husky, lint-staged, Prettier, type checking, tests를 포함한 pre-commit hook을 설정하는 스킬입니다. (&lt;a href=&quot;https://github.com/mattpocock/skills&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 커밋 전에 자동으로 코드 포맷, 린트, 타입 체크, 테스트를 돌리게 만드는 개발 품질 장치입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;전체 흐름으로 보면&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 저장소는 이렇게 쓰는 게 가장 자연스럽습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. /setup-matt-pocock-skills
   &amp;darr;
2. /grill-with-docs
   요구사항 질문 + 도메인 용어 정리
   &amp;darr;
3. /to-prd
   PRD 작성
   &amp;darr;
4. /to-issues
   구현 가능한 이슈로 분해
   &amp;darr;
5. /tdd
   테스트 기반 구현
   &amp;darr;
6. diagnosing-bugs
   버그 발생 시 재현 중심으로 진단
   &amp;darr;
7. /improve-codebase-architecture
   코드가 복잡해지기 전에 구조 개선
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄로 요약하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mattpocock/skills는 AI 코딩 에이전트에게 &amp;ldquo;기획을 확인하고, 문서화하고, 이슈로 쪼개고, 테스트하며, 구조를 관리하는 개발 습관&amp;rdquo;을 강제로 주입하는 스킬 세트입니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 그냥 &amp;ldquo;만들어줘&amp;rdquo;라고 던지는 방식보다, &lt;b&gt;PRD &amp;rarr; 이슈 &amp;rarr; TDD &amp;rarr; 디버깅 &amp;rarr; 아키텍처 개선&lt;/b&gt; 흐름을 만들고 싶은 사람에게 유용합니다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1782</guid>
      <comments>https://javaexpert.tistory.com/1782#entry1782comment</comments>
      <pubDate>Thu, 25 Jun 2026 09:48:46 +0900</pubDate>
    </item>
    <item>
      <title>Loop 방식의 에이전트 (Loom)</title>
      <link>https://javaexpert.tistory.com/1781</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deRVml/dJMcaa6Scc7/ghgHN4eQ8Z4UfjjQr4pYd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deRVml/dJMcaa6Scc7/ghgHN4eQ8Z4UfjjQr4pYd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deRVml/dJMcaa6Scc7/ghgHN4eQ8Z4UfjjQr4pYd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeRVml%2FdJMcaa6Scc7%2FghgHN4eQ8Z4UfjjQr4pYd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 data-end=&quot;112&quot; data-start=&quot;56&quot;&gt;&lt;span&gt;AI 코딩 에이전트의 다음 단계: Loom은 왜 &amp;ldquo;프롬프트&amp;rdquo;가 아니라 &amp;ldquo;전달 체계&amp;rdquo;를 말하는가&lt;/span&gt;&lt;/h1&gt;
&lt;p data-end=&quot;235&quot; data-start=&quot;114&quot; data-ke-size=&quot;size16&quot;&gt;요즘 개발자들은 Claude Code, Codex, OpenCode 같은 AI 코딩 에이전트를 점점 더 자연스럽게 사용한다. 간단한 컴포넌트 수정, API 작성, 테스트 코드 생성 정도는 이제 꽤 익숙한 작업이 됐다.&lt;/p&gt;
&lt;p data-end=&quot;280&quot; data-start=&quot;237&quot; data-ke-size=&quot;size16&quot;&gt;그런데 실제 제품 개발에 AI 에이전트를 붙여 보면 곧 다른 문제가 드러난다.&lt;/p&gt;
&lt;p data-end=&quot;424&quot; data-start=&quot;282&quot; data-ke-size=&quot;size16&quot;&gt;AI는 코드를 꽤 잘 짠다. 하지만 &lt;b&gt;끝까지 책임지고 전달하는 일&lt;/b&gt;은 여전히 어렵다. 요구사항을 기억하고, 작업을 쪼개고, 검증하고, 실패하면 고치고, 중간에 세션이 끊겨도 다시 이어가고, 마지막에는 사람이 확인할 수 있는 근거까지 남기는 일 말이다.&lt;/p&gt;
&lt;p data-end=&quot;706&quot; data-start=&quot;426&quot; data-ke-size=&quot;size16&quot;&gt;valkor-ai/loom은 바로 이 지점을 겨냥한 오픈소스 프로젝트다. Loom은 스스로를 &amp;ldquo;agentic software delivery를 위한 loop engineering&amp;rdquo; 도구라고 설명한다. 기존 코딩 에이전트를 대체하는 모델이나 에디터가 아니라, Claude Code, Codex, OpenCode 같은 에이전트를 &lt;b&gt;반복 가능하고 검증 가능한 소프트웨어 전달 시스템&lt;/b&gt;으로 바꾸는 &amp;ldquo;delivery harness&amp;rdquo;다.&lt;/p&gt;
&lt;h2 data-end=&quot;719&quot; data-start=&quot;708&quot; data-ke-size=&quot;size26&quot;&gt;한 줄로 말하면&lt;/h2&gt;
&lt;p data-end=&quot;841&quot; data-start=&quot;721&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 AI 코딩 에이전트에게 &amp;ldquo;코드만 작성하게 하는 도구&amp;rdquo;가 아니라, &lt;b&gt;요구사항 확인 &amp;rarr; 계획 &amp;rarr; 구현 &amp;rarr; 검증 &amp;rarr; 수정 &amp;rarr; 미리보기 &amp;rarr; 인수인계&lt;/b&gt;까지 이어지는 작업 루프를 제공하는 오픈소스 전달 레이어다.&lt;/p&gt;
&lt;p data-end=&quot;857&quot; data-start=&quot;843&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 이런 차이다.&lt;/p&gt;
&lt;p data-end=&quot;873&quot; data-start=&quot;859&quot; data-ke-size=&quot;size16&quot;&gt;기존 방식은 보통 이렇다.&lt;/p&gt;
&lt;blockquote data-end=&quot;889&quot; data-start=&quot;875&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;889&quot; data-start=&quot;877&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 기능 만들어줘.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1038&quot; data-start=&quot;891&quot; data-ke-size=&quot;size16&quot;&gt;그러면 AI가 코드를 작성한다. 운이 좋으면 바로 작동한다. 하지만 프로젝트가 커지면 문제가 생긴다. AI가 이전 요구사항을 잊거나, 일부만 구현하고 완료했다고 말하거나, 테스트 없이 자신 있게 답하거나, 세션이 끊긴 뒤 같은 파일을 다시 읽으며 토큰을 낭비한다.&lt;/p&gt;
&lt;p data-end=&quot;1064&quot; data-start=&quot;1040&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 이 흐름을 다음처럼 바꾸려 한다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1149&quot; data-start=&quot;1066&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1149&quot; data-start=&quot;1068&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;요구사항을 구조화하고, 작업을 나누고, 각 작업의 결과를 기록하고, 검증하고, 실패하면 수리 요청을 만들고, 다음 세션에서도 이어서 진행하자.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1251&quot; data-start=&quot;1151&quot; data-ke-size=&quot;size16&quot;&gt;즉, Loom의 핵심은 &lt;b&gt;AI에게 더 좋은 프롬프트를 주는 것&lt;/b&gt;이 아니라, AI가 장기 작업을 수행할 때 필요한 &lt;b&gt;상태, 절차, 근거, 복구 루프&lt;/b&gt;를 만들어 주는 것이다.&lt;/p&gt;
&lt;h2 data-end=&quot;1269&quot; data-start=&quot;1253&quot; data-ke-size=&quot;size26&quot;&gt;왜 이런 도구가 필요한가&lt;/h2&gt;
&lt;p data-end=&quot;1343&quot; data-start=&quot;1271&quot; data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구의 가장 큰 착각은 &amp;ldquo;코드를 잘 쓰면 개발이 끝난다&amp;rdquo;는 생각이다. 실제 소프트웨어 개발은 코드 작성보다 훨씬 넓다.&lt;/p&gt;
&lt;p data-end=&quot;1527&quot; data-start=&quot;1345&quot; data-ke-size=&quot;size16&quot;&gt;요구사항을 확인해야 하고, 아키텍처 결정을 내려야 하며, 작업 단위를 나눠야 한다. 백엔드 상태, 환경 변수, 데이터베이스, 인증, 스토리지, 런타임 조건도 챙겨야 한다. UI라면 화면 흐름, 반응형 상태, 접근성, 인터랙션까지 고려해야 한다. 이후에는 테스트를 돌리고, 로그를 확인하고, 실패를 고친 뒤 다시 검증해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1729&quot; data-start=&quot;1529&quot; data-ke-size=&quot;size16&quot;&gt;Loom의 README도 이 문제를 명확히 짚는다. 웹사이트나 앱 생성 자체는 점점 쉬워지고 있지만, 더 어려운 문제는 &amp;ldquo;신뢰할 수 있는 전달&amp;rdquo;이라고 본다. 특히 장기 작업에서는 부분 완료, 목표 이탈, 자기검증 편향, 토큰 낭비, 인수인계 공백 같은 문제가 반복적으로 발생한다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;1947&quot; data-start=&quot;1731&quot; data-ke-size=&quot;size16&quot;&gt;이 지점에서 Loom은 프롬프트 파일이나 단발성 워크플로와 구분된다. 단순히 CLAUDE.md, AGENTS.md, .cursorrules 같은 지침 파일을 두는 것이 아니라, 프로젝트 내부의 .loom/ 디렉터리에 작업 상태와 증거를 저장하고, 에이전트 중립적인 CLI 명령으로 다음 단계를 라우팅한다.&lt;/p&gt;
&lt;h2 data-end=&quot;1969&quot; data-start=&quot;1949&quot; data-ke-size=&quot;size26&quot;&gt;Loom의 핵심 개념: &amp;ldquo;루프&amp;rdquo;&lt;/h2&gt;
&lt;p data-end=&quot;2055&quot; data-start=&quot;1971&quot; data-ke-size=&quot;size16&quot;&gt;Loom이 말하는 루프는 단순 반복이 아니다. 소프트웨어 전달 과정 전체를 단계별로 쪼개고, 각 단계의 결과를 기록하며, 실패하면 다시 고치는 구조다.&lt;/p&gt;
&lt;p data-end=&quot;2077&quot; data-start=&quot;2057&quot; data-ke-size=&quot;size16&quot;&gt;Loom의 기본 흐름은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2212&quot; data-start=&quot;2079&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2092&quot; data-start=&quot;2079&quot;&gt;전달 범위 확인&lt;/li&gt;
&lt;li data-end=&quot;2111&quot; data-start=&quot;2093&quot;&gt;압축된 컨텍스트 팩 생성&lt;/li&gt;
&lt;li data-end=&quot;2135&quot; data-start=&quot;2112&quot;&gt;계획, 아키텍처, 작업 계약 생성&lt;/li&gt;
&lt;li data-end=&quot;2159&quot; data-start=&quot;2136&quot;&gt;한 번에 하나의 제한된 작업 실행&lt;/li&gt;
&lt;li data-end=&quot;2178&quot; data-start=&quot;2160&quot;&gt;증거 기록 및 검증 실행&lt;/li&gt;
&lt;li data-end=&quot;2195&quot; data-start=&quot;2179&quot;&gt;리뷰, 수정, 재검증&lt;/li&gt;
&lt;li data-end=&quot;2212&quot; data-start=&quot;2196&quot;&gt;최종 전달 상태 보고&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2307&quot; data-start=&quot;2214&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름은 README의 &amp;ldquo;How It Works&amp;rdquo; 섹션에 제시된 Loom의 핵심 루프와 일치한다.&lt;/p&gt;
&lt;p data-end=&quot;2481&quot; data-start=&quot;2309&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 표현은 &lt;b&gt;작업 계약(task contract)&lt;/b&gt;이다. AI에게 &amp;ldquo;대충 이 기능 만들어줘&amp;rdquo;라고 맡기는 것이 아니라, 특정 소스 참조, 수용 기준, 결과 파일, 계속 진행 규칙 등을 가진 작업 단위로 나누는 방식이다. 이렇게 해야 AI가 일부만 만들고 끝났다고 착각하는 문제를 줄일 수 있다.&lt;/p&gt;
&lt;h2 data-end=&quot;2506&quot; data-start=&quot;2483&quot; data-ke-size=&quot;size26&quot;&gt;기존 AI 코딩 도구와 무엇이 다른가&lt;/h2&gt;
&lt;p data-end=&quot;2572&quot; data-start=&quot;2508&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 Claude Code나 Codex와 경쟁하는 도구라기보다, 그 위에 얹히는 &lt;b&gt;운영 레이어&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;2694&quot; data-start=&quot;2574&quot; data-ke-size=&quot;size16&quot;&gt;Claude Code, Codex, OpenCode는 코드를 작성하고 명령을 실행하는 에이전트다. Loom은 이 에이전트들이 어떤 순서로 일해야 하는지, 무엇을 기억해야 하는지, 어떤 근거를 남겨야 하는지 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;2725&quot; data-start=&quot;2696&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 Codex에서는 다음처럼 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@loom build a visitor registration system
@loom plan this feature first
@loom continue
@loom review
@loom deploy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3079&quot; data-start=&quot;2853&quot; data-ke-size=&quot;size16&quot;&gt;Claude Code와 OpenCode에서는 /loom build, /loom continue, /loom review, /loom deploy 같은 명령 형태로 사용한다. Loom은 Codex, Claude Code, OpenCode용 로컬 어댑터 설치를 지원하며, 각 어댑터는 동일한 Loom 전달 프로토콜을 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;3096&quot; data-start=&quot;3081&quot; data-ke-size=&quot;size16&quot;&gt;이 구조의 장점은 명확하다.&lt;/p&gt;
&lt;p data-end=&quot;3223&quot; data-start=&quot;3098&quot; data-ke-size=&quot;size16&quot;&gt;하나의 AI 에이전트에 종속되지 않는다. 오늘은 Codex를 쓰고, 내일은 Claude Code를 쓰더라도, Loom이 저장한 전달 상태를 중심으로 이어갈 수 있다. 프로젝트 로컬 상태가 .loom/에 저장되기 때문이다.&lt;/p&gt;
&lt;h2 data-end=&quot;3244&quot; data-start=&quot;3225&quot; data-ke-size=&quot;size26&quot;&gt;토큰 절약이라는 현실적인 장점&lt;/h2&gt;
&lt;p data-end=&quot;3354&quot; data-start=&quot;3246&quot; data-ke-size=&quot;size16&quot;&gt;AI 개발 자동화에서 토큰 비용은 생각보다 큰 문제다. 특히 대형 코드베이스에서 에이전트가 매번 전체 저장소를 다시 읽거나, 이미 파악한 내용을 반복해서 요약하면 비용과 시간이 빠르게 증가한다.&lt;/p&gt;
&lt;p data-end=&quot;3523&quot; data-start=&quot;3356&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 프로젝트 요약, 작업 그래프, 백엔드 상태, 테스트 결과, 배포 증거 등을 저장해 에이전트가 매번 전체 맥락을 다시 읽지 않도록 설계되어 있다. README에서는 이를 &amp;ldquo;Token-Saving Context&amp;rdquo;로 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;3766&quot; data-start=&quot;3525&quot; data-ke-size=&quot;size16&quot;&gt;공개된 11개 케이스 benchmark 결과에서는 Codex 단독 실행과 Codex + Loom 실행을 비교했을 때, Codex + Loom이 전체 468,067 토큰 대비 394,037 토큰을 사용해 74,030 토큰, 즉 15.8%를 절약했다고 제시되어 있다. 또한 11개 중 8개 케이스에서 토큰을 덜 사용했고, 완료율은 양쪽 모두 100%로 기록되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;3858&quot; data-start=&quot;3768&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 수치는 프로젝트 측 벤치마크 결과다. 외부 독립 검증 결과는 아니므로, 실제 효과는 코드베이스 규모, 에이전트 종류, 작업 유형에 따라 달라질 수 있다.&lt;/p&gt;
&lt;h2 data-end=&quot;3872&quot; data-start=&quot;3860&quot; data-ke-size=&quot;size26&quot;&gt;설치와 기술 스택&lt;/h2&gt;
&lt;p data-end=&quot;4090&quot; data-start=&quot;3874&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 Node.js 기반 프로젝트다. package.json 기준 버전은 0.1.0, 라이선스는 Apache-2.0이며, TypeScript로 빌드되는 CLI 도구 구조를 갖고 있다. 의존성에는 commander, yaml, zod, pdf-parse, mammoth 등이 포함되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;4109&quot; data-start=&quot;4092&quot; data-ke-size=&quot;size16&quot;&gt;기본 설치 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;git clone https://github.com/valkor-ai/loom.git
cd loom
npm install&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4220&quot; data-start=&quot;4192&quot; data-ke-size=&quot;size16&quot;&gt;그다음 사용하는 에이전트에 따라 어댑터를 설치한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-codex
npm run plugin:install-claude
npm run plugin:install-opencode&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4361&quot; data-start=&quot;4326&quot; data-ke-size=&quot;size16&quot;&gt;여러 에이전트를 함께 테스트하려면 다음 명령을 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-adapters&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4525&quot; data-start=&quot;4408&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 Node.js 20 이상과 npm을 요구하며, loom deploy 기능을 위해 Docker가 필요하다고 문서에 명시되어 있다.&lt;/p&gt;
&lt;h2 data-end=&quot;4542&quot; data-start=&quot;4527&quot; data-ke-size=&quot;size26&quot;&gt;사용방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom 사용방법은 크게 &lt;b&gt;설치 &amp;rarr; 에이전트 어댑터 연결 &amp;rarr; 프로젝트에서 Loom 명령 실행 &amp;rarr; continue/review/deploy로 반복&lt;/b&gt; 흐름입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Loom 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 GitHub 저장소를 받아옵니다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;git clone https://github.com/valkor-ai/loom.git
cd loom
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom은 &lt;b&gt;Node.js 20 이상&lt;/b&gt;, npm이 필요하고, loom deploy를 쓰려면 &lt;b&gt;Docker&lt;/b&gt;가 필요합니다. 공식 README에도 prerequisites로 Node.js &amp;gt;= 20, npm, 사용하는 코딩 에이전트 CLI, Docker가 명시되어 있습니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 사용하는 AI 코딩 에이전트에 맞게 어댑터 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom은 단독으로 쓰는 앱이라기보다 &lt;b&gt;Codex, Claude Code, OpenCode 같은 코딩 에이전트 안에서 명령어로 호출&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex용&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-codex
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code용&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-claude
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenCode용&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-opencode
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전부 설치&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run plugin:install-adapters
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서 기준으로 각 어댑터 설치 스크립트는 CLI를 빌드하고, ~/.loom/bin/loom-cli 런처와 각 에이전트용 로컬 어댑터 파일을 갱신합니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 설치 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 중인 에이전트 안에서 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom status
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code / OpenCode&lt;/h3&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;/loom status
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 사용하는 프로젝트라면 STATE_NOT_INITIALIZED가 나올 수 있는데, 이것은 오류가 아니라 &lt;b&gt;아직 해당 프로젝트에서 Loom delivery가 시작되지 않았다는 뜻&lt;/b&gt;입니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실제 프로젝트에서 Loom 시작하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 방문자 등록 시스템을 만들고 싶다면:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex&lt;/h3&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;@loom build a visitor registration system
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code / OpenCode&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;/loom build a visitor registration system
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어로도 이렇게 지시할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom build 관리자용 방문자 등록 시스템을 만들어줘. 방문자는 이름, 연락처, 방문 목적, 방문 시간을 입력하고 관리자는 목록을 확인할 수 있어야 해.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code나 OpenCode라면:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/loom build 관리자용 방문자 등록 시스템을 만들어줘. 방문자는 이름, 연락처, 방문 목적, 방문 시간을 입력하고 관리자는 목록을 확인할 수 있어야 해.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령을 실행하면 Loom이 프로젝트 안에 .loom/ 상태를 만들고, 요구사항 확인, 계획, 작업 분해, 구현, 검증 흐름을 시작합니다. Loom은 프로젝트 로컬 전달 상태를 .loom/ 아래에 저장한다고 공식 문서가 설명합니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 먼저 계획만 세우게 하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 구현시키기보다 먼저 설계를 보고 싶으면:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom plan this feature first
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code / OpenCode&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/loom plan this feature first
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom plan 회원 초대 기능을 먼저 설계해줘. 초대 링크 생성, 이메일 전송, 만료 시간, 권한 처리를 포함해야 해.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AI가 바로 코드를 수정하기 전에 &lt;b&gt;요구사항, 아키텍처, 작업 단위&lt;/b&gt;를 정리하는 흐름으로 갑니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 중단 후 이어서 진행하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom에서 가장 중요한 명령은 continue입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 중간에 세션이 끊겼거나, AI가 멈췄거나, 다음 단계가 뭔지 애매할 때는 내부 명령을 추측하지 말고 continue를 실행하는 방식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom continue
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code / OpenCode&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;/loom continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 README에서도 세션을 다시 열었거나, 중단 이후 또는 다음 내부 단계가 확실하지 않을 때 continue를 먼저 실행하라고 설명합니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 리뷰 실행하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현된 결과를 검토하게 하려면:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Codex&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom review
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Claude Code / OpenCode&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/loom review
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loom의 핵심은 구현과 검증을 분리하는 것입니다. 공식 문서도 review, verification, repair requests, evidence records를 통해 구현과 검증을 분리한다고 설명합니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 로컬 미리보기 / 검증하기&lt;/h2&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/loom deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 여기서 말하는 deploy가 &lt;b&gt;프로덕션 배포&lt;/b&gt;는 아니라는 것입니다. 현재 Loom의 배포 지원은 로컬 Docker Compose 미리보기, 검증, 로그, 수리 가이드 중심이며, production deployment는 아직 미지원이라고 FAQ에 명시되어 있습니다. (&lt;a href=&quot;https://github.com/valkor-ai/loom?utm_source=chatgpt.com&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;실제 사용 흐름 예시&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Next.js 프로젝트에서 관리자 페이지를 만든다고 하면 이렇게 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;cd my-next-app
codex
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex 안에서:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom build 관리자 대시보드를 만들어줘. 로그인한 관리자만 접근 가능해야 하고, 사용자 목록, 검색, 상태 변경 기능이 필요해.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획을 보고 싶으면:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom plan this feature first
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현이 끝난 뒤 검토:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom review
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 검증:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 있으면 다시:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름을 반복하는 방식입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개인 개발자 기준 추천 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 큰 프로젝트 전체를 맡기기보다, 기능 단위로 쓰는 게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 예:&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;@loom build 게시글 작성, 수정, 삭제 기능을 만들어줘. 기존 Prisma 스키마와 Next.js App Router 구조를 유지해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 예:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom plan 게시글 작성, 수정, 삭제 기능을 먼저 설계해줘. DB 스키마 변경이 필요한지 확인하고, API route와 UI 컴포넌트 작업을 나눠줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피하는 게 좋은 예:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom build 인스타그램 같은 앱 만들어줘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 범위가 커서 요구사항 확인 단계가 길어지고, 결과 품질도 흔들릴 가능성이 큽니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄 사용 공식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처음 시작:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom build 만들 기능 설명
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설계부터:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom plan 기능 설명
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중간 이어가기:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@loom continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검토:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom review
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로컬 미리보기/검증:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@loom deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code와 OpenCode에서는 @loom 대신 /loom을 쓰면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, Loom은 &amp;ldquo;한 번에 코드 만들어줘&amp;rdquo;가 아니라 &lt;b&gt;AI 코딩 에이전트에게 계획, 구현, 검증, 수리, 인수인계 루프를 태우는 방식&lt;/b&gt;으로 사용하는 도구입니다.&lt;/p&gt;
&lt;h2 data-end=&quot;4542&quot; data-start=&quot;4527&quot; data-ke-size=&quot;size26&quot;&gt;어떤 상황에서 유용한가&lt;/h2&gt;
&lt;p data-end=&quot;4596&quot; data-start=&quot;4544&quot; data-ke-size=&quot;size16&quot;&gt;Loom이 빛나는 상황은 단순 코드 수정이 아니다. 오히려 다음과 같은 작업에서 의미가 크다.&lt;/p&gt;
&lt;p data-end=&quot;4722&quot; data-start=&quot;4598&quot; data-ke-size=&quot;size16&quot;&gt;첫째, 기능 하나가 여러 파일과 레이어를 건드릴 때다. 예를 들어 회원 초대 기능을 만든다면 UI, API, 권한, 이메일, 데이터베이스, 테스트가 함께 얽힌다. 이런 작업은 단발 프롬프트보다 계획과 검증 루프가 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;4898&quot; data-start=&quot;4724&quot; data-ke-size=&quot;size16&quot;&gt;둘째, 에이전트 작업이 여러 세션에 걸쳐 이어질 때다. AI 코딩 도구는 세션이 길어질수록 컨텍스트 압축이나 누락 문제가 생긴다. Loom은 작업 상태를 프로젝트 내부에 저장해 continue 명령으로 다음 단계를 재개하도록 설계되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;5047&quot; data-start=&quot;4900&quot; data-ke-size=&quot;size16&quot;&gt;셋째, 결과물에 대한 증거가 필요할 때다. &amp;ldquo;완료했습니다&amp;rdquo;라는 말만으로는 부족하다. 어떤 테스트를 돌렸는지, 어떤 로그를 확인했는지, 어떤 미리보기 결과가 있었는지 기록되어야 한다. Loom은 검증, 수리 요청, 배포 미리보기, 전달 보고서를 일급 단계로 다룬다.&lt;/p&gt;
&lt;p data-end=&quot;5174&quot; data-start=&quot;5049&quot; data-ke-size=&quot;size16&quot;&gt;넷째, 여러 AI 코딩 에이전트를 병행하고 싶을 때다. Codex, Claude Code, OpenCode를 각각 따로 쓰면 작업 방식과 명령 체계가 흩어진다. Loom은 이들을 공통 전달 프로토콜로 묶으려는 접근을 취한다.&lt;/p&gt;
&lt;h2 data-end=&quot;5184&quot; data-start=&quot;5176&quot; data-ke-size=&quot;size26&quot;&gt;사용 사례&lt;/h2&gt;
&lt;p data-end=&quot;5350&quot; data-start=&quot;5186&quot; data-ke-size=&quot;size16&quot;&gt;Loom 문서에는 AI 제품 런칭 사이트, 크리에이터 분석 워크스페이스, 인터랙티브 캠페인 마이크로사이트, 비행 시뮬레이터, 퀀트 트레이딩 워크벤치, 생물다양성 리서치, 영양 건강 앱 같은 예시 사용 사례가 나열되어 있다.&lt;/p&gt;
&lt;p data-end=&quot;5437&quot; data-start=&quot;5352&quot; data-ke-size=&quot;size16&quot;&gt;이 목록을 보면 Loom이 단순 &amp;ldquo;코드 수정 보조&amp;rdquo;가 아니라, 작은 제품이나 프로토타입을 끝까지 만들어 전달하는 워크플로를 겨냥하고 있음을 알 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;5548&quot; data-start=&quot;5439&quot; data-ke-size=&quot;size16&quot;&gt;특히 개인 개발자나 소규모 팀이 AI로 빠르게 MVP를 만들 때 유용해 보인다. 바이브 코딩으로 데모를 만드는 단계에서 한 걸음 더 나아가, 실제로 검증 가능한 전달물을 만들고 싶은 경우에 맞다.&lt;/p&gt;
&lt;h2 data-end=&quot;5561&quot; data-start=&quot;5550&quot; data-ke-size=&quot;size26&quot;&gt;한계도 분명하다&lt;/h2&gt;
&lt;p data-end=&quot;5612&quot; data-start=&quot;5563&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 흥미로운 도구지만, 아직 모든 것을 해결하는 완성형 플랫폼으로 보기는 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;5798&quot; data-start=&quot;5614&quot; data-ke-size=&quot;size16&quot;&gt;첫째, 현재 production 배포 기능은 제공하지 않는다. README의 FAQ에 따르면 production deployment는 아직 추가 예정이며, 현재 배포 지원은 로컬 Docker Compose 미리보기, 검증, 로그, 수리 가이드에 초점을 둔다.&lt;/p&gt;
&lt;p data-end=&quot;5980&quot; data-start=&quot;5800&quot; data-ke-size=&quot;size16&quot;&gt;둘째, 초기 버전이다. package.json 기준 버전이 0.1.0이고, 저장소도 빠르게 변할 가능성이 있다. 실제 팀 워크플로에 적용하려면 파일 생성 방식, .loom/ 상태 관리, CI/CD 연동, 보안 정책 등을 직접 검토해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;6107&quot; data-start=&quot;5982&quot; data-ke-size=&quot;size16&quot;&gt;셋째, AI 에이전트 자체의 품질 문제를 완전히 없애는 도구는 아니다. Loom은 루프, 상태, 검증 절차를 제공하지만, 최종 구현 품질은 여전히 사용하는 에이전트, 모델, 테스트 환경, 개발자의 리뷰 역량에 영향을 받는다.&lt;/p&gt;
&lt;p data-end=&quot;6215&quot; data-start=&quot;6109&quot; data-ke-size=&quot;size16&quot;&gt;넷째, 팀 단위 사용에서는 규칙 설계가 필요하다. .loom/ 디렉터리를 Git에 포함할지, 어떤 증거를 남길지, 민감한 로그나 환경 정보가 기록되지 않도록 어떻게 관리할지 정해야 한다.&lt;/p&gt;
&lt;h2 data-end=&quot;6238&quot; data-start=&quot;6217&quot; data-ke-size=&quot;size26&quot;&gt;그래서 Loom의 의미는 무엇인가&lt;/h2&gt;
&lt;p data-end=&quot;6289&quot; data-start=&quot;6240&quot; data-ke-size=&quot;size16&quot;&gt;Loom의 가장 큰 의미는 AI 코딩의 관심사를 &amp;ldquo;생성&amp;rdquo;에서 &amp;ldquo;전달&amp;rdquo;로 옮긴다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;6363&quot; data-start=&quot;6291&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 많은 AI 코딩 도구는 &amp;ldquo;얼마나 코드를 잘 생성하는가&amp;rdquo;에 집중했다. 그러나 실제 개발 현장에서 더 중요한 질문은 다르다.&lt;/p&gt;
&lt;p data-end=&quot;6492&quot; data-start=&quot;6365&quot; data-ke-size=&quot;size16&quot;&gt;이 기능이 요구사항을 충족했는가?&lt;br /&gt;검증했는가?&lt;br /&gt;실패했을 때 다시 고쳤는가?&lt;br /&gt;다음 사람이 이어받을 수 있는가?&lt;br /&gt;중간에 세션이 끊겨도 복구할 수 있는가?&lt;br /&gt;사용한 에이전트가 바뀌어도 같은 맥락에서 이어갈 수 있는가?&lt;/p&gt;
&lt;p data-end=&quot;6516&quot; data-start=&quot;6494&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 이 질문들에 답하려는 시도다.&lt;/p&gt;
&lt;p data-end=&quot;6637&quot; data-start=&quot;6518&quot; data-ke-size=&quot;size16&quot;&gt;[Inference] 앞으로 AI 코딩 도구의 경쟁력은 단순한 코드 생성 능력보다, 이런 전달 루프를 얼마나 안정적으로 설계하느냐로 이동할 가능성이 크다. Loom은 그 방향을 비교적 명확하게 보여주는 프로젝트다.&lt;/p&gt;
&lt;h2 data-end=&quot;6644&quot; data-start=&quot;6639&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-end=&quot;6726&quot; data-start=&quot;6646&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 &amp;ldquo;AI 코딩 에이전트를 더 똑똑하게 만드는 도구&amp;rdquo;라기보다, &lt;b&gt;AI 코딩 에이전트가 일을 끝까지 하게 만드는 운영 체계&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;6813&quot; data-start=&quot;6728&quot; data-ke-size=&quot;size16&quot;&gt;코드는 AI가 쓴다.&lt;br /&gt;하지만 요구사항을 보존하고, 작업을 나누고, 검증하고, 실패를 복구하고, 다음 세션으로 이어주는 것은 별도의 시스템이 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;6843&quot; data-start=&quot;6815&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 그 시스템을 오픈소스로 만들려는 시도다.&lt;/p&gt;
&lt;p data-end=&quot;7046&quot; data-start=&quot;6845&quot; data-ke-size=&quot;size16&quot;&gt;아직 초기 단계이고, production deployment 같은 영역은 미완성이다. 그럼에도 AI 기반 개발 자동화를 진지하게 고민하는 개발자라면 살펴볼 가치가 있다. 특히 Claude Code, Codex, OpenCode를 단순한 &amp;ldquo;코딩 도우미&amp;rdquo;가 아니라 &amp;ldquo;소프트웨어 전달 파이프라인의 일부&amp;rdquo;로 쓰고 싶다면 Loom의 접근 방식은 꽤 중요한 힌트를 준다.&lt;/p&gt;
&lt;hr data-end=&quot;7051&quot; data-start=&quot;7048&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;7061&quot; data-start=&quot;7053&quot; data-ke-size=&quot;size26&quot;&gt;짧은 요약&lt;/h2&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;7457&quot; data-start=&quot;7063&quot; data-ke-size=&quot;size16&quot;&gt;Loom은 Claude Code, Codex, OpenCode 같은 AI 코딩 에이전트를 위한 오픈소스 delivery harness다. 단순히 코드를 생성하는 것이 아니라, 요구사항 확인, 계획, 작업 분해, 구현, 검증, 수리, 미리보기, 인수인계까지 이어지는 반복 가능한 전달 루프를 만든다. .loom/에 프로젝트 상태와 작업 증거를 저장하고, @loom continue 또는 /loom continue 같은 명령으로 중단된 작업을 이어갈 수 있다. 공개 벤치마크에서는 Codex + Loom 조합이 Codex 단독 대비 15.8% 토큰을 절약했다고 제시되어 있다. 다만 현재 production 배포는 지원하지 않고, 로컬 Docker Compose 기반 미리보기와 검증 중심이다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;7457&quot; data-start=&quot;7063&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1782196055831&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - valkor-ai/loom: Loop engineering for agentic software delivery.&quot; data-og-description=&quot;Loop engineering for agentic software delivery. Contribute to valkor-ai/loom development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/valkor-ai/loom&quot; data-og-url=&quot;https://github.com/valkor-ai/loom&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fRtn1/dJMb86Pbzl4/zFxNLziKSBk13yVq1UBtOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/LI5py/dJMb8VNFvUi/C8OIrWBM2hVzEk4k1CepU0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/BCBES/dJMb9efnLik/W9jCuJwlmZtLmVpSbbb1H0/img.png?width=1983&amp;amp;height=793&amp;amp;face=0_0_1983_793&quot;&gt;&lt;a href=&quot;https://github.com/valkor-ai/loom&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/valkor-ai/loom&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fRtn1/dJMb86Pbzl4/zFxNLziKSBk13yVq1UBtOK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/LI5py/dJMb8VNFvUi/C8OIrWBM2hVzEk4k1CepU0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/BCBES/dJMb9efnLik/W9jCuJwlmZtLmVpSbbb1H0/img.png?width=1983&amp;amp;height=793&amp;amp;face=0_0_1983_793');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - valkor-ai/loom: Loop engineering for agentic software delivery.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Loop engineering for agentic software delivery. Contribute to valkor-ai/loom development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1781</guid>
      <comments>https://javaexpert.tistory.com/1781#entry1781comment</comments>
      <pubDate>Tue, 23 Jun 2026 15:25:16 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트가 코드를 너무 많이 쓴다면: Ponytail</title>
      <link>https://javaexpert.tistory.com/1780</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6p23/dJMcahSjGAZ/1UwQG8HdNACqmy43TgzBO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6p23/dJMcahSjGAZ/1UwQG8HdNACqmy43TgzBO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6p23/dJMcahSjGAZ/1UwQG8HdNACqmy43TgzBO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6p23%2FdJMcahSjGAZ%2F1UwQG8HdNACqmy43TgzBO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 쓰다 보면 이상한 순간을 자주 만난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 간단한 기능을 부탁했는데, 에이전트는 갑자기 새로운 컴포넌트를 만들고, 유틸 파일을 추가하고, 추상화 계층을 만들고, 아직 필요하지도 않은 설정값까지 준비한다.&lt;br /&gt;사람 개발자라면 한 줄로 끝냈을 일을 AI는 &amp;ldquo;미래 확장성&amp;rdquo;이라는 이름으로 100줄짜리 구조로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 정면으로 겨냥한 프로젝트가 &lt;b&gt;Ponytail&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail의 저장소 설명은 노골적이다.&lt;br /&gt;&amp;ldquo;AI 에이전트가 방 안에서 가장 게으른 시니어 개발자처럼 생각하게 만든다.&amp;rdquo;&lt;br /&gt;여기서 게으르다는 말은 대충 만든다는 뜻이 아니다. &lt;b&gt;안 만들어도 되는 코드는 만들지 않는 개발자&lt;/b&gt;에 가깝다. GitHub README에서도 Ponytail은 &amp;ldquo;가장 좋은 코드는 작성하지 않은 코드&amp;rdquo;라는 철학을 전면에 내세운다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/tree/main&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ponytail은 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 새로운 LLM 모델이 아니다.&lt;br /&gt;새로운 IDE도 아니다.&lt;br /&gt;코드를 직접 실행하는 별도의 자동화 서버도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히 말하면 Ponytail은 &lt;b&gt;AI 코딩 에이전트를 위한 스킬, 규칙, 플러그인 패키지&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code, Codex, OpenCode, Gemini CLI, Cursor, Windsurf, Cline, GitHub Copilot, Kiro 같은 여러 코딩 에이전트 환경에 &amp;ldquo;최소 구현 우선&amp;rdquo; 규칙을 주입한다. 프로젝트 문서에 따르면 핵심 동작은 skills/에 들어 있고, 각 에이전트별 파일은 이 핵심 규칙을 해당 도구가 읽을 수 있게 연결하는 어댑터 역할을 한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/docs/agent-portability.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 이런 역할이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;AI야, 새로 만들기 전에 먼저 생각해.&lt;br /&gt;이 기능이 정말 필요한가?&lt;br /&gt;표준 라이브러리에 이미 있지 않은가?&lt;br /&gt;브라우저나 플랫폼 기본 기능으로 충분하지 않은가?&lt;br /&gt;이미 설치된 의존성으로 해결되지 않는가?&lt;br /&gt;한 줄로 끝낼 수 있지 않은가?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 AI에게 이 질문 순서를 강제로 떠올리게 만든다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ponytail의 핵심 철학: 게으르지만 무책임하지 않게&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail의 핵심 규칙은 매우 단순하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 기능이 정말 존재해야 하는가?&lt;/li&gt;
&lt;li&gt;표준 라이브러리가 이미 해결하는가?&lt;/li&gt;
&lt;li&gt;플랫폼 기본 기능으로 가능한가?&lt;/li&gt;
&lt;li&gt;이미 설치된 의존성으로 해결 가능한가?&lt;/li&gt;
&lt;li&gt;한 줄로 가능한가?&lt;/li&gt;
&lt;li&gt;그래도 안 되면 최소한의 코드만 작성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 순서는 Ponytail 문서에서 &amp;ldquo;ladder&amp;rdquo;, 즉 사다리처럼 설명된다. 에이전트는 코드를 작성하기 전에 가장 위의 단계부터 확인하고, 해결 가능한 단계가 나오면 거기서 멈춰야 한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 날짜 선택 기능이 필요하다고 해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 AI 에이전트는 date picker 라이브러리를 설치하고, React wrapper를 만들고, 스타일 파일을 만들고, 타임존 이야기를 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail이 적용된 에이전트는 먼저 이렇게 생각한다.&lt;/p&gt;
&lt;pre class=&quot;fsharp&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;date&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 이미 지원하는 기능이면 굳이 새 컴포넌트를 만들 필요가 없다는 것이다. README도 날짜 선택기 예시에서 이 방향을 직접 보여준다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/tree/main&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 Ponytail이 &amp;ldquo;짧은 코드&amp;rdquo; 자체를 숭배하지 않는다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에는 입력 검증, 데이터 손실 방지용 에러 처리, 보안, 접근성, 하드웨어 보정 같은 요소는 절대 줄이면 안 된다고 되어 있다. 즉 Ponytail이 추구하는 것은 코드 골프가 아니다. &lt;b&gt;필요 없는 코드를 없애되, 필요한 안전장치는 남기는 것&lt;/b&gt;이다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/skills/ponytail/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이런 도구가 필요해졌나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트의 장점은 빠르게 코드를 만들어 준다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 바로 그 장점 때문에 문제가 생긴다.&lt;br /&gt;AI는 사람보다 코드를 쓰는 비용이 낮다. 몇 초 만에 파일을 여러 개 만들 수 있고, 수십 줄의 보일러플레이트도 부담 없이 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 개발자는 귀찮아서라도 이렇게 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이거 그냥 기본 함수 쓰면 안 되나?&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 추상화 지금 꼭 필요해?&amp;rdquo;&lt;br /&gt;&amp;ldquo;설정 파일까지 만들 일인가?&amp;rdquo;&lt;br /&gt;&amp;ldquo;나중에 필요하면 그때 만들자.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 AI는 종종 &amp;ldquo;완성도 있어 보이는 구조&amp;rdquo;를 선호한다. 문제는 완성도 있어 보이는 구조가 실제로는 유지보수 비용을 늘린다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 바로 이 지점을 건드린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 더 많은 코드를 쓰게 하는 것이 아니라, &lt;b&gt;덜 만들고도 충분히 작동하는 선택지를 먼저 찾게 만드는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조적으로 보면 Ponytail은 &amp;ldquo;에이전트 이식형 스킬 배포판&amp;rdquo;이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail의 재미있는 점은 단일 도구에 묶이지 않는다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소에는 Claude Code용 플러그인, Codex용 플러그인, OpenCode용 플러그인, Gemini CLI 확장, Cursor/Windsurf/Cline용 규칙 파일, GitHub Copilot 지침 파일, Kiro steering 파일, AGENTS.md 등이 함께 들어 있다. GitHub 페이지의 파일 구조에서도 .claude-plugin, .codex-plugin, .cursor/rules, .windsurf/rules, .clinerules, .kiro/steering, skills, commands, hooks 같은 폴더가 확인된다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/tree/main&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 의미하는 바는 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 특정 AI 에이전트 하나를 위한 팁 모음이 아니다.&lt;br /&gt;AI 코딩 에이전트들이 공통으로 받아들일 수 있는 &lt;b&gt;작업 철학 패키지&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 규칙은 skills/ponytail/SKILL.md와 AGENTS.md에 들어 있고, 각 도구별 어댑터가 그것을 자기 환경에 맞게 불러온다. 문서에서는 &amp;ldquo;어댑터는 얇게 유지하고, 가능한 기존 skills와 hooks를 가리키라&amp;rdquo;고 설명한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/docs/agent-portability.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점은 앞으로 AI 개발 환경에서 중요해질 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 더 이상 &amp;ldquo;어떤 모델을 쓰느냐&amp;rdquo;만 고민하지 않는다.&lt;br /&gt;이제는 &lt;b&gt;에이전트에게 어떤 개발 습관을 지속적으로 주입할 것인가&lt;/b&gt;가 중요해지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 그 습관 중 하나를 매우 선명하게 정의한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 명령어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 단순한 규칙 파일만 제공하지 않는다. 스킬 지원이 되는 호스트에서는 명령어도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README에 따르면 주요 명령은 다음과 같다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;/ponytail [lite | full | ultra | off]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail의 강도를 조절한다.&lt;br /&gt;기본값은 full이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/ponytail-review
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 diff에서 과잉 구현된 부분을 찾아준다.&lt;br /&gt;목표는 &amp;ldquo;무엇을 더할까&amp;rdquo;가 아니라 &amp;ldquo;무엇을 지울 수 있을까&amp;rdquo;다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/ponytail-audit
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 변경분이 아니라 전체 저장소 기준으로 과잉 설계를 점검한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/ponytail-debt
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ponytail: 주석으로 남긴 의도적 단순화, 임시 선택, 나중에 확장할 지점을 모아서 관리한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;/ponytail-gain
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 공개한 벤치마크 기반으로 코드량, 비용, 속도 개선 수치를 보여주는 명령이다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;/ponytail-help
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 도움말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 /ponytail-review는 실무적으로 유용해 보인다.&lt;br /&gt;일반 코드 리뷰가 correctness, security, performance를 본다면, Ponytail review는 오직 &amp;ldquo;이 코드가 불필요하게 복잡한가&amp;rdquo;만 본다. 스킬 문서에서도 이 리뷰의 범위는 과잉 설계와 복잡도에 한정되며, 보안 구멍이나 correctness 문제는 일반 리뷰로 넘기라고 명시한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/skills/ponytail-review/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ponytail이 주장하는 성능 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Unverified] Ponytail README는 자체 벤치마크 결과를 공개하고 있다. 프로젝트 측 설명에 따르면, 실제 Claude Code 세션에서 FastAPI + React 오픈소스 저장소를 수정하게 하고, Ponytail 적용 전후의 git diff를 비교했다. 조건은 Haiku 4.5, 12개 기능 작업, 각 작업 n=4였다고 설명한다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/tree/main&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 공개한 평균 결과는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 기준Ponytail 결과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;추가 코드 라인&lt;/td&gt;
&lt;td&gt;-54%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;토큰&lt;/td&gt;
&lt;td&gt;-22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비용&lt;/td&gt;
&lt;td&gt;-20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간&lt;/td&gt;
&lt;td&gt;-27%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전성 테스트&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README에는 Ponytail이 no-skill baseline 대비 LOC, token, cost, time을 모두 줄였고, 안전성 기준도 유지했다고 적혀 있다. 다만 이 수치는 프로젝트가 공개한 자체 벤치마크이며, 외부 독립 검증 결과로 보면 안 된다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 예시는 date picker와 color picker다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 측 벤치마크 문서에 따르면, date picker 작업에서 baseline은 평균 404줄을 만들었고 Ponytail은 23줄을 만들었다. color picker도 baseline 287줄, Ponytail 23줄로 측정되었다. 이유는 단순하다. baseline은 커스텀 컴포넌트를 만들었고, Ponytail은 브라우저 기본 input을 먼저 선택했다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/blob/main/benchmarks/results/2026-06-18-agentic.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 backend CRUD처럼 원래부터 줄일 여지가 작은 작업에서는 차이가 거의 없었다고 문서가 설명한다. 이 지점이 오히려 중요하다. Ponytail은 모든 코드를 마법처럼 줄이는 도구가 아니라, &lt;b&gt;과잉 구현이 발생하기 쉬운 지점에서 효과가 커지는 도구&lt;/b&gt;에 가깝다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/blob/main/benchmarks/results/2026-06-18-agentic.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단순히 &amp;ldquo;짧게 써&amp;rdquo;라고 말하는 것과 무엇이 다른가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 보면 이런 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;그냥 프롬프트에 YAGNI 원칙을 따르고 한 줄을 선호하라고 쓰면 되는 것 아닌가?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail 벤치마크 문서는 이 질문을 직접 다룬다. 비교군에는 &amp;ldquo;YAGNI + one-liners&amp;rdquo; 짧은 프롬프트도 들어 있었다. 프로젝트 측 결과에 따르면 이 짧은 프롬프트는 일부 작업에서는 잘 작동했지만, 작업에 따라 일관성이 떨어졌고 안전성 테스트에서 한 번 guard를 놓쳤다고 설명한다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/blob/main/benchmarks/results/2026-06-18-agentic.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Ponytail의 차별점은 세 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 단순히 짧게 쓰라는 지시가 아니라 &lt;b&gt;판단 순서&lt;/b&gt;를 제공한다.&lt;br /&gt;필요성 &amp;rarr; 표준 라이브러리 &amp;rarr; 플랫폼 기본 기능 &amp;rarr; 기존 의존성 &amp;rarr; 한 줄 &amp;rarr; 최소 구현이라는 순서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;줄이면 안 되는 영역&lt;/b&gt;을 명확히 정한다.&lt;br /&gt;입력 검증, 보안, 접근성, 데이터 손실 방지, 하드웨어 보정은 &amp;ldquo;게으름&amp;rdquo;의 대상이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 여러 에이전트 환경에 지속적으로 적용할 수 있다.&lt;br /&gt;한 번 프롬프트에 쓰고 끝나는 방식이 아니라, 플러그인&amp;middot;스킬&amp;middot;규칙 파일 형태로 세션과 프로젝트에 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이가 Ponytail을 단순 프롬프트 팁이 아니라 &amp;ldquo;AI 개발 워크플로우 규칙 세트&amp;rdquo;로 보게 만드는 부분이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 사용하는 에이전트에 따라 설치 방식이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code에서는 README 기준으로 다음 명령을 사용한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/plugin marketplace add DietrichGebert/ponytail
/plugin install ponytail@ponytail
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex에서는 다음 방식이 제시되어 있다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;codex plugin marketplace add DietrichGebert/ponytail
codex
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 뒤 /plugins에서 Ponytail marketplace를 선택해 설치하고, /hooks에서 lifecycle hook을 확인하고 trust한 뒤 새 thread를 시작하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor, Windsurf, Cline, GitHub Copilot editor, Aider, Kiro, Zed, CodeWhale 같은 환경은 완전한 명령어 플러그인 방식이 아니라, 각 도구가 읽을 수 있는 규칙 파일을 복사해 사용하는 형태에 가깝다. README는 .cursor/rules, .windsurf/rules, .clinerules, .github/copilot-instructions.md, AGENTS.md, .kiro/steering 등을 언급한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/README.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Ponytail은 크게 두 층으로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 &lt;b&gt;풀 플러그인 모드&lt;/b&gt;다.&lt;br /&gt;Claude Code, Codex, OpenCode, Gemini CLI처럼 명령어와 hook을 지원하는 환경에서 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 하나는 &lt;b&gt;instruction-only 모드&lt;/b&gt;다.&lt;br /&gt;Cursor, Windsurf, Copilot editor처럼 프로젝트 규칙 파일을 읽는 도구에 Ponytail 규칙을 넣는 방식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에서 어디에 쓸 수 있을까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail이 특히 잘 맞는 상황은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;b&gt;AI 에이전트로 MVP를 빠르게 만들 때&lt;/b&gt;다.&lt;br /&gt;MVP에서 가장 위험한 것은 기능이 부족한 것이 아니라, 검증되지 않은 아이디어에 과한 구조를 먼저 얹는 것이다. Ponytail은 &amp;ldquo;일단 필요한 만큼만 만들자&amp;rdquo;는 방향을 유지하게 해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;프론트엔드 컴포넌트 과잉 생성을 줄일 때&lt;/b&gt;다.&lt;br /&gt;날짜 선택, 색상 선택, 파일 업로드, 기본 폼 입력처럼 브라우저 기본 기능이 있는 영역에서 AI는 종종 불필요한 커스텀 컴포넌트를 만든다. Ponytail은 이런 작업에서 native feature를 먼저 검토하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, &lt;b&gt;레거시 코드베이스 리팩터링 전에 삭제 후보를 찾을 때&lt;/b&gt;다.&lt;br /&gt;/ponytail-audit나 /ponytail-review는 &amp;ldquo;무엇을 고칠까&amp;rdquo;보다 &amp;ldquo;무엇을 지울까&amp;rdquo;에 초점을 맞춘다. 기능 추가보다 코드 축소가 먼저 필요한 팀에 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넷째, &lt;b&gt;개인 개발자가 AI 코딩 도구를 쓸 때 기본 습관으로 붙이기 좋다&lt;/b&gt;.&lt;br /&gt;AI에게 매번 &amp;ldquo;간단하게 해줘&amp;rdquo;, &amp;ldquo;과하게 만들지 마&amp;rdquo;라고 말하는 대신, 프로젝트 규칙으로 상시 적용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하지만 Ponytail을 맹신하면 안 된다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail의 방향은 좋지만, 모든 상황에서 정답은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 요구사항이 이미 명확한 서비스, 보안&amp;middot;금융&amp;middot;의료처럼 규제가 강한 도메인, 장기적으로 확장될 아키텍처를 설계해야 하는 상황에서는 &amp;ldquo;최소 구현&amp;rdquo;이 오히려 부족할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail 문서도 안전성을 완전히 증명한다고 주장하지는 않는다. 벤치마크 문서는 한 모델, 제한된 작업 수, deterministic safety check라는 한계를 직접 적고 있다. 특히 안전성 테스트는 특정 guard를 놓치는지 보는 수준이지, 전체 보안을 증명하는 것은 아니다. (&lt;a href=&quot;https://github.com/DietrichGebert/ponytail/blob/main/benchmarks/results/2026-06-18-agentic.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Ponytail은 &amp;ldquo;AI가 짧게 만들었으니 안전하다&amp;rdquo;는 보증 장치가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 위치는 이렇다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 AI 코딩 에이전트의 과잉 구현 성향을 줄이는 규칙 세트다.&lt;br /&gt;하지만 최종 설계 판단, 보안 검토, 비즈니스 요구사항 검증은 여전히 사람의 몫이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발팀에 적용한다면 이렇게 쓰는 것이 좋다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 단위로 Ponytail을 도입한다면 처음부터 전체 코드베이스에 무작정 적용하기보다 다음 순서가 적절하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계는 개인 개발 환경에서 적용하는 것이다.&lt;br /&gt;Claude Code나 Codex를 쓰는 개발자가 자기 작업 branch에서 Ponytail을 켜고, 생성되는 diff가 실제로 줄어드는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계는 PR 리뷰 보조로 쓰는 것이다.&lt;br /&gt;/ponytail-review를 사용해 &amp;ldquo;삭제 가능한 코드&amp;rdquo;, &amp;ldquo;표준 라이브러리로 대체 가능한 코드&amp;rdquo;, &amp;ldquo;native feature로 충분한 코드&amp;rdquo;를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3단계는 팀 컨벤션으로 정리하는 것이다.&lt;br /&gt;&amp;ldquo;새 의존성을 추가하기 전에 표준 라이브러리와 플랫폼 기능을 먼저 검토한다&amp;rdquo;, &amp;ldquo;한 구현체뿐인 interface는 만들지 않는다&amp;rdquo;, &amp;ldquo;추상화는 두 번째 사례가 생긴 뒤 만든다&amp;rdquo; 같은 규칙으로 문서화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단계는 예외 기준을 명확히 하는 것이다.&lt;br /&gt;보안, 접근성, 데이터 손실 방지, 명시 요구사항, 테스트 가능한 핵심 로직은 줄이지 않는다는 기준이 필요하다. Ponytail 자체도 이 영역은 lazy 대상이 아니라고 명시한다. (&lt;a href=&quot;https://raw.githubusercontent.com/DietrichGebert/ponytail/main/skills/ponytail/SKILL.md&quot;&gt;GitHub&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ponytail이 보여주는 더 큰 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 작은 프로젝트처럼 보이지만, AI 개발 도구의 중요한 변화를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 AI 코딩 도구의 관심은 &amp;ldquo;얼마나 많은 코드를 생성할 수 있는가&amp;rdquo;였다.&lt;br /&gt;이제 관심은 조금씩 바뀌고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;얼마나 적절한 코드를 생성하는가.&amp;rdquo;&lt;br /&gt;&amp;ldquo;얼마나 불필요한 코드를 만들지 않는가.&amp;rdquo;&lt;br /&gt;&amp;ldquo;기존 플랫폼 기능을 얼마나 잘 활용하는가.&amp;rdquo;&lt;br /&gt;&amp;ldquo;사람 개발자의 유지보수 감각을 얼마나 잘 흉내 내는가.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 이 흐름의 상징적인 사례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 많이 쓰는 시대에는 역설적으로 &lt;b&gt;코드를 덜 쓰게 만드는 기술&lt;/b&gt;이 필요해진다.&lt;br /&gt;그리고 그 방향은 단순히 &amp;ldquo;짧게 작성하라&amp;rdquo;가 아니다.&lt;br /&gt;필요성, 표준 기능, 플랫폼 기능, 기존 의존성, 안전장치 사이에서 올바른 우선순위를 잡는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: Ponytail은 AI 시대의 YAGNI 엔진이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail을 한 문장으로 정리하면 이렇게 말할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트에게 YAGNI, 표준 라이브러리 우선, native feature 우선, 최소 diff 우선이라는 시니어 개발자의 습관을 주입하는 스킬 세트.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트의 가치는 &amp;ldquo;코드를 줄인다&amp;rdquo;는 숫자 자체에만 있지 않다.&lt;br /&gt;더 중요한 가치는 AI 에이전트에게 &lt;b&gt;무엇을 만들지 않을지 판단하게 만드는 방식&lt;/b&gt;에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 시대에는 누구나 빠르게 코드를 만들 수 있다.&lt;br /&gt;그래서 앞으로 더 중요한 능력은 &amp;ldquo;빨리 만드는 능력&amp;rdquo;이 아니라 &amp;ldquo;안 만들어도 되는 것을 구분하는 능력&amp;rdquo;이 될 가능성이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ponytail은 바로 그 능력을 AI 에이전트에게 이식하려는 시도다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 시도는 꽤 현실적인 문제를 건드린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 코드를 너무 많이 쓰는 시대.&lt;br /&gt;어쩌면 우리에게 필요한 것은 더 강한 코딩 에이전트가 아니라, 더 게으른 시니어 개발자의 감각일지도 모른다.&lt;/p&gt;</description>
      <category>AI/추천 오픈소스</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1780</guid>
      <comments>https://javaexpert.tistory.com/1780#entry1780comment</comments>
      <pubDate>Fri, 19 Jun 2026 10:56:52 +0900</pubDate>
    </item>
    <item>
      <title>Codex의 Computer use 사용법 3가지</title>
      <link>https://javaexpert.tistory.com/1779</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Computer Use, Chrome, Browser는 언제 어떻게 써야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 코드를 짜는 시대는 이미 익숙해졌다.&lt;br /&gt;그런데 이제 중요한 질문은 조금 달라졌다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;AI가 코드를 쓰는 것을 넘어, 실제 컴퓨터 화면을 보고 조작할 수 있다면 무엇이 달라질까?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenAI Codex에는 컴퓨터를 다루는 방식이 여러 가지 있다.&lt;br /&gt;겉으로 보면 비슷해 보인다. 모두 웹사이트를 열 수 있고, 화면을 볼 수 있고, 사용자를 대신해 작업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 목적이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex가 컴퓨터를 사용하는 대표적인 방식은 크게 세 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Computer Use&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Chrome Extension&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;In-app Browser&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 추가로 &lt;b&gt;Appshots&lt;/b&gt;라는 기능이 있다.&lt;br /&gt;Appshots는 Codex가 직접 조작하는 도구라기보다, 사용자가 보고 있는 화면을 Codex에게 &amp;ldquo;이거 봐&amp;rdquo; 하고 보여주는 방식에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 세 가지 방식의 차이와 실제 활용법을 정리해본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Computer Use: 가장 넓은 범위의 컴퓨터 조작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Computer Use&lt;/b&gt;는 세 가지 중 가장 넓은 조작 범위를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex가 macOS나 Windows의 그래픽 인터페이스를 보고, 창을 열고, 메뉴를 누르고, 키보드 입력을 하고, 클립보드를 사용할 수 있게 해주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 사용자가 허용한 앱 안에서 Codex가 사람처럼 컴퓨터를 조작하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 작업에 적합하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spotify 같은 데스크톱 앱 조작&lt;/li&gt;
&lt;li&gt;Xcode나 iOS 시뮬레이터 테스트&lt;/li&gt;
&lt;li&gt;iPhone Mirroring을 통한 모바일 앱 확인&lt;/li&gt;
&lt;li&gt;시스템 설정 변경 확인&lt;/li&gt;
&lt;li&gt;API가 없는 금융 앱, 업무 앱, 내부 프로그램 사용&lt;/li&gt;
&lt;li&gt;여러 앱을 오가야 하는 복합 워크플로우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Computer Use의 장점은 명확하다.&lt;br /&gt;API나 플러그인이 없는 앱도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 단점도 있다.&lt;br /&gt;화면을 보고, 클릭하고, 반응을 기다리고, 다시 화면을 확인하는 방식이기 때문에 구조화된 API 호출보다 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 GitHub 플러그인이 있다면 GitHub 웹사이트를 눈으로 보며 클릭하는 것보다 플러그인을 쓰는 편이 더 정확하고 검토하기 쉽다. Slack 스레드를 읽는 것도 마찬가지다. Slack 플러그인이 있다면 화면을 클릭하는 것보다 API 기반으로 가져오는 편이 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기본 원칙은 이렇다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 도구가 있으면 먼저 그것을 쓰고, 화면 조작은 마지막 경계에서 쓴다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Computer Use는 &amp;ldquo;사람만 할 수 있던 GUI 작업&amp;rdquo;에 강하다.&lt;br /&gt;하지만 사용자의 컴퓨터를 넓게 다룰 수 있는 만큼 신뢰 경계도 넓다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 계정, 결제, 금융, 개인정보, 보안 설정과 관련된 작업은 사용자가 직접 확인하는 절차를 두는 것이 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Chrome Extension: 로그인된 브라우저 상태가 필요할 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 방식은 &lt;b&gt;Codex Chrome Extension&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 사용자의 로그인된 Chrome 상태를 사용할 수 있다는 점이 핵심이다.&lt;br /&gt;쿠키, 계정 세션, 브라우저 프로필, 열려 있는 탭 등이 필요한 작업에 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 경우다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gmail 확인&lt;/li&gt;
&lt;li&gt;LinkedIn 메시지 확인&lt;/li&gt;
&lt;li&gt;Salesforce나 고객지원 콘솔 사용&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 data-ke-size=&quot;size16&quot;&gt;Computer Use도 브라우저를 조작할 수는 있다.&lt;br /&gt;하지만 Chrome Extension은 브라우저 작업을 더 자연스럽게 이해한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 여러 탭을 다루는 작업에 유리하다.&lt;br /&gt;예를 들어 한 탭에는 고객 계정 페이지가 있고, 다른 탭에는 고객지원 티켓이 있고, 또 다른 탭에는 내부 문서가 열려 있다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Chrome Extension은 같은 작업에 필요한 여러 탭을 묶어 비교하고, 필요한 정보를 찾아서 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 프롬프트는 이런 식이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Chrome을 사용해서 열린 고객 계정 페이지와 다른 탭의 지원 티켓을 비교하고, 누락된 필드를 초안으로 작성해줘. 제출은 하지 마.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 부분은 마지막 문장이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;제출은 하지 마.&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chrome Extension은 사용자의 로그인 상태로 동작한다.&lt;br /&gt;즉, Codex가 클릭하는 버튼이나 작성하는 메시지는 웹사이트 입장에서는 사용자가 직접 한 행동처럼 보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자동화의 경계를 명확히 해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조사하기&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 data-ke-size=&quot;size16&quot;&gt;여기까지는 자동화하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음 작업은 사용자의 확인을 거치는 편이 안전하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보내기&lt;/li&gt;
&lt;li&gt;게시하기&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 data-ke-size=&quot;size16&quot;&gt;Chrome Extension은 &amp;ldquo;로그인된 웹 업무&amp;rdquo;에 가장 적합한 표면이다.&lt;br /&gt;작업 전체가 브라우저 안에서 끝난다면 Computer Use보다 Chrome을 먼저 고려하는 것이 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. In-app Browser: 내가 만들고 있는 웹사이트를 테스트할 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 방식은 &lt;b&gt;In-app Browser&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 Codex 앱 안에 들어 있는 브라우저다.&lt;br /&gt;사용자와 Codex가 같은 렌더링 화면을 보면서 작업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 잘 맞는 용도는 웹 개발이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 작업이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 개발 서버 확인&lt;/li&gt;
&lt;li&gt;Vite, Next.js, React 앱 테스트&lt;/li&gt;
&lt;li&gt;로그인 없는 공개 페이지 확인&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 data-ke-size=&quot;size16&quot;&gt;In-app Browser의 핵심 특징은 &lt;b&gt;격리&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Chrome 프로필을 쓰지 않는다.&lt;br /&gt;쿠키, 로그인 세션, 확장 프로그램, 기존 탭을 공유하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 단점이기도 하고 장점이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인이 필요한 서비스에는 부적합하다.&lt;br /&gt;Google 로그인, 패스키, 브라우저 확장 프로그램 의존성이 있는 페이지라면 Chrome Extension이 더 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개발 중인 웹앱을 테스트할 때는 오히려 좋다.&lt;br /&gt;불필요한 계정 상태나 확장 프로그램의 영향을 덜 받기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 프롬프트는 이렇게 쓸 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Browser로 localhost:3000을 열고 모바일 화면에서 overflow 버그를 재현해줘. 수정한 뒤 데스크톱과 모바일 화면에서 다시 확인해줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 피드백 루프가 짧다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex가 코드를 수정한다.&lt;br /&gt;브라우저에서 결과를 본다.&lt;br /&gt;스크린샷을 확인한다.&lt;br /&gt;다시 코드를 고친다.&lt;br /&gt;다시 렌더링을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 한 스레드 안에서 반복할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 디자인 작업에서 유용하다.&lt;br /&gt;텍스트로 &amp;ldquo;좀 더 깔끔하게 해줘&amp;rdquo;라고 말하는 것보다, 실제 화면의 특정 영역을 클릭하고 &amp;ldquo;여기 계층이 이상해&amp;rdquo;, &amp;ldquo;이 카드 느낌을 줄여줘&amp;rdquo;, &amp;ldquo;이 버튼 간격을 넓혀줘&amp;rdquo;라고 남기는 편이 훨씬 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 화면 자체가 요구사항 문서가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Appshots: 조작 도구가 아니라 &amp;ldquo;가리키는 도구&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appshots는 Computer Use, Chrome, Browser와 조금 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 세 가지가 Codex가 무언가를 조작하는 방식이라면, Appshots는 사용자가 보고 있는 화면을 Codex에게 전달하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac에서는 특정 단축키로 현재 앞에 있는 창을 캡처해 Codex 스레드에 첨부할 수 있다.&lt;br /&gt;이미지와 가능한 텍스트 정보가 함께 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 상황에서 유용하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에러 메시지를 보여줄 때&lt;/li&gt;
&lt;li&gt;이메일 내용을 설명할 때&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 data-ke-size=&quot;size16&quot;&gt;정리하면 이렇게 볼 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appshots는 &amp;ldquo;이걸 봐&amp;rdquo;라고 가리키는 방식이고,&lt;br /&gt;Browser, Chrome, Computer Use는 &amp;ldquo;이걸 처리해&amp;rdquo;라고 맡기는 방식이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Appshots는 특히 맥락 전달에 좋다.&lt;br /&gt;전체 데스크톱을 열어주는 것이 아니라 현재 창 중심으로 보여줄 수 있기 때문에 비교적 좁은 범위의 컨텍스트를 전달할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 가지 방식은 어떻게 구분하면 될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 다음 기준으로 선택하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. API나 플러그인이 있으면 먼저 그것을 쓴다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 좋은 자동화는 화면 클릭이 아니라 구조화된 도구 호출이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 메시지를 읽어야 한다면 Slack 플러그인이나 MCP가 더 낫다.&lt;br /&gt;GitHub 작업을 해야 한다면 GitHub 플러그인이 더 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 간단하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 빠르다&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 data-ke-size=&quot;size16&quot;&gt;화면 조작은 구조화된 도구가 닿지 않는 마지막 구간에서 쓰는 것이 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 데스크톱 앱이면 Computer Use&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 대상이 브라우저 밖이라면 Computer Use가 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Spotify 앱을 열거나, Xcode를 조작하거나, iOS 시뮬레이터에서 버그를 재현해야 한다면 Computer Use를 쓰는 것이 자연스럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 범위가 넓기 때문에 한 번에 너무 많은 권한을 주기보다, 하나의 앱 또는 하나의 흐름으로 좁혀서 맡기는 것이 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 로그인된 웹 업무면 Chrome&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gmail, LinkedIn, Salesforce, 사내 관리자 페이지처럼 로그인 상태가 필요한 웹 작업은 Chrome Extension이 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 여러 탭을 비교해야 하는 업무라면 Chrome이 강하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 로그인된 상태로 행동하는 만큼, 자동 제출이나 자동 전송은 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 지시 방식은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조사하고, 비교하고, 초안까지 작성해줘.&lt;br /&gt;하지만 보내기, 제출, 결제는 하지 마.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 웹앱 개발과 UI 디버깅은 In-app Browser&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만들고 있는 웹사이트를 열어보고, 버그를 재현하고, 레이아웃을 점검하고, 디자인 피드백을 반영해야 한다면 In-app Browser가 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 로컬 서버와 궁합이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 &amp;ldquo;코드를 고치고 &amp;rarr; 화면을 확인하고 &amp;rarr; 다시 수정하는&amp;rdquo; 반복 작업을 많이 한다.&lt;br /&gt;In-app Browser는 이 루프를 Codex 안으로 가져온다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 프롬프트 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Computer Use 예시&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Computer를 사용해서 Spotify를 열고 Discover Weekly 플레이리스트를 찾아 재생해줘. 계정 설정이나 구독 설정은 변경하지 마.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Chrome 예시&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Chrome을 사용해서 열린 고객 계정 페이지와 지원 티켓을 비교해줘. 누락된 정보를 정리하고 입력 초안을 만들어줘. 제출은 하지 마.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;In-app Browser 예시&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Browser로 localhost:3000을 열고 모바일 화면에서 레이아웃이 깨지는 부분을 찾아줘. 수정 후 모바일과 데스크톱 화면에서 다시 검증해줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Appshots 예시&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 에러 화면을 보고 원인을 분석해줘. 가능한 해결 순서를 알려줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심은 &amp;ldquo;가장 좁은 표면&amp;rdquo;을 선택하는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트에게 많은 권한을 주는 것이 항상 좋은 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 중요한 원칙은 반대다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 만큼만 보여주고, 필요한 만큼만 조작하게 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 안에서 끝나는 일이라면 Computer Use까지 열 필요가 없다.&lt;br /&gt;로그인이 필요 없는 개발 테스트라면 Chrome 프로필을 쓸 필요가 없다.&lt;br /&gt;API로 가능한 일이라면 화면 클릭을 시킬 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원칙을 지키면 자동화는 더 빠르고, 더 안전하고, 더 검토 가능해진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리: Codex는 코딩 도구를 넘어 작업 표면을 다루는 에이전트가 되고 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 AI 코딩 도구는 주로 코드 편집기 안에서 일했다.&lt;br /&gt;파일을 읽고, 코드를 고치고, 테스트를 실행하는 방식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Computer Use, Chrome Extension, In-app Browser가 결합되면 Codex의 역할은 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Codex는 코드만 보는 것이 아니라, 실제 앱 화면과 브라우저, 로컬 개발 서버, 로그인된 업무 도구까지 연결해 작업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 중요한 것은 도구를 많이 쓰는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 상황에 맞는 표면을 고르는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데스크톱 앱은 &lt;b&gt;Computer Use&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;로그인된 웹 업무는 &lt;b&gt;Chrome&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;웹앱 개발과 UI 디버깅은 &lt;b&gt;In-app Browser&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;현재 보고 있는 화면을 전달할 때는 &lt;b&gt;Appshots&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;API나 플러그인이 있으면 먼저 &lt;b&gt;구조화된 도구&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구분만 명확히 해도 Codex를 훨씬 안정적으로 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 시대의 생산성은 단순히 &amp;ldquo;프롬프트를 잘 쓰는 능력&amp;rdquo;에서 끝나지 않는다.&lt;br /&gt;이제는 어떤 작업을 어떤 표면에 맡길지 설계하는 능력이 중요해지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex를 잘 쓰는 사람은 단순히 명령을 잘 내리는 사람이 아니다.&lt;br /&gt;작업의 경계, 권한의 범위, 검토 지점을 설계할 줄 아는 사람이다.&lt;/p&gt;</description>
      <category>AI</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1779</guid>
      <comments>https://javaexpert.tistory.com/1779#entry1779comment</comments>
      <pubDate>Wed, 17 Jun 2026 12:04:29 +0900</pubDate>
    </item>
    <item>
      <title>[OpenHands] 자율 AI 에이전트</title>
      <link>https://javaexpert.tistory.com/1778</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4MNUH/dJMcabEzbzy/XFQKZhReQZ0KIWqzuwT04K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4MNUH/dJMcabEzbzy/XFQKZhReQZ0KIWqzuwT04K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4MNUH/dJMcabEzbzy/XFQKZhReQZ0KIWqzuwT04K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4MNUH%2FdJMcabEzbzy%2FXFQKZhReQZ0KIWqzuwT04K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1122&quot; height=&quot;1402&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;1402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 개발자 도구의 흐름은 빠르게 바뀌고 있다.&lt;br /&gt;처음에는 AI가 코드 한 줄을 자동완성해 주는 수준이었다. 그다음에는 채팅창에 물어보면 함수나 컴포넌트를 만들어 주는 수준으로 발전했다. 그런데 이제는 한 단계 더 나아가고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 버그 고쳐줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이슈 보고 원인 찾아서 PR 만들어줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;레거시 코드를 리팩터링해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;테스트 깨지는 이유를 찾아줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 요청을 AI에게 단순히 묻는 것이 아니라, 실제 개발 환경 안에서 실행하게 만드는 방향이다.&lt;br /&gt;OpenHands는 바로 이 흐름을 대표하는 오픈소스 프로젝트다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. OpenHands는 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 AI 기반 소프트웨어 개발 에이전트 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, ChatGPT나 Claude에게 코드 질문을 던지는 수준을 넘어, AI가 직접 개발 작업을 수행할 수 있도록 만든 도구다. OpenHands 에이전트는 코드베이스를 읽고, 파일을 수정하고, 터미널 명령을 실행하고, 필요하면 웹을 탐색하고, GitHub 이슈나 PR 흐름 안에서 작업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 AI 코딩 도구가 &amp;ldquo;옆에서 도와주는 조수&amp;rdquo;에 가까웠다면, OpenHands는 &amp;ldquo;작업을 맡길 수 있는 주니어 개발자형 에이전트&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 여기서 중요한 전제가 있다.&lt;br /&gt;AI가 모든 개발을 완벽하게 대신한다는 뜻은 아니다. OpenHands의 핵심 가치는 &amp;ldquo;개발자의 판단을 없애는 것&amp;rdquo;이 아니라, 반복적이고 시간이 많이 드는 개발 작업을 에이전트에게 넘기고, 사람은 설계&amp;middot;검토&amp;middot;의사결정에 집중하게 만드는 데 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 OpenHands가 주목받는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands가 흥미로운 이유는 단순히 &amp;ldquo;AI가 코드를 짜준다&amp;rdquo;는 데 있지 않다. 이미 그런 도구는 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 세 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, OpenHands는 오픈소스 기반이다.&lt;br /&gt;많은 AI 코딩 도구는 특정 회사의 모델, 특정 IDE, 특정 클라우드 환경에 강하게 묶여 있다. 반면 OpenHands는 오픈소스 프로젝트로 공개되어 있고, 개발자가 직접 구조를 보고 수정하고 확장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 모델에 비교적 독립적인 구조를 지향한다.&lt;br /&gt;OpenHands는 Claude, OpenAI 계열 모델, Gemini, 오픈소스 모델 등 여러 LLM을 사용할 수 있는 방향으로 설계되어 있다. AI 모델 시장은 빠르게 변하기 때문에, 특정 모델 하나에 묶이지 않는 구조는 실무적으로 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 단순 채팅이 아니라 &amp;ldquo;실행 환경&amp;rdquo;을 포함한다.&lt;br /&gt;AI 개발 에이전트에서 가장 중요한 것은 말솜씨가 아니다. 실제 파일을 수정하고, 테스트를 돌리고, 에러 로그를 보고, 다시 고치고, 결과를 남길 수 있어야 한다. OpenHands는 이 실행 루프를 중심에 둔다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. OpenHands의 구성: SDK, CLI, GUI, Cloud&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 하나의 앱이라기보다 여러 층으로 구성된 플랫폼에 가깝다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Software Agent SDK&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 핵심은 OpenHands Software Agent SDK다.&lt;br /&gt;이 SDK는 개발자가 직접 코드 안에서 소프트웨어 개발 에이전트를 만들 수 있게 해주는 Python/REST API 기반 도구다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 일을 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 저장소의 README를 자동 생성하는 에이전트&lt;/li&gt;
&lt;li&gt;의존성 업데이트를 자동으로 처리하는 에이전트&lt;/li&gt;
&lt;li&gt;대규모 리팩터링을 여러 단계로 나눠 수행하는 에이전트&lt;/li&gt;
&lt;li&gt;사내 개발 워크플로우에 맞춘 커스텀 코드 리뷰 에이전트&lt;/li&gt;
&lt;li&gt;OpenAI 호환 API 형태로 IDE나 내부 서비스에 붙이는 에이전트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, SDK는 OpenHands의 엔진이다.&lt;br /&gt;CLI, GUI, Cloud 같은 상위 제품들은 이 엔진 위에서 동작하는 인터페이스라고 보면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) CLI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands CLI는 터미널에서 바로 에이전트를 실행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 자연어로 작업을 입력하고, 에이전트가 어떤 작업을 수행하는지 실시간으로 확인할 수 있다. 예를 들어 다음과 같은 식이다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;openhands -t &quot;Create a REST API for user management&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 Claude Code나 Codex CLI 같은 도구를 써본 개발자에게 익숙하다.&lt;br /&gt;특히 터미널 중심으로 일하는 개발자라면 OpenHands CLI가 가장 빠른 진입점이 될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Local GUI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local GUI는 브라우저 기반 화면에서 OpenHands를 실행하는 방식이다.&lt;br /&gt;Docker 기반으로 로컬 환경에서 실행할 수 있고, 웹 UI를 통해 에이전트와 상호작용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 터미널 명령보다 시각적인 흐름을 선호하는 개발자에게 적합하다.&lt;br /&gt;에이전트가 어떤 파일을 보고 있는지, 어떤 작업을 진행 중인지, 어떤 결과를 냈는지 확인하기 쉽다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) OpenHands Cloud&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands Cloud는 설치 없이 클라우드에서 OpenHands를 사용하는 방식이다.&lt;br /&gt;GitHub 또는 GitLab 계정으로 로그인해 바로 사용할 수 있는 흐름을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 개발자는 빠르게 체험할 수 있고, 팀 단위에서는 클라우드 기반 협업 흐름을 만들 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) Enterprise&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands Enterprise는 기업이 자체 인프라나 VPC 안에서 OpenHands를 운영할 수 있도록 하는 형태다.&lt;br /&gt;보안, 권한 관리, 사용자 관리, 협업 기능, 사내 시스템 연동이 중요한 조직을 겨냥한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 에이전트는 코드와 내부 문서를 다루기 때문에 기업 환경에서는 &amp;ldquo;어디서 실행되는가&amp;rdquo;가 매우 중요하다. 그런 점에서 self-hosting과 엔터프라이즈 배포 옵션은 OpenHands의 중요한 방향성이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. OpenHands가 실제로 할 수 있는 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands를 실무 관점에서 보면 다음 작업에 특히 적합하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) GitHub 이슈 자동 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 GitHub Action과 연결해 이슈 기반 작업을 자동화할 수 있다.&lt;br /&gt;예를 들어 저장소에 이슈를 만들고 fix-me 라벨을 붙이면 OpenHands가 해당 이슈를 분석하고 해결을 시도한다. 이후 PR을 만들고, 사람은 결과를 검토한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 흐름은 매우 중요하다.&lt;br /&gt;AI가 바로 main 브랜치에 코드를 밀어 넣는 것이 아니라, 사람이 검토 가능한 PR 단위로 작업을 남긴다. 실무에서는 이 구조가 훨씬 안전하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 코드 리뷰와 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 단순히 &amp;ldquo;이 코드 어때?&amp;rdquo;라고 묻는 도구가 아니다.&lt;br /&gt;코드를 읽고, 문제를 찾고, 수정안을 만들고, 테스트까지 시도할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 요청이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 PR에서 테스트가 실패하는 원인을 찾아서 고쳐줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;중복된 로직을 공통 함수로 분리해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;타입 에러를 해결하고 관련 테스트를 추가해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 컴포넌트를 더 작은 단위로 리팩터링해줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 개발자의 역할은 에이전트가 만든 결과를 검토하고, 방향이 맞는지 판단하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 의존성 업데이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 업데이트는 중요하지만 귀찮은 작업이다.&lt;br /&gt;버전을 올리고, 깨지는 부분을 찾고, 테스트를 돌리고, 수정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 이런 반복 작업에 잘 맞는다.&lt;br /&gt;특히 여러 저장소를 관리하는 팀이라면 의존성 업데이트, 보안 패치, 마이그레이션 작업에 에이전트를 붙이는 방식이 현실적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 테스트 작성과 QA 자동화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 부족한 프로젝트에서 OpenHands를 활용하면 특정 함수나 API에 대한 테스트 코드를 생성하게 할 수 있다.&lt;br /&gt;또한 실패한 테스트를 기반으로 원인을 추적하게 만들 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 테스트 품질은 반드시 사람이 검토해야 한다.&lt;br /&gt;AI가 작성한 테스트는 때때로 구현을 검증하는 것이 아니라 구현에 맞춰 형식적으로 통과하는 테스트가 될 수 있기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 레거시 코드 리팩터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 코드 리팩터링은 AI 에이전트에게 특히 흥미로운 영역이다.&lt;br /&gt;한 번에 전체 구조를 바꾸는 것은 위험하지만, 작은 단위로 나누면 자동화할 수 있는 부분이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 단계적 작업이 가능하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;코드 구조 분석&lt;/li&gt;
&lt;li&gt;중복 로직 탐지&lt;/li&gt;
&lt;li&gt;작은 단위 리팩터링&lt;/li&gt;
&lt;li&gt;테스트 실행&lt;/li&gt;
&lt;li&gt;실패 원인 수정&lt;/li&gt;
&lt;li&gt;변경 내역 요약&lt;/li&gt;
&lt;li&gt;PR 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 루프는 OpenHands 같은 에이전트형 도구가 지향하는 대표적인 활용 방식이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. OpenHands의 핵심 구조를 쉽게 이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands를 이해할 때는 네 가지 개념을 잡으면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Agent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent는 작업을 수행하는 AI 주체다.&lt;br /&gt;LLM을 기반으로 판단하고, 필요한 도구를 선택하고, 다음 행동을 결정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tools&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tools는 에이전트가 사용할 수 있는 능력이다.&lt;br /&gt;예를 들어 터미널 실행, 파일 편집, 웹 브라우징, 태스크 트래킹 같은 기능이 도구에 해당한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workspace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Workspace는 에이전트가 작업하는 공간이다.&lt;br /&gt;로컬 폴더일 수도 있고, Docker 컨테이너일 수도 있고, 원격 서버 환경일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 개발 에이전트에서 Workspace는 매우 중요하다.&lt;br /&gt;에이전트가 실제 코드를 만지고 명령어를 실행하기 때문에, 안전한 격리 환경이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Conversation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Conversation은 사용자와 에이전트 사이의 작업 흐름을 관리하는 단위다.&lt;br /&gt;사용자가 요청을 보내고, 에이전트가 계획을 세우고, 도구를 실행하고, 결과를 다시 관찰하는 전체 루프가 Conversation 안에서 진행된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. OpenHands와 기존 AI 코딩 도구의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands를 이해하려면 GitHub Copilot, Cursor, Claude Code 같은 도구와 비교해 보면 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Copilot은 IDE 안에서 코드 자동완성과 추천에 강하다.&lt;br /&gt;Cursor는 에디터 안에서 코드베이스를 이해하고 수정하는 경험이 좋다.&lt;br /&gt;Claude Code나 Codex류 도구는 터미널 기반 에이전트 작업에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 여기에 오픈소스 플랫폼성과 확장성을 더한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, OpenHands는 &amp;ldquo;하나의 완성된 코딩 앱&amp;rdquo;이라기보다 &amp;ldquo;AI 소프트웨어 개발 에이전트를 만들고 운영하기 위한 기반&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 개발자가 단순히 코딩 보조를 원한다면 Cursor나 Claude Code가 더 편할 수 있다.&lt;br /&gt;하지만 사내 워크플로우에 맞춘 에이전트를 만들고 싶거나, GitHub 이슈 처리 자동화, 보안 패치 자동화, 사내 개발 플랫폼 통합까지 고려한다면 OpenHands가 더 흥미로운 선택지가 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 설치와 실행 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 로컬 GUI, CLI, SDK, Cloud 방식으로 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 GUI를 실행하려면 일반적으로 Docker 기반 환경이 필요하다.&lt;br /&gt;공식 문서에서는 macOS, Linux, Windows WSL 환경을 안내하고 있으며, 최소 4GB RAM 이상의 시스템을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python 패키지 방식으로는 uv를 이용해 설치하는 흐름이 안내되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;uv tool install openhands --python 3.12
openhands serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 pip 기반으로 SDK를 설치할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;pip install -U openhands-sdk openhands-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDK를 사용하면 Python 코드에서 직접 에이전트를 만들 수 있다.&lt;br /&gt;예를 들어 LLM을 설정하고, TerminalTool, FileEditorTool, TaskTrackerTool 같은 도구를 붙인 뒤, Conversation을 생성해 작업을 실행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서는 이 부분이 가장 중요하다.&lt;br /&gt;OpenHands는 단순 앱이 아니라 &amp;ldquo;내가 원하는 에이전트형 개발 워크플로우를 코드로 구성할 수 있는 도구&amp;rdquo;이기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 한국 개발자&amp;middot;스타트업 관점에서의 활용 아이디어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 한국 개발자와 스타트업에게도 꽤 실용적인 가능성이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 소규모 개발팀의 유지보수 자동화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 스타트업은 기능 개발에 바빠서 리팩터링, 테스트, 문서화, 의존성 업데이트가 밀리기 쉽다.&lt;br /&gt;OpenHands를 GitHub 이슈와 연결하면 반복 유지보수 작업을 에이전트에게 맡기고, 개발자는 PR 검토자로 남을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 매주 다음과 같은 이슈를 자동 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오래된 npm 패키지 업데이트&amp;rdquo;&lt;br /&gt;&amp;ldquo;테스트 커버리지 낮은 모듈에 테스트 추가&amp;rdquo;&lt;br /&gt;&amp;ldquo;README와 실제 API 스펙 불일치 확인&amp;rdquo;&lt;br /&gt;&amp;ldquo;TODO 주석 기반 개선 PR 생성&amp;rdquo;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 사내 개발 플랫폼과 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 내부에 Backstage, Jira, Linear, GitHub Enterprise 같은 도구가 있다면 OpenHands를 사내 개발 플랫폼과 연결하는 시나리오를 생각해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Jira 티켓이 생성되면, OpenHands가 관련 저장소를 분석하고 초안 PR을 만드는 식이다.&lt;br /&gt;개발자는 처음부터 코드를 작성하는 대신, AI가 만든 초안을 리뷰하고 보완한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 레거시 시스템 현대화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 기업에는 오래된 Java, PHP, Spring, 전자정부 프레임워크, 내부 ERP 코드가 많다.&lt;br /&gt;이런 시스템은 한 번에 바꾸기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 레거시 코드를 작은 단위로 분석하고, 테스트를 추가하고, 마이그레이션 범위를 나누는 데 활용할 수 있다.&lt;br /&gt;특히 &amp;ldquo;전체 교체&amp;rdquo;가 아니라 &amp;ldquo;작은 PR을 반복적으로 쌓는 방식&amp;rdquo;으로 접근하면 현실성이 높다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) AI 에이전트 SaaS 개발&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands SDK는 단순 사용자가 아니라 개발자에게도 의미가 크다.&lt;br /&gt;예를 들어 다음과 같은 자체 서비스를 만들 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub 저장소 분석 리포트 자동 생성 서비스&lt;/li&gt;
&lt;li&gt;보안 취약점 수정 PR 자동 생성 서비스&lt;/li&gt;
&lt;li&gt;코드 리뷰 자동화 SaaS&lt;/li&gt;
&lt;li&gt;스타트업용 기술 부채 관리 에이전트&lt;/li&gt;
&lt;li&gt;레거시 코드 문서화 에이전트&lt;/li&gt;
&lt;li&gt;내부 API 문서 자동 동기화 에이전트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 서비스는 단순 챗봇보다 훨씬 높은 가치를 만들 수 있다.&lt;br /&gt;왜냐하면 실제 코드베이스 안에서 작업하고 결과물을 남기기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. OpenHands를 도입할 때 주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 강력하지만, 아무 프로젝트에나 무작정 붙이면 위험하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 권한을 좁게 줘야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 터미널을 실행하고 파일을 수정할 수 있다는 것은 장점이면서 동시에 위험이다.&lt;br /&gt;처음에는 읽기 전용 분석, 제한된 브랜치, 제한된 디렉터리, 테스트 전용 환경에서 시작하는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 자동 머지는 피해야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 코드는 반드시 PR 기반으로 검토해야 한다.&lt;br /&gt;특히 프로덕션 코드, 결제, 인증, 개인정보, 보안 관련 코드는 사람 리뷰가 필수다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 작업 단위를 작게 쪼개야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;우리 서비스를 전부 리팩터링해줘&amp;rdquo; 같은 요청은 좋지 않다.&lt;br /&gt;대신 다음처럼 구체적으로 요청해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;UserService의 중복 validation 로직을 별도 함수로 분리하고 기존 테스트가 통과하는지 확인해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 API 엔드포인트에 누락된 에러 케이스 테스트를 추가해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;README의 설치 방법이 현재 package.json과 맞는지 확인하고 수정해줘.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 명확한 범위와 성공 조건이 있을 때 훨씬 잘 작동한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 비용 관리가 필요하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 LLM API를 사용한다.&lt;br /&gt;작업이 길어지고 반복 실행이 많아지면 비용이 늘어날 수 있다.&lt;br /&gt;따라서 팀 단위로 사용할 때는 모델 선택, 최대 반복 횟수, 작업 제한, 승인 단계, 로그 모니터링을 설정해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) 보안과 비밀키 관리가 중요하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 코드 저장소에 접근한다면 .env, API 키, DB 접속 정보, 내부 문서 접근 권한을 신중히 다뤄야 한다.&lt;br /&gt;가능하면 샌드박스 환경에서 실행하고, 비밀 정보는 별도 Secret 관리 체계를 사용하는 것이 좋다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. OpenHands의 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 &amp;ldquo;AI 개발자의 미래&amp;rdquo;를 보여주는 프로젝트지만, 아직 모든 것을 자동으로 해결하는 도구는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 AI 개발 에이전트의 일반적인 한계는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 복잡한 제품 의사결정은 여전히 사람의 몫이다.&lt;br /&gt;AI는 코드를 수정할 수 있지만, 어떤 기능이 비즈니스에 맞는지, 어떤 구조가 조직에 맞는지는 사람이 판단해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, 환경 설정 문제에서 막힐 수 있다.&lt;br /&gt;실제 개발 프로젝트는 의존성, 로컬 DB, 권한, 사내 네트워크, 빌드 도구 등 변수가 많다. 에이전트가 이런 환경을 완벽히 이해하지 못하면 작업이 중간에 실패할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋째, 그럴듯하지만 틀린 수정이 나올 수 있다.&lt;br /&gt;AI가 테스트를 통과시키기 위해 문제의 본질이 아니라 증상만 고칠 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넷째, 장기 작업에서는 방향이 흐려질 수 있다.&lt;br /&gt;큰 리팩터링이나 아키텍처 변경은 여러 단계의 설계와 검토가 필요하다. 이 경우 에이전트에게 한 번에 맡기기보다 작은 단위로 나누는 전략이 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 그래서 OpenHands는 누구에게 적합한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 다음 사용자에게 특히 잘 맞는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI 코딩 도구를 단순 보조가 아니라 자동화 워크플로우로 쓰고 싶은 개발자&lt;/li&gt;
&lt;li&gt;GitHub 이슈와 PR 기반으로 반복 작업을 자동화하고 싶은 팀&lt;/li&gt;
&lt;li&gt;자체 AI 개발 에이전트를 만들고 싶은 스타트업&lt;/li&gt;
&lt;li&gt;사내 개발 플랫폼에 AI 에이전트를 붙이고 싶은 엔지니어링 조직&lt;/li&gt;
&lt;li&gt;오픈소스 기반으로 AI 개발 에이전트 구조를 연구하고 싶은 개발자&lt;/li&gt;
&lt;li&gt;Claude Code, Codex, Cursor 이후의 에이전트형 개발 환경을 실험하고 싶은 사용자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 다음 사용자에게는 다소 부담스러울 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 코드 자동완성만 원하는 사용자&lt;/li&gt;
&lt;li&gt;Docker, 터미널, GitHub Action 설정이 익숙하지 않은 초보자&lt;/li&gt;
&lt;li&gt;AI가 만든 코드를 검토할 개발자가 없는 팀&lt;/li&gt;
&lt;li&gt;보안상 외부 LLM API 사용이 어려운 조직&lt;/li&gt;
&lt;li&gt;자동화보다 IDE 안의 즉각적인 편집 경험을 더 중시하는 개발자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. OpenHands를 한 문장으로 정리하면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 &amp;ldquo;AI에게 코드를 물어보는 도구&amp;rdquo;가 아니라, &amp;ldquo;AI에게 개발 작업을 맡기기 위한 오픈소스 실행 플랫폼&amp;rdquo;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구의 다음 경쟁은 더 이상 &amp;ldquo;누가 코드를 더 잘 추천하느냐&amp;rdquo;만이 아니다.&lt;br /&gt;앞으로는 &amp;ldquo;누가 실제 개발 워크플로우 안에서 안전하게 일을 끝내느냐&amp;rdquo;가 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenHands는 바로 그 방향을 보여준다.&lt;br /&gt;개발자는 모든 코드를 직접 치는 사람이 아니라, 에이전트에게 일을 나누고, 결과를 검토하고, 시스템을 설계하는 사람으로 바뀌고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 OpenHands를 봐야 하는 이유도 여기에 있다.&lt;br /&gt;이 프로젝트는 단순한 오픈소스 코딩 도구가 아니라, 앞으로 개발팀이 AI 에이전트를 어떻게 조직 안에 들여올 것인지 보여주는 실험장이기 때문이다.&lt;/p&gt;
&lt;figure id=&quot;og_1781656831743&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - OpenHands/OpenHands:   OpenHands: AI-Driven Development&quot; data-og-description=&quot;  OpenHands: AI-Driven Development. Contribute to OpenHands/OpenHands development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/OpenHands/openhands&quot; data-og-url=&quot;https://github.com/OpenHands/OpenHands&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Dkudj/dJMb896cEGP/wqiC7rn5q4pFPZTV2rVXek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dHUl1o/dJMb9bwa9Cu/ssFzeBlan3bwPcdKRRoLk0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bpIvaQ/dJMb82eVYra/pCVWRNtqgTm8IzvX941Jrk/img.png?width=3840&amp;amp;height=2160&amp;amp;face=0_0_3840_2160&quot;&gt;&lt;a href=&quot;https://github.com/OpenHands/openhands&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/OpenHands/openhands&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Dkudj/dJMb896cEGP/wqiC7rn5q4pFPZTV2rVXek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dHUl1o/dJMb9bwa9Cu/ssFzeBlan3bwPcdKRRoLk0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bpIvaQ/dJMb82eVYra/pCVWRNtqgTm8IzvX941Jrk/img.png?width=3840&amp;amp;height=2160&amp;amp;face=0_0_3840_2160');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - OpenHands/OpenHands:   OpenHands: AI-Driven Development&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  OpenHands: AI-Driven Development. Contribute to OpenHands/OpenHands development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <author>행복한 수지아빠</author>
      <guid isPermaLink="true">https://javaexpert.tistory.com/1778</guid>
      <comments>https://javaexpert.tistory.com/1778#entry1778comment</comments>
      <pubDate>Wed, 17 Jun 2026 09:41:04 +0900</pubDate>
    </item>
  </channel>
</rss>