<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>HAHWUL (한국어)</title>
    <link>https://www.hahwul.com</link>
    <description>Offensive Security Engineer, Developer and H4cker.</description>
    <language>ko</language>
    <atom:link href="https://www.hahwul.com/ko/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Traveling with Hermes in Japan</title>
      <link>https://www.hahwul.com/ko/posts/2026/traveling-with-hermes-in-japan/</link>
      <guid>https://www.hahwul.com/ko/posts/2026/traveling-with-hermes-in-japan/</guid>
      <description>리모트 설정과 여행 중 프로젝트 워크플로우</description>
      <content:encoded><![CDATA[<p>지난 일주일 동안 일본 여행을 다녀왔습니다. 매년 가는 익숙한 나라지만, 올해는 특별한 실험을 하나 준비했습니다. 집에 있는 Mac Studio에 Hermes Agent를 세팅하고, Discord를 통해 여행 중에도 지시를 내리며 프로젝트 작업을 병행해보았습니다. 결과는 생각보다 만족스러웠고, 그 경험을 공유하려 합니다.</p>
<p>보통 AI 워크플로우에서 AI에게 오케스트레이터 역할을 주기도 하지만, 중요한 프로젝트는 직접 키를 잡고 진행하는 편입니다. 이번에는 원격으로 Hermes를 활용하면서 이동 시간과 여유 시간을 효율적으로 활용할 수 있었습니다. 물론 맥북도 함께 가져갔기 때문에 밤이나 아침 일찍 직접 작업하기도 했습니다.</p>
<h2 id="hermes-agent">Hermes Agent</h2>
<p><a href="https://hermes-agent.nousresearch.com">Hermes</a>는 Nous Research에서 만든 오픈소스 AI Agent로, OpenClaw의 대안으로 주목받고 있습니다. 핵심 기능은 자가 개선(self-improving)입니다. Agent가 작업을 수행한 뒤 스스로 결과를 검토하고, 패턴을 기억하거나 새로운 스킬로 만들어 재사용할 수 있게 합니다. 사용하면 할수록 더 똑똑해지는 구조죠.</p>
<p>개인적으로 자가 학습 컨셉을 좋아해 3월부터 로컬에서 테스트 중이었고, 이번에 실제 오픈소스 프로젝트에 적용해 보았습니다.</p>
<p><img loading="lazy" src="images/hermes.webp" alt="Hermes Agent" /></p>
<h3 id="setup">Setup</h3>
<p>Hermes는 다양한 Provider를 지원하므로 원하는 형태로 설정할 수 있습니다. 저는 Claude, Codex, Gemini 구독 사용량이 많아 상대적으로 여유 있는 GitHub Copilot을 가벼운 작업용으로 연결했습니다. (Copilot의 월간 쿼터를 채우기 어려웠던 터라 딱 맞았습니다)</p>
<p>Provider 처리는 가벼운 작업이라 Sonnet 4.6 모델을 주로 사용했으며, 비용을 더 아끼고 싶다면 Grok Code Fast도 좋은 선택일 것 같네요.</p>
<p><img loading="lazy" src="images/copilot.webp" alt="Output of hermes model" /></p>
<p>메시징 게이트웨이는 <a href="https://hermes-agent.nousresearch.com/docs/user-guide/messaging/discord">Discord로 세팅</a>했습니다. Discord 봇을 만들고 토큰을 Hermes에 등록하는 방식입니다.</p>
<p>개인적으로 보안 측면에서 메시징 채널은 매우 중요하다고 생각합니다. Discord에서는 봇 권한을 세밀하게 제어할 수 있고, Hermes 설정에서 특정 유저 ID만 허용하도록 ALLOWED_USERS를 지정할 수 있습니다. 이렇게 설정하는 경우 봇은 필요한 권한만 가지게 되며, 임의 유저가 마음대로 호출할 수 없도록 제한됩니다.</p>
<p><code>~/.hermes/.env</code>를 보면 아래와 같이 정의됩니다.</p>
<pre><code class="language-toml hljs">DISCORD_BOT_TOKEN=********
DISCORD_ALLOWED_USERS=********
DISCORD_HOME_CHANNEL=********
</code></pre>
<h3 id="workflow">Workflow</h3>
<p>코드 작업은 정교함이 필요하기 때문에 Hermes(Copilot)만으로는 퀄리티가 다소 부족합니다. 그래서 대부분의 실제 작업은 Claude Code, Codex, Gemini에게 위임하는 형태로 진행했습니다.
초기에는 지시가 여러 번 필요했지만, 대화가 누적된 이후부터는 Hermes가 상황에 따라 적절한 모델(Claude, Codex, Gemini)로 자동 분기해 주었습니다. Copilot은 주로 대화 채널과 가벼운 작업용으로 활용했습니다.
(특히 대화가 누적된 이후에 용도에 따라서 Hermes가 Claude, Codex, Gemini를 알아서 분기하는건 좀 매력적이었습니다)</p>
<div class="mermaid">flowchart LR
    A[Me] --&gt;|Send Message| B(Hermes Agent with Github Copilot)
    B --&gt; C{Thinking}
    C --&gt;|Write Code| D[Claude Code]
    C --&gt;|Code Refactoring| E[Codex]
    C --&gt;|Design Task| F[Gemini]
</div>
<h3 id="block-macos-sleep">Block macOS sleep</h3>
<p>Mac Studio를 장시간 방치하면 자동으로 슬립 모드로 전환되기 때문에 슬립 방지가 필요했습니다. 저는 직접 개발 중인 <a href="https://apps.apple.com/kr/app/nodecaf/id6762029386?l=en-GB&amp;mt=12">NoDecaf</a>라는 간단한 앱으로 이를 처리했는데, 실사용 테스트도 겸할 수 있어 좋았습니다.</p>
<h2 id="in-japan">In Japan</h2>
<p>출국 전에 충분히 테스트를 마치고 Discord로 주요 작업 지시를 남겨둔 뒤 여행을 즐겼습니다. 중간중간 피드백 요청이나 권한 승인 알림이 왔지만, 부담스럽지 않은 수준이라 가볍게 확인하며 처리할 수 있었습니다.</p>
<div class="images-full-width">


<div class="images-grid">
    
    <div class="images-grid-item">
        <img src="images/1.webp" alt="" loading="lazy">
    </div>
    
    <div class="images-grid-item">
        <img src="images/2.webp" alt="" loading="lazy">
    </div>
    
</div>
</div>
<p>대화가 누적된 이후에는 작업 지시도 간결하게 보내도 알아서 잘 하기에 만족스러웠네요. 특히 사용량 limit을 보고 대기 작업을 걸어주는 모습은 제게 살짝 감동을 주었습니다 :D</p>
<h2 id="generated-skill">Generated Skill</h2>
<p>Hermes가 작업 과정을 바탕으로 자동 생성한 스킬을 확인할 수 있었습니다. 위 이미지에서 보이는 <code>hwaro-examples-batch</code> 스킬이 대표적입니다. 제가 자주 시키는 <a href="https://github.com/hahwul/hwaro-examples">hahwul/hwaro-examples</a> 관련 작업 패턴을 스스로 학습해 스킬로 만들어준 것입니다.</p>
<p>스킬은 <code>~/.hermes/skills</code> 아래에 저장되며, built-in 스킬과 사용자 기반 스킬이 함께 관리됩니다. <code>hwaro-examples-batch</code>는 <code>~/.hermes/skills/github/hwaro-examples-batch</code> 경로에 있었고, SKILL.md를 보면 제가 주로 이 작업을 위해 트리거하는 말, 로컬 클론 경로 (이건 좀 특이한게, 제가 특정 경로를 사용해도 된다고 지정했지만 별도로 구성했네요. 아마 사용자 작업과의 충돌 방지 목적일 것 같습니다.) 등을 명시하고 작업을 처리하기 위한 스크립트 등을 담고 있습니다.</p>
<p><img loading="lazy" src="images/hwaro-example.webp" alt="Screenshot of SKILL" /></p>
<p>실제 수행 작업을 기반으로 만들기 때문에 어쩌면 SKILL을 직접 쓰는 것 보다 AGENT와의 작업 흐름을 기반으로 자동으로 만드는게 더 나은 선택일 수 있다는 생각이 드네요. hermes 굴리면서 스킬들 많이 뽑아내야겠습니다.</p>
<h2 id="jules">Jules</h2>
<p>그리고.. Hermes와 별개로 사실 여행중에도 <a href="https://jules.google">Jules</a> 또한 계속 자동으로 동작중이였습니다. Jules 작업은 간단한 작업 위주로 돌리고 있으며, 순수하게 클라우드 기반이라 집이던, 외부던 편하게 돌릴 수 있다는 장점이 있습니다. hwaro-example 쪽에도 schedule 작업으로 몇개 적용되어 있어 주기적으로 문제가 있는 페이지들을 식별하고 수정한 후 PR 보내는 작업을 진행하고 있습니다. 올라온 PR은 Hermes가 Codex 이용해서 처리하고 있구요.</p>
<p><img loading="lazy" src="images/jules.webp" alt="Jules" /></p>
<p>Jules 내 Enviroment 내 작업 환경 셋업을 해둔다면 실제 로컬 PC에서 구동하는 것과 크게 차이가 없어집니다.</p>
<h2 id="areas-for-improvement">Areas for Improvement</h2>
<p>편리함에는 보안적인 리스크가 따릅니다. 이 플로우로 진행하면서 느꼈던 점 중 하나는 권한 분리가 잘 되어야 한다는 점입니다. 특히 깃헙같이 높은 권한을 가진 계정은 Agent 전용 계정을 하나 더 만드는게 좋을거란 생각이 듭니다.</p>
<h2 id="conclusion">Conclusion</h2>
<p>대부분의 작업 시 맥 앞에 직접 앉아서 작업하다 보니 여행중에 핸드폰만 사용해서 AI를 잘 쓸 수 있을지가 가장 궁금했었습니다. 생각보다 편하게 작업을 이어갈 수 있었고, 이동 시간 등을 크게 아낄 수 있는 구조로 보여서 휴가가 끝나면 출퇴근에도 이러한 플로우를 적용해볼까 합니다. 물론 제대로된 작업은 직접 통제하는게 저 뿐만 아니라 AI에게도 편하고 결과도 좋지만, 충분히 스킬 기반으로 검토까지 가능한 대상(e.g., 테스트가 촘촘한 개발, 권한이 많이 필요없는 작업 등)들은 Hermes 통해서 틈틈히 처리하는게 더 좋아 보입니다.</p>
<p>온전히 내 시간을 즐기면서 자잘한 작업을 지속적으로 이어나갈 수 있다는 점은 정말 매력적인 것 같습니다. Hermes처럼 자가 개선 기능을 가진 Agent를 키워보는 것도 꽤 의미 있는 경험일 것 같습니다. 관심 있으신 분들은 한 번 시도해 보시길 추천드려요.</p>
]]></content:encoded>
      <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
      <category>ai</category>
      <category>hermes</category>
    </item>
    <item>
      <title>Building AI-Friendly CLIs</title>
      <link>https://www.hahwul.com/ko/posts/2026/building-ai-friendly-clis/</link>
      <guid>https://www.hahwul.com/ko/posts/2026/building-ai-friendly-clis/</guid>
      <description>JSON-First Design with Schema Commands</description>
      <content:encoded><![CDATA[<p>요즘 AI 에이전트가 코드를 짜고, 도구를 호출하고, 심지어 배포까지 하는 세상이 되면서 한동안 조용했던 CLI가 다시 주목받고 있습니다. GUI나 웹 대시보드는 사람에겐 편하지만, AI 에이전트 입장에서는 CLI가 훨씬 다루기 좋은 인터페이스거든요.</p>
<p>그런데 기존 CLI들은 사실 사람을 위해 설계되었습니다. 예쁜 테이블 출력, 컬러 코드, 축약된 플래그 등 사람이 읽기엔 좋지만 에이전트가 파싱하기엔 꽤 고통스러운 부분이 많습니다. 출력 포맷이 버전마다 미묘하게 바뀌기도 하고, 정확한 사용법을 알려면 문서를 따로 읽어야 하는 경우도 많죠.</p>
<p>이번주 동안 회사에서 팀 내부용으로 사용하는 CLI를 JSON I/O와 schema 명령어 기반으로 대폭 수정했고, 에이전트의 작업 성공률이 눈에 띄게 올라갔습니다. 이 경험을 바탕으로 AI-Friendly CLI를 만드는 방법에 대해 정리해보려 합니다.</p>
<h2 id="why-json-first-input-output-matters-for-ai-agents">Why JSON-First Input / Output Matters for AI Agents</h2>
<h3 id="human-vs-ai-cli-usage-patterns">Human vs AI CLI Usage Patterns</h3>
<p>사람은 CLI를 쓸 때 <code>--help</code>를 보고, man page를 읽고, 에러 메시지를 눈으로 확인하면서 반복적으로 시도합니다. 출력이 좀 달라져도 맥락을 파악해서 적응하죠. 반면 AI 에이전트는 출력을 문자열 그대로 받아서 처리합니다. 테이블 형태의 출력을 정규식으로 파싱해야 하고, 컬럼 순서가 바뀌거나 줄바꿈이 달라지면 바로 깨집니다.</p>
<pre><code class="language-bash hljs"># 사람에겐 이게 편하지만...
$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
my-app-7d4b8c6f5-x2k9z  1/1     Running   0          3d

# 에이전트에겐 이게 필요합니다
$ kubectl get pods -o json
{
  &quot;items&quot;: [{
    &quot;metadata&quot;: {&quot;name&quot;: &quot;my-app-7d4b8c6f5-x2k9z&quot;},
    &quot;status&quot;: {&quot;phase&quot;: &quot;Running&quot;, &quot;containerStatuses&quot;: [{&quot;ready&quot;: true}]}
  }]
}
</code></pre>
<h3 id="the-pain-of-unstructured-text-output">The Pain of Unstructured Text Output</h3>
<p>구조화되지 않은 텍스트 출력이 에이전트에게 주는 고통은 생각보다 큽니다. 실제로 제가 겪었던 패턴들을 몇 가지 나열해보면 아래와 같습니다.</p>
<ul>
<li>파싱 불안정: 출력에 헤더가 있을 때도 있고 없을 때도 있음</li>
<li>로케일 의존: 날짜/숫자 포맷이 시스템 로케일에 따라 달라짐</li>
<li>색상 코드 오염: ANSI escape code가 섞여 들어와서 문자열 비교 실패</li>
<li>Progress bar 충돌: stderr와 stdout이 섞이면서 출력이 꼬임</li>
<li>암묵적 truncation: 긴 값이 <code>...</code>으로 잘리는데 이를 알 방법이 없음</li>
</ul>
<p>이런 문제를 하나하나 예외 처리하다 보면 에이전트 코드가 CLI 파싱 로직으로 뒤덮이게 됩니다. 본질적인 작업보다 출력을 해석하는 데 더 많은 시간을 쓰게 되는 거죠.</p>
<h3 id="advantages-of-json">Advantages of JSON</h3>
<p>JSON 중심 입출력으로 전환하면 이 문제들이 대부분 사라집니다.</p>
<ul>
<li>타입 안전성: 숫자는 숫자, 문자열은 문자열. <code>&quot;3&quot;</code> vs <code>3</code>을 구분할 수 있음</li>
<li>스키마 기반 검증: JSON Schema로 입출력 형태를 사전 정의하고 검증 가능</li>
<li>체이닝 용이성: <code>jq</code>, 파이프라인, 프로그래밍 언어에서 바로 파싱 가능</li>
<li>일관성: 로케일, 터미널 설정과 무관하게 동일한 출력 보장</li>
<li>에러 구조화: 에러도 JSON으로 반환하면 에이전트가 에러 유형을 판단하고 적절히 대응 가능</li>
</ul>
<pre><code class="language-json hljs">{
  &quot;error&quot;: {
    &quot;code&quot;: &quot;RESOURCE_NOT_FOUND&quot;,
    &quot;message&quot;: &quot;Pod 'my-app' not found in namespace 'default'&quot;,
    &quot;suggestions&quot;: [&quot;Check namespace with --namespace flag&quot;]
  }
}
</code></pre>
<h3 id="real-world-test-results-from-our-project">Real-World Test Results from Our Project</h3>
<p>CLI에 <code>--json</code> 플래그를 추가하고 에이전트에게 동일한 태스크를 수행시켰을 때의 변화를 간단히 정리하면..</p>
<table>
<thead>
<tr>
<th>지표</th>
<th>텍스트 출력</th>
<th>JSON 출력</th>
</tr>
</thead>
<tbody>
<tr>
<td>태스크 성공률</td>
<td>60% 정도</td>
<td>90% 정도</td>
</tr>
<tr>
<td>평균 재시도 횟수</td>
<td>2.3회</td>
<td>0.4회</td>
</tr>
<tr>
<td>파싱 관련 에러</td>
<td>전체 에러의 41%</td>
<td>거의 0%</td>
</tr>
</tbody>
</table>
<p>물론 이건 제가 테스트한 특정 태스크들 기준이라 일반화하긴 어렵지만, JSON 전환만으로 이 정도의 차이가 나온다는 건 꽤 의미 있는 결과였습니다.</p>
<h2 id="the-schema-command-letting-ai-learn-and-adapt-at-runtime">The Schema Command – Letting AI Learn and Adapt at Runtime</h2>
<p>JSON I/O만으로도 큰 개선이 되지만, 여기서 한 단계 더 나아갈 수 있는 방법이 있습니다. 바로 schema 명령어입니다.</p>
<h3 id="inspiration-from-google-workspace-cli-gws">Inspiration from Google Workspace CLI (gws)</h3>
<p>이 아이디어는 <a href="https://github.com/googleworkspace/cli">Google Workspace CLI(gws)</a>에서 영감을 받았습니다. gws는 리소스별로 스키마 정보를 런타임에 조회할 수 있는 구조를 가지고 있는데요, 이를 보면서 &quot;에이전트가 문서를 읽는 대신 CLI에 직접 물어보면 되지 않을까?&quot;라는 생각이 들었습니다.</p>
<h3 id="how-the-schema-subcommand-works">How the Schema Subcommand Works</h3>
<p>개념은 단순합니다. CLI에 <code>schema</code>라는 서브커맨드를 추가하고, 리소스와 액션을 지정하면 해당 명령어의 입출력 JSON Schema를 반환하는 겁니다.</p>
<pre><code class="language-bash hljs">$ mytool schema user.create
{
  &quot;$schema&quot;: &quot;http://json-schema.org/draft-07/schema#&quot;,
  &quot;description&quot;: &quot;Create a new user&quot;,
  &quot;input&quot;: {
    &quot;type&quot;: &quot;object&quot;,
    &quot;required&quot;: [&quot;email&quot;, &quot;role&quot;],
    &quot;properties&quot;: {
      &quot;email&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;format&quot;: &quot;email&quot;},
      &quot;role&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;enum&quot;: [&quot;admin&quot;, &quot;member&quot;, &quot;viewer&quot;]},
      &quot;name&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;maxLength&quot;: 100}
    }
  },
  &quot;output&quot;: {
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot;: {
      &quot;id&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;format&quot;: &quot;uuid&quot;},
      &quot;email&quot;: {&quot;type&quot;: &quot;string&quot;},
      &quot;role&quot;: {&quot;type&quot;: &quot;string&quot;},
      &quot;created_at&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;format&quot;: &quot;date-time&quot;}
    }
  }
}
</code></pre>
<p>리소스 목록 자체도 조회할 수 있으면 더 좋습니다.</p>
<pre><code class="language-bash hljs">$ mytool schema --list
[&quot;user.create&quot;, &quot;user.delete&quot;, &quot;user.get&quot;, &quot;user.list&quot;, &quot;project.create&quot;, ...]
</code></pre>
<h3 id="benefits-for-agents">Benefits for Agents</h3>
<p>이 구조가 에이전트에게 주는 이점은 상당합니다.</p>
<ul>
<li>외부 문서 불필요: 에이전트가 CLI에 직접 물어보고 정확한 입력을 구성할 수 있음</li>
<li>API 변경에 자동 적응: CLI가 업데이트되면 schema도 함께 바뀌므로 에이전트가 항상 최신 스펙으로 동작</li>
<li>Dry-run과 결합: schema로 입력을 만들고 <code>--dry-run</code>으로 검증한 뒤 실행하는 안전한 워크플로우 가능</li>
<li>Self-describing: 별도의 AGENTS.md나 tool description 없이도 CLI 스스로 자신을 설명할 수 있음</li>
</ul>
<h3 id="our-implementation-overview">Our Implementation Overview</h3>
<p>저희 프로젝트에서는 다음과 같은 구조로 구현했습니다.</p>
<ol>
<li>각 명령어 핸들러에 input/output schema를 정의 (Pydantic 모델 기반)</li>
<li><code>schema</code> 서브커맨드에서 이를 JSON Schema로 직렬화하여 반환</li>
<li><code>--list</code> 옵션으로 전체 리소스/액션 트리를 탐색 가능하게 구성</li>
<li>schema 응답에 <code>examples</code> 필드를 포함시켜 에이전트가 참고할 수 있도록 함</li>
</ol>
<p>구현 자체는 크게 어렵지 않았습니다. 이미 Pydantic으로 입출력 모델을 정의하고 있었기 때문에 <code>.model_json_schema()</code>를 호출하는 것만으로 대부분 해결됐거든요.</p>
<h3 id="example-agent-workflow-using-schema">Example Agent Workflow Using Schema</h3>
<p>실제 에이전트가 schema를 활용하는 흐름을 보면 이런 식입니다.</p>
<pre><code>1. Agent: mytool schema --list
   → 사용 가능한 명령어 목록 확인

