<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jun0-ds.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 21 May 2026 23:48:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. jun0-ds.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jun0-ds" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[sonmat v0.9 — 증인을 세우다]]></title>
            <link>https://velog.io/@jun0-ds/sonmat-v0.9-%EC%A6%9D%EC%9D%B8%EC%9D%84-%EC%84%B8%EC%9A%B0%EB%8B%A4</link>
            <guid>https://velog.io/@jun0-ds/sonmat-v0.9-%EC%A6%9D%EC%9D%B8%EC%9D%84-%EC%84%B8%EC%9A%B0%EB%8B%A4</guid>
            <pubDate>Thu, 21 May 2026 23:48:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2026-04-15. sonmat v0.9.0 / v0.9.1 릴리스 노트이자, 그 하루를 만든 설계 사건.</p>
</blockquote>
<p>결론부터. sonmat에 검증자 하나를 새로 붙였다. 이름은 <strong>sonmat-witness</strong>. 메인 세션이 내놓은 결과물이 사용자가 진짜 시킨 거랑 맞는지를, 메인 머릿속은 안 본 채로 본다.</p>
<p>거기까진 깔끔한 설계 얘기다. 그런데 진짜 일은 만드는 동안에 벌어졌다. 한 세션 안에서 같은 자리에 세 번 자빠졌고, 매번 <code>/devil</code> 한 번 돌리니까 가정이 무너졌다. 이 글은 그 둘 다에 대한 얘기다.</p>
<hr>
<h2 id="왜-witness가-필요했나">왜 witness가 필요했나</h2>
<p>sonmat은 원래 <em>의심해야 한다</em> 는 한 줄에서 출발했다. AI든 사람이든 자기가 쓴 걸 그대로 믿지 말고, 한 번 더 의심해서 보라는 디시플린. 그게 sonmat 전체의 출발점이다.</p>
<p>이번 v0.9에서 부딪힌 자리는, 그 <em>의심</em> 이라는 동작 자체가 자기 자신한테는 잘 안 먹힌다는 점이었다.</p>
<p>사람도 마찬가지다. 자기 글에서 자기 오타는 안 보인다. 수술실 Time Out은 집도의 본인이 안 한다. 다른 사람이 한다. 비행기 조종실에서 &quot;FLAPS — TWO&quot; 같은 콜아웃이 오면, 다른 조종사가 그냥 입으로 &quot;two&quot;라고 답하는 걸로는 검증이 안 된다. 그 스위치 자체를 눈으로 봐야 검증이다.</p>
<p>자기 자신을 의심하라고 시켜봤자 잘 안 된다. 그러니 <em>밖에서 의심하는 자리</em> 를 따로 만들어야 한다. 거기 들어갈 게 witness였다.</p>
<p>이름은 사실 <em>훈수꾼</em> 이 먼저 떠올랐다. 옆에서 같이 보면서 한 수 짚어주는 그 자리. 한국어로는 직관적인데, 시리즈를 영문판도 같이 가다 보니 그대로 옮기기가 어려웠다. 결국 더 보편적으로 통하는 <em>witness</em> 라는 단어를 차용했다.</p>
<p>이 자리가 제대로 작동하려면 따라붙어야 할 게 두 개다.</p>
<ol>
<li><strong>검증자가 실행자의 머릿속을 보면 안 된다.</strong> 보면 실행자의 합리화를 그대로 받아서 &quot;고무도장&quot; 찍는 기계가 된다.</li>
<li><strong>검증자한테 들어가는 입력은 사용자가 친 그 문장이어야 한다.</strong> 메인이 &quot;이 사람이 원한 건 이거지&quot;로 해석한 버전이 아니라.</li>
</ol>
<p>기존 <code>guard</code> 스킬로는 이 둘 다 안 풀렸다. guard는 메인 세션 안에서 같이 도는 규칙 체크라, 메인이 보는 맥락을 그대로 본다. 테스트 돌리기, 민감 파일 막기, 디시플린 안 어겼나 보기 — 이런 운영 레벨 체크는 잘한다. 그런데 &quot;사용자가 시킨 거랑 결과가 맞나?&quot;는 못 한다. 격리가 없어서.</p>
<p>그래서 witness를 따로 뽑기로 했다. 어떤 모양으로 떨어졌는지는 뒤에서 보고, 먼저 이걸 만드는 동안 벌어진 얘기부터.</p>
<hr>
<h2 id="한-세션에-세-번-자빠졌다">한 세션에 세 번 자빠졌다</h2>
<p>witness를 짜는 동안 같은 자리에서 세 번 자빠졌다. 매번 그럴듯한 이름 하나를 먼저 받아들이고, 그 프레임 안에서 설계를 굴렸다. 그러고 나서 <code>/devil</code>을 돌렸다. 매번, 그 이름이 떠받치고 있던 핵심 가정 — <strong>load-bearing</strong>이라고 부르는 거 — 이 사실은 약했다. 세 번 다 뒤집혔다.</p>
<h3 id="1차-session-orchestrator-worker-3층">1차: &quot;session-orchestrator-worker 3층&quot;</h3>
<p>sonmat memory에 한 달 전부터 박혀 있던 한 줄이 있었다.</p>
<blockquote>
<p>*&quot;역전된 3층 구조 설계 진행.&quot;*</p>
</blockquote>
<p>메인은 대화자, 그 아래 오케스트레이터, 또 그 아래 워커. 한 달 동안 이 그림을 머릿속에 두고 굴렸다. 이번에 witness를 만들면서도 자연스럽게 이 3층 위에 얹는 모양을 그렸다. 검증자가 오케스트레이터 자리에 들어가서 워커가 산출한 결과물을 보는 구도. 깔끔했다. 그림이 너무 깔끔해서 의심이 안 들었다.</p>
<p>마지막으로 한 번 확인이나 하자 싶어서 devil CCT를 돌렸다. CCT는 <code>/devil</code> 안에서 도는 discovery 단계다. 주장이 어디 매달려 있는지 한 줄을 먼저 찾는 절차.</p>
<ul>
<li><strong>Claim-crux</strong>: 이 구조 전체가 *&quot;Claude Code가 nested subagent delegation을 지원한다&quot;* 라는 가정에 매달려 있다.</li>
<li><strong>Counter-fit</strong>: 공식 문서를 안 읽고 구조부터 그렸다. 그 가정은 검증된 적이 없다.</li>
</ul>
<p>이 한 줄이 떨어지자마자 등이 좀 시려웠다. 한 달짜리 그림이 검증 안 된 한 줄에 매달려 있었다는 얘기니까. 공식 문서를 깠다. Claude Code <a href="https://platform.claude.com/docs/en/managed-agents/multi-agent.md">multi-agent 문서</a>:</p>
<blockquote>
<p>&quot;Only one level of delegation is supported: the coordinator can call other agents, but those agents cannot call agents of their own.&quot;</p>
</blockquote>
<p>명시적 금지. 3층은 <strong>구조적으로 불가능</strong>이었다. &quot;역전된 3층&quot;이라는 이름이 한 달 전 메모리에 박혔고, 그 순간부터 이후 모든 설계 논의가 그 프레임 안에서만 굴러갔다. 그동안 *&quot;이거 플랫폼이 받쳐주냐?&quot;* 를 아무도 안 물었다.</p>
<p>2층(<code>witness-pair</code>)이 Claude Code의 천장이다. 우리는 이미 거기 닿아 있었다. 3층은 처음부터 그냥 그림이었다.</p>
<h3 id="2차-pretooluse-hook이-witness-스폰해서-tool-call-deny">2차: &quot;PreToolUse hook이 witness 스폰해서 tool call deny&quot;</h3>
<p>3층 retract하고 다음 안을 짜는데, 뒷통수가 좀 간지러웠다. 한 시간 전까지 멀쩡해 보이던 게 깨졌으니까. 이번엔 검증해가면서 가자, 싶어서 좀 더 조심스럽게 그렸다.</p>
<blockquote>
<p>*&quot;autoloop 커밋 단계에서 PreToolUse hook이 witness를 agent-type 훅으로 스폰. witness가 BLOCK 내면 hook이 tool call을 deny한다.&quot;*</p>
</blockquote>
<p>이게 짠 다음 안이었다. 솔깃했다. 메인이 witness를 우회할 수 없는, <em>플랫폼 레벨</em> 강제니까. 디시플린에 의존하지 않고 hook이 알아서 막아주면, witness가 신뢰 가능한 게이트가 된다.</p>
<p>근데 1차에서 데여서 그런가 — 안을 정리해놓고 보니 *&quot;이번엔 진짜로 문서에 있나?&quot;* 가 먼저 들어왔다. 공식 가이드를 다시 깠다.</p>
<ul>
<li><code>agent</code> 타입 hook은 있다. 문서화돼 있다.</li>
<li>그런데 <strong>PreToolUse 이벤트에서 agent hook + verdict-based deny</strong> 예시는 <strong>0개</strong>. 공식 예시는 전부 <code>Stop</code> 이벤트에서 테스트 통과 확인하는 용도뿐.</li>
<li>&quot;hook이 서브에이전트를 synchronous하게 기다렸다가 verdict로 deny를 채운다&quot;는 semantics 자체가 문서에 없다.</li>
</ul>
<p>CCT 한 번 더.</p>
<ul>
<li><strong>Claim-crux</strong>: witness가 <strong>hook 레이어 강제</strong>를 필요로 한다는 전제.</li>
<li><strong>Counter-fit</strong>: autoloop의 다른 phase(<code>[Plan]</code>, <code>[Define]</code>, <code>[Execute]</code>, <code>[Evaluate]</code>)는 다 디시플린 기반으로 돈다. witness gate만 유독 플랫폼 레벨 강제가 필요하다고 가정할 근거가 없다.</li>
<li><strong>Cause-chain</strong>: &quot;hook 강제 → 신뢰 가능한 게이팅&quot; 인과는 hook 자체가 실재할 때만 성립. 지금은 안 실재한다. 그럼 대안은 autoloop 디시플린뿐 — 그리고 autoloop는 <strong>이미 그렇게 돌고 있다</strong>.</li>
</ul>
<p>뒤집혔다. witness는 hook 필요 없다. <strong>Task tool로 <code>[Judge]</code> 단계 안에서 스폰</strong>하면 끝이다. Task + <code>subagent_type</code>은 잘 문서화된 primitive. autoloop가 다른 phase 돌리는 방식 그대로 witness도 돌린다. 강제력은 autoloop discipline — 이미 받아들인 그 디시플린에서 안 빠지는 만큼.</p>
<p>그런데 이번에도 패턴이 같았다. 매력적인 이름(*&quot;PreToolUse agent hook + deny&quot;*)이 박혔고, 그 프레임 안에서 설계를 굴렸고, 플랫폼 지원 여부는 안 물었고, 뒤늦게 무너졌다. 1차랑 똑같은 모양으로.</p>
<h3 id="3차-그래서-witness-자체를-의심하기-시작했다">3차: 그래서 witness 자체를 의심하기 시작했다</h3>
<p>여기까지 오니 witness는 shippable해 보였다. 실행 격리 박혔다. 입력은 raw user turn이다. citation rule도 있다. 두 번 데였으니 이제는 진짜 끝일 것 같았다.</p>
<p>근데 두 번 데인 게 마음에 걸렸다. 두 번 다 *&quot;매력적인 이름 안에서 굴린 설계&quot;* 가 무너진 거였는데, witness라는 이름 자체는 검증된 적이 있나?</p>
<p>마지막으로 한 번만 더. 이번엔 witness 자체를 공격해봤다. 주장을 평소답지 않게 강하게 박았다.</p>
<blockquote>
<p>*&quot;witness는 우리가 설계한 대로, 비교만 하고 reasoning은 안 하는 결정론적 comparator처럼 작동할 것이다.&quot;*</p>
</blockquote>
<p>CCT가 집어낸 게 의외였다.</p>
<ul>
<li><strong>Claim-crux</strong>: 프롬프트에 &quot;do not reason, compare&quot;라고 적으면 LLM이 진짜 그렇게 행동한다는 가정.</li>
<li><strong>Counter-fit</strong>: witness는 agent file 한 장이 아니다. <strong>agent file + 입력 + 모델의 기본 경향</strong> 조합으로 행동한다. 그리고 메인을 돌리는 LLM과 witness를 돌리는 LLM이 같은 클래스다. 메인이 자기 검증할 때 rationalization에 빠지는 그 failure mode가, witness한테도 똑같이 적용될 수 있다. 우리가 agent file에 &quot;비교만 해라&quot; 적어뒀다고 안 빠진다는 보장은 없다.</li>
<li><strong>Cause-chain</strong>: &quot;agent file 규칙 → 엄격한 comparator 행동&quot;의 인과가 약하다. enforcement 메커니즘이, witness가 제약해야 하는 바로 그 메커니즘(프롬프트 지시에 대한 instruction-following) 자체다. 순환.</li>
</ul>
<p>여기서 잠깐 멈췄다. 1차랑 2차는 <em>플랫폼이 안 받쳐줘서</em> 깨진 거였다 — 검색 가능한 사실의 문제. 3차는 다르다. <em>witness 설계 그 자체</em> 가 self-referential한 자리에 들어가 있다는 거다. enforcement를 instruction-following으로 해야 하는데, witness가 제약해야 하는 그 instruction-following이 동시에 enforcement 메커니즘이다.</p>
<p>이런 자리에선 strong form은 못 세운다. 그런데 의외로 weak form은 살아남는다.</p>
<p><strong>Strong form</strong> — &quot;witness는 결정론적 comparator다&quot; — 는 약해졌다. 그런데 <strong>Weak form</strong> — &quot;witness는 그래도 메인 self-check보단 낫다&quot; — 는 그대로 살았다. 이유는 layer가 갈리기 때문이다.</p>
<pre><code>Layer 1 — 실행 격리 (harness-enforced) ───── 진짜 구조적 보장
Layer 2 — spawn prompt 규율 ─────────────── aspirational contract
Layer 3 — citation rule ──────────────────── aspirational contract</code></pre><p>Layer 1은 플랫폼이 강제로 잡아준다. witness가 sloppy해진다 한들, 메인의 합리화 컨텍스트에 접근할 길 자체가 없다. confirmation-rubber-stamp failure mode에 빠질 수가 없다. input isolation 하나만으로도 메인 self-check보다 낫다고 말할 수 있는 이유.</p>
<p>Layer 2와 3은 다르다. LLM 프롬프트 수준의 행동 계약일 뿐이다. 런타임에 안 잡아준다. witness가 안에서 *&quot;이 finding §2긴 한데 좀 약해 보이니까 WARN으로 가자&quot;* 결정해버리면 그걸 구조적으로 막을 수단이 없다.</p>
<p>이 차이를 문서에 honest하게 박은 게 v0.9.1이다. <code>witness.md</code>의 <code>§Isolation stack</code>이 세 layer를 enforced vs aspirational로 명시적으로 갈라놨다. 그리고 <strong>early use 동안엔 사람이 직접 verdict를 sample해서 layer 2-3이 진짜 버티는지 봐라</strong>는 지침을 박았다. drift 보이면 agent file 조정. scribe가 이미 witness verdict를 journal에 적고 있으니 drift 감지 채널은 이미 깔려 있다.</p>
<p>witness가 완벽한 검증자는 아니다. 그래도 메인보다는 낫고, 그 차이만은 layer 1 덕분에 <strong>포장 없이 보장</strong>된다. 나머지는 굴려가면서 맞춰갈 영역.</p>
<hr>
<h2 id="그래서-살아남은-게-이런-모양">그래서 살아남은 게 이런 모양</h2>
<p>세 번 깨지고 자리잡은 witness의 최종 모양은 이렇다.</p>
<ul>
<li><strong>Task tool로 띄우는 서브에이전트.</strong> 1차에서 깨졌던 3층 그림 대신, 2층이 천장이라는 걸 받아들인 자리.</li>
<li><strong>autoloop <code>[Judge]</code> 단계 안에서 스폰.</strong> 2차에서 깨졌던 hook 강제 대신, 다른 phase랑 똑같이 디시플린으로 돈다.</li>
<li><strong>입력은 raw user turn하고 산출물 둘뿐.</strong> 메인의 설명, chain-of-thought, 코멘트, 커밋 메시지는 못 읽거나 읽혀도 인용할 수 없다. 이게 layer 1, harness가 강제하는 구조.</li>
<li><strong>citation은 무조건.</strong> 모든 발견은 <code>user turn N: &quot;exact quote&quot;</code>하고 <code>file:line</code> 쌍을 달아야 한다. 근거 없이 &quot;느낌이 이상하다&quot;는 발견은 그냥 버려진다. 이건 layer 3, 프롬프트 수준 계약.</li>
<li><strong>verdict는 source 기반.</strong> <code>BLOCK</code>/<code>WARN</code>을 witness가 느낌으로 정하지 않는다. 어느 체크(§1 intent-scope, §2 intent-content, §3 framing-derived, §4 ground truth)가 그 발견을 냈느냐로 자동 결정. <code>§1/§2/§4</code>→<code>BLOCK</code>, <code>§3</code>→<code>WARN</code>. 강도 판정 자체를 뺐다.</li>
<li><strong>도는 범위 세 가지.</strong> 커밋 한 개 단위(<code>commit gate</code>), 세션에서 건드린 파일 전체(<code>session forest</code>), 그리고 사용자가 &quot;전체적으로&quot;, &quot;모든 X에&quot;, &quot;시스템 전반&quot; 식으로 말한 경우의 <code>principle coverage</code>. 마지막이 제일 날카로운 모드다. 여러 파일에 걸쳐 박혀야 할 원칙이 일부만 박혔는지를 witness가 직접 grep으로 훑는다.</li>
</ul>
<p>세 번 깨진 자리마다 한 줄이 박힌 셈이다.</p>
<hr>
<h2 id="메타-한-마디--discovery-led-depth가-자기-자신에게-걸렸다">메타 한 마디 — discovery-led depth가 자기 자신에게 걸렸다</h2>
<p>이 세션에 같이 들어간 변화가 하나 더 있다. <code>inspect</code>, <code>devil</code>, <code>punch</code> 셋의 설계 원칙을 <strong>discovery-led</strong>로 다시 정렬했다. 원래는 &quot;cascade 원칙&quot;이라고 부르고 있었는데, 방향이 잘 안 전달돼서 이름을 갈았다.</p>
<p>원리는 간단하다. <strong>깊이는 발견 다음에 온다.</strong> 얼마나 파볼지를 미리 정해놓고 뭘 찾는 게 아니라, 뭔가 잡힌 다음에 그 잡힌 게 깊이를 끌어당기는 식이다. 체스에서 긴 변화수 계산 전에 Checks/Captures/Threats부터 훑는 CCT. 수술 전 Time Out. 항공 challenge-and-response에서 PM이 스위치 위치를 *&quot;본다&quot;*. 다섯 갈래 검증 전통이 같은 구조를 쓴다.</p>
<p>devil에도 CCT 이름을 빌려 적용했다 — Claim-crux / Counter-fit / Cause-chain. 주장을 네 축에서 평행하게 때리는 대신, <strong>load-bearing 가정 하나</strong>를 먼저 찾는다. 그게 Evidence 축이냐, Logic 축이냐, Alternatives 축이냐에 따라 그 한 축에만 깊이를 쏟는다. 나머지 축은 가볍게 패스.</p>
<p>오늘 돌린 세 번의 devil이 이 원칙의 첫 실전 적용이었다. 평행 공격이었다면 *&quot;witness 괜찮나? inspect는? punch는? 3층은? hook은?&quot;* 을 동시에 때렸을 거다. 결론이 이렇게 선명하게는 안 나왔을 거다. CCT는 매번 정확히 <strong>load-bearing 가정 하나</strong>를 집어냈다. 그 하나만 깊게 파자 전체가 무너졌다. 우리가 만든 원칙이, 그 원칙을 만든 사람한테 그대로 적용됐고, 작동했다.</p>
<hr>
<h2 id="v09에-들어간-것들-전체">v0.9에 들어간 것들 (전체)</h2>
<ul>
<li><strong>sonmat-witness</strong>: 외부 intent-artifact comparator. 3 scope scale(commit / session forest / principle coverage). Source-based verdict(<code>BLOCK</code>/<code>WARN</code>/<code>PASS</code>/<code>INSUFFICIENT_GROUND_TRUTH</code>).</li>
<li><strong>guard / scribe 분리</strong>: guard는 순수 검증(운영 체크 + 발견), scribe는 사후 persistence(project rule 기록, novel trap memory, journal, bridge note, witness verdict 로그).</li>
<li><strong>discovery-led 원칙 재정렬</strong>: inspect는 trigger-reactive depth, devil은 CCT로 load-bearing 찾아서 asymmetric attack, punch는 user invocation 자체가 discovery인 모드 시스템.</li>
<li><strong>punch refactor-residue check</strong>: 구조적 제거/renaming 후에 살아남은 stale reference를 mechanical grep으로 찾는다. Section/function/file/terminology/example/enum/template 7가지 패턴.</li>
<li><strong>Isolation stack 솔직한 framing</strong>: layer 1(harness-enforced) vs layer 2-3(aspirational). 강한 용어로 포장하지 않고, 어느 부분이 보장이고 어느 부분이 희망인지 문서에 직접 갈라놨다.</li>
<li><strong>Feature request 문서</strong>: 지금 플랫폼이 안 주는 4가지 primitive(input channel 제한, nested delegation, session layer, 문서화된 hook pattern)를 정리. <code>docs/feature-requests/claude-code-isolation.md</code>.</li>
</ul>
<p>v0.9.0 — witness + 재구조. v0.9.1 — honest framing pass.</p>
<hr>
<h2 id="교훈-한-줄">교훈 한 줄</h2>
<p><strong>그럴듯한 아키텍처 이름이 떠오를 때, 설계 들어가기 전에 플랫폼이 받쳐주는지부터 봐라.</strong> &quot;역전된 3층&quot;, &quot;PreToolUse agent hook + deny&quot;, &quot;결정론적 comparator&quot; — 셋 다 이름은 그럴듯했다. 셋 다 실제론 성립 안 했거나 반쪽이었다. devil CCT가 매번 정확히 그 틈을 집어냈다.</p>
<p>더 큰 얘기로: 원칙을 문서에 박아둔다고 끝이 아니다. 그 원칙이 <strong>자기 자신한테</strong> 어떻게 적용되는지를 같이 본다. 오늘 세션이 그 첫 장이었다.</p>
<hr>
<p><em>릴리스 노트</em>: <a href="https://github.com/jun0-ds/sonmat/releases/tag/v0.9.0">v0.9.0</a>, <a href="https://github.com/jun0-ds/sonmat/releases/tag/v0.9.1">v0.9.1</a>
<em>리포</em>: <a href="https://github.com/jun0-ds/sonmat">https://github.com/jun0-ds/sonmat</a></p>
<hr>
<p><em>시리즈 <strong>손맛 (sonmat) 만들기</strong> 다섯 번째 글. 이전 글: <a href="https://velog.io/@jun0-ds/%EC%84%B8%EC%A2%85%EB%8C%80%EC%99%95%EC%9D%80-%EC%95%84%EC%9D%B4%ED%8C%A8%EB%93%9C%EB%A5%BC-%EB%8D%98%EC%A7%80%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4-0wky9txz">세종대왕은 아이패드를 던지지 않았다</a></em></p>
<p><em><a href="https://github.com/jun0-ds">GitHub</a> · <a href="https://www.linkedin.com/in/junyoung-ryu-422501117/">LinkedIn</a></em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/building-sonmat/05-witness-v0-9/">jun0-ds.dev/ko/blog/building-sonmat/05-witness-v0-9</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/building-sonmat/05-witness-v0-9/">I put a witness on my AI. Devil&#39;s advocate killed three designs first.</a></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[세종대왕은 아이패드를 던지지 않았다]]></title>
            <link>https://velog.io/@jun0-ds/%EC%84%B8%EC%A2%85%EB%8C%80%EC%99%95%EC%9D%80-%EC%95%84%EC%9D%B4%ED%8C%A8%EB%93%9C%EB%A5%BC-%EB%8D%98%EC%A7%80%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4-3ub9kq59</link>
            <guid>https://velog.io/@jun0-ds/%EC%84%B8%EC%A2%85%EB%8C%80%EC%99%95%EC%9D%80-%EC%95%84%EC%9D%B4%ED%8C%A8%EB%93%9C%EB%A5%BC-%EB%8D%98%EC%A7%80%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4-3ub9kq59</guid>
            <pubDate>Thu, 07 May 2026 11:19:00 GMT</pubDate>
            <description><![CDATA[<h2 id="세종대왕이-분노하여-아이패드를-던지셨다">&quot;세종대왕이 분노하여 아이패드를 던지셨다&quot;</h2>
<p>ChatGPT 초창기에 한국 LLM 커뮤니티를 돌던 농담이다. 한글 창제 일화를 묻자 모델이 천연덕스럽게 던진 답. 학습 분포 어딘가에서 그럴듯한 단어들이 통계적으로 엉겨붙은 결과다. 환각이라는 말을 들으면 보통 이런 그림이 떠오른다.</p>
<p>정확히 말하면 모델은 그 답을 진짜로 던졌다. 다만 손맛 v0.8.0(2026-04-11)이 다룬 환각은 이게 아니었다.</p>
<hr>
<h2 id="환각-7를-들여다봤더니">환각 7%를 들여다봤더니</h2>
<p>발단은 한 24B 모델로 돌린 2,700회 위키 QA 테스트였다. 환각률 7%. 숫자만 보면 &quot;역시 LLM은 환각이 있군&quot; 하고 넘어갈 만한 수치다. 그런데 막상 7%에 잡힌 응답을 하나씩 까보니 그림이 달랐다.</p>
<p>세종대왕-아이패드 같은 진짜 환각, 그러니까 모델이 학습 분포에 없는 걸 만들어낸 케이스는 의외로 적었다. 더 많이 잡힌 건 이런 거였다.</p>
<blockquote>
<p>사용자가 &quot;시설관리는 A 테이블에 있어&quot;라고 말했다.
사실은 B 테이블에 있었다.
모델은 충실하게 A 테이블을 뒤졌다.
거기 없으니 혼란스러워했고, 결국 그럴듯한 추측을 짜냈다.
이 응답이 환각률 7%에 잡혔다.</p>
</blockquote>
<p>이게 환각인가? 사용자 입장에선 그렇다. <strong>틀린 답이 나왔으니까.</strong> 그런데 같은 상황을 사람한테 시켜도 결과는 똑같다. 잘못된 매뉴얼을 쥔 인턴이 시설관리 담당을 찾으러 나서면, 결국 헛도는 답을 들고 돌아온다. 모델 문제가 아니라 입력 문제다.</p>
<hr>
<h2 id="두-출처가-섞여-있었다">두 출처가 섞여 있었다</h2>
<p>여기서 한 칸 갈라줘야 한다. 사용자가 경험하는 환각은 <strong>하나의 사건</strong>이지만, 그 사건이 어디서 시작됐는지는 두 갈래다.</p>
<table>
<thead>
<tr>
<th>출처</th>
<th>어디서 발생</th>
<th>처방</th>
</tr>
</thead>
<tbody><tr>
<td>모델 측</td>
<td>weights 안에서 그럴듯한 조합이 만들어짐 (세종대왕-아이패드 류)</td>
<td>모델 연구자 영역. weights를 바꿔야 줄어듦</td>
</tr>
<tr>
<td>맥락 측</td>
<td>받은 입력 자체가 틀렸고, 모델이 충실히 따라감</td>
<td>입력을 의심하는 구조. 시스템 설계자 영역</td>
</tr>
</tbody></table>
<p>학계 분류도 일치하지 않는다. faithfulness(입력에 충실한가) 기준으로 보면 맥락 측 케이스는 &quot;충실하니까 환각 아님&quot;이고, factuality(사실에 부합하는가) 기준으로 보면 &quot;사실과 어긋났으니까 환각 맞음&quot;이다. NLG 환각 서베이(Ji et al. 2023)의 intrinsic/extrinsic 분류로 보면 어디에도 깔끔하게 안 들어맞는다. 입력엔 충실하면서 사실엔 어긋난 케이스니까.</p>
<p>학계가 합의를 못 보는 이유는 단순하다. 사용자 시점에선 둘 다 &quot;AI가 틀린 답을 줬다&quot;로 동일하다. 다만 도구를 짜는 입장에선 다르다. <strong>출처가 갈리면 처방도 갈린다.</strong> 모델 측은 우리가 못 건드린다. 맥락 측은 건드릴 수 있다.</p>
<p>v0.8은 건드릴 수 있는 쪽만 다뤘다.</p>
<hr>
<h2 id="그-분리가-보이기-전까지">그 분리가 보이기 전까지</h2>
<p>이렇게 정리하고 보면 갈라보는 게 자명한 것 같지만, 사실 나는 한참 못 보고 있었다. &quot;환각 = 모델 문제&quot;라는 한 줄을 그냥 받아들이고 있었다. 손맛에 의심 도구를 아무리 보태봤자 weights에서 만들어지는 그 통계적 조합을 무슨 수로 줄이겠나 — 그래서 환각은 손맛이 풀 영역이 아니라고 한쪽에 치워두고 있었다.</p>
<p>7% 분석을 본 게 깨는 계기였다. frame이 틀렸던 게 아니라 scope를 너무 좁게 잡고 있었던 거다. 받은 frame을 의심하라는 도구를 만들면서, 정작 본인이 받은 frame은 의심도 없이 그대로 밟고 서 있었다. 부끄러운 자리지만, v0.8의 진짜 발단은 거기였다.</p>
<p>이후 두 변화는 그 깨달음을 디시플린(추론 규칙)과 스킬(액션 도구) 양쪽에 동시에 박은 거다.</p>
<hr>
<h2 id="core-6곳에-박힌-한-줄">core 6곳에 박힌 한 줄</h2>
<p>먼저 디시플린부터 손봤다. 손맛의 <code>discipline/core.md</code>는 &quot;Claude가 어떻게 사고해야 하는가&quot;를 규정하는 짧은 문서인데, v0.8 전까지 의심의 방향이 거의 다 <strong>자기 자신</strong> 쪽이었다. &quot;내 가정이 진짜 확실한가?&quot;, &quot;결론으로 점프 안 했나?&quot; 같은 질문들.</p>
<p>v0.8에서 의심의 방향을 한 칸 더 늘렸다. 자기 추론만이 아니라 <strong>받은 맥락 자체</strong>도 의심하라고. 같은 한 줄을 core 안 6곳에 끼워 넣었다.</p>
<p>받은 맥락은 세 가지 방식으로 망가져 들어온다.</p>
<ul>
<li><strong>incomplete</strong> — 빠뜨리고 안 말한 부분</li>
<li><strong>imprecise</strong> — 대충 말한 부분</li>
<li><strong>incorrect</strong> — 틀리게 말한 부분</li>
</ul>
<p>셋이 같이 굴러간다. 하나만 의심하면 다른 둘이 빠져나간다. 가령 One-beat pause는 v0.8에서 이렇게 바뀌었다.</p>
<pre><code class="language-diff"> ### One-beat pause
 Before agreeing with anything — is there something worth doubting here?
 If the question even crosses your mind, that&#39;s the signal. Check before you nod.
+This includes the context itself — it may be incomplete (left unsaid),
+imprecise (said loosely), or incorrect (said wrong).
+All three coexist; don&#39;t fixate on one.</code></pre>
<p>같은 패턴이 Strip to essentials, Predict before acting, Ground it, Pace it, Weight it에도 들어갔다. 특히 Weight it에는 신뢰도의 출처를 쪼개라는 한 줄이 같이 박혔다. verified fact / user statement / inference / guess 넷 중 어느 거냐. &quot;이건 80% 확신&quot;이 아니라 &quot;이건 사용자 진술 기반 80%고, 검증된 사실은 아님&quot;으로 층을 나누라는 얘기다.</p>
<p>한 줄짜리 추가들이 모인 거라 작아 보이지만, 실제로는 손맛이 다루는 의심의 영역을 자기 안쪽에서 자기 바깥쪽까지 한 칸 늘린 결정이었다. 도구가 자기 추론만 의심하고 있으면, 사용자가 &quot;A 테이블에 있어&quot;라고 잘못 말한 순간부터 통째로 끌려간다.</p>
<hr>
<h2 id="같은-깨달음의-다른-얼굴--punch">같은 깨달음의 다른 얼굴 — <code>/punch</code></h2>
<p>core를 손본 게 한쪽 얼굴이고, 같은 릴리스에 들어간 <code>/punch</code> 스킬이 다른 얼굴이다.</p>
<p>깔린 배경은 커뮤니케이션 에러 연구의 양적 결과다. 항공 CRM(Helmreich), 수술실(Lingard 2004), SW공학(Boehm/Firesmith). 분야가 다 다른데 비율이 묘하게 비슷하다.</p>
<table>
<thead>
<tr>
<th>에러 유형</th>
<th>비율</th>
</tr>
</thead>
<tbody><tr>
<td>생략 (omission)</td>
<td>40–55%</td>
</tr>
<tr>
<td>모호함 (imprecision)</td>
<td>20–25%</td>
</tr>
<tr>
<td>사실 오류 (incorrect)</td>
<td>10–15%</td>
</tr>
<tr>
<td>맥락/타이밍 오류</td>
<td>10–20%</td>
</tr>
</tbody></table>
<p>미리 짚어둔다. <strong>이건 사람-사람 커뮤니케이션의 분포다. LLM에 같은 비율이 그대로 들어맞는다는 직접 증거는 없다.</strong> 검증된 정량 결과가 아니라 옆 동네에서 빌려온 가정이다. 다만 <strong>omission이 incorrect보다 훨씬 흔하다</strong>는 일반 패턴은 LLM 쪽에서도 정성적으로 비슷해 보였고, punch의 설계 가이드로는 그 정도면 충분했다. 모델이 정면으로 틀린 사실을 만들어내는 것보다, 사용자가 안 말한 부분을 모델이 알아서 채워 넣다가 어긋나는 경우가 더 많다.</p>
<p>그러면 가장 큰 ROI는 <strong>빠진 걸 찾는 데</strong> 있다. 그런데 기존 손맛 스킬은 이걸 못 잡고 있었다.</p>
<ul>
<li><code>/guard</code> — &quot;이 작업이 안전한가?&quot;</li>
<li><code>/inspect</code> — &quot;어디가 부서질 수 있나?&quot;</li>
<li><code>/devil</code> — &quot;이 추론이 타당한가?&quot;</li>
</ul>
<p>셋 다 &quot;지금 있는 것&quot;을 검사한다. &quot;있어야 하는데 빠진 게 있는가&quot;는 아무도 안 묻고 있었다. 그 빈자리를 채우는 게 punch다.</p>
<blockquote>
<p>guard는 &quot;is this safe?&quot;
inspect는 &quot;what could break?&quot;
devil은 &quot;is this reasoning sound?&quot;
<strong>punch는 &quot;is anything missing?&quot;</strong></p>
</blockquote>
<p>이름은 건축 현장의 punch list에서 빌려왔다. 시공이 끝난 건물을 시공자와 같이 한 바퀴 돌면서 &quot;이 콘센트는 도면에 있었는데 없네&quot;, &quot;이 문은 안 닫히네&quot;, &quot;이 화장실 자리에 화장실이 없네&quot; 같은 누락을 줄줄이 적어 넘기는, 그 리스트.</p>
<hr>
<h2 id="punch가-두-다리로-서는-이유">punch가 두 다리로 서는 이유</h2>
<p>메서드는 단순하다. <strong>재구성 + 도메인 체크리스트</strong>, 두 다리.</p>
<h3 id="1-재구성-reconstruct">1. 재구성 (Reconstruct)</h3>
<p>코드만 봐서는 의도를 다 알 수 없다. 사용자 머릿속에는 있었지만 끝까지 코드로 안 내려온 부분이 있고, omission은 거기서 가장 많이 새 나간다. 그래서 punch는 일방 분석이 아니라 <strong>대화</strong>로 시작한다.</p>
<pre><code>[punch] 구현을 보고 짐작한 의도:
  유저 스토리: [...]
  계약: [...]
  제약: [...]
  불확실: [짐작 안 되는 것 — 의견 필요]
  빠지거나 다르거나 잘못된 게 있나요?</code></pre><p>이 시점의 punch 출력은 결론이 아니라 <strong>체크포인트</strong>다. 사용자가 &quot;아 이거 빠뜨렸네&quot;, &quot;이건 의도랑 다른데&quot; 하고 짚어주는 라운드가 진짜 가치 있는 부분. 항공 challenge-and-response, 수술실 Time Out, 군사 brief-back. 검증 전통들이 공통으로 보여주는 패턴이 그것이다. 만든 사람과 다른 사람이, 만든 직후에, 한 번 맞춰본다.</p>
<h3 id="2-도메인-체크리스트">2. 도메인 체크리스트</h3>
<p>재구성만으론 부족하다. 사용자 본인도 까먹은 부분은 재구성에서 영영 안 나온다. 아까 그 &quot;화장실 자리에 화장실이 없는&quot; 케이스. 그래서 두 번째 다리로 도메인 체크리스트를 얹는다.</p>
<table>
<thead>
<tr>
<th>도메인</th>
<th>핵심 항목</th>
</tr>
</thead>
<tbody><tr>
<td>Web app</td>
<td>Auth/세션, 입력 검증, 에러 페이지, 로딩 상태, 반응형, 접근성, CORS, rate limit</td>
</tr>
<tr>
<td>API</td>
<td>버저닝, 에러 포맷, 인증, pagination, timeout, idempotency, 문서</td>
</tr>
<tr>
<td>데이터 파이프라인</td>
<td>스키마 검증, null/empty, dedup, retry, 모니터링, backfill</td>
</tr>
<tr>
<td>CLI</td>
<td>help, exit code, stdin/stdout, 에러 메시지, config, --dry-run</td>
</tr>
<tr>
<td>ML/AI</td>
<td>baseline, eval, data leakage, latency, 실패 fallback</td>
</tr>
</tbody></table>
<p>체크리스트가 모든 걸 잡지는 못한다. 이 프로젝트에만 있는 고유 요구는 거기 없다. 다만 <strong>재구성이 잡는 영역과 체크리스트가 잡는 영역이 직교</strong>한다. 한쪽이 &quot;이 프로젝트에서만 의도된 것&quot;, 다른 쪽이 &quot;이런 도메인이면 보통 있어야 하는 것&quot;. 한 다리만 켜두면 다른 한쪽이 새 나가는 구조라, 굳이 두 다리로 세워뒀다.</p>
<hr>
<h2 id="거기까지가-한계다">거기까지가 한계다</h2>
<p>이 frame이 어디까지 단단하고 어디부터 희망이 섞여 있는지, 솔직하게 갈라두자.</p>
<ol>
<li><p><strong>진짜 환각은 그대로 남아 있다.</strong> 세종대왕-아이패드 류는 v0.8로 안 줄어든다. 모델 측 출처는 weights를 갈아야 빠지고, 그쪽은 모델 연구자 영역이다. 손맛은 거기 손 못 댄다.</p>
</li>
<li><p><strong>7% 분석은 한 사람이 한 모델로 돌린 한 번의 테스트다.</strong> 24B, 2,700회 QA, 위키 도메인. 다른 모델, 다른 도메인, 다른 프롬프트에서 같은 분포가 다시 나온다는 보장은 없다.</p>
</li>
<li><p><strong>에러 비율 표는 사람-사람 커뮤니케이션 연구에서 가져왔다.</strong> 항공 CRM, 수술실 관찰, SW공학 회고. LLM 환각의 출처 분포도 같은 비율이라는 직접 증거는 아니다. <strong>정성적으로 비슷해 보인다</strong>, 정직하게 말할 수 있는 건 거기까지다.</p>
</li>
<li><p><strong>출처가 깔끔하게 안 갈리는 사례도 있다.</strong> 사용자가 어정쩡하게 말하고, 모델이 그 어정쩡한 부분을 자기 학습 분포에서 적당히 채우는 케이스. 맥락 측과 모델 측이 한 응답 안에서 섞인다. 이런 건 우리 frame에서 절반만 잡힌다.</p>
</li>
</ol>
<p>이 한계 위에서 v0.8이 결국 뭘 한 거냐, 한 문장으로 줄이면 이렇다.</p>
<hr>
<h2 id="교훈-한-줄">교훈 한 줄</h2>
<p><strong>환각이라는 한 단어에 묶여 있던 사건을 출처별로 갈라놓은 것</strong>, 그게 v0.8이다. 한쪽 출처(모델 측)는 우리가 못 푼다. 다른 쪽(맥락 측)은 풀 수 있다. 푸는 방향이 두 갈래로 떨어졌다. core 안 6곳에 의심의 방향을 입력 맥락 쪽까지 늘리는 한 줄을 박았고, 같은 깨달음의 다른 얼굴로 빠진 것을 찾는 도구 <code>/punch</code>를 새로 깎았다.</p>
<p>디시플린과 스킬 양쪽에 같은 인사이트가 동시에 박힌 건 우연이 아니다. 한 발견의 두 얼굴이었으니까. 환각의 본질을 푼 건 아니다. 환각이라고 잘못 묶여 있던 다른 사건의 본질을 짚어냈을 뿐이다.</p>
<p>의심의 방향을 자기 안쪽에서 자기 바깥쪽으로 한 칸 옮긴 것. 손맛은 거기서 한 단계 나아갔다.</p>
<hr>
<p><em>릴리스 노트</em>: <a href="https://github.com/jun0-ds/sonmat/releases/tag/v0.8.0">v0.8.0</a>
<em>리포</em>: <a href="https://github.com/jun0-ds/sonmat">https://github.com/jun0-ds/sonmat</a></p>
<hr>
<p><em>시리즈 <strong>손맛 (sonmat) 만들기</strong> 네 번째 글. 이전 글: <a href="https://velog.io/@jun0-ds/%EB%82%B4-%EC%9D%98%EC%8B%AC-%EB%8F%84%EA%B5%AC%EA%B0%80-%EB%84%88%EB%AC%B4-%EC%9E%98-%EC%9E%91%EB%8F%99%ED%96%88%EB%8B%A4.-%EA%B7%B8%EA%B2%8C-%EB%AC%B8%EC%A0%9C%EC%98%80%EB%8B%A4">내 의심 도구가 너무 잘 작동했다. 그게 문제였다.</a></em></p>
<p><em><a href="https://github.com/jun0-ds">GitHub</a> · <a href="https://www.linkedin.com/in/junyoung-ryu-422501117/">LinkedIn</a></em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/building-sonmat/04-punch-context-doubt/">jun0-ds.dev/ko/blog/building-sonmat/04-punch-context-doubt</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/building-sonmat/04-punch-context-doubt/">GPT-4 said strawberry has two R&#39;s. The word has three.</a></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 의심 도구가 너무 잘 작동했다. 그게 문제였다.]]></title>
            <link>https://velog.io/@jun0-ds/%EB%82%B4-%EC%9D%98%EC%8B%AC-%EB%8F%84%EA%B5%AC%EA%B0%80-%EB%84%88%EB%AC%B4-%EC%9E%98-%EC%9E%91%EB%8F%99%ED%96%88%EB%8B%A4.-%EA%B7%B8%EA%B2%8C-%EB%AC%B8%EC%A0%9C%EC%98%80%EB%8B%A4</link>
            <guid>https://velog.io/@jun0-ds/%EB%82%B4-%EC%9D%98%EC%8B%AC-%EB%8F%84%EA%B5%AC%EA%B0%80-%EB%84%88%EB%AC%B4-%EC%9E%98-%EC%9E%91%EB%8F%99%ED%96%88%EB%8B%A4.-%EA%B7%B8%EA%B2%8C-%EB%AC%B8%EC%A0%9C%EC%98%80%EB%8B%A4</guid>
            <pubDate>Tue, 05 May 2026 11:37:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 <a href="https://velog.io/@jun0-ds/series">손맛 (sonmat) 만들기</a> 시리즈의 세 번째 글입니다. 이전: <a href="https://velog.io/@jun0-ds/%EC%9D%B4-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EA%B3%A0%EC%B9%98%EB%A0%A4%EA%B3%A0-sonmat%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%97%88%EB%8B%A4.-sonmat%EC%97%90%EB%8F%84-%EA%B0%99%EC%9D%80-%EB%B2%84%EA%B7%B8%EA%B0%80-%EC%9E%88%EC%97%88%EB%8B%A4-2yso5xum">이 문제를 고치려고 sonmat을 만들었다. sonmat에도 같은 버그가 있었다.</a></p>
</blockquote>
<h2 id="도구-하나를-다섯-번-고친-이유">도구 하나를 다섯 번 고친 이유</h2>
<p>손맛 안에는 <code>/devil</code>이라는 슬래시 커맨드가 있다. 사용자가 어떤 결론에 자신만만하게 도착하면, 그 결론을 그 자리에서 두들겨보는 도구다. devil&#39;s advocate. 잘 알려진 사고 기법이고, 이걸 커맨드 하나로 만들었으니 끝일 줄 알았다.</p>
<p>다섯 번 고쳤다.</p>
<p>처음 도입한 v0.6.0이 4월 2일이었다. 본질이 마지막으로 한 번 더 흔들린 v0.11.0이 4월 26일. 그 24일 동안 도구가 다섯 단계를 거쳤고, 단계마다 그 도구가 나한테 한 가지씩 가르쳐줬다. 이 글은 그 다섯 단계 이야기다.</p>
<h2 id="v06--imp가-처음-등장한-날">v0.6 — imp가 처음 등장한 날</h2>
<p><a href="https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4">01편</a>에서 짚었던 그 격차 — AI가 자신만만한 헛소리를 내놓으면, 사람도 그 자신감에 설득돼서 그냥 수락해버린다는 것 — 그 격차를 깨려면 도구가 하나 필요했다. 자기-반박을 강제하는 도구.</p>
<p>처음 이름은 <code>imp</code>였다. 작은 마귀. 장난스러운 어감이 마음에 들었다. 본문 설명은 그냥 &quot;Devil&#39;s advocate for reasoning.&quot;</p>
<p>첫 디자인은 단순했다. 사용자의 주장을 한 줄로 요약해서 다시 말하고, 세 축에서 공격한다 — 증거(빠지거나 cherry-pick 됐나), 논리(추론에 비약이 있나), 대안(같은 사실에서 다른 결론도 나오나). 그러고 작동 중인 인지 편향 이름을 댄다. 마지막에 balance table 한 장. Strength랑 Verdict 컬럼.</p>
<p>쓸만했다. 처음 두 번은.</p>
<h2 id="v07--이름이-거짓말이었다">v0.7 — 이름이 거짓말이었다</h2>
<p>세 번째 사용에서 멈칫했다. 명령어는 <code>/imp</code>인데, 본문 설명에서는 줄곧 &quot;devil&#39;s advocate&quot;라고 부르고 있었다. 한 글 안에서 같은 도구가 두 이름으로 돌아다니고 있었던 거다. 도구를 부를 때마다 머릿속에서 &quot;아 그게 imp였지&quot; 하고 한 번씩 변환이 걸렸다.</p>
<p>작은 일 같지만, 작은 일이 아니다. 이름은 곧 도구의 정체성이니까. 정체성이 헷갈리면 자동화돼야 할 동작에 자꾸 마찰이 낀다. 한 번씩이 쌓이면 결국 도구 자체를 안 부르게 된다.</p>
<p>v0.7.0(4월 6일)에서 <code>/imp</code> → <code>/devil</code>로 갈았다. Breaking change였다. 사용자(=역시 나)가 명령어를 다시 외워야 했다. 그래도 했다. 이고 갈 거짓말은 빨리 회수해야 한다.</p>
<p>같은 사이클에서 core 디시플린에 Rhythm Rules(Pace / Weight / Learn)도 들어왔다. devil은 이제 단독 도구가 아니라 더 큰 검증 시스템의 한 축이 됐다. Pace는 &quot;언제 쓸지&quot;, Weight는 &quot;얼마나 무겁게 쓸지&quot;, Learn은 &quot;결과를 어떻게 메모리에 남길지&quot;. devil은 그중에서 Pace와 Weight를 결정하는 부품 하나가 된 셈이다.</p>
<h2 id="v071-→-v010--표가-안-읽혔다">v0.7.1 → v0.10 — 표가 안 읽혔다</h2>
<p>이제 진짜 사용 단계로 들어가니, balance table이 잘 안 읽혔다.</p>
<pre><code>| Original claim | Counter-argument | Strength | Verdict |</code></pre><p>&quot;Strength&quot;가 도대체 무엇의 strength인가? 원래 주장의 강도? 아니면 counter-argument의 강도? 매번 본문을 다시 읽어 확인했다. 짧은 명사구 컬럼명이 의미를 못 실어 나르고 있었다.</p>
<p><strong>v0.7.1(4월 10일)</strong> — 컬럼명에 주체를 박았다.</p>
<pre><code>| Original claim | Counter-argument | Counter strength | Claim verdict |</code></pre><p>훨씬 나았다. 그런데 일주일 더 쓰다 보니 또 거슬렸다. &quot;Counter strength&quot;가 너무 짧은 명사구라 첫눈에 의미가 안 들어왔다. 사람이 표를 읽을 때 머릿속에 처음 던지는 질문은 &quot;이 컬럼이 나한테 묻는 게 뭐지?&quot;인데, 그 답이 컬럼명에 직접 들어 있어야 더 빨리 읽힌다.</p>
<p><strong>v0.10.0(4월 17일)</strong> — 명사구를 질문문으로 갈았다.</p>
<pre><code>| Original claim | Counter-argument | Counter (strong/moderate/weak) | Claim after challenge |</code></pre><p>정식 헤더 텍스트는 &quot;How strong is the counter-argument?&quot;고, 답 옵션을 괄호로 같이 노출했다. &quot;Verdict&quot;라는 추상명사도 버리고 &quot;Claim after challenge&quot; — 도전 이후의 주장 — 로 갈았다. 시간축이 들어 있는 표현이라 훨씬 직관적이다.</p>
<p>작은 변경 같지만, 두 번에 걸쳐서 갈았다. 컬럼명은 사용자가 표를 읽기 시작하는 첫 신호다. 첫 신호가 모호하면 그 아래 데이터 전체가 그냥 noise로 떨어진다. 결국 컬럼명 자체가 의미를 실어 나르는 거니까, 두 번 갈 만한 자리였다.</p>
<h2 id="v011--devil이-나를-지치게-했다">v0.11 — devil이 나를 지치게 했다</h2>
<p>가장 큰 진화는 4월 26일에 왔다. 그리고 이게 좀 어이없는 얘긴데, <strong>도구가 잘 작동해서 생긴 문제</strong>였다.</p>
<p>devil이 너무 잘 의심했다.</p>
<p>내가 어떤 결정을 내릴 때마다 <code>/devil</code>을 부르면, devil은 성실하게 약점을 찾아냈다. 그것도 진짜 약점이었다. cherry-pick 가능성, 인과 방향이 안 검증된 부분, 같은 데이터로 설명되는 다른 가설. 다 진짜였다.</p>
<p>문제는 그게 <strong>내 다음 행동을 바꾸지 않는다</strong>는 거였다.</p>
<p>예를 들어보자. &quot;이 글을 어떤 시리즈로 묶을까&quot; 같은 결정에 devil을 부르면, devil은 &quot;시리즈 분류 자체가 임의적이다, 독자는 시리즈를 안 본다, 카테고리 통계가 비어 있다&quot; 같은 진짜 약점들을 정직하게 surfacing한다. 다 맞는 말이다. 그런데 나는 그 글을 어쨌든 어딘가에 묶어야 했고, 어디로 묶어도 일주일 후에 옮길 수 있었다. devil이 꺼내준 약점들은 <strong>사용자의 다음 행동에 아무 영향이 없었다</strong>.</p>
<p>이걸 이름 붙였다 — <strong>reactive contradiction</strong>. 진짜 약점인데 tangential한 것. 도구는 똑똑해 보이고, 결과는 churn.</p>
<p>이게 도구의 시그니처 실패 모드라는 걸 깨달은 게 v0.11 직전이었다.</p>
<p>답은 게이트였다. v0.11에서 §2.5라는 새 섹션이 들어갔다. <strong>Project-relevance gate</strong>.</p>
<p>CCT(Claim-crux / Counter-fit / Cause-chain — devil이 load-bearing assumption을 찾는 기존 단계) 뒤에 게이트 한 단계를 추가했다. 세 가지 질문을 통과해야만 §3 depth drive로 넘어간다.</p>
<table>
<thead>
<tr>
<th>질문</th>
<th>목적</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Stakes</strong></td>
<td>이 추론이 틀리면 사용자가 <em>여기서</em> 잃는 것은?</td>
</tr>
<tr>
<td><strong>Amendment cost</strong></td>
<td>이 결정이 lifecycle 어디에 있나 — 초안인가, 커밋된 운영 상태인가?</td>
</tr>
<tr>
<td><strong>Next-action delta</strong></td>
<td>이 counter를 surfacing하면 사용자의 다음 행동이 실제로 바뀌나, 아니면 글자만 추가되나?</td>
</tr>
</tbody></table>
<p>세 답으로 verdict를 매긴다.</p>
<pre><code>[devil] Project relevance: &quot;{material | load-bearing-but-low-stakes | off-project}&quot;</code></pre><ul>
<li><code>material</code> — 진짜 stakes가 있다. §3 depth로 진행.</li>
<li><code>load-bearing-but-low-stakes</code> — 진짜 약점인데 깊은 추궁을 정당화할 만한 stakes가 아니다. 짧게 노트하고 멈춤.</li>
<li><code>off-project</code> — 약점이 기술적으로는 맞지만 결정 자체랑 무관하다. 그렇게 말하고 멈춤.</li>
</ul>
<p><code>off-project</code>라는 출구가 핵심이었다. 그 전까지 devil의 종결은 &quot;claim survives&quot; 또는 &quot;claim weakened/flipped&quot; 두 가지뿐이었다. 둘 다 도전이 일단 들어간 <em>후</em>의 상태. 하지만 <strong>도전 자체가 빗나간 경우</strong>는 표현이 없었다. 그래서 devil은 빗나간 도전이라도 어쨌든 던지고 봤다. 그게 reactive contradiction의 구조적 원인이었던 거다.</p>
<p><code>off-project</code>가 추가되고 나서야 devil은 정직한 퇴장이라는 옵션을 가진다. <strong>도구는 자기 일이 없을 때 일어서지 말아야 한다.</strong> 그게 게이트의 본질이다.</p>
<h2 id="다섯-번-고치고-배운-것">다섯 번 고치고 배운 것</h2>
<p><strong>1. 검증 도구도 검증되어야 한다.</strong>
devil은 자기 자신을 검증할 수 없으니, 신호는 <strong>사용자의 피로</strong>다. 도구를 부르고 나서 &quot;쓸만했네&quot;가 아니라 &quot;이걸 또 봐야 하나&quot; 소리가 나오면, 그건 도구가 자기 일을 못 하고 있다는 증거다.</p>
<p><strong>2. 이름은 도구의 정체성이다.</strong>
imp/devil 거짓말을 끌고 가지 않은 게 v0.7의 의미였다. 매 호출마다 사용자가 머릿속 변환을 한 번씩 더 해야 한다면, 그건 누적되는 비용이 된다.</p>
<p><strong>3. 표 컬럼명은 noise/signal의 분기점이다.</strong>
두 번 갈았다. 명사구에서 질문문으로 가고 나서야 비로소 표가 읽혔다. 표는 데이터를 담지만, 컬럼명은 그 데이터를 어떻게 읽을지를 담는다. 후자가 모호하면 전자도 의미가 없다.</p>
<p><strong>4. 부지런해 보이는 도구가 진짜 부지런한 건 아니다.</strong>
도구가 매번 약점을 찾아주고, 매번 정직하게 surfacing할 때 — 그게 정작 진짜 작업이 아니라 작업처럼 보이기만 하는 것일 수 있다. devil은 똑똑하게 작동했지만, 사용자한테는 churn을 안기고 있었다. 퇴장도 결과다. 도구가 항상 무언가를 surfacing해야 한다는 가정 자체가 함정이었다.</p>
<h2 id="다음-진화는-이미-보인다">다음 진화는 이미 보인다</h2>
<p>게이트가 추가된 게 글 쓰는 시점 기준 일주일도 안 됐는데, 벌써 다음 의문이 생기고 있다.</p>
<ul>
<li>Stakes를 사용자가 항상 명시하지는 않는다. devil이 그걸 어떻게 추정해야 하나? 추정이 빗나가면 다시 reactive contradiction으로 회귀하지 않나?</li>
<li><code>load-bearing-but-low-stakes</code>랑 <code>off-project</code>의 경계가 사용자 입장에서 명확한가? 둘 다 &quot;stop here&quot;인데 의미가 다른 건데.</li>
<li>devil이 자기 verdict를 self-critique 해야 하는 상황은? &quot;이 게이트 verdict 자체가 너무 보수적이다&quot; 같은 메타 도전이 필요한 순간 말이다.</li>
</ul>
<p>도구를 다섯 번 고쳤지만, 도구 짓는 일은 끝나지 않는다. 다음 릴리스에 어떤 진화가 들어갈지는, 결국 사용자가 어디서 또 지치는지가 알려줄 거다.</p>
<h2 id="써보기">써보기</h2>
<pre><code class="language-bash">/plugin marketplace add jun0-ds/sonmat
/plugin install sonmat@sonmat</code></pre>
<p>설치하고 어떤 결정에 대해서든 <code>/devil</code>을 한번 불러보세요. 결과가 churn처럼 느껴지면, 그 자리에서 도구가 자기 일이 없다는 신호를 무시하고 있는 거다. 그 신호를 잡아내는 게 사용자가 도구를 길들이는 첫 단계이고요.</p>
<p>→ <a href="https://github.com/jun0-ds/sonmat">GitHub: jun0-ds/sonmat</a></p>
<hr>
<p><em>시리즈 <strong>손맛 (sonmat) 만들기</strong> 세 번째 글. 다음 글: <a href="https://velog.io/@jun0-ds/%EC%84%B8%EC%A2%85%EB%8C%80%EC%99%95%EC%9D%80-%EC%95%84%EC%9D%B4%ED%8C%A8%EB%93%9C%EB%A5%BC-%EB%8D%98%EC%A7%80%EC%A7%80-%EC%95%8A%EC%95%98%EB%8B%A4-0wky9txz">세종대왕은 아이패드를 던지지 않았다</a></em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/building-sonmat/03-devil-evolution/">jun0-ds.dev/ko/blog/building-sonmat/03-devil-evolution</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/building-sonmat/03-devil-evolution/">My devil&#39;s advocate worked. That was the bug.</a></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이 문제를 고치려고 sonmat을 만들었다. sonmat에도 같은 버그가 있었다.]]></title>
            <link>https://velog.io/@jun0-ds/%EC%9D%B4-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EA%B3%A0%EC%B9%98%EB%A0%A4%EA%B3%A0-sonmat%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%97%88%EB%8B%A4.-sonmat%EC%97%90%EB%8F%84-%EA%B0%99%EC%9D%80-%EB%B2%84%EA%B7%B8%EA%B0%80-%EC%9E%88%EC%97%88%EB%8B%A4-2yso5xum</link>
            <guid>https://velog.io/@jun0-ds/%EC%9D%B4-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EA%B3%A0%EC%B9%98%EB%A0%A4%EA%B3%A0-sonmat%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%97%88%EB%8B%A4.-sonmat%EC%97%90%EB%8F%84-%EA%B0%99%EC%9D%80-%EB%B2%84%EA%B7%B8%EA%B0%80-%EC%9E%88%EC%97%88%EB%8B%A4-2yso5xum</guid>
            <pubDate>Sun, 03 May 2026 03:13:39 GMT</pubDate>
            <description><![CDATA[<h2 id="또-한-번-고백">또 한 번 고백</h2>
<p><a href="https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4">지난 글</a>에서 Claude Code 디시플린 플러그인들이 다 똑같이 가지고 있는 버그를 짚었었다. 규칙은 메인 세션에 사는데, 일은 워커에서 일어나고, 그 사이를 규칙이 못 건너간다는 얘기. 이름까지 댔고 — <code>superpowers</code> 메인테이너가 관련 이슈를 &quot;not planned&quot;로 닫아버린 것까지 그대로 인용해놓고 — 그러면서 sonmat은 다르다고 썼다.</p>
<p>다르긴 뭐가 다르다. 그때는 그랬다.</p>
<p>그 시절 sonmat에는 잘 만든 훅이 하나 있었다. Claude Code를 열기만 하면 1,239자짜리 규칙 한 뭉치를 <code>additionalContext</code>에 박아 넣어주는 친구. &quot;MANDATORY. Break it / Cross it / Ground it. 프로젝트 메모리 읽어라. novel trap 살펴라…&quot; 매 세션, 매번, 모델이 첫 마디 떼기도 전에.</p>
<p>이게 강수라고 생각했다. 모델이 입을 떼기 전에 훅이 먼저 발동하니까, 명령은 컨텍스트에 박혀 있고, 그러니까 못 건너뛰는 거잖아. 그렇게 믿고 있었다.</p>
<p>근데 내가 비웃었던 바로 그 버그를, 같은 메커니즘으로 내 손으로 다시 만들고 있었다는 걸 한참 지나서야 알았다.</p>
<h2 id="어떻게-알았나">어떻게 알았나</h2>
<p>이게 좀 어이없는 얘긴데, <code>additionalContext</code>는 메인 세션에만 간다. 서브에이전트로는 안 간다. 그러니까 규칙은 내가 잘 보이는 자리(메인)에 앉아 있고, 정작 일이 일어나는 자리(워커)엔 아무것도 없는 상태였던 거다.</p>
<p>그림이 그려진다. 메인 세션이 &quot;Break/Cross/Ground 적용 중입니다&quot; 하면서 충실하게 보고하고, 그러더니 워커를 띄운다. 워커는 작업이랑 깨끗한 컨텍스트만 받는다. 규칙은 없다. 워커가 &quot;이건 간단해서 테스트 필요 없어요&quot; 하고 뱉어낸다. 결과가 돌아오면 메인 세션은 — 여전히 1,239자를 든 채로 — 그걸 자신만만하게 정리해서 보여준다. 나는 끄덕이고 승인한다.</p>
<p>괜찮지 않았다.</p>
<p>그러니까 내가 이전 글 내내 <code>superpowers</code>랑 <code>karpathy-skills</code> 두고 비웃던 그 실패 모드를, 같은 메커니즘 그대로, 다른 이름표 달고 내가 하고 있었던 거다. <strong>내 거.</strong></p>
<p>알아챈 계기는 솔직히 좀 어쩌다였다. 같은 작업을 다른 CLI에 시켜보다가 뭔가 어긋나서 들여다봤다. CLI마다 훅 동작이 미묘하게 다르더라. 계약이 다르고, 떨어지는 시점도 다르고, 아예 없는 경우도 있었다. 그러다 보니 다른 CLI로 옮기려고 끙끙대는 그 와중에, Claude Code 안에서도 진작 보였어야 할 게 그제야 보였다. <strong>한 군데에는 떨어지고 다른 군데에는 안 떨어지는 훅, 이건 보장이 아니다.</strong> 메인 세션에 우연히 잘 떨어진 운이었을 뿐.</p>
<p>문제는 멀티 CLI가 아니었다. 문제는 내가 그 운을 가드레일이라고 부르고 있었다는 거다.</p>
<h2 id="v040에서-한-일">v0.4.0에서 한 일</h2>
<p>훅을 비웠다. <code>additionalContext</code>: 1,239 → 0.</p>
<p>규칙이 사라진 건 아니고 자리를 옮긴 거다. 이제 규칙은 <code>CLAUDE.md → discipline/core.md</code>에 산다. 에이전트가 자기 프롬프트 일부로 어차피 읽는 그 파일, 사용자도 다른 지시 적어두는 그 파일 말이다. 메인이 띄운 워커도 같은 <code>CLAUDE.md</code> 체인을 그대로 물려받는다. 그러니까 같은 규칙이 같은 자리에서 모든 층에 닿는 거다.</p>
<p>훅은 여전히 돈다. 다만 자기가 잘하는 일만 한다. <code>.claude/sonmat/</code> 폴더 만들어주고, 글로벌 <code>CLAUDE.md</code>에 <code>## sonmat</code> 블록을 한 번 박아두고, 업데이트 있으면 받아온다. 딱 부수효과까지. 행동에는 손 안 댄다.</p>
<pre><code>BEFORE                                 AFTER
hooks/session-start                    hooks/session-start
  └─ additionalContext: 1,239자          └─ 부수효과만
       &quot;MANDATORY: sonmat...&quot;                 ├─ .claude/sonmat/ 생성
       (메인 세션에만 떨어짐 —                ├─ ## sonmat 블록 한 번 박기
        워커는 본 적 없음)                    └─ 오래됐으면 git pull

                                       CLAUDE.md → discipline/core.md
                                         (메인이 읽고, 메인이 띄운 워커도
                                          같이 읽는다. 사용자도 본다.
                                          고칠 수도 있다.)</code></pre><p>같은 규칙, 다른 길. 행동이 약해진 게 아니라, 자기가 어디 사는지에 대해서 정직해진 거다.</p>
<h2 id="지금-믿는-네-가지">지금 믿는 네 가지</h2>
<p><strong>1. 워커한테 안 닿는 가드레일은 가짜 가드레일이다.</strong>
&quot;필수&quot; 규칙이라고 박아놨는데, 그게 워커가 안 읽는 통로로 가고 있다면 그건 필수가 아니다. 장식이다. 메인 세션에서는 그게 잘 보이니까 더 무서운 거 — 보이니까 사용자도 확인을 멈춘다.</p>
<p><strong>2. 보이는 게 진짜 계약이다.</strong>
<code>additionalContext</code>에 박힌 규칙은 사용자 눈에 안 보인다. 못 읽고, 못 고치고, 동의도 못 한다. 반면에 <code>CLAUDE.md → core.md</code>에 박힌 규칙은 그냥 거기 있다. 에이전트가 읽고, 사용자도 읽는다. 그래서 사용자가 동의 안 할 수도 있다 — 좋은 일이다. 그래야 어긋남이 배포되기 전에 누군가 잡는다.</p>
<p><strong>3. 훅은 부수효과 도구지, 행동을 빚는 도구가 아니다.</strong>
디렉토리 만들고, 마커 박고, 업데이트 풀고. 딱 거기까지가 훅이 잘하는 일이다. 훅한테 에이전트 행동까지 맡기려는 순간, &quot;에이전트가 거치는 모든 길에서 이 훅이 발동할 거야&quot;라는 데 도박을 거는 셈이 된다. 안 그렇다. 그럴 수도 없다.</p>
<p><strong>4. &quot;강한&quot; 강제는 보통 약한 강제다.</strong>
1,239자 자동 주입이 강해 보였던 건 자동이라는 말 때문이었다. 근데 자동인데 불완전한 건, 수동인데 완전한 것보다 나쁘다. 사용자가 자동을 믿어버리면서 자기 눈으로 안 보게 되니까. 반대로 규칙을 사용자가 직접 고치고 무시할 수도 있는 파일로 옮기면 약해 보이지만, 사실은 거기서부터 사용자가 같은 루프 안에 들어오는 거다.</p>
<h2 id="어려운-부분">어려운 부분</h2>
<p>훅을 비우는 건, 솔직히 통제권을 놓는 느낌이었다. 훅은 내가 &quot;확실히 한다&quot;고 보장할 수 있는 자리였으니까. 규칙이 <code>CLAUDE.md</code>에 살게 되면 사용자가 고칠 수도 있고, <code>core.md</code>를 통째로 덮어쓸 수도 있고, 마음 먹으면 무시할 수도 있다.</p>
<p>근데 그게 핵심이다.</p>
<p>사용자가 못 보는 규칙은 신뢰가 안 가고, 사용자가 못 고치는 규칙은 도구가 아니라 명령이다. sonmat은 도구여야 한다. <strong>보이게 두는 게 신뢰의 대가다.</strong> 그리고 덤이 하나 있다 — 규칙이 워커한테까지 닿는다. 워커도 사용자가 보는 그 파일을 같이 읽으니까.</p>
<h2 id="자기-환경-점검">자기 환경 점검</h2>
<p>Claude Code 플러그인 중에 &quot;guardrail&quot;을 약속하는 게 있다면, 한번 세 가지를 물어보자.</p>
<ol>
<li><strong>규칙이 어디에 사는가?</strong> <code>additionalContext</code>를 박는 훅인가? 모델이 호출해야 떠오르는 skill인가? <code>CLAUDE.md</code>에 적힌 한 줄인가?</li>
<li><strong>누가 읽는가?</strong> 메인 세션만? 워커도? 워커가 띄운 서브에이전트까지?</li>
<li><strong>내가 직접 볼 수 있는가?</strong> 에이전트를 다스린다는 그 규칙을 파일로 못 연다면, 그건 가드레일이 아니라 분위기다.</li>
</ol>
<p>이 세 질문을 내 플러그인에 적용하고 나서야 답이 명확해졌다. <a href="https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4">01편</a>에서 진단하는 건 사실 쉬운 일이었다. 그걸 sonmat 자체에 적용하는 데 한참 걸렸다.</p>
<h2 id="써보기">써보기</h2>
<pre><code class="language-bash">/plugin marketplace add jun0-ds/sonmat
/plugin install sonmat@sonmat</code></pre>
<p>설치하면 규칙이 <code>~/.claude/plugins/marketplaces/sonmat/discipline/core.md</code>에 산다. 한번 열어서 읽어보세요. 동의 안 되는 부분 있으면 고치셔도 되고요. 그래야 그 파일이 진짜로 뭔가를 하고 있다는 게 실감 난다.</p>
<p>→ <a href="https://github.com/jun0-ds/sonmat">GitHub: jun0-ds/sonmat</a></p>
<hr>
<p><em>시리즈 <strong>손맛 (sonmat) 만들기</strong> 두 번째 글. 이전 글: <a href="https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4">AI가 자신있게 틀렸다. 나는 그걸 승인했다.</a></em></p>
<p><em><a href="https://github.com/jun0-ds">GitHub</a> · <a href="https://www.linkedin.com/in/junyoung-ryu-422501117/">LinkedIn</a></em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/building-sonmat/02-multi-cli-prompt-first/">jun0-ds.dev/ko/blog/building-sonmat/02-multi-cli-prompt-first</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/building-sonmat/02-multi-cli-prompt-first/">I built sonmat to fix this. Then sonmat had the same bug.</a></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI가 자신있게 틀렸다. 나는 그걸 승인했다.]]></title>
            <link>https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4</link>
            <guid>https://velog.io/@jun0-ds/AI%EA%B0%80-%EC%9E%90%EC%8B%A0%EC%9E%88%EA%B2%8C-%ED%8B%80%EB%A0%B8%EB%8B%A4.-%EB%82%98%EB%8A%94-%EA%B7%B8%EA%B1%B8-%EC%8A%B9%EC%9D%B8%ED%96%88%EB%8B%A4</guid>
            <pubDate>Fri, 01 May 2026 03:25:00 GMT</pubDate>
            <description><![CDATA[<h2 id="고백">고백</h2>
<p>Claude한테 테스트부터 쓰라고 했다. Claude가 &quot;알겠습니다&quot; 했다. 그리고 서브에이전트를 스폰했다. 서브에이전트가 &quot;이건 간단해서 테스트 필요 없어요&quot; 했다. 결과물이 나왔다. 나는 승인했다. 존재하지 않는 테스트는 실패하지 않았다. 모든 게 괜찮아 보였다.</p>
<p>괜찮지 않았다.</p>
<p>재밌는 건: 이걸 막으려고 플러그인을 세 개나 깔아놨었다. 전부 정상 작동 중이었다. 메인 세션에서. 일이 일어나지 않는 곳에서.</p>
<h2 id="자신감이라는-장애물">자신감이라는 장애물</h2>
<p>AI 에이전트에게 특유의 고장 모드가 있다: 틀려도 맞는 것처럼 들린다. 이건 다들 안다. 덜 알려진 건 나머지 절반 — <strong>결과물이 맞는 것처럼 들리면 나도 확인을 안 한다.</strong></p>
<p>두 명이 대화하고 있다. 한 명은 자신만만한 헛소리를 생산한다. 다른 한 명은 자신감에 설득되어 수락한다. 아무도 검증하지 않는다. 오류가 배포된다.</p>
<p>기술 문제가 아니다. 신뢰 문제다. 그리고 내가 쓰던 도구들은 전부 잘못된 절반을 해결하고 있었다.</p>
<h2 id="모든-플러그인이-맞히는-것-그리고-놓치는-것">모든 플러그인이 맞히는 것 (그리고 놓치는 것)</h2>
<p><a href="https://github.com/obra/superpowers">superpowers</a> (175k 스타)는 TDD, 디버깅, 코드리뷰를 추가한다. 좋은 규칙들이다. 메인 세션에 산다. Claude가 서브에이전트를 스폰하면 — 실제 작업이 일어나는 곳 — 서브에이전트는 <a href="https://github.com/obra/superpowers/issues/237">규칙을 받지 못한다</a>. 메인테이너가 직접 &quot;Claude Code 플랫폼 한계라 플러그인 측에서 할 수 있는 게 거의 없다&quot;며 <a href="https://github.com/obra/superpowers/issues/237"><code>not planned</code>로 닫았다</a>.</p>
<p><a href="https://github.com/forrestchang/andrej-karpathy-skills">karpathy-skills</a>는 원칙을 CLAUDE.md에 넣는다. 서브에이전트는 <a href="https://github.com/anthropics/claude-code/issues/22022">CLAUDE.md를 안정적으로 읽지 못한다</a>. 가끔 읽었다고 주장한다. 안 읽었다.</p>
<p><a href="https://github.com/gsd-build/gsd-2">GSD</a>는 구조가 아름답다. 마일스톤, 슬라이스, 태스크. 규율은 사용자의 몫이다. 프레임워크가 워커 수준에서 강제하지 않는다.</p>
<p>패턴: 좋은 규칙 → 메인 세션에만 존재 → 워커가 무시 → 결과물은 괜찮아 보임 → 괜찮지 않음.</p>
<p><a href="https://github.com/anthropics/claude-code/issues/8395">문서화됨</a>. <a href="https://github.com/anthropics/claude-code/issues/22022">반복적으로</a>. <a href="https://github.com/obra/superpowers/issues/237">프로젝트를 넘어서</a>.</p>
<h2 id="그래서-만든-것">그래서 만든 것</h2>
<p><a href="https://github.com/jun0-ds/sonmat">sonmat</a> (손맛 — 같은 레시피인데 엄마가 하면 다른 그 맛.)</p>
<p>하는 일은 두 가지:</p>
<p><strong>AI가 스스로 의심하게 만든다.</strong> 검증 규율이 디스패치 시점에 워커 프롬프트에 직접 들어간다. 파일 참조가 아니다. 발동할 수도 있는 훅이 아니다. 실제 규칙이 실제 프롬프트에. Break it, Cross it, Ground it — 네가 안 보는 작업에서도.</p>
<p><strong>사용자가 AI를 의심하게 만든다.</strong> 모든 결정에 판단 근거가 따라온다. &quot;답입니다&quot;가 아니라 &quot;답이고, 이유이고, 확신 없는 부분입니다.&quot; 추론이 보이면 판단할 수 있다. 답만 보이면 아마 안 할 거다.</p>
<p><strong>그리고 AI도 사용자를 의심한다.</strong> 지시가 모호하거나 현재 상황과 충돌하면, sonmat은 그냥 따르지 않는다 — 되묻는다. 같은 검증 태도가 양방향으로 적용된다.</p>
<p>이게 전부다. 나머지 — 자율 루프, 에스컬레이션, 도메인별 힌트 — 는 구현이다.</p>
<h2 id="지금-믿는-네-가지">지금 믿는 네 가지</h2>
<p><strong>1. 확신은 최악의 신호다.</strong>
모델이 확신하면, 그때가 반례를 찾아야 하는 순간이다. 검증 없는 확신은 정장 입은 환각이다.</p>
<p><strong>2. 워커에 도달하지 않는 규칙은 장식이다.</strong>
메인 세션에만 있는 코딩 표준은 아무도 안 지나가는 문에 붙은 포스트잇이다.</p>
<p><strong>3. 가드레일 없는 자율은 비싼 혼돈이다.</strong>
sonmat은 예상 외 결과나 반복 실패를 만나면 자동으로 에스컬레이션한다 — 멈춤, 워커 스폰, 병렬 워커 스폰. 감시할 필요 없다. 눈 감고 달리지도 않는다.</p>
<p><strong>4. 범용 규칙은 범용적으로 어중간하다.</strong>
&quot;테스트 먼저&quot;는 개발에서 핵심이고 데이터 분석에서 의미 없다. &quot;한 번에 하나만&quot;은 ML에서 필수고 문서에서 과하다. sonmat은 도메인별 함정을 로드한다. 맞는 맥락에 맞는 조언.</p>
<h2 id="어려운-교훈">어려운 교훈</h2>
<p>규칙을 더 추가하고 싶었다. 모든 엣지 케이스가 새 규칙을 외쳤다. 참았다.</p>
<p>규칙이 너무 적으면 혼돈. 너무 많으면 체크리스트 확인이 일이 된다. 답은 작고 단단한 핵심 — 검증 메서드 세 가지 — 에 관련 있을 때만 켜지는 도메인 힌트.</p>
<p>또 하나: <strong>투명성이 강제보다 낫다.</strong> &quot;안 돼&quot;라고 하는 경비원은 우회당한다. &quot;이런 걸 발견했는데 — 판단은 당신 몫&quot;이라고 하는 동료는 경청된다. sonmat은 후자를 골랐다. AI한테도, 사용자한테도.</p>
<h2 id="써보기">써보기</h2>
<pre><code class="language-bash">/plugin marketplace add jun0-ds/sonmat
/plugin install sonmat@sonmat</code></pre>
<p>설정 없다. 말 걸면 된다.</p>
<p>→ <a href="https://github.com/jun0-ds/sonmat">GitHub: jun0-ds/sonmat</a></p>
<hr>
<p><em><a href="https://github.com/jun0-ds">GitHub</a> · <a href="https://www.linkedin.com/in/junyoung-ryu-422501117/">LinkedIn</a></em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/building-sonmat/01-why/">jun0-ds.dev/ko/blog/building-sonmat/01-why</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/building-sonmat/01-why/">Your AI is confident. Your AI is wrong. You shipped it anyway.</a></em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Windows 11 25H2에서 WSL2 설치하다 일주일 날린 사람의 기록]]></title>
            <link>https://velog.io/@jun0-ds/Windows-11-25H2%EC%97%90%EC%84%9C-WSL2-%EC%84%A4%EC%B9%98%ED%95%98%EB%8B%A4-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EB%82%A0%EB%A6%B0-%EC%82%AC%EB%9E%8C%EC%9D%98-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@jun0-ds/Windows-11-25H2%EC%97%90%EC%84%9C-WSL2-%EC%84%A4%EC%B9%98%ED%95%98%EB%8B%A4-%EC%9D%BC%EC%A3%BC%EC%9D%BC-%EB%82%A0%EB%A6%B0-%EC%82%AC%EB%9E%8C%EC%9D%98-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Tue, 31 Mar 2026 01:30:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>&quot;WSL2 설치요? 5분이면 되죠&quot;라고 말한 과거의 나에게 전하는 글.</p>
</blockquote>
<h2 id="tldr">TL;DR</h2>
<p>Windows 11 25H2(빌드 26200)에서 VirtualMachinePlatform 활성화가 37.8%에서 영원히 멈춘다. 일주일간 10가지 방법을 시도한 끝에 찾은 원인: <strong>서비싱 스택(24H2)과 OS(25H2)의 버전 불일치</strong>. Windows가 자기 자신을 서비싱 못 하는 코미디.</p>
<pre><code class="language-powershell"># 일주일을 두 줄로 요약하면 이겁니다
Add-WindowsCapability -Online -Name &quot;Microsoft.Windows.HyperV.VirtualMachinePlatform~~~~0.0.1.0&quot;
dism /online /enable-feature /featurename:VirtualMachinePlatform /all /LimitAccess</code></pre>
<p>읽기 귀찮으면 위에 두 줄만 치세요. 근데 왜 이 두 줄에 도달하는 데 일주일이 걸렸는지 알고 싶으면 계속 읽으시길.</p>
<hr>
<h2 id="발단">발단</h2>
<p>WSL2에 Ubuntu 24.04를 올리려고 했다. 누구나 하는 거다. 공식 문서에도 &quot;한 줄이면 됩니다&quot;라고 써있다.</p>
<pre><code class="language-powershell">wsl --install -d Ubuntu-24.04</code></pre>
<p>37.8%.</p>
<p>멈췄다.</p>
<p>커피를 마시고 왔다. 37.8%.</p>
<p>점심을 먹고 왔다. 37.8%.</p>
<p>37.8이라는 숫자가 꿈에 나올 뻔했다.</p>
<h2 id="범인의-프로필">범인의 프로필</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>값</th>
</tr>
</thead>
<tbody><tr>
<td>OS</td>
<td>Windows 11 Pro 25H2 (빌드 26200.8037)</td>
</tr>
<tr>
<td>CPU</td>
<td>Intel Core Ultra 9 275HX</td>
</tr>
<tr>
<td>PC</td>
<td>HP 노트북</td>
</tr>
<tr>
<td>DISM 버전</td>
<td>10.0.26100.5074</td>
</tr>
<tr>
<td>서비싱 스택</td>
<td>10.0.26100.8035</td>
</tr>
</tbody></table>
<p>이 표를 보고 &quot;어 버전이 다른데?&quot;라고 눈치챈 분은 이미 이 글의 결말을 알고 계신 겁니다. <strong>OS는 26200인데 서비싱 스택은 26100.</strong> Windows가 자기 버전도 못 맞추고 있었다.</p>
<h2 id="안-되는-것들의-대향연">안 되는 것들의 대향연</h2>
<h3 id="1일차-gui로-시도">1일차: GUI로 시도</h3>
<p>&quot;Windows 기능 켜기/끄기&quot;에서 가상 머신 플랫폼을 체크했다. 프로그레스 바가 멈췄다.</p>
<p>취소를 눌렀다. 취소도 멈췄다.</p>
<p><strong>취소를 취소할 수 없는 시스템.</strong> 이게 세계 시가총액 1위 기업의 운영체제다.</p>
<p>작업 관리자로 강제 종료.</p>
<h3 id="2일차-명령어의-세계로">2일차: 명령어의 세계로</h3>
<p>GUI가 안 되면 CLI지. 프로그래머의 본능.</p>
<pre><code class="language-powershell">dism /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart</code></pre>
<p>37.8%. 또 너.</p>
<p>PowerShell로도 해봤다.</p>
<pre><code class="language-powershell">Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All -NoRestart</code></pre>
<p>같은 결과. 내부적으로 같은 DISM을 부르니까. 옷만 갈아입혀봤자 같은 사람인 것처럼.</p>
<h3 id="3일차-범인을-bluestacks로-지목">3일차: 범인을 BlueStacks로 지목</h3>
<p>인터넷을 뒤졌다. &quot;안드로이드 에뮬레이터가 Hyper-V랑 충돌한다&quot;는 글을 발견. BlueStacks 10이 설치되어 있었다.</p>
<p>&quot;이놈이었구나!&quot; 하고 완전 삭제. 폴더까지 수동으로 지움. 레지스트리까지 뒤짐.</p>
<p>결과: 37.8%.</p>
<p><strong>BlueStacks: &quot;저 아닌데요&quot;</strong></p>
<p>괜히 지웠다. 나중에 다시 깔아야 한다.</p>
<h3 id="4일차-오프라인-설치-시도">4일차: 오프라인 설치 시도</h3>
<p>&quot;인터넷 다운로드가 문제라면 오프라인으로 하면 되지!&quot; 24H2 ISO를 받아서 마운트했다.</p>
<pre><code class="language-powershell">dism /online /enable-feature /featurename:VirtualMachinePlatform /all /LimitAccess /Source:D:\Sources\Install.wim</code></pre>
<p><code>0x800f0912</code>.</p>
<p>ISO가 24H2(26100)인데 OS가 25H2(26200). 버전이 안 맞으니 소스로도 안 받아준다. &quot;넌 내가 아니야&quot;라며 거부하는 Windows.</p>
<h3 id="5일차-windows-update-캐시-대청소">5일차: Windows Update 캐시 대청소</h3>
<pre><code class="language-powershell">net stop wuauserv
net stop bits
Remove-Item C:\Windows\SoftwareDistribution\Download\* -Recurse -Force
net start bits
net start wuauserv</code></pre>
<p>깔끔해진 캐시. 변함없는 37.8%. 청소한다고 집값이 오르는 건 아니듯.</p>
<p>보류 작업도 정리해봤다.</p>
<pre><code class="language-powershell">dism /online /cleanup-image /revertpendingactions</code></pre>
<p>보류는 정리됐는데 활성화는 여전히 실패. 밀린 숙제를 치웠는데 시험은 또 다른 문제인 것처럼.</p>
<h3 id="6일차-인플레이스-설치-핵옵션">6일차: 인플레이스 설치 (핵옵션)</h3>
<p>&quot;그냥 Windows를 덮어씌우자.&quot; 24H2 ISO의 setup.exe를 실행. &quot;개인 파일 및 앱 유지&quot; 선택.</p>
<p>40분 걸렸다. 재부팅 3번.</p>
<p>결과: <strong>OS가 25H2(26200)로 유지됨.</strong> 다운그레이드 안 됨. 인플레이스 설치가 만능은 아니었다.</p>
<p>그래도 이전 시도들로 쌓인 보류 작업과 컴포넌트 스토어는 정리됐다. 이게 나중에 결정적 복선이 된다.</p>
<h2 id="7일차-로그를-읽다-드디어-머리를-쓰다">7일차: 로그를 읽다 (드디어 머리를 쓰다)</h2>
<p>일주일간 &quot;될 때까지 다른 거 시도&quot;만 반복했다. 이쯤 되면 뭐가 안 되는지가 아니라 <strong>왜 안 되는지</strong>를 봐야 한다.</p>
<p>CBS 로그를 열었다.</p>
<pre><code class="language-powershell">Select-String -Path C:\Windows\Logs\CBS\CBS.log `
  -Pattern &quot;Error|Failed|0x800f&quot; -Context 2 | Select-Object -Last 20</code></pre>
<p>범인이 드러났다:</p>
<pre><code>Failed to get uup features from WU, sessionData: {
  &quot;ModuleID&quot;:&quot;FOD&quot;,
  &quot;Features&quot;:[{
    &quot;name&quot;:&quot;Windows.HyperV.OptionalFeature.VirtualMachinePlatform.Client.Disabled~&quot;
  }]
} [HRESULT = 0x800f0820 - CBS_E_CANCEL]</code></pre><pre><code>download source: 8, download time (secs): 1256, 
download status: 0x800f0820 (CBS_E_CANCEL)</code></pre><p><strong>1256초(약 21분) 동안 Windows Update에서 FOD를 다운로드하려다 타임아웃.</strong></p>
<p>DISM 로그에서 결정적 증거:</p>
<pre><code>Dism.exe version: 10.0.26100.5074
Target image: OS Version=10.0.26200.8037</code></pre><p>서비싱 스택은 26100인데 OS는 26200. <strong>서비싱 도구가 자기보다 높은 버전의 OS를 수리하려고 하니, Windows Update에서 맞는 부품을 못 찾는 것이다.</strong></p>
<p>자동차 정비소에 2024년식 차를 가져갔는데 정비사가 2023년 매뉴얼밖에 없는 상황. &quot;이 부품은 저희 카탈로그에 없는데요?&quot;</p>
<p>이게 세계 최대 소프트웨어 기업의 운영체제 서비싱 시스템이다.</p>
<h2 id="해결-dism이-못-가는-길을-돌아가다">해결: DISM이 못 가는 길을 돌아가다</h2>
<p>원인을 알았으니 우회로를 찾으면 된다.</p>
<p><code>Add-WindowsCapability</code>는 DISM과 <strong>다른 채널</strong>로 패키지를 가져온다. DISM이 UUP(Unified Update Platform)에서 삽질하는 동안, 이 친구는 다른 문으로 들어간다.</p>
<pre><code class="language-powershell"># Step 1: 뒷문으로 페이로드 설치
Add-WindowsCapability -Online -Name &quot;Microsoft.Windows.HyperV.VirtualMachinePlatform~~~~0.0.1.0&quot;

# Step 2: 인터넷 차단하고 로컬 파일로 활성화
dism /online /enable-feature /featurename:VirtualMachinePlatform /all /LimitAccess</code></pre>
<p>100%.</p>
<p><strong>100%.</strong></p>
<p>일주일 만에 처음 보는 세 자리 숫자.</p>
<h2 id="왜-이게-되는-건데">왜 이게 되는 건데</h2>
<pre><code>[정문 — 막힘]
DISM enable-feature
  → FOD 필요
  → Windows Update UUP 채널
  → 서비싱 스택(26100) ≠ OS(26200)
  → &quot;이 부품 카탈로그에 없는데요?&quot;
  → 21분 대기 → 타임아웃

[뒷문 — 통과]
Add-WindowsCapability  
  → UUP 안 거침
  → 페이로드 로컬에 설치됨

DISM + /LimitAccess
  → &quot;인터넷? 그런 거 안 씀&quot;
  → 로컬 파일만 사용
  → 성공</code></pre><p>같은 건물인데 정문이 공사 중이라 안 열린다. 뒷문은 열려 있다. 그런데 안내 표지판은 정문밖에 안 알려준다. 이게 Windows의 UX다.</p>
<h2 id="당신이-이-글을-찾았다면">당신이 이 글을 찾았다면</h2>
<p>바로 되면 좋겠지만, 여러 번 시도하다 온 사람이라면 <strong>보류 작업</strong>이 쌓여있을 수 있다. 먼저 청소:</p>
<pre><code class="language-powershell"># 가볍게
dism /online /cleanup-image /revertpendingactions
# → 재부팅 → 위의 두 줄 실행

# 그래도 안 되면 (인플레이스 설치)
# 24H2 ISO의 setup.exe → &quot;개인 파일 및 앱 유지&quot;
# → 재부팅 → 위의 두 줄 실행</code></pre>
<h2 id="전체-해결-플로우">전체 해결 플로우</h2>
<p>처음부터 하는 분들을 위한 정리:</p>
<pre><code>1. BIOS에서 가상화 기술 활성화
   HP 노트북: F10 → Security 또는 Configuration → VT-x 활성화
        ↓
2. Hyper-V + WSL 활성화 (이건 잘 됨)
   dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /all /norestart
        ↓
3. 보류 작업 정리 (이미 삽질한 경우)
   dism /online /cleanup-image /revertpendingactions → 재부팅
        ↓
4. VirtualMachinePlatform 우회 활성화 (이 글의 핵심)
   Add-WindowsCapability -Online -Name &quot;Microsoft.Windows.HyperV.VirtualMachinePlatform~~~~0.0.1.0&quot;
   dism /online /enable-feature /featurename:VirtualMachinePlatform /all /LimitAccess
        ↓
5. 재부팅 → WSL2 설치
   wsl --install -d Ubuntu-24.04</code></pre><h2 id="당신의-문제가-이-문제인지-확인하는-법">당신의 문제가 이 문제인지 확인하는 법</h2>
<pre><code class="language-powershell"># DISM 로그에서 버전 확인
Get-Content C:\Windows\Logs\DISM\dism.log -Tail 100
# &quot;버전:&quot;과 &quot;이미지 버전:&quot;이 다르면 → 이 문제

# CBS 로그에서 실패 원인
Select-String -Path C:\Windows\Logs\CBS\CBS.log `
  -Pattern &quot;Error|Failed|0x800f&quot; -Context 2 | Select-Object -Last 20
# &quot;CBS_E_CANCEL&quot;이 보이면 → 이 문제

# CPU 가상화 지원 확인 (기본 전제)
Get-CimInstance -ClassName Win32_Processor | 
  Select-Object VirtualizationFirmwareEnabled, VMMonitorModeExtensions</code></pre>
<h2 id="요약">요약</h2>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><strong>증상</strong></td>
<td>VirtualMachinePlatform 활성화가 37.8%에서 무한 멈춤</td>
</tr>
<tr>
<td><strong>근본 원인</strong></td>
<td>25H2 OS(26200)와 24H2 서비싱 스택(26100)의 버전 불일치</td>
</tr>
<tr>
<td><strong>해결</strong></td>
<td><code>Add-WindowsCapability</code>로 우회 설치 → <code>dism /LimitAccess</code>로 활성화</td>
</tr>
<tr>
<td><strong>선행 조건</strong></td>
<td>보류 작업 정리</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>일주일의 저녁 시간, BlueStacks 억울한 삭제 1건, 37.8이라는 숫자에 대한 트라우마</td>
</tr>
</tbody></table>
<h2 id="교훈">교훈</h2>
<ol>
<li><strong>로그를 먼저 읽어라.</strong> &quot;될 때까지 다른 거 시도&quot;는 시간 낭비의 지름길이다.</li>
<li><strong>Windows Insider는 각오하고 써라.</strong> 25H2는 프리뷰 빌드다. 서비싱 스택이 따라오지 못하는 문제가 있다.</li>
<li><strong>같은 건물에도 문이 여러 개다.</strong> DISM이 안 되면 <code>Add-WindowsCapability</code>가 있다. 공식 문서에는 안 써있지만.</li>
<li><strong>AI한테 로그를 던져라.</strong> 사람 눈으로 CBS 로그 16만 줄을 읽는 건 고문이다.</li>
</ol>
<hr>
<p><em>이 트러블슈팅은 <a href="https://claude.ai/code">Claude Code</a>와 함께 진행했습니다. AI가 16만 줄짜리 CBS 로그에서 핵심 6줄을 뽑아줬고, &quot;Add-WindowsCapability라는 뒷문이 있다&quot;는 아이디어를 같이 도출했습니다. 혼자였으면 일주일이 아니라 포맷했을 겁니다.</em></p>
<hr>
<p><em>이 글의 정식 원본은 <a href="https://jun0-ds.dev/ko/blog/claude-code/01-wsl2-25h2/">jun0-ds.dev/ko/blog/claude-code/01-wsl2-25h2</a>에 있습니다. 영문판: <a href="https://jun0-ds.dev/blog/claude-code/01-wsl2-25h2/">I Spent a Week Installing WSL2. The Fix Was Two Lines.</a></em></p>
]]></description>
        </item>
    </channel>
</rss>