<?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>Tue, 05 May 2026 11:39:49 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[내 의심 도구가 너무 잘 작동했다. 그게 문제였다.]]></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:39:49 GMT</pubDate>
            <description><![CDATA[<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> 세 번째 글. 다음 글: punch 도입 + context doubt를 core에 박은 이유 (v0.8)</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/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[내 의심 도구가 너무 잘 작동했다. 그게 문제였다.]]></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> 세 번째 글. 다음 글: punch 도입 + context doubt를 core에 박은 이유 (v0.8)</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/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>