<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Dannian's Warehouse</title>
    <link>https://dragoner.tistory.com/</link>
    <description>블로그에 재미를 붙일 겸, 공부나 업무하면서 알게 된 것 정리 등 이것 저것 올리는 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 13 Aug 2026 01:37:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Dannian</managingEditor>
    <image>
      <title>Dannian's Warehouse</title>
      <url>https://tistory1.daumcdn.net/tistory/833900/attach/ef43fa16d3b24e48af6b07cd49a40ed5</url>
      <link>https://dragoner.tistory.com</link>
    </image>
    <item>
      <title>[회고] 1주차 회고 (2026.07.20 ~ 07.24)</title>
      <link>https://dragoner.tistory.com/280</link>
      <description>&lt;h1&gt;1주차 회고 (2026.07.20 ~ 07.24)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;과정&lt;/strong&gt;: 원티드 포텐업 AI Agent 트랙 4기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주제 키워드&lt;/strong&gt;: 파이썬 기초 · Numpy/Pandas 입문 · AI 트렌드 감 잡기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;한 줄 소감&lt;/strong&gt;: 다시 학부생 때로 돌아간 느낌인데, 다만 술이 없는 학부생 느낌.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;  이번 주 숫자&lt;/strong&gt;: 블로그 3편 · 자습 12개(심화 5+연습 7) · 과제 3 · 인포그래픽 3 · 인텔 5일&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. 이번 주 커리큘럼&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;날짜&lt;/th&gt;