2. Agent: mytool schema user.create
   → 입력 스키마 확인 (필수 필드: email, role)

3. Agent: mytool user create --json '{&quot;email&quot;:&quot;new@example.com&quot;,&quot;role&quot;:&quot;member&quot;}' --dry-run
   → 실행 전 검증

4. Agent: mytool user create --json '{&quot;email&quot;:&quot;new@example.com&quot;,&quot;role&quot;:&quot;member&quot;}'
   → 실제 실행, JSON 응답 수신

5. Agent: 응답의 id 필드를 다음 작업에 활용
</code></pre>
<p>이 흐름에서 에이전트는 단 한 번도 문서를 참조하지 않습니다. CLI 자체가 문서 역할을 하는 거죠.</p>
<h2 id="practical-design-patterns">Practical Design Patterns</h2>
<p>JSON I/O와 Schema를 실제로 적용할 때 고려할 패턴들을 정리해봤습니다.</p>
<h3 id="input-design-choices">Input Design Choices</h3>
<p>입력 방식은 크게 세 가지가 있고, 상황에 따라 선택하면 됩니다.</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>예시</th>
<th>적합한 경우</th>
</tr>
</thead>
<tbody>
<tr>
<td>stdin JSON</td>
<td><code>echo &#39;{&quot;key&quot;:&quot;val&quot;}&#39; | mytool create</code></td>
<td>큰 페이로드, 파이프라인 체이닝</td>
</tr>
<tr>
<td>argument JSON</td>
<td><code>mytool create --json &#39;{&quot;key&quot;:&quot;val&quot;}&#39;</code></td>
<td>단일 명령 실행, 히스토리에 남기고 싶을 때</td>
</tr>
<tr>
<td>Mixed</td>
<td><code>mytool create --name foo --json &#39;{&quot;extra&quot;:&quot;opts&quot;}&#39;</code></td>
<td>자주 쓰는 옵션은 플래그로, 나머지는 JSON으로</td>
</tr>
</tbody>
</table>
<p>개인적으로는 argument JSON 방식을 기본으로 하되, stdin도 지원하는 형태를 추천합니다. 에이전트 입장에서는 하나의 명령어로 완결되는 게 가장 다루기 쉽거든요.</p>
<h3 id="output-design-best-practices">Output Design Best Practices</h3>
<p>출력 설계에서 몇 가지 중요한 원칙들이 있습니다.</p>
<ul>
<li><code>--json</code> 플래그는 필수: 기본은 사람이 읽기 좋은 형태를 유지하되, <code>--json</code>을 넣으면 구조화된 출력을 반환</li>
<li>NDJSON 지원: 스트리밍이 필요한 경우 (로그, 이벤트 등) 줄 단위 JSON 지원</li>
<li>에러도 JSON으로: <code>--json</code> 모드에서는 에러 역시 JSON 형태로 반환. exit code와 함께 사용</li>
<li>메타데이터 포함: pagination 정보, 요청 ID, 타임스탬프 등을 응답에 포함</li>
</ul>
<pre><code class="language-bash hljs"># 일반 모드
$ mytool user list
EMAIL              ROLE     CREATED
alice@example.com  admin    2026-01-15
bob@example.com    member   2026-02-20