&lt;th&gt;학습 내용&lt;/th&gt;
&lt;th&gt;그날의 트렌드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;07.20&lt;/td&gt;
&lt;td&gt;오리엔테이션 · Markdown&lt;/td&gt;
&lt;td&gt;트렌드 소스·공모전 사이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07.21&lt;/td&gt;
&lt;td&gt;파이썬 기초 — 변수/자료형&lt;/td&gt;
&lt;td&gt;AI 툴 3종(Alt·NotebookLM·Perplexity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07.22&lt;/td&gt;
&lt;td&gt;파이썬 기초 — 제어문&lt;/td&gt;
&lt;td&gt;Agent의 발전(Orca/ADE), AI 생태계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07.23&lt;/td&gt;
&lt;td&gt;파이썬 기초 — 함수/클래스&lt;/td&gt;
&lt;td&gt;Loop Engineering, Gemini 3.6 Flash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07.24&lt;/td&gt;
&lt;td&gt;Numpy &amp;amp; Pandas&lt;/td&gt;
&lt;td&gt;지식 관리의 발전 — RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;날짜별 상세: &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/26.07.20/intel.md&quot;&gt;07.20&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/26.07.21/intel.md&quot;&gt;07.21&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/26.07.22/intel.md&quot;&gt;07.22&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/26.07.23/intel.md&quot;&gt;07.23&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/26.07.24/intel.md&quot;&gt;07.24&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;2. 트렌드로 알게 된 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI 툴&lt;/strong&gt;: Alt, NotebookLM, Perplexity — 실무 활용 감 잡기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에이전트&lt;/strong&gt;: Orca / ADE(Agent Development Environment) — 여러 AI를 한 팀처럼 운영&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;엔지니어링 4단계&lt;/strong&gt;: Prompt → Context → Harness → &lt;strong&gt;Loop Engineering&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지식 관리&lt;/strong&gt;: RAG(검색증강생성), Knowledge Graph, 파일 저장 포맷 변화(→ Markdown·JSON)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모델&lt;/strong&gt;: Google Gemini 3.6 Flash 출시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 내가 한 것 (산출물)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;과제&lt;/strong&gt;: &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/data_analysis/01.Assignment/02_%ED%8C%8C%EC%9D%B4%EC%8D%AC_%EA%B8%B0%EC%B4%88_%EB%AC%B8%EB%B2%95%28%EC%A0%9C%EC%96%B4%EB%AC%B8%29_%EA%B3%BC%EC%A0%9C_%EB%B0%95%EC%A4%80%ED%98%B8.ipynb&quot;&gt;제어문&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/data_analysis/01.Assignment/03_%ED%8C%8C%EC%9D%B4%EC%8D%AC_%EA%B8%B0%EC%B4%88_%EB%AC%B8%EB%B2%95%28%ED%95%A8%EC%88%98%29_%EA%B3%BC%EC%A0%9C_%EB%B0%95%EC%A4%80%ED%98%B8.ipynb&quot;&gt;함수&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/data_analysis/01.Assignment/06_%EB%8D%B0%EC%9D%B4%ED%84%B0_%EB%B6%84%EC%84%9D_%EA%B8%B0%EC%B4%88_%ED%8C%90%EB%8B%A4%EC%8A%A4_%EA%B3%BC%EC%A0%9C_%EB%B0%95%EC%A4%80%ED%98%B8.ipynb&quot;&gt;판다스&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;블로그 3편&lt;/strong&gt;: &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/04.Blog_Post_Documents/01_%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B8%B0%EC%B4%88-%EB%B3%80%EC%88%98%EC%99%80-%EB%AC%B8%EC%9E%90%EC%97%B4.md&quot;&gt;변수와 문자열&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/04.Blog_Post_Documents/02_%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B8%B0%EC%B4%88-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%99%80-%ED%8A%9C%ED%94%8C.md&quot;&gt;리스트와 튜플&lt;/a&gt; · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/04.Blog_Post_Documents/03_%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B8%B0%EC%B4%88-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC-%EC%A7%91%ED%95%A9-%EB%B6%88.md&quot;&gt;딕셔너리·집합·불&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자습&lt;/strong&gt;: &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/data_analysis/02.Self-Study/&quot;&gt;클래스 심화(OOP 실무) 5편 + 연습문제 7제&lt;/a&gt; — 수업 범위를 넘어 스스로 확장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인포그래픽 3건&lt;/strong&gt;: AI 에이전트 트렌드 · AI 엔지니어링 트렌드 · &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/02.infographic/03.AI_knowledge_storage_trend_infographic_data/&quot;&gt;지식 저장 트렌드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데일리 인텔 파이프라인 구축&lt;/strong&gt;: Slack에서 매일 AI 트렌드를 날짜별로 자동 수집·정리하는 체계 세팅&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. KPT 회고&lt;/h2&gt;
&lt;h3&gt;Keep — 잘했고 계속할 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;매일 트렌드를 &lt;code&gt;intel.md&lt;/code&gt;로 정리하는 습관 → 블로그·복습 자료가 자동으로 쌓임&lt;/li&gt;
&lt;li&gt;수업 진도를 그날 블로그로 소화(3편) - 그 외는 차후 복습하며 추가로 작성 예정&lt;/li&gt;
&lt;li&gt;자습으로 클래스 심화·연습문제까지 스스로 확장&lt;/li&gt;
&lt;li&gt;앞으로 뭘 더 할지 지속적으로 상담 및 고민 할 것&lt;/li&gt;
&lt;li&gt;1분 설명 카드를 만들기 위한 디자인 가이드라인 구축해두었고, 이를 통해서 보기 편하면서 핵심 내용들이 들어가있는 한장짜리 인포그래픽을 만들 수 있게 환경을 구축한 점.&lt;/li&gt;
&lt;li&gt;위의 인포그래픽을 통해서 어디서든 참고하며 공부할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Problem — 어려웠거나 아쉬운 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pandas &lt;code&gt;import&lt;/code&gt; 실패(스마트 앱 컨트롤)로 실습 시간 지연 → &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/03.%EC%98%A4%EB%A5%98_%EB%B0%8F_%ED%95%B4%EA%B2%B0_%EB%AA%A8%EC%9D%8C.md&quot;&gt;해결 기록&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RAG·Agent·Loop Engineering 등 새 용어가 한꺼번에 쏟아져 소화가 벅참&lt;/li&gt;
&lt;li&gt;Pandas는 사용방식이 조금 생소해서 더 공부할 필요가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Try — 다음에 시도할 것&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;새 용어는 그날 바로 &amp;quot;1분 설명&amp;quot; 카드로 정리하기&lt;/li&gt;
&lt;li&gt;Numpy/Pandas를 실제 데이터셋에 적용해보기&lt;/li&gt;
&lt;li&gt;데이터 전처리를 포함한 작업을 진행할 때 이번주에 부족한 부분이 보이면 추가 학습 진행할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 막혔던 것 &amp;amp; 해결&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제&lt;/th&gt;
&lt;th&gt;원인&lt;/th&gt;
&lt;th&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Pandas &lt;code&gt;import&lt;/code&gt; 시 &lt;code&gt;DLL load failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;윈도우 스마트 앱 컨트롤이 DLL 차단&lt;/td&gt;
&lt;td&gt;스마트 앱 컨트롤 끄고 VS Code 재시작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;상세: &lt;a href=&quot;https://github.com/pjh6954/Wanted_Potenup_AI_Agent_Track_4th/tree/main/01.daily_morning_intel_directory/03.%EC%98%A4%EB%A5%98_%EB%B0%8F_%ED%95%B4%EA%B2%B0_%EB%AA%A8%EC%9D%8C.md&quot;&gt;03.오류&lt;em&gt;및&lt;/em&gt;해결_모음.md&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;6. 다음 주 목표&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;학습&lt;/strong&gt;: 실제 CSV 데이터셋 1개로 로드 → 결측치 처리 → &lt;code&gt;groupby&lt;/code&gt; 집계까지 한 사이클 완주 (이번 주 생소했던 Pandas &lt;code&gt;loc&lt;/code&gt;/&lt;code&gt;iloc&lt;/code&gt; 포함)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;습관/태도&lt;/strong&gt;: 새 용어는 당일 &amp;#39;1분 카드&amp;#39;로 즉시 정리하고, 수업 후 복습 루틴을 밀리지 않게 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 한 줄 정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;생소한 Python 언어 학습과 새로운 정보들로 정신없었지만, 재밌는 일주일이었으며, 추가로 모르는걸 바로 정리하려 하는 새로운 습관을 정립하기 좋은 시간이라고 생각했다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>원티드 포텐업 AI Agent 개발 트랙 4기/회고록</category>
      <category>AI Agent 트랙</category>
      <category>PotenUp</category>
      <category>원티드 포텐업</category>
      <category>원티드 포텐업 4기</category>
      <category>회고</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/280</guid>
      <comments>https://dragoner.tistory.com/280#entry280comment</comments>
      <pubDate>Mon, 27 Jul 2026 13:26:08 +0900</pubDate>
    </item>
    <item>
      <title>[파이썬 기초 1편] 변수와 문자열 &amp;mdash; 에러를 일부러 내면서 배운 것들</title>
      <link>https://dragoner.tistory.com/278</link>
      <description>&lt;h1&gt;[파이썬 기초 1편] 변수와 문자열 &amp;mdash; 에러를 일부러 내면서 배운 것들&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 기초 문법 시리즈&lt;br /&gt;&lt;b&gt;1편. 변수 / 숫자형 / 문자열&lt;/b&gt; &amp;larr; 현재 글&lt;br /&gt;2편. 리스트 / 튜플&lt;br /&gt;3편. 딕셔너리 / 집합 / 불&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석 공부를 시작하면서 파이썬 기초 문법부터 다시 정리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 &quot;변수? 문자열? 이런 건 그냥 읽고 넘어가면 되는 거 아닌가&quot; 싶었는데, 막상 손으로 쳐보니 &lt;b&gt;생각대로 안 되는 지점&lt;/b&gt;이 계속 나왔습니다. 그래서 이번 정리는 문법 나열 대신, &lt;b&gt;제가 실제로 걸려 넘어진 부분&lt;/b&gt; 위주로 남깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법책을 눈으로 읽는 것과 셀을 직접 실행해서 빨간 에러를 보는 건 완전히 다른 경험이더군요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 변수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수는 결국 &quot;이름표&quot;&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;name = &quot;홍길동&quot;
position = &quot;대리&quot;
email = &quot;steadywing01@gmail.com&quot;
phone_number = &quot;019-8928-1927&quot;

print(name, position, email, phone_number)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;홍길동 대리 steadywing01@gmail.com 019-8928-1927&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 어렵지 않습니다. 문제는 &lt;b&gt;이름을 아무렇게나 지을 수 없다&lt;/b&gt;는 데서 시작했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 1. 대소문자를 구분한다&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Age = 30
age = 35
print(Age, age)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;30 35&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Age&lt;/code&gt;와 &lt;code&gt;age&lt;/code&gt;는 &lt;b&gt;완전히 다른 변수&lt;/b&gt;입니다. 사람 눈에는 같아 보여도 파이썬은 다르게 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 왜 중요하냐면, 나중에 변수가 수십 개로 늘어났을 때 &lt;code&gt;userName&lt;/code&gt;으로 저장해놓고 &lt;code&gt;username&lt;/code&gt;으로 불러오는 실수를 하면 &quot;분명히 저장했는데 왜 없다고 하지?&quot; 하면서 한참 헤매게 됩니다. 실제로 제가 그랬습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 2. 특수문자와 숫자로 시작하는 이름은 안 된다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부러 에러를 내봤습니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;item?count = 5&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;SyntaxError: invalid syntax&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;100days = 100&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;SyntaxError: invalid decimal literal&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑줄(&lt;code&gt;_&lt;/code&gt;)만 예외적으로 허용됩니다. 그래서 &lt;code&gt;phone_number&lt;/code&gt;처럼 쓰는 겁니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 3. ⚠️ 예약어를 변수로 쓰면 큰일 난다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 이번 실습에서 가장 인상 깊었던 부분입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;print = &quot;hello&quot;

print(print)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;TypeError: 'str' object is not callable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 일이 벌어진 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;print&lt;/code&gt;는 원래 &lt;b&gt;함수&lt;/b&gt;입니다. 그런데 &lt;code&gt;print = &quot;hello&quot;&lt;/code&gt;로 덮어쓰는 순간, &lt;code&gt;print&lt;/code&gt;라는 이름표가 함수에서 떨어져 나와 &lt;code&gt;&quot;hello&quot;&lt;/code&gt;라는 문자열에 붙어버립니다. 이제 &lt;code&gt;print(...)&lt;/code&gt;를 호출하면 파이썬은 &lt;b&gt;문자열을 함수처럼 호출하려고 시도&lt;/b&gt;하고, 당연히 실패합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무서운 건 &lt;b&gt;에러 메시지가 원인을 알려주지 않는다&lt;/b&gt;는 점입니다. &lt;code&gt;'str' object is not callable&lt;/code&gt;만 보고 &quot;내가 print를 덮어썼구나&quot;를 떠올리기는 쉽지 않습니다. 게다가 이 상태는 커널을 재시작하기 전까지 계속 유지되기 때문에, 이후에 작성하는 &lt;b&gt;모든 코드에서 print가 고장난 채로&lt;/b&gt; 작업하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;list&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt; 같은 이름들도 마찬가지입니다. &lt;code&gt;list = [1, 2, 3]&lt;/code&gt;처럼 무심코 쓰기 쉬운 이름이라 더 위험합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;규칙 4. ⚠️ 주피터에서는 &quot;셀 실행 순서&quot;가 곧 코드 순서다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주피터 노트북을 쓰면서 처음 당황했던 지점입니다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;# 커널 재시작 직후 실행
print(name)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;NameError: name 'name' is not defined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 위쪽 셀에 &lt;code&gt;name = &quot;홍길동&quot;&lt;/code&gt;이 &lt;b&gt;눈에 보이는데도&lt;/b&gt; 에러가 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 이겁니다. &lt;b&gt;파이썬은 화면에 보이는 순서가 아니라, 내가 실행 버튼을 누른 순서대로 기억한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널을 재시작하면 그동안 기억하던 변수가 전부 사라집니다. 화면의 코드는 그대로 남아있으니 &quot;저장은 되어 있는데 왜?&quot; 하고 착각하기 딱 좋습니다. 변수가 있는 셀을 다시 실행하면 정상 동작합니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;name = &quot;홍길동&quot;   # 이 셀을 다시 실행한 뒤에
print(name)      # 이 셀을 실행하면 정상&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;홍길동&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;교훈&lt;/b&gt;: 노트북 작업 중 이상한 에러가 나면, 코드를 의심하기 전에 &lt;b&gt;위에서부터 전부 다시 실행&lt;/b&gt;(Restart &amp;amp; Run All)해보는 게 빠릅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 숫자형&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정수와 실수&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;x = 5
y = -8
z = 0

print(x, y, z)
print(type(x), type(y), type(z))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;5 -8 0
&amp;lt;class 'int'&amp;gt; &amp;lt;class 'int'&amp;gt; &amp;lt;class 'int'&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;a = 3.14
b = -0.5
c = 1.0

print(type(a), type(b), type(c))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;class 'float'&amp;gt; &amp;lt;class 'float'&amp;gt; &amp;lt;class 'float'&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;1.0&lt;/code&gt;처럼 소수점 아래가 0이어도 &lt;b&gt;점이 찍혀 있으면 실수(float)&lt;/b&gt; 입니다. 이 구분은 나중에 나눗셈에서 다시 발목을 잡습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;큰 숫자는 밑줄로 끊어 읽자&lt;/h3&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;money = 100_000_000_000_000
print(money)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;100000000000000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;100000000000000&lt;/code&gt;을 눈으로 세는 건 고문입니다. 파이썬은 숫자 사이의 밑줄을 &lt;b&gt;그냥 무시&lt;/b&gt;하기 때문에, 읽기 좋은 위치에 끊어 쓸 수 있습니다. 출력할 때는 밑줄 없이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌는 건 밑줄 위치가 3자리 단위일 필요도 없다는 점입니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;money2 = 100_00_0_000_000_000
print(money2)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;100000000000000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법적으로는 통과하지만, 이렇게 쓰면 읽기 편하라고 만든 기능의 취지를 정면으로 배신하는 셈입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;지수 표현은 결과가 float이다&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;num = 1.0e3
print(num)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1000.0&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;num_under  = 2e+10
num_under2 = 2e-10
num_under3 = 0.000000321000011

print(num_under, num_under2, num_under3)
print(type(num_under), type(num_under2), type(num_under3))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;20000000000.0 2e-10 3.21000011e-07
&amp;lt;class 'float'&amp;gt; &amp;lt;class 'float'&amp;gt; &amp;lt;class 'float'&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 두 가지가 눈에 띄었습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;2e+10&lt;/code&gt;은 지수 표기로 썼지만 출력은 &lt;code&gt;20000000000.0&lt;/code&gt;으로 풀려서 나옵니다.&lt;/li&gt;
&lt;li&gt;반대로 &lt;code&gt;0.000000321000011&lt;/code&gt;은 그냥 소수로 썼는데 출력은 &lt;code&gt;3.21000011e-07&lt;/code&gt;로 지수 표기가 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;내가 어떻게 썼는지와 파이썬이 어떻게 보여주는지는 별개&lt;/b&gt;입니다. 파이썬은 &quot;그 숫자를 가장 짧게 표현하는 방식&quot;을 알아서 고릅니다. 값 자체는 동일하니 헷갈리지 않으면 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정수 + 실수 = 실수&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;num1 = 10      # int
num2 = 1.0     # float

result = num1 + num2
print(result, type(result))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;11.0 &amp;lt;class 'float'&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수와 실수를 섞으면 &lt;b&gt;더 정밀한 쪽인 실수로 통일&lt;/b&gt;됩니다. 정수 쪽에 맞춰 소수점을 버리면 정보가 사라지니까요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;산술 연산 7종&lt;/h3&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;num1 = 10
num2 = 3

print(num1 + num2)   # 13   덧셈
print(num1 - num2)   # 7    뺄셈
print(num1 * num2)   # 30   곱셈
print(num1 / num2)   # 3.3333333333333335   나눗셈
print(num1 // num2)  # 3    몫
print(num1 % num2)   # 1    나머지
print(num1 ** num2)  # 1000 거듭제곱&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 중요한 포인트 하나.&lt;/b&gt; &lt;code&gt;10 / 3&lt;/code&gt;은 나누어떨어지지 않으니 실수가 되는 게 당연해 보이는데, 사실 파이썬 3의 &lt;code&gt;/&lt;/code&gt;는 &lt;b&gt;나누어떨어져도 무조건 실수&lt;/b&gt;를 돌려줍니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;print(10 / 5)    # 2.0  (2가 아님!)
print(10 // 5)   # 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 결과가 꼭 필요하면 &lt;code&gt;//&lt;/code&gt;를 써야 합니다. 이걸 모르고 &lt;code&gt;/&lt;/code&gt;를 쓰면 인덱스 자리에 실수가 들어가서 &lt;code&gt;TypeError&lt;/code&gt;가 나는데, 나중에 리스트를 다룰 때 실제로 마주치게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복합 연산자&lt;/h3&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;money = 0

money += 2000   # money = money + 2000
money -= 500
money *= 3
money /= 3      # &amp;larr; 여기서 사고 발생
money **= 2
money //= 10
money %= 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계의 값을 찍어보면 이렇습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money += 2000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money -= 500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1500&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money *= 3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money /= 3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1500.0&lt;/code&gt; &amp;larr; &lt;b&gt;여기서 실수로 변함&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money **= 2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2250000.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money //= 10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;225000.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;money %= 10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;money /= 3&lt;/code&gt;을 지나는 순간 &lt;code&gt;4500&lt;/code&gt;이 &lt;code&gt;1500.0&lt;/code&gt;이 됩니다. 값은 같아 보이지만 타입이 int에서 float로 바뀐 거고, &lt;b&gt;이후 모든 연산이 float로 끌려갑니다.&lt;/b&gt; 마지막 &lt;code&gt;//&lt;/code&gt;와 &lt;code&gt;%&lt;/code&gt;까지도 &lt;code&gt;225000.0&lt;/code&gt;, &lt;code&gt;0.0&lt;/code&gt;처럼 소수점이 붙어서 나오죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돈 계산처럼 정수로 딱 떨어져야 하는 값에서 이런 일이 생기면 곤란합니다. 나눗셈 한 번이 타입 전체를 오염시킨다는 걸 기억해둘 만합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 1. 페이지네이션 &amp;mdash; &lt;code&gt;//&lt;/code&gt;와 &lt;code&gt;%&lt;/code&gt;를 같이 쓰는 대표 사례&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시물 100개를 한 페이지에 30개씩 보여주려면 총 몇 페이지가 필요할까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;100 // 30 = 3&lt;/code&gt;. 그런데 3페이지만 만들면 남는 10개는 어디에도 못 보여줍니다. &lt;b&gt;나머지가 있으면 페이지 하나를 더 만들어야&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;post = 100
post_per_page = 30

page_num = post // post_per_page          # 3
if post % post_per_page != 0:
    page_num += 1                          # 나머지 10개를 위한 4페이지

print(page_num)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄로 줄이면 이렇게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;page_num = (post // post_per_page) + (0 if post % post_per_page == 0 else 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;//&lt;/code&gt;(전체 몫)와 &lt;code&gt;%&lt;/code&gt;(남은 것)를 &lt;b&gt;짝으로 쓰는 패턴&lt;/b&gt;은 페이지 나누기, 배치 처리, 시간 변환(초 &amp;rarr; 분/초) 등에서 계속 나옵니다. 그냥 연산자 두 개가 아니라 하나의 관용구로 외워두는 게 좋겠더군요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 2. 홀짝 판별&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;birth_year = 1982
remainder = birth_year % 2   # 0이면 짝수, 1이면 홀수

if remainder == 0:
    print(&quot;짝수&quot;)
else:
    print(&quot;홀수&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;짝수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;% 2&lt;/code&gt;는 홀짝 판별의 표준 관용구입니다. 확장하면 &lt;code&gt;% 3 == 0&lt;/code&gt;은 3의 배수, &lt;code&gt;% 100 == 0&lt;/code&gt;은 100단위 판별이 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. ⭐ 문자열&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석에서 가장 많이 만지게 되는 자료형입니다. 실제로 수집한 데이터는 &lt;b&gt;거의 다 문자열 상태로 들어옵니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;따옴표 안에 따옴표 넣기&lt;/h3&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;message1 = &quot;I'm happy&quot;
message2 = 'I\'m happy'

print(message1)
print(message2)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;I'm happy
I'm happy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법이 두 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;바깥 따옴표를 다른 걸로 쓴다&lt;/b&gt; &amp;mdash; 안에 &lt;code&gt;'&lt;/code&gt;가 있으면 바깥은 &lt;code&gt;&quot;&lt;/code&gt;로. 가장 깔끔합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백슬래시로 탈출시킨다&lt;/b&gt; &amp;mdash; &lt;code&gt;\'&lt;/code&gt;는 &quot;이건 문자열 끝이 아니라 그냥 작은따옴표 글자야&quot;라는 표시입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;줄바꿈과 여러 줄 문자열&lt;/h3&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(&quot;안녕하세요.\n제 이름은 홍길동 입니다&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;안녕하세요.
제 이름은 홍길동 입니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글은 따옴표 3개를 씁니다. 그런데 여기에 함정이 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;review = &quot;&quot;&quot;
test text
text test
&quot;&quot;&quot;
print(review)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;
test text
text test
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위아래로 &lt;b&gt;빈 줄이 하나씩 생깁니다.&lt;/b&gt; &lt;code&gt;&quot;&quot;&quot;&lt;/code&gt; 바로 뒤의 줄바꿈과 마지막 줄바꿈까지 전부 문자열에 포함되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 없애려면 줄 끝에 백슬래시를 붙입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;review = '''\
짜장면이 너무너무 맛있는데,
기사 아저씨가 너무 불친절했어요.\
'''
print(review)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;짜장면이 너무너무 맛있는데,
기사 아저씨가 너무 불친절했어요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄 끝의 &lt;code&gt;\&lt;/code&gt;는 &lt;b&gt;&quot;이 줄바꿈은 문자열에 넣지 마&quot;&lt;/b&gt; 라는 뜻입니다. 코드에서는 들여쓰기 없이 예쁘게 쓰면서, 실제 문자열에는 불필요한 공백을 안 넣는 방법입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열 연산 &amp;mdash; 덧셈과 곱셈&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;text1 = &quot;hello&quot;
text2 = &quot;python&quot;

print(text1 + text2)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;hellopython&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(&quot;=&quot; * 50)
print(&quot;best&quot; * 3)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;diff&quot;&gt;&lt;code&gt;==================================================
bestbestbest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&quot;=&quot; * 50&lt;/code&gt;은 구분선 만들 때 정말 자주 씁니다. 반복문 안에서 로그를 찍을 때 유용합니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(&quot;=&quot; * 50)
print(&quot;Loop 1.&quot;)
print(&quot;=&quot; * 50)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, &lt;b&gt;문자열 &amp;times; 문자열은 안 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(text1 * text2)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;sml&quot;&gt;&lt;code&gt;TypeError: can't multiply sequence by non-int of type 'str'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곱셈은 &quot;몇 번 반복할지&quot;를 정수로 받아야 하는데, &lt;code&gt;&quot;python&quot;&lt;/code&gt;은 횟수가 될 수 없으니까요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;길이 재기 &amp;mdash; 눈에 안 보이는 공백을 찾는 법&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;message = &quot;hello&quot;
print(len(message))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;len()&lt;/code&gt;이 진짜 유용해지는 순간은 이겁니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;text1 = &quot;홍길동&quot;
text2 = &quot;홍길동 &quot;    # 뒤에 공백 하나

print(text1, len(text1))
print(text2, len(text2))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;홍길동 3
홍길동  4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력만 보면 완전히 똑같아 보이는데 길이가 다릅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 실무에서 왜 중요하냐면, &lt;code&gt;&quot;홍길동&quot;&lt;/code&gt;과 &lt;code&gt;&quot;홍길동 &quot;&lt;/code&gt;은 파이썬 입장에서 &lt;b&gt;다른 값&lt;/b&gt;입니다. 비교하면 &lt;code&gt;False&lt;/code&gt;가 나오고, 딕셔너리 키로 쓰면 다른 키가 되고, 데이터를 합칠 때 매칭이 안 됩니다. &quot;분명히 같은 이름인데 왜 조인이 안 되지?&quot;의 범인이 대부분 이 눈에 안 보이는 공백입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의심스러울 땐 &lt;code&gt;len()&lt;/code&gt;으로 찍어보는 게 가장 확실합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱싱 &amp;mdash; 앞에서 세기, 뒤에서 세기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 글자마다 번호가 붙어 있습니다. &lt;b&gt;0부터&lt;/b&gt; 시작합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;문자     9    0    1    2    1    2    -    1    3    2    5    2    2    1
앞에서   0    1    2    3    4    5    6    7    8    9   10   11   12   13
뒤에서 -14  -13  -12  -11  -10   -9   -8   -7   -6   -5   -4   -3   -2   -1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 셀 땐 &lt;b&gt;0부터&lt;/b&gt;, 뒤에서 셀 땐 &lt;b&gt;-1부터&lt;/b&gt; 시작합니다. 뒤에서 셀 때 -0이 없으니 -1부터인 거죠.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제.&lt;/b&gt; 주민등록번호에서 성별 자리를 뽑아보세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;regist_num = &quot;901212-1325221&quot;

print(regist_num[7])    # 1
print(regist_num[-7])   # 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 글자에 &lt;b&gt;두 개의 주소&lt;/b&gt;가 있는 셈입니다. 어느 쪽을 쓸지는 상황에 따라 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 길이가 항상 같다면(주민번호처럼) &amp;rarr; 앞에서 세는 게 직관적&lt;/li&gt;
&lt;li&gt;뒤쪽이 고정이고 앞쪽 길이가 들쭉날쭉하다면 &amp;rarr; 뒤에서 세는 게 안전&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;슬라이싱 &amp;mdash; &lt;code&gt;[시작:끝]&lt;/code&gt;, 끝은 포함 안 됨&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제.&lt;/b&gt; 주민등록번호에서 생년월일을 뽑아보세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;regist_num = &quot;901212-1325221&quot;

print(regist_num[0:6])   # 901212
print(regist_num[:6])    # 901212  (시작 생략 = 처음부터)
print(regist_num[6:])    # -1325221 (끝 생략 = 끝까지)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 헷갈렸던 게 &lt;b&gt;끝 번호는 포함되지 않는다&lt;/b&gt;는 규칙이었습니다. &lt;code&gt;[0:6]&lt;/code&gt;은 0,1,2,3,4,5번째 글자만 가져오고 6번은 안 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 불친절하다고 생각했는데, 익숙해지니 오히려 편했습니다. &lt;b&gt;끝 번호 - 시작 번호 = 가져온 개수&lt;/b&gt;가 딱 맞아떨어지거든요. &lt;code&gt;[0:6]&lt;/code&gt;은 고민할 것 없이 6글자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음수 인덱스와도 섞어 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;print(regist_num[2:6])      # 1212
print(regist_num[-12:-8])   # 1212
print(regist_num[-12:6])    # 1212  (음수 시작 + 양수 끝도 가능)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제. 주민번호 마스킹&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제.&lt;/b&gt; 주민등록번호 뒷자리에서 성별 자리만 남기고 나머지를 &lt;code&gt;*&lt;/code&gt;로 가려주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;regist_num = &quot;901212-1325221&quot;

temp = regist_num[:8]    # 901212-1  (생년월일 + 성별까지)
stars = &quot;*&quot; * 6          # ******

print(temp + stars)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;901212-1******&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이싱으로 자르고, 곱셈으로 별을 만들고, 덧셈으로 붙이는 &lt;b&gt;3단 조합&lt;/b&gt;입니다. 개인정보 마스킹은 실무에서 꼭 필요한 처리라 패턴 자체를 기억해둘 만합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ f-string &amp;mdash; 문자열 포매팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 &quot;왜 필요한가&quot;를 실패에서 출발해야 이해가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 1. 그냥 더해보기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;name = &quot;홍길동&quot;
age = 24

print(name + age)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;TypeError: can only concatenate str (not &quot;int&quot;) to str&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 &lt;code&gt;+&lt;/code&gt; 숫자는 안 됩니다. 파이썬 입장에서 &lt;code&gt;+&lt;/code&gt;가 &quot;이어붙이기&quot;인지 &quot;더하기&quot;인지 알 수 없거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 2. str()로 변환해서 더하기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;print(name + &quot;(&quot; + str(age) + &quot;)&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;홍길동(24)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작은 합니다. 그런데 따옴표와 &lt;code&gt;+&lt;/code&gt;가 뒤엉켜서 &lt;b&gt;읽기가 괴롭습니다.&lt;/b&gt; 변수가 다섯 개쯤 되면 사람이 읽을 수 있는 코드가 아니게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시도 3. f-string&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(f&quot;{name}({age})&quot;)
print(f&quot;{name}님 안녕하세요.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;홍길동(24)
홍길동님 안녕하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 앞에 &lt;code&gt;f&lt;/code&gt;를 붙이고, 중괄호 안에 변수를 넣으면 끝입니다. &lt;b&gt;결과물의 모양이 코드에 그대로 보인다&lt;/b&gt;는 게 최고의 장점입니다. &lt;code&gt;str()&lt;/code&gt; 변환도 알아서 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;accuracy = 0.8
print(f&quot;Accuracy: {accuracy}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Accuracy: 0.8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중괄호 안에는 변수뿐 아니라 &lt;b&gt;식도 들어갑니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(f&quot;정확도: {accuracy * 100}%&quot;)
print(f&quot;합계: {10 + 20}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;정확도: 80.0%
합계: 30&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 문자열을 조립할 일이 있으면 고민 없이 f-string을 쓰기로 했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 문자열 메서드 &amp;mdash; 데이터 전처리의 도구들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 &quot;문법&quot;이라기보다 &lt;b&gt;실제로 지저분한 데이터를 청소하는 도구&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⭐ split() &amp;mdash; 문자열 쪼개기&lt;/h3&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;address1 = '서울시  관악구 신림동'    # 서울시 뒤에 공백 2개
address2 = '강남구/동작구/노원구'

print(address1.split())
print(address1.split(' '))
print(address2.split('/'))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;['서울시', '관악구', '신림동']
['서울시', '', '관악구', '신림동']
['강남구', '동작구', '노원구']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기가 이번 실습에서 두 번째로 인상 깊었던 지점입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;split()&lt;/code&gt;과 &lt;code&gt;split(' ')&lt;/code&gt;는 같아 보이지만 결과가 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;split()&lt;/code&gt; &amp;mdash; 인자 없이 쓰면 &lt;b&gt;연속된 공백을 하나로 묶어서&lt;/b&gt; 처리하고, 빈 값도 안 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;split(' ')&lt;/code&gt; &amp;mdash; 공백 하나를 정확히 구분자로 봅니다. 공백이 두 개면 그 사이에 &lt;b&gt;빈 문자열 &lt;code&gt;''&lt;/code&gt;이 끼어듭니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무 데이터는 공백이 두 개, 세 개씩 들어있는 경우가 흔합니다. 그래서 &lt;b&gt;공백으로 자를 때는 인자 없이 &lt;code&gt;split()&lt;/code&gt;을 쓰는 게 안전&lt;/b&gt;합니다. 저는 이걸 모르고 &lt;code&gt;split(' ')&lt;/code&gt;을 쓰다가 빈 문자열이 섞인 리스트를 받고 한참 봤습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제. 파일 확장자 분리&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;file_name1 = &quot;수업자료.pdf&quot;
file_name2 = &quot;수업자료_복습.ipynb&quot;

print(file_name1.split(&quot;.&quot;))
print(file_name2.split(&quot;.&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;['수업자료', 'pdf']
['수업자료_복습', 'ipynb']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;split()&lt;/code&gt;의 결과는 항상 &lt;b&gt;리스트&lt;/b&gt;입니다. 그래서 파일명은 &lt;code&gt;[0]&lt;/code&gt;, 확장자는 &lt;code&gt;[-1]&lt;/code&gt;로 꺼냅니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 &lt;code&gt;&quot;보고서.최종.v2.xlsx&quot;&lt;/code&gt;처럼 점이 여러 개면 &lt;code&gt;['보고서', '최종', 'v2', 'xlsx']&lt;/code&gt;가 되므로, 확장자는 &lt;code&gt;[1]&lt;/code&gt;이 아니라 &lt;b&gt;&lt;code&gt;[-1]&lt;/code&gt;로 꺼내는 게 안전&lt;/b&gt;합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replace() &amp;mdash; 문자열 바꾸기&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;message = &quot;오늘의 날씨는 맑음&quot;
result = message.replace(&quot;맑음&quot;, &quot;흐림&quot;)

print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;오늘의 날씨는 흐림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 꼭 짚고 갈 것:&lt;/b&gt; &lt;code&gt;replace()&lt;/code&gt;는 원본을 바꾸지 않습니다. &lt;b&gt;바뀐 새 문자열을 돌려줄 뿐&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;message.replace(&quot;맑음&quot;, &quot;흐림&quot;)
print(message)     # 오늘의 날씨는 맑음  &amp;larr; 그대로!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 한 번 만들면 못 바꾸는(immutable) 자료형이라 그렇습니다. 반드시 &lt;b&gt;결과를 변수에 다시 담아야&lt;/b&gt; 합니다. 이건 뒤에 나올 &lt;code&gt;strip()&lt;/code&gt;, &lt;code&gt;lower()&lt;/code&gt;, &lt;code&gt;upper()&lt;/code&gt;도 전부 동일합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전. 뒤죽박죽 성별 데이터 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수집된 성별 데이터가 이렇게 들어왔다고 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;gender = &quot;fffmmm여자mm남자f&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글과 알파벳이 섞여 있습니다. 이걸 &lt;code&gt;replace()&lt;/code&gt;를 &lt;b&gt;연쇄로&lt;/b&gt; 써서 정리합니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;result1 = gender.replace(&quot;여자&quot;, &quot;f&quot;)
result2 = result1.replace(&quot;남자&quot;, &quot;m&quot;)
print(result2)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;fffmmmfmmmf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;replace()&lt;/code&gt;가 문자열을 돌려주니까, 그 결과에 또 &lt;code&gt;replace()&lt;/code&gt;를 붙일 수 있습니다. 그래서 이렇게 한 줄로도 됩니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;print(gender.replace(&quot;여자&quot;, &quot;f&quot;).replace(&quot;남자&quot;, &quot;m&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 응용하면 &lt;b&gt;붙어있는 데이터를 낱개로 분리&lt;/b&gt;할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;print(result2.replace(&quot;f&quot;, &quot;f &quot;).replace(&quot;m&quot;, &quot;m &quot;))
print(result2.replace(&quot;f&quot;, &quot;f &quot;).replace(&quot;m&quot;, &quot;m &quot;).split())&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;f f f m m m f m m m f 
['f', 'f', 'f', 'm', 'm', 'm', 'f', 'm', 'm', 'm', 'f']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분자가 없어서 자를 수 없던 문자열에, &lt;b&gt;replace로 공백을 심어놓고 split으로 자르는&lt;/b&gt; 발상입니다. 처음 봤을 때 &quot;이런 식으로도 푸는구나&quot; 싶었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전. 리뷰에서 노이즈 제거&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;review = &quot;이거 진짜 웃기닼ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ&quot;
print(review.replace(&quot;ㅋ&quot;, &quot;&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이거 진짜 웃기닼&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;replace(찾을것, &quot;&quot;)&lt;/code&gt;은 &lt;b&gt;삭제&lt;/b&gt;입니다. 빈 문자열로 바꾼다 = 지운다. 텍스트 분석 전에 의미 없는 문자를 걷어낼 때 쓰는 기본 기술입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;lower() / upper() &amp;mdash; 대소문자 통일&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;gender = &quot;f,F,f,m,M,m,F,f,M,M&quot;

print(gender.lower())
print(gender.upper())&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;r&quot;&gt;&lt;code&gt;f,f,f,m,m,m,f,f,m,m
F,F,F,M,M,M,F,F,M,M&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 성별인데 &lt;code&gt;f&lt;/code&gt;와 &lt;code&gt;F&lt;/code&gt;로 따로 집계되면 안 되니, 세기 전에 &lt;b&gt;한쪽으로 통일&lt;/b&gt;하는 게 먼저입니다. 이메일이나 아이디 비교에서도 필수 전처리입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;strip() / lstrip() / rstrip() &amp;mdash; 공백 제거&lt;/h3&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;name = &quot;             홍길동                    &quot;

print(f&quot;|{name.strip()}|&quot;)     # 양쪽
print(f&quot;|{name.lstrip()}|&quot;)    # 왼쪽만
print(f&quot;|{name.rstrip()}|&quot;)    # 오른쪽만&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;|홍길동|
|홍길동                    |
|             홍길동|&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;작은 팁:&lt;/b&gt; 결과를 &lt;code&gt;|&lt;/code&gt; 사이에 끼워서 출력한 것에 주목하세요. 공백은 눈에 안 보이기 때문에, 이렇게 &lt;b&gt;경계 문자로 감싸야 얼마나 지워졌는지 확인&lt;/b&gt;할 수 있습니다. &lt;code&gt;len()&lt;/code&gt;과 함께 공백 디버깅의 필수 기술입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 앞뒤의 공백은 사용자 입력에서 정말 흔하게 들어옵니다. 앞서 본 &lt;code&gt;&quot;홍길동&quot;&lt;/code&gt; vs &lt;code&gt;&quot;홍길동 &quot;&lt;/code&gt; 문제를 떠올려보면, &lt;b&gt;입력받은 문자열은 일단 &lt;code&gt;strip()&lt;/code&gt;부터 거는 습관&lt;/b&gt;이 왜 필요한지 알 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;count() &amp;mdash; 개수 세기&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;gender = &quot;F,F,F,M,M,M,F,F,M,M&quot;

print(gender.count(&quot;F&quot;))   # 5
print(gender.count(&quot;M&quot;))   # 5
print(gender.count(&quot;m&quot;))   # 0  &amp;larr; 대소문자 구분!
print(gender.count(&quot;f&quot;))   # 0
print(len(gender))         # 19&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count()&lt;/code&gt;도 &lt;b&gt;대소문자를 구분&lt;/b&gt;합니다. 그래서 세기 전에 &lt;code&gt;lower()&lt;/code&gt;로 통일하는 게 순서입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제. 리뷰 키워드 빈도&lt;/h3&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;review = &quot;&quot;&quot;\
짬뽕은 정말 맵고 맛있었어요. 해산물이 가득 들어가서 만족했습니다.
짜장면은 이 집에서 먹어본 것 중 가장 맛있었어요
짜장면 굿\
&quot;&quot;&quot;

print(f&quot;짜장면 등장 횟수: {review.count('짜장면')}&quot;)
print(f&quot;짬뽕 등장 횟수: {review.count('짬뽕')}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;짜장면 등장 횟수: 2
짬뽕 등장 횟수: 1&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ f-string 안에서 따옴표를 쓸 때는 &lt;b&gt;바깥과 다른 종류&lt;/b&gt;를 써야 합니다. &lt;code&gt;f&quot;...{review.count('짜장면')}...&quot;&lt;/code&gt;처럼 바깥이 &lt;code&gt;&quot;&lt;/code&gt;면 안쪽은 &lt;code&gt;'&lt;/code&gt;로.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 텍스트 분석의 가장 원시적인 형태입니다. 여러 리뷰에 이걸 돌리면 &quot;어떤 메뉴가 자주 언급되는가&quot;를 알 수 있고, 그게 곧 &lt;b&gt;워드클라우드나 키워드 분석의 출발점&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1편 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오늘의 함정 4선&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;함정&lt;/th&gt;
&lt;th&gt;증상&lt;/th&gt;
&lt;th&gt;대응&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;예약어 덮어쓰기&lt;/td&gt;
&lt;td&gt;&lt;code&gt;print = &quot;hello&quot;&lt;/code&gt; 후 &lt;code&gt;'str' object is not callable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;print&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt; 을 변수명으로 쓰지 않기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커널 재시작 후 NameError&lt;/td&gt;
&lt;td&gt;코드가 화면에 보이는데도 &quot;정의되지 않았다&quot;&lt;/td&gt;
&lt;td&gt;위에서부터 다시 실행(Restart &amp;amp; Run All)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt; 나눗셈의 타입 오염&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4500&lt;/code&gt;이 &lt;code&gt;1500.0&lt;/code&gt;으로 변해서 이후 계산 전부 float&lt;/td&gt;
&lt;td&gt;정수가 필요하면 &lt;code&gt;//&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;split(' ')&lt;/code&gt;의 빈 문자열&lt;/td&gt;
&lt;td&gt;공백 2개 사이에 &lt;code&gt;''&lt;/code&gt;가 끼어듦&lt;/td&gt;
&lt;td&gt;공백 기준이면 인자 없이 &lt;code&gt;split()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;손에 익혀야 할 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문자열 메서드는 원본을 안 바꾼다&lt;/b&gt; &amp;rarr; 결과를 변수에 다시 담기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;f-string&lt;/b&gt; &amp;rarr; 문자열 조립은 무조건 이걸로&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;len()&lt;/code&gt;과 &lt;code&gt;|{...}|&lt;/code&gt;&lt;/b&gt; &amp;rarr; 눈에 안 보이는 공백 찾는 두 가지 기술&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;//&lt;/code&gt;와 &lt;code&gt;%&lt;/code&gt;는 짝&lt;/b&gt; &amp;rarr; 페이지네이션, 홀짝, 단위 변환의 관용구&lt;/li&gt;
&lt;li&gt;&lt;b&gt;슬라이싱 &lt;code&gt;[시작:끝]&lt;/code&gt;, 끝은 미포함&lt;/b&gt; &amp;rarr; 대신 &lt;code&gt;끝 - 시작 = 개수&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 여러 값을 한 번에 담는 &lt;b&gt;리스트와 튜플&lt;/b&gt;을 정리합니다. &lt;code&gt;lst[8:] = 10&lt;/code&gt;은 에러가 나는데 &lt;code&gt;lst[8:] = &quot;python&quot;&lt;/code&gt;은 되는, 좀 이상한 이야기가 기다리고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://dragoner.tistory.com/279&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[2편] 리스트와 튜플 &amp;mdash; 슬라이스 대입이 이상하게 동작하는 이유&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>AI/Python 기초</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/278</guid>
      <comments>https://dragoner.tistory.com/278#entry278comment</comments>
      <pubDate>Wed, 22 Jul 2026 18:43:25 +0900</pubDate>
    </item>
    <item>
      <title>Personal AI Company 셋업 ⑤: 자동 wakeup 패턴으로 에이전트 파이프라인 직렬화하기</title>
      <link>https://dragoner.tistory.com/277</link>
      <description>&lt;h2&gt;왜 자동 wakeup이 필요한가&lt;/h2&gt;
&lt;p&gt;에이전트가 작업을 완료했는데 다음 단계가 시작되지 않는다면 파이프라인은 조용히 멈춘다. 사람이 대시보드를 계속 보면서 &amp;quot;Researcher 끝났으니까 이제 Creator 깨워야지&amp;quot;를 직접 수행하면 자동화가 아니다. 그것은 사람이 폴링하는 것이다.&lt;/p&gt;
&lt;p&gt;Paperclip은 이 문제를 &lt;strong&gt;children API + tree-hold&lt;/strong&gt; 조합으로 해결한다. CEO가 child 이슈를 올바르게 등록하고 자신을 blocked 상태로 두면, child가 완료될 때 Paperclip이 CEO를 자동으로 깨운다. 이 메커니즘이 제대로 작동하려면 정확한 API 호출 순서와 상태 관리가 필요하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;children API가 자동 wakeup의 열쇠다&lt;/h2&gt;
&lt;p&gt;Paperclip에서 이슈를 만드는 방법은 두 가지다. 이 둘은 겉보기에 비슷하지만 자동 wakeup 관점에서 완전히 다르게 동작한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;&lt;code&gt;POST /api/issues/{id}/children&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;POST /api/issues&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;parent_id 자동 연결&lt;/td&gt;
&lt;td&gt;✅ 자동&lt;/td&gt;
&lt;td&gt;❌ 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자동 wakeup 트리거&lt;/td&gt;
&lt;td&gt;✅ child done 시 parent wakeup&lt;/td&gt;
&lt;td&gt;❌ 동작 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조직 트리 표시&lt;/td&gt;
&lt;td&gt;✅ parent 아래 계층 표시&lt;/td&gt;
&lt;td&gt;❌ 독립 이슈로 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;권한 컨텍스트 상속&lt;/td&gt;
&lt;td&gt;✅ parent 컨텍스트 상속&lt;/td&gt;
&lt;td&gt;❌ 독립 컨텍스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tree-hold 연동&lt;/td&gt;
&lt;td&gt;✅ 동작&lt;/td&gt;
&lt;td&gt;❌ 동작 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파이프라인 사용 가능 여부&lt;/td&gt;
&lt;td&gt;✅ 필수 사용&lt;/td&gt;
&lt;td&gt;❌ 사용 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;POST /api/issues&lt;/code&gt;로 만든 이슈는 &lt;code&gt;parent_id&lt;/code&gt;가 없다. Paperclip이 해당 이슈의 완료를 누구에게도 알릴 의무가 없으므로 parent는 영원히 blocked 상태에 머문다. children 엔드포인트를 사용해야 &lt;code&gt;parent_id&lt;/code&gt;가 자동으로 설정되고 완료 시 parent 이슈가 wakeup 큐에 올라간다.&lt;/p&gt;
&lt;p&gt;children API 호출 예시는 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X POST https://api.paperclip.sh/api/issues/{parentIssueId}/children \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{
    &amp;quot;assigneeAgentId&amp;quot;: &amp;quot;{researcherAgentId}&amp;quot;,
    &amp;quot;title&amp;quot;: &amp;quot;[Researcher] 5편 wakeup 패턴 조사&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;... 위임 메시지 전체 ...&amp;quot;
  }&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;tree-hold 패턴: blocked + tree-hold는 한 쌍이다&lt;/h2&gt;
&lt;p&gt;child 이슈를 만든 것만으로는 부족하다. CEO 이슈가 계속 &lt;code&gt;in_progress&lt;/code&gt; 상태라면 다음 heartbeat에서 CEO가 깨어나 다음 작업을 시작할 수 있다. child 완료를 기다리지 않고 Creator child를 먼저 만들어버리는 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;두 가지 API를 순서대로 호출해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 이슈 상태를 blocked로 변경&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X PATCH https://api.paperclip.sh/api/issues/{parentIssueId} \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{&amp;quot;status&amp;quot;: &amp;quot;blocked&amp;quot;}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;② tree-hold 등록&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X POST https://api.paperclip.sh/api/issues/{parentIssueId}/tree-holds \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{
    &amp;quot;mode&amp;quot;: &amp;quot;pause&amp;quot;,
    &amp;quot;reason&amp;quot;: &amp;quot;waiting for child {childIssueId} (Researcher 완료 대기)&amp;quot;
  }&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mode: &amp;quot;pause&amp;quot;&lt;/code&gt;는 child가 완료되면 tree-hold가 자동으로 해제되는 방식이다. &lt;code&gt;blocked&lt;/code&gt; 상태는 &amp;quot;외부 의존성 때문에 진행 불가&amp;quot;라는 신호이고, tree-hold는 실행 큐에 &amp;quot;내가 풀 때까지 이 이슈를 깨우지 말라&amp;quot;는 잠금이다. 둘 중 하나만 설정해도 예상과 다른 동작이 발생한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;직렬 처리 강제 패턴&lt;/h2&gt;
&lt;h3&gt;동시에 만들면 생기는 문제&lt;/h3&gt;
&lt;p&gt;조사→생성→검증→실행의 4단계 파이프라인이 있다고 가정하자. CEO가 한 번에 4개 child를 만들면 다음 상황이 발생한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CEO → Researcher, Creator, Reviewer, Executor 동시 생성
         ↓           ↓          ↓           ↓
     (리서치 중)  (참조할     (참조할     (참조할
                  리서치 없음) 초안 없음)  검수 없음)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creator는 Researcher의 산출물 경로를 참조해야 하지만 실행 시점에 해당 파일이 존재하지 않는다. 결과는 에러이거나 잘못된 산출물이다. 직렬 처리는 설계 취향의 문제가 아니라 의존성 때문에 반드시 지켜야 하는 제약이다.&lt;/p&gt;
&lt;h3&gt;올바른 직렬 처리 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CEO 이슈 (in_progress)
  │
  ├─ [Step 1] POST /children → Researcher child 생성
  │     ↓
  │  CEO → blocked + tree-hold 등록
  │     ↓ (Researcher done)
  │  [자동 wakeup] CEO → in_progress 복귀
  │
  ├─ [Step 2] 리서치 산출물 확인 후
  │           POST /children → Creator child 생성
  │     ↓
  │  CEO → blocked + tree-hold 등록
  │     ↓ (Creator done)
  │  [자동 wakeup] CEO → in_progress 복귀
  │
  ├─ [Step 3] 초안 확인 후
  │           POST /children → Reviewer child 생성
  │     ↓
  │  CEO → blocked + tree-hold 등록
  │     ↓ (Reviewer done)
  │  [자동 wakeup] CEO → in_progress 복귀
  │
  └─ [Step 4] 검수 통과 확인 후 CEO → done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심 원칙은 하나다: &lt;strong&gt;다음 child는 이전 child가 done이 된 후에만 만든다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;CEO 관점 전체 시퀀스&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1단계: 에이전트 ID 조회
curl https://api.paperclip.sh/api/companies/{companyId}/agents \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot;
# → Researcher의 agentId 확인

# 2단계: child 이슈 생성
curl -X POST https://api.paperclip.sh/api/issues/{myCEOIssueId}/children \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{&amp;quot;assigneeAgentId&amp;quot;: &amp;quot;{researcherAgentId}&amp;quot;, &amp;quot;title&amp;quot;: &amp;quot;...&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;...&amp;quot;}&amp;#39;
# → childIssueId 저장

# 3단계: 내 이슈를 blocked로
curl -X PATCH https://api.paperclip.sh/api/issues/{myCEOIssueId} \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{&amp;quot;status&amp;quot;: &amp;quot;blocked&amp;quot;}&amp;#39;

# 4단계: tree-hold 등록 (즉시)
curl -X POST https://api.paperclip.sh/api/issues/{myCEOIssueId}/tree-holds \
  -H &amp;quot;Authorization: Bearer $PAPERCLIP_API_KEY&amp;quot; \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;#39;{&amp;quot;mode&amp;quot;: &amp;quot;pause&amp;quot;, &amp;quot;reason&amp;quot;: &amp;quot;waiting for child {childIssueId}&amp;quot;}&amp;#39;

# → Paperclip이 Researcher를 wakeup
# → Researcher done → CEO 자동 wakeup&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;이 시리즈 자체가 작동 증거다&lt;/h2&gt;
&lt;p&gt;이 블로그 시리즈(Personal AI Company 셋업)는 위 패턴을 그대로 적용해 운영한다. 각 편의 작성 흐름은 다음 구조를 따른다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CEO 이슈: 블로그 N편 작성
  │
  ├─ child: [Researcher] N편 조사
  │     → /app/workspace/blog-personal-ai-company-setup/research/{N편}.md
  │     → done → CEO 자동 wakeup
  │
  ├─ child: [Creator] N편 초안 작성
  │     → /app/workspace/blog-personal-ai-company-setup/drafts/{N편}.md
  │     → done → CEO 자동 wakeup
  │
  └─ child: [Reviewer] N편 검수
        → /app/workspace/blog-personal-ai-company-setup/reviews/{N편}-review.md
        → done → CEO 최종 보고&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 편의 리서치 파일이 &lt;code&gt;/app/workspace/&lt;/code&gt; 아래에 실제로 존재하고 Creator인 내가 지금 이 글을 쓰고 있다는 사실 자체가 패턴이 작동했다는 증거다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;에이전트가 스스로 blocked를 선택하는 이유&lt;/h2&gt;
&lt;p&gt;&amp;quot;왜 에이전트가 스스로 blocked 상태로 가는가?&amp;quot;라는 질문이 처음에는 어색하게 느껴질 수 있다. blocked는 보통 &amp;quot;문제가 생긴 상태&amp;quot;로 인식되기 때문이다.&lt;/p&gt;
&lt;p&gt;Paperclip에서 blocked는 &lt;strong&gt;의도적인 대기 신호&lt;/strong&gt;다. &amp;quot;나는 다음 단계를 알고 있지만, 선행 조건이 충족될 때까지 실행하지 않겠다&amp;quot;는 명시적 선언이다. 에이전트가 blocked를 선택하지 않으면 시스템은 에이전트가 계속 작업할 수 있다고 판단하고, 다음 heartbeat에서 불완전한 상태로 진행을 강제한다. blocked + tree-hold는 &amp;quot;올바른 순서를 강제하는 자기 제어 메커니즘&amp;quot;이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;트러블슈팅: wakeup 실패 시나리오 3가지&lt;/h2&gt;
&lt;h3&gt;시나리오 1: child를 일반 API로 만든 경우&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;증상&lt;/strong&gt;: Researcher가 done이 됐는데 CEO 이슈가 계속 blocked 상태다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: &lt;code&gt;POST /api/issues&lt;/code&gt; 또는 &lt;code&gt;POST /api/companies/{id}/issues&lt;/code&gt;로 child를 만들어서 &lt;code&gt;parent_id&lt;/code&gt;가 없다. Paperclip이 완료를 parent에게 알리지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결책&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Board가 대시보드에서 CEO 이슈 status를 &lt;code&gt;todo&lt;/code&gt; → &lt;code&gt;in_progress&lt;/code&gt;로 수동 변경한다.&lt;/li&gt;
&lt;li&gt;CEO가 깨어난 후 리서치 산출물 경로를 직접 확인한다.&lt;/li&gt;
&lt;li&gt;이후 단계부터 반드시 &lt;code&gt;/api/issues/{id}/children&lt;/code&gt; 엔드포인트를 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;시나리오 2: blocked 처리를 잊은 경우&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;증상&lt;/strong&gt;: CEO가 Researcher child를 만들었는데 Researcher 완료 전에 Creator child도 생성됐다. Creator가 리서치 파일 없이 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: child 생성 후 blocked 상태로 변경하지 않아 다음 heartbeat에서 CEO가 다음 단계를 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결책&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;잘못 생성된 Creator child 이슈를 &lt;code&gt;cancelled&lt;/code&gt;로 변경한다.&lt;/li&gt;
&lt;li&gt;Researcher가 완료될 때까지 CEO 이슈를 수동으로 blocked 상태로 유지한다.&lt;/li&gt;
&lt;li&gt;Researcher 완료 후 Board가 CEO 이슈를 수동 unblock한다.&lt;/li&gt;
&lt;li&gt;CEO가 리서치 산출물을 확인한 후 Creator child를 재생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;시나리오 3: tree-hold 해제 후에도 wakeup이 오지 않는 경우&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;증상&lt;/strong&gt;: child가 done이 됐고 tree-hold도 해제됐는데 CEO 이슈가 &lt;code&gt;in_progress&lt;/code&gt;로 돌아오지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원인&lt;/strong&gt;: 자동 wakeup 메커니즘의 현재 알려진 제약이다. 버그가 아니며, Paperclip에서 개선 예정 기능이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결책&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Board가 대시보드에서 CEO 이슈와 child 이슈의 상태를 확인한다.&lt;/li&gt;
&lt;li&gt;Child가 실제로 done인지 확인한다.&lt;/li&gt;
&lt;li&gt;CEO 이슈 status를 &lt;code&gt;todo&lt;/code&gt; 또는 &lt;code&gt;in_progress&lt;/code&gt;로 수동 변경한다.&lt;/li&gt;
&lt;li&gt;CEO가 깨어나면 child 산출물 경로를 확인하고 다음 단계를 진행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;현재 운영 중에는 Board(사용자)가 대시보드를 주기적으로 확인해 stuck 이슈를 수동으로 unblock하는 것을 권장한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;패턴 체크리스트&lt;/h2&gt;
&lt;p&gt;파이프라인 구성 시 다음 순서를 반드시 지킨다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;POST /api/issues/{id}/children&lt;/code&gt; 으로 child 생성 (&lt;code&gt;POST /api/issues&lt;/code&gt; 금지)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; child 생성 직후 &lt;code&gt;PATCH /api/issues/{id}&lt;/code&gt; 로 parent를 &lt;code&gt;blocked&lt;/code&gt; 상태로 변경&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; blocked 변경 직후 &lt;code&gt;POST /api/issues/{id}/tree-holds&lt;/code&gt; 로 &lt;code&gt;mode: &amp;quot;pause&amp;quot;&lt;/code&gt; 등록&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 다음 child는 이전 child가 done이 된 후에만 생성&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 자동 wakeup 실패 대비: Board가 대시보드를 주기적으로 모니터링&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;다음 편 예고&lt;/h2&gt;
&lt;p&gt;6편에서는 Telegram bridge로 AI 에이전트 팀을 모바일에서 원격 제어하는 방법을 다룬다. polling과 webhook의 선택 기준, callback_data 64바이트 제약 해결 패턴, 화이트리스트 기반 접근 제어, Paperclip API 연동 흐름을 설명한다.&lt;/p&gt;</description>
      <category>AI</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/277</guid>
      <comments>https://dragoner.tistory.com/277#entry277comment</comments>
      <pubDate>Thu, 21 May 2026 09:00:52 +0900</pubDate>
    </item>
    <item>
      <title># Personal AI Company 셋업 ④: Path B &amp;mdash; 에이전트 간 파일 공유의 해법</title>
      <link>https://dragoner.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Researcher가 조사를 마치고 &quot;저장 완료&quot;를 보고했다. Creator가 해당 파일을 읽으려 했는데 파일이 없다. 에이전트 로그에는 Write 도구 호출이 성공으로 기록되어 있다. 이 시리즈 4편에서는 이 현상의 원인과 해법, 절대경로 규칙의 필요성을 실제 프로젝트 구조를 기반으로 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 인식: 에이전트는 왜 서로의 파일을 못 찾는가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;per-agent sandbox의 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Paperclip의 각 에이전트는 독립된 실행 환경(sandbox)에서 작동한다. 에이전트가 실행될 때마다 격리된 git worktree 또는 operator branch가 할당되며, 상대경로(&lt;code&gt;./workspace/&lt;/code&gt;)는 이 격리 디렉토리를 기준으로 resolve된다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;에이전트 A (Researcher) CWD: /paperclip/workspaces/run-20260518-001/
에이전트 B (Creator)    CWD: /paperclip/workspaces/run-20260518-002/

A에서 ./workspace/research/output.md
  = /paperclip/workspaces/run-20260518-001/workspace/research/output.md

B에서 ./workspace/research/output.md
  = /paperclip/workspaces/run-20260518-002/workspace/research/output.md&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 상대경로가 물리적으로 서로 다른 파일을 가리킨다. Researcher가 상대경로로 저장했다면, Creator는 그 파일을 읽을 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상대경로가 실패하는 두 가지 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트의 Write 도구 호출이 성공으로 기록되어도 다른 에이전트가 파일을 찾지 못하는 경우는 두 가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에이전트가 상대경로를 사용해 자신의 격리 sandbox에 저장했다.&lt;/li&gt;
&lt;li&gt;격리 sandbox는 heartbeat 간 상태 유지를 보장하지 않아, 다음 실행에서 파일이 사라졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 경우 모두 공유 경로 미사용이 근본 원인이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Path B: 공유 워크스페이스 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;host bind-mount란 무엇인가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;/app/workspace/&lt;/code&gt;는 호스트 파일시스템의 특정 디렉토리를 모든 에이전트 컨테이너에 동일하게 마운트한 공유 경로다. 아래는 docker-compose 기반 설정 예시다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# docker-compose.yml
services:
  paperclip:
    volumes:
      - /host/data/workspace:/app/workspace&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 내부에서 &lt;code&gt;/app/workspace/&lt;/code&gt;를 읽거나 쓰면, 그 변경이 호스트 파일시스템(&lt;code&gt;/host/data/workspace/&lt;/code&gt;)에 그대로 반영된다. 어떤 컨테이너에서 쓰든 같은 물리 위치에 기록된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/app/workspace/가 공유 채널이 되는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 에이전트는 동일한 &lt;code&gt;/app/workspace/&lt;/code&gt; 마운트 포인트를 가진다. Researcher가 &lt;code&gt;/app/workspace/blog-project/research/output.md&lt;/code&gt;에 저장하면, Creator는 같은 절대경로로 그 파일을 읽을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;[Researcher sandbox]
/app/workspace/blog/research/output.md
  = /host/data/workspace/blog/research/output.md  &amp;larr; 동일 물리 경로

[Creator sandbox]
/app/workspace/blog/research/output.md
  = /host/data/workspace/blog/research/output.md  &amp;larr; 동일 물리 경로&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트가 어떤 격리 sandbox에서 실행되더라도 &lt;code&gt;/app/workspace/&lt;/code&gt;는 항상 같은 물리 위치를 가리킨다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Path A(sandbox) vs Path B(공유) 비교&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Path A &amp;mdash; per-agent sandbox&lt;/th&gt;
&lt;th&gt;Path B &amp;mdash; 공유 워크스페이스&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;경로 형식&lt;/td&gt;
&lt;td&gt;상대경로 (&lt;code&gt;./workspace/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;절대경로 (&lt;code&gt;/app/workspace/&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;물리 위치&lt;/td&gt;
&lt;td&gt;에이전트마다 다름&lt;/td&gt;
&lt;td&gt;모든 에이전트 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;에이전트 간 파일 공유&lt;/td&gt;
&lt;td&gt;불가&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heartbeat 간 영속성&lt;/td&gt;
&lt;td&gt;보장 안 됨&lt;/td&gt;
&lt;td&gt;보장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;용도&lt;/td&gt;
&lt;td&gt;임시 스크래치&lt;/td&gt;
&lt;td&gt;공식 산출물 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;절대경로 규칙: 5개 에이전트의 공통 약속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5개 에이전트(CEO, Researcher, Creator, Reviewer, Executor)는 각각의 시스템 프롬프트에 동일한 절대경로 규칙 섹션을 가진다. 실제 시스템 프롬프트에서 발췌한 CEO의 규칙은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;## ⚠️ 절대경로 규칙 (최우선)

모든 파일 참조는 호스트와 공유되는 절대경로를 사용한다.
본인의 격리 workspace(상대경로 ./workspace/)는 임시 스크래치 용도로만 쓴다.

| 종류              | 절대경로                                     |
|-------------------|----------------------------------------------|
| 스킬 파일         | /app/skills/{name}.md                        |
| Researcher 산출물 | /app/workspace/{project-name}/research/      |
| Creator 산출물    | /app/workspace/{project-name}/drafts/        |
| Reviewer 산출물   | /app/workspace/{project-name}/reviews/       |
| Executor 최종본   | /app/workspace/{project-name}/final/         |&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CEO가 정의하는 {project-name}&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CEO는 새 프로젝트 시작 시 &lt;code&gt;{project-name}&lt;/code&gt;을 결정하고 위임 메시지에 명시한다. 나머지 에이전트는 CEO가 지정한 프로젝트명을 임의로 변경하지 않는다. 이 규칙 하나가 파이프라인 전체의 경로 일관성을 보장한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할별 읽기/쓰기 경로 매핑&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;CEO:        읽기 &amp;mdash; 모든 하위 디렉토리
Researcher: 쓰기 &amp;mdash; /app/workspace/{project}/research/
            읽기 &amp;mdash; /app/skills/
Creator:    쓰기 &amp;mdash; /app/workspace/{project}/drafts/
            읽기 &amp;mdash; /app/workspace/{project}/research/, /app/skills/
Reviewer:   쓰기 &amp;mdash; /app/workspace/{project}/reviews/
            읽기 &amp;mdash; research/, drafts/, /app/skills/
Executor:   쓰기 &amp;mdash; /app/workspace/{project}/final/
            읽기 &amp;mdash; drafts/, reviews/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 디렉토리 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;research/ &amp;rarr; drafts/ &amp;rarr; reviews/ &amp;rarr; final/ 4단계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디렉토리 구조는 파이프라인 단계와 1:1로 대응한다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;/app/workspace/{project-name}/
├── research/    # Researcher 산출물 &amp;rarr; Creator, Reviewer가 읽음
├── drafts/      # Creator 산출물  &amp;rarr; Reviewer, Executor가 읽음
├── reviews/     # Reviewer 산출물 &amp;rarr; Executor가 읽음
└── final/       # Executor 최종본 &amp;rarr; Board 승인 후 외부 발행&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;현 블로그 프로젝트 구조 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈를 작성하면서 실제로 생성된 구조다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;/app/workspace/blog-personal-ai-company-setup/
│
├── research/
│   ├── 01-why-ai-company.md
│   ├── 02-paperclip-setup.md
│   ├── 03-agent-design.md
│   └── 04-shared-workspace.md
│
├── drafts/
│   ├── 01-why-ai-company.md
│   ├── 02-paperclip-setup.md
│   └── 03-agent-design.md
│
├── reviews/
│   ├── 01-why-ai-company.review.md
│   ├── 02-paperclip-setup.review.md
│   └── 03-agent-design.review.md
│
└── final/
    ├── 01-why-ai-company.md
    ├── 02-paperclip-setup.md
    └── 03-agent-design.md&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CEO의 초기화 책임&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CEO는 새 프로젝트를 시작할 때 Researcher에게 위임하기 전에 디렉토리 구조를 먼저 생성해야 한다. 이 단계를 빠뜨리면 Researcher의 첫 번째 저장에서 &lt;code&gt;FileNotFoundError&lt;/code&gt;가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# CEO가 프로젝트 시작 시 실행
mkdir -p /app/workspace/blog-personal-ai-company-setup/{research,drafts,reviews,final}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트러블슈팅: 경로 관련 자주 만나는 오류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 1: 파일 없음 (상대경로 저장 불일치)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;FileNotFoundError: /app/workspace/blog-project/research/output.md not found&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Creator가 Researcher 산출물을 찾지 못한다. Researcher가 완료 보고를 했는데도 파일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;br /&gt;Researcher가 &lt;code&gt;./workspace/research/output.md&lt;/code&gt;(상대경로)에 저장했다. Creator는 위임 메시지에 적힌 절대경로 &lt;code&gt;/app/workspace/blog-project/research/output.md&lt;/code&gt;를 읽으려 하지만 해당 위치에 파일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;br /&gt;Researcher 시스템 프롬프트의 &lt;code&gt;## ⚠️ 절대경로 규칙&lt;/code&gt; 섹션을 점검한다. 파일 존재 여부는 아래 명령으로 직접 확인한다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;ls /app/workspace/blog-project/research/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 2: 프로젝트 디렉토리 없음 (CEO 초기화 누락)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;FileNotFoundError: [Errno 2] No such file or directory:
'/app/workspace/new-project/research/output.md'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Researcher가 파일 저장을 시도하지만 디렉토리 자체가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;br /&gt;CEO가 새 프로젝트 시작 시 &lt;code&gt;/app/workspace/{project-name}/&lt;/code&gt; 하위 디렉토리를 생성하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;br /&gt;CEO가 Researcher에게 위임하기 전에 프로젝트 디렉토리 구조를 먼저 생성한다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;mkdir -p /app/workspace/{project-name}/{research,drafts,reviews,final}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 3: sandbox 혼동 (성공 보고 후 파일 없음)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;br /&gt;Researcher가 &quot;저장 완료&quot;를 보고했다. 에이전트 로그에는 Write 도구 호출이 성공으로 기록되어 있다. 하지만 &lt;code&gt;/app/workspace/project/research/&lt;/code&gt;를 확인하면 파일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;br /&gt;에이전트가 상대경로(&lt;code&gt;./workspace/research/output.md&lt;/code&gt;)로 파일을 저장했다. Write 도구 자체는 성공했지만 에이전트의 격리 sandbox에 저장됐다. 격리 sandbox는 공유 경로가 아니므로 다른 에이전트에서 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;br /&gt;에이전트 heartbeat 로그에서 실제 Write 도구 호출 경로를 확인한다. 경로가 &lt;code&gt;/app/workspace/&lt;/code&gt;로 시작하지 않으면 절대경로 규칙 위반이다. Paperclip UI &amp;rarr; 해당 에이전트 &amp;rarr; Instructions 탭에서 절대경로 규칙 섹션을 점검하고 재저장한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 4: 프로젝트명 불일치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;br /&gt;&lt;code&gt;/app/workspace/blog-personal-ai-company/research/&lt;/code&gt;에 파일이 있는데 Creator가 &lt;code&gt;/app/workspace/blog-personal-ai-company-setup/research/&lt;/code&gt;를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;br /&gt;CEO 위임 메시지에 기재한 &lt;code&gt;{project-name}&lt;/code&gt;과 에이전트가 실제로 사용한 디렉토리명이 다르다. 오타, 버전 불일치, 또는 에이전트가 임의로 프로젝트명을 변경했을 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;br /&gt;CEO가 위임 메시지에서 &lt;code&gt;{project-name}&lt;/code&gt;을 명확히 명시한다. 에이전트는 CEO가 지정한 프로젝트명을 임의로 변경하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# 실제 디렉토리 확인
ls /app/workspace/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 5: 스킬 파일 읽기 실패&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;증상&lt;/b&gt;&lt;br /&gt;에이전트가 스킬 파일 없이 기본 행동으로 작업을 진행한다. 블로그 문체 규칙이나 코드 스타일 가이드가 무시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;br /&gt;스킬 파일 경로가 상대경로이거나 오타가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;br /&gt;스킬 파일 참조 시 반드시 절대경로를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;올바름: /app/skills/blog-writing.md
잘못됨: ./skills/blog-writing.md
잘못됨: skills/blog-writing.md
잘못됨: ../skills/blog-writing.md&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며: 절대경로 하나로 팀 협업이 성립한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절대경로 규칙 하나가 5개 에이전트의 협업을 성립시킨다. 어느 한 에이전트가 이 규칙을 어기면 파이프라인 전체가 깨진다. 시스템 프롬프트에 &lt;code&gt;## ⚠️ 절대경로 규칙 (최우선)&lt;/code&gt;을 명시하고 모든 에이전트에 일관되게 적용하면, Researcher의 산출물이 Creator에게, Creator의 초안이 Reviewer에게, Reviewer의 검증본이 Executor에게 정확하게 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 이 파이프라인의 품질 게이트인 Reviewer 에이전트의 검수 기준과 실제 리뷰 산출물 구조를 살펴본다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>AI 에이전트 파일 공유</category>
      <category>bind-mount</category>
      <category>Paperclip 워크스페이스</category>
      <category>Personal AI Company</category>
      <category>에이전트 sandbox</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/276</guid>
      <comments>https://dragoner.tistory.com/276#entry276comment</comments>
      <pubDate>Wed, 20 May 2026 23:00:15 +0900</pubDate>
    </item>
    <item>
      <title>Personal AI Company 셋업 ③: 5개 에이전트 설계와 등록</title>
      <link>https://dragoner.tistory.com/275</link>
      <description>&lt;h2&gt;팀 구조 설계: 왜 5명인가&lt;/h2&gt;
&lt;p&gt;에이전트를 하나로 만들면 간단해 보이지만 실제로는 역효과이다. 단일 에이전트가 조사·생성·검증·실행을 모두 담당하면 시스템 프롬프트가 비대해지고, 한 역할의 실수가 전체 파이프라인에 영향을 준다.&lt;/p&gt;
&lt;p&gt;Personal AI Company는 &lt;strong&gt;단일 책임 원칙(Single Responsibility)&lt;/strong&gt;을 에이전트 설계에 적용했다. CEO는 조율만 하고, 나머지 4개가 각 단계를 전담한다. 이렇게 하면 에이전트 교체나 재설계가 쉽고, 각 역할의 품질 기준을 독립적으로 높일 수 있다.&lt;/p&gt;
&lt;p&gt;파이프라인 흐름은 다음과 같다: &lt;strong&gt;조사(Researcher) → 생성(Creator) → 검증(Reviewer) → 실행(Executor)&lt;/strong&gt;. CEO는 Board(사용자)의 고수준 지시를 이 4단계로 분해하여 child issue로 위임한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;각 에이전트 비교표&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;에이전트&lt;/th&gt;
&lt;th&gt;Paperclip role&lt;/th&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;th&gt;주요 역할&lt;/th&gt;
&lt;th&gt;월 예산&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CEO&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;분석·위임·검수, child issue 생성&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Researcher&lt;/td&gt;
&lt;td&gt;researcher&lt;/td&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;외부 정보 수집, research/ 저장&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creator&lt;/td&gt;
&lt;td&gt;engineer&lt;/td&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;블로그·코드·SNS 초안 생성&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer&lt;/td&gt;
&lt;td&gt;qa&lt;/td&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;스킬 기준 검수, 최대 2회 재검토&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executor&lt;/td&gt;
&lt;td&gt;devops&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Haiku 4.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;승인된 배포·Git 명령 실행&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Executor만 Haiku를 사용한다. 파일 복사, API 호출, Git 명령처럼 단순한 실행 작업에 Sonnet을 쓰는 건 과소비이기 때문이다. 설정 파일에도 이 의도가 주석으로 명시되어 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# executor.yaml
name: executor
model: claude-haiku-4-5-20251001  # 단순 실행 작업 — Haiku로 비용 절감
budget_monthly: 5&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;AGENTS.md로 시스템 프롬프트 관리하기&lt;/h2&gt;
&lt;p&gt;Paperclip은 에이전트 시스템 프롬프트를 &lt;strong&gt;managed bundle&lt;/strong&gt; 방식으로 관리한다. 실제 저장 위치는 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/paperclip/instances/default/companies/{companyId}/agents/{agentId}/instructions/AGENTS.md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AGENTS.md는 Claude Code가 대화 시작 시 자동으로 로드하는 특별 파일이다. Paperclip이 이 파일을 에이전트 시스템 프롬프트의 진입점으로 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;&lt;strong&gt;Append 패턴&lt;/strong&gt;이란 초기 역할 정의 위에 섹션을 점진적으로 추가하는 방식이다. 에이전트를 재시작하지 않아도 지침을 동적으로 수정할 수 있고, 변경 이력도 추적할 수 있다.&lt;/del&gt; &amp;lt;- 이런게 AI가 맘대로 작성한 내용이다. 그렇기 때문에 사용자는 모든걸 곧이곧대로 믿지 말고 항상 의심하고 검수해야만 한다. 그냥 단순히 AGENTS.md 파일 내의 내용을 추가하거나 변경 할 때 매번 Agents들을 재시작 할 필요 없다 라고 이해하면 된다.&lt;/p&gt;
&lt;p&gt;아래는 Creator의 AGENTS.md에서 절대경로 규칙 섹션을 추가한 예시이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;## ⚠️ 절대경로 규칙 (최우선)

모든 파일 참조는 호스트와 공유되는 절대경로를 사용한다.

- 스킬 파일: `/app/skills/{name}.md`
- 산출물 저장: `/app/workspace/{project-name}/drafts/`
- Researcher 자료 참조: `/app/workspace/{project-name}/research/`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 규칙이 없으면 에이전트가 상대경로로 파일을 참조해 호스트 파일시스템과 연결이 끊어진다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;instructionsFilePath 함정 피하기&lt;/h2&gt;
&lt;h3&gt;증상&lt;/h3&gt;
&lt;p&gt;에이전트가 올바른 역할로 동작하지 않거나, 특정 머신에서만 시스템 프롬프트가 로드된다. Paperclip UI에 경고 메시지가 표시되기도 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Using legacy relative instructionsFilePath;
migrate this agent to a managed or absolute external bundle.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;원인&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;instructionsFilePath&lt;/code&gt;는 &lt;strong&gt;레거시(deprecated) 필드&lt;/strong&gt;이다. 상대경로를 사용하면 &lt;code&gt;adapterConfig.cwd&lt;/code&gt;(에이전트 작업 디렉토리)를 기준으로 경로를 resolve한다. 환경마다 cwd가 다를 수 있어 경로가 불일치한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// agent-instructions.ts (간략화)
function resolveLegacyInstructionsPath(candidatePath: string, config: Record&amp;lt;string, unknown&amp;gt;): string {
  if (path.isAbsolute(candidatePath)) return candidatePath;
  const cwd = asString(config.cwd);
  if (!cwd || !path.isAbsolute(cwd)) {
    throw unprocessable(
      &amp;quot;Legacy relative instructionsFilePath requires adapterConfig.cwd to be set to an absolute path&amp;quot;,
    );
  }
  return path.resolve(cwd, candidatePath);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;해결책&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;권장 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Managed Bundle (Paperclip UI → Instructions 탭 직접 편집)&lt;/td&gt;
&lt;td&gt;✅ 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instructionsRootPath&lt;/code&gt;에 절대경로 지정&lt;/td&gt;
&lt;td&gt;✅ 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instructionsFilePath&lt;/code&gt;에 절대경로&lt;/td&gt;
&lt;td&gt;⚠️ 동작하나 비권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instructionsFilePath&lt;/code&gt;에 &lt;strong&gt;상대경로&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;신규 에이전트는 반드시 managed bundle로 시작하고, 기존 에이전트에 상대경로가 있다면 즉시 마이그레이션해야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;dangerouslySkipPermissions — 보안 트레이드오프&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dangerouslySkipPermissions&lt;/code&gt;는 &lt;code&gt;claude-local&lt;/code&gt; 어댑터의 설정 필드로, 기본값은 &lt;code&gt;true&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;Claude Code CLI는 파일 쓰기·터미널 실행 등 위험한 작업 전에 사용자 승인 프롬프트를 표시한다. Paperclip heartbeat는 완전 자동화된 헤드리스 실행이므로 사람이 이 프롬프트에 응답할 수 없다. 프롬프트가 대기 상태가 되면 heartbeat가 타임아웃될 때까지 멈추기 때문에, &lt;code&gt;dangerouslySkipPermissions: true&lt;/code&gt;는 자동화 실행의 구조적 필요조건이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;위험 측면&lt;/strong&gt;은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에이전트가 시스템의 임의 파일에 접근하거나 수정할 수 있음&lt;/li&gt;
&lt;li&gt;악의적 프롬프트 인젝션 공격 시 완충재 없음&lt;/li&gt;
&lt;li&gt;잘못 설계된 에이전트가 의도치 않은 파일을 덮어쓸 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;완화 조치&lt;/strong&gt;도 함께 작동한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예산 강제&lt;/strong&gt;: 월 예산 초과 시 에이전트 자동 중단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;거버넌스&lt;/strong&gt;: Board가 언제든 에이전트를 pause/terminate 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;샌드박스 실행&lt;/strong&gt;: sandbox 환경에서는 &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; 대신 허용 도구 목록(&lt;code&gt;--allowedTools&lt;/code&gt;)으로 대체&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;감사 로그&lt;/strong&gt;: 모든 도구 호출 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// permissions.ts
export function buildClaudeExecutionPermissionArgs(input: {
  dangerouslySkipPermissions: boolean;
  targetIsSandbox: boolean;
}): string[] {
  if (!input.dangerouslySkipPermissions) return [];
  if (input.targetIsSandbox) {
    return [&amp;quot;--allowedTools&amp;quot;, SANDBOX_ALLOWED_TOOLS];  // 샌드박스: 목록 제한
  }
  return [&amp;quot;--dangerously-skip-permissions&amp;quot;];  // 로컬: 전체 허용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로컬 환경에서 자신이 통제하는 인프라에서 실행하는 경우 합리적인 선택이다. 공용 서버나 신뢰할 수 없는 입력이 있는 환경에서는 반드시 샌드박스 실행을 검토해야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Paperclip UI에서 에이전트 등록하기&lt;/h2&gt;
&lt;p&gt;에이전트 등록 절차는 다음 순서로 진행한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Paperclip 대시보드 → Agents 탭&lt;/strong&gt;으로 이동한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New Agent&lt;/strong&gt; 버튼을 클릭하고 이름과 role(researcher, engineer, qa, devops 중 선택)을 입력한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt; 필드에서 모델을 선택한다. Executor는 &lt;code&gt;claude-haiku-4-5-20251001&lt;/code&gt;, 나머지는 &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;을 지정한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Budget&lt;/strong&gt; 필드에 월 예산 한도(USD)를 입력한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instructions 탭&lt;/strong&gt;으로 이동해 시스템 프롬프트를 편집한다. 여기서 편집한 내용이 managed bundle(AGENTS.md)로 저장된다.&lt;/li&gt;
&lt;li&gt;절대경로 규칙(&lt;code&gt;/app/workspace/{project-name}/&lt;/code&gt;)을 Instructions 최상단에 명시한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt;를 클릭하면 다음 heartbeat부터 새 Instructions가 적용된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;instructionsFilePath&lt;/code&gt;나 &lt;code&gt;instructionsRootPath&lt;/code&gt;를 별도로 설정하지 않아도 되며, UI에서 직접 편집하는 managed bundle이 기본값이자 권장 방식이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;다음 편 예고&lt;/h2&gt;
&lt;p&gt;4편에서는 CEO가 child issue를 생성하고 blocked 상태를 관리하는 &lt;strong&gt;워크플로우 자동화&lt;/strong&gt;를 다룬다. &lt;code&gt;POST /api/issues/{id}/children&lt;/code&gt; 패턴과 tree-hold 메커니즘을 실전 예시로 살펴본다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>AGENTS.md</category>
      <category>AI 에이전트</category>
      <category>claude code</category>
      <category>paperclip</category>
      <category>역할 분리</category>
      <category>자율 에이전트</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/275</guid>
      <comments>https://dragoner.tistory.com/275#entry275comment</comments>
      <pubDate>Mon, 18 May 2026 21:00:09 +0900</pubDate>
    </item>
    <item>
      <title>Personal AI Company 셋업 ②: Paperclip + Claude Code Max 셋업</title>
      <link>https://dragoner.tistory.com/274</link>
      <description>&lt;h2&gt;들어가며&lt;/h2&gt;
&lt;p&gt;Personal AI Company 시리즈 1편에서 전체 아키텍처와 개념을 살펴봤다. 2편에서는 Paperclip을 실제로 설치하고 Claude Code Max와 연결하는 구체적인 절차를 다룬다. 이 글을 따라하면 로컬 또는 서버 환경에서 Paperclip API 서버가 실행되고, Claude Code가 에이전트로 동작하는 상태가 된다.&lt;/p&gt;
&lt;h2&gt;사전 요구사항&lt;/h2&gt;
&lt;h3&gt;필수 항목 확인&lt;/h3&gt;
&lt;p&gt;아래 항목이 모두 준비되어 있어야 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;버전 / 조건&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;20 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pnpm&lt;/td&gt;
&lt;td&gt;9.15 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 구독&lt;/td&gt;
&lt;td&gt;Max 플랜 (Claude Code 사용 필수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 체제&lt;/td&gt;
&lt;td&gt;macOS, Linux, Windows (WSL2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Claude Max 구독이 필요한 이유&lt;/h3&gt;
&lt;p&gt;Paperclip은 Claude Code를 에이전트 런타임으로 사용한다. Claude Code는 Claude Max 플랜 이상에서 높은 사용량 한도로 에이전트 루프 실행이 가능하다. 구독 없이 시작하면 에이전트 실행이 즉시 제한되므로 시작 전에 반드시 확인한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Claude Max 구독 가격은 Anthropic 공식 사이트에서 확인한다 (공식 문서 없음, 추가 확인 필요).&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;Node.js와 pnpm 설치 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;node --version   # 20.x 이상인지 확인
pnpm --version   # 9.15 이상인지 확인

# pnpm이 설치되어 있지 않다면
npm install -g pnpm&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Paperclip 설치&lt;/h2&gt;
&lt;h3&gt;npx 원커맨드 설치 (권장)&lt;/h3&gt;
&lt;p&gt;가장 빠른 설치 방법이다. 아래 한 줄로 Paperclip 설치와 초기 설정이 완료된다. 내장 PostgreSQL이 자동 생성되어 별도 DB 설정이 불필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx paperclipai onboard --yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이미 설정이 있는 경우 &lt;code&gt;onboard&lt;/code&gt;를 재실행해도 기존 설정을 유지한다. 설정 변경이 필요한 경우 &lt;code&gt;paperclipai configure&lt;/code&gt; 명령을 사용한다.&lt;/p&gt;
&lt;h3&gt;bind 모드 선택&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--bind&lt;/code&gt; 옵션으로 서버의 접근 범위를 설정한다. 처음부터 자신의 용도에 맞는 모드를 선택하는 것이 중요하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;모드&lt;/th&gt;
&lt;th&gt;옵션&lt;/th&gt;
&lt;th&gt;접근 범위&lt;/th&gt;
&lt;th&gt;적합한 상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;로컬 (기본)&lt;/td&gt;
&lt;td&gt;옵션 생략&lt;/td&gt;
&lt;td&gt;localhost만&lt;/td&gt;
&lt;td&gt;개발·테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAN&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--bind lan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;같은 Wi-Fi 내 기기&lt;/td&gt;
&lt;td&gt;팀 내부 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tailscale&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--bind tailnet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tailscale 망 전체&lt;/td&gt;
&lt;td&gt;원격·스마트폰 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# LAN 접근 허용 (같은 네트워크의 다른 기기에서 접근)
npx paperclipai onboard --yes --bind lan

# Tailscale 망 내 접근 (원격 접근, 스마트폰 관리용)
npx paperclipai onboard --yes --bind tailnet&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;수동 설치 (개발자용)&lt;/h3&gt;
&lt;p&gt;소스 코드를 직접 수정하거나 커스텀 환경을 구성할 때 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/paperclipai/paperclip.git
cd paperclip
pnpm install
pnpm dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버가 시작되면 API 서버는 &lt;code&gt;http://localhost:3100&lt;/code&gt;에서 동작한다.&lt;/p&gt;
&lt;h2&gt;환경변수 설정&lt;/h2&gt;
&lt;p&gt;수동 설치의 경우 또는 기존 설정을 수정할 때 &lt;code&gt;.env&lt;/code&gt; 파일을 직접 편집한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cp .env.example .env&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 환경변수는 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dotenv&quot;&gt;# PostgreSQL 연결 문자열
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip

# API 서버 포트 (기본: 3100)
PORT=3100

# UI 서빙 여부 (별도 프론트엔드 서버 사용 시 false)
SERVE_UI=false

# 인증 시크릿 키 ⚠️ 프로덕션에서 반드시 변경
BETTER_AUTH_SECRET=paperclip-dev-secret&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;BETTER_AUTH_SECRET 설정&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;BETTER_AUTH_SECRET&lt;/code&gt;은 세션 인증에 사용하는 핵심 보안 키다. 기본값 &lt;code&gt;paperclip-dev-secret&lt;/code&gt;은 개발 전용이며, 외부 접근이 가능한 환경에서는 반드시 강력한 난수로 교체해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# macOS / Linux
openssl rand -base64 32

# Node.js
node -e &amp;quot;console.log(require(&amp;#39;crypto&amp;#39;).randomBytes(32).toString(&amp;#39;base64&amp;#39;))&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;생성된 값을 &lt;code&gt;.env&lt;/code&gt; 파일의 &lt;code&gt;BETTER_AUTH_SECRET&lt;/code&gt;에 입력한다. 이 값이 유출되면 세션 위조가 가능하므로 Git에 커밋하지 않도록 &lt;code&gt;.gitignore&lt;/code&gt;에 &lt;code&gt;.env&lt;/code&gt;가 포함되어 있는지 반드시 확인한다. 값을 변경하면 모든 활성 세션이 무효화된다.&lt;/p&gt;
&lt;h2&gt;Claude CLI 설치 및 인증&lt;/h2&gt;
&lt;h3&gt;Claude CLI 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g @anthropic-ai/claude-code

# 설치 확인
claude --version&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;OAuth 인증 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Claude CLI 로그인 (브라우저 OAuth 팝업)
claude auth login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 명령을 실행하면 브라우저가 열리고 Anthropic 계정 로그인 페이지로 이동한다. Claude Max 플랜이 연결된 계정으로 로그인한다. 인증이 완료되면 credentials가 &lt;code&gt;~/.claude/&lt;/code&gt; 디렉토리에 저장된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 인증 상태 확인
claude auth status&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;컨테이너 환경에서 credentials 전달&lt;/h3&gt;
&lt;p&gt;Docker 등 컨테이너 환경에서는 호스트의 &lt;code&gt;~/.claude/&lt;/code&gt; 디렉토리를 컨테이너 내부에 전달해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;아래 내용은 공식 README에 Docker 가이드가 없으므로, 실제 운영 환경에서 관찰된 패턴 기반이다 (공식 문서 없음, 추가 확인 필요).&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;방법 1: Volume 마운트 (권장)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# docker-compose.yml
services:
  paperclip:
    volumes:
      - ~/.claude:/root/.claude:ro  # 읽기 전용 마운트&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;방법 2: 직접 복사&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 호스트에서 컨테이너로 복사
docker cp ~/.claude/. &amp;lt;container_id&amp;gt;:/root/.claude/

# 또는 컨테이너 내부에서 직접 인증
docker exec -it &amp;lt;container_id&amp;gt; claude auth login&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;트러블슈팅&lt;/h2&gt;
&lt;h3&gt;T1. Node.js 버전 오류로 설치 실패&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;증상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Error: The engine &amp;quot;node&amp;quot; is incompatible with this module&lt;/code&gt; 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;원인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js 버전이 20 미만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결책&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nvm install 20 &amp;amp;&amp;amp; nvm use 20&lt;/code&gt; 실행 또는 공식 Node.js 사이트에서 최신 LTS를 설치한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;T2. BETTER_AUTH_SECRET 미설정으로 인증 실패&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;증상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로그인 시도 시 세션이 즉시 만료되거나 &amp;quot;Invalid secret&amp;quot; 오류가 발생한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;원인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;에 &lt;code&gt;BETTER_AUTH_SECRET&lt;/code&gt;이 없거나 기본값(&lt;code&gt;paperclip-dev-secret&lt;/code&gt;) 상태로 프로덕션 모드 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결책&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openssl rand -base64 32&lt;/code&gt;로 새 시크릿 생성 후 &lt;code&gt;.env&lt;/code&gt;에 입력하고 서버를 재시작한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;T3. Claude 인증 후에도 에이전트가 &amp;quot;인증 오류&amp;quot; 반환&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;증상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude auth status&lt;/code&gt;는 정상인데, Paperclip 에이전트 실행 시 &amp;quot;Authentication required&amp;quot; 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;원인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;컨테이너·격리 환경에서 &lt;code&gt;~/.claude&lt;/code&gt; credentials를 찾지 못함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결책&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;호스트의 &lt;code&gt;~/.claude&lt;/code&gt;를 컨테이너 내부로 마운트하거나, 컨테이너 내부에서 직접 &lt;code&gt;claude auth login&lt;/code&gt;을 실행한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;T4. 포트 3100 충돌로 서버 시작 실패&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;증상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Error: listen EADDRINUSE: address already in use :::3100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;원인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;다른 프로세스가 이미 3100 포트를 사용 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결책&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.env&lt;/code&gt;에서 &lt;code&gt;PORT=3101&lt;/code&gt;(또는 다른 빈 포트)로 변경 후 재시작. &lt;code&gt;lsof -i :3100&lt;/code&gt;으로 충돌 프로세스를 먼저 확인한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;T5. Tailscale bind 모드에서 외부 접근 불가&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;증상&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--bind tailnet&lt;/code&gt;으로 시작했는데 외부 기기에서 대시보드에 접근할 수 없다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;원인&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailscale이 설치되어 있지 않거나 호스트가 Tailscale 망에 미연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;해결책&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailscale 설치 및 &lt;code&gt;tailscale up&lt;/code&gt;으로 로그인 후 Paperclip을 재시작한다. 접근 시 Tailscale IP를 사용한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;다음 편 예고&lt;/h2&gt;
&lt;p&gt;3편에서는 Paperclip 위에서 동작하는 첫 에이전트를 직접 정의하고, CEO-Researcher-Creator 역할 분리 구조를 실제로 구성하는 방법을 다룬다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>AI 에이전트 설치</category>
      <category>BETTER_AUTH_SECRET</category>
      <category>Claude Code Max</category>
      <category>Claude OAuth</category>
      <category>npx onboard</category>
      <category>Paperclip AI</category>
      <category>Personal AI Company</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/274</guid>
      <comments>https://dragoner.tistory.com/274#entry274comment</comments>
      <pubDate>Mon, 18 May 2026 18:00:59 +0900</pubDate>
    </item>
    <item>
      <title>Personal AI Company 셋업 ①: 왜 1인 AI 회사인가</title>
      <link>https://dragoner.tistory.com/273</link>
      <description>&lt;p&gt;AI 에이전트를 여러 개 동시에 운영하다 보면 터미널 탭이 20개를 넘어가고, 어느 에이전트가 무엇을 하고 있는지 파악하기 어려워진다. 1인 AI 회사(Personal AI Company)는 이 문제를 &amp;quot;에이전트를 도구로 쓰지 말고 회사처럼 운영한다&amp;quot;는 관점으로 해결한다. Paperclip은 그 운영 인프라를 제공하는 오픈소스 오케스트레이션 플랫폼이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 문제: AI 에이전트를 많이 쓸수록 복잡해진다&lt;/h2&gt;
&lt;h3&gt;1-1. 터미널 탭 20개의 혼돈&lt;/h3&gt;
&lt;p&gt;Claude Code, Cursor, Copilot 등 AI 에이전트를 적극적으로 활용하기 시작하면 곧 관리 문제에 부딪힌다. 에이전트 하나에 터미널 세션 하나씩 붙여두면, 어느 시점부터는 어느 에이전트가 어느 작업을 담당하는지 직접 추적해야 한다. 재시작하면 컨텍스트가 모두 사라지고, 처음부터 다시 설명해야 한다.&lt;/p&gt;
&lt;h3&gt;1-2. 조율과 비용 통제가 없다&lt;/h3&gt;
&lt;p&gt;단일 에이전트에게 조사, 생성, 검증, 배포를 모두 맡기면 품질이 들쭉날쭉해진다. pay-as-you-go API 방식이라면 토큰 루프 하나로 예상치 못한 청구서가 날아오기도 한다. 에이전트가 &amp;quot;왜&amp;quot; 이 작업을 해야 하는지 모르기 때문에 주어진 지시의 경계를 벗어나기도 한다.&lt;/p&gt;
&lt;p&gt;핵심 문제는 AI 모델 성능이 아니다. 에이전트를 어떻게 조율하느냐가 핵심이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 해결책: 직원이 아닌 회사&lt;/h2&gt;
&lt;h3&gt;2-1. Paperclip이란&lt;/h3&gt;
&lt;p&gt;Paperclip은 AI 에이전트를 회사 수준으로 운영하는 오픈소스 오케스트레이션 플랫폼이다(MIT 라이선스). 공식 문서의 표현을 빌리면 &amp;quot;Claude Code가 직원이라면, Paperclip은 그 회사다.&amp;quot; 조직도, 예산, 거버넌스, 티켓 시스템, 목표 계층을 갖추고 있어 에이전트가 항상 자신이 무엇을 왜 해야 하는지 알고 실행한다.&lt;/p&gt;
&lt;h3&gt;2-2. 핵심 구조&lt;/h3&gt;
&lt;p&gt;Paperclip은 다음 메커니즘으로 에이전트 혼돈을 해결한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;티켓 기반 작업 추적&lt;/strong&gt;: 모든 작업이 이슈로 관리되며 에이전트가 checkout하여 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent agent state&lt;/strong&gt;: Heartbeat(주기적 wakeup) 사이에도 작업 컨텍스트가 유지된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;목표 계층(Goal Tree)&lt;/strong&gt;: 작업이 상위 목표와 연결되어 에이전트가 &amp;quot;왜&amp;quot;를 항상 파악한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에이전트별 예산&lt;/strong&gt;: 월 예산 초과 시 자동 일시정지로 비용 폭주를 막는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Governance with rollback&lt;/strong&gt;: 승인 게이트와 설정 버전 관리로 변경을 통제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로컬 설치는 Node.js 20+, pnpm 9.15+만 있으면 된다(공식 README 기준).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx paperclipai onboard --yes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 명령 한 줄로 로컬 서버와 내장 PostgreSQL이 구동된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 5개 에이전트 조직도&lt;/h2&gt;
&lt;p&gt;Personal AI Company의 기본 구성은 5가지 역할로 이루어진다. 각 역할은 독립된 시스템 프롬프트로 정의되며, Paperclip의 Org Chart 기능이 상하 보고 구조를 관리한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;담당&lt;/th&gt;
&lt;th&gt;역할 설명&lt;/th&gt;
&lt;th&gt;주요 산출물&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CEO&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CEO&lt;/td&gt;
&lt;td&gt;Board(사용자)로부터 지시를 받아 작업을 분해·위임·검수한다. 직접 산출물을 만들지 않는다.&lt;/td&gt;
&lt;td&gt;child issue, 작업 분해&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Researcher&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Researcher&lt;/td&gt;
&lt;td&gt;조사 목표와 범위를 확인하고 정보를 수집·구조화한다.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/research/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Creator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creator&lt;/td&gt;
&lt;td&gt;Researcher 자료를 바탕으로 콘텐츠·코드를 생성한다.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/drafts/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reviewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reviewer&lt;/td&gt;
&lt;td&gt;Creator 산출물을 스킬 파일 기준으로 검증하고 통과/수정 판정을 내린다.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/reviews/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Executor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Executor&lt;/td&gt;
&lt;td&gt;Board 승인 후 최종 산출물을 외부에 배포한다(블로그 발행, git push 등).&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/final/*.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;워크플로우 방향은 &lt;strong&gt;Board → CEO → Researcher → Creator → Reviewer → Executor&lt;/strong&gt; 순서다. 각 단계는 child issue로 직렬 위임되며, 이전 단계가 완료되어야 다음 단계가 시작된다. 파일시스템은 역할별 디렉토리로 분리되어 에이전트 간 산출물 충돌을 방지한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 비용 구조: Claude Max + Paperclip OSS&lt;/h2&gt;
&lt;h3&gt;4-1. Claude Max 구독 정책&lt;/h3&gt;
&lt;p&gt;Paperclip은 Claude Code를 에이전트 런타임으로 사용한다. 비용은 Anthropic의 Claude Max 구독으로 처리한다(공식 페이지 기준).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;플랜&lt;/th&gt;
&lt;th&gt;월 구독료&lt;/th&gt;
&lt;th&gt;용량&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Claude Pro&lt;/td&gt;
&lt;td&gt;$20/월&lt;/td&gt;
&lt;td&gt;기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Max (기본)&lt;/td&gt;
&lt;td&gt;$100/월&lt;/td&gt;
&lt;td&gt;Pro의 5배&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Max (고용량)&lt;/td&gt;
&lt;td&gt;$200/월&lt;/td&gt;
&lt;td&gt;Pro의 20배&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;4-2. 토큰 추가 과금이 없는 의미&lt;/h3&gt;
&lt;p&gt;API 키 기반 pay-as-you-go 방식과 달리, Claude Max는 구독 고정비만으로 에이전트를 24시간 운영해도 추가 청구가 없다. 1인 운영자 입장에서 월 비용을 $100~$200로 고정할 수 있다는 것은 비용 예측이 단순해진다는 의미다. Paperclip의 에이전트별 예산 기능과 결합하면 각 에이전트의 사용량을 더 세밀하게 통제할 수 있다.&lt;/p&gt;
&lt;p&gt;Paperclip 자체는 MIT 오픈소스이므로 별도 라이선스 비용이 없다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 실전 시나리오&lt;/h2&gt;
&lt;h3&gt;시나리오 A: 블로그 자동화 파이프라인&lt;/h3&gt;
&lt;p&gt;이 글 자체가 해당 파이프라인으로 작성됐다. (물론 AI가 작성한 모든걸 믿지 않기 때문에 전체 내용을 리뷰 한 상태이다.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Board가 Paperclip에 이슈 등록: &amp;quot;Personal AI Company 셋업 시리즈 1편 작성&amp;quot;&lt;/li&gt;
&lt;li&gt;CEO가 작업을 분해하고 Researcher child issue를 생성한다.&lt;/li&gt;
&lt;li&gt;Researcher가 참조 파일을 읽고 리서치 문서를 &lt;code&gt;/research/&lt;/code&gt; 에 저장한다.&lt;/li&gt;
&lt;li&gt;Creator가 리서치 자료 기반으로 블로그 초안을 &lt;code&gt;/drafts/&lt;/code&gt; 에 작성한다.&lt;/li&gt;
&lt;li&gt;Reviewer가 스킬 파일 기준으로 검수(맞춤법, SEO, 코드 정확성)한다.&lt;/li&gt;
&lt;li&gt;Board 승인 후 Executor가 GitHub Pages 또는 Tistory에 발행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;시나리오 B: Swift 코드 생성 파이프라인&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Board: &amp;quot;VIPER 패턴으로 로그인 화면 구현&amp;quot;&lt;/li&gt;
&lt;li&gt;CEO가 Creator에게 직행 위임한다(사전 조사가 불필요한 경우).&lt;/li&gt;
&lt;li&gt;Creator가 &lt;code&gt;/app/skills/swift-coding.md&lt;/code&gt; 규칙에 따라 SnapKit + async/await 코드를 생성한다.&lt;/li&gt;
&lt;li&gt;Reviewer가 컴파일 가능 여부, VIPER 레이어 분리, 네이밍 컨벤션을 검수한다.&lt;/li&gt;
&lt;li&gt;Executor가 git commit하고 PR을 생성한다(main 브랜치 push는 Board 승인 필수).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;시나리오 C: 정기 리포트 자동화&lt;/h3&gt;
&lt;p&gt;Paperclip의 Routines(스케줄) 기능을 활용하면 매주 월요일 오전 9시 같은 트리거를 설정할 수 있다. 트리거 시 Paperclip이 자동으로 이슈를 생성하고 에이전트를 깨운다(Heartbeat). Researcher가 지난 주 작업 현황을 수집하고, Creator가 요약 리포트를 작성한 뒤, Executor가 Telegram이나 이메일로 발송한다. 사람의 개입 없이 정기 보고가 자동화된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 이 시리즈에서 다루는 내용&lt;/h2&gt;
&lt;p&gt;이 시리즈는 Personal AI Company를 직접 구축하는 과정을 순서대로 기록한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1편 (이 글)&lt;/strong&gt;: Personal AI Company 셋업 ①: 왜 1인 AI 회사인가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2편&lt;/strong&gt;: Personal AI Company 셋업 ②: Paperclip + Claude Code Max 셋업&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3편&lt;/strong&gt;: Personal AI Company 셋업 ③: 5개 에이전트 설계와 등록&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4편&lt;/strong&gt;: Personal AI Company 셋업 ④: Path B — 에이전트 간 파일 공유의 해법&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5편&lt;/strong&gt;: Personal AI Company 셋업 ⑤: 자동 wakeup 패턴으로 에이전트 파이프라인 직렬화하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6편&lt;/strong&gt;: Personal AI Company 셋업 ⑥: Telegram bridge로 어디서든 에이전트 제어하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;7편&lt;/strong&gt;: Personal AI Company 셋업 ⑦: 개발자 메모 — 자잘한 함정들&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;다음 편 예고&lt;/strong&gt;: 2편에서는 &lt;code&gt;npx paperclipai onboard --yes&lt;/code&gt; 한 줄부터 시작해 Org Chart와 5개 에이전트를 실제로 등록하는 과정을 다룬다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>1인AI회사</category>
      <category>ai에이전트</category>
      <category>ClaudeMax</category>
      <category>paperclip</category>
      <category>에이전트자동화</category>
      <category>오케스트레이션</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/273</guid>
      <comments>https://dragoner.tistory.com/273#entry273comment</comments>
      <pubDate>Mon, 18 May 2026 11:56:19 +0900</pubDate>
    </item>
    <item>
      <title>[Codewars] [7Kyu] V A P O R C O D E</title>
      <link>https://dragoner.tistory.com/267</link>
      <description>&lt;figure id=&quot;og_1732949092045&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Codewars - Achieve mastery through coding practice and developer mentorship&quot; data-og-description=&quot;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&quot; data-og-host=&quot;www.codewars.com&quot; data-og-source-url=&quot;https://www.codewars.com/kata/5966eeb31b229e44eb00007a/swift&quot; data-og-url=&quot;https://www.codewars.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c50bE2/hyXGFJJUQb/t5pkDAUmVXdIKGA4aPuFs0/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/b8tf5w/hyXDe76YCQ/ypaJuCQvWqF8faIqQATEq1/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374&quot;&gt;&lt;a href=&quot;https://www.codewars.com/kata/5966eeb31b229e44eb00007a/swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codewars.com/kata/5966eeb31b229e44eb00007a/swift&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c50bE2/hyXGFJJUQb/t5pkDAUmVXdIKGA4aPuFs0/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/b8tf5w/hyXDe76YCQ/ypaJuCQvWqF8faIqQATEq1/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Codewars - Achieve mastery through coding practice and developer mentorship&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codewars.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 받은 문자열을 특정 조건에 맞게 변환해서 반환하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 받은 문자열의 각 문자를 대문자로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 각 문자 사이 간격 스페이스2개로 구분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 원 문자의 간격은 무시&lt;/p&gt;
&lt;pre id=&quot;code_1732950407753&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func vaporcode(_ str: String) -&amp;gt; String {
    var strArr = str.compactMap({ $0.uppercased() }) // split(separator: &quot;&quot;)
    strArr.removeAll(where: { $0 == &quot; &quot; })
    let returnValue = strArr.joined(separator: &quot;  &quot;)
    return returnValue
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 어려울게 딱히 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 조건대로 사용해주면 되고, 간략하게 적느냐 정도의 차이만 있네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Codewars(알고리즘)/7Kyu</category>
      <category>7kyu</category>
      <category>codewars</category>
      <category>Solution</category>
      <category>SWIFT</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/267</guid>
      <comments>https://dragoner.tistory.com/267#entry267comment</comments>
      <pubDate>Sat, 30 Nov 2024 16:07:38 +0900</pubDate>
    </item>
    <item>
      <title>[Codewars] [5Kyu] Consecutive k-Primes</title>
      <link>https://dragoner.tistory.com/266</link>
      <description>&lt;figure id=&quot;og_1732944066184&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Codewars - Achieve mastery through coding practice and developer mentorship&quot; data-og-description=&quot;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&quot; data-og-host=&quot;www.codewars.com&quot; data-og-source-url=&quot;https://www.codewars.com/kata/573182c405d14db0da00064e/train/swift&quot; data-og-url=&quot;https://www.codewars.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5vykM/hyXGBHkyJE/e3qrCxEMWZhshQ4ARoLvm1/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/dbAJDZ/hyXGKEgDsN/Awq2okTzQ065Ru83pNC4t1/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374&quot;&gt;&lt;a href=&quot;https://www.codewars.com/kata/573182c405d14db0da00064e/train/swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codewars.com/kata/573182c405d14db0da00064e/train/swift&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5vykM/hyXGBHkyJE/e3qrCxEMWZhshQ4ARoLvm1/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/dbAJDZ/hyXGKEgDsN/Awq2okTzQ065Ru83pNC4t1/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Codewars - Achieve mastery through coding practice and developer mentorship&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codewars.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 소인수분해를 해서 나온 소수들의 갯수를 확인하고, k 값과 동일한 소수의 갯수가 연속되는 횟수에 대해서 구하는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Swift&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-1. 본인의 풀이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소인수분해를 하는 함수를 하나 만들고(primeCountChecker) 해당 함수에서 반환하는 값을 비교해서 k와 동일한 값이 연속해서 반환되는 횟수를 구했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732948423740&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func consecKprimes(_ k: Int, _ arr: [Int]) -&amp;gt; Int {
    // your code
  var returnValue: Int = 0
  var isContinue: Bool = false
  for element in arr {
    let test = primeCountChecker(element)
    guard test == k else {
      if isContinue { 
        isContinue = false
      }
      continue
    }
    if isContinue == false {
      isContinue = true 
    } else {
      returnValue += 1
    }
    
  }
  return returnValue
}

func primeCountChecker(_ n: Int) -&amp;gt; Int {
  var num = n
  var result: [Int] = []
  var count = 2
  while num != 1 {
    if num % count == 0 {
      num = num / count
      result.append(count)
      count = 2
    } else {
      count += 1
    }
  }
  return result.count
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;primeCountChecker의 동작은 나머지가 0인 값이 나오면 해당 값으로 나눈 결과물을 num에 저장하고, 그 값을 다시 2부터 차례로 나누게 하는 식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 함수에서 result 배열에는 소수들이 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-2. Best Solution&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Best Practices를 받은 문제풀이입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732948450282&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func consecKprimes(_ k: Int, _ arr: [Int]) -&amp;gt; Int {
    
    func prim(_ n: Int) -&amp;gt; Int {
        var na = n, i = 2, cn = 0
        while na &amp;gt; 1 { while na % i == 0 { cn += 1; na /= i }; i += 1 }
        return cn
    }
    
    var i = 0, cn = 0
    while i &amp;lt; arr.count - 1 {
        if (prim(arr[i]) == k) &amp;amp;&amp;amp; (prim(arr[i + 1]) == k)
        {cn += 1}
        i += 1;
    }
    
    return cn
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작의 원리 자체는 비슷하지만, 훨씬 간략하게 작성했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘을 풀다보면 언제나 간략하게 작성하는 사람들을 보게되는데, 항상 신기하게 느껴집니다..&lt;/p&gt;</description>
      <category>Codewars(알고리즘)/5Kyu</category>
      <category>5kyu</category>
      <category>codewars</category>
      <category>Solution</category>
      <category>SWIFT</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/266</guid>
      <comments>https://dragoner.tistory.com/266#entry266comment</comments>
      <pubDate>Sat, 30 Nov 2024 15:40:41 +0900</pubDate>
    </item>
    <item>
      <title>[Codewars] [8Kyu] Convert a String to a Number!</title>
      <link>https://dragoner.tistory.com/264</link>
      <description>&lt;figure id=&quot;og_1732941169449&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Codewars - Achieve mastery through coding practice and developer mentorship&quot; data-og-description=&quot;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&quot; data-og-host=&quot;www.codewars.com&quot; data-og-source-url=&quot;https://www.codewars.com/kata/544675c6f971f7399a000e79/train/swift&quot; data-og-url=&quot;https://www.codewars.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9efeT/hyXDfzaZnp/5EFiYHuSeNAAAivoUXFFD1/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/mqolX/hyXDc3n8ih/kDV4pfe0MF4QsakWWF2cjk/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374&quot;&gt;&lt;a href=&quot;https://www.codewars.com/kata/544675c6f971f7399a000e79/train/swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codewars.com/kata/544675c6f971f7399a000e79/train/swift&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9efeT/hyXDfzaZnp/5EFiYHuSeNAAAivoUXFFD1/img.png?width=1280&amp;amp;height=670&amp;amp;face=0_0_1280_670,https://scrap.kakaocdn.net/dn/mqolX/hyXDc3n8ih/kDV4pfe0MF4QsakWWF2cjk/img.png?width=1200&amp;amp;height=630&amp;amp;face=271_208_422_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Codewars - Achieve mastery through coding practice and developer mentorship&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A coding practice website for all programming levels &amp;ndash; Join a community of over 3 million developers and improve your coding skills in over 55 programming languages!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codewars.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 매우 간단한 String -&amp;gt; Int 변경 관련 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 조건에 적혀있는대로면 String은 무조건 들어오고, 그 값 또한 무조건 Int라고 하네요.&lt;/p&gt;
&lt;pre id=&quot;code_1732941371493&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func stringToNumber(_ string:String) -&amp;gt; Int {
  return Int(string)!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 해설도 필요없지만, 변환하는 방법들을 적어보기 위해서 갖고왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String에서 Int로 변환할 때는 해당 String(string으로 명명) 값이 정확히 정수로 들어온다는 조건 하에 다음과 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732941610842&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let returnValue = Int(string) // optional Int value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Int의 생성자 중 String을 바로 받는게 있어서 가능한데요, 문서에서 관련 내용을 찾아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/int#Converting-Strings&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/swift/int#Converting-Strings&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcK6xp/btsK1IHKOSd/T7JPJjF3tUX0R1LlhE844K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcK6xp/btsK1IHKOSd/T7JPJjF3tUX0R1LlhE844K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcK6xp/btsK1IHKOSd/T7JPJjF3tUX0R1LlhE844K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcK6xp%2FbtsK1IHKOSd%2FT7JPJjF3tUX0R1LlhE844K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1692&quot; height=&quot;622&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용에 따르면 string으로 받은 값을 optional Int로 반환해주는 생성자이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 이런 Convert관련된 함수는 잘 되어있는 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 optional return value들은 optional이 아닌 변수에 할당할 수 없습니다. 별다른 조치를 취하지 않으면 다음과 같은 에러가 발생하겠죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Suve/btsK2tQtLP4/hKLmi4C3TTYKoYtgsHkpB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Suve/btsK2tQtLP4/hKLmi4C3TTYKoYtgsHkpB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Suve/btsK2tQtLP4/hKLmi4C3TTYKoYtgsHkpB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Suve%2FbtsK2tQtLP4%2FhKLmi4C3TTYKoYtgsHkpB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1096&quot; height=&quot;168&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기본값을 ?? 통해서 지정하거나&lt;/p&gt;
&lt;pre id=&quot;code_1732941973283&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let returnValue = Int(string) ?? 0 // not optional Int value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. !를 통해서 force-unwrap을 해주거나.&lt;/p&gt;
&lt;pre id=&quot;code_1732941983965&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let returnValue = Int(string)! // optional Int value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 2번 값의 경우는 강제종료가 일어날 수 있으니 주의해서 사용하셔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 확실하게 변환에 문제가 없다 할 때는 사용할 수 있겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 방법은 다음의 방식이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732942060567&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let returnIntValue = (string as NSString).integerValue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 Objective-C 또는 초기 Swift를 사용하던 분들이라면 익숙할 NSString에 선언 되어있는 integerValue를 통해서 Int 값을 불러오는 것입니다. 이경우는 요즘은 사용하는 경우가 없을 것 같지만, 만에 하나 NSString을 다루는 경우에는 이런 방법도 있다 정도로 알고 계시면 될 것 같네요.&lt;/p&gt;</description>
      <category>Codewars(알고리즘)/8Kyu</category>
      <category>8kyu</category>
      <category>codewars</category>
      <category>Solution</category>
      <category>SWIFT</category>
      <author>Dannian</author>
      <guid isPermaLink="true">https://dragoner.tistory.com/264</guid>
      <comments>https://dragoner.tistory.com/264#entry264comment</comments>
      <pubDate>Sat, 30 Nov 2024 15:00:45 +0900</pubDate>
    </item>
  </channel>
</rss>