# JSON 모드
$ mytool user list --json
{
  &quot;data&quot;: [
    {&quot;email&quot;: &quot;alice@example.com&quot;, &quot;role&quot;: &quot;admin&quot;, &quot;created_at&quot;: &quot;2026-01-15T00:00:00Z&quot;},
    {&quot;email&quot;: &quot;bob@example.com&quot;, &quot;role&quot;: &quot;member&quot;, &quot;created_at&quot;: &quot;2026-02-20T00:00:00Z&quot;}
  ],
  &quot;meta&quot;: {&quot;total&quot;: 2, &quot;page&quot;: 1, &quot;per_page&quot;: 50}
}
</code></pre>
<p>결과적으로 저는 json 출력을 기본으로 사용하고 --no-json을 추가하는 형태가 되었네요. 사람이 쓰지 않는 도구라면 입출력을 모두 JSON으로 통일하는게 작업 히트율이 가장 좋았습니다.</p>
<h3 id="versioning-amp-backward-compatibility">Versioning &amp; Backward Compatibility</h3>
<p>JSON 출력의 버저닝은 신경 써야 할 부분입니다. 몇 가지 전략을 공유하면 아래와 같습니다.</p>
<ul>
<li>필드 추가는 자유, 삭제/변경은 신중하게: 새 필드를 추가하는 건 하위 호환을 깨지 않지만, 기존 필드를 제거하거나 타입을 바꾸면 에이전트가 깨질 수 있음</li>
<li>버전 필드 포함: 응답에 <code>&quot;api_version&quot;: &quot;v1&quot;</code> 같은 필드를 넣어두면 에이전트가 버전에 따라 분기 가능</li>
<li>Deprecation 경고: 제거 예정인 필드는 별도 warnings 배열에 알림</li>
</ul>
<h3 id="using-pydantic-zod-json-schema-for-validation">Using Pydantic / Zod / JSON Schema for Validation</h3>
<p>스키마 정의에 사용할 수 있는 도구들입니다.</p>
<ul>
<li><strong>Python</strong>: Pydantic이 가장 편합니다. 모델 정의 → JSON Schema 자동 생성 → 입력 검증까지 한 번에</li>
<li><strong>TypeScript/Node</strong>: Zod로 스키마를 정의하고 <code>zod-to-json-schema</code>로 변환</li>
<li><strong>Go/Rust 등</strong>: JSON Schema 파일을 직접 작성하거나, 코드에서 생성하는 라이브러리 활용</li>
</ul>
<p>핵심은 코드에서 사용하는 타입 정의와 schema 명령어가 반환하는 스키마가 동일한 소스에서 나와야 한다는 겁니다. 이 둘이 따로 관리되면 결국 싱크가 깨집니다.</p>
<blockquote>
<p>사실 진행하던 개발 프로젝트에선 이 부분을 크게 신경쓰진 않았습니다. 덕분에 여러번의 실패가 있었죠.</p>
</blockquote>
<h3 id="ai-friendly-helper-flags">AI-Friendly Helper Flags</h3>
<p>schema와 JSON 외에도 에이전트 친화적인 플래그들을 추가하면 좋습니다.</p>
<ul>
<li><code>--dry-run</code>: 실제 실행 없이 결과를 미리 확인. 에이전트가 안전하게 시도해볼 수 있음</li>
<li><code>--explain</code>: 명령어가 수행할 작업을 자연어로 설명. 에이전트의 계획 수립에 도움</li>
<li><code>--output-format</code>: json, yaml, csv 등 출력 포맷 선택</li>
<li><code>--quiet</code>: 불필요한 배너, 경고를 제거하고 핵심 출력만 반환</li>
<li><code>--no-color</code>: ANSI escape code 제거 (이건 사실 모든 CLI에 있어야 합니다)</li>
</ul>
<h2 id="results-lessons-and-caveats-after-adoption">Results, Lessons, and Caveats After Adoption</h2>
<h3 id="quantitative-amp-qualitative-outcomes">Quantitative &amp; Qualitative Outcomes</h3>
<p>앞서 JSON 전환 결과를 공유했는데, schema 명령어까지 추가한 뒤의 변화도 정리해보면 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th>JSON만</th>
<th>JSON + Schema</th>
</tr>
</thead>
<tbody>
<tr>
<td>태스크 성공률</td>
<td>90% 정도</td>
<td>~97% (거의 대부분 성공)</td>
</tr>
<tr>
<td>에이전트의 첫 시도 정확도</td>
<td>~70%</td>
<td>~90% ( 솔직히 이 구간이 좋아졌습니다)</td>
</tr>
<tr>
<td>문서 참조 필요 횟수</td>
<td>태스크당 평균 1.2회</td>
<td>거의 0회</td>
</tr>
</tbody>
</table>
<p>빈도가 크진 않아서 의미 있는 수치는 아닐 수 있습니다. 다만 정성적으로 더 크게 느낀 부분은 에이전트 코드의 복잡도가 확 줄었다는 점입니다. 파싱 로직이 사라지고 비즈니스 로직에 집중할 수 있게 됐거든요.</p>
<h3 id="common-failure-patterns-we-observed">Common Failure Patterns We Observed</h3>
<p>물론 만능은 아닙니다. 에이전트가 자주 실패하는 패턴과 해결책을 정리하면:</p>
<ul>
<li>너무 큰 JSON 응답: 리스트 API에서 수천 개의 항목을 반환하면 에이전트의 컨텍스트 윈도우를 넘어감 → pagination과 필터링 필수</li>
<li>중첩이 깊은 구조: 5단계 이상 중첩된 JSON은 에이전트가 정확히 탐색하기 어려움 → 가능하면 flat하게</li>
<li>enum 값 오류: schema에 enum이 정의되어 있어도 에이전트가 가끔 유사하지만 다른 값을 넣음 → 입력 검증 + 명확한 에러 메시지</li>
<li>optional vs required 혼동: 에이전트가 optional 필드를 빠트리는 건 괜찮지만, required를 빠트리는 경우 발생 → schema에 required를 명확히 표시하고 에러 메시지에서 누락된 필드를 알려주기</li>
</ul>
<h3 id="remaining-challenges">Remaining Challenges</h3>
<p>아직 해결하지 못한 부분도 있습니다.</p>
<ul>
<li>복잡한 pagination: cursor 기반 pagination을 에이전트가 자연스럽게 처리하는 건 여전히 까다로움</li>
<li>Binary data: 파일 업로드/다운로드 같은 바이너리 데이터 처리는 JSON으로 깔끔하게 표현하기 어려움</li>
<li>Long-running operations: 수 분 이상 걸리는 작업의 상태 추적과 타임아웃 처리</li>
</ul>
<p>이런 부분들은 아직 좋은 답을 찾지 못해서 계속 고민 중입니다. 특히 Long-running 부분은 대부분 에이전트가 sleep을 자체적으로 걸면서 체크하고 있던데 상당히 비 효율적으로 보입니다. 반대로 agent가 callback 받을 수 있어야 할텐데, 이게 쉽진 않네요.</p>
<h2 id="conclusion">Conclusion</h2>
<p>정리하자면, 제가 AI-Friendly CLI를 만드는 핵심은 두 가지입니다.</p>
<ol>
<li>JSON-First I/O: 입출력을 구조화하여 에이전트가 안정적으로 파싱하고 활용할 수 있게 하기</li>
<li>Schema Command: CLI 스스로 자신의 인터페이스를 설명할 수 있게 하여 문서 의존성 제거</li>
</ol>
<p>이 두 가지만 갖춰도 에이전트 성능이 크게 향상되는 걸 직관했습니다. 당장 적용해보고 싶다면 가장 쉬운 시작점은 기존 CLI에 <code>--json</code> 플래그 하나를 추가하는 것입니다. 그것만으로도 에이전트와의 연동이 훨씬 수월해집니다.</p>
<p>다시 한번 CLI의 시대가 열렸습니다.</p>
<p><img loading="lazy" src="cli.webp" alt="" /></p>
]]></content:encoded>
      <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
      <category>ai</category>
      <category>cli</category>
      <category>development</category>
    </item>
    <item>
      <title>10년의 회고와 새로운 시작</title>
      <link>https://www.hahwul.com/ko/posts/2026/10years/</link>
      <guid>https://www.hahwul.com/ko/posts/2026/10years/</guid>
      <description>hahwul.com 10주년. 지난 10년을 돌아보고 앞으로의 방향을 공유합니다.</description>
      <content:encoded><![CDATA[<p>안녕하세요! 2026년의 첫 글이네요. 1월에 이 글을 내고 싶었으나, 여러가지로 작업이 길어지다 보니 이제서야 글을 올리게 되었습니다.</p>
<p>2026년이 되면서 하나 재미있는 사실이 있는데여, 놀랍게도 이 도메인을 사용한지, 즉 hahwul이란 이름으로 활동한지 딱 10주년이 되었습니다. 생각보다 긴 시간인데 빠르게 지나가서 놀랍네요. 오늘은 10년의 회고와 함께 블로그 운영, 컨텐츠, 방향성 관련해서 다소 큰 변경이 있어 이 글에서 해당 내용도 공유드릴까 합니다.</p>
<pre><code>Domain Name: hahwul.com
Registry Domain ID: 1992489747_DOMAIN_COM-VRSN
...
Updated Date: 2026-01-05T20:29:07Z
Creation Date: 2016-01-07T23:55:03Z
</code></pre>
<h2 id="10-years-ago">10 Years Ago</h2>
<p>사실 hahwul이란 이름을 사용하기 전에도 약 2년정도 따로 사용한 도메인이 있었고, 그 전에도 다른 블로그를 운영(꼬꼬마 시절) 했더지라 온라인에 글을 쓴지는 15년이 넘어갑니다. 물론 당시 블로그는 개발 관련 블로그고 그냥 내용을 정리하는 정도라 현재 모습을 있게 해준건 하훌(hahwul)이란 이름과 이 블로그입니다.</p>
<p>제 이름을 본 따 hahwul이란 이름을 만들고 이 이름으로 10년이란 시간 동안 정말 많은 글을 작성 했었습니다. 중간에 한두번 크게 정리했지만 약 1400개 정도의 글을 작성했었네요. 대부분 보안, 개발 기술에 대한 정보와 제 고민, 생각들이였고 글로 정리하는 행동은 몸에 지식을 새기기 때문에 많은 것들을 배우고 익힐 수 있는 순간이였다고 생각합니다.</p>
<p>그리고 제가 이렇게 글을 계속 쓸 수 있었던 것은 나름대로의 꾸준함도 있었겠지만, 계속 찾아봐주시는 많은 분들이 있었기에 가능하지 않았을까 싶습니다. 감사합니다!</p>
<div class="images-full-width">


<div class="images-grid">
    
    <div class="images-grid-item">
        <img src="images/1.png" alt="" loading="lazy">
    </div>
    
    <div class="images-grid-item">
        <img src="images/2.png" alt="" loading="lazy">
    </div>
    
    <div class="images-grid-item">
        <img src="images/3.png" alt="" loading="lazy">
    </div>
    
</div>
</div>
<h2 id="announcement">Announcement</h2>
<p>컨텐츠 방향성 관련하여 하나 공지하고 싶은 내용이 있습니다. 기존에 여러 정보를 다뤘던 블로그에 가까웠다면 이제는 좀 더 제 이야기와 생각을 나누고자 합니다.</p>
<p>물론 기존에 작성하던 형태의 글들이 없어지는 것은 아닙니다. 그저 재편되어 너무 오래되거나 가벼운 정보를 담은 글들은 제거되고 조금 더 탄탄한 형태로 만들어서 작성하려고 합니다. 계획중인 내용으로는 Posts 아래에는 기술에 대한 생각, 의견 등을 종종 남길 예정이고 Notes 하위에는 규격화되고 정리된 글을 남길 예정입니다. 그리고 여력이 된다면 시리즈 형태의 글들을 추가하려고 합니다. (e.g., ZAP 가이드 등)</p>
<h2 id="conclusion">Conclusion</h2>
<p>아무튼 결론으로 와서 다시 한번 감사의 인사를 드리고 싶습니다. 앞으로도 계속 글을 써내려갈 예정이니 재미있게 지켜봐주세요!</p>
<p><img loading="lazy" src="images/2026.jpg" alt="" /></p>
]]></content:encoded>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <category>announcement</category>
    </item>
  </channel>
</rss>
